@Override
 public Object getData(@NonNls String dataId) {
   if (CommonDataKeys.PROJECT.is(dataId)) {
     return myModel.getProject();
   } else if (DIR_DIFF_MODEL.is(dataId)) {
     return myModel;
   } else if (DIR_DIFF_TABLE.is(dataId)) {
     return myTable;
   } else if (DiffDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
     return getNavigatableArray();
   } else if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
     return myPrevNextDifferenceIterable;
   }
   return null;
 }
 @Override
 public void calcData(final DataKey key, final DataSink sink) {
   if (key.equals(LangDataKeys.PSI_ELEMENT)) {
     if (mySelectedElements != null && !mySelectedElements.isEmpty()) {
       T selectedElement = mySelectedElements.iterator().next();
       if (selectedElement instanceof ClassMemberWithElement) {
         sink.put(
             LangDataKeys.PSI_ELEMENT, ((ClassMemberWithElement) selectedElement).getElement());
       }
     }
   }
 }
 public void calcData(DataKey key, DataSink sink) {
   if (OpenFileDescriptor.NAVIGATE_IN_EDITOR == key) {
     sink.put(OpenFileDescriptor.NAVIGATE_IN_EDITOR, myConsoleEditor);
     return;
   } else if (getProject().isInitialized()) {
     FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
     final Object o =
         ((FileEditorManagerImpl) editorManager)
             .getData(key.getName(), myConsoleEditor, myVirtualFile);
     sink.put(key, o);
   }
 }
  @Override
  public Object getData(String dataId) {
    if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID;
    if (DATA_KEY.is(dataId)) return this;
    if (myTree == null) return null;
    TreePath[] paths = myTree.getSelectionPaths();

    if (paths == null || paths.length == 0) return null;

    if (paths.length > 1) {
      if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
        return collectPsiElements();
      }
      return null;
    }

    TreePath path = paths[0];

    InspectionTreeNode selectedNode = (InspectionTreeNode) path.getLastPathComponent();

    if (selectedNode instanceof RefElementNode) {
      final RefElementNode refElementNode = (RefElementNode) selectedNode;
      RefEntity refElement = refElementNode.getElement();
      if (refElement == null) return null;
      final RefEntity item = refElement.getRefManager().getRefinedElement(refElement);

      if (!item.isValid()) return null;

      PsiElement psiElement = item instanceof RefElement ? ((RefElement) item).getElement() : null;
      if (psiElement == null) return null;

      final CommonProblemDescriptor problem = refElementNode.getProblem();
      if (problem != null) {
        if (problem instanceof ProblemDescriptor) {
          psiElement = ((ProblemDescriptor) problem).getPsiElement();
          if (psiElement == null) return null;
        } else {
          return null;
        }
      }

      if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
        return getSelectedNavigatable(problem, psiElement);
      } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
        return psiElement.isValid() ? psiElement : null;
      }
    } else if (selectedNode instanceof ProblemDescriptionNode
        && CommonDataKeys.NAVIGATABLE.is(dataId)) {
      return getSelectedNavigatable(((ProblemDescriptionNode) selectedNode).getDescriptor());
    }

    return null;
  }
Exemple #5
0
 @Override
 @Nullable
 public Object getData(@NonNls final String dataId) {
   if (XDEBUGGER_TREE_KEY.is(dataId)) {
     return this;
   }
   if (PlatformDataKeys.PREDEFINED_TEXT.is(dataId)) {
     XValueNodeImpl[] selectedNodes = getSelectedNodes(XValueNodeImpl.class, null);
     if (selectedNodes.length == 1 && selectedNodes[0].getFullValueEvaluator() == null) {
       return DebuggerUIUtil.getNodeRawValue(selectedNodes[0]);
     }
   }
   return null;
 }
    @Override
    public void calcData(final DataKey key, final DataSink sink) {
      Node node = getSelectedNode();

      if (key == PlatformDataKeys.PROJECT) {
        sink.put(PlatformDataKeys.PROJECT, myProject);
      } else if (key == USAGE_VIEW_KEY) {
        sink.put(USAGE_VIEW_KEY, UsageViewImpl.this);
      } else if (key == PlatformDataKeys.NAVIGATABLE_ARRAY) {
        sink.put(PlatformDataKeys.NAVIGATABLE_ARRAY, getNavigatablesForNodes(getSelectedNodes()));
      } else if (key == PlatformDataKeys.EXPORTER_TO_TEXT_FILE) {
        sink.put(PlatformDataKeys.EXPORTER_TO_TEXT_FILE, myTextFileExporter);
      } else if (key == USAGES_KEY) {
        final Set<Usage> selectedUsages = getSelectedUsages();
        sink.put(
            USAGES_KEY,
            selectedUsages != null
                ? selectedUsages.toArray(new Usage[selectedUsages.size()])
                : null);
      } else if (key == USAGE_TARGETS_KEY) {
        sink.put(USAGE_TARGETS_KEY, getSelectedUsageTargets());
      } else if (key == PlatformDataKeys.VIRTUAL_FILE_ARRAY) {
        final Set<Usage> usages = getSelectedUsages();
        Usage[] ua = usages != null ? usages.toArray(new Usage[usages.size()]) : null;
        VirtualFile[] data = UsageDataUtil.provideVirtualFileArray(ua, getSelectedUsageTargets());
        sink.put(PlatformDataKeys.VIRTUAL_FILE_ARRAY, data);
      } else if (key == PlatformDataKeys.HELP_ID) {
        sink.put(PlatformDataKeys.HELP_ID, HELP_ID);
      } else if (key == PlatformDataKeys.COPY_PROVIDER) {
        sink.put(PlatformDataKeys.COPY_PROVIDER, this);
      } else if (node != null) {
        Object userObject = node.getUserObject();
        if (userObject instanceof TypeSafeDataProvider) {
          ((TypeSafeDataProvider) userObject).calcData(key, sink);
        } else if (userObject instanceof DataProvider) {
          DataProvider dataProvider = (DataProvider) userObject;
          Object data = dataProvider.getData(key.getName());
          if (data != null) {
            sink.put(key, data);
          }
        }
      }
    }
 @Override
 @Nullable
 public Object getData(String dataId) {
   if (DATA_KEY.is(dataId)) {
     return this;
   }
   final SimpleNode simpleNode = getTree().getSelectedNode();
   if (simpleNode instanceof AbstractDomElementNode) {
     final DomElement domElement = ((AbstractDomElementNode) simpleNode).getDomElement();
     if (domElement != null && domElement.isValid()) {
       if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
         final XmlElement tag = domElement.getXmlElement();
         if (tag instanceof Navigatable) {
           return tag;
         }
       }
     }
   }
   return null;
 }
Exemple #8
0
/** @author nik */
public class XDebuggerTree extends DnDAwareTree implements DataProvider, Disposable {
  private final TransferToEDTQueue<Runnable> myLaterInvocator =
      TransferToEDTQueue.createRunnableMerger("XDebuggerTree later invocator", 100);

  private final ComponentListener myMoveListener =
      new ComponentAdapter() {
        @Override
        public void componentMoved(ComponentEvent e) {
          repaint(); // needed to repaint links in cell renderer on horizontal scrolling
        }
      };

  private static final DataKey<XDebuggerTree> XDEBUGGER_TREE_KEY = DataKey.create("xdebugger.tree");
  private final SingleAlarm myAlarm =
      new SingleAlarm(
          new Runnable() {
            @Override
            public void run() {
              final Editor editor =
                  FileEditorManager.getInstance(myProject).getSelectedTextEditor();
              if (editor != null) {
                editor.getContentComponent().revalidate();
                editor.getContentComponent().repaint();
              }
            }
          },
          100,
          this);

  private static final Convertor<TreePath, String> SPEED_SEARCH_CONVERTER =
      new Convertor<TreePath, String>() {
        @Override
        public String convert(TreePath o) {
          String text = null;
          if (o != null) {
            final Object node = o.getLastPathComponent();
            if (node instanceof XDebuggerTreeNode) {
              text = ((XDebuggerTreeNode) node).getText().toString();
            }
          }
          return StringUtil.notNullize(text);
        }
      };

  private static final TransferHandler DEFAULT_TRANSFER_HANDLER =
      new TransferHandler() {
        @Override
        protected Transferable createTransferable(JComponent c) {
          if (!(c instanceof XDebuggerTree)) {
            return null;
          }
          XDebuggerTree tree = (XDebuggerTree) c;
          //noinspection deprecation
          TreePath[] selectedPaths = tree.getSelectionPaths();
          if (selectedPaths == null || selectedPaths.length == 0) {
            return null;
          }

          StringBuilder plainBuf = new StringBuilder();
          StringBuilder htmlBuf = new StringBuilder();
          htmlBuf.append("<html>\n<body>\n<ul>\n");
          TextTransferable.ColoredStringBuilder coloredTextContainer =
              new TextTransferable.ColoredStringBuilder();
          for (TreePath path : selectedPaths) {
            htmlBuf.append("  <li>");
            Object node = path.getLastPathComponent();
            if (node != null) {
              if (node instanceof XDebuggerTreeNode) {
                ((XDebuggerTreeNode) node).appendToComponent(coloredTextContainer);
                coloredTextContainer.appendTo(plainBuf, htmlBuf);
              } else {
                String text = node.toString();
                plainBuf.append(text);
                htmlBuf.append(text);
              }
            }
            plainBuf.append('\n');
            htmlBuf.append("</li>\n");
          }

          // remove the last newline
          plainBuf.setLength(plainBuf.length() - 1);
          htmlBuf.append("</ul>\n</body>\n</html>");
          return new TextTransferable(htmlBuf.toString(), plainBuf.toString());
        }

        @Override
        public int getSourceActions(JComponent c) {
          return COPY;
        }
      };

  private final DefaultTreeModel myTreeModel;
  private final Project myProject;
  private final XDebuggerEditorsProvider myEditorsProvider;
  private XSourcePosition mySourcePosition;
  private final List<XDebuggerTreeListener> myListeners =
      ContainerUtil.createLockFreeCopyOnWriteList();
  private final XValueMarkers<?, ?> myValueMarkers;

  public XDebuggerTree(
      final @NotNull Project project,
      final @NotNull XDebuggerEditorsProvider editorsProvider,
      final @Nullable XSourcePosition sourcePosition,
      final @NotNull String popupActionGroupId,
      @Nullable XValueMarkers<?, ?> valueMarkers) {
    myValueMarkers = valueMarkers;
    myProject = project;
    myEditorsProvider = editorsProvider;
    mySourcePosition = sourcePosition;
    myTreeModel = new DefaultTreeModel(null);
    setModel(myTreeModel);
    setCellRenderer(new XDebuggerTreeRenderer());
    new TreeLinkMouseListener(new XDebuggerTreeRenderer()) {
      @Override
      protected boolean doCacheLastNode() {
        return false;
      }

      @Override
      protected void handleTagClick(@Nullable Object tag, @NotNull MouseEvent event) {
        if (tag instanceof XDebuggerTreeNodeHyperlink) {
          ((XDebuggerTreeNodeHyperlink) tag).onClick(event);
        }
      }
    }.installOn(this);
    setRootVisible(false);
    setShowsRootHandles(true);

    new DoubleClickListener() {
      @Override
      protected boolean onDoubleClick(MouseEvent e) {
        return expandIfEllipsis();
      }
    }.installOn(this);

    addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            int key = e.getKeyCode();
            if (key == KeyEvent.VK_ENTER || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_RIGHT) {
              expandIfEllipsis();
            }
          }
        });

    if (Boolean.valueOf(System.getProperty("xdebugger.variablesView.rss"))) {
      new XDebuggerTreeSpeedSearch(this, SPEED_SEARCH_CONVERTER);
    } else {
      new TreeSpeedSearch(this, SPEED_SEARCH_CONVERTER);
    }

    final ActionManager actionManager = ActionManager.getInstance();
    addMouseListener(
        new PopupHandler() {
          @Override
          public void invokePopup(final Component comp, final int x, final int y) {
            ActionGroup group = (ActionGroup) actionManager.getAction(popupActionGroupId);
            actionManager
                .createActionPopupMenu(ActionPlaces.UNKNOWN, group)
                .getComponent()
                .show(comp, x, y);
          }
        });
    registerShortcuts();

    setTransferHandler(DEFAULT_TRANSFER_HANDLER);

    addComponentListener(myMoveListener);
  }

  public void updateEditor() {
    myAlarm.cancelAndRequest();
  }

  public boolean isUnderRemoteDebug() {
    // FIXME [VISTALL] we can access to RemoteRunProfile from platform - java specific
    // DataContext context = DataManager.getInstance().getDataContext(this);
    // ExecutionEnvironment env = LangDataKeys.EXECUTION_ENVIRONMENT.getData(context);
    // if (env != null && env.getRunProfile() instanceof RemoteRunProfile) {
    //  return true;
    // }
    return false;
  }

  private boolean expandIfEllipsis() {
    MessageTreeNode[] treeNodes = getSelectedNodes(MessageTreeNode.class, null);
    if (treeNodes.length == 1) {
      MessageTreeNode node = treeNodes[0];
      if (node.isEllipsis()) {
        TreeNode parent = node.getParent();
        if (parent instanceof XValueContainerNode) {
          ((XValueContainerNode) parent).startComputingChildren();
          return true;
        }
      }
    }
    return false;
  }

  public void addTreeListener(@NotNull XDebuggerTreeListener listener) {
    myListeners.add(listener);
  }

  public void removeTreeListener(@NotNull XDebuggerTreeListener listener) {
    myListeners.remove(listener);
  }

  public void setRoot(XDebuggerTreeNode root, final boolean rootVisible) {
    setRootVisible(rootVisible);
    myTreeModel.setRoot(root);
  }

  public XDebuggerTreeNode getRoot() {
    return (XDebuggerTreeNode) myTreeModel.getRoot();
  }

  @Nullable
  public XSourcePosition getSourcePosition() {
    return mySourcePosition;
  }

  public void setSourcePosition(final @Nullable XSourcePosition sourcePosition) {
    mySourcePosition = sourcePosition;
  }

  @NotNull
  public XDebuggerEditorsProvider getEditorsProvider() {
    return myEditorsProvider;
  }

  @NotNull
  public Project getProject() {
    return myProject;
  }

  @Nullable
  public XValueMarkers<?, ?> getValueMarkers() {
    return myValueMarkers;
  }

  public DefaultTreeModel getTreeModel() {
    return myTreeModel;
  }

  @Override
  @Nullable
  public Object getData(@NonNls final String dataId) {
    if (XDEBUGGER_TREE_KEY.is(dataId)) {
      return this;
    }
    if (PlatformDataKeys.PREDEFINED_TEXT.is(dataId)) {
      XValueNodeImpl[] selectedNodes = getSelectedNodes(XValueNodeImpl.class, null);
      if (selectedNodes.length == 1 && selectedNodes[0].getFullValueEvaluator() == null) {
        return DebuggerUIUtil.getNodeRawValue(selectedNodes[0]);
      }
    }
    return null;
  }

  public void rebuildAndRestore(final XDebuggerTreeState treeState) {
    Object rootNode = myTreeModel.getRoot();
    if (rootNode instanceof XDebuggerTreeNode) {
      ((XDebuggerTreeNode) rootNode).clearChildren();
      treeState.restoreState(this);
      repaint();
    }
  }

  public void childrenLoaded(
      final @NotNull XDebuggerTreeNode node,
      final @NotNull List<XValueContainerNode<?>> children,
      final boolean last) {
    for (XDebuggerTreeListener listener : myListeners) {
      listener.childrenLoaded(node, children, last);
    }
  }

  public void nodeLoaded(final @NotNull RestorableStateNode node, final @NotNull String name) {
    for (XDebuggerTreeListener listener : myListeners) {
      listener.nodeLoaded(node, name);
    }
  }

  public void markNodesObsolete() {
    Object root = myTreeModel.getRoot();
    if (root instanceof XValueContainerNode<?>) {
      markNodesObsolete((XValueContainerNode<?>) root);
    }
  }

  @Override
  public void dispose() {
    ActionManager actionManager = ActionManager.getInstance();
    actionManager.getAction(XDebuggerActions.SET_VALUE).unregisterCustomShortcutSet(this);
    actionManager.getAction(XDebuggerActions.COPY_VALUE).unregisterCustomShortcutSet(this);
    actionManager.getAction(XDebuggerActions.JUMP_TO_SOURCE).unregisterCustomShortcutSet(this);
    actionManager.getAction(XDebuggerActions.JUMP_TO_TYPE_SOURCE).unregisterCustomShortcutSet(this);
    actionManager.getAction(XDebuggerActions.MARK_OBJECT).unregisterCustomShortcutSet(this);

    // clear all possible inner fields that may still have links to debugger objects
    myTreeModel.setRoot(null);
    setCellRenderer(null);
    UIUtil.dispose(this);
    setLeadSelectionPath(null);
    setAnchorSelectionPath(null);
    removeComponentListener(myMoveListener);
  }

  private void registerShortcuts() {
    ActionManager actionManager = ActionManager.getInstance();
    actionManager
        .getAction(XDebuggerActions.SET_VALUE)
        .registerCustomShortcutSet(
            new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)), this);
    actionManager
        .getAction(XDebuggerActions.COPY_VALUE)
        .registerCustomShortcutSet(CommonShortcuts.getCopy(), this);
    actionManager
        .getAction(XDebuggerActions.JUMP_TO_SOURCE)
        .registerCustomShortcutSet(CommonShortcuts.getEditSource(), this);
    Shortcut[] editTypeShortcuts =
        KeymapManager.getInstance()
            .getActiveKeymap()
            .getShortcuts(XDebuggerActions.EDIT_TYPE_SOURCE);
    actionManager
        .getAction(XDebuggerActions.JUMP_TO_TYPE_SOURCE)
        .registerCustomShortcutSet(new CustomShortcutSet(editTypeShortcuts), this);
    actionManager
        .getAction(XDebuggerActions.MARK_OBJECT)
        .registerCustomShortcutSet(
            new CustomShortcutSet(
                KeymapManager.getInstance().getActiveKeymap().getShortcuts("ToggleBookmark")),
            this);
  }

  private static void markNodesObsolete(final XValueContainerNode<?> node) {
    node.setObsolete();
    List<? extends XValueContainerNode<?>> loadedChildren = node.getLoadedChildren();
    if (loadedChildren != null) {
      for (XValueContainerNode<?> child : loadedChildren) {
        markNodesObsolete(child);
      }
    }
  }

  @Nullable
  public static XDebuggerTree getTree(final AnActionEvent e) {
    return e.getData(XDEBUGGER_TREE_KEY);
  }

  @Nullable
  public static XDebuggerTree getTree(DataContext context) {
    return XDEBUGGER_TREE_KEY.getData(context);
  }

  public TransferToEDTQueue<Runnable> getLaterInvocator() {
    return myLaterInvocator;
  }

  public void selectNodeOnLoad(final Condition<TreeNode> nodeFilter) {
    addTreeListener(
        new XDebuggerTreeListener() {
          @Override
          public void nodeLoaded(@NotNull RestorableStateNode node, String name) {
            if (nodeFilter.value(node)) {
              setSelectionPath(node.getPath());
            }
          }

          @Override
          public void childrenLoaded(
              @NotNull XDebuggerTreeNode node,
              @NotNull List<XValueContainerNode<?>> children,
              boolean last) {}
        });
  }

  public void expandNodesOnLoad(final Condition<TreeNode> nodeFilter) {
    addTreeListener(
        new XDebuggerTreeListener() {
          @Override
          public void nodeLoaded(@NotNull RestorableStateNode node, String name) {
            if (nodeFilter.value(node) && !node.isLeaf()) {
              // cause children computing
              node.getChildCount();
            }
          }

          @Override
          public void childrenLoaded(
              @NotNull XDebuggerTreeNode node,
              @NotNull List<XValueContainerNode<?>> children,
              boolean last) {
            if (nodeFilter.value(node)) {
              expandPath(node.getPath());
            }
          }
        });
  }
}
Exemple #9
0
 @Nullable
 public static XDebuggerTree getTree(DataContext context) {
   return XDEBUGGER_TREE_KEY.getData(context);
 }
public class FavoritesTreeViewPanel extends JPanel implements DataProvider {
  @NonNls public static final String ABSTRACT_TREE_NODE_TRANSFERABLE = "AbstractTransferable";

  private final FavoritesTreeStructure myFavoritesTreeStructure;
  private FavoritesViewTreeBuilder myBuilder;
  private final CopyPasteDelegator myCopyPasteDelegator;
  private final MouseListener myTreePopupHandler;

  public static final DataKey<FavoritesTreeNodeDescriptor[]> CONTEXT_FAVORITES_ROOTS_DATA_KEY =
      DataKey.create("FavoritesRoot");

  @Deprecated
  public static final String CONTEXT_FAVORITES_ROOTS = CONTEXT_FAVORITES_ROOTS_DATA_KEY.getName();

  public static final DataKey<String> FAVORITES_LIST_NAME_DATA_KEY =
      DataKey.create("FavoritesListName");

  @Deprecated
  public static final String FAVORITES_LIST_NAME = FAVORITES_LIST_NAME_DATA_KEY.getName();

  protected Project myProject;
  private final String myHelpId;
  protected DnDAwareTree myTree;

  private final MyDeletePSIElementProvider myDeletePSIElementProvider =
      new MyDeletePSIElementProvider();
  private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider();

  private String myListName;
  private final IdeView myIdeView = new MyIdeView();

