blob: 13fe7802481cfd94af4dab71e91e3f23e4939a12 [file] [log] [blame]
/*
* Copyright 2000-2009 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.xdebugger.impl.ui.tree;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.execution.Executor;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.execution.ui.RunContentManager;
import com.intellij.execution.ui.RunContentWithExecutorListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Disposer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author nik
*/
public abstract class TreeInplaceEditor implements AWTEventListener {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.impl.watch.DebuggerTreeInplaceEditor");
private JComponent myInplaceEditorComponent;
private final List<Runnable> myRemoveActions = new ArrayList<Runnable>();
protected abstract JComponent createInplaceEditorComponent();
protected abstract JComponent getPreferredFocusedComponent();
public abstract Editor getEditor();
public abstract JComponent getEditorComponent();
protected abstract TreePath getNodePath();
protected abstract JTree getTree();
public void doOKAction() {
hide();
}
public void cancelEditing() {
hide();
}
private void hide() {
if (!isShown()) {
return;
}
myInplaceEditorComponent = null;
onHidden();
for (Runnable action : myRemoveActions) {
action.run();
}
myRemoveActions.clear();
final JTree tree = getTree();
tree.repaint();
tree.requestFocus();
}
protected void onHidden() {
}
protected abstract Project getProject();
public final void show() {
LOG.assertTrue(myInplaceEditorComponent == null, "editor is not released");
final JTree tree = getTree();
tree.scrollPathToVisible(getNodePath());
final JRootPane rootPane = tree.getRootPane();
if (rootPane == null) {
return;
}
final JLayeredPane layeredPane = rootPane.getLayeredPane();
Rectangle bounds = getEditorBounds();
if (bounds == null) {
return;
}
Point layeredPanePoint = SwingUtilities.convertPoint(tree, bounds.x, bounds.y,layeredPane);
final JComponent inplaceEditorComponent = createInplaceEditorComponent();
myInplaceEditorComponent = inplaceEditorComponent;
LOG.assertTrue(inplaceEditorComponent != null);
inplaceEditorComponent.setBounds(
layeredPanePoint.x,
layeredPanePoint.y,
bounds.width,
Math.max(bounds.height, inplaceEditorComponent.getPreferredSize().height)
);
layeredPane.add(inplaceEditorComponent, new Integer(250));
myRemoveActions.add(new Runnable() {
@Override
public void run() {
layeredPane.remove(inplaceEditorComponent);
}
});
inplaceEditorComponent.validate();
inplaceEditorComponent.paintImmediately(0,0,inplaceEditorComponent.getWidth(),inplaceEditorComponent.getHeight());
getPreferredFocusedComponent().requestFocus();
final ComponentAdapter componentListener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
final Project project = getProject();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (!isShown() || project == null || project.isDisposed()) {
return;
}
JTree tree = getTree();
JLayeredPane layeredPane = tree.getRootPane().getLayeredPane();
Rectangle bounds = getEditorBounds();
if (bounds == null) {
return;
}
Point layeredPanePoint = SwingUtilities.convertPoint(tree, bounds.x, bounds.y, layeredPane);
inplaceEditorComponent.setBounds(layeredPanePoint.x, layeredPanePoint.y, bounds.width, bounds.height);
inplaceEditorComponent.revalidate();
}
});
}
@Override
public void componentHidden(ComponentEvent e) {
cancelEditing();
}
};
final HierarchyListener hierarchyListener = new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if (!tree.isShowing()) {
cancelEditing();
}
}
};
tree.addHierarchyListener(hierarchyListener);
tree.addComponentListener(componentListener);
rootPane.addComponentListener(componentListener);
myRemoveActions.add(new Runnable() {
@Override
public void run() {
tree.removeHierarchyListener(hierarchyListener);
tree.removeComponentListener(componentListener);
rootPane.removeComponentListener(componentListener);
}
});
final Disposable disposable = Disposer.newDisposable();
getProject().getMessageBus().connect(disposable).subscribe(RunContentManager.TOPIC, new RunContentWithExecutorListener() {
@Override
public void contentSelected(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor) {
cancelEditing();
}
@Override
public void contentRemoved(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor) {
cancelEditing();
}
});
myRemoveActions.add(new Runnable() {
@Override
public void run() {
disposable.dispose();
}
});
final JComponent editorComponent = getEditorComponent();
editorComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enterStroke");
editorComponent.getActionMap().put("enterStroke", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
doOKAction();
}
});
editorComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escapeStroke");
editorComponent.getActionMap().put("escapeStroke", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
cancelEditing();
}
});
final Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!isShown()) return;
defaultToolkit.addAWTEventListener(TreeInplaceEditor.this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
}
});
myRemoveActions.add(new Runnable() {
@Override
public void run() {
defaultToolkit.removeAWTEventListener(TreeInplaceEditor.this);
}
});
onShown();
}
protected void onShown() {
}
@Override
public void eventDispatched(AWTEvent event) {
if (!isShown()) {
return;
}
MouseEvent mouseEvent = (MouseEvent)event;
if (mouseEvent.getClickCount() == 0 && !(event instanceof MouseWheelEvent)) {
return;
}
final int id = mouseEvent.getID();
if (id != MouseEvent.MOUSE_PRESSED && id != MouseEvent.MOUSE_RELEASED && id != MouseEvent.MOUSE_CLICKED && id != MouseEvent.MOUSE_WHEEL) {
return;
}
final Component sourceComponent = mouseEvent.getComponent();
final Point originalPoint = mouseEvent.getPoint();
final Editor editor = getEditor();
if (editor == null) return;
final LookupImpl activeLookup = (LookupImpl)LookupManager.getInstance(editor.getProject()).getActiveLookup();
if (activeLookup != null){
final Point lookupPoint = SwingUtilities.convertPoint(sourceComponent, originalPoint, activeLookup.getComponent());
if (activeLookup.getComponent().getBounds().contains(lookupPoint)){
return; //mouse click inside lookup
} else {
activeLookup.hide(); //hide popup on mouse position changed
}
}
// do not cancel editing if we click in editor popup
final List<JBPopup> popups = JBPopupFactory.getInstance().getChildPopups(myInplaceEditorComponent);
for (JBPopup popup : popups) {
if (SwingUtilities.isDescendingFrom(sourceComponent, popup.getContent())) {
return;
}
}
final Point point = SwingUtilities.convertPoint(sourceComponent, originalPoint, myInplaceEditorComponent);
if (myInplaceEditorComponent.contains(point)) {
return;
}
final Component componentAtPoint = SwingUtilities.getDeepestComponentAt(sourceComponent, originalPoint.x, originalPoint.y);
for (Component comp = componentAtPoint; comp != null; comp = comp.getParent()) {
if (comp instanceof ComboPopup) {
if (id != MouseEvent.MOUSE_WHEEL) {
doOKAction();
}
return;
}
}
cancelEditing();
}
@Nullable
private Rectangle getEditorBounds() {
final JTree tree = getTree();
Rectangle bounds = tree.getVisibleRect();
Rectangle nodeBounds = tree.getPathBounds(getNodePath());
if (bounds == null || nodeBounds == null) {
return null;
}
bounds.y = nodeBounds.y;
bounds.height = nodeBounds.height;
if(nodeBounds.x > bounds.x) {
bounds.width = bounds.width - nodeBounds.x + bounds.x;
bounds.x = nodeBounds.x;
}
return bounds;
}
public boolean isShown() {
return myInplaceEditorComponent != null;
}
}