VCS: apply patch dialog: highlight patch original path
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / ChangesTreeList.java
blob369838c98818bad1cae4e0a188746f863f289116
1 /*
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.
16 package com.intellij.openapi.vcs.changes.ui;
18 import com.intellij.ide.util.PropertiesComponent;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.keymap.KeymapManager;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.EmptyRunnable;
23 import com.intellij.openapi.util.IconLoader;
24 import com.intellij.openapi.util.Pair;
25 import com.intellij.openapi.util.SystemInfo;
26 import com.intellij.openapi.vcs.FilePath;
27 import com.intellij.openapi.vcs.FileStatus;
28 import com.intellij.openapi.vcs.FileStatusManager;
29 import com.intellij.openapi.vcs.VcsBundle;
30 import com.intellij.openapi.vcs.changes.Change;
31 import com.intellij.openapi.vcs.changes.ChangesUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.ui.*;
34 import com.intellij.ui.treeStructure.Tree;
35 import com.intellij.ui.treeStructure.actions.CollapseAllAction;
36 import com.intellij.ui.treeStructure.actions.ExpandAllAction;
37 import com.intellij.util.Icons;
38 import com.intellij.util.ui.tree.TreeUtil;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import javax.swing.*;
44 import javax.swing.tree.DefaultTreeModel;
45 import javax.swing.tree.TreeCellRenderer;
46 import javax.swing.tree.TreePath;
47 import java.awt.*;
48 import java.awt.event.*;
49 import java.io.File;
50 import java.util.*;
51 import java.util.List;
53 /**
54 * @author max
56 public abstract class ChangesTreeList<T> extends JPanel {
57 private final Tree myTree;
58 private final JList myList;
59 protected final Project myProject;
60 private final boolean myShowCheckboxes;
61 private final boolean myHighlightProblems;
62 private boolean myShowFlatten;
64 private final Collection<T> myIncludedChanges;
65 private Runnable myDoubleClickHandler = EmptyRunnable.getInstance();
67 @NonNls private static final String TREE_CARD = "Tree";
68 @NonNls private static final String LIST_CARD = "List";
69 @NonNls private static final String ROOT = "root";
70 private final CardLayout myCards;
72 @NonNls private final static String FLATTEN_OPTION_KEY = "ChangesBrowser.SHOW_FLATTEN";
74 private final Runnable myInclusionListener;
75 @Nullable private final ChangeNodeDecorator myChangeDecorator;
77 public ChangesTreeList(final Project project, Collection<T> initiallyIncluded, final boolean showCheckboxes,
78 final boolean highlightProblems, @Nullable final Runnable inclusionListener, @Nullable final ChangeNodeDecorator decorator) {
79 myProject = project;
80 myShowCheckboxes = showCheckboxes;
81 myHighlightProblems = highlightProblems;
82 myInclusionListener = inclusionListener;
83 myChangeDecorator = decorator;
84 myIncludedChanges = new HashSet<T>(initiallyIncluded);
86 myCards = new CardLayout();
88 setLayout(myCards);
90 final int checkboxWidth = new JCheckBox().getPreferredSize().width;
91 myTree = new Tree(ChangesBrowserNode.create(myProject, ROOT)) {
92 public Dimension getPreferredScrollableViewportSize() {
93 Dimension size = super.getPreferredScrollableViewportSize();
94 size = new Dimension(size.width + 10, size.height);
95 return size;
98 protected void processMouseEvent(MouseEvent e) {
99 if (e.getID() == MouseEvent.MOUSE_PRESSED) {
100 int row = myTree.getRowForLocation(e.getX(), e.getY());
101 if (row >= 0) {
102 final Rectangle baseRect = myTree.getRowBounds(row);
103 baseRect.setSize(checkboxWidth, baseRect.height);
104 if (baseRect.contains(e.getPoint())) {
105 myTree.setSelectionRow(row);
106 toggleSelection();
110 super.processMouseEvent(e);
113 public int getToggleClickCount() {
114 return -1;
118 myTree.setRootVisible(false);
119 myTree.setShowsRootHandles(true);
121 myTree.setCellRenderer(new MyTreeCellRenderer());
123 myList = new JList(new DefaultListModel());
124 myList.setVisibleRowCount(10);
126 add(new JScrollPane(myList), LIST_CARD);
127 add(new JScrollPane(myTree), TREE_CARD);
129 new ListSpeedSearch(myList) {
130 protected String getElementText(Object element) {
131 if (element instanceof Change) {
132 return ChangesUtil.getFilePath((Change)element).getName();
134 return super.getElementText(element);
138 myList.setCellRenderer(new MyListCellRenderer());
140 new MyToggleSelectionAction().registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0)), this);
142 registerKeyboardAction(new ActionListener() {
143 public void actionPerformed(ActionEvent e) {
144 includeSelection();
147 }, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
149 registerKeyboardAction(new ActionListener() {
150 public void actionPerformed(ActionEvent e) {
151 excludeSelection();
153 }, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
155 myList.addMouseListener(new MouseAdapter() {
156 public void mouseClicked(MouseEvent e) {
157 final int idx = myList.locationToIndex(e.getPoint());
158 if (idx >= 0) {
159 final Rectangle baseRect = myList.getCellBounds(idx, idx);
160 baseRect.setSize(checkboxWidth, baseRect.height);
161 if (baseRect.contains(e.getPoint())) {
162 toggleSelection();
163 e.consume();
165 else if (e.getClickCount() == 2) {
166 myDoubleClickHandler.run();
167 e.consume();
173 myTree.addMouseListener(new MouseAdapter() {
174 public void mouseClicked(MouseEvent e) {
175 final int row = myTree.getRowForLocation(e.getPoint().x, e.getPoint().y);
176 if (row >= 0) {
177 final Rectangle baseRect = myTree.getRowBounds(row);
178 baseRect.setSize(checkboxWidth, baseRect.height);
179 if (!baseRect.contains(e.getPoint()) && e.getClickCount() == 2) {
180 myDoubleClickHandler.run();
181 e.consume();
187 setShowFlatten(PropertiesComponent.getInstance(myProject).isTrueValue(FLATTEN_OPTION_KEY));
190 public void setDoubleClickHandler(final Runnable doubleClickHandler) {
191 myDoubleClickHandler = doubleClickHandler;
194 public void installPopupHandler(ActionGroup group) {
195 PopupHandler.installUnknownPopupHandler(myList, group, ActionManager.getInstance());
196 PopupHandler.installUnknownPopupHandler(myTree, group, ActionManager.getInstance());
199 public Dimension getPreferredSize() {
200 return new Dimension(400, 400);
203 public boolean isShowFlatten() {
204 return myShowFlatten;
207 public void setShowFlatten(final boolean showFlatten) {
208 myShowFlatten = showFlatten;
209 myCards.show(this, myShowFlatten ? LIST_CARD : TREE_CARD);
210 if (myList.hasFocus() || myTree.hasFocus()) {
211 SwingUtilities.invokeLater(new Runnable() {
212 public void run() {
213 requestFocus();
220 public void requestFocus() {
221 if (myShowFlatten) {
222 myList.requestFocus();
224 else {
225 myTree.requestFocus();
229 public void setChangesToDisplay(final List<T> changes) {
230 final DefaultListModel listModel = (DefaultListModel)myList.getModel();
231 final List<T> sortedChanges = new ArrayList<T>(changes);
232 Collections.sort(sortedChanges, new Comparator<T>() {
233 public int compare(final T o1, final T o2) {
234 return TreeModelBuilder.getPathForObject(o1).getName().compareToIgnoreCase(TreeModelBuilder.getPathForObject(o2).getName());
238 listModel.removeAllElements();
239 for (T change : sortedChanges) {
240 listModel.addElement(change);
243 final DefaultTreeModel model = buildTreeModel(changes, myChangeDecorator);
244 myTree.setModel(model);
246 SwingUtilities.invokeLater(new Runnable() {
247 public void run() {
248 if (myProject.isDisposed()) return;
249 TreeUtil.expandAll(myTree);
251 if (myIncludedChanges.size() > 0) {
252 int listSelection = 0;
253 int count = 0;
254 for (T change : changes) {
255 if (myIncludedChanges.contains(change)) {
256 listSelection = count;
257 break;
259 count ++;
262 ChangesBrowserNode root = (ChangesBrowserNode)model.getRoot();
263 Enumeration enumeration = root.depthFirstEnumeration();
265 while (enumeration.hasMoreElements()) {
266 ChangesBrowserNode node = (ChangesBrowserNode)enumeration.nextElement();
267 final CheckboxTree.NodeState state = getNodeStatus(node);
268 if (node != root && state == CheckboxTree.NodeState.CLEAR) {
269 myTree.collapsePath(new TreePath(node.getPath()));
273 enumeration = root.depthFirstEnumeration();
274 int scrollRow = 0;
275 while (enumeration.hasMoreElements()) {
276 ChangesBrowserNode node = (ChangesBrowserNode)enumeration.nextElement();
277 final CheckboxTree.NodeState state = getNodeStatus(node);
278 if (state == CheckboxTree.NodeState.FULL && node.isLeaf()) {
279 scrollRow = myTree.getRowForPath(new TreePath(node.getPath()));
280 break;
284 if (changes.size() > 0) {
285 myList.setSelectedIndex(listSelection);
286 myList.ensureIndexIsVisible(listSelection);
288 myTree.setSelectionRow(scrollRow);
289 TreeUtil.showRowCentered(myTree, scrollRow, false);
296 protected abstract DefaultTreeModel buildTreeModel(final List<T> changes, final ChangeNodeDecorator changeNodeDecorator);
298 @SuppressWarnings({"SuspiciousMethodCalls"})
299 private void toggleSelection() {
300 boolean hasExcluded = false;
301 for (T value : getSelectedChanges()) {
302 if (!myIncludedChanges.contains(value)) {
303 hasExcluded = true;
307 if (hasExcluded) {
308 includeSelection();
310 else {
311 excludeSelection();
314 repaint();
317 private void includeSelection() {
318 for (T change : getSelectedChanges()) {
319 myIncludedChanges.add(change);
321 notifyInclusionListener();
322 repaint();
325 @SuppressWarnings({"SuspiciousMethodCalls"})
326 private void excludeSelection() {
327 for (T change : getSelectedChanges()) {
328 myIncludedChanges.remove(change);
330 notifyInclusionListener();
331 repaint();
334 public int getSelectionCount() {
335 if (myShowFlatten) {
336 return myList.getSelectedIndices().length;
337 } else {
338 return myTree.getSelectionCount();
342 @NotNull
343 public List<T> getSelectedChanges() {
344 if (myShowFlatten) {
345 final Object[] o = myList.getSelectedValues();
346 final List<T> changes = new ArrayList<T>();
347 for (Object anO : o) {
348 changes.add((T)anO);
351 return changes;
353 else {
354 List<T> changes = new ArrayList<T>();
355 final TreePath[] paths = myTree.getSelectionPaths();
356 if (paths != null) {
357 for (TreePath path : paths) {
358 ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
359 changes.addAll(getSelectedObjects(node));
363 return changes;
367 protected abstract List<T> getSelectedObjects(final ChangesBrowserNode<T> node);
369 @Nullable
370 protected abstract T getLeadSelectedObject(final ChangesBrowserNode node);
372 @Nullable
373 public T getHighestLeadSelection() {
374 if (myShowFlatten) {
375 final int index = myList.getLeadSelectionIndex();
376 ListModel listModel = myList.getModel();
377 if (index < 0 || index >= listModel.getSize()) return null;
378 //noinspection unchecked
379 return (T)listModel.getElementAt(index);
381 else {
382 final TreePath path = myTree.getSelectionPath();
383 if (path == null) return null;
384 return getLeadSelectedObject((ChangesBrowserNode<T>)path.getLastPathComponent());
388 @Nullable
389 public T getLeadSelection() {
390 if (myShowFlatten) {
391 final int index = myList.getLeadSelectionIndex();
392 ListModel listModel = myList.getModel();
393 if (index < 0 || index >= listModel.getSize()) return null;
394 //noinspection unchecked
395 return (T)listModel.getElementAt(index);
397 else {
398 final TreePath path = myTree.getSelectionPath();
399 if (path == null) return null;
400 final List<T> changes = getSelectedObjects(((ChangesBrowserNode<T>)path.getLastPathComponent()));
401 return changes.size() > 0 ? changes.get(0) : null;
405 private void notifyInclusionListener() {
406 if (myInclusionListener != null) {
407 myInclusionListener.run();
411 // no listener supposed to be called
412 public void setIncludedChanges(final Collection<T> changes) {
413 myIncludedChanges.clear();
414 myIncludedChanges.addAll(changes);
415 myTree.repaint();
416 myList.repaint();
419 public void includeChange(final T change) {
420 myIncludedChanges.add(change);
421 notifyInclusionListener();
422 myTree.repaint();
423 myList.repaint();
426 public void includeChanges(final Collection<T> changes) {
427 myIncludedChanges.addAll(changes);
428 notifyInclusionListener();
429 myTree.repaint();
430 myList.repaint();
433 public void excludeChange(final T change) {
434 myIncludedChanges.remove(change);
435 notifyInclusionListener();
436 myTree.repaint();
437 myList.repaint();
440 public void excludeChanges(final Collection<T> changes) {
441 myIncludedChanges.removeAll(changes);
442 notifyInclusionListener();
443 myTree.repaint();
444 myList.repaint();
447 public boolean isIncluded(final T change) {
448 return myIncludedChanges.contains(change);
451 public Collection<T> getIncludedChanges() {
452 return myIncludedChanges;
455 public void expandAll() {
456 TreeUtil.expandAll(myTree);
459 public AnAction[] getTreeActions() {
460 final ToggleShowDirectoriesAction directoriesAction = new ToggleShowDirectoriesAction();
461 final ExpandAllAction expandAllAction = new ExpandAllAction(myTree) {
462 public void update(AnActionEvent e) {
463 e.getPresentation().setVisible(!myShowFlatten);
466 final CollapseAllAction collapseAllAction = new CollapseAllAction(myTree) {
467 public void update(AnActionEvent e) {
468 e.getPresentation().setVisible(!myShowFlatten);
471 final SelectAllAction selectAllAction = new SelectAllAction();
472 final AnAction[] actions = new AnAction[]{directoriesAction, expandAllAction, collapseAllAction, selectAllAction};
473 directoriesAction.registerCustomShortcutSet(
474 new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_P, SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK)),
475 this);
476 expandAllAction.registerCustomShortcutSet(
477 new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_EXPAND_ALL)),
478 myTree);
479 collapseAllAction.registerCustomShortcutSet(
480 new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_COLLAPSE_ALL)),
481 myTree);
482 selectAllAction.registerCustomShortcutSet(
483 new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_A, SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK)),
484 this);
485 return actions;
488 private class MyTreeCellRenderer extends JPanel implements TreeCellRenderer {
489 private final ChangesBrowserNodeRenderer myTextRenderer;
490 private final JCheckBox myCheckBox;
493 public MyTreeCellRenderer() {
494 super(new BorderLayout());
495 myCheckBox = new JCheckBox();
496 myTextRenderer = new ChangesBrowserNodeRenderer(myProject, false, myHighlightProblems);
498 myCheckBox.setBackground(null);
499 setBackground(null);
501 if (myShowCheckboxes) {
502 add(myCheckBox, BorderLayout.WEST);
505 add(myTextRenderer, BorderLayout.CENTER);
508 public Component getTreeCellRendererComponent(JTree tree,
509 Object value,
510 boolean selected,
511 boolean expanded,
512 boolean leaf,
513 int row,
514 boolean hasFocus) {
515 myTextRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
516 if (myShowCheckboxes) {
517 ChangesBrowserNode node = (ChangesBrowserNode)value;
519 CheckboxTree.NodeState state = getNodeStatus(node);
520 myCheckBox.setSelected(state != CheckboxTree.NodeState.CLEAR);
521 myCheckBox.setEnabled(state != CheckboxTree.NodeState.PARTIAL);
522 revalidate();
524 return this;
526 else {
527 return myTextRenderer;
533 private CheckboxTree.NodeState getNodeStatus(ChangesBrowserNode node) {
534 boolean hasIncluded = false;
535 boolean hasExcluded = false;
537 for (T change : getSelectedObjects(node)) {
538 if (myIncludedChanges.contains(change)) {
539 hasIncluded = true;
541 else {
542 hasExcluded = true;
546 if (hasIncluded && hasExcluded) return CheckboxTree.NodeState.PARTIAL;
547 if (hasIncluded) return CheckboxTree.NodeState.FULL;
548 return CheckboxTree.NodeState.CLEAR;
551 private class MyListCellRenderer extends JPanel implements ListCellRenderer {
552 private final ColoredListCellRenderer myTextRenderer;
553 public final JCheckBox myCheckbox;
555 public MyListCellRenderer() {
556 super(new BorderLayout());
557 myCheckbox = new JCheckBox();
558 myTextRenderer = new ColoredListCellRenderer() {
559 protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
560 final FilePath path = TreeModelBuilder.getPathForObject(value);
561 if (path.isDirectory()) {
562 setIcon(Icons.DIRECTORY_CLOSED_ICON);
563 } else {
564 setIcon(path.getFileType().getIcon());
566 final FileStatus fileStatus;
567 if (value instanceof Change) {
568 fileStatus = ((Change) value).getFileStatus();
570 else {
571 final VirtualFile virtualFile = path.getVirtualFile();
572 if (virtualFile != null) {
573 fileStatus = FileStatusManager.getInstance(myProject).getStatus(virtualFile);
575 else {
576 fileStatus = FileStatus.NOT_CHANGED;
579 append(path.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, fileStatus.getColor(), null));
580 final boolean applyChangeDecorator = (value instanceof Change) && myChangeDecorator != null;
581 final File parentFile = path.getIOFile().getParentFile();
582 if (parentFile != null) {
583 final String parentPath = parentFile.getPath();
584 List<Pair<String,ChangeNodeDecorator.Stress>> parts = null;
585 if (applyChangeDecorator) {
586 parts = myChangeDecorator.stressPartsOfFileName((Change)value, parentPath);
588 if (parts == null) {
589 parts = Collections.singletonList(new Pair<String, ChangeNodeDecorator.Stress>(parentPath, ChangeNodeDecorator.Stress.PLAIN));
592 append(" (");
593 for (Pair<String, ChangeNodeDecorator.Stress> part : parts) {
594 append(part.getFirst(), part.getSecond().derive(SimpleTextAttributes.GRAYED_ATTRIBUTES));
596 append(")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
598 if (applyChangeDecorator) {
599 myChangeDecorator.decorate((Change) value, this, isShowFlatten());
604 myCheckbox.setBackground(null);
605 setBackground(null);
607 if (myShowCheckboxes) {
608 add(myCheckbox, BorderLayout.WEST);
610 add(myTextRenderer, BorderLayout.CENTER);
613 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
614 myTextRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
615 if (myShowCheckboxes) {
616 myCheckbox.setSelected(myIncludedChanges.contains(value));
617 return this;
619 else {
620 return myTextRenderer;
625 private class MyToggleSelectionAction extends AnAction {
626 public void actionPerformed(AnActionEvent e) {
627 toggleSelection();
631 public class ToggleShowDirectoriesAction extends ToggleAction {
632 public ToggleShowDirectoriesAction() {
633 super(VcsBundle.message("changes.action.show.directories.text"),
634 VcsBundle.message("changes.action.show.directories.description"),
635 Icons.DIRECTORY_CLOSED_ICON);
638 public boolean isSelected(AnActionEvent e) {
639 return !PropertiesComponent.getInstance(myProject).isTrueValue(FLATTEN_OPTION_KEY);
642 public void setSelected(AnActionEvent e, boolean state) {
643 PropertiesComponent.getInstance(myProject).setValue(FLATTEN_OPTION_KEY, String.valueOf(!state));
644 setShowFlatten(!state);
648 private class SelectAllAction extends AnAction {
649 private SelectAllAction() {
650 super("Select All", "Select all items", IconLoader.getIcon("/actions/selectall.png"));
653 public void actionPerformed(final AnActionEvent e) {
654 if (myShowFlatten) {
655 final int count = myList.getModel().getSize();
656 if (count > 0) {
657 myList.setSelectionInterval(0, count-1);
660 else {
661 final int count = myTree.getRowCount();
662 if (count > 0) {
663 myTree.setSelectionInterval(0, count-1);