  public FavoritesTreeViewPanel(Project project, String helpId, String name) {
    super(new BorderLayout());
    myProject = project;
    myHelpId = helpId;
    myListName = name;

    myFavoritesTreeStructure = new FavoritesTreeStructure(project, myListName);
    DefaultMutableTreeNode root = new DefaultMutableTreeNode();
    root.setUserObject(myFavoritesTreeStructure.getRootElement());
    final DefaultTreeModel treeModel = new DefaultTreeModel(root);
    myTree = new DnDAwareTree(treeModel);
    myBuilder =
        new FavoritesViewTreeBuilder(
            myProject, myTree, treeModel, myFavoritesTreeStructure, myListName);

    TreeUtil.installActions(myTree);
    UIUtil.setLineStyleAngled(myTree);
    myTree.setRootVisible(false);
    myTree.setShowsRootHandles(true);
    myTree.setLargeModel(true);
    new TreeSpeedSearch(myTree);
    ToolTipManager.sharedInstance().registerComponent(myTree);
    myTree.setCellRenderer(
        new NodeRenderer() {
          public void customizeCellRenderer(
              JTree tree,
              Object value,
              boolean selected,
              boolean expanded,
              boolean leaf,
              int row,
              boolean hasFocus) {
            super.customizeCellRenderer(tree, value, selected, expanded, leaf, row, hasFocus);
            if (value instanceof DefaultMutableTreeNode) {
              final DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
              // only favorites roots to explain
              final Object userObject = node.getUserObject();
              if (userObject instanceof FavoritesTreeNodeDescriptor) {
                final FavoritesTreeNodeDescriptor favoritesTreeNodeDescriptor =
                    (FavoritesTreeNodeDescriptor) userObject;
                AbstractTreeNode treeNode = favoritesTreeNodeDescriptor.getElement();
                final ItemPresentation presentation = treeNode.getPresentation();
                String locationString = presentation.getLocationString();
                if (locationString != null && locationString.length() > 0) {
                  append(" (" + locationString + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
                } else if (node.getParent() != null && node.getParent().getParent() == null) {
                  final String location = favoritesTreeNodeDescriptor.getLocation();
                  if (location != null && location.length() > 0) {
                    append(" (" + location + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
                  }
                }
              }
            }
          }
        });
    JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
    myTreePopupHandler =
        CustomizationUtil.installPopupHandler(
            myTree, IdeActions.GROUP_FAVORITES_VIEW_POPUP, ActionPlaces.FAVORITES_VIEW_POPUP);
    add(scrollPane, BorderLayout.CENTER);
    // add(createActionsToolbar(), BorderLayout.NORTH);

    EditSourceOnDoubleClickHandler.install(myTree);
    EditSourceOnEnterKeyHandler.install(myTree);
    myCopyPasteDelegator =
        new CopyPasteDelegator(myProject, this) {
          @NotNull
          protected PsiElement[] getSelectedElements() {
            return getSelectedPsiElements();
          }
        };
  }

  public void selectElement(
      final Object selector, final VirtualFile file, final boolean requestFocus) {
    myBuilder.select(selector, file, requestFocus);
  }

  public void dispose() {
    Disposer.dispose(myBuilder);
    myBuilder = null;
  }

  public DnDAwareTree getTree() {
    return myTree;
  }

  public String getName() {
    return myListName;
  }

  public void setName(String name) {
    myListName = name;
  }

  @NotNull
  private PsiElement[] getSelectedPsiElements() {
    final Object[] elements = getSelectedNodeElements();
    if (elements == null) {
      return PsiElement.EMPTY_ARRAY;
    }
    ArrayList<PsiElement> result = new ArrayList<PsiElement>();
    for (Object element : elements) {
      if (element instanceof PsiElement) {
        result.add((PsiElement) element);
      } else if (element instanceof SmartPsiElementPointer) {
        PsiElement psiElement = ((SmartPsiElementPointer) element).getElement();
        if (psiElement != null) {
          result.add(psiElement);
        }
      } else {
        for (FavoriteNodeProvider provider :
            Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, myProject)) {
          final PsiElement psiElement = provider.getPsiElement(element);
          if (psiElement != null) {
            result.add(psiElement);
            break;
          }
        }
      }
    }
    return result.toArray(new PsiElement[result.size()]);
  }

  public FavoritesTreeStructure getFavoritesTreeStructure() {
    return myFavoritesTreeStructure;
  }

  public Object getData(String dataId) {
    if (PlatformDataKeys.PROJECT.is(dataId)) {
      return myProject;
    }
    if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
      final FavoritesTreeNodeDescriptor[] selectedNodeDescriptors = getSelectedNodeDescriptors();
      return selectedNodeDescriptors.length == 1 ? selectedNodeDescriptors[0].getElement() : null;
    }
    if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
      final List<Navigatable> selectedElements = getSelectedElements(Navigatable.class);
      return selectedElements.toArray(new Navigatable[selectedElements.size()]);
    }

    if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCutProvider();
    }
    if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCopyProvider();
    }
    if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getPasteProvider();
    }
    if (PlatformDataKeys.HELP_ID.is(dataId)) {
      return myHelpId;
    }
    if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
      PsiElement[] elements = getSelectedPsiElements();
      if (elements.length != 1) {
        return null;
      }
      return elements[0] != null && elements[0].isValid() ? elements[0] : null;
    }
    if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
      final PsiElement[] elements = getSelectedPsiElements();
      ArrayList<PsiElement> result = new ArrayList<PsiElement>();
      for (PsiElement element : elements) {
        if (element.isValid()) {
          result.add(element);
        }
      }
      return result.isEmpty() ? null : result.toArray(new PsiElement[result.size()]);
    }

    if (LangDataKeys.IDE_VIEW.is(dataId)) {
      return myIdeView;
    }

    if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
      return null;
    }

    if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
      Module[] selected = getSelectedModules();
      return selected != null && selected.length == 1 ? selected[0] : null;
    }
    if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
      return getSelectedModules();
    }

    if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
      final Object[] elements = getSelectedNodeElements();
      return elements != null && elements.length >= 1 && elements[0] instanceof Module
          ? myDeleteModuleProvider
          : myDeletePSIElementProvider;
    }
    if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
      final List<ModuleGroup> selectedElements = getSelectedElements(ModuleGroup.class);
      return selectedElements.isEmpty()
          ? null
          : selectedElements.toArray(new ModuleGroup[selectedElements.size()]);
    }
    if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
      final List<LibraryGroupElement> selectedElements =
          getSelectedElements(LibraryGroupElement.class);
      return selectedElements.isEmpty()
          ? null
          : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]);
    }
    if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
      final List<NamedLibraryElement> selectedElements =
          getSelectedElements(NamedLibraryElement.class);
      return selectedElements.isEmpty()
          ? null
          : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]);
    }
    if (CONTEXT_FAVORITES_ROOTS_DATA_KEY.is(dataId)) {
      List<FavoritesTreeNodeDescriptor> result = new ArrayList<FavoritesTreeNodeDescriptor>();
      FavoritesTreeNodeDescriptor[] selectedNodeDescriptors = getSelectedNodeDescriptors();
      for (FavoritesTreeNodeDescriptor selectedNodeDescriptor : selectedNodeDescriptors) {
        FavoritesTreeNodeDescriptor root = selectedNodeDescriptor.getFavoritesRoot();
        if (root != null && !(root.getElement().getValue() instanceof String)) {
          result.add(root);
        }
      }
      return result.toArray(new FavoritesTreeNodeDescriptor[result.size()]);
    }
    if (FAVORITES_LIST_NAME_DATA_KEY.is(dataId)) {
      return myListName;
    }
    FavoritesTreeNodeDescriptor[] descriptors = getSelectedNodeDescriptors();
    if (descriptors.length > 0) {
      List<AbstractTreeNode> nodes = new ArrayList<AbstractTreeNode>();
      for (FavoritesTreeNodeDescriptor descriptor : descriptors) {
        nodes.add(descriptor.getElement());
      }
      return myFavoritesTreeStructure.getDataFromProviders(nodes, dataId);
    }
    return null;
  }

  private <T> List<T> getSelectedElements(Class<T> klass) {
    final Object[] elements = getSelectedNodeElements();
    ArrayList<T> result = new ArrayList<T>();
    if (elements == null) {
      return result;
    }
    for (Object element : elements) {
      if (element == null) continue;
      if (klass.isAssignableFrom(element.getClass())) {
        result.add((T) element);
      }
    }
    return result;
  }

  private Module[] getSelectedModules() {
    final Object[] elements = getSelectedNodeElements();
    if (elements == null) {
      return null;
    }
    ArrayList<Module> result = new ArrayList<Module>();
    for (Object element : elements) {
      if (element instanceof Module) {
        result.add((Module) element);
      } else if (element instanceof ModuleGroup) {
        result.addAll(((ModuleGroup) element).modulesInGroup(myProject, true));
      }
    }

    return result.isEmpty() ? null : result.toArray(new Module[result.size()]);
  }

  private Object[] getSelectedNodeElements() {
    final FavoritesTreeNodeDescriptor[] selectedNodeDescriptors = getSelectedNodeDescriptors();
    ArrayList<Object> result = new ArrayList<Object>();
    for (FavoritesTreeNodeDescriptor selectedNodeDescriptor : selectedNodeDescriptors) {
      if (selectedNodeDescriptor != null) {
        Object value = selectedNodeDescriptor.getElement().getValue();
        if (value instanceof SmartPsiElementPointer) {
          value = ((SmartPsiElementPointer) value).getElement();
        }
        result.add(value);
      }
    }
    return ArrayUtil.toObjectArray(result);
  }

  @NotNull
  public FavoritesTreeNodeDescriptor[] getSelectedNodeDescriptors() {
    TreePath[] path = myTree.getSelectionPaths();
    if (path == null) {
      return FavoritesTreeNodeDescriptor.EMPTY_ARRAY;
    }
    ArrayList<FavoritesTreeNodeDescriptor> result = new ArrayList<FavoritesTreeNodeDescriptor>();
    for (TreePath treePath : path) {
      DefaultMutableTreeNode lastPathNode =
          (DefaultMutableTreeNode) treePath.getLastPathComponent();
      Object userObject = lastPathNode.getUserObject();
      if (!(userObject instanceof FavoritesTreeNodeDescriptor)) {
        continue;
      }
      FavoritesTreeNodeDescriptor treeNodeDescriptor = (FavoritesTreeNodeDescriptor) userObject;
      result.add(treeNodeDescriptor);
    }
    return result.toArray(new FavoritesTreeNodeDescriptor[result.size()]);
  }

  public static String getQualifiedName(final VirtualFile file) {
    return file.getPresentableUrl();
  }

  public FavoritesViewTreeBuilder getBuilder() {
    return myBuilder;
  }

  private final class MyDeletePSIElementProvider implements DeleteProvider {
    public boolean canDeleteElement(DataContext dataContext) {
      final PsiElement[] elements = getElementsToDelete();
      return DeleteHandler.shouldEnableDeleteAction(elements);
    }

    public void deleteElement(DataContext dataContext) {
      List<PsiElement> allElements = Arrays.asList(getElementsToDelete());
      List<PsiElement> validElements = new ArrayList<PsiElement>();
      for (PsiElement psiElement : allElements) {
        if (psiElement != null && psiElement.isValid()) validElements.add(psiElement);
      }
      final PsiElement[] elements = validElements.toArray(new PsiElement[validElements.size()]);

      LocalHistoryAction a =
          LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
      try {
        DeleteHandler.deletePsiElement(elements, myProject);
      } finally {
        a.finish();
      }
    }

    private PsiElement[] getElementsToDelete() {
      ArrayList<PsiElement> result = new ArrayList<PsiElement>();
      Object[] elements = getSelectedNodeElements();
      for (int idx = 0; elements != null && idx < elements.length; idx++) {
        if (elements[idx] instanceof PsiElement) {
          final PsiElement element = (PsiElement) elements[idx];
          result.add(element);
          if (element instanceof PsiDirectory) {
            final VirtualFile virtualFile = ((PsiDirectory) element).getVirtualFile();
            final String path = virtualFile.getPath();
            if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root
              final VirtualFile vFile =
                  LocalFileSystem.getInstance()
                      .findFileByPath(
                          path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length()));
              if (vFile != null) {
                final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
                if (psiFile != null) {
                  elements[idx] = psiFile;
                }
              }
            }
          }
        }
      }

      return result.toArray(new PsiElement[result.size()]);
    }
  }

  private final class MyIdeView implements IdeView {
    public void selectElement(final PsiElement element) {
      if (element != null) {
        selectPsiElement(element, false);
        boolean requestFocus = true;
        final boolean isDirectory = element instanceof PsiDirectory;
        if (!isDirectory) {
          Editor editor = EditorHelper.openInEditor(element);
          if (editor != null) {
            ToolWindowManager.getInstance(myProject).activateEditorComponent();
            requestFocus = false;
          }
        }
        if (requestFocus) {
          selectPsiElement(element, true);
        }
      }
    }

    private void selectPsiElement(PsiElement element, boolean requestFocus) {
      VirtualFile virtualFile = PsiUtilBase.getVirtualFile(element);
      FavoritesTreeViewPanel.this.selectElement(element, virtualFile, requestFocus);
    }

    @Nullable
    private PsiDirectory[] getSelectedDirectories() {
      if (myBuilder == null) return null;
      final Object[] selectedNodeElements = getSelectedNodeElements();
      if (selectedNodeElements.length != 1) return null;
      for (FavoriteNodeProvider nodeProvider :
          Extensions.getExtensions(FavoriteNodeProvider.EP_NAME, myProject)) {
        final PsiElement psiElement = nodeProvider.getPsiElement(selectedNodeElements[0]);
        if (psiElement instanceof PsiDirectory) {
          return new PsiDirectory[] {(PsiDirectory) psiElement};
        } else if (psiElement instanceof PsiDirectoryContainer) {
          final String moduleName = nodeProvider.getElementModuleName(selectedNodeElements[0]);
          GlobalSearchScope searchScope = GlobalSearchScope.projectScope(myProject);
          if (moduleName != null) {
            final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleName);
            if (module != null) {
              searchScope = GlobalSearchScope.moduleScope(module);
            }
          }
          return ((PsiDirectoryContainer) psiElement).getDirectories(searchScope);
        }
      }
      return selectedNodeElements[0] instanceof PsiDirectory
          ? new PsiDirectory[] {(PsiDirectory) selectedNodeElements[0]}
          : null;
    }

    public PsiDirectory[] getDirectories() {
      final PsiDirectory[] directories = getSelectedDirectories();
      return directories == null ? PsiDirectory.EMPTY_ARRAY : directories;
    }

    public PsiDirectory getOrChooseDirectory() {
      return DirectoryChooserUtil.getOrChooseDirectory(this);
    }
  }
}
Exemple #11
0
/** @author michael.golubev */
public class ServersToolWindowContent extends JPanel implements Disposable {
  public static final DataKey<ServersToolWindowContent> KEY =
      DataKey.create("serversToolWindowContent");
  @NonNls private static final String PLACE_TOOLBAR = "ServersToolWindowContent#Toolbar";
  @NonNls private static final String SERVERS_TOOL_WINDOW_TOOLBAR = "RemoteServersViewToolbar";

  @NonNls private static final String HELP_ID = "Application_Servers_tool_window";
  private static final String MESSAGE_CARD = "message";
  private static final String EMPTY_SELECTION_MESSAGE =
      "Select a server or deployment in the tree to view details";

  private final Tree myTree;
  private final CardLayout myPropertiesPanelLayout;
  private final JPanel myPropertiesPanel;
  private final JLabel myMessageLabel;
  private final Map<String, JComponent> myLogComponents = new HashMap<String, JComponent>();

  private final DefaultTreeModel myTreeModel;
  private TreeBuilderBase myBuilder;
  private AbstractTreeNode<?> myLastSelection;

  private final Project myProject;

