blob: 0830e9b8b22a65e81656e35c18a3c1a95eadda66 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.impl;
import com.intellij.ide.DataManager;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.projectView.impl.ProjectRootsUtil;
import com.intellij.ide.structureView.*;
import com.intellij.ide.structureView.impl.StructureViewComposite;
import com.intellij.ide.structureView.newStructureView.StructureViewComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorProvider;
import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
import com.intellij.openapi.fileEditor.impl.EditorWindow;
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
import com.intellij.openapi.module.InternalModuleType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.PersistentFSConstants;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.ex.ToolWindowEx;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.ui.content.*;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.util.List;
/**
* @author Eugene Belyaev
*/
public class StructureViewWrapperImpl implements StructureViewWrapper, Disposable {
private final Project myProject;
private final ToolWindowEx myToolWindow;
private VirtualFile myFile;
private StructureView myStructureView;
private FileEditor myFileEditor;
private ModuleStructureComponent myModuleStructureComponent;
private JPanel[] myPanels = new JPanel[0];
private final MergingUpdateQueue myUpdateQueue;
private final String myKey = new String("DATA_SELECTOR");
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
private Runnable myPendingSelection;
private boolean myFirstRun = true;
public StructureViewWrapperImpl(Project project, ToolWindowEx toolWindow) {
myProject = project;
myToolWindow = toolWindow;
myUpdateQueue = new MergingUpdateQueue("StructureView", Registry.intValue("structureView.coalesceTime"), false, myToolWindow.getComponent(), this, myToolWindow.getComponent(), true);
myUpdateQueue.setRestartTimerOnAdd(true);
final TimerListener timerListener = new TimerListener() {
@Override
public ModalityState getModalityState() {
return ModalityState.stateForComponent(myToolWindow.getComponent());
}
@Override
public void run() {
checkUpdate();
}
};
ActionManager.getInstance().addTimerListener(500, timerListener);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
ActionManager.getInstance().removeTimerListener(timerListener);
}
});
myToolWindow.getComponent().addHierarchyListener(new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
scheduleRebuild();
}
}
});
myToolWindow.getContentManager().addContentManagerListener(new ContentManagerAdapter() {
@Override
public void selectionChanged(ContentManagerEvent event) {
if (myStructureView instanceof StructureViewComposite) {
StructureViewComposite.StructureViewDescriptor[] views = ((StructureViewComposite)myStructureView).getStructureViews();
for (StructureViewComposite.StructureViewDescriptor view : views) {
if (view.title.equals(event.getContent().getTabName())) {
updateHeaderActions(view.structureView);
break;
}
}
}
}
});
Disposer.register(myToolWindow.getContentManager(), this);
}
private void checkUpdate() {
if (myProject.isDisposed()) return;
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
final boolean insideToolwindow = SwingUtilities.isDescendingFrom(myToolWindow.getComponent(), owner);
if (!myFirstRun && (insideToolwindow || JBPopupFactory.getInstance().isPopupActive())) {
return;
}
final DataContext dataContext = DataManager.getInstance().getDataContext(owner);
if (dataContext.getData(myKey) == this) return;
if (CommonDataKeys.PROJECT.getData(dataContext) != myProject) return;
final VirtualFile[] files = hasFocus() ? null : CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
if (!myToolWindow.isVisible()) {
if (files != null && files.length > 0) {
myFile = files[0];
}
return;
}
if (files != null && files.length == 1) {
setFile(files[0]);
}
else if (files != null && files.length > 1) {
setFile(null);
} else if (myFirstRun) {
final FileEditorManagerImpl editorManager = (FileEditorManagerImpl)FileEditorManager.getInstance(myProject);
final List<Pair<VirtualFile,EditorWindow>> history = editorManager.getSelectionHistory();
if (! history.isEmpty()) {
setFile(history.get(0).getFirst());
}
}
myFirstRun = false;
}
private boolean hasFocus() {
final JComponent tw = myToolWindow.getComponent();
Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
while (owner != null) {
if (owner == tw) return true;
owner = owner.getParent();
}
return false;
}
private void setFile(VirtualFile file) {
boolean forceRebuild = !Comparing.equal(file, myFile);
if (!forceRebuild && myStructureView != null) {
StructureViewModel model = myStructureView.getTreeModel();
StructureViewTreeElement treeElement = model.getRoot();
Object value = treeElement.getValue();
if (value == null || value instanceof PsiElement && !((PsiElement)value).isValid()) {
forceRebuild = true;
}
}
if (forceRebuild) {
myFile = file;
scheduleRebuild();
}
}
// -------------------------------------------------------------------------
// StructureView interface implementation
// -------------------------------------------------------------------------
@Override
public void dispose() {
//we don't really need it
//rebuild();
}
@Override
public boolean selectCurrentElement(final FileEditor fileEditor, final VirtualFile file, final boolean requestFocus) {
//todo [kirillk]
// this is dirty hack since some bright minds decided to used different TreeUi every time, so selection may be followed
// by rebuild on completely different instance of TreeUi
Runnable runnable = new Runnable() {
@Override
public void run() {
if (myStructureView != null) {
if (!Comparing.equal(myFileEditor, fileEditor)) {
myFile = file;
rebuild();
}
myStructureView.navigateToSelectedElement(requestFocus);
}
}
};
if (isStructureViewShowing()) {
if (myUpdateQueue.isEmpty()) {
runnable.run();
} else {
myPendingSelection = runnable;
}
} else {
myPendingSelection = runnable;
}
return true;
}
private void scheduleRebuild() {
myUpdateQueue.queue(new Update("rebuild") {
@Override
public void run() {
if (myProject.isDisposed()) return;
rebuild();
}
});
}
public void rebuild() {
if (myProject.isDisposed()) return;
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
Dimension referenceSize = null;
if (myStructureView != null) {
if (myStructureView instanceof StructureView.Scrollable) {
referenceSize = ((StructureView.Scrollable)myStructureView).getCurrentSize();
}
myStructureView.storeState();
Disposer.dispose(myStructureView);
myStructureView = null;
myFileEditor = null;
}
if (myModuleStructureComponent != null) {
Disposer.dispose(myModuleStructureComponent);
myModuleStructureComponent = null;
}
final ContentManager contentManager = myToolWindow.getContentManager();
contentManager.removeAllContents(true);
if (!isStructureViewShowing()) {
return;
}
VirtualFile file = myFile;
if (file == null) {
final VirtualFile[] selectedFiles = FileEditorManager.getInstance(myProject).getSelectedFiles();
if (selectedFiles.length > 0) {
file = selectedFiles[0];
}
}
String[] names = {""};
if (file != null && file.isValid()) {
if (file.isDirectory()) {
if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) {
Module module = ModuleUtilCore.findModuleForFile(file, myProject);
if (module != null && !(ModuleUtil.getModuleType(module) instanceof InternalModuleType)) {
myModuleStructureComponent = new ModuleStructureComponent(module);
createSinglePanel(myModuleStructureComponent.getComponent());
Disposer.register(this, myModuleStructureComponent);
}
}
}
else {
FileEditor editor = FileEditorManager.getInstance(myProject).getSelectedEditor(file);
boolean needDisposeEditor = false;
if (editor == null) {
editor = createTempFileEditor(file);
needDisposeEditor = true;
}
if (editor != null && editor.isValid()) {
final StructureViewBuilder structureViewBuilder = editor.getStructureViewBuilder();
if (structureViewBuilder != null) {
myStructureView = structureViewBuilder.createStructureView(editor, myProject);
myFileEditor = editor;
Disposer.register(this, myStructureView);
updateHeaderActions(myStructureView);
if (myStructureView instanceof StructureView.Scrollable) {
((StructureView.Scrollable)myStructureView).setReferenceSizeWhileInitializing(referenceSize);
}
if (myStructureView instanceof StructureViewComposite) {
final StructureViewComposite composite = (StructureViewComposite)myStructureView;
final StructureViewComposite.StructureViewDescriptor[] views = composite.getStructureViews();
myPanels = new JPanel[views.length];
names = new String[views.length];
for (int i = 0; i < myPanels.length; i++) {
myPanels[i] = createContentPanel(views[i].structureView.getComponent());
names[i] = views[i].title;
}
}
else {
createSinglePanel(myStructureView.getComponent());
}
myStructureView.restoreState();
myStructureView.centerSelectedRow();
}
}
if (needDisposeEditor && editor != null) {
Disposer.dispose(editor);
}
}
}
if (myModuleStructureComponent == null && myStructureView == null) {
createSinglePanel(new JLabel(IdeBundle.message("message.nothing.to.show.in.structure.view"), SwingConstants.CENTER));
}
for (int i = 0; i < myPanels.length; i++) {
final Content content = ContentFactory.SERVICE.getInstance().createContent(myPanels[i], names[i], false);
contentManager.addContent(content);
if (i == 0 && myStructureView != null) {
Disposer.register(content, myStructureView);
}
}
if (myPendingSelection != null) {
Runnable selection = myPendingSelection;
myPendingSelection = null;
selection.run();
}
}
private void updateHeaderActions(StructureView structureView) {
ActionGroup gearActions = null;
AnAction[] titleActions = AnAction.EMPTY_ARRAY;
if (structureView instanceof StructureViewComponent) {
gearActions = ((StructureViewComponent)structureView).getGearActions();
titleActions = ((StructureViewComponent)structureView).getTitleActions();
}
myToolWindow.setAdditionalGearActions(gearActions);
myToolWindow.setTitleActions(titleActions);
}
private void createSinglePanel(final JComponent component) {
myPanels = new JPanel[1];
myPanels[0] = createContentPanel(component);
}
private ContentPanel createContentPanel(JComponent component) {
final ContentPanel panel = new ContentPanel();
panel.setBackground(UIUtil.getTreeTextBackground());
panel.add(component, BorderLayout.CENTER);
return panel;
}
@Nullable
private FileEditor createTempFileEditor(@NotNull VirtualFile file) {
if (file.getLength() > PersistentFSConstants.getMaxIntellisenseFileSize()) return null;
FileEditorProviderManager editorProviderManager = FileEditorProviderManager.getInstance();
final FileEditorProvider[] providers = editorProviderManager.getProviders(myProject, file);
return providers.length == 0 ? null : providers[0].createEditor(myProject, file);
}
protected boolean isStructureViewShowing() {
ToolWindowManager windowManager = ToolWindowManager.getInstance(myProject);
ToolWindow toolWindow = windowManager.getToolWindow(ToolWindowId.STRUCTURE_VIEW);
// it means that window is registered
return toolWindow != null && toolWindow.isVisible();
}
private class ContentPanel extends JPanel implements DataProvider {
public ContentPanel() {
super(new BorderLayout());
}
@Override
public Object getData(@NonNls String dataId) {
if (dataId.equals(myKey)) return StructureViewWrapperImpl.this;
return null;
}
}
}