IDEA-81134 'Untracked Files Preventing Checkout': add action to delete files
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / ChangesTreeList.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vcs.changes.ui;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.util.PropertiesComponent;
20 import com.intellij.ide.util.treeView.TreeState;
21 import com.intellij.openapi.actionSystem.*;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.diff.DiffBundle;
24 import com.intellij.openapi.keymap.KeymapManager;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.EmptyRunnable;
27 import com.intellij.openapi.util.Pair;
28 import com.intellij.openapi.util.SystemInfo;
29 import com.intellij.openapi.util.registry.Registry;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.vcs.FilePath;
32 import com.intellij.openapi.vcs.FilePathImpl;
33 import com.intellij.openapi.vcs.VcsBundle;
34 import com.intellij.openapi.vcs.changes.Change;
35 import com.intellij.openapi.vcs.changes.ChangesUtil;
36 import com.intellij.openapi.vcs.changes.ContentRevision;
37 import com.intellij.openapi.vfs.LocalFileSystem;
38 import com.intellij.openapi.vfs.VirtualFile;
39 import com.intellij.ui.*;
40 import com.intellij.ui.components.JBList;
41 import com.intellij.ui.components.panels.NonOpaquePanel;
42 import com.intellij.ui.treeStructure.Tree;
43 import com.intellij.ui.treeStructure.actions.CollapseAllAction;
44 import com.intellij.ui.treeStructure.actions.ExpandAllAction;
45 import com.intellij.util.PlatformIcons;
46 import com.intellij.util.containers.Convertor;
47 import com.intellij.util.ui.UIUtil;
48 import com.intellij.util.ui.tree.TreeUtil;
49 import gnu.trove.TIntArrayList;
50 import org.jetbrains.annotations.NonNls;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53
54 import javax.swing.*;
55 import javax.swing.border.Border;
56 import javax.swing.event.ListSelectionEvent;
57 import javax.swing.event.ListSelectionListener;
58 import javax.swing.event.TreeSelectionEvent;
59 import javax.swing.event.TreeSelectionListener;
60 import javax.swing.tree.*;
61 import java.awt.*;
62 import java.awt.event.ActionEvent;
63 import java.awt.event.ActionListener;
64 import java.awt.event.KeyEvent;
65 import java.awt.event.MouseEvent;
66 import java.util.*;
67 import java.util.List;
68
69 /**
70  * @author max
71  */
72 public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataProvider {
73   private final Tree myTree;
74   private final JBList myList;
75   private final JScrollPane myTreeScrollPane;
76   private final JScrollPane myListScrollPane;
77   protected final Project myProject;
78   private final boolean myShowCheckboxes;
79   private final boolean myHighlightProblems;
80   private boolean myShowFlatten;
81
82   private final Collection<T> myIncludedChanges;
83   private Runnable myDoubleClickHandler = EmptyRunnable.getInstance();
84   private boolean myAlwaysExpandList;
85
86   @NonNls private static final String TREE_CARD = "Tree";
87   @NonNls private static final String LIST_CARD = "List";
88   @NonNls private static final String ROOT = "root";
89   private final CardLayout myCards;
90
91   @NonNls private final static String FLATTEN_OPTION_KEY = "ChangesBrowser.SHOW_FLATTEN";
92
93   private final Runnable myInclusionListener;
94   @Nullable private ChangeNodeDecorator myChangeDecorator;
95   private Runnable myGenericSelectionListener;
96
97   public ChangesTreeList(final Project project, Collection<T> initiallyIncluded, final boolean showCheckboxes,
98                          final boolean highlightProblems, @Nullable final Runnable inclusionListener, @Nullable final ChangeNodeDecorator decorator) {
99     myProject = project;
100     myShowCheckboxes = showCheckboxes;
101     myHighlightProblems = highlightProblems;
102     myInclusionListener = inclusionListener;
103     myChangeDecorator = decorator;
104     myIncludedChanges = new HashSet<T>(initiallyIncluded);
105     myAlwaysExpandList = true;
106
107     myCards = new CardLayout();
108
109     setLayout(myCards);
110
111     final int checkboxWidth = new JCheckBox().getPreferredSize().width;
112     myTree = new MyTree(project, checkboxWidth);
113
114     myTree.setRootVisible(false);
115     myTree.setShowsRootHandles(true);
116     myTree.setOpaque(false);
117     myTree.setCellRenderer(new MyTreeCellRenderer());
118     new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
119       public String convert(TreePath o) {
120         ChangesBrowserNode node = (ChangesBrowserNode) o.getLastPathComponent();
121         return node.getTextPresentation();
122       }
123     });
124
125     myList = new JBList(new DefaultListModel());
126     myList.setVisibleRowCount(10);
127
128     add(myListScrollPane = ScrollPaneFactory.createScrollPane(myList), LIST_CARD);
129     add(myTreeScrollPane = ScrollPaneFactory.createScrollPane(myTree), TREE_CARD);
130
131     new ListSpeedSearch(myList) {
132       protected String getElementText(Object element) {
133         if (element instanceof Change) {
134           return ChangesUtil.getFilePath((Change)element).getName();
135         }
136         return super.getElementText(element);
137       }
138     };
139
140     myList.setCellRenderer(new MyListCellRenderer());
141
142     new MyToggleSelectionAction().registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0)), this);
143     if (myShowCheckboxes) {
144       registerKeyboardAction(new ActionListener() {
145         public void actionPerformed(ActionEvent e) {
146           includeSelection();
147         }
148
149       }, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
150
151       registerKeyboardAction(new ActionListener() {
152         public void actionPerformed(ActionEvent e) {
153           excludeSelection();
154         }
155       }, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
156     }
157
158     new ClickListener() {
159       @Override
160       public boolean onClick(MouseEvent e, int clickCount) {
161         final int idx = myList.locationToIndex(e.getPoint());
162         if (idx >= 0) {
163           final Rectangle baseRect = myList.getCellBounds(idx, idx);
164           baseRect.setSize(checkboxWidth, baseRect.height);
165           if (baseRect.contains(e.getPoint())) {
166             toggleSelection();
167             return true;
168           }
169           else if (clickCount == 2) {
170             myDoubleClickHandler.run();
171             return true;
172           }
173         }
174         return false;
175       }
176     }.installOn(myList);
177
178     new ClickListener() {
179       @Override
180       public boolean onClick(MouseEvent e, int clickCount) {
181         final int row = myTree.getRowForLocation(e.getPoint().x, e.getPoint().y);
182         if (row >= 0) {
183           final Rectangle baseRect = myTree.getRowBounds(row);
184           baseRect.setSize(checkboxWidth, baseRect.height);
185           if (!baseRect.contains(e.getPoint()) && clickCount == 2) {
186             myDoubleClickHandler.run();
187             return true;
188           }
189         }
190         return false;
191       }
192     }.installOn(myTree);
193
194     setShowFlatten(PropertiesComponent.getInstance(myProject).isTrueValue(FLATTEN_OPTION_KEY));
195
196     String emptyText = StringUtil.capitalize(DiffBundle.message("diff.count.differences.status.text", 0));
197     setEmptyText(emptyText);
198   }
199
200   public void setEmptyText(@NotNull String emptyText) {
201     myTree.getEmptyText().setText(emptyText);
202     myList.getEmptyText().setText(emptyText);
203   }
204
205   // generic, both for tree and list
206   public void addSelectionListener(final Runnable runnable) {
207     myGenericSelectionListener = runnable;
208     myList.addListSelectionListener(new ListSelectionListener() {
209       @Override
210       public void valueChanged(ListSelectionEvent e) {
211         myGenericSelectionListener.run();
212       }
213     });
214     myTree.addTreeSelectionListener(new TreeSelectionListener() {
215       @Override
216       public void valueChanged(TreeSelectionEvent e) {
217         myGenericSelectionListener.run();
218       }
219     });
220   }
221
222   public void setChangeDecorator(@Nullable ChangeNodeDecorator changeDecorator) {
223     myChangeDecorator = changeDecorator;
224   }
225
226   public void setDoubleClickHandler(final Runnable doubleClickHandler) {
227     myDoubleClickHandler = doubleClickHandler;
228   }
229
230   public void installPopupHandler(ActionGroup group) {
231     PopupHandler.installUnknownPopupHandler(myList, group, ActionManager.getInstance());
232     PopupHandler.installUnknownPopupHandler(myTree, group, ActionManager.getInstance());
233   }
234   
235   public JComponent getPreferredFocusedComponent() {
236     return myShowFlatten ? myList : myTree;
237   }
238
239   public Dimension getPreferredSize() {
240     return new Dimension(400, 400);
241   }
242
243   public boolean isShowFlatten() {
244     return myShowFlatten;
245   }
246
247   public void setScrollPaneBorder(Border border) {
248     myListScrollPane.setBorder(border);
249     myTreeScrollPane.setBorder(border);
250   }
251
252   public void setShowFlatten(final boolean showFlatten) {
253     final List<T> wasSelected = getSelectedChanges();
254     myShowFlatten = showFlatten;
255     myCards.show(this, myShowFlatten ? LIST_CARD : TREE_CARD);
256     select(wasSelected);
257     if (myList.hasFocus() || myTree.hasFocus()) {
258       SwingUtilities.invokeLater(new Runnable() {
259         public void run() {
260           requestFocus();
261         }
262       });
263     }
264   }
265
266
267   public void requestFocus() {
268     if (myShowFlatten) {
269       myList.requestFocus();
270     }
271     else {
272       myTree.requestFocus();
273     }
274   }
275
276   public void setChangesToDisplay(final List<T> changes) {
277     setChangesToDisplay(changes, null);
278   }
279   
280   public void setChangesToDisplay(final List<T> changes, @Nullable final VirtualFile toSelect) {
281     final boolean wasEmpty = myList.isEmpty();
282     final List<T> sortedChanges = new ArrayList<T>(changes);
283     Collections.sort(sortedChanges, new Comparator<T>() {
284       public int compare(final T o1, final T o2) {
285         return TreeModelBuilder.getPathForObject(o1).getName().compareToIgnoreCase(TreeModelBuilder.getPathForObject(o2).getName());
286       }
287     });
288
289     final Set<Object> wasSelected = new HashSet<Object>(Arrays.asList(myList.getSelectedValues()));
290     myList.setModel(new AbstractListModel() {
291       @Override
292       public int getSize() {
293         return sortedChanges.size();
294       }
295
296       @Override
297       public Object getElementAt(int index) {
298         return sortedChanges.get(index);
299       }
300     });
301
302     final DefaultTreeModel model = buildTreeModel(changes, myChangeDecorator);
303     TreeState state = null;
304     if (! myAlwaysExpandList && ! wasEmpty) {
305       state = TreeState.createOn(myTree, (DefaultMutableTreeNode) myTree.getModel().getRoot());
306     }
307     myTree.setModel(model);
308     if (! myAlwaysExpandList && ! wasEmpty) {
309       state.applyTo(myTree, (DefaultMutableTreeNode) myTree.getModel().getRoot());
310
311       final TIntArrayList indices = new TIntArrayList();
312       for (int i = 0; i < sortedChanges.size(); i++) {
313         T t = sortedChanges.get(i);
314         if (wasSelected.contains(t)) {
315           indices.add(i);
316         }
317       }
318       myList.setSelectedIndices(indices.toNativeArray());
319       return;
320     }
321
322     final Runnable runnable = new Runnable() {
323       public void run() {
324         if (myProject.isDisposed()) return;
325         TreeUtil.expandAll(myTree);
326
327         int listSelection = 0;
328         int scrollRow = 0;
329
330         if (myShowCheckboxes) {
331           if (myIncludedChanges.size() > 0) {
332             int count = 0;
333             for (T change : changes) {
334               if (myIncludedChanges.contains(change)) {
335                 listSelection = count;
336                 break;
337               }
338               count++;
339             }
340
341             ChangesBrowserNode root = (ChangesBrowserNode)model.getRoot();
342             Enumeration enumeration = root.depthFirstEnumeration();
343
344             while (enumeration.hasMoreElements()) {
345               ChangesBrowserNode node = (ChangesBrowserNode)enumeration.nextElement();
346               final CheckboxTree.NodeState state = getNodeStatus(node);
347               if (node != root && state == CheckboxTree.NodeState.CLEAR) {
348                 myTree.collapsePath(new TreePath(node.getPath()));
349               }
350             }
351
352             enumeration = root.depthFirstEnumeration();
353             while (enumeration.hasMoreElements()) {
354               ChangesBrowserNode node = (ChangesBrowserNode)enumeration.nextElement();
355               final CheckboxTree.NodeState state = getNodeStatus(node);
356               if (state == CheckboxTree.NodeState.FULL && node.isLeaf()) {
357                 scrollRow = myTree.getRowForPath(new TreePath(node.getPath()));
358                 break;
359               }
360             }
361           }
362         } else {
363           if (toSelect != null) {
364             ChangesBrowserNode root = (ChangesBrowserNode)model.getRoot();
365             final int[] rowToSelect = new int[] {-1}; 
366             TreeUtil.traverse(root, new TreeUtil.Traverse() {
367               @Override
368               public boolean accept(Object node) {
369                 if (node instanceof DefaultMutableTreeNode) {
370                   Object userObject = ((DefaultMutableTreeNode)node).getUserObject();
371                   if (userObject instanceof Change) {
372                     Change change = (Change)userObject;
373                     VirtualFile virtualFile = change.getVirtualFile();
374                     if ((virtualFile != null && virtualFile.equals(toSelect)) || seemsToBeMoved(change, toSelect)) {
375                       TreeNode[] path = ((DefaultMutableTreeNode)node).getPath();
376                       rowToSelect[0] = myTree.getRowForPath(new TreePath(path));
377                     }
378                   }
379                 }
380
381                 return rowToSelect[0] == -1;
382               }
383             });
384             
385             scrollRow = rowToSelect[0] == -1 ? scrollRow : rowToSelect[0];
386           }
387         }
388         
389         if (changes.size() > 0) {
390           myList.setSelectedIndex(listSelection);
391           myList.ensureIndexIsVisible(listSelection);
392
393           myTree.setSelectionRow(scrollRow);
394           TreeUtil.showRowCentered(myTree, scrollRow, false);
395         }
396       }
397     };
398     if (ApplicationManager.getApplication().isDispatchThread()) {
399       runnable.run();
400     } else {
401       SwingUtilities.invokeLater(runnable);
402     }
403   }
404
405   private static boolean seemsToBeMoved(Change change, VirtualFile toSelect) {
406     ContentRevision afterRevision = change.getAfterRevision();
407     if (afterRevision == null) return false;
408     FilePath file = afterRevision.getFile();
409     return file.getName().equals(toSelect.getName());
410   }
411
412   protected abstract DefaultTreeModel buildTreeModel(final List<T> changes, final ChangeNodeDecorator changeNodeDecorator);
413
414   @SuppressWarnings({"SuspiciousMethodCalls"})
415   private void toggleSelection() {
416     boolean hasExcluded = false;
417     for (T value : getSelectedChanges()) {
418       if (!myIncludedChanges.contains(value)) {
419         hasExcluded = true;
420       }
421     }
422
423     if (hasExcluded) {
424       includeSelection();
425     }
426     else {
427       excludeSelection();
428     }
429
430     repaint();
431   }
432
433   private void includeSelection() {
434     for (T change : getSelectedChanges()) {
435       myIncludedChanges.add(change);
436     }
437     notifyInclusionListener();
438     repaint();
439   }
440
441   @SuppressWarnings({"SuspiciousMethodCalls"})
442   private void excludeSelection() {
443     for (T change : getSelectedChanges()) {
444       myIncludedChanges.remove(change);
445     }
446     notifyInclusionListener();
447     repaint();
448   }
449
450   public List<T> getChanges() {
451     if (myShowFlatten) {
452       ListModel m = myList.getModel();
453       int size = m.getSize();
454       List result = new ArrayList(size);
455       for (int i = 0; i < size; i++) {
456         result.add(m.getElementAt(i));
457       }
458       return result;
459     }
460     else {
461       final LinkedHashSet result = new LinkedHashSet();
462       TreeUtil.traverseDepth((ChangesBrowserNode)myTree.getModel().getRoot(), new TreeUtil.Traverse() {
463         public boolean accept(Object node) {
464           ChangesBrowserNode changeNode = (ChangesBrowserNode)node;
465           if (changeNode.isLeaf()) result.addAll(changeNode.getAllChangesUnder());
466           return true;
467         }
468       });
469       return new ArrayList<T>(result);
470     }
471   }
472
473   public int getSelectionCount() {
474     if (myShowFlatten) {
475       return myList.getSelectedIndices().length;
476     } else {
477       return myTree.getSelectionCount();
478     }
479   }
480
481   @NotNull
482   public List<T> getSelectedChanges() {
483     if (myShowFlatten) {
484       final Object[] o = myList.getSelectedValues();
485       final List<T> changes = new ArrayList<T>();
486       for (Object anO : o) {
487         changes.add((T)anO);
488       }
489
490       return changes;
491     }
492     else {
493       final List<T> changes = new ArrayList<T>();
494       final Set<Integer> checkSet = new HashSet<Integer>();
495       final TreePath[] paths = myTree.getSelectionPaths();
496       if (paths != null) {
497         for (TreePath path : paths) {
498           final ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
499           final List<T> objects = getSelectedObjects(node);
500           for (T object : objects) {
501             final int hash = object.hashCode();
502             if (! checkSet.contains(hash)) {
503               changes.add(object);
504               checkSet.add(hash);
505             } else {
506               if (! changes.contains(object)) {
507                 changes.add(object);
508               }
509             }
510           }
511         }
512       }
513
514       return changes;
515     }
516   }
517
518   protected abstract List<T> getSelectedObjects(final ChangesBrowserNode<T> node);
519
520   @Nullable
521   protected abstract T getLeadSelectedObject(final ChangesBrowserNode node);
522
523   @Nullable
524   public T getHighestLeadSelection() {
525     if (myShowFlatten) {
526       final int index = myList.getLeadSelectionIndex();
527       ListModel listModel = myList.getModel();
528       if (index < 0 || index >= listModel.getSize()) return null;
529       //noinspection unchecked
530       return (T)listModel.getElementAt(index);
531     }
532     else {
533       final TreePath path = myTree.getSelectionPath();
534       if (path == null) return null;
535       return getLeadSelectedObject((ChangesBrowserNode<T>)path.getLastPathComponent());
536     }
537   }
538
539   @Nullable
540   public T getLeadSelection() {
541     if (myShowFlatten) {
542       final int index = myList.getLeadSelectionIndex();
543       ListModel listModel = myList.getModel();
544       if (index < 0 || index >= listModel.getSize()) return null;
545       //noinspection unchecked
546       return (T)listModel.getElementAt(index);
547     }
548     else {
549       final TreePath path = myTree.getSelectionPath();
550       if (path == null) return null;
551       final List<T> changes = getSelectedObjects(((ChangesBrowserNode<T>)path.getLastPathComponent()));
552       return changes.size() > 0 ? changes.get(0) : null;
553     }
554   }
555
556   private void notifyInclusionListener() {
557     if (myInclusionListener != null) {
558       myInclusionListener.run();
559     }
560   }
561
562   // no listener supposed to be called
563   public void setIncludedChanges(final Collection<T> changes) {
564     myIncludedChanges.clear();
565     myIncludedChanges.addAll(changes);
566     myTree.repaint();
567     myList.repaint();
568   }
569
570   public void includeChange(final T change) {
571     myIncludedChanges.add(change);
572     notifyInclusionListener();
573     myTree.repaint();
574     myList.repaint();
575   }
576
577   public void includeChanges(final Collection<T> changes) {
578     myIncludedChanges.addAll(changes);
579     notifyInclusionListener();
580     myTree.repaint();
581     myList.repaint();
582   }
583
584   public void excludeChange(final T change) {
585     myIncludedChanges.remove(change);
586     notifyInclusionListener();
587     myTree.repaint();
588     myList.repaint();
589   }
590
591   public void excludeChanges(final Collection<T> changes) {
592     myIncludedChanges.removeAll(changes);
593     notifyInclusionListener();
594     myTree.repaint();
595     myList.repaint();
596   }
597
598   public boolean isIncluded(final T change) {
599     return myIncludedChanges.contains(change);
600   }
601
602   public Collection<T> getIncludedChanges() {
603     return myIncludedChanges;
604   }
605
606   public void expandAll() {
607     TreeUtil.expandAll(myTree);
608   }
609
610   public AnAction[] getTreeActions() {
611     final ToggleShowDirectoriesAction directoriesAction = new ToggleShowDirectoriesAction();
612     final ExpandAllAction expandAllAction = new ExpandAllAction(myTree) {
613       public void update(AnActionEvent e) {
614         e.getPresentation().setVisible(!myShowFlatten);
615       }
616     };
617     final CollapseAllAction collapseAllAction = new CollapseAllAction(myTree) {
618       public void update(AnActionEvent e) {
619         e.getPresentation().setVisible(!myShowFlatten);
620       }
621     };
622     final SelectAllAction selectAllAction = new SelectAllAction();
623     final AnAction[] actions = new AnAction[]{directoriesAction, expandAllAction, collapseAllAction, selectAllAction};
624     directoriesAction.registerCustomShortcutSet(
625       new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_P, SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK)),
626       this);
627     expandAllAction.registerCustomShortcutSet(
628       new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_EXPAND_ALL)),
629       myTree);
630     collapseAllAction.registerCustomShortcutSet(
631       new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_COLLAPSE_ALL)),
632       myTree);
633     selectAllAction.registerCustomShortcutSet(
634       new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_A, SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK)),
635       this);
636     return actions;
637   }
638
639   private class MyTreeCellRenderer extends JPanel implements TreeCellRenderer {
640     private final ChangesBrowserNodeRenderer myTextRenderer;
641     private final JCheckBox myCheckBox;
642
643
644     public MyTreeCellRenderer() {
645       super(new BorderLayout());
646       myCheckBox = new JCheckBox();
647       myTextRenderer = new ChangesBrowserNodeRenderer(myProject, false, myHighlightProblems);
648
649       if (myShowCheckboxes) {
650         add(myCheckBox, BorderLayout.WEST);
651       }
652
653       add(myTextRenderer, BorderLayout.CENTER);
654       setOpaque(false);
655     }
656
657     public Component getTreeCellRendererComponent(JTree tree,
658                                                   Object value,
659                                                   boolean selected,
660                                                   boolean expanded,
661                                                   boolean leaf,
662                                                   int row,
663                                                   boolean hasFocus) {
664
665       if (UIUtil.isUnderGTKLookAndFeel() || UIUtil.isUnderNimbusLookAndFeel()) {
666         NonOpaquePanel.setTransparent(this);
667         NonOpaquePanel.setTransparent(myCheckBox);
668       } else {
669         setBackground(null);
670         myCheckBox.setBackground(null);
671         myCheckBox.setOpaque(false);
672       }
673
674       myTextRenderer.setOpaque(false);
675       myTextRenderer.setTransparentIconBackground(true);
676       myTextRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
677       if (myShowCheckboxes) {
678         ChangesBrowserNode node = (ChangesBrowserNode)value;
679
680         CheckboxTree.NodeState state = getNodeStatus(node);
681         myCheckBox.setSelected(state != CheckboxTree.NodeState.CLEAR);
682         myCheckBox.setEnabled(state != CheckboxTree.NodeState.PARTIAL);
683         revalidate();
684
685         return this;
686       }
687       else {
688         return myTextRenderer;
689       }
690     }
691   }
692
693
694   private CheckboxTree.NodeState getNodeStatus(ChangesBrowserNode<T> node) {
695     boolean hasIncluded = false;
696     boolean hasExcluded = false;
697
698     for (T change : getSelectedObjects(node)) {
699       if (myIncludedChanges.contains(change)) {
700         hasIncluded = true;
701       }
702       else {
703         hasExcluded = true;
704       }
705     }
706
707     if (hasIncluded && hasExcluded) return CheckboxTree.NodeState.PARTIAL;
708     if (hasIncluded) return CheckboxTree.NodeState.FULL;
709     return CheckboxTree.NodeState.CLEAR;
710   }
711
712   private class MyListCellRenderer extends JPanel implements ListCellRenderer {
713     private final ColoredListCellRenderer myTextRenderer;
714     public final JCheckBox myCheckbox;
715
716     public MyListCellRenderer() {
717       super(new BorderLayout());
718       myCheckbox = new JCheckBox();
719       myTextRenderer = new VirtualFileListCellRenderer(myProject) {
720         @Override
721         protected void putParentPath(Object value, FilePath path, FilePath self) {
722           super.putParentPath(value, path, self);
723           final boolean applyChangeDecorator = (value instanceof Change) && myChangeDecorator != null;
724           if (applyChangeDecorator) {
725             myChangeDecorator.decorate((Change) value, this, isShowFlatten());
726           }
727         }
728
729         @Override
730         protected void putParentPathImpl(Object value, String parentPath, FilePath self) {
731           final boolean applyChangeDecorator = (value instanceof Change) && myChangeDecorator != null;
732           List<Pair<String,ChangeNodeDecorator.Stress>> parts = null;
733           if (applyChangeDecorator) {
734             parts = myChangeDecorator.stressPartsOfFileName((Change)value, parentPath);
735           }
736           if (parts == null) {
737             super.putParentPathImpl(value, parentPath, self);
738             return;
739           }
740
741           for (Pair<String, ChangeNodeDecorator.Stress> part : parts) {
742             append(part.getFirst(), part.getSecond().derive(SimpleTextAttributes.GRAYED_ATTRIBUTES));
743           }
744         }
745
746         @Override
747         public Component getListCellRendererComponent(JList list,
748                                                       Object value,
749                                                       int index,
750                                                       boolean selected,
751                                                       boolean hasFocus) {
752           final Component component = super.getListCellRendererComponent(list, value, index, selected, hasFocus);
753           final FileColorManager colorManager = FileColorManager.getInstance(myProject);
754           if (!selected) {
755             if (Registry.is("file.colors.in.commit.dialog") && colorManager.isEnabled() && colorManager.isEnabledForProjectView()) {
756               if (value instanceof Change) {
757                 final VirtualFile file = ((Change)value).getVirtualFile();
758                 if (file != null) {
759                   final Color color = colorManager.getFileColor(file);
760                   if (color != null) {
761                       component.setBackground(color);
762                   }
763                 }
764               }
765             }
766           }
767           return component;
768         }
769       };
770
771       myCheckbox.setBackground(null);
772       setBackground(null);
773
774       if (myShowCheckboxes) {
775         add(myCheckbox, BorderLayout.WEST);
776       }
777       add(myTextRenderer, BorderLayout.CENTER);
778     }
779
780     public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
781       myTextRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
782       if (myShowCheckboxes) {
783         myCheckbox.setSelected(myIncludedChanges.contains(value));
784         return this;
785       }
786       else {
787         return myTextRenderer;
788       }
789     }
790   }
791
792   private class MyToggleSelectionAction extends AnAction {
793     public void actionPerformed(AnActionEvent e) {
794       toggleSelection();
795     }
796   }
797
798   public class ToggleShowDirectoriesAction extends ToggleAction {
799     public ToggleShowDirectoriesAction() {
800       super(VcsBundle.message("changes.action.show.directories.text"),
801             VcsBundle.message("changes.action.show.directories.description"),
802             PlatformIcons.DIRECTORY_CLOSED_ICON);
803     }
804
805     public boolean isSelected(AnActionEvent e) {
806       return (! myProject.isDisposed()) && !PropertiesComponent.getInstance(myProject).isTrueValue(FLATTEN_OPTION_KEY);
807     }
808
809     public void setSelected(AnActionEvent e, boolean state) {
810       PropertiesComponent.getInstance(myProject).setValue(FLATTEN_OPTION_KEY, String.valueOf(!state));
811       setShowFlatten(!state);
812     }
813   }
814
815   private class SelectAllAction extends AnAction {
816     private SelectAllAction() {
817       super("Select All", "Select all items", AllIcons.Actions.Selectall);
818     }
819
820     public void actionPerformed(final AnActionEvent e) {
821       if (myShowFlatten) {
822         final int count = myList.getModel().getSize();
823         if (count > 0) {
824           myList.setSelectionInterval(0, count-1);
825         }
826       }
827       else {
828         final int countTree = myTree.getRowCount();
829         if (countTree > 0) {
830           myTree.setSelectionInterval(0, countTree-1);
831         }
832       }
833     }
834   }
835
836   public void select(final List<T> changes) {
837     final DefaultTreeModel treeModel = (DefaultTreeModel) myTree.getModel();
838     final TreeNode root = (TreeNode) treeModel.getRoot();
839     final List<TreePath> treeSelection = new ArrayList<TreePath>(changes.size());
840     TreeUtil.traverse(root, new TreeUtil.Traverse() {
841       public boolean accept(Object node) {
842         final T change = (T) ((DefaultMutableTreeNode) node).getUserObject();
843         if (changes.contains(change)) {
844           treeSelection.add(new TreePath(((DefaultMutableTreeNode) node).getPath()));
845         }
846         return true;
847       }
848     });
849     myTree.setSelectionPaths(treeSelection.toArray(new TreePath[treeSelection.size()]));
850
851     // list
852     final ListModel model = myList.getModel();
853     final int size = model.getSize();
854     final List<Integer> listSelection = new ArrayList<Integer>(changes.size());
855     for (int i = 0; i < size; i++) {
856       final T el = (T) model.getElementAt(i);
857       if (changes.contains(el)) {
858         listSelection.add(i);
859       }
860     }
861     myList.setSelectedIndices(int2int(listSelection));
862   }
863
864   private static int[] int2int(List<Integer> treeSelection) {
865     final int[] toPass = new int[treeSelection.size()];
866     int i = 0;
867     for (Integer integer : treeSelection) {
868       toPass[i] = integer;
869       ++ i;
870     }
871     return toPass;
872   }
873
874   public void enableSelection(final boolean value) {
875     myTree.setEnabled(value);
876   }
877
878   public void setAlwaysExpandList(boolean alwaysExpandList) {
879     myAlwaysExpandList = alwaysExpandList;
880   }
881
882   public void setPaintBusy(final boolean value) {
883     myTree.setPaintBusy(value);
884     myList.setPaintBusy(value);
885   }
886
887   @Override
888   public void calcData(DataKey key, DataSink sink) {
889   }
890
891   private class MyTree extends Tree implements TypeSafeDataProvider {
892
893     private final Project myProject;
894     private final int myCheckboxWidth;
895
896     public MyTree(Project project, int checkboxWidth) {
897       super(ChangesBrowserNode.create(ChangesTreeList.this.myProject, ChangesTreeList.ROOT));
898       myProject = project;
899       myCheckboxWidth = checkboxWidth;
900     }
901
902     @Override
903     public boolean isFileColorsEnabled() {
904       final boolean enabled = Registry.is("file.colors.in.commit.dialog")
905                         && FileColorManager.getInstance(myProject).isEnabled()
906                         && FileColorManager.getInstance(myProject).isEnabledForProjectView();
907       final boolean opaque = isOpaque();
908       if (enabled && opaque) {
909         setOpaque(false);
910       } else if (!enabled && !opaque) {
911         setOpaque(true);
912       }
913       return enabled;
914     }
915
916     @Override
917     public Color getFileColorFor(Object object) {
918       VirtualFile file = null;
919       if (object instanceof FilePathImpl) {
920         file = LocalFileSystem.getInstance().findFileByPath(((FilePathImpl)object).getPath());
921       } else if (object instanceof Change) {
922         file = ((Change)object).getVirtualFile();
923       }
924
925       if (file != null) {
926         return FileColorManager.getInstance(myProject).getFileColor(file);
927       }
928       return super.getFileColorFor(object);
929     }
930
931     public Dimension getPreferredScrollableViewportSize() {
932       Dimension size = super.getPreferredScrollableViewportSize();
933       size = new Dimension(size.width + 10, size.height);
934       return size;
935     }
936
937     protected void processMouseEvent(MouseEvent e) {
938       if (e.getID() == MouseEvent.MOUSE_PRESSED) {
939         if (! myTree.isEnabled()) return;
940         int row = myTree.getRowForLocation(e.getX(), e.getY());
941         if (row >= 0) {
942           final Rectangle baseRect = myTree.getRowBounds(row);
943           baseRect.setSize(myCheckboxWidth, baseRect.height);
944           if (baseRect.contains(e.getPoint())) {
945             myTree.setSelectionRow(row);
946             toggleSelection();
947           }
948         }
949       }
950       super.processMouseEvent(e);
951     }
952
953     public int getToggleClickCount() {
954       return -1;
955     }
956
957     @Override
958     public void calcData(DataKey key, DataSink sink) {
959       // just delegate to the change list
960       ChangesTreeList.this.calcData(key, sink);
961     }
962   }
963 }