  public ServersToolWindowContent(@NotNull Project project) {
    super(new BorderLayout());
    myProject = project;

    myTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
    myTree = new Tree(myTreeModel);
    myTree.setRootVisible(false);

    myTree.setShowsRootHandles(true);
    myTree.setCellRenderer(new NodeRenderer());
    myTree.setLineStyleAngled();

    getMainPanel().add(createToolbar(), BorderLayout.WEST);
    Splitter splitter = new Splitter(false, 0.3f);
    splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT));
    myPropertiesPanelLayout = new CardLayout();
    myPropertiesPanel = new JPanel(myPropertiesPanelLayout);
    myMessageLabel = new JLabel(EMPTY_SELECTION_MESSAGE, SwingConstants.CENTER);
    myPropertiesPanel.add(MESSAGE_CARD, new Wrapper(myMessageLabel));
    splitter.setSecondComponent(myPropertiesPanel);
    getMainPanel().add(splitter, BorderLayout.CENTER);

    setupBuilder(project);

    for (RemoteServersViewContributor contributor :
        RemoteServersViewContributor.EP_NAME.getExtensions()) {
      contributor.setupTree(myProject, myTree, myBuilder);
    }

    myTree.addTreeSelectionListener(
        new TreeSelectionListener() {
          @Override
          public void valueChanged(TreeSelectionEvent e) {
            onSelectionChanged();
          }
        });
    new DoubleClickListener() {
      @Override
      protected boolean onDoubleClick(MouseEvent event) {
        Set<ServersTreeStructure.RemoteServerNode> nodes = getSelectedRemoteServerNodes();
        if (nodes.size() == 1) {
          RemoteServer<?> server = nodes.iterator().next().getValue();
          ServerConnectionManager.getInstance().getOrCreateConnection(server);
          return true;
        }
        return false;
      }
    }.installOn(myTree);
  }

  private void onSelectionChanged() {
    Set<AbstractTreeNode> nodes = myBuilder.getSelectedElements(AbstractTreeNode.class);
    if (nodes.size() != 1) {
      showMessageLabel(EMPTY_SELECTION_MESSAGE);
      myLastSelection = null;
      return;
    }

    AbstractTreeNode<?> node = nodes.iterator().next();
    if (Comparing.equal(node, myLastSelection)) {
      return;
    }

    myLastSelection = node;
    if (node instanceof ServersTreeStructure.LogProvidingNode) {
      ServersTreeStructure.LogProvidingNode logNode = (ServersTreeStructure.LogProvidingNode) node;
      LoggingHandlerImpl loggingHandler = logNode.getLoggingHandler();
      if (loggingHandler != null) {
        String cardName = logNode.getLogId();
        JComponent oldComponent = myLogComponents.get(cardName);
        JComponent logComponent = loggingHandler.getConsole().getComponent();
        if (!logComponent.equals(oldComponent)) {
          myLogComponents.put(cardName, logComponent);
          if (oldComponent != null) {
            myPropertiesPanel.remove(oldComponent);
          }
          myPropertiesPanel.add(cardName, logComponent);
        }
        myPropertiesPanelLayout.show(myPropertiesPanel, cardName);
      } else {
        showMessageLabel("");
      }
    } else if (node instanceof ServersTreeStructure.RemoteServerNode) {
      updateServerDetails((ServersTreeStructure.RemoteServerNode) node);
    } else {
      showMessageLabel("");
    }
  }

  private void updateServerDetails(ServersTreeStructure.RemoteServerNode node) {
    RemoteServer<?> server = ((ServersTreeStructure.RemoteServerNode) node).getValue();
    ServerConnection connection = ServerConnectionManager.getInstance().getConnection(server);
    if (connection == null || connection.getStatus() == ConnectionStatus.DISCONNECTED) {
      showMessageLabel("Double-click on the server node to connect");
    } else {
      showMessageLabel(connection.getStatusText());
    }
  }

  private void showMessageLabel(final String text) {
    myMessageLabel.setText(UIUtil.toHtml(text));
    myPropertiesPanelLayout.show(myPropertiesPanel, MESSAGE_CARD);
  }

  private void setupBuilder(final @NotNull Project project) {
    ServersTreeStructure structure = new ServersTreeStructure(project);
    myBuilder =
        new TreeBuilderBase(myTree, structure, myTreeModel) {
          @Override
          protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) {
            return nodeDescriptor instanceof ServersTreeStructure.RemoteServerNode
                || nodeDescriptor instanceof ServersTreeStructure.DeploymentNodeImpl;
          }
        };
    Disposer.register(this, myBuilder);

    project
        .getMessageBus()
        .connect()
        .subscribe(
            ServerConnectionListener.TOPIC,
            new ServerConnectionListener() {
              @Override
              public void onConnectionCreated(@NotNull ServerConnection<?> connection) {
                getBuilder().queueUpdate();
              }

              @Override
              public void onConnectionStatusChanged(@NotNull ServerConnection<?> connection) {
                getBuilder().queueUpdate();
                updateSelectedServerDetails();
              }

              @Override
              public void onDeploymentsChanged(@NotNull ServerConnection<?> connection) {
                getBuilder().queueUpdate();
                updateSelectedServerDetails();
              }
            });
  }

  private void updateSelectedServerDetails() {
    if (myLastSelection instanceof ServersTreeStructure.RemoteServerNode) {
      updateServerDetails((ServersTreeStructure.RemoteServerNode) myLastSelection);
    }
  }

  private JComponent createToolbar() {
    DefaultActionGroup group = new DefaultActionGroup();
    group.add(ActionManager.getInstance().getAction(SERVERS_TOOL_WINDOW_TOOLBAR));
    group.add(new Separator());
    group.add(new ContextHelpAction(HELP_ID));

    ActionToolbar actionToolBar =
        ActionManager.getInstance().createActionToolbar(PLACE_TOOLBAR, group, false);

    myTree.putClientProperty(
        DataManager.CLIENT_PROPERTY_DATA_PROVIDER,
        new DataProvider() {

          @Override
          public Object getData(@NonNls String dataId) {
            if (KEY.getName().equals(dataId)) {
              return ServersToolWindowContent.this;
            }
            for (RemoteServersViewContributor contributor :
                RemoteServersViewContributor.EP_NAME.getExtensions()) {
              Object data = contributor.getData(dataId, ServersToolWindowContent.this);
              if (data != null) {
                return data;
              }
            }
            return null;
          }
        });
    actionToolBar.setTargetComponent(myTree);
    return actionToolBar.getComponent();
  }

  public JPanel getMainPanel() {
    return this;
  }

  public Set<ServerNode> getSelectedServerNodes() {
    return myBuilder.getSelectedElements(ServerNode.class);
  }

  public Set<DeploymentNode> getSelectedDeploymentNodes() {
    return myBuilder.getSelectedElements(DeploymentNode.class);
  }

  public Set<ServersTreeStructure.RemoteServerNode> getSelectedRemoteServerNodes() {
    return myBuilder.getSelectedElements(ServersTreeStructure.RemoteServerNode.class);
  }

  @Override
  public void dispose() {}

  public TreeBuilderBase getBuilder() {
    return myBuilder;
  }

  @NotNull
  public Project getProject() {
    return myProject;
  }

  public void select(@NotNull final ServerConnection<?> connection) {
    myBuilder.select(
        ServersTreeStructure.RemoteServerNode.class,
        new TreeVisitor<ServersTreeStructure.RemoteServerNode>() {
          @Override
          public boolean visit(@NotNull ServersTreeStructure.RemoteServerNode node) {
            return node.getValue().equals(connection.getServer());
          }
        },
        null,
        false);
  }

  public void select(
      @NotNull final ServerConnection<?> connection, @NotNull final String deploymentName) {
    myBuilder
        .getUi()
        .queueUpdate(connection)
        .doWhenDone(
            new Runnable() {
              @Override
              public void run() {
                myBuilder.select(
                    ServersTreeStructure.DeploymentNodeImpl.class,
                    new TreeVisitor<ServersTreeStructure.DeploymentNodeImpl>() {
                      @Override
                      public boolean visit(@NotNull ServersTreeStructure.DeploymentNodeImpl node) {
                        AbstractTreeNode parent = node.getParent();
                        return parent instanceof ServersTreeStructure.RemoteServerNode
                            && ((ServersTreeStructure.RemoteServerNode) parent)
                                .getValue()
                                .equals(connection.getServer())
                            && node.getValue().getName().equals(deploymentName);
                      }
                    },
                    null,
                    false);
              }
            });
  }
}
public class FileChooserDialogImpl extends DialogWrapper
    implements FileChooserDialog, PathChooserDialog, FileLookup {
  @NonNls public static final String FILE_CHOOSER_SHOW_PATH_PROPERTY = "FileChooser.ShowPath";
  public static final String RECENT_FILES_KEY = "file.chooser.recent.files";
  private final FileChooserDescriptor myChooserDescriptor;
  protected FileSystemTreeImpl myFileSystemTree;
  private Project myProject;
  private VirtualFile[] myChosenFiles = VirtualFile.EMPTY_ARRAY;

  private JPanel myNorthPanel;
  private TextFieldAction myTextFieldAction;
  protected FileTextFieldImpl myPathTextField;
  private JComponent myPathTextFieldWrapper;

  private MergingUpdateQueue myUiUpdater;
  private boolean myTreeIsUpdating;

  public static DataKey<PathField> PATH_FIELD = DataKey.create("PathField");

  public FileChooserDialogImpl(
      @NotNull final FileChooserDescriptor descriptor, @Nullable Project project) {
    super(project, true);
    myChooserDescriptor = descriptor;
    myProject = project;
    setTitle(getChooserTitle(descriptor));
  }

  public FileChooserDialogImpl(
      @NotNull final FileChooserDescriptor descriptor, @NotNull Component parent) {
    this(descriptor, parent, null);
  }

  public FileChooserDialogImpl(
      @NotNull final FileChooserDescriptor descriptor,
      @NotNull Component parent,
      @Nullable Project project) {
    super(parent, true);
    myChooserDescriptor = descriptor;
    myProject = project;
    setTitle(getChooserTitle(descriptor));
  }

  private static String getChooserTitle(final FileChooserDescriptor descriptor) {
    final String title = descriptor.getTitle();
    return title != null ? title : UIBundle.message("file.chooser.default.title");
  }

  @Override
  @NotNull
  public VirtualFile[] choose(
      @Nullable final Project project, @NotNull final VirtualFile... toSelect) {
    init();
    if ((myProject == null) && (project != null)) {
      myProject = project;
    }
    if (toSelect.length == 1) {
      restoreSelection(toSelect[0]);
    } else if (toSelect.length == 0) {
      restoreSelection(null); // select last opened file
    } else {
      selectInTree(toSelect, true);
    }

    Runnable showRunnable =
        new Runnable() {
          @Override
          public void run() {
            show();
          }
        };
    // file chooser calls VFS refresh which might lead to rootsChanged in any open project and dumb
    // mode that the clients don't expect.
    // so if reindexing has to happen, let it happen under a modal progress and be finished before
    // the file chooser returns.
    // this hack should be gone if file chooser doesn't use VFS
    // (https://youtrack.jetbrains.com/issue/IDEA-101218)
    for (final Project eachProject : ProjectManager.getInstance().getOpenProjects()) {
      showRunnable = allowModalDumbModeInside(showRunnable, eachProject);
    }
    showRunnable.run();

    return myChosenFiles;
  }

  @NotNull
  private static Runnable allowModalDumbModeInside(
      final @NotNull Runnable runnable, @NotNull final Project eachProject) {
    return new Runnable() {
      @Override
      public void run() {
        DumbService.getInstance(eachProject)
            .allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, runnable);
      }
    };
  }

  @NotNull
  @Override
  public VirtualFile[] choose(
      @Nullable final VirtualFile toSelect, @Nullable final Project project) {
    if (toSelect == null) {
      return choose(project);
    }
    return choose(project, toSelect);
  }

  @Override
  public void choose(
      @Nullable VirtualFile toSelect, @NotNull Consumer<List<VirtualFile>> callback) {
    init();
    restoreSelection(toSelect);
    show();
    if (myChosenFiles.length > 0) {
      callback.consume(Arrays.asList(myChosenFiles));
    } else if (callback instanceof FileChooser.FileChooserConsumer) {
      ((FileChooser.FileChooserConsumer) callback).cancelled();
    }
  }

  protected void restoreSelection(@Nullable VirtualFile toSelect) {
    final VirtualFile lastOpenedFile = FileChooserUtil.getLastOpenedFile(myProject);
    final VirtualFile file =
        FileChooserUtil.getFileToSelect(myChooserDescriptor, myProject, toSelect, lastOpenedFile);

    if (file != null && file.isValid()) {
      myFileSystemTree.select(
          file,
          new Runnable() {
            public void run() {
              if (!file.equals(myFileSystemTree.getSelectedFile())) {
                VirtualFile parent = file.getParent();
                if (parent != null) {
                  myFileSystemTree.select(parent, null);
                }
              } else if (file.isDirectory()) {
                myFileSystemTree.expand(file, null);
              }
            }
          });
    }
  }

  protected void storeSelection(@Nullable VirtualFile file) {
    FileChooserUtil.setLastOpenedFile(myProject, file);
    if (file != null && file.getFileSystem() instanceof LocalFileSystem) {
      saveRecent(file.getPath());
    }
  }

  protected void saveRecent(String path) {
    final List<String> files = new ArrayList<String>(Arrays.asList(getRecentFiles()));
    files.remove(path);
    files.add(0, path);
    while (files.size() > 30) {
      files.remove(files.size() - 1);
    }
    PropertiesComponent.getInstance().setValues(RECENT_FILES_KEY, ArrayUtil.toStringArray(files));
  }

  @NotNull
  private String[] getRecentFiles() {
    final String[] recent = PropertiesComponent.getInstance().getValues(RECENT_FILES_KEY);
    if (recent != null) {
      if (recent.length > 0
          && myPathTextField.getField().getText().replace('\\', '/').equals(recent[0])) {
        final String[] pathes = new String[recent.length - 1];
        System.arraycopy(recent, 1, pathes, 0, recent.length - 1);
        return pathes;
      }
      return recent;
    }
    return ArrayUtil.EMPTY_STRING_ARRAY;
  }

  protected JComponent createHistoryButton() {
    JLabel label = new JLabel(AllIcons.Actions.Get);
    label.setToolTipText("Recent files");
    new ClickListener() {
      @Override
      public boolean onClick(@NotNull MouseEvent event, int clickCount) {
        showRecentFilesPopup();
        return true;
      }
    }.installOn(label);

    new AnAction() {
      @Override
      public void actionPerformed(AnActionEvent e) {
        showRecentFilesPopup();
      }

      @Override
      public void update(AnActionEvent e) {
        e.getPresentation().setEnabled(!IdeEventQueue.getInstance().isPopupActive());
      }
    }.registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, myPathTextField.getField());
    return label;
  }

  private void showRecentFilesPopup() {
    final JBList files =
        new JBList(getRecentFiles()) {
          @Override
          public Dimension getPreferredSize() {
            return new Dimension(
                myPathTextField.getField().getWidth(), super.getPreferredSize().height);
          }
        };
    files.setCellRenderer(
        new ColoredListCellRenderer() {
          @Override
          protected void customizeCellRenderer(
              JList list, Object value, int index, boolean selected, boolean hasFocus) {
            final String path = value.toString();
            append(path);
            final VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
            if (file != null) {
              setIcon(IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, null));
            }
          }
        });
    JBPopupFactory.getInstance()
        .createListPopupBuilder(files)
        .setItemChoosenCallback(
            new Runnable() {
              @Override
              public void run() {
                myPathTextField.getField().setText(files.getSelectedValue().toString());
              }
            })
        .createPopup()
        .showUnderneathOf(myPathTextField.getField());
  }

  protected DefaultActionGroup createActionGroup() {
    registerFileChooserShortcut(IdeActions.ACTION_DELETE, "FileChooser.Delete");
    registerFileChooserShortcut(IdeActions.ACTION_SYNCHRONIZE, "FileChooser.Refresh");

    return (DefaultActionGroup) ActionManager.getInstance().getAction("FileChooserToolbar");
  }

  private void registerFileChooserShortcut(
      @NonNls final String baseActionId, @NonNls final String fileChooserActionId) {
    final JTree tree = myFileSystemTree.getTree();
    final AnAction syncAction = ActionManager.getInstance().getAction(fileChooserActionId);

    AnAction original = ActionManager.getInstance().getAction(baseActionId);
    syncAction.registerCustomShortcutSet(original.getShortcutSet(), tree, myDisposable);
  }

  @Nullable
  protected final JComponent createTitlePane() {
    final String description = myChooserDescriptor.getDescription();
    if (StringUtil.isEmptyOrSpaces(description)) return null;

    final JLabel label = new JLabel(description);
    label.setBorder(
        BorderFactory.createCompoundBorder(
            new SideBorder(UIUtil.getPanelBackground().darker(), SideBorder.BOTTOM),
            JBUI.Borders.empty(0, 5, 10, 5)));
    return label;
  }

  protected JComponent createCenterPanel() {
    JPanel panel = new MyPanel();

    myUiUpdater = new MergingUpdateQueue("FileChooserUpdater", 200, false, panel);
    Disposer.register(myDisposable, myUiUpdater);
    new UiNotifyConnector(panel, myUiUpdater);

    panel.setBorder(JBUI.Borders.empty());

    createTree();

    final DefaultActionGroup group = createActionGroup();
    ActionToolbar toolBar =
        ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
    toolBar.setTargetComponent(panel);

    final JPanel toolbarPanel = new JPanel(new BorderLayout());
    toolbarPanel.add(toolBar.getComponent(), BorderLayout.CENTER);

    myTextFieldAction =
        new TextFieldAction() {
          public void linkSelected(final LinkLabel aSource, final Object aLinkData) {
            toggleShowTextField();
          }
        };
    toolbarPanel.add(myTextFieldAction, BorderLayout.EAST);

    myPathTextFieldWrapper = new JPanel(new BorderLayout());
    myPathTextFieldWrapper.setBorder(JBUI.Borders.emptyBottom(2));
    myPathTextField =
        new FileTextFieldImpl.Vfs(
            FileChooserFactoryImpl.getMacroMap(),
            getDisposable(),
            new LocalFsFinder.FileChooserFilter(myChooserDescriptor, myFileSystemTree)) {
          protected void onTextChanged(final String newValue) {
            myUiUpdater.cancelAllUpdates();
            updateTreeFromPath(newValue);
          }
        };
    Disposer.register(myDisposable, myPathTextField);
    myPathTextFieldWrapper.add(myPathTextField.getField(), BorderLayout.CENTER);
    if (getRecentFiles().length > 0) {
      myPathTextFieldWrapper.add(createHistoryButton(), BorderLayout.EAST);
    }

    myNorthPanel = new JPanel(new BorderLayout());
    myNorthPanel.add(toolbarPanel, BorderLayout.NORTH);

    updateTextFieldShowing();

    panel.add(myNorthPanel, BorderLayout.NORTH);

    registerMouseListener(group);

    JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myFileSystemTree.getTree());
    // scrollPane.setBorder(BorderFactory.createLineBorder(new Color(148, 154, 156)));
    panel.add(scrollPane, BorderLayout.CENTER);
    panel.setPreferredSize(JBUI.size(400));

    panel.add(
        new JLabel(
            "<html><center><small><font color=gray>Drag and drop a file into the space above to quickly locate it in the tree.</font></small></center></html>",
            SwingConstants.CENTER),
        BorderLayout.SOUTH);

    ApplicationManager.getApplication()
        .getMessageBus()
        .connect(getDisposable())
        .subscribe(
            ApplicationActivationListener.TOPIC,
            new ApplicationActivationListener.Adapter() {
              @Override
              public void applicationActivated(IdeFrame ideFrame) {
                ((SaveAndSyncHandlerImpl) SaveAndSyncHandler.getInstance())
                    .maybeRefresh(ModalityState.current());
              }
            });

    return panel;
  }

  public JComponent getPreferredFocusedComponent() {
    if (isToShowTextField()) {
      return myPathTextField != null ? myPathTextField.getField() : null;
    } else {
      return myFileSystemTree != null ? myFileSystemTree.getTree() : null;
    }
  }

  public final void dispose() {
    LocalFileSystem.getInstance().removeWatchedRoots(myRequests.values());
    super.dispose();
  }

  private boolean isTextFieldActive() {
    return myPathTextField.getField().getRootPane() != null;
  }

  protected void doOKAction() {
    if (!isOKActionEnabled()) {
      return;
    }

    if (isTextFieldActive()) {
      final String text = myPathTextField.getTextFieldText();
      final LookupFile file = myPathTextField.getFile();
      if (text == null || file == null || !file.exists()) {
        setErrorText("Specified path cannot be found");
        return;
      }
    }

    final List<VirtualFile> selectedFiles = Arrays.asList(getSelectedFilesInt());
    final VirtualFile[] files =
        VfsUtilCore.toVirtualFileArray(
            FileChooserUtil.getChosenFiles(myChooserDescriptor, selectedFiles));
    if (files.length == 0) {
      myChosenFiles = VirtualFile.EMPTY_ARRAY;
      close(CANCEL_EXIT_CODE);
      return;
    }

    try {
      myChooserDescriptor.validateSelectedFiles(files);
    } catch (Exception e) {
      Messages.showErrorDialog(getContentPane(), e.getMessage(), getTitle());
      return;
    }

    myChosenFiles = files;
    storeSelection(files[files.length - 1]);

    super.doOKAction();
  }

  public final void doCancelAction() {
    myChosenFiles = VirtualFile.EMPTY_ARRAY;
    super.doCancelAction();
  }

  protected JTree createTree() {
    myFileSystemTree = new FileSystemTreeImpl(myProject, myChooserDescriptor);
    Disposer.register(myDisposable, myFileSystemTree);

    myFileSystemTree.addOkAction(
        new Runnable() {
          public void run() {
            doOKAction();
          }
        });
    JTree tree = myFileSystemTree.getTree();
    tree.setCellRenderer(new NodeRenderer());
    tree.getSelectionModel().addTreeSelectionListener(new FileTreeSelectionListener());
    tree.addTreeExpansionListener(new FileTreeExpansionListener());
    setOKActionEnabled(false);

    myFileSystemTree.addListener(
        new FileSystemTree.Listener() {
          public void selectionChanged(final List<VirtualFile> selection) {
            updatePathFromTree(selection, false);
          }
        },
        myDisposable);

    new FileDrop(
        tree,
        new FileDrop.Target() {
          public FileChooserDescriptor getDescriptor() {
            return myChooserDescriptor;
          }

          public boolean isHiddenShown() {
            return myFileSystemTree.areHiddensShown();
          }

          public void dropFiles(final List<VirtualFile> files) {
            if (!myChooserDescriptor.isChooseMultiple() && files.size() > 0) {
              selectInTree(new VirtualFile[] {files.get(0)}, true);
            } else {
              selectInTree(VfsUtilCore.toVirtualFileArray(files), true);
            }
          }
        });

    return tree;
  }

  protected final void registerMouseListener(final ActionGroup group) {
    myFileSystemTree.registerMouseListener(group);
  }

  private VirtualFile[] getSelectedFilesInt() {
    if (myTreeIsUpdating || !myUiUpdater.isEmpty()) {
      if (isTextFieldActive() && !StringUtil.isEmpty(myPathTextField.getTextFieldText())) {
        LookupFile toFind = myPathTextField.getFile();
        if (toFind instanceof LocalFsFinder.VfsFile && toFind.exists()) {
          VirtualFile file = ((LocalFsFinder.VfsFile) toFind).getFile();
          if (file != null) {
            return new VirtualFile[] {file};
          }
        }
      }
      return VirtualFile.EMPTY_ARRAY;
    }

    return myFileSystemTree.getSelectedFiles();
  }

  private final Map<String, LocalFileSystem.WatchRequest> myRequests =
      new HashMap<String, LocalFileSystem.WatchRequest>();

  private static boolean isToShowTextField() {
    return PropertiesComponent.getInstance().getBoolean(FILE_CHOOSER_SHOW_PATH_PROPERTY, true);
  }

  private static void setToShowTextField(boolean toShowTextField) {
    PropertiesComponent.getInstance()
        .setValue(FILE_CHOOSER_SHOW_PATH_PROPERTY, Boolean.toString(toShowTextField));
  }

  private final class FileTreeExpansionListener implements TreeExpansionListener {
    public void treeExpanded(TreeExpansionEvent event) {
      final Object[] path = event.getPath().getPath();
      if (path.length == 2) {
        // top node has been expanded => watch disk recursively
        final DefaultMutableTreeNode node = (DefaultMutableTreeNode) path[1];
        Object userObject = node.getUserObject();
        if (userObject instanceof FileNodeDescriptor) {
          final VirtualFile file = ((FileNodeDescriptor) userObject).getElement().getFile();
          if (file != null && file.isDirectory()) {
            final String rootPath = file.getPath();
            if (myRequests.get(rootPath) == null) {
              final LocalFileSystem.WatchRequest watchRequest =
                  LocalFileSystem.getInstance().addRootToWatch(rootPath, true);
              myRequests.put(rootPath, watchRequest);
            }
          }
        }
      }
    }

    public void treeCollapsed(TreeExpansionEvent event) {}
  }

  private final class FileTreeSelectionListener implements TreeSelectionListener {
    public void valueChanged(TreeSelectionEvent e) {
      TreePath[] paths = e.getPaths();

      boolean enabled = true;
      for (TreePath treePath : paths) {
        if (!e.isAddedPath(treePath)) {
          continue;
        }
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
        Object userObject = node.getUserObject();
        if (!(userObject instanceof FileNodeDescriptor)) {
          enabled = false;
          break;
        }
        FileElement descriptor = ((FileNodeDescriptor) userObject).getElement();
        VirtualFile file = descriptor.getFile();
        enabled = file != null && myChooserDescriptor.isFileSelectable(file);
      }
      setOKActionEnabled(enabled);
    }
  }

  protected final class MyPanel extends JPanel implements DataProvider {
    public MyPanel() {
      super(new BorderLayout(0, 0));
    }

    public Object getData(String dataId) {
      if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
        return myFileSystemTree.getSelectedFiles();
      } else if (PATH_FIELD.is(dataId)) {
        return new PathField() {
          public void toggleVisible() {
            toggleShowTextField();
          }
        };
      } else if (FileSystemTree.DATA_KEY.is(dataId)) {
        return myFileSystemTree;
      }
      return myChooserDescriptor.getUserData(dataId);
    }
  }

  public void toggleShowTextField() {
    setToShowTextField(!isToShowTextField());
    updateTextFieldShowing();
  }

  private void updateTextFieldShowing() {
    myTextFieldAction.update();
    myNorthPanel.remove(myPathTextFieldWrapper);
    if (isToShowTextField()) {
      final ArrayList<VirtualFile> selection = new ArrayList<VirtualFile>();
      if (myFileSystemTree.getSelectedFile() != null) {
        selection.add(myFileSystemTree.getSelectedFile());
      }
      updatePathFromTree(selection, true);
      myNorthPanel.add(myPathTextFieldWrapper, BorderLayout.CENTER);
    } else {
      setErrorText(null);
    }
    myPathTextField.getField().requestFocus();

    myNorthPanel.revalidate();
    myNorthPanel.repaint();
  }

  private void updatePathFromTree(final List<VirtualFile> selection, boolean now) {
    if (!isToShowTextField() || myTreeIsUpdating) return;

    String text = "";
    if (selection.size() > 0) {
      text = VfsUtil.getReadableUrl(selection.get(0));
    } else {
      final List<VirtualFile> roots = myChooserDescriptor.getRoots();
      if (!myFileSystemTree.getTree().isRootVisible() && roots.size() == 1) {
        text = VfsUtil.getReadableUrl(roots.get(0));
      }
    }

    myPathTextField.setText(
        text,
        now,
        new Runnable() {
          public void run() {
            myPathTextField.getField().selectAll();
            setErrorText(null);
          }
        });
  }

  private void updateTreeFromPath(final String text) {
    if (!isToShowTextField()) return;
    if (myPathTextField.isPathUpdating()) return;
    if (text == null) return;

    myUiUpdater.queue(
        new Update("treeFromPath.1") {
          public void run() {
            ApplicationManager.getApplication()
                .executeOnPooledThread(
                    new Runnable() {
                      public void run() {
                        final LocalFsFinder.VfsFile toFind =
                            (LocalFsFinder.VfsFile) myPathTextField.getFile();
                        if (toFind == null || !toFind.exists()) return;

                        myUiUpdater.queue(
                            new Update("treeFromPath.2") {
                              public void run() {
                                selectInTree(toFind.getFile(), text);
                              }
                            });
                      }
                    });
          }
        });
  }

  private void selectInTree(final VirtualFile vFile, String fromText) {
    if (vFile != null && vFile.isValid()) {
      if (fromText == null || fromText.equalsIgnoreCase(myPathTextField.getTextFieldText())) {
        selectInTree(new VirtualFile[] {vFile}, false);
      }
    } else {
      reportFileNotFound();
    }
  }

  private void selectInTree(final VirtualFile[] array, final boolean requestFocus) {
    myTreeIsUpdating = true;
    final List<VirtualFile> fileList = Arrays.asList(array);
    if (!Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
      myFileSystemTree.select(
          array,
          new Runnable() {
            public void run() {
              if (!myFileSystemTree.areHiddensShown()
                  && !Arrays.asList(myFileSystemTree.getSelectedFiles()).containsAll(fileList)) {
                myFileSystemTree.showHiddens(true);
                selectInTree(array, requestFocus);
                return;
              }

              myTreeIsUpdating = false;
              setErrorText(null);
              if (requestFocus) {
                //noinspection SSBasedInspection
                SwingUtilities.invokeLater(
                    new Runnable() {
                      public void run() {
                        myFileSystemTree.getTree().requestFocus();
                      }
                    });
              }
            }
          });
    } else {
      myTreeIsUpdating = false;
      setErrorText(null);
    }
  }

  private void reportFileNotFound() {
    myTreeIsUpdating = false;
    setErrorText(null);
  }

  @Override
  protected String getDimensionServiceKey() {
    return "FileChooserDialogImpl";
  }

  @Override
  protected String getHelpId() {
    return "select.path.dialog";
  }
}
/** @author max */
public class ChangesListView extends Tree implements TypeSafeDataProvider, AdvancedDnDSource {
  private ChangesListView.DropTarget myDropTarget;
  private DnDManager myDndManager;
  private ChangeListOwner myDragOwner;
  private final Project myProject;
  private TreeState myTreeState;
  private boolean myShowFlatten = false;
  private final CopyProvider myCopyProvider;

  @NonNls public static final String HELP_ID_KEY = "helpId";
  @NonNls public static final String ourHelpId = "ideaInterface.changes";

  @NonNls
  public static final DataKey<List<VirtualFile>> UNVERSIONED_FILES_DATA_KEY =
      DataKey.create("ChangeListView.UnversionedFiles");

  @NonNls
  public static final DataKey<List<FilePath>> MISSING_FILES_DATA_KEY =
      DataKey.create("ChangeListView.MissingFiles");

  @NonNls
  public static final DataKey<List<LocallyDeletedChange>> LOCALLY_DELETED_CHANGES =
      DataKey.create("ChangeListView.LocallyDeletedChanges");

