diff --git a/platform/platform-impl/src/com/intellij/internal/inspector/UiInspectorAction.java b/platform/platform-impl/src/com/intellij/internal/inspector/UiInspectorAction.java index f4c1acd64ac3..75a098b5906b 100644 --- a/platform/platform-impl/src/com/intellij/internal/inspector/UiInspectorAction.java +++ b/platform/platform-impl/src/com/intellij/internal/inspector/UiInspectorAction.java @@ -15,6 +15,10 @@ */ package com.intellij.internal.inspector; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; +import com.intellij.notification.NotificationsManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.ToggleAction; @@ -25,16 +29,20 @@ import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; import javax.swing.plaf.ColorUIResource; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; +import javax.swing.tree.*; import java.awt.*; import java.awt.event.*; import java.lang.reflect.InvocationTargetException; @@ -42,6 +50,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Vector; /** * User: spLeaner @@ -62,6 +71,12 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { myInspector = new UiInspector(); Toolkit.getDefaultToolkit().addAWTEventListener(myInspector, AWTEvent.MOUSE_EVENT_MASK); } + + UiInspectorNotification[] existing = + NotificationsManager.getNotificationsManager().getNotificationsOfType(UiInspectorNotification.class, null); + if (existing.length == 0) { + Notifications.Bus.notify(new UiInspectorNotification(), null); + } } else { if (myInspector != null) { @@ -71,41 +86,36 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { } } } + + private static class UiInspectorNotification extends Notification { + private UiInspectorNotification() { + super(Notifications.SYSTEM_MESSAGES_GROUP_ID, "UI Inspector", "Control-Alt-Click to view component info!", + NotificationType.INFORMATION); + } + } private static class InspectorWindow extends JFrame { private InspectorTable myInspectorTable; private UiInspector myUiInspector; private JComponent myComponent; - private boolean myHighlighted = false; + private boolean myHighlighted = true; private HighlightComponent myHighlightComponent; + private HierarchyTree myHierarchyTree; + private final JPanel myWrapperPanel; private InspectorWindow(@NotNull final JComponent component, UiInspector uiInspector) throws HeadlessException { myComponent = component; myUiInspector = uiInspector; getRootPane().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setLayout(new BorderLayout()); final String simpleName = component.getClass().getSimpleName(); setTitle(simpleName.length() == 0 ? component.getClass().getName() : simpleName); - JToolBar bar = new JToolBar(JToolBar.HORIZONTAL); + JToolBar bar = new JToolBar(SwingConstants.HORIZONTAL); bar.setFloatable(false); - bar.add(new AbstractAction("Show parent", IconLoader.getIcon("/nodes/parentsIntroduction.png")) { - public void actionPerformed(ActionEvent e) { - Container parent = component.getParent(); - if (parent instanceof JComponent) { - myUiInspector.showInspector((JComponent) parent); - } - } - - @Override - public boolean isEnabled() { - return myComponent.getParent() != null; - } - }); - bar.add(new AbstractAction("Highlight", IconLoader.getIcon("/toolbar/unknown.png")) { public void actionPerformed(ActionEvent e) { myHighlighted = !myHighlighted; @@ -122,7 +132,7 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { bar.add(new AbstractAction("Refresh", IconLoader.getIcon("/vcs/refresh.png")) { public void actionPerformed(ActionEvent e) { - myInspectorTable.refresh(); + getCurrentTable().refresh(); } @Override @@ -133,9 +143,31 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { add(bar, BorderLayout.NORTH); + + myWrapperPanel = new JPanel(new BorderLayout()); + add(new JLabel(component.getClass().getName()), BorderLayout.SOUTH); myInspectorTable = new InspectorTable(component); - add(myInspectorTable, BorderLayout.CENTER); + myHierarchyTree = new HierarchyTree(component) { + @Override + public void onComponentChanged(Component c) { + highlight(myComponent, true); + switchInfo(c); + if (myHighlighted) highlight(c, false); + } + }; + + myWrapperPanel.add(myInspectorTable, BorderLayout.CENTER); + + JSplitPane splitPane = new JSplitPane(); + splitPane.setDividerLocation(0.5); + splitPane.setRightComponent(myWrapperPanel); + + JScrollPane pane = new JScrollPane(myHierarchyTree); + splitPane.setLeftComponent(pane); + add(splitPane, BorderLayout.CENTER); + + myHierarchyTree.expandPath(); addWindowListener(new WindowAdapter() { @Override @@ -157,6 +189,18 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "CLOSE"); } + private InspectorTable getCurrentTable() { + return myInspectorTable; + } + + private void switchInfo(Component c) { + myInspectorTable = new InspectorTable(c); + myWrapperPanel.removeAll(); + myWrapperPanel.add(myInspectorTable, BorderLayout.CENTER); + myWrapperPanel.revalidate(); + myWrapperPanel.repaint(); + } + public void close() { highlight(myComponent, true); myComponent = null; @@ -193,7 +237,150 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { return myComponent; } } + + private static class ComponentTreeCellRenderer extends JLabel implements TreeCellRenderer { + private ComponentTreeCellRenderer() { + setOpaque(true); + setFont(UIUtil.getLabelFont().deriveFont(11f)); + setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3)); + } + @Override + public Component getTreeCellRendererComponent(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + Color foreground = selected ? UIUtil.getTreeSelectionForeground() : UIUtil.getTreeForeground(); + Color background = selected ? UIUtil.getTreeSelectionBackground() : null; + if (value instanceof HierarchyTree.ComponentNode) { + HierarchyTree.ComponentNode componentNode = (HierarchyTree.ComponentNode)value; + Component component = componentNode.getOwnComponent(); + String name = component.getClass().getSimpleName(); + if (name.length() == 0) { + name = component.getClass().getSuperclass().getSimpleName(); + } + + if (!selected) { + if (!component.isVisible()) { + foreground = Color.GRAY; + } else if (component.getWidth() == 0 || component.getHeight() == 0) { + foreground = new Color(128, 10, 0); + } + + if (componentNode.getToSelect() == componentNode.getOwnComponent()) { + background = new Color(31, 128, 8, 58); + } + } + setText(name); + } + + setForeground(foreground); + setBackground(background); + + return this; + } + } + + private abstract static class HierarchyTree extends JTree implements TreeSelectionListener { + private final JComponent myComponent; + + private HierarchyTree(JComponent c) { + myComponent = c; + setModel(buildModel(c)); + setCellRenderer(new ComponentTreeCellRenderer()); + getSelectionModel().addTreeSelectionListener(this); + } + + public void expandPath() { + TreeUtil.expandAll(this); + int count = getRowCount(); + ComponentNode node = new ComponentNode(myComponent, myComponent.getParent()); + + for (int i = 0; i < count; i++) { + TreePath row = getPathForRow(i); + if (row.getLastPathComponent().equals(node)) { + setSelectionPath(row); + scrollPathToVisible(getSelectionPath()); + break; + } + } + } + + @Override + public void valueChanged(TreeSelectionEvent e) { + TreePath path = e.getNewLeadSelectionPath(); + Object component = path.getLastPathComponent(); + if (component instanceof ComponentNode) { + Component c = ((ComponentNode)component).getOwnComponent(); + onComponentChanged(c); + } + } + + private static TreeModel buildModel(JComponent c) { + return new DefaultTreeModel(new ComponentNode(c, null)); + } + + public abstract void onComponentChanged(Component c); + + private static class ComponentNode extends DefaultMutableTreeNode { + private final Component myParent; + private final Component myToSelect; + + private ComponentNode(Object userObject, Component parent) { + super(userObject); + myParent = parent == null ? SwingUtilities.getWindowAncestor((Component) userObject) : parent; + myToSelect = (Component)userObject; + children = prepareChildren(myToSelect, parent); + } + + public Component getOwnComponent() { + return myParent; + } + + public Component getToSelect() { + return myToSelect; + } + + @Override + public String toString() { + return myParent.getClass().getName(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ComponentNode && ((ComponentNode)obj).getOwnComponent() == getOwnComponent(); + } + + @SuppressWarnings("UseOfObsoleteCollectionType") + private static Vector prepareChildren(Component toSelect, Component parent) { + Vector result = new Vector(); + if (parent == null) { + Container root = toSelect.getParent(); + while (true) { + Container _superRoot = root.getParent(); + if (_superRoot == null) break; + root = _superRoot; + } + + for (Component component : root.getComponents()) { + result.add(new ComponentNode(toSelect, component)); + } + } else { + if (parent instanceof Container) { + for (Component component : ((Container)parent).getComponents()) { + result.add(new ComponentNode(toSelect, component)); + } + } + } + + return result; + } + } + } + private static class HighlightComponent extends JComponent { private Color myColor; @@ -226,7 +413,7 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { private InspectorTableModel myModel; private DimensionsComponent myDimensionComponent; - private InspectorTable(@NotNull final JComponent component) { + private InspectorTable(@NotNull final Component component) { setLayout(new BorderLayout()); myModel = new InspectorTableModel(component); @@ -258,13 +445,13 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { } private static class DimensionsComponent extends JComponent { - private JComponent myComponent; + private Component myComponent; private int myWidth; private int myHeight; private Border myBorder; private Insets myInsets; - private DimensionsComponent(@NotNull final JComponent component) { + private DimensionsComponent(@NotNull final Component component) { myComponent = component; setOpaque(true); setBackground(Color.WHITE); @@ -278,8 +465,10 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { public void update() { myWidth = myComponent.getWidth(); myHeight = myComponent.getHeight(); - myBorder = myComponent.getBorder(); - myInsets = myComponent.getInsets(); + if (myComponent instanceof JComponent) { + myBorder = ((JComponent)myComponent).getBorder(); + myInsets = ((JComponent)myComponent).getInsets(); + } } @Override @@ -485,17 +674,17 @@ public class UiInspectorAction extends ToggleAction implements DumbAware { "isShowing", "isEnabled", "isLightweight", "isFocusable", "isFocusOwner" }; - private JComponent myComponent; + private Component myComponent; private List myProperties = new ArrayList(); - public InspectorTableModel(@NotNull final JComponent c) { + public InspectorTableModel(@NotNull final Component c) { myComponent = c; fillTable(); } private void fillTable() { - final Class cls = myComponent.getClass(); + final Class cls = myComponent.getClass(); for (final String methodName: JCOMPONENT_METHODS) { try { final Method method = cls.getMethod(methodName);