@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());
       }
     }
   }
 }
Esempio n. 3
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);
          }
        }
      }
    }
Esempio n. 5
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());
            }
          }
        });
  }
}
Esempio n. 6
0
 @Nullable
 public static XDebuggerTree getTree(DataContext context) {
   return XDEBUGGER_TREE_KEY.getData(context);
 }
/** @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;
    }
  }
}