  @NonNls public static final DataKey<String> HELP_ID_DATA_KEY = DataKey.create(HELP_ID_KEY);

  private ActionGroup myMenuGroup;

  public ChangesListView(final Project project) {
    myProject = project;

    getModel().setRoot(ChangesBrowserNode.create(myProject, TreeModelBuilder.ROOT_NODE_VALUE));

    setShowsRootHandles(true);
    setRootVisible(false);

    new TreeSpeedSearch(this, new NodeToTextConvertor());
    SmartExpander.installOn(this);
    myCopyProvider = new TreeCopyProvider(this);
    new TreeLinkMouseListener(new ChangesBrowserNodeRenderer(myProject, false, false))
        .installOn(this);
  }

  @Override
  public DefaultTreeModel getModel() {
    return (DefaultTreeModel) super.getModel();
  }

  public void installDndSupport(ChangeListOwner owner) {
    myDragOwner = owner;
    myDropTarget = new DropTarget();
    myDndManager = DnDManager.getInstance();

    myDndManager.registerSource(this);
    myDndManager.registerTarget(myDropTarget, this);
  }

  @Override
  public void dispose() {
    if (myDropTarget != null) {
      myDndManager.unregisterSource(this);
      myDndManager.unregisterTarget(myDropTarget, this);

      myDropTarget = null;
      myDndManager = null;
      myDragOwner = null;
    }
  }

  private void storeState() {
    myTreeState = TreeState.createOn(this, (ChangesBrowserNode) getModel().getRoot());
  }

  private void restoreState() {
    myTreeState.applyTo(this, (ChangesBrowserNode) getModel().getRoot());
  }

  public boolean isShowFlatten() {
    return myShowFlatten;
  }

  public void setShowFlatten(final boolean showFlatten) {
    myShowFlatten = showFlatten;
  }

  public void updateModel(
      List<? extends ChangeList> changeLists,
      Trinity<List<VirtualFile>, Integer, Integer> unversionedFiles,
      final List<LocallyDeletedChange> locallyDeletedFiles,
      List<VirtualFile> modifiedWithoutEditing,
      MultiMap<String, VirtualFile> switchedFiles,
      @Nullable Map<VirtualFile, String> switchedRoots,
      @Nullable List<VirtualFile> ignoredFiles,
      final List<VirtualFile> lockedFolders,
      @Nullable final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
    TreeModelBuilder builder = new TreeModelBuilder(myProject, isShowFlatten());
    final DefaultTreeModel model =
        builder.buildModel(
            changeLists,
            unversionedFiles,
            locallyDeletedFiles,
            modifiedWithoutEditing,
            switchedFiles,
            switchedRoots,
            ignoredFiles,
            lockedFolders,
            logicallyLockedFiles);

    storeState();
    DefaultTreeModel oldModel = getModel();
    setModel(model);
    setCellRenderer(new ChangesBrowserNodeRenderer(myProject, isShowFlatten(), true));
    ChangesBrowserNode root = (ChangesBrowserNode) model.getRoot();
    expandPath(new TreePath(root.getPath()));
    restoreState();
    expandDefaultChangeList(oldModel, root);
  }

  private void expandDefaultChangeList(DefaultTreeModel oldModel, ChangesBrowserNode root) {
    if (((ChangesBrowserNode) oldModel.getRoot()).getCount() == 0
        && TreeUtil.collectExpandedPaths(this).size() == 1) {
      TreeNode toExpand = null;
      for (int i = 0; i < root.getChildCount(); i++) {
        TreeNode node = root.getChildAt(i);
        if (node instanceof ChangesBrowserChangeListNode && node.getChildCount() > 0) {
          ChangeList object = ((ChangesBrowserChangeListNode) node).getUserObject();
          if (object instanceof LocalChangeList) {
            if (((LocalChangeList) object).isDefault()) {
              toExpand = node;
              break;
            }
          }
        }
      }

      if (toExpand != null) {
        expandPath(new TreePath(new Object[] {root, toExpand}));
      }
    }
  }

  @Override
  public void calcData(DataKey key, DataSink sink) {
    if (key == VcsDataKeys.CHANGES) {
      sink.put(VcsDataKeys.CHANGES, getSelectedChanges());
    } else if (key == VcsDataKeys.CHANGE_LEAD_SELECTION) {
      sink.put(VcsDataKeys.CHANGE_LEAD_SELECTION, getLeadSelection());
    } else if (key == VcsDataKeys.CHANGE_LISTS) {
      sink.put(VcsDataKeys.CHANGE_LISTS, getSelectedChangeLists());
    } else if (key == PlatformDataKeys.VIRTUAL_FILE_ARRAY) {
      sink.put(PlatformDataKeys.VIRTUAL_FILE_ARRAY, getSelectedFiles());
    } else if (key == PlatformDataKeys.NAVIGATABLE) {
      final VirtualFile[] files = getSelectedFiles();
      if (files.length == 1 && !files[0].isDirectory()) {
        sink.put(PlatformDataKeys.NAVIGATABLE, new OpenFileDescriptor(myProject, files[0], 0));
      }
    } else if (key == PlatformDataKeys.NAVIGATABLE_ARRAY) {
      sink.put(
          PlatformDataKeys.NAVIGATABLE_ARRAY,
          ChangesUtil.getNavigatableArray(myProject, getSelectedFiles()));
    } else if (key == PlatformDataKeys.DELETE_ELEMENT_PROVIDER) {
      final TreePath[] paths = getSelectionPaths();
      if (paths != null) {
        for (TreePath path : paths) {
          ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
          if (!(node.getUserObject() instanceof ChangeList)) {
            sink.put(PlatformDataKeys.DELETE_ELEMENT_PROVIDER, new VirtualFileDeleteProvider());
            break;
          }
        }
      }
    } else if (key == PlatformDataKeys.COPY_PROVIDER) {
      sink.put(PlatformDataKeys.COPY_PROVIDER, myCopyProvider);
    } else if (key == UNVERSIONED_FILES_DATA_KEY) {
      sink.put(UNVERSIONED_FILES_DATA_KEY, getSelectedUnversionedFiles());
    } else if (key == VcsDataKeys.MODIFIED_WITHOUT_EDITING_DATA_KEY) {
      sink.put(VcsDataKeys.MODIFIED_WITHOUT_EDITING_DATA_KEY, getSelectedModifiedWithoutEditing());
    } else if (key == LOCALLY_DELETED_CHANGES) {
      sink.put(LOCALLY_DELETED_CHANGES, getSelectedLocallyDeletedChanges());
    } else if (key == MISSING_FILES_DATA_KEY) {
      sink.put(MISSING_FILES_DATA_KEY, getSelectedMissingFiles());
    } else if (VcsDataKeys.HAVE_LOCALLY_DELETED == key) {
      sink.put(VcsDataKeys.HAVE_LOCALLY_DELETED, haveLocallyDeleted());
    } else if (VcsDataKeys.HAVE_MODIFIED_WITHOUT_EDITING == key) {
      sink.put(VcsDataKeys.HAVE_MODIFIED_WITHOUT_EDITING, haveLocallyModified());
    } else if (VcsDataKeys.HAVE_SELECTED_CHANGES == key) {
      sink.put(VcsDataKeys.HAVE_SELECTED_CHANGES, haveSelectedChanges());
    } else if (key == HELP_ID_DATA_KEY) {
      sink.put(HELP_ID_DATA_KEY, ourHelpId);
    } else if (key == VcsDataKeys.CHANGES_IN_LIST_KEY) {
      final TreePath selectionPath = getSelectionPath();
      if (selectionPath != null && selectionPath.getPathCount() > 1) {
        ChangesBrowserNode<?> firstNode = (ChangesBrowserNode) selectionPath.getPathComponent(1);
        if (firstNode instanceof ChangesBrowserChangeListNode) {
          final List<Change> list = firstNode.getAllChangesUnder();
          sink.put(VcsDataKeys.CHANGES_IN_LIST_KEY, list);
        }
      }
    }
  }

  private List<VirtualFile> getSelectedUnversionedFiles() {
    return getSelectedVirtualFiles(ChangesBrowserNode.UNVERSIONED_FILES_TAG);
  }

  private List<VirtualFile> getSelectedModifiedWithoutEditing() {
    return getSelectedVirtualFiles(ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
  }

  private List<VirtualFile> getSelectedIgnoredFiles() {
    return getSelectedVirtualFiles(ChangesBrowserNode.IGNORED_FILES_TAG);
  }

  private List<VirtualFile> getSelectedVirtualFiles(final Object tag) {
    Set<VirtualFile> files = new HashSet<VirtualFile>();
    final TreePath[] paths = getSelectionPaths();
    if (paths != null) {
      for (TreePath path : paths) {
        if (path.getPathCount() > 1) {
          ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1);
          if (tag == null || firstNode.getUserObject() == tag) {
            ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent();
            files.addAll(node.getAllFilesUnder());
          }
        }
      }
    }
    return new ArrayList<VirtualFile>(files);
  }

  private List<FilePath> getSelectedFilePaths(final Object tag) {
    Set<FilePath> files = new HashSet<FilePath>();
    final TreePath[] paths = getSelectionPaths();
    if (paths != null) {
      for (TreePath path : paths) {
        if (path.getPathCount() > 1) {
          ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1);
          if (tag == null || firstNode.getUserObject() == tag) {
            ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent();
            files.addAll(node.getAllFilePathsUnder());
          }
        }
      }
    }
    return new ArrayList<FilePath>(files);
  }

  private List<LocallyDeletedChange> getSelectedLocallyDeletedChanges() {
    Set<LocallyDeletedChange> files = new HashSet<LocallyDeletedChange>();
    final TreePath[] paths = getSelectionPaths();
    if (paths != null) {
      for (TreePath path : paths) {
        if (path.getPathCount() > 1) {
          ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1);
          if (firstNode.getUserObject() == TreeModelBuilder.LOCALLY_DELETED_NODE) {
            ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent();
            final List<LocallyDeletedChange> objectsUnder =
                node.getAllObjectsUnder(LocallyDeletedChange.class);
            files.addAll(objectsUnder);
          }
        }
      }
    }
    return new ArrayList<LocallyDeletedChange>(files);
  }

  private List<FilePath> getSelectedMissingFiles() {
    return getSelectedFilePaths(TreeModelBuilder.LOCALLY_DELETED_NODE);
  }

  protected VirtualFile[] getSelectedFiles() {
    final Change[] changes = getSelectedChanges();
    Collection<VirtualFile> files = new HashSet<VirtualFile>();
    for (Change change : changes) {
      final ContentRevision afterRevision = change.getAfterRevision();
      if (afterRevision != null) {
        final VirtualFile file = afterRevision.getFile().getVirtualFile();
        if (file != null && file.isValid()) {
          files.add(file);
        }
      }
    }

    files.addAll(getSelectedVirtualFiles(null));

    return VfsUtilCore.toVirtualFileArray(files);
  }

  protected boolean haveSelectedFileType(final Object tag) {
    final TreePath[] paths = getSelectionPaths();
    if (paths != null) {
      for (TreePath path : paths) {
        if (path.getPathCount() > 1) {
          ChangesBrowserNode firstNode = (ChangesBrowserNode) path.getPathComponent(1);
          if ((tag == null || firstNode.getUserObject() == tag) && path.getPathCount() > 2) {
            return true;
          }
        }
      }
    }
    return false;
  }

  public boolean haveLocallyDeleted() {
    return haveSelectedFileType(TreeModelBuilder.LOCALLY_DELETED_NODE);
  }

  public boolean haveLocallyModified() {
    return haveSelectedFileType(ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
  }

  private Boolean haveSelectedChanges() {
    final TreePath[] paths = getSelectionPaths();
    if (paths == null) return false;

    for (TreePath path : paths) {
      ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
      if (node instanceof ChangesBrowserChangeNode) {
        return true;
      } else if (node instanceof ChangesBrowserChangeListNode) {
        final ChangesBrowserChangeListNode changeListNode = (ChangesBrowserChangeListNode) node;
        if (changeListNode.getChildCount() > 0) {
          return true;
        }
      }
    }
    return false;
  }

  private Change[] getLeadSelection() {
    final Set<Change> changes = new LinkedHashSet<Change>();

    final TreePath[] paths = getSelectionPaths();
    if (paths == null) return new Change[0];

    for (TreePath path : paths) {
      ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
      if (node instanceof ChangesBrowserChangeNode) {
        changes.add(((ChangesBrowserChangeNode) node).getUserObject());
      }
    }

    return changes.toArray(new Change[changes.size()]);
  }

  @NotNull
  public Change[] getSelectedChanges() {
    Set<Change> changes = new LinkedHashSet<Change>();

    final TreePath[] paths = getSelectionPaths();
    if (paths == null) {
      return new Change[0];
    }

    for (TreePath path : paths) {
      ChangesBrowserNode<?> node = (ChangesBrowserNode) path.getLastPathComponent();
      changes.addAll(node.getAllChangesUnder());
    }

    if (changes.isEmpty()) {
      final List<VirtualFile> selectedModifiedWithoutEditing = getSelectedModifiedWithoutEditing();
      if (selectedModifiedWithoutEditing != null && !selectedModifiedWithoutEditing.isEmpty()) {
        for (VirtualFile file : selectedModifiedWithoutEditing) {
          AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(file);
          if (vcs == null) continue;
          final VcsCurrentRevisionProxy before =
              VcsCurrentRevisionProxy.create(file, myProject, vcs.getKeyInstanceMethod());
          if (before != null) {
            ContentRevision afterRevision = new CurrentContentRevision(new FilePathImpl(file));
            changes.add(new Change(before, afterRevision, FileStatus.HIJACKED));
          }
        }
      }
    }

    return changes.toArray(new Change[changes.size()]);
  }

  @NotNull
  private ChangeList[] getSelectedChangeLists() {
    Set<ChangeList> lists = new HashSet<ChangeList>();

    final TreePath[] paths = getSelectionPaths();
    if (paths == null) return new ChangeList[0];

    for (TreePath path : paths) {
      ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
      final Object userObject = node.getUserObject();
      if (userObject instanceof ChangeList) {
        lists.add((ChangeList) userObject);
      }
    }

    return lists.toArray(new ChangeList[lists.size()]);
  }

  public void setMenuActions(final ActionGroup menuGroup) {
    myMenuGroup = menuGroup;
    updateMenu();
    editSourceRegistration();
  }

  protected void editSourceRegistration() {
    EditSourceOnDoubleClickHandler.install(this);
    EditSourceOnEnterKeyHandler.install(this);
  }

  private void updateMenu() {
    PopupHandler.installPopupHandler(
        this, myMenuGroup, ActionPlaces.CHANGES_VIEW_POPUP, ActionManager.getInstance());
  }

  @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
  private static class DragImageFactory {
    private static void drawSelection(JTable table, int column, Graphics g, final int width) {
      int y = 0;
      final int[] rows = table.getSelectedRows();
      final int height = table.getRowHeight();
      for (int row : rows) {
        final TableCellRenderer renderer = table.getCellRenderer(row, column);
        final Component component =
            renderer.getTableCellRendererComponent(
                table, table.getValueAt(row, column), false, false, row, column);
        g.translate(0, y);
        component.setBounds(0, 0, width, height);
        boolean wasOpaque = false;
        if (component instanceof JComponent) {
          final JComponent j = (JComponent) component;
          if (j.isOpaque()) wasOpaque = true;
          j.setOpaque(false);
        }
        component.paint(g);
        if (wasOpaque) {
          ((JComponent) component).setOpaque(true);
        }
        y += height;
        g.translate(0, -y);
      }
    }

    private static void drawSelection(JTree tree, Graphics g, final int width) {
      int y = 0;
      final int[] rows = tree.getSelectionRows();
      final int height = tree.getRowHeight();
      for (int row : rows) {
        final TreeCellRenderer renderer = tree.getCellRenderer();
        final Object value = tree.getPathForRow(row).getLastPathComponent();
        if (value == null) continue;
        final Component component =
            renderer.getTreeCellRendererComponent(tree, value, false, false, false, row, false);
        if (component.getFont() == null) {
          component.setFont(tree.getFont());
        }
        g.translate(0, y);
        component.setBounds(0, 0, width, height);
        boolean wasOpaque = false;
        if (component instanceof JComponent) {
          final JComponent j = (JComponent) component;
          if (j.isOpaque()) wasOpaque = true;
          j.setOpaque(false);
        }
        component.paint(g);
        if (wasOpaque) {
          ((JComponent) component).setOpaque(true);
        }
        y += height;
        g.translate(0, -y);
      }
    }

    public static Image createImage(final JTable table, int column) {
      final int height =
          Math.max(20, Math.min(100, table.getSelectedRowCount() * table.getRowHeight()));
      final int width = table.getColumnModel().getColumn(column).getWidth();

      final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = (Graphics2D) image.getGraphics();

      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));

      drawSelection(table, column, g2, width);
      return image;
    }

    public static Image createImage(final JTree tree) {
      final TreeSelectionModel model = tree.getSelectionModel();
      final TreePath[] paths = model.getSelectionPaths();

      int count = 0;
      final List<ChangesBrowserNode> nodes = new ArrayList<ChangesBrowserNode>();
      for (final TreePath path : paths) {
        final ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
        if (!node.isLeaf()) {
          nodes.add(node);
          count += node.getCount();
        }
      }

      for (TreePath path : paths) {
        final ChangesBrowserNode element = (ChangesBrowserNode) path.getLastPathComponent();
        boolean child = false;
        for (final ChangesBrowserNode node : nodes) {
          if (node.isNodeChild(element)) {
            child = true;
            break;
          }
        }

        if (!child) {
          if (element.isLeaf()) count++;
        } else if (!element.isLeaf()) {
          count -= element.getCount();
        }
      }

      final JLabel label = new JLabel(VcsBundle.message("changes.view.dnd.label", count));
      label.setOpaque(true);
      label.setForeground(tree.getForeground());
      label.setBackground(tree.getBackground());
      label.setFont(tree.getFont());
      label.setSize(label.getPreferredSize());
      final BufferedImage image =
          new BufferedImage(label.getWidth(), label.getHeight(), BufferedImage.TYPE_INT_ARGB);

      Graphics2D g2 = (Graphics2D) image.getGraphics();
      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));
      label.paint(g2);
      g2.dispose();

      return image;
    }
  }

  public class DropTarget implements DnDTarget {
    @Override
    public boolean update(DnDEvent aEvent) {
      aEvent.hideHighlighter();
      aEvent.setDropPossible(false, "");

      Object attached = aEvent.getAttachedObject();
      if (!(attached instanceof ChangeListDragBean)) return false;

      final ChangeListDragBean dragBean = (ChangeListDragBean) attached;
      if (dragBean.getSourceComponent() != ChangesListView.this) return false;
      dragBean.setTargetNode(null);

      RelativePoint dropPoint = aEvent.getRelativePoint();
      Point onTree = dropPoint.getPoint(ChangesListView.this);
      final TreePath dropPath = getPathForLocation(onTree.x, onTree.y);

      if (dropPath == null) return false;

      ChangesBrowserNode dropNode = (ChangesBrowserNode) dropPath.getLastPathComponent();
      while (!((ChangesBrowserNode) dropNode.getParent()).isRoot()) {
        dropNode = (ChangesBrowserNode) dropNode.getParent();
      }

      if (!dropNode.canAcceptDrop(dragBean)) {
        return false;
      }

      final Rectangle tableCellRect = getPathBounds(new TreePath(dropNode.getPath()));
      if (fitsInBounds(tableCellRect)) {
        aEvent.setHighlighting(
            new RelativeRectangle(ChangesListView.this, tableCellRect),
            DnDEvent.DropTargetHighlightingType.RECTANGLE);
      }

      aEvent.setDropPossible(true);
      dragBean.setTargetNode(dropNode);

      return false;
    }

    @Override
    public void drop(DnDEvent aEvent) {
      Object attached = aEvent.getAttachedObject();
      if (!(attached instanceof ChangeListDragBean)) return;

      final ChangeListDragBean dragBean = (ChangeListDragBean) attached;
      final ChangesBrowserNode changesBrowserNode = dragBean.getTargetNode();
      if (changesBrowserNode != null) {
        changesBrowserNode.acceptDrop(myDragOwner, dragBean);
      }
    }

    @Override
    public void cleanUpOnLeave() {}

    @Override
    public void updateDraggedImage(Image image, Point dropPoint, Point imageOffset) {}
  }

  private boolean fitsInBounds(final Rectangle rect) {
    final Container container = getParent();
    if (container instanceof JViewport) {
      final Container scrollPane = container.getParent();
      if (scrollPane instanceof JScrollPane) {
        final Rectangle rectangle =
            SwingUtilities.convertRectangle(this, rect, scrollPane.getParent());
        return scrollPane.getBounds().contains(rectangle);
      }
    }
    return true;
  }

  private static class NodeToTextConvertor implements Convertor<TreePath, String> {
    @Override
    public String convert(final TreePath path) {
      ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
      return node.getTextPresentation();
    }
  }

  @Override
  public boolean canStartDragging(DnDAction action, Point dragOrigin) {
    return action == DnDAction.MOVE
        && (getSelectedChanges().length > 0
            || !getSelectedUnversionedFiles().isEmpty()
            || !getSelectedIgnoredFiles().isEmpty());
  }

  @Override
  public DnDDragStartBean startDragging(DnDAction action, Point dragOrigin) {
    return new DnDDragStartBean(
        new ChangeListDragBean(
            this, getSelectedChanges(), getSelectedUnversionedFiles(), getSelectedIgnoredFiles()));
  }

  @Override
  @Nullable
  public Pair<Image, Point> createDraggedImage(DnDAction action, Point dragOrigin) {
    final Image image = DragImageFactory.createImage(this);
    return new Pair<Image, Point>(image, new Point(-image.getWidth(null), -image.getHeight(null)));
  }

  @Override
  public void dragDropEnd() {}

  @Override
  public void dropActionChanged(final int gestureModifiers) {}

  @Override
  @NotNull
  public JComponent getComponent() {
    return this;
  }

  @Override
  public void processMouseEvent(final MouseEvent e) {
    if (MouseEvent.MOUSE_RELEASED == e.getID()
        && !isSelectionEmpty()
        && !e.isShiftDown()
        && !e.isControlDown()
        && !e.isMetaDown()
        && !e.isPopupTrigger()) {
      if (isOverSelection(e.getPoint())) {
        clearSelection();
        final TreePath path = getPathForLocation(e.getPoint().x, e.getPoint().y);
        if (path != null) {
          setSelectionPath(path);
        }
      }
    }

    super.processMouseEvent(e);
  }

  @Override
  public boolean isOverSelection(final Point point) {
    return TreeUtil.isOverSelection(this, point);
  }

  @Override
  public void dropSelectionButUnderPoint(final Point point) {
    TreeUtil.dropSelectionButUnderPoint(this, point);
  }
}
/** @author Konstantin Bulenkov */
@SuppressWarnings({"unchecked"})
public class DirDiffPanel implements Disposable, DataProvider {
  private static final Logger LOG = Logger.getInstance(DirDiffPanel.class);

