2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.ide.commander;
19 import com.intellij.history.LocalHistory;
20 import com.intellij.history.LocalHistoryAction;
21 import com.intellij.ide.CopyPasteDelegator;
22 import com.intellij.ide.DeleteProvider;
23 import com.intellij.ide.IdeBundle;
24 import com.intellij.ide.IdeView;
25 import com.intellij.ide.projectView.ProjectViewNode;
26 import com.intellij.ide.projectView.impl.ModuleGroup;
27 import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase;
28 import com.intellij.ide.projectView.impl.nodes.LibraryGroupElement;
29 import com.intellij.ide.projectView.impl.nodes.NamedLibraryElement;
30 import com.intellij.ide.structureView.StructureViewTreeElement;
31 import com.intellij.ide.ui.customization.CustomActionsSchema;
32 import com.intellij.ide.util.DeleteHandler;
33 import com.intellij.ide.util.DirectoryChooserUtil;
34 import com.intellij.ide.util.EditSourceUtil;
35 import com.intellij.ide.util.EditorHelper;
36 import com.intellij.ide.util.treeView.AbstractTreeNode;
37 import com.intellij.openapi.actionSystem.*;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.application.ModalityState;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.module.Module;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.util.SystemInfo;
44 import com.intellij.openapi.wm.ToolWindowManager;
45 import com.intellij.pom.Navigatable;
46 import com.intellij.psi.PsiDirectory;
47 import com.intellij.psi.PsiElement;
48 import com.intellij.psi.util.PsiUtilBase;
49 import com.intellij.ui.*;
50 import com.intellij.ui.components.JBList;
51 import com.intellij.util.ArrayUtil;
52 import com.intellij.util.containers.ContainerUtil;
53 import com.intellij.util.ui.UIUtil;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
59 import javax.swing.border.BevelBorder;
61 import java.awt.event.*;
62 import java.util.ArrayList;
63 import java.util.List;
66 * @author Eugene Belyaev
68 public class CommanderPanel extends JPanel {
69 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.commander.CommanderPanel");
71 private static final Color DARK_BLUE = new Color(55, 85, 134);
72 private static final Color DARK_BLUE_BRIGHTER = new Color(58, 92, 149);
73 private static final Color DARK_BLUE_DARKER = new Color(38, 64, 106);
75 private Project myProject;
76 private AbstractListBuilder myBuilder;
77 private JPanel myTitlePanel;
78 private JLabel myParentTitle;
79 protected final JBList myList;
80 private final MyModel myModel;
82 private CopyPasteDelegator myCopyPasteDelegator;
83 protected final ListSpeedSearch myListSpeedSearch;
84 private final IdeView myIdeView = new MyIdeView();
85 private final MyDeleteElementProvider myDeleteElementProvider = new MyDeleteElementProvider();
87 private static final String ACTION_DRILL_DOWN = "DrillDown";
89 private static final String ACTION_GO_UP = "GoUp";
90 private ProjectAbstractTreeStructureBase myProjectTreeStructure;
91 private boolean myActive = true;
92 private final List<CommanderHistoryListener> myHistoryListeners = ContainerUtil.createEmptyCOWList();
93 private boolean myMoveFocus = false;
94 private boolean myEnableSearchHighlighting;
96 public CommanderPanel(final Project project, final boolean enablePopupMenu, final boolean enableSearchHighlighting) {
97 super(new BorderLayout());
99 myEnableSearchHighlighting = enableSearchHighlighting;
100 myModel = new MyModel();
101 myList = new JBList(myModel);
102 myList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
104 if (enablePopupMenu) {
105 myCopyPasteDelegator = new CopyPasteDelegator(myProject, myList) {
107 protected PsiElement[] getSelectedElements() {
108 return CommanderPanel.this.getSelectedElements();
113 myListSpeedSearch = new ListSpeedSearch(myList);
115 ListScrollingUtil.installActions(myList);
117 myList.registerKeyboardAction(new ActionListener() {
118 public void actionPerformed(final ActionEvent e) {
119 if (myBuilder == null) return;
120 myBuilder.buildRoot();
122 }, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH, SystemInfo.isMac ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
124 myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ACTION_DRILL_DOWN);
125 myList.getInputMap(WHEN_FOCUSED)
126 .put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, SystemInfo.isMac ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK), ACTION_DRILL_DOWN);
127 myList.getActionMap().put(ACTION_DRILL_DOWN, new AbstractAction() {
128 public void actionPerformed(final ActionEvent e) {
132 myList.addMouseListener(new MouseAdapter() {
133 public void mouseClicked(final MouseEvent e) {
134 if (e.getClickCount() == 2) {
139 myList.getInputMap(WHEN_FOCUSED)
140 .put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, SystemInfo.isMac ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK), ACTION_GO_UP);
141 myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), ACTION_GO_UP);
142 myList.getActionMap().put(ACTION_GO_UP, new AbstractAction() {
143 public void actionPerformed(final ActionEvent e) {
148 myList.getActionMap().put("selectAll", new AbstractAction() {
149 public void actionPerformed(final ActionEvent e) {
154 if (enablePopupMenu) {
155 myList.addMouseListener(new PopupHandler() {
156 public void invokePopup(final Component comp, final int x, final int y) {
157 CommanderPanel.this.invokePopup(comp, x, y);
162 myList.addFocusListener(new FocusAdapter() {
163 public void focusGained(final FocusEvent e) {
167 public void focusLost(final FocusEvent e) {
174 public boolean isEnableSearchHighlighting() {
175 return myEnableSearchHighlighting;
178 public ListSpeedSearch getListSpeedSearch() {
179 return myListSpeedSearch;
182 public void addHistoryListener(CommanderHistoryListener listener) {
183 myHistoryListeners.add(listener);
186 public void removeHistoryListener(CommanderHistoryListener listener) {
187 myHistoryListeners.remove(listener);
190 private void updateHistory(boolean elementExpanded) {
191 for(CommanderHistoryListener listener: myHistoryListeners) {
192 listener.historyChanged(getSelectedElement(), elementExpanded);
196 public final JList getList() {
200 public final AbstractListBuilder.Model getModel() {
204 public void setMoveFocus(final boolean moveFocus) {
205 myMoveFocus = moveFocus;
209 if (myBuilder == null) {
214 updateHistory(false);
217 public void drillDown() {
218 if (topElementIsSelected()) {
223 if (getSelectedValue() == null) {
227 final AbstractTreeNode element = getSelectedNode();
228 if (element.getChildren().size() == 0) {
229 if (!shouldDrillDownOnEmptyElement(element)) {
230 navigateSelectedElement();
235 if (myBuilder == null) {
238 updateHistory(false);
239 myBuilder.drillDown();
243 public boolean navigateSelectedElement() {
244 final AbstractTreeNode selectedNode = getSelectedNode();
245 if (selectedNode != null) {
246 if (selectedNode.canNavigateToSource()) {
247 selectedNode.navigate(true);
254 protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) {
255 return node instanceof ProjectViewNode && ((ProjectViewNode)node).shouldDrillDownOnEmptyElement();
258 private boolean topElementIsSelected() {
259 int[] selectedIndices = myList.getSelectedIndices();
260 return selectedIndices.length == 1 && selectedIndices[0] == 0 && myModel.getElementAt(selectedIndices[0]) instanceof TopLevelNode;
263 public final void setBuilder(final AbstractListBuilder builder) {
267 myTitlePanel = new JPanel(new BorderLayout());
268 myTitlePanel.setBackground(UIUtil.getControlColor());
269 myTitlePanel.setOpaque(true);
271 myParentTitle = new MyTitleLabel(myTitlePanel);
272 myParentTitle.setText(" ");
273 myParentTitle.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
274 myParentTitle.setForeground(Color.black);
275 myParentTitle.setUI(new RightAlignedLabelUI());
276 final JPanel panel1 = new JPanel(new BorderLayout());
277 panel1.setOpaque(false);
278 panel1.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
279 panel1.add(myParentTitle, BorderLayout.CENTER);
280 myTitlePanel.add(panel1, BorderLayout.CENTER);
282 add(myTitlePanel, BorderLayout.NORTH);
283 final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList);
284 scrollPane.setBorder(null);
285 scrollPane.getVerticalScrollBar().setFocusable(false); // otherwise the scrollbar steals focus and panel switching with tab is broken
286 scrollPane.getHorizontalScrollBar().setFocusable(false);
287 add(scrollPane, BorderLayout.CENTER);
289 myBuilder.setParentTitle(myParentTitle);
291 // TODO[vova,anton] it seems that the code below performs double focus request. Is it OK?
292 myTitlePanel.addMouseListener(new MouseAdapter() {
293 public void mouseClicked(final MouseEvent e) {
294 myList.requestFocus();
297 public void mousePressed(final MouseEvent e) {
298 myList.requestFocus();
303 public final AbstractListBuilder getBuilder() {
307 public final PsiElement getSelectedElement() {
308 Object value = getValueAtIndex(getSelectedNode());
309 return (PsiElement)(value instanceof PsiElement ? value : null);
312 public final PsiElement getSelectedElement(int index) {
313 Object elementAtIndex = myModel.getElementAt(index);
314 Object value = getValueAtIndex(elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode)elementAtIndex : null);
315 return (PsiElement)(value instanceof PsiElement ? value : null);
318 public AbstractTreeNode getSelectedNode() {
319 if (myBuilder == null) return null;
320 final int[] indices = myList.getSelectedIndices();
321 if (indices.length != 1) return null;
322 int index = indices[0];
323 if (index >= myModel.getSize()) return null;
324 Object elementAtIndex = myModel.getElementAt(index);
325 return elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode)elementAtIndex : null;
328 private ArrayList<AbstractTreeNode> getSelectedNodes() {
329 if (myBuilder == null) return null;
330 final int[] indices = myList.getSelectedIndices();
331 ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
332 for (int index : indices) {
333 if (index >= myModel.getSize()) continue;
334 Object elementAtIndex = myModel.getElementAt(index);
335 AbstractTreeNode node = elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode)elementAtIndex : null;
344 public Object getSelectedValue() {
345 return getValueAtIndex(getSelectedNode());
348 private PsiElement[] getSelectedElements() {
349 if (myBuilder == null) return PsiElement.EMPTY_ARRAY;
350 final int[] indices = myList.getSelectedIndices();
352 final ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
353 for (int index : indices) {
354 final PsiElement element = getSelectedElement(index);
355 if (element != null) {
356 elements.add(element);
360 return elements.toArray(new PsiElement[elements.size()]);
363 private static Object getValueAtIndex(AbstractTreeNode node) {
364 if (node == null) return null;
365 Object value = node.getValue();
366 if (value instanceof StructureViewTreeElement) {
367 return ((StructureViewTreeElement)value).getValue();
372 public final void setActive(final boolean active) {
375 myTitlePanel.setBackground(DARK_BLUE);
376 myTitlePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED, DARK_BLUE_BRIGHTER, DARK_BLUE_DARKER));
377 myParentTitle.setForeground(Color.white);
380 final Color color = UIUtil.getPanelBackground();
381 LOG.assertTrue(color != null);
382 myTitlePanel.setBackground(color);
383 myTitlePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED, color.brighter(), color.darker()));
384 myParentTitle.setForeground(Color.black);
386 final int[] selectedIndices = myList.getSelectedIndices();
387 if (selectedIndices.length == 0 && myList.getModel().getSize() > 0) {
388 myList.setSelectedIndex(0);
389 if (!myList.hasFocus()) {
390 myList.requestFocus();
393 else if (myList.getModel().getSize() > 0) {
394 // need this to generate SelectionChanged events so that listeners, added by Commander, will be notified
395 myList.setSelectedIndices(selectedIndices);
399 public boolean isActive() {
403 private void invokePopup(final Component c, final int x, final int y) {
404 if (myBuilder == null) return;
406 if (myList.getSelectedIndices().length <= 1) {
407 final int popupIndex = myList.locationToIndex(new Point(x, y));
408 if (popupIndex >= 0) {
409 myList.setSelectedIndex(popupIndex);
410 myList.requestFocus();
414 final ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_COMMANDER_POPUP);
415 final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.COMMANDER_POPUP, group);
416 popupMenu.getComponent().show(c, x, y);
419 public final void dispose() {
420 if (myBuilder != null) {
427 public final void setTitlePanelVisible(final boolean flag) {
428 myTitlePanel.setVisible(flag);
431 public final Object getDataImpl(final String dataId) {
432 if (myBuilder == null) return null;
433 final Object selectedValue = getSelectedValue();
434 if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
435 final PsiElement selectedElement = getSelectedElement();
436 return selectedElement != null && selectedElement.isValid() ? selectedElement : null;
438 if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
439 return filterInvalidElements(getSelectedElements());
441 else if (LangDataKeys.PASTE_TARGET_PSI_ELEMENT.is(dataId)) {
442 final AbstractTreeNode parentNode = myBuilder.getParentNode();
443 final Object element = parentNode != null ? parentNode.getValue() : null;
444 return element instanceof PsiElement && ((PsiElement)element).isValid() ? element : null;
446 else if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
447 return getNavigatables();
449 else if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
450 return myCopyPasteDelegator != null ? myCopyPasteDelegator.getCopyProvider() : null;
452 else if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
453 return myCopyPasteDelegator != null ? myCopyPasteDelegator.getCutProvider() : null;
455 else if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
456 return myCopyPasteDelegator != null ? myCopyPasteDelegator.getPasteProvider() : null;
458 else if (LangDataKeys.IDE_VIEW.is(dataId)) {
461 else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
462 return myDeleteElementProvider;
464 else if (LangDataKeys.MODULE.is(dataId)) {
465 return selectedValue instanceof Module ? selectedValue : null;
467 else if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
468 return selectedValue instanceof ModuleGroup ? new ModuleGroup[]{(ModuleGroup)selectedValue} : null;
470 else if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
471 return selectedValue instanceof LibraryGroupElement ? new LibraryGroupElement[]{(LibraryGroupElement)selectedValue} : null;
473 else if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
474 return selectedValue instanceof NamedLibraryElement ? new NamedLibraryElement[]{(NamedLibraryElement)selectedValue} : null;
477 if (myProjectTreeStructure != null) {
478 return myProjectTreeStructure.getDataFromProviders(getSelectedNodes(), dataId);
484 private Navigatable[] getNavigatables() {
485 if (myBuilder == null) return null;
486 final int[] indices = myList.getSelectedIndices();
487 if (indices == null || indices.length == 0) return null;
489 final ArrayList<Navigatable> elements = new ArrayList<Navigatable>();
490 for (int index : indices) {
491 final Object element = myModel.getElementAt(index);
492 if (element instanceof AbstractTreeNode) {
493 elements.add((Navigatable)element);
497 return elements.toArray(new Navigatable[elements.size()]);
502 private static PsiElement[] filterInvalidElements(final PsiElement[] elements) {
503 if (elements == null || elements.length == 0) {
506 final List<PsiElement> validElements = new ArrayList<PsiElement>(elements.length);
507 for (final PsiElement element : elements) {
508 if (element.isValid()) {
509 validElements.add(element);
512 return validElements.size() == elements.length ? elements : validElements.toArray(new PsiElement[validElements.size()]);
515 protected final Navigatable createEditSourceDescriptor() {
516 return EditSourceUtil.getDescriptor(getSelectedElement());
519 public void setProjectTreeStructure(final ProjectAbstractTreeStructureBase projectTreeStructure) {
520 myProjectTreeStructure = projectTreeStructure;
523 private static final class MyTitleLabel extends JLabel {
524 private final JPanel myPanel;
526 public MyTitleLabel(final JPanel panel) {
530 public void setText(String text) {
531 if (text == null || text.length() == 0) {
535 if (myPanel != null) {
536 myPanel.setToolTipText(text.trim().length() == 0 ? null : text);
541 private final class MyDeleteElementProvider implements DeleteProvider {
542 public void deleteElement(final DataContext dataContext) {
543 LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
545 final PsiElement[] elements = getSelectedElements();
546 DeleteHandler.deletePsiElement(elements, myProject);
553 public boolean canDeleteElement(final DataContext dataContext) {
554 final PsiElement[] elements = getSelectedElements();
555 return DeleteHandler.shouldEnableDeleteAction(elements);
559 private final class MyIdeView implements IdeView {
560 public void selectElement(final PsiElement element) {
561 final boolean isDirectory = element instanceof PsiDirectory;
563 EditorHelper.openInEditor(element);
565 ApplicationManager.getApplication().invokeLater(new Runnable() {
567 myBuilder.selectElement(element, PsiUtilBase.getVirtualFile(element));
569 ApplicationManager.getApplication().invokeLater(new Runnable() {
572 ToolWindowManager.getInstance(myProject).activateEditorComponent();
578 }, ModalityState.NON_MODAL);
581 private PsiDirectory getDirectory() {
582 if (myBuilder == null) return null;
583 final Object parentElement = myBuilder.getParentNode();
584 if (parentElement instanceof AbstractTreeNode) {
585 final AbstractTreeNode parentNode = (AbstractTreeNode)parentElement;
586 if (!(parentNode.getValue() instanceof PsiDirectory)) return null;
587 return (PsiDirectory)parentNode.getValue();
594 public PsiDirectory[] getDirectories() {
595 PsiDirectory directory = getDirectory();
596 return directory == null ? PsiDirectory.EMPTY_ARRAY : new PsiDirectory[]{directory};
599 public PsiDirectory getOrChooseDirectory() {
600 return DirectoryChooserUtil.getOrChooseDirectory(this);
604 private static final class MyModel extends AbstractListBuilder.Model {
605 final List myElements = new ArrayList();
606 public void removeAllElements() {
607 int index1 = myElements.size()-1;
610 fireIntervalRemoved(this, 0, index1);
614 public void addElement(final Object obj) {
615 int index = myElements.size();
617 fireIntervalAdded(this, index, index);
620 public void replaceElements(final List newElements) {
622 myElements.addAll(newElements);
623 fireIntervalAdded(this, 0, newElements.size());
626 public Object[] toArray() {
627 return ArrayUtil.toObjectArray(myElements);
630 public int indexOf(final Object o) {
631 return myElements.indexOf(o);
634 public int getSize() {
635 return myElements.size();
638 public Object getElementAt(final int index) {
639 return myElements.get(index);