a8c8f0de563162e059c0e901763b025fb7272793
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / classpath / ClasspathPanelImpl.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.roots.ui.configuration.classpath;
3
4 import com.intellij.ide.JavaUiBundle;
5 import com.intellij.openapi.actionSystem.*;
6 import com.intellij.openapi.diagnostic.Logger;
7 import com.intellij.openapi.module.Module;
8 import com.intellij.openapi.project.Project;
9 import com.intellij.openapi.projectRoots.Sdk;
10 import com.intellij.openapi.roots.*;
11 import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil;
12 import com.intellij.openapi.roots.libraries.Library;
13 import com.intellij.openapi.roots.libraries.LibraryTable;
14 import com.intellij.openapi.roots.libraries.LibraryTablePresentation;
15 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
16 import com.intellij.openapi.roots.ui.CellAppearanceEx;
17 import com.intellij.openapi.roots.ui.OrderEntryAppearanceService;
18 import com.intellij.openapi.roots.ui.configuration.LibraryTableModifiableModelProvider;
19 import com.intellij.openapi.roots.ui.configuration.ModuleConfigurationState;
20 import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
21 import com.intellij.openapi.roots.ui.configuration.libraries.LibraryEditingUtil;
22 import com.intellij.openapi.roots.ui.configuration.libraryEditor.EditExistingLibraryDialog;
23 import com.intellij.openapi.roots.ui.configuration.projectRoot.ConvertModuleLibraryToRepositoryLibraryAction;
24 import com.intellij.openapi.roots.ui.configuration.projectRoot.FindUsagesInProjectStructureActionBase;
25 import com.intellij.openapi.roots.ui.configuration.projectRoot.ModuleStructureConfigurable;
26 import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
27 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement;
28 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement;
29 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
30 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.SdkProjectStructureElement;
31 import com.intellij.openapi.ui.ComboBox;
32 import com.intellij.openapi.ui.ComboBoxTableRenderer;
33 import com.intellij.openapi.ui.popup.JBPopup;
34 import com.intellij.openapi.ui.popup.JBPopupFactory;
35 import com.intellij.openapi.ui.popup.PopupStep;
36 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
37 import com.intellij.openapi.wm.IdeFocusManager;
38 import com.intellij.ui.*;
39 import com.intellij.ui.awt.RelativePoint;
40 import com.intellij.ui.scale.JBUIScale;
41 import com.intellij.ui.table.JBTable;
42 import com.intellij.util.EventDispatcher;
43 import com.intellij.util.IconUtil;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.ui.TextTransferable;
46 import gnu.trove.TIntArrayList;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import javax.swing.*;
51 import javax.swing.border.Border;
52 import javax.swing.table.TableCellRenderer;
53 import javax.swing.table.TableColumn;
54 import javax.swing.table.TableModel;
55 import javax.swing.table.TableRowSorter;
56 import java.awt.*;
57 import java.awt.datatransfer.Transferable;
58 import java.awt.event.ActionEvent;
59 import java.awt.event.ActionListener;
60 import java.awt.event.KeyEvent;
61 import java.awt.event.MouseEvent;
62 import java.util.List;
63 import java.util.*;
64
65 import static com.intellij.openapi.wm.IdeFocusManager.getGlobalInstance;
66
67 public class ClasspathPanelImpl extends JPanel implements ClasspathPanel {
68   private static final Logger LOG = Logger.getInstance(ClasspathPanelImpl.class);
69   private final JBTable myEntryTable;
70   private final ClasspathTableModel myModel;
71   private final EventDispatcher<OrderPanelListener> myListeners = EventDispatcher.create(OrderPanelListener.class);
72   private List<AddItemPopupAction<?>> myPopupActions = null;
73   private final AnActionButton myEditButton;
74   private final ModuleConfigurationState myState;
75   private AnActionButton myRemoveButton;
76
77   public ClasspathPanelImpl(ModuleConfigurationState state) {
78     super(new BorderLayout());
79
80     myState = state;
81     myModel = new ClasspathTableModel(state, getStructureConfigurableContext());
82     myEntryTable = new JBTable(myModel) {
83       @Override
84       protected TableRowSorter<TableModel> createRowSorter(TableModel model) {
85         return new DefaultColumnInfoBasedRowSorter(model) {
86           @Override
87           public void toggleSortOrder(int column) {
88             if (isSortable(column)) {
89               SortKey oldKey = ContainerUtil.getFirstItem(getSortKeys());
90               SortOrder oldOrder;
91               if (oldKey == null || oldKey.getColumn() != column) {
92                 oldOrder = SortOrder.UNSORTED;
93               }
94               else {
95                 oldOrder = oldKey.getSortOrder();
96               }
97               setSortKeys(Collections.singletonList(new SortKey(column, getNextSortOrder(oldOrder))));
98             }
99           }
100         };
101       }
102     };
103     myEntryTable.setShowGrid(false);
104     myEntryTable.setDragEnabled(false);
105     myEntryTable.setIntercellSpacing(new Dimension(0, 0));
106
107     myEntryTable.setDefaultRenderer(ClasspathTableItem.class, new TableItemRenderer(getStructureConfigurableContext()));
108     myEntryTable.setDefaultRenderer(Boolean.class, new ExportFlagRenderer(myEntryTable.getDefaultRenderer(Boolean.class)));
109
110     JComboBox scopeEditor = new ComboBox<>(new EnumComboBoxModel<>(DependencyScope.class));
111     myEntryTable.setDefaultEditor(DependencyScope.class, new DefaultCellEditor(scopeEditor));
112     myEntryTable.setDefaultRenderer(DependencyScope.class, new ComboBoxTableRenderer<DependencyScope>(DependencyScope.values()) {
113         @Override
114         protected String getTextFor(@NotNull final DependencyScope value) {
115           return value.getDisplayName();
116         }
117       });
118
119     myEntryTable.setTransferHandler(new TransferHandler() {
120       @Nullable
121       @Override
122       protected Transferable createTransferable(JComponent c) {
123         OrderEntry entry = getSelectedEntry();
124         if (entry == null) {
125           return null;
126         }
127         return new TextTransferable(entry.getPresentableName());
128       }
129
130       @Override
131       public int getSourceActions(JComponent c) {
132         return COPY;
133       }
134     });
135
136     myEntryTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
137
138     new SpeedSearchBase<JBTable>(myEntryTable) {
139       @Override
140       public int getSelectedIndex() {
141         return myEntryTable.getSelectedRow();
142       }
143
144       @Override
145       protected int convertIndexToModel(int viewIndex) {
146         return myEntryTable.convertRowIndexToModel(viewIndex);
147       }
148
149       @Override
150       public Object @NotNull [] getAllElements() {
151         final int count = myModel.getRowCount();
152         Object[] elements = new Object[count];
153         for (int idx = 0; idx < count; idx++) {
154           elements[idx] = myModel.getItem(idx);
155         }
156         return elements;
157       }
158
159       @Override
160       public String getElementText(Object element) {
161         return getCellAppearance((ClasspathTableItem<?>)element, getStructureConfigurableContext(), false).getText();
162       }
163
164       @Override
165       public void selectElement(Object element, String selectedText) {
166         final int count = myModel.getRowCount();
167         for (int row = 0; row < count; row++) {
168           if (element.equals(myModel.getItem(row))) {
169             final int viewRow = myEntryTable.convertRowIndexToView(row);
170             myEntryTable.getSelectionModel().setSelectionInterval(viewRow, viewRow);
171             TableUtil.scrollSelectionToVisible(myEntryTable);
172             break;
173           }
174         }
175       }
176     };
177     setFixedColumnWidth(ClasspathTableModel.EXPORT_COLUMN, ClasspathTableModel.getExportColumnName());
178     setFixedColumnWidth(ClasspathTableModel.SCOPE_COLUMN, DependencyScope.COMPILE.toString() + "     ");  // leave space for combobox border
179     myEntryTable.getTableHeader().getColumnModel().getColumn(ClasspathTableModel.ITEM_COLUMN).setPreferredWidth(10000); // consume all available space
180
181     myEntryTable.registerKeyboardAction(
182       new ActionListener() {
183         @Override
184         public void actionPerformed(ActionEvent e) {
185           final int[] selectedRows = myEntryTable.getSelectedRows();
186           boolean currentlyMarked = true;
187           for (final int selectedRow : selectedRows) {
188             final ClasspathTableItem<?> item = getItemAt(selectedRow);
189             if (selectedRow < 0 || !item.isExportable()) {
190               return;
191             }
192             currentlyMarked &= item.isExported();
193           }
194           for (final int selectedRow : selectedRows) {
195             getItemAt(selectedRow).setExported(!currentlyMarked);
196           }
197           myModel.fireTableDataChanged();
198           TableUtil.selectRows(myEntryTable, selectedRows);
199         }
200       },
201       KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0),
202       WHEN_FOCUSED
203     );
204
205     myEditButton = new AnActionButton(JavaUiBundle.message("module.classpath.button.edit"), null, IconUtil.getEditIcon()) {
206       @Override
207       public void actionPerformed(@NotNull AnActionEvent e) {
208         doEdit();
209       }
210
211       @Override
212       public boolean isEnabled() {
213         ClasspathTableItem<?> selectedItem = getSelectedItem();
214         return selectedItem != null && selectedItem.isEditable();
215       }
216
217       @Override
218       public boolean isDumbAware() {
219         return true;
220       }
221     };
222     add(createTableWithButtons(), BorderLayout.CENTER);
223
224     if (myEntryTable.getRowCount() > 0) {
225       myEntryTable.getSelectionModel().setSelectionInterval(0,0);
226     }
227
228     new DoubleClickListener() {
229       @Override
230       protected boolean onDoubleClick(@NotNull MouseEvent e) {
231         navigate(true);
232         return true;
233       }
234     }.installOn(myEntryTable);
235
236     DefaultActionGroup actionGroup = new DefaultActionGroup();
237     final AnAction navigateAction = new AnAction(JavaUiBundle.message("classpath.panel.navigate.action.text")) {
238       @Override
239       public void actionPerformed(@NotNull AnActionEvent e) {
240         navigate(false);
241       }
242
243       @Override
244       public void update(@NotNull AnActionEvent e) {
245         final Presentation presentation = e.getPresentation();
246         presentation.setEnabled(false);
247         final OrderEntry entry = getSelectedEntry();
248         if (entry != null && entry.isValid()){
249           if (!(entry instanceof ModuleSourceOrderEntry)){
250             presentation.setEnabled(true);
251           }
252         }
253       }
254     };
255     navigateAction.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(),
256                                              myEntryTable);
257     actionGroup.add(myEditButton);
258     actionGroup.add(myRemoveButton);
259     actionGroup.add(navigateAction);
260     actionGroup.add(new InlineModuleDependencyAction(this));
261     actionGroup.add(new MyFindUsagesAction());
262     actionGroup.add(new AnalyzeModuleDependencyAction(this));
263     addChangeLibraryLevelAction(actionGroup, LibraryTablesRegistrar.PROJECT_LEVEL);
264     addChangeLibraryLevelAction(actionGroup, LibraryTablesRegistrar.APPLICATION_LEVEL);
265     addChangeLibraryLevelAction(actionGroup, LibraryTableImplUtil.MODULE_LEVEL);
266     actionGroup.add(new ConvertModuleLibraryToRepositoryLibraryAction(this, getStructureConfigurableContext()));
267     PopupHandler.installPopupHandler(myEntryTable, actionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance());
268   }
269
270   @NotNull
271   private static SortOrder getNextSortOrder(@NotNull SortOrder order) {
272     switch (order) {
273       case ASCENDING:
274         return SortOrder.DESCENDING;
275       case DESCENDING:
276         return SortOrder.UNSORTED;
277       case UNSORTED:
278       default:
279         return SortOrder.ASCENDING;
280     }
281   }
282
283   private ClasspathTableItem<?> getItemAt(int selectedRow) {
284     return myModel.getItem(myEntryTable.convertRowIndexToModel(selectedRow));
285   }
286
287   private void addChangeLibraryLevelAction(DefaultActionGroup actionGroup, String tableLevel) {
288     final LibraryTablePresentation presentation = LibraryEditingUtil.getLibraryTablePresentation(getProject(), tableLevel);
289     actionGroup.add(new ChangeLibraryLevelInClasspathAction(this, presentation.getDisplayName(true), tableLevel));
290   }
291
292   @Override
293   @Nullable
294   public OrderEntry getSelectedEntry() {
295     ClasspathTableItem<?> item = getSelectedItem();
296     return item != null ? item.getEntry() : null;
297   }
298
299   @Nullable
300   private ClasspathTableItem<?> getSelectedItem() {
301     if (myEntryTable.getSelectedRowCount() != 1) return null;
302     return getItemAt(myEntryTable.getSelectedRow());
303   }
304
305   private void setFixedColumnWidth(final int columnIndex, String sampleText) {
306     final TableColumn column = myEntryTable.getTableHeader().getColumnModel().getColumn(columnIndex);
307     final FontMetrics fontMetrics = myEntryTable.getFontMetrics(myEntryTable.getFont());
308     final int width = fontMetrics.stringWidth(" " + sampleText + " ") + JBUIScale.scale(4);
309     column.setPreferredWidth(width);
310     column.setMinWidth(width);
311     column.setResizable(false);
312   }
313
314   @Override
315   public void navigate(boolean openLibraryEditor) {
316     final OrderEntry entry = getSelectedEntry();
317     final ProjectStructureConfigurable rootConfigurable = ProjectStructureConfigurable.getInstance(myState.getProject());
318     if (entry instanceof ModuleOrderEntry){
319       Module module = ((ModuleOrderEntry)entry).getModule();
320       if (module != null) {
321         rootConfigurable.select(module.getName(), null, true);
322       }
323     }
324     else if (entry instanceof LibraryOrderEntry){
325       if (!openLibraryEditor && !((LibraryOrderEntry)entry).getLibraryLevel().equals(LibraryTableImplUtil.MODULE_LEVEL)) {
326         rootConfigurable.select((LibraryOrderEntry)entry, true);
327       }
328       else {
329         doEdit();
330       }
331     }
332     else if (entry instanceof JdkOrderEntry) {
333       Sdk jdk = ((JdkOrderEntry)entry).getJdk();
334       if (jdk != null) {
335         rootConfigurable.select(jdk, true);
336       }
337     }
338   }
339
340
341   private JComponent createTableWithButtons() {
342     final ClasspathPanelAction removeAction = new ClasspathPanelAction(this) {
343       @Override
344       public void run() {
345         removeSelectedItems(TableUtil.removeSelectedItems(myEntryTable));
346       }
347     };
348
349     //addButton.setShortcut(CustomShortcutSet.fromString("alt A", "INSERT"));
350     //removeButton.setShortcut(CustomShortcutSet.fromString("alt DELETE"));
351     //upButton.setShortcut(CustomShortcutSet.fromString("alt UP"));
352     //downButton.setShortcut(CustomShortcutSet.fromString("alt DOWN"));
353
354     final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myEntryTable);
355     AnActionButtonUpdater moveUpDownUpdater = e -> {
356       for (RowSorter.SortKey key : myEntryTable.getRowSorter().getSortKeys()) {
357         if (key.getSortOrder() != SortOrder.UNSORTED) {
358           return false;
359         }
360       }
361       return true;
362     };
363     decorator.setAddAction(new AnActionButtonRunnable() {
364       @Override
365       public void run(AnActionButton button) {
366         initPopupActions();
367         final JBPopup popup = JBPopupFactory.getInstance().createListPopup(
368           new BaseListPopupStep<AddItemPopupAction<?>>(null, myPopupActions) {
369             @Override
370             public Icon getIconFor(AddItemPopupAction<?> aValue) {
371               return aValue.getIcon();
372             }
373
374             @Override
375             public boolean hasSubstep(AddItemPopupAction<?> selectedValue) {
376               return selectedValue.hasSubStep();
377             }
378
379             @Override
380             public boolean isMnemonicsNavigationEnabled() {
381               return true;
382             }
383
384             @Override
385             public PopupStep onChosen(final AddItemPopupAction<?> selectedValue, final boolean finalChoice) {
386               if (selectedValue.hasSubStep()) {
387                 return selectedValue.createSubStep();
388               }
389               return doFinalStep(() -> selectedValue.execute());
390             }
391
392             @Override
393             @NotNull
394             public String getTextFor(AddItemPopupAction<?> value) {
395               return "&" + value.getIndex() + "  " + value.getTitle();
396             }
397           });
398         popup.show(button.getPreferredPopupPoint());
399       }
400     })
401       .setRemoveAction(new AnActionButtonRunnable() {
402         @Override
403         public void run(AnActionButton button) {
404           removeAction.actionPerformed(null);
405         }
406       })
407       .setRemoveActionUpdater(e -> {
408         final int[] selectedRows = myEntryTable.getSelectedRows();
409         for (final int selectedRow : selectedRows) {
410           if (!getItemAt(selectedRow).isRemovable()) {
411             return false;
412           }
413         }
414         return selectedRows.length > 0;
415       })
416       .setMoveUpAction(new AnActionButtonRunnable() {
417         @Override
418         public void run(AnActionButton button) {
419           moveSelectedRows(-1);
420         }
421       })
422       .setMoveUpActionUpdater(moveUpDownUpdater)
423       .setMoveUpActionName(JavaUiBundle.message("action.text.class.path.move.up"))
424       .setMoveDownAction(new AnActionButtonRunnable() {
425         @Override
426         public void run(AnActionButton button) {
427           moveSelectedRows(+1);
428         }
429       })
430       .setMoveDownActionUpdater(moveUpDownUpdater)
431       .setMoveDownActionName(JavaUiBundle.message("action.text.class.path.move.down"))
432       .addExtraAction(myEditButton);
433
434     final JPanel panel = decorator.createPanel();
435     myRemoveButton = ToolbarDecorator.findRemoveButton(panel);
436     return panel;
437   }
438
439   private void doEdit() {
440     final OrderEntry entry = getSelectedEntry();
441     if (!(entry instanceof LibraryOrderEntry)) return;
442
443     final Library library = ((LibraryOrderEntry)entry).getLibrary();
444     if (library == null) {
445       return;
446     }
447     final LibraryTable table = library.getTable();
448     final String tableLevel = table != null ? table.getTableLevel() : LibraryTableImplUtil.MODULE_LEVEL;
449     final LibraryTablePresentation presentation = LibraryEditingUtil.getLibraryTablePresentation(getProject(), tableLevel);
450     final LibraryTableModifiableModelProvider provider = getModifiableModelProvider(tableLevel);
451     EditExistingLibraryDialog dialog = EditExistingLibraryDialog.createDialog(this, provider, library, myState.getProject(),
452                                                                               presentation, getStructureConfigurableContext());
453     dialog.setContextModule(getRootModel().getModule());
454     dialog.show();
455     myEntryTable.repaint();
456     ModuleStructureConfigurable.getInstance(myState.getProject()).getTree().repaint();
457   }
458
459   private void removeSelectedItems(final List removedRows) {
460     if (removedRows.isEmpty()) {
461       return;
462     }
463     for (final Object removedRow : removedRows) {
464       final ClasspathTableItem<?> item = (ClasspathTableItem<?>)((Object[])removedRow)[ClasspathTableModel.ITEM_COLUMN];
465       final OrderEntry orderEntry = item.getEntry();
466       if (orderEntry == null) {
467         continue;
468       }
469
470       getRootModel().removeOrderEntry(orderEntry);
471     }
472     final int[] selectedRows = myEntryTable.getSelectedRows();
473     myModel.fireTableDataChanged();
474     TableUtil.selectRows(myEntryTable, selectedRows);
475     final StructureConfigurableContext context = ModuleStructureConfigurable.getInstance(myState.getProject()).getContext();
476     context.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(context, getRootModel().getModule()));
477   }
478
479   @Override
480   @NotNull
481   public LibraryTableModifiableModelProvider getModifiableModelProvider(@NotNull String tableLevel) {
482     if (LibraryTableImplUtil.MODULE_LEVEL.equals(tableLevel)) {
483       final LibraryTable moduleLibraryTable = getRootModel().getModuleLibraryTable();
484       return new LibraryTableModifiableModelProvider() {
485         @Override
486         public LibraryTable.ModifiableModel getModifiableModel() {
487           return moduleLibraryTable.getModifiableModel();
488         }
489       };
490     }
491     else {
492       return getStructureConfigurableContext().createModifiableModelProvider(tableLevel);
493     }
494   }
495
496   @Override
497   public void runClasspathPanelAction(Runnable action) {
498     try {
499       disableModelUpdate();
500       action.run();
501     }
502     finally {
503       enableModelUpdate();
504       getGlobalInstance().doWhenFocusSettlesDown(() -> getGlobalInstance().requestFocus(myEntryTable, true));
505     }
506   }
507
508   @Override
509   public void addItems(List<? extends ClasspathTableItem<?>> toAdd) {
510     for (ClasspathTableItem<?> item : toAdd) {
511       myModel.addRow(item);
512     }
513     TIntArrayList toSelect = new TIntArrayList();
514     for (int i = myModel.getRowCount() - toAdd.size(); i < myModel.getRowCount(); i++) {
515       toSelect.add(myEntryTable.convertRowIndexToView(i));
516     }
517     TableUtil.selectRows(myEntryTable, toSelect.toNativeArray());
518     TableUtil.scrollSelectionToVisible(myEntryTable);
519
520     final StructureConfigurableContext context = ModuleStructureConfigurable.getInstance(myState.getProject()).getContext();
521     context.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(context, getRootModel().getModule()));
522   }
523
524   @Override
525   public ModifiableRootModel getRootModel() {
526     return myState.getRootModel();
527   }
528
529   @Override
530   public Project getProject() {
531     return myState.getProject();
532   }
533
534   @Override
535   public ModuleConfigurationState getModuleConfigurationState() {
536     return myState;
537   }
538
539   @Override
540   public JComponent getComponent() {
541     return this;
542   }
543
544   public void rootsChanged() {
545     forceInitFromModel();
546   }
547
548   private void initPopupActions() {
549     if (myPopupActions == null) {
550       int actionIndex = 1;
551       final List<AddItemPopupAction<?>> actions = new ArrayList<>();
552       final StructureConfigurableContext context = getStructureConfigurableContext();
553       actions.add(new AddNewModuleLibraryAction(this, actionIndex++, context));
554       actions.add(new AddLibraryDependencyAction(this, actionIndex++, JavaUiBundle.message("classpath.add.library.action"), context));
555       actions.add(new AddModuleDependencyAction(this, actionIndex, context)
556       );
557
558       myPopupActions = actions;
559     }
560   }
561
562   private StructureConfigurableContext getStructureConfigurableContext() {
563     return ProjectStructureConfigurable.getInstance(myState.getProject()).getContext();
564   }
565
566
567   private void enableModelUpdate() {
568     myInsideChange--;
569   }
570
571   private void disableModelUpdate() {
572     myInsideChange++;
573   }
574
575   public void addListener(OrderPanelListener listener) {
576     myListeners.addListener(listener);
577   }
578
579   public void removeListener(OrderPanelListener listener) {
580     myListeners.removeListener(listener);
581   }
582
583   private void moveSelectedRows(int increment) {
584     LOG.assertTrue(increment == -1 || increment == 1);
585     if (myEntryTable.isEditing()) {
586       myEntryTable.getCellEditor().stopCellEditing();
587     }
588     final ListSelectionModel selectionModel = myEntryTable.getSelectionModel();
589     for (int row = increment < 0 ? 0 : myModel.getRowCount() - 1; increment < 0 ? row < myModel.getRowCount() : row >= 0; row +=
590       increment < 0 ? +1 : -1) {
591       if (selectionModel.isSelectedIndex(row)) {
592         final int newRow = moveRow(row, increment);
593         selectionModel.removeSelectionInterval(row, row);
594         selectionModel.addSelectionInterval(newRow, newRow);
595       }
596     }
597     Rectangle cellRect = myEntryTable.getCellRect(selectionModel.getMinSelectionIndex(), 0, true);
598     myEntryTable.scrollRectToVisible(cellRect);
599     myEntryTable.repaint();
600   }
601
602   public void selectOrderEntry(@NotNull OrderEntry entry) {
603     for (int row = 0; row < myModel.getRowCount(); row++) {
604       final OrderEntry orderEntry = getItemAt(row).getEntry();
605       if (orderEntry != null && entry.getPresentableName().equals(orderEntry.getPresentableName())) {
606         if (orderEntry instanceof ExportableOrderEntry && entry instanceof ExportableOrderEntry &&
607             ((ExportableOrderEntry)entry).getScope() != ((ExportableOrderEntry)orderEntry).getScope()) {
608           continue;
609         }
610         myEntryTable.getSelectionModel().setSelectionInterval(row, row);
611         TableUtil.scrollSelectionToVisible(myEntryTable);
612       }
613     }
614     IdeFocusManager.getInstance(myState.getProject()).requestFocus(myEntryTable, true);
615   }
616
617   private int moveRow(final int row, final int increment) {
618     int newIndex = Math.abs(row + increment) % myModel.getRowCount();
619     myModel.exchangeRows(row, newIndex);
620     return newIndex;
621   }
622
623   public void stopEditing() {
624     TableUtil.stopEditing(myEntryTable);
625   }
626
627   private int myInsideChange = 0;
628   public void initFromModel() {
629     if (myInsideChange == 0) {
630       forceInitFromModel();
631     }
632   }
633
634   public void forceInitFromModel() {
635     Set<ClasspathTableItem<?>> oldSelection = new HashSet<>();
636     for (int i : myEntryTable.getSelectedRows()) {
637       ContainerUtil.addIfNotNull(oldSelection, getItemAt(i));
638     }
639     myModel.clear();
640     myModel.init();
641     myModel.fireTableDataChanged();
642     TIntArrayList newSelection = new TIntArrayList();
643     for (int i = 0; i < myModel.getRowCount(); i++) {
644       if (oldSelection.contains(getItemAt(i))) {
645         newSelection.add(i);
646       }
647     }
648     TableUtil.selectRows(myEntryTable, newSelection.toNativeArray());
649   }
650
651   static CellAppearanceEx getCellAppearance(final ClasspathTableItem<?> item,
652                                             final StructureConfigurableContext context,
653                                             final boolean selected) {
654     final OrderEntryAppearanceService service = OrderEntryAppearanceService.getInstance();
655     if (item instanceof InvalidJdkItem) {
656       return service.forJdk(null, false, selected, true);
657     }
658     else {
659       final OrderEntry entry = item.getEntry();
660       assert entry != null : item;
661       return service.forOrderEntry(context.getProject(), entry, selected);
662     }
663   }
664
665   private static class TableItemRenderer extends ColoredTableCellRenderer {
666     private final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
667     private final StructureConfigurableContext myContext;
668
669     TableItemRenderer(StructureConfigurableContext context) {
670       myContext = context;
671     }
672
673     @Override
674     protected void customizeCellRenderer(@NotNull JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
675       setPaintFocusBorder(false);
676       setFocusBorderAroundIcon(true);
677       setBorder(NO_FOCUS_BORDER);
678       if (value instanceof ClasspathTableItem<?>) {
679         final ClasspathTableItem<?> tableItem = (ClasspathTableItem<?>)value;
680         getCellAppearance(tableItem, myContext, selected).customize(this);
681         setToolTipText(tableItem.getTooltipText());
682       }
683     }
684   }
685
686   private static class ExportFlagRenderer implements TableCellRenderer {
687     private final TableCellRenderer myDelegate;
688     private final JPanel myBlankPanel;
689
690     ExportFlagRenderer(TableCellRenderer delegate) {
691       myDelegate = delegate;
692       myBlankPanel = new JPanel();
693     }
694
695     @Override
696     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
697       if (!table.isCellEditable(row, column)) {
698         myBlankPanel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
699         return myBlankPanel;
700       }
701       return myDelegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
702     }
703   }
704
705   private final class MyFindUsagesAction extends FindUsagesInProjectStructureActionBase {
706     private MyFindUsagesAction() {
707       super(myEntryTable, myState.getProject());
708     }
709
710     @Override
711     protected boolean isEnabled() {
712       return getSelectedElement() != null;
713     }
714
715     @Override
716     protected ProjectStructureElement getSelectedElement() {
717       final OrderEntry entry = getSelectedEntry();
718       if (entry instanceof LibraryOrderEntry) {
719         final Library library = ((LibraryOrderEntry)entry).getLibrary();
720         if (library != null) {
721           return new LibraryProjectStructureElement(getContext(), library);
722         }
723       }
724       else if (entry instanceof ModuleOrderEntry) {
725         final Module module = ((ModuleOrderEntry)entry).getModule();
726         if (module != null) {
727           return new ModuleProjectStructureElement(getContext(), module);
728         }
729       }
730       else if (entry instanceof JdkOrderEntry) {
731         final Sdk jdk = ((JdkOrderEntry)entry).getJdk();
732         if (jdk != null) {
733           return new SdkProjectStructureElement(getContext(), jdk);
734         }
735       }
736       return null;
737     }
738
739     @Override
740     protected RelativePoint getPointToShowResults() {
741       Rectangle rect = myEntryTable.getCellRect(myEntryTable.getSelectedRow(), 1, false);
742       Point location = rect.getLocation();
743       location.y += rect.height;
744       return new RelativePoint(myEntryTable, location);
745     }
746   }
747 }