  public static final String DIVIDER_PROPERTY = "dir.diff.panel.divider.location";
  private static final int DIVIDER_PROPERTY_DEFAULT_VALUE = 200;
  private JPanel myDiffPanel;
  private JBTable myTable;
  private JPanel myComponent;
  private JSplitPane mySplitPanel;
  private TextFieldWithBrowseButton mySourceDirField;
  private TextFieldWithBrowseButton myTargetDirField;
  private JBLabel myTargetDirLabel;
  private JBLabel mySourceDirLabel;
  private JPanel myToolBarPanel;
  private JPanel myRootPanel;
  private JPanel myFilterPanel;
  private JBLabel myFilterLabel;
  private JPanel myFilesPanel;
  private JPanel myHeaderPanel;
  private FilterComponent myFilter;
  private final DirDiffTableModel myModel;
  private final DirDiffWindow myDiffWindow;
  private final MyDiffRequestProcessor myDiffRequestProcessor;
  private final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
  private String oldFilter;
  public static final DataKey<DirDiffTableModel> DIR_DIFF_MODEL = DataKey.create("DIR_DIFF_MODEL");
  public static final DataKey<JTable> DIR_DIFF_TABLE = DataKey.create("DIR_DIFF_TABLE");

  public DirDiffPanel(DirDiffTableModel model, DirDiffWindow wnd) {
    myModel = model;
    myDiffWindow = wnd;
    mySourceDirField.setText(model.getSourceDir().getPath());
    myTargetDirField.setText(model.getTargetDir().getPath());
    mySourceDirField.setBorder(JBUI.Borders.emptyRight(8));
    myTargetDirField.setBorder(JBUI.Borders.emptyRight(12));
    mySourceDirLabel.setIcon(model.getSourceDir().getIcon());
    myTargetDirLabel.setIcon(model.getTargetDir().getIcon());
    myTargetDirLabel.setBorder(JBUI.Borders.emptyLeft(8));
    myModel.setTable(myTable);
    myModel.setPanel(this);
    Disposer.register(this, myModel);
    myTable.setModel(myModel);
    new TableSpeedSearch(myTable);

    final DirDiffTableCellRenderer renderer = new DirDiffTableCellRenderer();
    myTable.setExpandableItemsEnabled(false);
    myTable.setDefaultRenderer(Object.class, renderer);
    myTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    final Project project = myModel.getProject();
    myTable
        .getSelectionModel()
        .addListSelectionListener(
            new ListSelectionListener() {
              @Override
              public void valueChanged(ListSelectionEvent e) {
                final int lastIndex = e.getLastIndex();
                final int firstIndex = e.getFirstIndex();
                final DirDiffElementImpl last = myModel.getElementAt(lastIndex);
                final DirDiffElementImpl first = myModel.getElementAt(firstIndex);
                if (last == null || first == null) {
                  update(false);
                  return;
                }
                if (last.isSeparator()) {
                  final int ind = lastIndex + ((lastIndex < firstIndex) ? 1 : -1);
                  myTable.getSelectionModel().addSelectionInterval(ind, ind);
                } else if (first.isSeparator()) {
                  final int ind = firstIndex + ((firstIndex < lastIndex) ? 1 : -1);
                  myTable.getSelectionModel().addSelectionInterval(ind, ind);
                } else {
                  update(false);
                }
                myDiffWindow.setTitle(myModel.getTitle());
              }
            });
    if (model.isOperationsEnabled()) {
      new AnAction("Change diff operation") {
        @Override
        public void actionPerformed(AnActionEvent e) {
          changeOperationForSelection();
        }
      }.registerCustomShortcutSet(CustomShortcutSet.fromString("SPACE"), myTable);
      new ClickListener() {
        @Override
        public boolean onClick(@NotNull MouseEvent e, int clickCount) {
          if (e.getButton() == MouseEvent.BUTTON3) return false;
          if (myTable.getRowCount() > 0) {
            final int row = myTable.rowAtPoint(e.getPoint());
            final int col = myTable.columnAtPoint(e.getPoint());

            if (row != -1 && col == ((myTable.getColumnCount() - 1) / 2)) {
              changeOperationForSelection();
            }
          }
          return true;
        }
      }.installOn(myTable);
    }
    myTable.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            final int keyCode = e.getKeyCode();

            int row;
            if (keyCode == KeyEvent.VK_DOWN) {
              row = getNextRow();
            } else if (keyCode == KeyEvent.VK_UP) {
              row = getPrevRow();
            } else {
              row = -1;
            }

            if (row != -1) {
              selectRow(row, e.isShiftDown());
              e.consume();
            }
          }
        });
    final TableColumnModel columnModel = myTable.getColumnModel();
    final TableColumn operationColumn =
        columnModel.getColumn((columnModel.getColumnCount() - 1) / 2);
    operationColumn.setMaxWidth(JBUI.scale(25));
    operationColumn.setMinWidth(JBUI.scale(25));
    for (int i = 0; i < columnModel.getColumnCount(); i++) {
      final String name = myModel.getColumnName(i);
      final TableColumn column = columnModel.getColumn(i);
      if (DirDiffTableModel.COLUMN_DATE.equals(name)) {
        column.setMaxWidth(JBUI.scale(90));
        column.setMinWidth(JBUI.scale(90));
      } else if (DirDiffTableModel.COLUMN_SIZE.equals(name)) {
        column.setMaxWidth(JBUI.scale(120));
        column.setMinWidth(JBUI.scale(120));
      }
    }
    final DirDiffToolbarActions actions = new DirDiffToolbarActions(myModel, myDiffPanel);
    final ActionManager actionManager = ActionManager.getInstance();
    final ActionToolbar toolbar = actionManager.createActionToolbar("DirDiff", actions, true);
    registerCustomShortcuts(actions, myTable);
    myToolBarPanel.add(toolbar.getComponent(), BorderLayout.CENTER);
    final JBLabel label =
        new JBLabel(
            "Use Space button or mouse click to change operation for the selected elements."
                + " Enter to perform.",
            SwingConstants.CENTER);
    label.setForeground(UIUtil.getInactiveTextColor());
    UIUtil.applyStyle(UIUtil.ComponentStyle.MINI, label);
    DataManager.registerDataProvider(myFilesPanel, this);
    myTable.addMouseListener(
        new PopupHandler() {
          @Override
          public void invokePopup(Component comp, int x, int y) {
            final JPopupMenu popupMenu =
                actionManager
                    .createActionPopupMenu(
                        "DirDiffPanel", (ActionGroup) actionManager.getAction("DirDiffMenu"))
                    .getComponent();
            popupMenu.show(comp, x, y);
          }
        });
    myFilesPanel.add(label, BorderLayout.SOUTH);
    final JBLoadingPanel loadingPanel = new JBLoadingPanel(new BorderLayout(), wnd.getDisposable());
    loadingPanel.addListener(
        new JBLoadingPanelListener.Adapter() {
          boolean showHelp = true;

          @Override
          public void onLoadingFinish() {
            if (showHelp && myModel.isOperationsEnabled() && myModel.getRowCount() > 0) {
              final long count =
                  PropertiesComponent.getInstance().getOrInitLong("dir.diff.space.button.info", 0);
              if (count < 3) {
                JBPopupFactory.getInstance()
                    .createBalloonBuilder(new JLabel(" Use Space button to change operation"))
                    .setFadeoutTime(5000)
                    .setContentInsets(JBUI.insets(15))
                    .createBalloon()
                    .show(
                        new RelativePoint(myTable, new Point(myTable.getWidth() / 2, 0)),
                        Balloon.Position.above);
                PropertiesComponent.getInstance()
                    .setValue("dir.diff.space.button.info", String.valueOf(count + 1));
              }
            }
            showHelp = false;
          }
        });
    loadingPanel.add(myComponent, BorderLayout.CENTER);
    myTable.putClientProperty(myModel.DECORATOR, loadingPanel);
    myTable.addComponentListener(
        new ComponentAdapter() {
          @Override
          public void componentShown(ComponentEvent e) {
            myTable.removeComponentListener(this);
            myModel.reloadModel(false);
          }
        });
    myRootPanel.removeAll();
    myRootPanel.add(loadingPanel, BorderLayout.CENTER);
    myFilter =
        new FilterComponent("dir.diff.filter", 15, false) {
          @Override
          public void filter() {
            fireFilterUpdated();
          }

          @Override
          protected void onEscape(KeyEvent e) {
            e.consume();
            focusTable();
          }

          @Override
          protected JComponent getPopupLocationComponent() {
            return UIUtil.findComponentOfType(
                super.getPopupLocationComponent(), JTextComponent.class);
          }
        };

    myModel.addModelListener(
        new DirDiffModelListener() {
          @Override
          public void updateStarted() {
            myFilter.setEnabled(false);
          }

          @Override
          public void updateFinished() {
            myFilter.setEnabled(true);
          }
        });
    myFilter.getTextEditor().setColumns(10);
    myFilter.setFilter(myModel.getSettings().getFilter());
    // oldFilter = myFilter.getText();
    oldFilter = myFilter.getFilter();
    myFilterPanel.add(myFilter, BorderLayout.CENTER);
    myFilterLabel.setLabelFor(myFilter);
    final Callable<DiffElement> srcChooser = myModel.getSourceDir().getElementChooser(project);
    final Callable<DiffElement> trgChooser = myModel.getTargetDir().getElementChooser(project);
    mySourceDirField.setEditable(false);
    myTargetDirField.setEditable(false);

    if (srcChooser != null && myModel.getSettings().enableChoosers) {
      mySourceDirField.setButtonEnabled(true);
      mySourceDirField.addActionListener(
          new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
              try {
                final Callable<DiffElement> chooser =
                    myModel.getSourceDir().getElementChooser(project);
                if (chooser == null) return;
                final DiffElement newElement = chooser.call();
                if (newElement != null) {
                  if (!StringUtil.equals(mySourceDirField.getText(), newElement.getPath())) {
                    myModel.setSourceDir(newElement);
                    mySourceDirField.setText(newElement.getPath());
                    String shortcutsText =
                        KeymapUtil.getShortcutsText(
                            RefreshDirDiffAction.REFRESH_SHORTCUT.getShortcuts());
                    myModel.clearWithMessage(
                        "Source or Target has been changed."
                            + " Please run Refresh ("
                            + shortcutsText
                            + ")");
                  }
                }
              } catch (Exception ignored) {
              }
            }
          });
    } else {
      Dimension preferredSize = mySourceDirField.getPreferredSize();
      mySourceDirField.setButtonEnabled(false);
      mySourceDirField.getButton().setVisible(false);
      mySourceDirField.setPreferredSize(preferredSize);
    }

    if (trgChooser != null && myModel.getSettings().enableChoosers) {
      myTargetDirField.setButtonEnabled(true);
      myTargetDirField.addActionListener(
          new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
              try {
                final Callable<DiffElement> chooser =
                    myModel.getTargetDir().getElementChooser(project);
                if (chooser == null) return;
                final DiffElement newElement = chooser.call();
                if (newElement != null) {
                  myModel.setTargetDir(newElement);
                  myTargetDirField.setText(newElement.getPath());
                }
              } catch (Exception ignored) {
              }
            }
          });
    } else {
      Dimension preferredSize = myTargetDirField.getPreferredSize();
      myTargetDirField.setButtonEnabled(false);
      myTargetDirField.getButton().setVisible(false);
      myTargetDirField.setPreferredSize(preferredSize);
    }

    myDiffRequestProcessor = new MyDiffRequestProcessor(project);
    Disposer.register(this, myDiffRequestProcessor);
    myDiffPanel.add(myDiffRequestProcessor.getComponent(), BorderLayout.CENTER);

    myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
  }

  private int getNextRow() {
    if (myTable.getSelectedRows().length == 0) return -1;
    int rowCount = myTable.getRowCount();
    int row = myTable.getSelectionModel().getLeadSelectionIndex();

    while (true) {
      if (row >= rowCount) return -1;
      row++;
      DirDiffElementImpl element = myModel.getElementAt(row);
      if (element == null) return -1;
      if (!element.isSeparator()) break;
    }

    return row;
  }

  private int getPrevRow() {
    if (myTable.getSelectedRows().length == 0) return -1;
    int row = myTable.getSelectionModel().getLeadSelectionIndex();

    while (true) {
      if (row <= 0) return -1;
      row--;
      DirDiffElementImpl element = myModel.getElementAt(row);
      if (element == null) return -1;
      if (!element.isSeparator()) break;
    }

    return row;
  }

  private void selectRow(int row, boolean extend) {
    if (row == -1) return;
    DirDiffElementImpl element = myModel.getElementAt(row);
    if (element == null || element.isSeparator()) return;
    myTable.changeSelection(row, (myModel.getColumnCount() - 1) / 2, false, extend);
  }

  public AnAction[] getActions() {
    return new DirDiffToolbarActions(myModel, myDiffPanel).getChildren(null);
  }

  public JComponent extractFilterPanel() {
    myHeaderPanel.setVisible(false);
    return myFilterPanel;
  }

  private void changeOperationForSelection() {
    for (int row : myTable.getSelectedRows()) {
      if (row != -1) {
        final DirDiffElementImpl element = myModel.getElementAt(row);
        if (element != null) {
          element.setNextOperation();
          myModel.fireTableRowsUpdated(row, row);
        }
      }
    }
  }

  public void update(boolean force) {
    myDiffRequestProcessor.updateRequest(force);
  }

  private void registerCustomShortcuts(DirDiffToolbarActions actions, JComponent component) {
    for (AnAction action : actions.getChildren(null)) {
      if (action instanceof ShortcutProvider) {
        final ShortcutSet shortcut = ((ShortcutProvider) action).getShortcut();
        if (shortcut != null) {
          action.registerCustomShortcutSet(shortcut, component);
        }
      }
    }
  }

  public void focusTable() {
    final Project project = myModel.getProject();
    final IdeFocusManager focusManager =
        project == null || project.isDefault()
            ? IdeFocusManager.getGlobalInstance()
            : IdeFocusManager.getInstance(project);
    focusManager.requestFocus(myTable, true);
  }

  public String getFilter() {
    return myFilter.getFilter();
  }

  private void fireFilterUpdated() {
    final String newFilter = myFilter.getFilter();
    if (!StringUtil.equals(oldFilter, newFilter)) {
      oldFilter = newFilter;
      myModel.getSettings().setFilter(newFilter);
      myModel.applySettings();
    }
  }

  public JComponent getPanel() {
    return myRootPanel;
  }

  public JBTable getTable() {
    return myTable;
  }

  public void dispose() {
    myModel.stopUpdating();
    PropertiesComponent.getInstance()
        .setValue(
            DIVIDER_PROPERTY, mySplitPanel.getDividerLocation(), DIVIDER_PROPERTY_DEFAULT_VALUE);
  }

  private void createUIComponents() {
    mySourceDirField = new TextFieldWithBrowseButton(null, this);
    myTargetDirField = new TextFieldWithBrowseButton(null, this);

    final AtomicBoolean callUpdate = new AtomicBoolean(true);
    myRootPanel =
        new JPanel(new BorderLayout()) {
          @Override
          protected void paintChildren(Graphics g) {
            super.paintChildren(g);
            if (callUpdate.get()) {
              callUpdate.set(false);
              myModel.reloadModel(false);
            }
          }
        };
  }

  public void setupSplitter() {
    int value =
        PropertiesComponent.getInstance().getInt(DIVIDER_PROPERTY, DIVIDER_PROPERTY_DEFAULT_VALUE);
    mySplitPanel.setDividerLocation(Integer.valueOf(value));
  }

  @Override
  public Object getData(@NonNls String dataId) {
    if (CommonDataKeys.PROJECT.is(dataId)) {
      return myModel.getProject();
    } else if (DIR_DIFF_MODEL.is(dataId)) {
      return myModel;
    } else if (DIR_DIFF_TABLE.is(dataId)) {
      return myTable;
    } else if (DiffDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
      return getNavigatableArray();
    } else if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
      return myPrevNextDifferenceIterable;
    }
    return null;
  }

  @Nullable
  private Navigatable[] getNavigatableArray() {
    Project project = myModel.getProject();
    List<DirDiffElementImpl> elements = myModel.getSelectedElements();
    List<Navigatable> navigatables = new ArrayList<>();
    for (DirDiffElementImpl element : elements) {
      DiffElement source = element.getSource();
      DiffElement target = element.getTarget();
      Navigatable navigatable1 = source != null ? source.getNavigatable(project) : null;
      Navigatable navigatable2 = target != null ? target.getNavigatable(project) : null;
      if (navigatable1 != null) navigatables.add(navigatable1);
      if (navigatable2 != null) navigatables.add(navigatable2);
    }
    return ContainerUtil.toArray(navigatables, new Navigatable[navigatables.size()]);
  }

  private class MyPrevNextDifferenceIterable implements PrevNextDifferenceIterable {
    @Override
    public boolean canGoPrev() {
      return getPrevRow() != -1;
    }

    @Override
    public boolean canGoNext() {
      return getNextRow() != -1;
    }

    @Override
    public void goPrev() {
      selectRow(getPrevRow(), false);
    }

    @Override
    public void goNext() {
      selectRow(getNextRow(), false);
    }
  }

  private class MyDiffRequestProcessor extends CacheDiffRequestProcessor<ElementWrapper> {
    public MyDiffRequestProcessor(@Nullable Project project) {
      super(project, DiffPlaces.DIR_DIFF);
    }

    @Nullable
    @Override
    protected String getRequestName(@NotNull ElementWrapper element) {
      return null;
    }

    @Override
    protected ElementWrapper getCurrentRequestProvider() {
      DirDiffElementImpl element = myModel.getElementAt(myTable.getSelectedRow());
      return element != null ? new ElementWrapper(element) : null;
    }

    @NotNull
    @Override
    protected DiffRequest loadRequest(
        @NotNull ElementWrapper element, @NotNull ProgressIndicator indicator)
        throws ProcessCanceledException, DiffRequestProducerException {
      final Project project = myModel.getProject();
      DiffElement sourceElement = element.sourceElement;
      DiffElement targetElement = element.targetElement;

      DiffContent sourceContent =
          sourceElement != null
              ? sourceElement.createDiffContent(project, indicator)
              : DiffContentFactory.getInstance().createEmpty();
      DiffContent targetContent =
          targetElement != null
              ? targetElement.createDiffContent(project, indicator)
              : DiffContentFactory.getInstance().createEmpty();

      return new SimpleDiffRequest(null, sourceContent, targetContent, null, null);
    }

    //
    // Navigation
    //

    @Override
    protected boolean hasNextChange() {
      return getNextRow() != -1;
    }

    @Override
    protected boolean hasPrevChange() {
      return getPrevRow() != -1;
    }

    @Override
    protected void goToNextChange(boolean fromDifferences) {
      selectRow(getNextRow(), false);
      updateRequest(false, fromDifferences ? ScrollToPolicy.FIRST_CHANGE : null);
    }

    @Override
    protected void goToPrevChange(boolean fromDifferences) {
      selectRow(getPrevRow(), false);
      updateRequest(false, fromDifferences ? ScrollToPolicy.LAST_CHANGE : null);
    }

    @Override
    protected boolean isNavigationEnabled() {
      return myModel.getRowCount() > 0;
    }
  }

  private static class ElementWrapper {
    @Nullable public final DiffElement sourceElement;
    @Nullable public final DiffElement targetElement;

    public ElementWrapper(@NotNull DirDiffElementImpl element) {
      sourceElement = element.getSource();
      targetElement = element.getTarget();
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      ElementWrapper wrapper = (ElementWrapper) o;

      if (sourceElement != null
          ? !sourceElement.equals(wrapper.sourceElement)
          : wrapper.sourceElement != null) return false;
      if (targetElement != null
          ? !targetElement.equals(wrapper.targetElement)
          : wrapper.targetElement != null) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = sourceElement != null ? sourceElement.hashCode() : 0;
      result = 31 * result + (targetElement != null ? targetElement.hashCode() : 0);
      return result;
    }
  }
}
public abstract class ChangesBrowserBase<T> extends JPanel
    implements TypeSafeDataProvider, Disposable {
  private static final Logger LOG = Logger.getInstance(ChangesBrowserBase.class);

  // for backgroundable rollback to mark
  private boolean myDataIsDirty;
  protected final Class<T> myClass;
  protected final ChangesTreeList<T> myViewer;
  protected final JScrollPane myViewerScrollPane;
  protected ChangeList mySelectedChangeList;
  protected List<T> myChangesToDisplay;
  protected final Project myProject;
  private final boolean myCapableOfExcludingChanges;
  protected final JPanel myHeaderPanel;
  private JComponent myBottomPanel;
  private DefaultActionGroup myToolBarGroup;
  private String myToggleActionTitle = VcsBundle.message("commit.dialog.include.action.name");

  private JComponent myDiffBottomComponent;

  public static DataKey<ChangesBrowserBase> DATA_KEY =
      DataKey.create("com.intellij.openapi.vcs.changes.ui.ChangesBrowser");
  private AnAction myDiffAction;
  private final VirtualFile myToSelect;
  @NotNull private final DeleteProvider myDeleteProvider = new VirtualFileDeleteProvider();

  public void setChangesToDisplay(final List<T> changes) {
    myChangesToDisplay = changes;
    myViewer.setChangesToDisplay(changes);
  }

  public void setDecorator(final ChangeNodeDecorator decorator) {
    myViewer.setChangeDecorator(decorator);
  }

  protected ChangesBrowserBase(
      final Project project,
      @NotNull List<T> changes,
      final boolean capableOfExcludingChanges,
      final boolean highlightProblems,
      @Nullable final Runnable inclusionListener,
      ChangesBrowser.MyUseCase useCase,
      @Nullable VirtualFile toSelect,
      Class<T> clazz) {
    super(new BorderLayout());
    setFocusable(false);

    myClass = clazz;
    myDataIsDirty = false;
    myProject = project;
    myCapableOfExcludingChanges = capableOfExcludingChanges;
    myToSelect = toSelect;

    ChangeNodeDecorator decorator =
        ChangesBrowser.MyUseCase.LOCAL_CHANGES.equals(useCase)
            ? RemoteRevisionsCache.getInstance(myProject).getChangesNodeDecorator()
            : null;

    myViewer =
        new ChangesTreeList<T>(
            myProject,
            changes,
            capableOfExcludingChanges,
            highlightProblems,
            inclusionListener,
            decorator) {
          protected DefaultTreeModel buildTreeModel(
              final List<T> changes, ChangeNodeDecorator changeNodeDecorator) {
            return ChangesBrowserBase.this.buildTreeModel(
                changes, changeNodeDecorator, isShowFlatten());
          }

          protected List<T> getSelectedObjects(final ChangesBrowserNode<T> node) {
            return ChangesBrowserBase.this.getSelectedObjects(node);
          }

          @Nullable
          protected T getLeadSelectedObject(final ChangesBrowserNode node) {
            return ChangesBrowserBase.this.getLeadSelectedObject(node);
          }

          @Override
          public void setScrollPaneBorder(Border border) {
            myViewerScrollPane.setBorder(border);
          }
        };
    myViewerScrollPane = ScrollPaneFactory.createScrollPane(myViewer);
    myHeaderPanel = new JPanel(new BorderLayout());
  }

  protected void init() {
    add(myViewerScrollPane, BorderLayout.CENTER);

    myHeaderPanel.add(createToolbar(), BorderLayout.CENTER);
    add(myHeaderPanel, BorderLayout.NORTH);

    myBottomPanel = new JPanel(new BorderLayout());
    add(myBottomPanel, BorderLayout.SOUTH);

    myViewer.installPopupHandler(myToolBarGroup);
    myViewer.setDoubleClickHandler(getDoubleClickHandler());
  }

  @NotNull
  protected abstract DefaultTreeModel buildTreeModel(
      final List<T> changes, ChangeNodeDecorator changeNodeDecorator, boolean showFlatten);

  @NotNull
  protected abstract List<T> getSelectedObjects(@NotNull ChangesBrowserNode<T> node);

  @Nullable
  protected abstract T getLeadSelectedObject(@NotNull ChangesBrowserNode node);

  @NotNull
  protected Runnable getDoubleClickHandler() {
    return new Runnable() {
      public void run() {
        showDiff();
      }
    };
  }

  protected void setInitialSelection(
      final List<? extends ChangeList> changeLists,
      @NotNull List<T> changes,
      final ChangeList initialListSelection) {
    mySelectedChangeList = initialListSelection;
  }

  public void dispose() {}

  public void addToolbarAction(AnAction action) {
    myToolBarGroup.add(action);
  }

  public void setDiffBottomComponent(JComponent diffBottomComponent) {
    myDiffBottomComponent = diffBottomComponent;
  }

  public void setToggleActionTitle(final String toggleActionTitle) {
    myToggleActionTitle = toggleActionTitle;
  }

  public JPanel getHeaderPanel() {
    return myHeaderPanel;
  }

  public ChangesTreeList<T> getViewer() {
    return myViewer;
  }

  @NotNull
  public JScrollPane getViewerScrollPane() {
    return myViewerScrollPane;
  }

  public void calcData(DataKey key, DataSink sink) {
    if (key == VcsDataKeys.CHANGES) {
      List<Change> list = getSelectedChanges();
      if (list.isEmpty()) list = getAllChanges();
      sink.put(VcsDataKeys.CHANGES, list.toArray(new Change[list.size()]));
    } else if (key == VcsDataKeys.CHANGES_SELECTION) {
      sink.put(VcsDataKeys.CHANGES_SELECTION, getChangesSelection());
    } else if (key == VcsDataKeys.CHANGE_LISTS) {
      sink.put(VcsDataKeys.CHANGE_LISTS, getSelectedChangeLists());
    } else if (key == VcsDataKeys.CHANGE_LEAD_SELECTION) {
      final Change highestSelection =
          ObjectUtils.tryCast(myViewer.getHighestLeadSelection(), Change.class);
      sink.put(
          VcsDataKeys.CHANGE_LEAD_SELECTION,
          (highestSelection == null) ? new Change[] {} : new Change[] {highestSelection});
    } else if (key == CommonDataKeys.VIRTUAL_FILE_ARRAY) {
      sink.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, getSelectedFiles().toArray(VirtualFile[]::new));
    } else if (key == CommonDataKeys.NAVIGATABLE_ARRAY) {
      sink.put(
          CommonDataKeys.NAVIGATABLE_ARRAY, getNavigatableArray(myProject, getSelectedFiles()));
    } else if (VcsDataKeys.IO_FILE_ARRAY.equals(key)) {
      sink.put(VcsDataKeys.IO_FILE_ARRAY, getSelectedIoFiles());
    } else if (key == DATA_KEY) {
      sink.put(DATA_KEY, this);
    } else if (VcsDataKeys.SELECTED_CHANGES_IN_DETAILS.equals(key)) {
      final List<Change> selectedChanges = getSelectedChanges();
      sink.put(
          VcsDataKeys.SELECTED_CHANGES_IN_DETAILS,
          selectedChanges.toArray(new Change[selectedChanges.size()]));
    } else if (UNVERSIONED_FILES_DATA_KEY.equals(key)) {
      sink.put(
          UNVERSIONED_FILES_DATA_KEY,
          getVirtualFiles(myViewer.getSelectionPaths(), UNVERSIONED_FILES_TAG));
    } else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.equals(key)) {
      sink.put(PlatformDataKeys.DELETE_ELEMENT_PROVIDER, myDeleteProvider);
    }
  }

  public void select(List<T> changes) {
    myViewer.select(changes);
  }

  public JComponent getBottomPanel() {
    return myBottomPanel;
  }

  private class ToggleChangeAction extends CheckboxAction {
    public ToggleChangeAction() {
      super(myToggleActionTitle);
    }

    public boolean isSelected(AnActionEvent e) {
      T change = ObjectUtils.tryCast(e.getData(VcsDataKeys.CURRENT_CHANGE), myClass);
      if (change == null) return false;

      return myViewer.isIncluded(change);
    }

    public void setSelected(AnActionEvent e, boolean state) {
      T change = ObjectUtils.tryCast(e.getData(VcsDataKeys.CURRENT_CHANGE), myClass);
      if (change == null) return;

      if (state) {
        myViewer.includeChange(change);
      } else {
        myViewer.excludeChange(change);
      }
    }
  }

  protected void showDiffForChanges(Change[] changesArray, final int indexInSelection) {
    final ShowDiffContext context =
        new ShowDiffContext(isInFrame() ? DiffDialogHints.FRAME : DiffDialogHints.MODAL);

    context.addActions(createDiffActions());
    if (myDiffBottomComponent != null) {
      context.putChainContext(DiffUserDataKeysEx.BOTTOM_PANEL, myDiffBottomComponent);
    }

    updateDiffContext(context);

    ShowDiffAction.showDiffForChange(
        myProject, Arrays.asList(changesArray), indexInSelection, context);
  }

  protected void updateDiffContext(@NotNull ShowDiffContext context) {}

  private boolean canShowDiff() {
    return ShowDiffAction.canShowDiff(myProject, getChangesSelection().getChanges());
  }

  private void showDiff() {
    ChangesSelection selection = getChangesSelection();
    List<Change> changes = selection.getChanges();

    Change[] changesArray = changes.toArray(new Change[changes.size()]);
    showDiffForChanges(changesArray, selection.getIndex());

    afterDiffRefresh();
  }

  @NotNull
  protected ChangesSelection getChangesSelection() {
    final Change leadSelection = ObjectUtils.tryCast(myViewer.getLeadSelection(), Change.class);
    List<Change> changes = getSelectedChanges();

    if (changes.size() < 2) {
      List<Change> allChanges = getAllChanges();
      if (allChanges.size() > 1 || changes.isEmpty()) {
        changes = allChanges;
      }
    }

    if (leadSelection != null) {
      int indexInSelection = changes.indexOf(leadSelection);
      if (indexInSelection == -1) {
        return new ChangesSelection(Collections.singletonList(leadSelection), 0);
      } else {
        return new ChangesSelection(changes, indexInSelection);
      }
    } else {
      return new ChangesSelection(changes, 0);
    }
  }

  protected void afterDiffRefresh() {}

  private static boolean isInFrame() {
    return ModalityState.current().equals(ModalityState.NON_MODAL);
  }

  protected List<AnAction> createDiffActions() {
    List<AnAction> actions = new ArrayList<>();
    if (myCapableOfExcludingChanges) {
      actions.add(new ToggleChangeAction());
    }
    return actions;
  }

  public void rebuildList() {
    myViewer.setChangesToDisplay(getCurrentDisplayedObjects(), myToSelect);
  }

  public void setAlwayExpandList(final boolean value) {
    myViewer.setAlwaysExpandList(value);
  }

  @NotNull
  protected JComponent createToolbar() {
    DefaultActionGroup toolbarGroups = new DefaultActionGroup();
    myToolBarGroup = new DefaultActionGroup();
    toolbarGroups.add(myToolBarGroup);
    buildToolBar(myToolBarGroup);

    toolbarGroups.addSeparator();
    DefaultActionGroup treeActionsGroup = new DefaultActionGroup();
    toolbarGroups.add(treeActionsGroup);
    for (AnAction action : myViewer.getTreeActions()) {
      treeActionsGroup.add(action);
    }

    ActionToolbar toolbar =
        ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, toolbarGroups, true);
    toolbar.setTargetComponent(this);
    return toolbar.getComponent();
  }

  protected void buildToolBar(final DefaultActionGroup toolBarGroup) {
    myDiffAction =
        new DumbAwareAction() {
          public void update(AnActionEvent e) {
            e.getPresentation().setEnabled(canShowDiff());
          }

          public void actionPerformed(AnActionEvent e) {
            showDiff();
          }
        };
    ActionUtil.copyFrom(myDiffAction, "ChangesView.Diff");
    myDiffAction.registerCustomShortcutSet(myViewer, null);
    toolBarGroup.add(myDiffAction);
  }

  @NotNull
  public Set<AbstractVcs> getAffectedVcses() {
    return ChangesUtil.getAffectedVcses(getCurrentDisplayedChanges(), myProject);
  }

  @NotNull
  public abstract List<Change> getCurrentIncludedChanges();

  @NotNull
  public List<Change> getCurrentDisplayedChanges() {
    return mySelectedChangeList != null
        ? ContainerUtil.newArrayList(mySelectedChangeList.getChanges())
        : Collections.emptyList();
  }

  @NotNull
  public abstract List<T> getCurrentDisplayedObjects();

  @NotNull
  public List<VirtualFile> getIncludedUnversionedFiles() {
    return Collections.emptyList();
  }

  public int getUnversionedFilesCount() {
    return 0;
  }

  public ChangeList getSelectedChangeList() {
    return mySelectedChangeList;
  }

  public JComponent getPreferredFocusedComponent() {
    return myViewer.getPreferredFocusedComponent();
  }

  private ChangeList[] getSelectedChangeLists() {
    if (mySelectedChangeList != null) {
      return new ChangeList[] {mySelectedChangeList};
    }
    return null;
  }

  private File[] getSelectedIoFiles() {
    final List<Change> changes = getSelectedChanges();
    final List<File> files = new ArrayList<>();
    for (Change change : changes) {
      final ContentRevision afterRevision = change.getAfterRevision();
      if (afterRevision != null) {
        final FilePath file = afterRevision.getFile();
        final File ioFile = file.getIOFile();
        files.add(ioFile);
      }
    }
    return files.toArray(new File[files.size()]);
  }

  @NotNull
  public abstract List<Change> getSelectedChanges();

  @NotNull
  public abstract List<Change> getAllChanges();

  @NotNull
  protected Stream<VirtualFile> getSelectedFiles() {
    return Stream.concat(
            getAfterRevisionsFiles(getSelectedChanges().stream()),
            getVirtualFiles(myViewer.getSelectionPaths(), null))
        .distinct();
  }

  public AnAction getDiffAction() {
    return myDiffAction;
  }

  public boolean isDataIsDirty() {
    return myDataIsDirty;
  }

  public void setDataIsDirty(boolean dataIsDirty) {
    myDataIsDirty = dataIsDirty;
  }

  public void setSelectionMode(@JdkConstants.TreeSelectionMode int mode) {
    myViewer.setSelectionMode(mode);
  }

  @Contract(pure = true)
  @NotNull
  protected static <T> List<Change> findChanges(@NotNull Collection<T> items) {
    return ContainerUtil.findAll(items, Change.class);
  }

  static boolean isUnderUnversioned(@NotNull ChangesBrowserNode node) {
    return isUnderTag(new TreePath(node.getPath()), UNVERSIONED_FILES_TAG);
  }
}
/** @author max */
public class InspectionResultsView extends JPanel
    implements Disposable, OccurenceNavigator, DataProvider {
  public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView");

  private final Project myProject;
  private final InspectionTree myTree;
  private final Browser myBrowser;
  private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>>
      myGroups = ContainerUtil.newConcurrentMap();
  private final OccurenceNavigator myOccurenceNavigator;
  private volatile InspectionProfile myInspectionProfile;
  private final AnalysisScope myScope;
  @NonNls private static final String HELP_ID = "reference.toolWindows.inspections";
  private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode>
      mySeverityGroupNodes = ContainerUtil.newConcurrentMap();

  private final Splitter mySplitter;
  @NotNull private final GlobalInspectionContextImpl myGlobalInspectionContext;
  private boolean myRerun;
  private volatile boolean myDisposed;

  @NotNull private final InspectionRVContentProvider myProvider;
  private AnAction myIncludeAction;
  private AnAction myExcludeAction;

  public InspectionResultsView(
      @NotNull final Project project,
      final InspectionProfile inspectionProfile,
      @NotNull AnalysisScope scope,
      @NotNull GlobalInspectionContextImpl globalInspectionContext,
      @NotNull InspectionRVContentProvider provider) {
    setLayout(new BorderLayout());

    myProject = project;
    myInspectionProfile = inspectionProfile;
    myScope = scope;
    myGlobalInspectionContext = globalInspectionContext;
    myProvider = provider;

    myTree = new InspectionTree(project, globalInspectionContext);
    initTreeListeners();

    myOccurenceNavigator = initOccurenceNavigator();

    myBrowser = new Browser(this);

    mySplitter =
        new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION);

    mySplitter.setFirstComponent(
        ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT | SideBorder.RIGHT));
    mySplitter.setSecondComponent(myBrowser);

    mySplitter.addPropertyChangeListener(
        new PropertyChangeListener() {
          @Override
          public void propertyChange(PropertyChangeEvent evt) {
            if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) {
              myGlobalInspectionContext.setSplitterProportion(
                  ((Float) evt.getNewValue()).floatValue());
            }
          }
        });
    add(mySplitter, BorderLayout.CENTER);

    myBrowser.addClickListener(
        new Browser.ClickListener() {
          @Override
          public void referenceClicked(final Browser.ClickEvent e) {
            if (e.getEventType() == Browser.ClickEvent.REF_ELEMENT) {
              final RefElement refElement = e.getClickedElement();
              final OpenFileDescriptor descriptor = getOpenFileDescriptor(refElement);
              if (descriptor != null) {
                FileEditorManager.getInstance(project).openTextEditor(descriptor, false);
              }
            } else if (e.getEventType() == Browser.ClickEvent.FILE_OFFSET) {
              final VirtualFile file = e.getFile();
              final OpenFileDescriptor descriptor =
                  new OpenFileDescriptor(project, file, e.getStartOffset());
              final Editor editor =
                  FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
              if (editor != null) {
                final TextAttributes selectionAttributes =
                    EditorColorsManager.getInstance()
                        .getGlobalScheme()
                        .getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
                HighlightManager.getInstance(project)
                    .addRangeHighlight(
                        editor,
                        e.getStartOffset(),
                        e.getEndOffset(),
                        selectionAttributes,
                        true,
                        null);
              }
            }
          }
        });

    createActionsToolbar();
    TreeUtil.selectFirstNode(myTree);
  }

  private void initTreeListeners() {
    myTree
        .getSelectionModel()
        .addTreeSelectionListener(
            new TreeSelectionListener() {
              @Override
              public void valueChanged(TreeSelectionEvent e) {
                syncBrowser();
                if (isAutoScrollMode()) {
                  OpenSourceUtil.openSourcesFrom(
                      DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
                }
              }
            });

    EditSourceOnDoubleClickHandler.install(myTree);

    myTree.addKeyListener(
        new KeyAdapter() {
          @Override
          public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
              OpenSourceUtil.openSourcesFrom(
                  DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
            }
          }
        });

    myTree.addMouseListener(
        new PopupHandler() {
          @Override
          public void invokePopup(Component comp, int x, int y) {
            popupInvoked(comp, x, y);
          }
        });

    SmartExpander.installOn(myTree);
  }

  private OccurenceNavigatorSupport initOccurenceNavigator() {
    return new OccurenceNavigatorSupport(myTree) {
      @Override
      @Nullable
      protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
        if (node instanceof InspectionTreeNode && ((InspectionTreeNode) node).isResolved()) {
          return null;
        }
        if (node instanceof RefElementNode) {
          final RefElementNode refNode = (RefElementNode) node;
          if (refNode.hasDescriptorsUnder()) return null;
          final RefEntity element = refNode.getElement();
          if (element == null || !element.isValid()) return null;
          final CommonProblemDescriptor problem = refNode.getProblem();
          if (problem != null) {
            return navigate(problem);
          }
          if (element instanceof RefElement) {
            return getOpenFileDescriptor((RefElement) element);
          }
        } else if (node instanceof ProblemDescriptionNode) {
          if (!((ProblemDescriptionNode) node).isValid()) return null;
          return navigate(((ProblemDescriptionNode) node).getDescriptor());
        }
        return null;
      }

      @Nullable
      private Navigatable navigate(final CommonProblemDescriptor descriptor) {
        return getSelectedNavigatable(descriptor);
      }

      @Override
      public String getNextOccurenceActionName() {
        return InspectionsBundle.message("inspection.action.go.next");
      }

      @Override
      public String getPreviousOccurenceActionName() {
        return InspectionsBundle.message("inspection.actiongo.prev");
      }
    };
  }

  private void createActionsToolbar() {
    final JComponent leftActionsToolbar = createLeftActionsToolbar();
    final JComponent rightActionsToolbar = createRightActionsToolbar();

    JPanel westPanel = new JPanel(new BorderLayout());
    westPanel.add(leftActionsToolbar, BorderLayout.WEST);
    westPanel.add(rightActionsToolbar, BorderLayout.EAST);
    add(westPanel, BorderLayout.WEST);
  }

  @SuppressWarnings({"NonStaticInitializer"})
  private JComponent createRightActionsToolbar() {
    myIncludeAction =
        new AnAction(InspectionsBundle.message("inspections.result.view.include.action.text")) {
          {
            registerCustomShortcutSet(CommonShortcuts.INSERT, myTree);
          }

          @Override
          public void actionPerformed(AnActionEvent e) {
            final TreePath[] paths = myTree.getSelectionPaths();
            if (paths != null) {
              for (TreePath path : paths) {
                ((InspectionTreeNode) path.getLastPathComponent()).amnesty();
              }
            }
            updateView(false);
          }

          @Override
          public void update(final AnActionEvent e) {
            final TreePath[] paths = myTree.getSelectionPaths();
            e.getPresentation()
                .setEnabled(
                    paths != null
                        && paths.length > 0
                        && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS);
          }
        };

    myExcludeAction =
        new AnAction(InspectionsBundle.message("inspections.result.view.exclude.action.text")) {
          {
            registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree);
          }

          @Override
          public void actionPerformed(final AnActionEvent e) {
            final TreePath[] paths = myTree.getSelectionPaths();
            if (paths != null) {
              for (TreePath path : paths) {
                ((InspectionTreeNode) path.getLastPathComponent()).ignoreElement();
              }
            }
            updateView(false);
          }

          @Override
          public void update(final AnActionEvent e) {
            final TreePath[] path = myTree.getSelectionPaths();
            e.getPresentation().setEnabled(path != null && path.length > 0);
          }
        };

    DefaultActionGroup specialGroup = new DefaultActionGroup();
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this));
    specialGroup.add(
        myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this));
    specialGroup.add(
        myGlobalInspectionContext.getUIOptions().createShowOutdatedProblemsAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createShowDiffOnlyAction(this));
    specialGroup.add(new EditSettingsAction());
    specialGroup.add(new InvokeQuickFixAction(this));
    specialGroup.add(new InspectionsOptionsToolbarAction(this));
    return createToolbar(specialGroup);
  }

  private JComponent createLeftActionsToolbar() {
    final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
    DefaultActionGroup group = new DefaultActionGroup();
    group.add(new RerunAction(this));
    group.add(new CloseAction());
    final TreeExpander treeExpander =
        new TreeExpander() {
          @Override
          public void expandAll() {
            TreeUtil.expandAll(myTree);
          }

          @Override
          public boolean canExpand() {
            return true;
          }

          @Override
          public void collapseAll() {
            TreeUtil.collapseAll(myTree, 0);
          }

          @Override
          public boolean canCollapse() {
            return true;
          }
        };
    group.add(actionsManager.createExpandAllAction(treeExpander, myTree));
    group.add(actionsManager.createCollapseAllAction(treeExpander, myTree));
    group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator()));
    group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator()));
    group.add(myGlobalInspectionContext.createToggleAutoscrollAction());
    group.add(new ExportHTMLAction(this));
    group.add(new ContextHelpAction(HELP_ID));

    return createToolbar(group);
  }

  private static JComponent createToolbar(final DefaultActionGroup specialGroup) {
    return ActionManager.getInstance()
        .createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false)
        .getComponent();
  }

  @Override
  public void dispose() {
    mySplitter.dispose();
    myBrowser.dispose();
    myInspectionProfile = null;
    myDisposed = true;
  }

  private boolean isAutoScrollMode() {
    String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId();
    return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE
        && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION));
  }

  @Nullable
  private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) {
    final VirtualFile[] file = new VirtualFile[1];
    final int[] offset = new int[1];

    ApplicationManager.getApplication()
        .runReadAction(
            new Runnable() {
              @Override
              public void run() {
                PsiElement psiElement = refElement.getElement();
                if (psiElement != null) {
                  final PsiFile containingFile = psiElement.getContainingFile();
                  if (containingFile != null) {
                    file[0] = containingFile.getVirtualFile();
                    offset[0] = psiElement.getTextOffset();
                  }
                } else {
                  file[0] = null;
                }
              }
            });

    if (file[0] != null && file[0].isValid()) {
      return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]);
    }
    return null;
  }

  private void syncBrowser() {
    if (myTree.getSelectionModel().getSelectionCount() != 1) {
      myBrowser.showEmpty();
    } else {
      TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath();
      if (pathSelected != null) {
        final InspectionTreeNode node = (InspectionTreeNode) pathSelected.getLastPathComponent();
        if (node instanceof RefElementNode) {
          final RefElementNode refElementNode = (RefElementNode) node;
          final CommonProblemDescriptor problem = refElementNode.getProblem();
          final RefEntity refSelected = refElementNode.getElement();
          if (problem != null) {
            showInBrowser(refSelected, problem);
          } else {
            showInBrowser(refSelected);
          }
        } else if (node instanceof ProblemDescriptionNode) {
          final ProblemDescriptionNode problemNode = (ProblemDescriptionNode) node;
          showInBrowser(problemNode.getElement(), problemNode.getDescriptor());
        } else if (node instanceof InspectionNode) {
          showInBrowser(((InspectionNode) node).getToolWrapper());
        } else {
          myBrowser.showEmpty();
        }
      }
    }
  }

  private void showInBrowser(final RefEntity refEntity) {
    Cursor currentCursor = getCursor();
    setCursor(new Cursor(Cursor.WAIT_CURSOR));
    myBrowser.showPageFor(refEntity);
    setCursor(currentCursor);
  }

  private void showInBrowser(@NotNull InspectionToolWrapper toolWrapper) {
    Cursor currentCursor = getCursor();
    setCursor(new Cursor(Cursor.WAIT_CURSOR));
    myBrowser.showDescription(toolWrapper);
    setCursor(currentCursor);
  }

  private void showInBrowser(final RefEntity refEntity, CommonProblemDescriptor descriptor) {
    Cursor currentCursor = getCursor();
    setCursor(new Cursor(Cursor.WAIT_CURSOR));
    myBrowser.showPageFor(refEntity, descriptor);
    setCursor(currentCursor);
  }

  @NotNull
  public InspectionNode addTool(
      @NotNull final InspectionToolWrapper toolWrapper,
      HighlightDisplayLevel errorLevel,
      boolean groupedBySeverity) {
    String groupName =
        toolWrapper.getGroupDisplayName().isEmpty()
            ? InspectionProfileEntry.GENERAL_GROUP_NAME
            : toolWrapper.getGroupDisplayName();
    InspectionTreeNode parentNode = getToolParentNode(groupName, errorLevel, groupedBySeverity);
    InspectionNode toolNode = new InspectionNode(toolWrapper);
    boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE;
    myProvider.appendToolNodeContent(
        myGlobalInspectionContext, toolNode, parentNode, showStructure);
    InspectionToolPresentation presentation =
        myGlobalInspectionContext.getPresentation(toolWrapper);
    toolNode =
        presentation.createToolNode(
            myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure);
    ((DefaultInspectionToolPresentation) presentation).setToolNode(toolNode);

    registerActionShortcuts(presentation);
    return toolNode;
  }

  private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) {
    final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY);
    if (fixes != null) {
      for (QuickFixAction fix : fixes) {
        fix.registerCustomShortcutSet(fix.getShortcutSet(), this);
      }
    }
  }

  private void clearTree() {
    myTree.removeAllNodes();
    mySeverityGroupNodes.clear();
  }

  @Nullable
  public String getCurrentProfileName() {
    return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName();
  }

  public InspectionProfile getCurrentProfile() {
    return myInspectionProfile;
  }

  public boolean update() {
    return updateView(true);
  }

  public boolean updateView(boolean strict) {
    if (!strict && !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) {
      myTree.repaint();
      return false;
    }
    clearTree();
    boolean resultsFound = buildTree();
    myTree.restoreExpansionAndSelection();
    return resultsFound;
  }

  private boolean buildTree() {
    InspectionProfile profile = myInspectionProfile;
    boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY;
    myGroups.clear();
    final Map<String, Tools> tools = myGlobalInspectionContext.getTools();
    boolean resultsFound = false;
    for (Tools currentTools : tools.values()) {
      InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool();
      final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName());
      for (ScopeToolState state : myProvider.getTools(currentTools)) {
        InspectionToolWrapper toolWrapper = state.getTool();
        if (myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper)) {
          addTool(
              toolWrapper,
              ((InspectionProfileImpl) profile)
                  .getErrorLevel(key, state.getScope(myProject), myProject),
              isGroupedBySeverity);
          resultsFound = true;
        }
      }
    }
    return resultsFound;
  }

  @NotNull
  private InspectionTreeNode getToolParentNode(
      @NotNull String groupName, HighlightDisplayLevel errorLevel, boolean groupedBySeverity) {
    if (groupName.isEmpty()) {
      return getRelativeRootNode(groupedBySeverity, errorLevel);
    }
    ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel);
    if (map == null) {
      map =
          ConcurrencyUtil.cacheOrGet(
              myGroups, errorLevel, ContainerUtil.<String, InspectionGroupNode>newConcurrentMap());
    }
    InspectionGroupNode group;
    if (groupedBySeverity) {
      group = map.get(groupName);
    } else {
      group = null;
      for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) {
        if ((group = groupMap.get(groupName)) != null) break;
      }
    }
    if (group == null) {
      group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName));
      addChildNodeInEDT(getRelativeRootNode(groupedBySeverity, errorLevel), group);
    }
    return group;
  }

  @NotNull
  private InspectionTreeNode getRelativeRootNode(
      boolean isGroupedBySeverity, HighlightDisplayLevel level) {
    if (isGroupedBySeverity) {
      InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level);
      if (severityGroupNode == null) {
        InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level);
        severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode);
        if (severityGroupNode == newNode) {
          InspectionTreeNode root = myTree.getRoot();
          addChildNodeInEDT(root, severityGroupNode);
        }
      }
      return severityGroupNode;
    }
    return myTree.getRoot();
  }

  private void addChildNodeInEDT(
      @NotNull final DefaultMutableTreeNode root,
      @NotNull final MutableTreeNode severityGroupNode) {
    UIUtil.invokeLaterIfNeeded(
        new Runnable() {
          @Override
          public void run() {
            if (!myDisposed) {
              root.add(severityGroupNode);
            }
          }
        });
  }

  private OccurenceNavigator getOccurenceNavigator() {
    return myOccurenceNavigator;
  }

  @Override
  public boolean hasNextOccurence() {
    return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence();
  }

  @Override
  public boolean hasPreviousOccurence() {
    return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence();
  }

  @Override
  public OccurenceInfo goNextOccurence() {
    return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null;
  }

  @Override
  public OccurenceInfo goPreviousOccurence() {
    return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null;
  }

  @Override
  public String getNextOccurenceActionName() {
    return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : "";
  }

  @Override
  public String getPreviousOccurenceActionName() {
    return myOccurenceNavigator != null
        ? myOccurenceNavigator.getPreviousOccurenceActionName()
        : "";
  }

  @NotNull
  public Project getProject() {
    return myProject;
  }

  @Override
  public Object getData(String dataId) {
    if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID;
    if (DATA_KEY.is(dataId)) return this;
    if (myTree == null) return null;
    TreePath[] paths = myTree.getSelectionPaths();

    if (paths == null || paths.length == 0) return null;

    if (paths.length > 1) {
      if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
        return collectPsiElements();
      }
      return null;
    }

    TreePath path = paths[0];

    InspectionTreeNode selectedNode = (InspectionTreeNode) path.getLastPathComponent();

    if (selectedNode instanceof RefElementNode) {
      final RefElementNode refElementNode = (RefElementNode) selectedNode;
      RefEntity refElement = refElementNode.getElement();
      if (refElement == null) return null;
      final RefEntity item = refElement.getRefManager().getRefinedElement(refElement);

      if (!item.isValid()) return null;

      PsiElement psiElement = item instanceof RefElement ? ((RefElement) item).getElement() : null;
      if (psiElement == null) return null;

      final CommonProblemDescriptor problem = refElementNode.getProblem();
      if (problem != null) {
        if (problem instanceof ProblemDescriptor) {
          psiElement = ((ProblemDescriptor) problem).getPsiElement();
          if (psiElement == null) return null;
        } else {
          return null;
        }
      }

      if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
        return getSelectedNavigatable(problem, psiElement);
      } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
        return psiElement.isValid() ? psiElement : null;
      }
    } else if (selectedNode instanceof ProblemDescriptionNode
        && CommonDataKeys.NAVIGATABLE.is(dataId)) {
      return getSelectedNavigatable(((ProblemDescriptionNode) selectedNode).getDescriptor());
    }

    return null;
  }

  @Nullable
  private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) {
    return getSelectedNavigatable(
        descriptor,
        descriptor instanceof ProblemDescriptor
            ? ((ProblemDescriptor) descriptor).getPsiElement()
            : null);
  }

  @Nullable
  private Navigatable getSelectedNavigatable(
      final CommonProblemDescriptor descriptor, final PsiElement psiElement) {
    if (descriptor instanceof ProblemDescriptorBase) {
      Navigatable navigatable = ((ProblemDescriptorBase) descriptor).getNavigatable();
      if (navigatable != null) {
        return navigatable;
      }
    }
    if (psiElement == null || !psiElement.isValid()) return null;
    PsiFile containingFile = psiElement.getContainingFile();
    VirtualFile virtualFile = containingFile == null ? null : containingFile.getVirtualFile();

    if (virtualFile != null) {
      int startOffset = psiElement.getTextOffset();
      if (descriptor instanceof ProblemDescriptorBase) {
        final TextRange textRange =
            ((ProblemDescriptorBase) descriptor).getTextRangeForNavigation();
        if (textRange != null) {
          if (virtualFile instanceof VirtualFileWindow) {
            virtualFile = ((VirtualFileWindow) virtualFile).getDelegate();
          }
          startOffset = textRange.getStartOffset();
        }
      }
      return new OpenFileDescriptor(myProject, virtualFile, startOffset);
    }
    return null;
  }

  private PsiElement[] collectPsiElements() {
    RefEntity[] refElements = myTree.getSelectedElements();
    List<PsiElement> psiElements = new ArrayList<PsiElement>();
    for (RefEntity refElement : refElements) {
      PsiElement psiElement =
          refElement instanceof RefElement ? ((RefElement) refElement).getElement() : null;
      if (psiElement != null && psiElement.isValid()) {
        psiElements.add(psiElement);
      }
    }

    return PsiUtilCore.toPsiElementArray(psiElements);
  }

  private void popupInvoked(Component component, int x, int y) {
    final TreePath path = myTree.getLeadSelectionPath();

    if (path == null) return;

    final DefaultActionGroup actions = new DefaultActionGroup();
    final ActionManager actionManager = ActionManager.getInstance();
    actions.add(actionManager.getAction(IdeActions.ACTION_EDIT_SOURCE));
    actions.add(actionManager.getAction(IdeActions.ACTION_FIND_USAGES));

    actions.add(myIncludeAction);
    actions.add(myExcludeAction);

    actions.addSeparator();

    final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper();
    if (toolWrapper != null) {
      final QuickFixAction[] quickFixes = myProvider.getQuickFixes(toolWrapper, myTree);
      if (quickFixes != null) {
        for (QuickFixAction quickFixe : quickFixes) {
          actions.add(quickFixe);
        }
      }
      final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
      if (key == null) return; // e.g. DummyEntryPointsTool

      // options
      actions.addSeparator();
      actions.add(new EditSettingsAction());
      final List<AnAction> options = new InspectionsOptionsToolbarAction(this).createActions();
      for (AnAction action : options) {
        actions.add(action);
      }
    }

    actions.addSeparator();
    actions.add(actionManager.getAction(IdeActions.GROUP_VERSION_CONTROLS));

    final ActionPopupMenu menu =
        actionManager.createActionPopupMenu(ActionPlaces.CODE_INSPECTION, actions);
    menu.getComponent().show(component, x, y);
  }

  @NotNull
  public InspectionTree getTree() {
    return myTree;
  }

  @NotNull
  public GlobalInspectionContextImpl getGlobalInspectionContext() {
    return myGlobalInspectionContext;
  }

  @NotNull
  public InspectionRVContentProvider getProvider() {
    return myProvider;
  }

  public boolean isSingleToolInSelection() {
    return myTree != null && myTree.getSelectedToolWrapper() != null;
  }

  public boolean isRerun() {
    boolean rerun = myRerun;
    myRerun = false;
    return rerun;
  }

  private InspectionProfile guessProfileToSelect(
      final InspectionProjectProfileManager profileManager) {
    final Set<InspectionProfile> profiles = new HashSet<InspectionProfile>();
    final RefEntity[] selectedElements = myTree.getSelectedElements();
    for (RefEntity selectedElement : selectedElements) {
      if (selectedElement instanceof RefElement) {
        final RefElement refElement = (RefElement) selectedElement;
        final PsiElement element = refElement.getElement();
        if (element != null) {
          profiles.add(profileManager.getInspectionProfile());
        }
      }
    }
    if (profiles.isEmpty()) {
      return (InspectionProfile) profileManager.getProjectProfileImpl();
    }
    return profiles.iterator().next();
  }

  public boolean isProfileDefined() {
    return myInspectionProfile != null && myInspectionProfile.isEditable();
  }

  public static void showPopup(AnActionEvent e, JBPopup popup) {
    final InputEvent event = e.getInputEvent();
    if (event instanceof MouseEvent) {
      popup.showUnderneathOf(event.getComponent());
    } else {
      popup.showInBestPositionFor(e.getDataContext());
    }
  }

  public AnalysisScope getScope() {
    return myScope;
  }

  private class CloseAction extends AnAction implements DumbAware {
    private CloseAction() {
      super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      myGlobalInspectionContext.close(true);
    }
  }

  private class EditSettingsAction extends AnAction {
    private EditSettingsAction() {
      super(
          InspectionsBundle.message("inspection.action.edit.settings"),
          InspectionsBundle.message("inspection.action.edit.settings"),
          AllIcons.General.Settings);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      final InspectionProjectProfileManager profileManager =
          InspectionProjectProfileManager.getInstance(myProject);
      final InspectionToolWrapper toolWrapper = myTree.getSelectedToolWrapper();
      InspectionProfile inspectionProfile = myInspectionProfile;
      final boolean profileIsDefined = isProfileDefined();
      if (!profileIsDefined) {
        inspectionProfile = guessProfileToSelect(profileManager);
      }

      if (toolWrapper != null) {
        final HighlightDisplayKey key =
            HighlightDisplayKey.find(
                toolWrapper.getShortName()); // do not search for dead code entry point tool
        if (key != null) {
          if (new EditInspectionToolsSettingsAction(key)
                  .editToolSettings(
                      myProject, (InspectionProfileImpl) inspectionProfile, profileIsDefined)
              && profileIsDefined) {
            updateCurrentProfile();
          }
          return;
        }
      }
      if (EditInspectionToolsSettingsAction.editToolSettings(
              myProject, inspectionProfile, profileIsDefined, null)
          && profileIsDefined) {
        updateCurrentProfile();
      }
    }
  }

  public void updateCurrentProfile() {
    final String name = myInspectionProfile.getName();
    myInspectionProfile =
        (InspectionProfile) myInspectionProfile.getProfileManager().getProfile(name);
  }

  private class RerunAction extends AnAction {
    public RerunAction(JComponent comp) {
      super(
          InspectionsBundle.message("inspection.action.rerun"),
          InspectionsBundle.message("inspection.action.rerun"),
          AllIcons.Actions.Rerun);
      registerCustomShortcutSet(CommonShortcuts.getRerun(), comp);
    }

    @Override
    public void update(AnActionEvent e) {
      e.getPresentation().setEnabled(myScope.isValid());
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      rerun();
    }

    private void rerun() {
      myRerun = true;
      if (myScope.isValid()) {
        AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions());
        myGlobalInspectionContext.doInspections(myScope);
      }
    }
  }
}
  public Object getData(String dataId) {
    if (PlatformDataKeys.PROJECT.is(dataId)) {
      return myProject;
    }
    if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
      final FavoritesTreeNodeDescriptor[] selectedNodeDescriptors = getSelectedNodeDescriptors();
      return selectedNodeDescriptors.length == 1 ? selectedNodeDescriptors[0].getElement() : null;
    }
    if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
      final List<Navigatable> selectedElements = getSelectedElements(Navigatable.class);
      return selectedElements.toArray(new Navigatable[selectedElements.size()]);
    }

    if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCutProvider();
    }
    if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCopyProvider();
    }
    if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getPasteProvider();
    }
    if (PlatformDataKeys.HELP_ID.is(dataId)) {
      return myHelpId;
    }
    if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
      PsiElement[] elements = getSelectedPsiElements();
      if (elements.length != 1) {
        return null;
      }
      return elements[0] != null && elements[0].isValid() ? elements[0] : null;
    }
    if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
      final PsiElement[] elements = getSelectedPsiElements();
      ArrayList<PsiElement> result = new ArrayList<PsiElement>();
      for (PsiElement element : elements) {
        if (element.isValid()) {
          result.add(element);
        }
      }
      return result.isEmpty() ? null : result.toArray(new PsiElement[result.size()]);
    }

    if (LangDataKeys.IDE_VIEW.is(dataId)) {
      return myIdeView;
    }

    if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
      return null;
    }

    if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
      Module[] selected = getSelectedModules();
      return selected != null && selected.length == 1 ? selected[0] : null;
    }
    if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
      return getSelectedModules();
    }

    if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
      final Object[] elements = getSelectedNodeElements();
      return elements != null && elements.length >= 1 && elements[0] instanceof Module
          ? myDeleteModuleProvider
          : myDeletePSIElementProvider;
    }
    if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
      final List<ModuleGroup> selectedElements = getSelectedElements(ModuleGroup.class);
      return selectedElements.isEmpty()
          ? null
          : selectedElements.toArray(new ModuleGroup[selectedElements.size()]);
    }
    if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
      final List<LibraryGroupElement> selectedElements =
          getSelectedElements(LibraryGroupElement.class);
      return selectedElements.isEmpty()
          ? null
          : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]);
    }
    if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
      final List<NamedLibraryElement> selectedElements =
          getSelectedElements(NamedLibraryElement.class);
      return selectedElements.isEmpty()
          ? null
          : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]);
    }
    if (CONTEXT_FAVORITES_ROOTS_DATA_KEY.is(dataId)) {
      List<FavoritesTreeNodeDescriptor> result = new ArrayList<FavoritesTreeNodeDescriptor>();
      FavoritesTreeNodeDescriptor[] selectedNodeDescriptors = getSelectedNodeDescriptors();
      for (FavoritesTreeNodeDescriptor selectedNodeDescriptor : selectedNodeDescriptors) {
        FavoritesTreeNodeDescriptor root = selectedNodeDescriptor.getFavoritesRoot();
        if (root != null && !(root.getElement().getValue() instanceof String)) {
          result.add(root);
        }
      }
      return result.toArray(new FavoritesTreeNodeDescriptor[result.size()]);
    }
    if (FAVORITES_LIST_NAME_DATA_KEY.is(dataId)) {
      return myListName;
    }
    FavoritesTreeNodeDescriptor[] descriptors = getSelectedNodeDescriptors();
    if (descriptors.length > 0) {
      List<AbstractTreeNode> nodes = new ArrayList<AbstractTreeNode>();
      for (FavoritesTreeNodeDescriptor descriptor : descriptors) {
        nodes.add(descriptor.getElement());
      }
      return myFavoritesTreeStructure.getDataFromProviders(nodes, dataId);
    }
    return null;
  }
