IDEA-27603 (Indicate which branch is being worked on)
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / ChangesListView.java
blobac42ae182fc7a5c87adce7166a6fb7ad0ccf8dda
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.CopyProvider;
19 import com.intellij.ide.dnd.*;
20 import com.intellij.ide.util.treeView.TreeState;
21 import com.intellij.openapi.actionSystem.*;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider;
24 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Pair;
27 import com.intellij.openapi.vcs.*;
28 import com.intellij.openapi.vcs.changes.*;
29 import com.intellij.openapi.vcs.changes.issueLinks.TreeLinkMouseListener;
30 import com.intellij.openapi.vcs.diff.DiffProvider;
31 import com.intellij.openapi.vfs.VfsUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.ui.PopupHandler;
34 import com.intellij.ui.SmartExpander;
35 import com.intellij.ui.TreeCopyProvider;
36 import com.intellij.ui.TreeSpeedSearch;
37 import com.intellij.ui.awt.RelativePoint;
38 import com.intellij.ui.awt.RelativeRectangle;
39 import com.intellij.ui.treeStructure.Tree;
40 import com.intellij.util.EditSourceOnDoubleClickHandler;
41 import com.intellij.util.EditSourceOnEnterKeyHandler;
42 import com.intellij.util.containers.Convertor;
43 import com.intellij.util.containers.MultiMap;
44 import com.intellij.util.ui.tree.TreeUtil;
45 import org.jetbrains.annotations.NonNls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
49 import javax.swing.*;
50 import javax.swing.table.TableCellRenderer;
51 import javax.swing.tree.DefaultTreeModel;
52 import javax.swing.tree.TreeCellRenderer;
53 import javax.swing.tree.TreePath;
54 import javax.swing.tree.TreeSelectionModel;
55 import java.awt.*;
56 import java.awt.event.MouseEvent;
57 import java.awt.image.BufferedImage;
58 import java.util.*;
59 import java.util.List;
61 /**
62 * @author max
64 public class ChangesListView extends Tree implements TypeSafeDataProvider, AdvancedDnDSource {
65 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.ui.ChangesListView");
67 private ChangesListView.DropTarget myDropTarget;
68 private DnDManager myDndManager;
69 private ChangeListOwner myDragOwner;
70 private final Project myProject;
71 private TreeState myTreeState;
72 private boolean myShowFlatten = false;
73 private final CopyProvider myCopyProvider;
75 @NonNls public static final String HELP_ID_KEY = "helpId";
76 @NonNls public static final String ourHelpId = "ideaInterface.changes";
77 @NonNls public static final DataKey<List<VirtualFile>> UNVERSIONED_FILES_DATA_KEY = DataKey.create("ChangeListView.UnversionedFiles");
78 @NonNls public static final DataKey<List<FilePath>> MISSING_FILES_DATA_KEY = DataKey.create("ChangeListView.MissingFiles");
79 @NonNls public static final DataKey<String> HELP_ID_DATA_KEY = DataKey.create(HELP_ID_KEY);
81 private ActionGroup myMenuGroup;
83 public ChangesListView(final Project project) {
84 myProject = project;
86 getModel().setRoot(ChangesBrowserNode.create(myProject, TreeModelBuilder.ROOT_NODE_VALUE));
88 setShowsRootHandles(true);
89 setRootVisible(false);
91 new TreeSpeedSearch(this, new NodeToTextConvertor());
92 SmartExpander.installOn(this);
93 myCopyProvider = new TreeCopyProvider(this);
94 new TreeLinkMouseListener(new ChangesBrowserNodeRenderer(myProject, false, false)).install(this);
97 public DefaultTreeModel getModel() {
98 return (DefaultTreeModel)super.getModel();
101 public void installDndSupport(ChangeListOwner owner) {
102 myDragOwner = owner;
103 myDropTarget = new DropTarget();
104 myDndManager = DnDManager.getInstance();
106 myDndManager.registerSource(this);
107 myDndManager.registerTarget(myDropTarget, this);
110 public void dispose() {
111 if (myDropTarget != null) {
112 myDndManager.unregisterSource(this);
113 myDndManager.unregisterTarget(myDropTarget, this);
115 myDropTarget = null;
116 myDndManager = null;
117 myDragOwner = null;
121 private void storeState() {
122 myTreeState = TreeState.createOn(this, (ChangesBrowserNode)getModel().getRoot());
125 private void restoreState() {
126 myTreeState.applyTo(this, (ChangesBrowserNode)getModel().getRoot());
129 public boolean isShowFlatten() {
130 return myShowFlatten;
133 public void setShowFlatten(final boolean showFlatten) {
134 myShowFlatten = showFlatten;
137 public void updateModel(List<? extends ChangeList> changeLists, List<VirtualFile> unversionedFiles, final List<LocallyDeletedChange> locallyDeletedFiles,
138 List<VirtualFile> modifiedWithoutEditing,
139 MultiMap<String, VirtualFile> switchedFiles,
140 @Nullable Map<VirtualFile, String> switchedRoots,
141 @Nullable List<VirtualFile> ignoredFiles,
142 final List<VirtualFile> lockedFolders,
143 @Nullable final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
144 storeState();
146 TreeModelBuilder builder = new TreeModelBuilder(myProject, isShowFlatten());
147 final DefaultTreeModel model = builder.buildModel(changeLists, unversionedFiles, locallyDeletedFiles, modifiedWithoutEditing,
148 switchedFiles, switchedRoots, ignoredFiles, lockedFolders, logicallyLockedFiles);
149 setModel(model);
150 setCellRenderer(new ChangesBrowserNodeRenderer(myProject, isShowFlatten(), true));
152 expandPath(new TreePath(((ChangesBrowserNode)model.getRoot()).getPath()));
154 restoreState();
157 public void calcData(DataKey key, DataSink sink) {
158 if (key == VcsDataKeys.CHANGES) {
159 sink.put(VcsDataKeys.CHANGES, getSelectedChanges());
161 else if (key == VcsDataKeys.CHANGE_LEAD_SELECTION) {
162 sink.put(VcsDataKeys.CHANGE_LEAD_SELECTION, getLeadSelection());
164 else if (key == VcsDataKeys.CHANGE_LISTS) {
165 sink.put(VcsDataKeys.CHANGE_LISTS, getSelectedChangeLists());
167 else if (key == PlatformDataKeys.VIRTUAL_FILE_ARRAY) {
168 sink.put(PlatformDataKeys.VIRTUAL_FILE_ARRAY, getSelectedFiles());
170 else if (key == PlatformDataKeys.NAVIGATABLE) {
171 final VirtualFile[] files = getSelectedFiles();
172 if (files.length == 1 && !files [0].isDirectory()) {
173 sink.put(PlatformDataKeys.NAVIGATABLE, new OpenFileDescriptor(myProject, files[0], 0));
176 else if (key == PlatformDataKeys.NAVIGATABLE_ARRAY) {
177 sink.put(PlatformDataKeys.NAVIGATABLE_ARRAY, ChangesUtil.getNavigatableArray(myProject, getSelectedFiles()));
179 else if (key == PlatformDataKeys.DELETE_ELEMENT_PROVIDER) {
180 final TreePath[] paths = getSelectionPaths();
181 if (paths != null) {
182 for(TreePath path: paths) {
183 ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
184 if (!(node.getUserObject() instanceof ChangeList)) {
185 sink.put(PlatformDataKeys.DELETE_ELEMENT_PROVIDER, new VirtualFileDeleteProvider());
186 break;
191 else if (key == PlatformDataKeys.COPY_PROVIDER) {
192 sink.put(PlatformDataKeys.COPY_PROVIDER, myCopyProvider);
194 else if (key == UNVERSIONED_FILES_DATA_KEY) {
195 sink.put(UNVERSIONED_FILES_DATA_KEY, getSelectedUnversionedFiles());
197 else if (key == VcsDataKeys.MODIFIED_WITHOUT_EDITING_DATA_KEY) {
198 sink.put(VcsDataKeys.MODIFIED_WITHOUT_EDITING_DATA_KEY, getSelectedModifiedWithoutEditing());
200 else if (key == MISSING_FILES_DATA_KEY) {
201 sink.put(MISSING_FILES_DATA_KEY, getSelectedMissingFiles());
203 else if (key == HELP_ID_DATA_KEY) {
204 sink.put(HELP_ID_DATA_KEY, ourHelpId);
206 else if (key == VcsDataKeys.CHANGES_IN_LIST_KEY) {
207 final TreePath selectionPath = getSelectionPath();
208 if (selectionPath != null && selectionPath.getPathCount() > 1) {
209 ChangesBrowserNode firstNode = (ChangesBrowserNode)selectionPath.getPathComponent(1);
210 if (firstNode instanceof ChangesBrowserChangeListNode) {
211 final List<Change> list = firstNode.getAllChangesUnder();
212 sink.put(VcsDataKeys.CHANGES_IN_LIST_KEY, list);
218 private List<VirtualFile> getSelectedUnversionedFiles() {
219 return getSelectedVirtualFiles(ChangesBrowserNode.UNVERSIONED_FILES_TAG);
222 private List<VirtualFile> getSelectedModifiedWithoutEditing() {
223 return getSelectedVirtualFiles(ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
226 private List<VirtualFile> getSelectedIgnoredFiles() {
227 return getSelectedVirtualFiles(ChangesBrowserNode.IGNORED_FILES_TAG);
230 private List<VirtualFile> getSelectedVirtualFiles(final Object tag) {
231 Set<VirtualFile> files = new HashSet<VirtualFile>();
232 final TreePath[] paths = getSelectionPaths();
233 if (paths != null) {
234 for (TreePath path : paths) {
235 if (path.getPathCount() > 1) {
236 ChangesBrowserNode firstNode = (ChangesBrowserNode)path.getPathComponent(1);
237 if (tag == null || firstNode.getUserObject() == tag) {
238 ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
239 files.addAll(node.getAllFilesUnder());
244 return new ArrayList<VirtualFile>(files);
247 private List<FilePath> getSelectedMissingFiles() {
248 List<FilePath> files = new ArrayList<FilePath>();
249 final TreePath[] paths = getSelectionPaths();
250 if (paths != null) {
251 for (TreePath path : paths) {
252 ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
253 files.addAll(node.getAllFilePathsUnder());
256 return files;
259 private VirtualFile[] getSelectedFiles() {
260 final Change[] changes = getSelectedChanges();
261 Collection<VirtualFile> files = new HashSet<VirtualFile>();
262 for (Change change : changes) {
263 final ContentRevision afterRevision = change.getAfterRevision();
264 if (afterRevision != null) {
265 final VirtualFile file = afterRevision.getFile().getVirtualFile();
266 if (file != null && file.isValid()) {
267 files.add(file);
272 files.addAll(getSelectedVirtualFiles(null));
274 return VfsUtil.toVirtualFileArray(files);
277 private Change[] getLeadSelection() {
278 final Set<Change> changes = new LinkedHashSet<Change>();
280 final TreePath[] paths = getSelectionPaths();
281 if (paths == null) return new Change[0];
283 for (TreePath path : paths) {
284 ChangesBrowserNode node = (ChangesBrowserNode) path.getLastPathComponent();
285 if (node instanceof ChangesBrowserChangeNode) {
286 changes.add(((ChangesBrowserChangeNode) node).getUserObject());
290 return changes.toArray(new Change[changes.size()]);
293 @NotNull
294 private Change[] getSelectedChanges() {
295 Set<Change> changes = new LinkedHashSet<Change>();
297 final TreePath[] paths = getSelectionPaths();
298 if (paths == null) return new Change[0];
300 for (TreePath path : paths) {
301 ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
302 changes.addAll(node.getAllChangesUnder());
305 if (changes.size() == 0) {
306 final List<VirtualFile> selectedModifiedWithoutEditing = getSelectedModifiedWithoutEditing();
307 if (selectedModifiedWithoutEditing != null && selectedModifiedWithoutEditing.size() > 0) {
308 for(VirtualFile file: selectedModifiedWithoutEditing) {
309 AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(file);
310 final DiffProvider diffProvider = vcs == null ? null : vcs.getDiffProvider();
311 if (diffProvider != null) {
312 ContentRevision beforeRevision = new VcsCurrentRevisionProxy(diffProvider, file);
313 ContentRevision afterRevision = new CurrentContentRevision(new FilePathImpl(file));
314 changes.add(new Change(beforeRevision, afterRevision, FileStatus.HIJACKED));
320 return changes.toArray(new Change[changes.size()]);
323 @NotNull
324 private ChangeList[] getSelectedChangeLists() {
325 Set<ChangeList> lists = new HashSet<ChangeList>();
327 final TreePath[] paths = getSelectionPaths();
328 if (paths == null) return new ChangeList[0];
330 for (TreePath path : paths) {
331 ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
332 final Object userObject = node.getUserObject();
333 if (userObject instanceof ChangeList) {
334 lists.add((ChangeList)userObject);
338 return lists.toArray(new ChangeList[lists.size()]);
341 public void setMenuActions(final ActionGroup menuGroup) {
342 myMenuGroup = menuGroup;
343 updateMenu();
344 EditSourceOnDoubleClickHandler.install(this);
345 EditSourceOnEnterKeyHandler.install(this);
348 private void updateMenu() {
349 PopupHandler.installPopupHandler(this, myMenuGroup, ActionPlaces.CHANGES_VIEW_POPUP, ActionManager.getInstance());
352 public void updateUI() {
353 super.updateUI();
354 if (myMenuGroup != null) {
355 updateMenu();
359 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
360 private static class DragImageFactory {
361 private static void drawSelection(JTable table, int column, Graphics g, final int width) {
362 int y = 0;
363 final int[] rows = table.getSelectedRows();
364 final int height = table.getRowHeight();
365 for (int row : rows) {
366 final TableCellRenderer renderer = table.getCellRenderer(row, column);
367 final Component component = renderer.getTableCellRendererComponent(table, table.getValueAt(row, column), false, false, row, column);
368 g.translate(0, y);
369 component.setBounds(0, 0, width, height);
370 boolean wasOpaque = false;
371 if (component instanceof JComponent) {
372 final JComponent j = (JComponent)component;
373 if (j.isOpaque()) wasOpaque = true;
374 j.setOpaque(false);
376 component.paint(g);
377 if (wasOpaque) {
378 ((JComponent)component).setOpaque(true);
380 y += height;
381 g.translate(0, -y);
385 private static void drawSelection(JTree tree, Graphics g, final int width) {
386 int y = 0;
387 final int[] rows = tree.getSelectionRows();
388 final int height = tree.getRowHeight();
389 for (int row : rows) {
390 final TreeCellRenderer renderer = tree.getCellRenderer();
391 final Object value = tree.getPathForRow(row).getLastPathComponent();
392 if (value == null) continue;
393 final Component component = renderer.getTreeCellRendererComponent(tree, value, false, false, false, row, false);
394 if (component.getFont() == null) {
395 component.setFont(tree.getFont());
397 g.translate(0, y);
398 component.setBounds(0, 0, width, height);
399 boolean wasOpaque = false;
400 if (component instanceof JComponent) {
401 final JComponent j = (JComponent)component;
402 if (j.isOpaque()) wasOpaque = true;
403 j.setOpaque(false);
405 component.paint(g);
406 if (wasOpaque) {
407 ((JComponent)component).setOpaque(true);
409 y += height;
410 g.translate(0, -y);
414 public static Image createImage(final JTable table, int column) {
415 final int height = Math.max(20, Math.min(100, table.getSelectedRowCount() * table.getRowHeight()));
416 final int width = table.getColumnModel().getColumn(column).getWidth();
418 final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
419 Graphics2D g2 = (Graphics2D)image.getGraphics();
421 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));
423 drawSelection(table, column, g2, width);
424 return image;
427 public static Image createImage(final JTree tree) {
428 final TreeSelectionModel model = tree.getSelectionModel();
429 final TreePath[] paths = model.getSelectionPaths();
431 int count = 0;
432 final List<ChangesBrowserNode> nodes = new ArrayList<ChangesBrowserNode>();
433 for (final TreePath path : paths) {
434 final ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
435 if (!node.isLeaf()) {
436 nodes.add(node);
437 count += node.getCount();
441 for (TreePath path : paths) {
442 final ChangesBrowserNode element = (ChangesBrowserNode)path.getLastPathComponent();
443 boolean child = false;
444 for (final ChangesBrowserNode node : nodes) {
445 if (node.isNodeChild(element)) {
446 child = true;
447 break;
451 if (!child) {
452 if (element.isLeaf()) count++;
453 } else if (!element.isLeaf()) {
454 count -= element.getCount();
458 final JLabel label = new JLabel(VcsBundle.message("changes.view.dnd.label", count));
459 label.setOpaque(true);
460 label.setForeground(tree.getForeground());
461 label.setBackground(tree.getBackground());
462 label.setFont(tree.getFont());
463 label.setSize(label.getPreferredSize());
464 final BufferedImage image = new BufferedImage(label.getWidth(), label.getHeight(), BufferedImage.TYPE_INT_ARGB);
466 Graphics2D g2 = (Graphics2D)image.getGraphics();
467 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f));
468 label.paint(g2);
469 g2.dispose();
471 return image;
477 public class DropTarget implements DnDTarget {
478 public boolean update(DnDEvent aEvent) {
479 aEvent.hideHighlighter();
480 aEvent.setDropPossible(false, "");
482 Object attached = aEvent.getAttachedObject();
483 if (!(attached instanceof ChangeListDragBean)) return false;
485 final ChangeListDragBean dragBean = (ChangeListDragBean)attached;
486 if (dragBean.getSourceComponent() != ChangesListView.this) return false;
487 dragBean.setTargetNode(null);
489 RelativePoint dropPoint = aEvent.getRelativePoint();
490 Point onTree = dropPoint.getPoint(ChangesListView.this);
491 final TreePath dropPath = getPathForLocation(onTree.x, onTree.y);
493 if (dropPath == null) return false;
495 ChangesBrowserNode dropNode = (ChangesBrowserNode)dropPath.getLastPathComponent();
496 while(!((ChangesBrowserNode) dropNode.getParent()).isRoot()) {
497 dropNode = (ChangesBrowserNode)dropNode.getParent();
500 if (!dropNode.canAcceptDrop(dragBean)) {
501 return false;
504 final Rectangle tableCellRect = getPathBounds(new TreePath(dropNode.getPath()));
505 if (fitsInBounds(tableCellRect)) {
506 aEvent.setHighlighting(new RelativeRectangle(ChangesListView.this, tableCellRect), DnDEvent.DropTargetHighlightingType.RECTANGLE);
509 aEvent.setDropPossible(true, null);
510 dragBean.setTargetNode(dropNode);
512 return false;
515 public void drop(DnDEvent aEvent) {
516 Object attached = aEvent.getAttachedObject();
517 if (!(attached instanceof ChangeListDragBean)) return;
519 final ChangeListDragBean dragBean = (ChangeListDragBean)attached;
520 final ChangesBrowserNode changesBrowserNode = dragBean.getTargetNode();
521 if (changesBrowserNode != null) {
522 changesBrowserNode.acceptDrop(myDragOwner, dragBean);
526 public void cleanUpOnLeave() {
529 public void updateDraggedImage(Image image, Point dropPoint, Point imageOffset) {
533 private boolean fitsInBounds(final Rectangle rect) {
534 final Container container = getParent();
535 if (container instanceof JViewport) {
536 final Container scrollPane = container.getParent();
537 if (scrollPane instanceof JScrollPane) {
538 final Rectangle rectangle = SwingUtilities.convertRectangle(this, rect, scrollPane.getParent());
539 return scrollPane.getBounds().contains(rectangle);
542 return true;
545 private static class NodeToTextConvertor implements Convertor<TreePath, String> {
546 public String convert(final TreePath path) {
547 ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent();
548 return node.getTextPresentation();
552 public boolean canStartDragging(DnDAction action, Point dragOrigin) {
553 return action == DnDAction.MOVE &&
554 (getSelectedChanges().length > 0 || getSelectedUnversionedFiles().size() > 0 || getSelectedIgnoredFiles().size() > 0);
557 public DnDDragStartBean startDragging(DnDAction action, Point dragOrigin) {
558 return new DnDDragStartBean(new ChangeListDragBean(this, getSelectedChanges(), getSelectedUnversionedFiles(),
559 getSelectedIgnoredFiles()));
562 @Nullable
563 public Pair<Image, Point> createDraggedImage(DnDAction action, Point dragOrigin) {
564 final Image image = DragImageFactory.createImage(this);
565 return new Pair<Image, Point>(image, new Point(-image.getWidth(null), -image.getHeight(null)));
568 public void dragDropEnd() {
571 public void dropActionChanged(final int gestureModifiers) {
574 @NotNull
575 public JComponent getComponent() {
576 return this;
579 public void processMouseEvent(final MouseEvent e) {
580 if (MouseEvent.MOUSE_RELEASED == e.getID() && !isSelectionEmpty() && !e.isShiftDown() && !e.isControlDown() &&
581 !e.isMetaDown() && !e.isPopupTrigger()) {
582 if (isOverSelection(e.getPoint())) {
583 clearSelection();
584 final TreePath path = getPathForLocation(e.getPoint().x, e.getPoint().y);
585 if (path != null) {
586 setSelectionPath(path);
587 e.consume();
593 super.processMouseEvent(e);
596 public boolean isOverSelection(final Point point) {
597 return TreeUtil.isOverSelection(this, point);
600 public void dropSelectionButUnderPoint(final Point point) {
601 TreeUtil.dropSelectionButUnderPoint(this, point);