public class DomModelTreeView extends Wrapper implements DataProvider, Disposable {
  public static final DataKey<DomModelTreeView> DATA_KEY =
      DataKey.create("DOM_MODEL_TREE_VIEW_KEY");
  @Deprecated @NonNls public static String DOM_MODEL_TREE_VIEW_KEY = DATA_KEY.getName();
  @NonNls public static String DOM_MODEL_TREE_VIEW_POPUP = "DOM_MODEL_TREE_VIEW_POPUP";

  private final SimpleTree myTree;
  private final AbstractTreeBuilder myBuilder;
  private DomManager myDomManager;
  @Nullable private DomElement myRootElement;

  public DomModelTreeView(@NotNull DomElement rootElement) {
    this(rootElement, rootElement.getManager(), new DomModelTreeStructure(rootElement));
  }

  protected DomModelTreeView(
      DomElement rootElement, DomManager manager, SimpleTreeStructure treeStructure) {
    myDomManager = manager;
    myRootElement = rootElement;
    myTree = new SimpleTree(new DefaultTreeModel(new DefaultMutableTreeNode()));
    myTree.setRootVisible(isRootVisible());
    myTree.setShowsRootHandles(true);
    myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

    ToolTipManager.sharedInstance().registerComponent(myTree);
    TreeUtil.installActions(myTree);

    myBuilder =
        new AbstractTreeBuilder(
            myTree,
            (DefaultTreeModel) myTree.getModel(),
            treeStructure,
            WeightBasedComparator.INSTANCE,
            false);
    Disposer.register(this, myBuilder);

    myBuilder.setNodeDescriptorComparator(null);

    myBuilder.initRootNode();

    add(myTree, BorderLayout.CENTER);

    myTree.addTreeExpansionListener(
        new TreeExpansionListener() {
          @Override
          public void treeExpanded(TreeExpansionEvent event) {
            final SimpleNode simpleNode = myTree.getNodeFor(event.getPath());

            if (simpleNode instanceof AbstractDomElementNode) {
              ((AbstractDomElementNode) simpleNode).setExpanded(true);
            }
          }

          @Override
          public void treeCollapsed(TreeExpansionEvent event) {
            final SimpleNode simpleNode = myTree.getNodeFor(event.getPath());

            if (simpleNode instanceof AbstractDomElementNode) {
              ((AbstractDomElementNode) simpleNode).setExpanded(false);
              simpleNode.update();
            }
          }
        });

    myDomManager.addDomEventListener(
        new DomChangeAdapter() {
          @Override
          protected void elementChanged(DomElement element) {
            if (element.isValid()) {
              queueUpdate(DomUtil.getFile(element).getVirtualFile());
            } else if (element instanceof DomFileElement) {
              final XmlFile xmlFile = ((DomFileElement) element).getFile();
              queueUpdate(xmlFile.getVirtualFile());
            }
          }
        },
        this);

    final Project project = myDomManager.getProject();
    DomElementAnnotationsManager.getInstance(project)
        .addHighlightingListener(
            new DomElementAnnotationsManager.DomHighlightingListener() {
              @Override
              public void highlightingFinished(@NotNull DomFileElement element) {
                if (element.isValid()) {
                  queueUpdate(DomUtil.getFile(element).getVirtualFile());
                }
              }
            },
            this);

    myTree.setPopupGroup(getPopupActions(), DOM_MODEL_TREE_VIEW_POPUP);
  }

  protected boolean isRightFile(final VirtualFile file) {
    return myRootElement == null
        || (myRootElement.isValid()
            && file.equals(DomUtil.getFile(myRootElement).getVirtualFile()));
  }

  private void queueUpdate(final VirtualFile file) {
    if (file == null) return;
    if (getProject().isDisposed()) return;
    ApplicationManager.getApplication()
        .invokeLater(
            () -> {
              if (getProject().isDisposed()) return;
              if (!file.isValid() || isRightFile(file)) {
                myBuilder.updateFromRoot();
              }
            });
  }

  protected boolean isRootVisible() {
    return true;
  }

  public final void updateTree() {
    myBuilder.updateFromRoot();
  }

  public DomElement getRootElement() {
    return myRootElement;
  }

  protected final Project getProject() {
    return myDomManager.getProject();
  }

  public AbstractTreeBuilder getBuilder() {
    return myBuilder;
  }

  @Override
  public void dispose() {}

  public SimpleTree getTree() {
    return myTree;
  }

  protected ActionGroup getPopupActions() {
    DefaultActionGroup group = new DefaultActionGroup();

    group.add(ActionManager.getInstance().getAction("DomElementsTreeView.TreePopup"));
    group.addSeparator();

    group.add(new ExpandAllAction(myTree));
    group.add(new CollapseAllAction(myTree));

    return group;
  }

  @Override
  @Nullable
  public Object getData(String dataId) {
    if (DATA_KEY.is(dataId)) {
      return this;
    }
    final SimpleNode simpleNode = getTree().getSelectedNode();
    if (simpleNode instanceof AbstractDomElementNode) {
      final DomElement domElement = ((AbstractDomElementNode) simpleNode).getDomElement();
      if (domElement != null && domElement.isValid()) {
        if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
          final XmlElement tag = domElement.getXmlElement();
          if (tag instanceof Navigatable) {
            return tag;
          }
        }
      }
    }
    return null;
  }

  public void setSelectedDomElement(final DomElement domElement) {
    if (domElement == null) return;

    final SimpleNode node = getNodeFor(domElement);

    if (node != null) {
      getTree().setSelectedNode(getBuilder(), node, true);
    }
  }

  @Nullable
  private SimpleNode getNodeFor(final DomElement domElement) {
    return visit((SimpleNode) myBuilder.getTreeStructure().getRootElement(), domElement);
  }

  @Nullable
  private SimpleNode visit(SimpleNode simpleNode, DomElement domElement) {
    boolean validCandidate = false;
    if (simpleNode instanceof AbstractDomElementNode) {
      final DomElement nodeElement = ((AbstractDomElementNode) simpleNode).getDomElement();
      if (nodeElement != null) {
        validCandidate = !(simpleNode instanceof DomElementsGroupNode);
        if (validCandidate && nodeElement.equals(domElement)) {
          return simpleNode;
        }
        if (!(nodeElement instanceof MergedObject) && !isParent(nodeElement, domElement)) {
          return null;
        }
      }
    }
    final Object[] childElements = myBuilder.getTreeStructure().getChildElements(simpleNode);
    if (childElements.length == 0 && validCandidate) { // leaf
      return simpleNode;
    }
    for (Object child : childElements) {
      SimpleNode result = visit((SimpleNode) child, domElement);
      if (result != null) {
        return result;
      }
    }
    return validCandidate ? simpleNode : null;
  }

  private static boolean isParent(final DomElement potentialParent, final DomElement domElement) {
    DomElement currParent = domElement;
    while (currParent != null) {
      if (currParent.equals(potentialParent)) return true;

      currParent = currParent.getParent();
    }
    return false;
  }
}
Exemple #19
0
public class ProjectStructureConfigurable extends BaseConfigurable
    implements SearchableConfigurable, Place.Navigator {

  public static final DataKey<ProjectStructureConfigurable> KEY =
      DataKey.create("ProjectStructureConfiguration");

  protected final UIState myUiState = new UIState();
  private JBSplitter mySplitter;
  private JComponent myToolbarComponent;
  @NonNls public static final String CATEGORY = "category";
  private JComponent myToFocus;
  private boolean myWasUiDisposed;
  private ConfigurationErrorsComponent myErrorsComponent;

  public static class UIState {
    public float proportion;
    public float sideProportion;

    public String lastEditedConfigurable;
  }

  private final Project myProject;
  private final FacetStructureConfigurable myFacetStructureConfigurable;
  private final ArtifactsStructureConfigurable myArtifactsStructureConfigurable;

  private History myHistory = new History(this);
  private SidePanel mySidePanel;

  private JPanel myComponent;
  private final Wrapper myDetails = new Wrapper();

  private Configurable mySelectedConfigurable;

  private final ProjectSdksModel myProjectJdksModel = new ProjectSdksModel();

  private ProjectConfigurable myProjectConfig;
  private final ProjectLibrariesConfigurable myProjectLibrariesConfig;
  private final GlobalLibrariesConfigurable myGlobalLibrariesConfig;
  private ModuleStructureConfigurable myModulesConfig;

  private boolean myUiInitialized;

  private final List<Configurable> myName2Config = new ArrayList<Configurable>();
  private final StructureConfigurableContext myContext;
  private final ModulesConfigurator myModuleConfigurator;
  private JdkListConfigurable myJdkListConfig;

  private final JLabel myEmptySelection =
      new JLabel(
          "<html><body><center>Select a setting to view or edit its details here</center></body></html>",
          JLabel.CENTER);

  public ProjectStructureConfigurable(
      final Project project,
      final ProjectLibrariesConfigurable projectLibrariesConfigurable,
      final GlobalLibrariesConfigurable globalLibrariesConfigurable,
      final ModuleStructureConfigurable moduleStructureConfigurable,
      FacetStructureConfigurable facetStructureConfigurable,
      ArtifactsStructureConfigurable artifactsStructureConfigurable) {
    myProject = project;
    myFacetStructureConfigurable = facetStructureConfigurable;
    myArtifactsStructureConfigurable = artifactsStructureConfigurable;

    myModuleConfigurator = new ModulesConfigurator(myProject);
    myContext = new StructureConfigurableContext(myProject, myModuleConfigurator);
    myModuleConfigurator.setContext(myContext);

    myProjectLibrariesConfig = projectLibrariesConfigurable;
    myGlobalLibrariesConfig = globalLibrariesConfigurable;
    myModulesConfig = moduleStructureConfigurable;

    myProjectLibrariesConfig.init(myContext);
    myGlobalLibrariesConfig.init(myContext);
    myModulesConfig.init(myContext);
    myFacetStructureConfigurable.init(myContext);
    if (!project.isDefault()) {
      myArtifactsStructureConfigurable.init(
          myContext, myModulesConfig, myProjectLibrariesConfig, myGlobalLibrariesConfig);
    }

    final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject);
    myUiState.lastEditedConfigurable =
        propertiesComponent.getValue("project.structure.last.edited");
    final String proportion = propertiesComponent.getValue("project.structure.proportion");
    myUiState.proportion = proportion != null ? Float.parseFloat(proportion) : 0;
    final String sideProportion = propertiesComponent.getValue("project.structure.side.proportion");
    myUiState.sideProportion = sideProportion != null ? Float.parseFloat(sideProportion) : 0;
  }

  @Override
  @NotNull
  @NonNls
  public String getId() {
    return "project.structure";
  }

  @Override
  @Nullable
  public Runnable enableSearch(final String option) {
    return null;
  }

  @Override
  @Nls
  public String getDisplayName() {
    return ProjectBundle.message("project.settings.display.name");
  }

  @Override
  @Nullable
  @NonNls
  public String getHelpTopic() {
    return mySelectedConfigurable != null ? mySelectedConfigurable.getHelpTopic() : "";
  }

  @Override
  public JComponent createComponent() {
    myComponent = new MyPanel();

    mySplitter = new JBSplitter(false, .15f);
    mySplitter.setSplitterProportionKey("ProjectStructure.TopLevelElements");
    mySplitter.setHonorComponentsMinimumSize(true);
    if (Registry.is("ide.new.project.settings")) {
      mySplitter.setOnePixelMode();
    }

    initSidePanel();

    final JPanel left =
        new JPanel(new BorderLayout()) {
          @Override
          public Dimension getMinimumSize() {
            final Dimension original = super.getMinimumSize();
            return new Dimension(Math.max(original.width, 100), original.height);
          }
        };

    final DefaultActionGroup toolbarGroup = new DefaultActionGroup();
    toolbarGroup.add(new BackAction(myComponent));
    toolbarGroup.add(new ForwardAction(myComponent));
    final ActionToolbar toolbar =
        ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarGroup, true);
    toolbar.setTargetComponent(myComponent);
    myToolbarComponent = toolbar.getComponent();
    if (Registry.is("ide.new.project.settings")) {
      left.setBackground(UIUtil.getSidePanelColor());
    } else {
      left.add(myToolbarComponent, BorderLayout.NORTH);
    }
    left.add(mySidePanel, BorderLayout.CENTER);

    mySplitter.setFirstComponent(left);
    mySplitter.setSecondComponent(myDetails);

    myComponent.add(mySplitter, BorderLayout.CENTER);
    myErrorsComponent = new ConfigurationErrorsComponent(myProject);
    if (!Registry.is("ide.new.project.settings")) {
      myComponent.add(myErrorsComponent, BorderLayout.SOUTH);
    }

    myUiInitialized = true;

    return myComponent;
  }

  private void initSidePanel() {
    boolean isDefaultProject = myProject == ProjectManager.getInstance().getDefaultProject();

    mySidePanel = new SidePanel(this, myHistory);
    mySidePanel.addSeparator("Project Settings");
    addProjectConfig();
    if (!isDefaultProject) {
      addModulesConfig();
    }
    addProjectLibrariesConfig();

    if (!isDefaultProject) {
      addFacetsConfig();
      addArtifactsConfig();
    }

    ProjectStructureConfigurableContributor[] adders =
        ProjectStructureConfigurableContributor.EP_NAME.getExtensions();
    for (ProjectStructureConfigurableContributor adder : adders) {
      for (Configurable configurable : adder.getExtraProjectConfigurables(myProject, myContext)) {
        addConfigurable(configurable, true);
      }
    }

    mySidePanel.addSeparator("Platform Settings");
    addJdkListConfig();
    addGlobalLibrariesConfig();

    for (ProjectStructureConfigurableContributor adder : adders) {
      for (Configurable configurable : adder.getExtraPlatformConfigurables(myProject, myContext)) {
        addConfigurable(configurable, true);
      }
    }

    if (Registry.is("ide.new.project.settings")) {
      mySidePanel.addSeparator("--");
      addErrorPane();
    }
  }

  private void addArtifactsConfig() {
    addConfigurable(myArtifactsStructureConfigurable, ConfigurableId.ARTIFACTS);
  }

  private void addConfigurable(
      final Configurable configurable, final ConfigurableId configurableId) {
    addConfigurable(configurable, isAvailable(configurableId));
  }

  private boolean isAvailable(ConfigurableId id) {
    for (ProjectStructureConfigurableFilter filter :
        ProjectStructureConfigurableFilter.EP_NAME.getExtensions()) {
      if (!filter.isAvailable(id, myProject)) {
        return false;
      }
    }
    return true;
  }

  public ArtifactsStructureConfigurable getArtifactsStructureConfigurable() {
    return myArtifactsStructureConfigurable;
  }

  private void addFacetsConfig() {
    if (myFacetStructureConfigurable.isVisible()) {
      addConfigurable(myFacetStructureConfigurable, ConfigurableId.FACETS);
    }
  }

  private void addJdkListConfig() {
    if (myJdkListConfig == null) {
      myJdkListConfig = JdkListConfigurable.getInstance(myProject);
      myJdkListConfig.init(myContext);
    }
    addConfigurable(myJdkListConfig, ConfigurableId.JDK_LIST);
  }

  private void addProjectConfig() {
    myProjectConfig =
        new ProjectConfigurable(myProject, myContext, myModuleConfigurator, myProjectJdksModel);
    addConfigurable(myProjectConfig, ConfigurableId.PROJECT);
  }

  private void addProjectLibrariesConfig() {
    addConfigurable(myProjectLibrariesConfig, ConfigurableId.PROJECT_LIBRARIES);
  }

  private void addErrorPane() {
    addConfigurable(new ErrorPaneConfigurable(myProject, myContext), true);
  }

  private void addGlobalLibrariesConfig() {
    addConfigurable(myGlobalLibrariesConfig, ConfigurableId.GLOBAL_LIBRARIES);
  }

  private void addModulesConfig() {
    myModulesConfig = ModuleStructureConfigurable.getInstance(myProject);
    addConfigurable(myModulesConfig, ConfigurableId.MODULES);
  }

  @Override
  public boolean isModified() {
    for (Configurable each : myName2Config) {
      if (each.isModified()) return true;
    }

    return false;
  }

  @Override
  public void apply() throws ConfigurationException {
    for (Configurable each : myName2Config) {
      if (each instanceof BaseStructureConfigurable && each.isModified()) {
        ((BaseStructureConfigurable) each).checkCanApply();
      }
    }
    for (Configurable each : myName2Config) {
      if (each.isModified()) {
        each.apply();
      }
    }

    myContext.getDaemonAnalyzer().clearCaches();
    SwingUtilities.invokeLater(
        new Runnable() {
          public void run() {
            BuildManager.getInstance().scheduleAutoMake();
          }
        });
  }

  @Override
  public void reset() {
    // need this to ensure VFS operations will not block because of storage flushing
    // and other maintenance IO tasks run in background
    HeavyProcessLatch.INSTANCE.processStarted();

    try {
      myWasUiDisposed = false;

      myContext.reset();

      myProjectJdksModel.reset(myProject);

      Configurable toSelect = null;
      for (Configurable each : myName2Config) {
        if (myUiState.lastEditedConfigurable != null
            && myUiState.lastEditedConfigurable.equals(each.getDisplayName())) {
          toSelect = each;
        }
        if (each instanceof MasterDetailsComponent) {
          ((MasterDetailsComponent) each).setHistory(myHistory);
        }
        each.reset();
      }

      myHistory.clear();

      if (toSelect == null && myName2Config.size() > 0) {
        toSelect = myName2Config.iterator().next();
      }

      removeSelected();

      navigateTo(toSelect != null ? createPlaceFor(toSelect) : null, false);

      if (myUiState.proportion > 0) {
        mySplitter.setProportion(myUiState.proportion);
      }
    } finally {
      HeavyProcessLatch.INSTANCE.processFinished();
    }
  }

  public void hideSidePanel() {
    mySplitter.getFirstComponent().setVisible(false);
  }

  @Override
  public void disposeUIResources() {
    if (!myUiInitialized) return;
    final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject);
    propertiesComponent.setValue("project.structure.last.edited", myUiState.lastEditedConfigurable);
    propertiesComponent.setValue(
        "project.structure.proportion", String.valueOf(myUiState.proportion));
    propertiesComponent.setValue(
        "project.structure.side.proportion", String.valueOf(myUiState.sideProportion));

    myWasUiDisposed = true;

    myUiState.proportion = mySplitter.getProportion();
    saveSideProportion();
    myContext.getDaemonAnalyzer().stop();
    for (Configurable each : myName2Config) {
      each.disposeUIResources();
    }
    myContext.clear();
    myName2Config.clear();

    myModuleConfigurator.getFacetsConfigurator().clearMaps();

    Disposer.dispose(myErrorsComponent);

    myUiInitialized = false;
  }

  public boolean isUiInitialized() {
    return myUiInitialized;
  }

  public History getHistory() {
    return myHistory;
  }

  @Override
  public void setHistory(final History history) {
    myHistory = history;
  }

  @Override
  public void queryPlace(@NotNull final Place place) {
    place.putPath(CATEGORY, mySelectedConfigurable);
    Place.queryFurther(mySelectedConfigurable, place);
  }

  public ActionCallback selectProjectGeneralSettings(final boolean requestFocus) {
    Place place = createPlaceFor(myProjectConfig);
    return navigateTo(place, requestFocus);
  }

  public ActionCallback select(
      @Nullable final String moduleToSelect,
      @Nullable String editorNameToSelect,
      final boolean requestFocus) {
    Place place = createModulesPlace();
    if (moduleToSelect != null) {
      final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleToSelect);
      assert module != null;
      place =
          place
              .putPath(ModuleStructureConfigurable.TREE_OBJECT, module)
              .putPath(ModuleEditor.SELECTED_EDITOR_NAME, editorNameToSelect);
    }
    return navigateTo(place, requestFocus);
  }

  public Place createModulesPlace() {
    return createPlaceFor(myModulesConfig);
  }

  public Place createModulePlace(@NotNull Module module) {
    return createModulesPlace().putPath(ModuleStructureConfigurable.TREE_OBJECT, module);
  }

  public ActionCallback select(@Nullable final Facet facetToSelect, final boolean requestFocus) {
    Place place = createModulesPlace();
    if (facetToSelect != null) {
      place = place.putPath(ModuleStructureConfigurable.TREE_OBJECT, facetToSelect);
    }
    return navigateTo(place, requestFocus);
  }

  public ActionCallback select(@NotNull Sdk sdk, final boolean requestFocus) {
    Place place = createPlaceFor(myJdkListConfig);
    place.putPath(BaseStructureConfigurable.TREE_NAME, sdk.getName());
    return navigateTo(place, requestFocus);
  }

  public ActionCallback selectGlobalLibraries(final boolean requestFocus) {
    Place place = createPlaceFor(myGlobalLibrariesConfig);
    return navigateTo(place, requestFocus);
  }

  public ActionCallback selectProjectOrGlobalLibrary(
      @NotNull Library library, boolean requestFocus) {
    Place place = createProjectOrGlobalLibraryPlace(library);
    return navigateTo(place, requestFocus);
  }

  public Place createProjectOrGlobalLibraryPlace(Library library) {
    Place place = createPlaceFor(getConfigurableFor(library));
    place.putPath(BaseStructureConfigurable.TREE_NAME, library.getName());
    return place;
  }

  public ActionCallback select(@Nullable Artifact artifact, boolean requestFocus) {
    Place place = createArtifactPlace(artifact);
    return navigateTo(place, requestFocus);
  }

  public Place createArtifactPlace(Artifact artifact) {
    Place place = createPlaceFor(myArtifactsStructureConfigurable);
    if (artifact != null) {
      place.putPath(BaseStructureConfigurable.TREE_NAME, artifact.getName());
    }
    return place;
  }

  public ActionCallback select(
      @NotNull LibraryOrderEntry libraryOrderEntry, final boolean requestFocus) {
    final Library lib = libraryOrderEntry.getLibrary();
    if (lib == null || lib.getTable() == null) {
      return selectOrderEntry(libraryOrderEntry.getOwnerModule(), libraryOrderEntry);
    }
    Place place = createPlaceFor(getConfigurableFor(lib));
    place.putPath(BaseStructureConfigurable.TREE_NAME, libraryOrderEntry.getLibraryName());
    return navigateTo(place, requestFocus);
  }

  public ActionCallback selectOrderEntry(
      @NotNull final Module module, @Nullable final OrderEntry orderEntry) {
    return ModuleStructureConfigurable.getInstance(myProject).selectOrderEntry(module, orderEntry);
  }

  @Override
  public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) {
    final Configurable toSelect = (Configurable) place.getPath(CATEGORY);

    JComponent detailsContent = myDetails.getTargetComponent();

    if (mySelectedConfigurable != toSelect) {
      if (mySelectedConfigurable instanceof BaseStructureConfigurable) {
        ((BaseStructureConfigurable) mySelectedConfigurable).onStructureUnselected();
      }
      saveSideProportion();
      removeSelected();

      if (toSelect != null) {
        detailsContent = toSelect.createComponent();
        myDetails.setContent(detailsContent);
      }

      mySelectedConfigurable = toSelect;
      if (mySelectedConfigurable != null) {
        myUiState.lastEditedConfigurable = mySelectedConfigurable.getDisplayName();
      }

      if (toSelect instanceof MasterDetailsComponent) {
        final MasterDetailsComponent masterDetails = (MasterDetailsComponent) toSelect;
        if (myUiState.sideProportion > 0) {
          masterDetails.getSplitter().setProportion(myUiState.sideProportion);
        }
        masterDetails.setHistory(myHistory);
      }

      if (toSelect instanceof DetailsComponent.Facade) {
        ((DetailsComponent.Facade) toSelect)
            .getDetailsComponent()
            .setBannerMinHeight(myToolbarComponent.getPreferredSize().height);
      }

      if (toSelect instanceof BaseStructureConfigurable) {
        ((BaseStructureConfigurable) toSelect).onStructureSelected();
      }
    }

    if (detailsContent != null) {
      JComponent toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(detailsContent);
      if (toFocus == null) {
        toFocus = detailsContent;
      }
      if (requestFocus) {
        myToFocus = toFocus;
        UIUtil.requestFocus(toFocus);
      }
    }

    final ActionCallback result = new ActionCallback();
    Place.goFurther(toSelect, place, requestFocus).notifyWhenDone(result);

    myDetails.revalidate();
    myDetails.repaint();

    if (toSelect != null) {
      mySidePanel.select(createPlaceFor(toSelect));
    }

    if (!myHistory.isNavigatingNow() && mySelectedConfigurable != null) {
      myHistory.pushQueryPlace();
    }

    return result;
  }

  private void saveSideProportion() {
    if (mySelectedConfigurable instanceof MasterDetailsComponent) {
      myUiState.sideProportion =
          ((MasterDetailsComponent) mySelectedConfigurable).getSplitter().getProportion();
    }
  }

  private void removeSelected() {
    myDetails.removeAll();
    mySelectedConfigurable = null;
    myUiState.lastEditedConfigurable = null;

    myDetails.add(myEmptySelection, BorderLayout.CENTER);
  }

  public static ProjectStructureConfigurable getInstance(final Project project) {
    return ServiceManager.getService(project, ProjectStructureConfigurable.class);
  }

  public ProjectSdksModel getProjectJdksModel() {
    return myProjectJdksModel;
  }

  public JdkListConfigurable getJdkConfig() {
    return myJdkListConfig;
  }

  public ProjectLibrariesConfigurable getProjectLibrariesConfig() {
    return myProjectLibrariesConfig;
  }

  public GlobalLibrariesConfigurable getGlobalLibrariesConfig() {
    return myGlobalLibrariesConfig;
  }

  public ModuleStructureConfigurable getModulesConfig() {
    return myModulesConfig;
  }

  public ProjectConfigurable getProjectConfig() {
    return myProjectConfig;
  }

  private void addConfigurable(Configurable configurable, boolean addToSidePanel) {
    myName2Config.add(configurable);

    if (addToSidePanel) {
      mySidePanel.addPlace(
          createPlaceFor(configurable), new Presentation(configurable.getDisplayName()));
    }
  }

  private static Place createPlaceFor(final Configurable configurable) {
    return new Place().putPath(CATEGORY, configurable);
  }

  public StructureConfigurableContext getContext() {
    return myContext;
  }

  private class MyPanel extends JPanel implements DataProvider {
    public MyPanel() {
      super(new BorderLayout());
    }

    @Override
    @Nullable
    public Object getData(@NonNls final String dataId) {
      if (KEY.is(dataId)) {
        return ProjectStructureConfigurable.this;
      } else if (History.KEY.is(dataId)) {
        return getHistory();
      } else {
        return null;
      }
    }

    @Override
    public Dimension getPreferredSize() {
      return new Dimension(1024, 768);
    }
  }

  public BaseLibrariesConfigurable getConfigurableFor(final Library library) {
    if (LibraryTablesRegistrar.PROJECT_LEVEL.equals(library.getTable().getTableLevel())) {
      return myProjectLibrariesConfig;
    } else {
      return myGlobalLibrariesConfig;
    }
  }

  @Override
  public JComponent getPreferredFocusedComponent() {
    return myToFocus;
  }

  protected void hideErrorsComponent() {
    myErrorsComponent.setVisible(false);
  }
}