VCS: asynch load of committed changes when do "Browse changes"
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / committed / CommittedChangesTreeBrowser.java
blob9be7b60e0aeeac62f324005732b358cf02bded94
1 /*
2 * Copyright (c) 2007, Your Corporation. All Rights Reserved.
3 */
5 package com.intellij.openapi.vcs.changes.committed;
7 import com.intellij.ide.CopyProvider;
8 import com.intellij.ide.DefaultTreeExpander;
9 import com.intellij.ide.TreeExpander;
10 import com.intellij.ide.actions.ContextHelpAction;
11 import com.intellij.ide.ui.SplitterProportionsDataImpl;
12 import com.intellij.ide.util.treeView.TreeState;
13 import com.intellij.openapi.Disposable;
14 import com.intellij.openapi.actionSystem.*;
15 import com.intellij.openapi.application.ApplicationManager;
16 import com.intellij.openapi.application.ModalityState;
17 import com.intellij.openapi.keymap.KeymapManager;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.ui.Splitter;
20 import com.intellij.openapi.ui.SplitterProportionsData;
21 import com.intellij.openapi.ui.ThreeComponentsSplitter;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.openapi.vcs.AbstractVcs;
25 import com.intellij.openapi.vcs.CachingCommittedChangesProvider;
26 import com.intellij.openapi.vcs.VcsBundle;
27 import com.intellij.openapi.vcs.VcsDataKeys;
28 import com.intellij.openapi.vcs.changes.Change;
29 import com.intellij.openapi.vcs.changes.ChangesUtil;
30 import com.intellij.openapi.vcs.changes.ContentRevision;
31 import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer;
32 import com.intellij.openapi.vcs.changes.issueLinks.TreeLinkMouseListener;
33 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
34 import com.intellij.pom.Navigatable;
35 import com.intellij.ui.ColoredTreeCellRenderer;
36 import com.intellij.ui.PopupHandler;
37 import com.intellij.ui.SimpleTextAttributes;
38 import com.intellij.ui.TreeCopyProvider;
39 import com.intellij.ui.treeStructure.actions.CollapseAllAction;
40 import com.intellij.ui.treeStructure.actions.ExpandAllAction;
41 import com.intellij.util.messages.MessageBusConnection;
42 import com.intellij.util.messages.Topic;
43 import com.intellij.util.ui.TreeWithEmptyText;
44 import com.intellij.util.ui.UIUtil;
45 import com.intellij.util.ui.tree.TreeUtil;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
50 import javax.swing.*;
51 import javax.swing.event.ChangeEvent;
52 import javax.swing.event.ChangeListener;
53 import javax.swing.event.TreeSelectionEvent;
54 import javax.swing.event.TreeSelectionListener;
55 import javax.swing.tree.DefaultMutableTreeNode;
56 import javax.swing.tree.DefaultTreeModel;
57 import javax.swing.tree.TreeModel;
58 import javax.swing.tree.TreePath;
59 import java.awt.*;
60 import java.awt.event.ActionListener;
61 import java.text.DateFormat;
62 import java.util.*;
63 import java.util.List;
65 /**
66 * @author yole
68 public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataProvider, Disposable, DecoratorManager {
69 private static final Object MORE_TAG = new Object();
71 private final Project myProject;
72 // left part - with committed changelists list
73 private final TreeWithEmptyText myChangesTree;
74 // right part - with details
75 private final RepositoryChangesBrowser myChangesView;
76 private List<CommittedChangeList> myChangeLists;
77 private List<CommittedChangeList> mySelectedChangeLists;
78 private ChangeListGroupingStrategy myGroupingStrategy = new ChangeListGroupingStrategy.DateChangeListGroupingStrategy();
79 private final CompositeChangeListFilteringStrategy myFilteringStrategy = new CompositeChangeListFilteringStrategy();
80 private final Splitter myFilterSplitter;
81 private final JPanel myLeftPanel;
82 private final JPanel myToolbarPanel;
83 private final FilterChangeListener myFilterChangeListener = new FilterChangeListener();
84 private final SplitterProportionsData mySplitterProportionsData = new SplitterProportionsDataImpl();
85 private final CopyProvider myCopyProvider;
86 private final TreeExpander myTreeExpander;
87 private String myHelpId;
89 public static final Topic<CommittedChangesReloadListener> ITEMS_RELOADED = new Topic<CommittedChangesReloadListener>("ITEMS_RELOADED", CommittedChangesReloadListener.class);
91 private final List<CommittedChangeListDecorator> myDecorators;
93 @NonNls public static final String ourHelpId = "reference.changesToolWindow.incoming";
95 private final WiseSplitter myInnerSplitter;
96 private MessageBusConnection myConnection;
97 private List<CommittedChangeList> myFilteredChangeLists;
98 private Object[] mySelectedFiltering;
99 private JLabel myLoadingLabel;
100 private int[] mySelectionRows;
101 private TreeState myState;
102 private JPanel myLoadingPanel;
104 public CommittedChangesTreeBrowser(final Project project, final List<CommittedChangeList> changeLists) {
105 super(new BorderLayout());
107 myProject = project;
108 myDecorators = new LinkedList<CommittedChangeListDecorator>();
109 myChangeLists = changeLists;
110 myChangesTree = new ChangesBrowserTree();
111 myChangesTree.setRootVisible(false);
112 myChangesTree.setShowsRootHandles(true);
113 myChangesTree.setCellRenderer(new CommittedChangeListRenderer(project, myDecorators));
114 TreeUtil.expandAll(myChangesTree);
116 myChangesView = new RepositoryChangesBrowser(project, changeLists);
117 myChangesView.getListPanel().setBorder(null);
119 myChangesTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
120 public void valueChanged(TreeSelectionEvent e) {
121 updateBySelectionChange();
125 final TreeLinkMouseListener linkMouseListener = new TreeLinkMouseListener(new CommittedChangeListRenderer(project, myDecorators));
126 linkMouseListener.install(myChangesTree);
128 myLeftPanel = new JPanel(new BorderLayout());
129 myToolbarPanel = new JPanel(new BorderLayout());
130 myLeftPanel.add(myToolbarPanel, BorderLayout.NORTH);
131 myFilterSplitter = new Splitter(false, 0.5f);
133 myLoadingPanel = new JPanel(new BorderLayout());
134 myLoadingPanel.setBackground(UIUtil.getToolTipBackground());
135 myLoadingLabel = new JLabel("Loading...");
137 myLoadingPanel.add(myLoadingLabel, BorderLayout.CENTER);
139 myToolbarPanel.add(myLoadingPanel, BorderLayout.CENTER);
140 myFilterSplitter.setSecondComponent(new JScrollPane(myChangesTree));
141 myLoadingPanel.setVisible(false);
142 myLeftPanel.add(myFilterSplitter, BorderLayout.CENTER);
143 final Splitter splitter = new Splitter(false, 0.7f);
144 splitter.setFirstComponent(myLeftPanel);
145 splitter.setSecondComponent(myChangesView);
147 add(splitter, BorderLayout.CENTER);
149 myInnerSplitter = new WiseSplitter(new Runnable() {
150 public void run() {
151 myFilterSplitter.doLayout();
152 updateModel();
154 }, myFilterSplitter);
156 mySplitterProportionsData.externalizeFromDimensionService("CommittedChanges.SplitterProportions");
157 mySplitterProportionsData.restoreSplitterProportions(this);
159 updateBySelectionChange();
161 ActionManager.getInstance().getAction("CommittedChanges.Details").registerCustomShortcutSet(
162 new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_QUICK_JAVADOC)),
163 this);
165 myCopyProvider = new TreeCopyProvider(myChangesTree);
166 myTreeExpander = new DefaultTreeExpander(myChangesTree);
167 myChangesView.addToolbarAction(ActionManager.getInstance().getAction("Vcs.ShowTabbedFileHistory"));
169 myHelpId = ourHelpId;
171 myChangesView.getDiffAction().registerCustomShortcutSet(CommonShortcuts.getDiff(), myChangesTree);
173 myConnection = myProject.getMessageBus().connect();
174 myConnection.subscribe(ITEMS_RELOADED, new CommittedChangesReloadListener() {
175 public void itemsReloaded() {
177 public void emptyRefresh() {
178 updateGrouping();
183 public void addFilter(final ChangeListFilteringStrategy strategy) {
184 myFilteringStrategy.addStrategy("permanent", strategy);
185 strategy.addChangeListener(myFilterChangeListener);
188 private void updateGrouping() {
189 if (myGroupingStrategy.changedSinceApply()) {
190 ApplicationManager.getApplication().invokeLater(new Runnable() {
191 public void run() {
192 updateModel();
194 }, ModalityState.NON_MODAL);
198 private TreeModel buildTreeModel(final List<CommittedChangeList> filteredChangeLists) {
199 myFilteredChangeLists = filteredChangeLists;
200 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
201 DefaultTreeModel model = new DefaultTreeModel(root);
202 DefaultMutableTreeNode lastGroupNode = null;
203 String lastGroupName = null;
204 Collections.sort(filteredChangeLists, myGroupingStrategy.getComparator());
205 myGroupingStrategy.beforeStart();
206 for(CommittedChangeList list: filteredChangeLists) {
207 String groupName = myGroupingStrategy.getGroupName(list);
208 if (!Comparing.equal(groupName, lastGroupName)) {
209 lastGroupName = groupName;
210 lastGroupNode = new DefaultMutableTreeNode(lastGroupName);
211 root.add(lastGroupNode);
213 assert lastGroupNode != null;
214 lastGroupNode.add(new DefaultMutableTreeNode(list));
216 return model;
219 public void setHelpId(final String helpId) {
220 myHelpId = helpId;
223 public void setEmptyText(final String emptyText) {
224 myChangesTree.setEmptyText(emptyText);
227 public void clearEmptyText() {
228 myChangesTree.clearEmptyText();
231 public void appendEmptyText(final String text, final SimpleTextAttributes attrs) {
232 myChangesTree.appendEmptyText(text, attrs);
235 public void appendEmptyText(final String text, final SimpleTextAttributes attrs, ActionListener clickListener) {
236 myChangesTree.appendEmptyText(text, attrs, clickListener);
239 public void addToolBar(JComponent toolBar) {
240 myToolbarPanel.add(toolBar, BorderLayout.NORTH);
243 public void dispose() {
244 myConnection.disconnect();
245 mySplitterProportionsData.saveSplitterProportions(this);
246 mySplitterProportionsData.externalizeToDimensionService("CommittedChanges.SplitterProportions");
247 myChangesView.dispose();
250 public void setItems(@NotNull List<CommittedChangeList> items, final boolean keepFilter, final CommittedChangesBrowserUseCase useCase) {
251 myChangesView.setUseCase(useCase);
252 myChangeLists = items;
253 if (!keepFilter) {
254 myFilteringStrategy.setFilterBase(items);
256 myProject.getMessageBus().syncPublisher(ITEMS_RELOADED).itemsReloaded();
257 updateModel();
260 private void updateModel() {
261 final List<CommittedChangeList> filteredChangeLists = myFilteringStrategy.filterChangeLists(myChangeLists);
262 myChangesTree.setModel(buildTreeModel(filteredChangeLists));
263 TreeUtil.expandAll(myChangesTree);
266 public void setGroupingStrategy(ChangeListGroupingStrategy strategy) {
267 myGroupingStrategy = strategy;
268 updateModel();
271 private void updateBySelectionChange() {
272 List<CommittedChangeList> selection = new ArrayList<CommittedChangeList>();
273 final TreePath[] selectionPaths = myChangesTree.getSelectionPaths();
274 if (selectionPaths != null) {
275 for(TreePath path: selectionPaths) {
276 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
277 if (node.getUserObject() instanceof CommittedChangeList) {
278 selection.add((CommittedChangeList) node.getUserObject());
283 if (!selection.equals(mySelectedChangeLists)) {
284 mySelectedChangeLists = selection;
285 myChangesView.setChangesToDisplay(collectChanges(mySelectedChangeLists, false));
289 private static List<Change> collectChanges(final List<CommittedChangeList> selectedChangeLists, final boolean withMovedTrees) {
290 List<Change> result = new ArrayList<Change>();
291 Collections.sort(selectedChangeLists, new Comparator<CommittedChangeList>() {
292 public int compare(final CommittedChangeList o1, final CommittedChangeList o2) {
293 return o1.getCommitDate().compareTo(o2.getCommitDate());
296 for(CommittedChangeList cl: selectedChangeLists) {
297 final Collection<Change> changes = withMovedTrees ? cl.getChangesWithMovedTrees() : cl.getChanges();
298 for(Change c: changes) {
299 addOrReplaceChange(result, c);
302 return result;
305 private static void addOrReplaceChange(final List<Change> changes, final Change c) {
306 final ContentRevision beforeRev = c.getBeforeRevision();
307 if (beforeRev != null) {
308 for(Change oldChange: changes) {
309 ContentRevision rev = oldChange.getAfterRevision();
310 if (rev != null && rev.getFile().getIOFile().getAbsolutePath().equals(beforeRev.getFile().getIOFile().getAbsolutePath())) {
311 changes.remove(oldChange);
312 if (oldChange.getBeforeRevision() != null || c.getAfterRevision() != null) {
313 changes.add(new Change(oldChange.getBeforeRevision(), c.getAfterRevision()));
315 return;
319 changes.add(c);
322 private List<CommittedChangeList> getSelectedChangeLists() {
323 return TreeUtil.collectSelectedObjectsOfType(myChangesTree, CommittedChangeList.class);
326 public void setTableContextMenu(final ActionGroup group, final List<AnAction> auxiliaryActions) {
327 DefaultActionGroup menuGroup = new DefaultActionGroup();
328 menuGroup.add(group);
329 for (AnAction action : auxiliaryActions) {
330 menuGroup.add(action);
332 menuGroup.add(ActionManager.getInstance().getAction(IdeActions.ACTION_COPY));
333 PopupHandler.installPopupHandler(myChangesTree, menuGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance());
336 public void removeFilteringStrategy(final String key) {
337 final ChangeListFilteringStrategy strategy = myFilteringStrategy.removeStrategy(key);
338 if (strategy != null) {
339 strategy.removeChangeListener(myFilterChangeListener);
341 myInnerSplitter.remove(key);
344 public boolean setFilteringStrategy(final String key, final ChangeListFilteringStrategy filteringStrategy) {
345 if (myInnerSplitter.canAdd()) {
346 filteringStrategy.setFilterBase(myChangeLists);
347 filteringStrategy.addChangeListener(myFilterChangeListener);
349 myFilteringStrategy.addStrategy(key, filteringStrategy);
351 final JComponent filterUI = filteringStrategy.getFilterUI();
352 if (filterUI != null) {
353 myInnerSplitter.add(key, filterUI);
355 return true;
357 return false;
360 public ActionToolbar createGroupFilterToolbar(final Project project, final ActionGroup leadGroup, @Nullable final ActionGroup tailGroup,
361 final List<AnAction> extra) {
362 DefaultActionGroup toolbarGroup = new DefaultActionGroup();
363 toolbarGroup.add(leadGroup);
364 toolbarGroup.addSeparator();
365 toolbarGroup.add(new SelectFilteringAction(project, this));
366 toolbarGroup.add(new SelectGroupingAction(this));
367 final ExpandAllAction expandAllAction = new ExpandAllAction(myChangesTree);
368 final CollapseAllAction collapseAllAction = new CollapseAllAction(myChangesTree);
369 expandAllAction.registerCustomShortcutSet(
370 new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_EXPAND_ALL)),
371 myChangesTree);
372 collapseAllAction.registerCustomShortcutSet(
373 new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_COLLAPSE_ALL)),
374 myChangesTree);
375 toolbarGroup.add(expandAllAction);
376 toolbarGroup.add(collapseAllAction);
377 toolbarGroup.add(ActionManager.getInstance().getAction(IdeActions.ACTION_COPY));
378 toolbarGroup.add(new ContextHelpAction(myHelpId));
379 if (tailGroup != null) {
380 toolbarGroup.add(tailGroup);
382 for (AnAction anAction : extra) {
383 toolbarGroup.add(anAction);
385 return ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarGroup, true);
388 public void calcData(DataKey key, DataSink sink) {
389 if (key.equals(VcsDataKeys.CHANGES)) {
390 final Collection<Change> changes = collectChanges(getSelectedChangeLists(), false);
391 sink.put(VcsDataKeys.CHANGES, changes.toArray(new Change[changes.size()]));
393 else if (key.equals(VcsDataKeys.CHANGES_WITH_MOVED_CHILDREN)) {
394 final Collection<Change> changes = collectChanges(getSelectedChangeLists(), true);
395 sink.put(VcsDataKeys.CHANGES_WITH_MOVED_CHILDREN, changes.toArray(new Change[changes.size()]));
397 else if (key.equals(VcsDataKeys.CHANGE_LISTS)) {
398 final List<CommittedChangeList> lists = getSelectedChangeLists();
399 if (lists.size() > 0) {
400 sink.put(VcsDataKeys.CHANGE_LISTS, lists.toArray(new CommittedChangeList[lists.size()]));
403 else if (key.equals(PlatformDataKeys.NAVIGATABLE_ARRAY)) {
404 final Collection<Change> changes = collectChanges(getSelectedChangeLists(), false);
405 Navigatable[] result = ChangesUtil.getNavigatableArray(myProject, ChangesUtil.getFilesFromChanges(changes));
406 sink.put(PlatformDataKeys.NAVIGATABLE_ARRAY, result);
408 else if (key.equals(PlatformDataKeys.HELP_ID)) {
409 sink.put(PlatformDataKeys.HELP_ID, myHelpId);
410 } else if (VcsDataKeys.SELECTED_CHANGES_IN_DETAILS.equals(key)) {
411 final List<Change> selectedChanges = myChangesView.getSelectedChanges();
412 sink.put(VcsDataKeys.SELECTED_CHANGES_IN_DETAILS, selectedChanges.toArray(new Change[selectedChanges.size()]));
416 public TreeExpander getTreeExpander() {
417 return myTreeExpander;
420 public void repaintTree() {
421 myChangesTree.revalidate();
422 myChangesTree.repaint();
425 public void install(final CommittedChangeListDecorator decorator) {
426 myDecorators.add(decorator);
427 repaintTree();
430 public void remove(final CommittedChangeListDecorator decorator) {
431 myDecorators.remove(decorator);
432 repaintTree();
435 public void reportLoadedLists(final CommittedChangeListsListener listener) {
436 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
437 public void run() {
438 listener.onBeforeStartReport();
439 for (CommittedChangeList list : myChangeLists) {
440 listener.report(list);
442 listener.onAfterEndReport();
444 });
447 // for appendable view
448 public void reset() {
449 myChangeLists.clear();
450 myFilteringStrategy.resetFilterBase();
452 myState = TreeState.createOn(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot());
453 updateModel();
456 public void append(final List<CommittedChangeList> list) {
457 final TreeState state = (myChangeLists.isEmpty() && myState != null) ? myState :
458 TreeState.createOn(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot());
459 state.setScrollToSelection(false);
460 myChangeLists.addAll(list);
462 myFilteringStrategy.appendFilterBase(list);
464 myChangesTree.setModel(buildTreeModel(myFilteringStrategy.filterChangeLists(myChangeLists)));
465 state.applyTo(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot());
466 TreeUtil.expandAll(myChangesTree);
467 myProject.getMessageBus().syncPublisher(ITEMS_RELOADED).itemsReloaded();
470 public static class CommittedChangeListRenderer extends ColoredTreeCellRenderer {
471 private final static DateFormat myDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
472 private static final SimpleTextAttributes LINK_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE, Color.blue);
473 private final IssueLinkRenderer myRenderer;
474 private final List<CommittedChangeListDecorator> myDecorators;
475 private final Project myProject;
477 public CommittedChangeListRenderer(final Project project, final List<CommittedChangeListDecorator> decorators) {
478 myProject = project;
479 myRenderer = new IssueLinkRenderer(project, this);
480 myDecorators = decorators;
483 public static String getDateOfChangeList(final Date date) {
484 return myDateFormat.format(date);
487 public static Pair<String, Boolean> getDescriptionOfChangeList(final String text) {
488 String description = text;
489 int pos = description.indexOf("\n");
490 if (pos >= 0) {
491 description = description.substring(0, pos).trim();
492 return new Pair<String, Boolean>(description, Boolean.TRUE);
494 return new Pair<String, Boolean>(description, Boolean.FALSE);
497 public static String truncateDescription(final String initDescription, final FontMetrics fontMetrics, int maxWidth) {
498 String description = initDescription;
499 int descWidth = fontMetrics.stringWidth(description);
500 while(description.length() > 0 && (descWidth > maxWidth)) {
501 description = trimLastWord(description);
502 descWidth = fontMetrics.stringWidth(description + " ");
504 return description;
507 public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
508 DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
509 if (node.getUserObject() instanceof CommittedChangeList) {
510 CommittedChangeList changeList = (CommittedChangeList) node.getUserObject();
512 final Container parent = tree.getParent();
513 int parentWidth = parent == null ? 100 : parent.getWidth() - 44;
514 String date = ", " + getDateOfChangeList(changeList.getCommitDate());
515 final FontMetrics fontMetrics = tree.getFontMetrics(tree.getFont());
516 final FontMetrics boldMetrics = tree.getFontMetrics(tree.getFont().deriveFont(Font.BOLD));
517 int size = fontMetrics.stringWidth(date);
518 size += boldMetrics.stringWidth(changeList.getCommitterName());
520 final Pair<String, Boolean> descriptionInfo = getDescriptionOfChangeList(changeList.getName().trim());
521 boolean truncated = descriptionInfo.getSecond().booleanValue();
522 String description = descriptionInfo.getFirst();
524 for (CommittedChangeListDecorator decorator : myDecorators) {
525 final Icon icon = decorator.decorate(changeList);
526 if (icon != null) {
527 setIcon(icon);
531 int descMaxWidth = parentWidth - size - 8;
532 boolean partial = (changeList instanceof ReceivedChangeList) && ((ReceivedChangeList)changeList).isPartial();
533 if (partial) {
534 final String partialMarker = VcsBundle.message("committed.changes.partial.list") + " ";
535 append(partialMarker, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
536 descMaxWidth -= boldMetrics.stringWidth(partialMarker);
539 int descWidth = fontMetrics.stringWidth(description);
541 int numberWidth = 0;
542 final AbstractVcs vcs = changeList.getVcs();
543 if (vcs != null) {
544 final CachingCommittedChangesProvider provider = vcs.getCachingCommittedChangesProvider();
545 if (provider != null && provider.getChangelistTitle() != null) {
546 String number = "#" + changeList.getNumber() + " ";
547 numberWidth = fontMetrics.stringWidth(number);
548 descWidth += numberWidth;
549 append(number, SimpleTextAttributes.GRAY_ATTRIBUTES);
553 if (description.length() == 0 && !truncated) {
554 append(VcsBundle.message("committed.changes.empty.comment"), SimpleTextAttributes.GRAYED_ATTRIBUTES);
555 appendAlign(parentWidth - size);
557 else if (descMaxWidth < 0) {
558 myRenderer.appendTextWithLinks(description);
560 else if (descWidth < descMaxWidth && !truncated) {
561 myRenderer.appendTextWithLinks(description);
562 appendAlign(parentWidth - size);
564 else {
565 final String moreMarker = VcsBundle.message("changes.browser.details.marker");
566 int moreWidth = fontMetrics.stringWidth(moreMarker);
567 description = truncateDescription(description, fontMetrics, (descMaxWidth - moreWidth - numberWidth));
568 myRenderer.appendTextWithLinks(description);
569 // we don't have place for changelist number in this case
570 append(" ", SimpleTextAttributes.REGULAR_ATTRIBUTES);
571 append(moreMarker, LINK_ATTRIBUTES, new MoreLauncher(myProject, changeList));
572 appendAlign(parentWidth - size);
575 append(changeList.getCommitterName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
576 append(date, SimpleTextAttributes.REGULAR_ATTRIBUTES);
578 else if (node.getUserObject() != null) {
579 append(node.getUserObject().toString(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
583 private void appendDescriptionAndNumber(final String description, final String number) {
584 myRenderer.appendTextWithLinks(description);
585 if (number != null) {
586 append(number, SimpleTextAttributes.GRAY_ATTRIBUTES);
590 private static String trimLastWord(final String description) {
591 int pos = description.trim().lastIndexOf(' ');
592 if (pos >= 0) {
593 return description.substring(0, pos).trim();
595 return description.substring(0, description.length()-1);
598 public Dimension getPreferredSize() {
599 return new Dimension(2000, super.getPreferredSize().height);
603 private static class MoreLauncher implements Runnable {
604 private final Project myProject;
605 private final CommittedChangeList myList;
607 private MoreLauncher(final Project project, final CommittedChangeList list) {
608 myProject = project;
609 myList = list;
612 public void run() {
613 ChangeListDetailsAction.showDetailsPopup(myProject, myList);
617 private class FilterChangeListener implements ChangeListener {
618 public void stateChanged(ChangeEvent e) {
619 if (ApplicationManager.getApplication().isDispatchThread()) {
620 updateModel();
621 } else {
622 ApplicationManager.getApplication().invokeLater(new Runnable() {
623 public void run() {
624 updateModel();
631 private class ChangesBrowserTree extends TreeWithEmptyText implements TypeSafeDataProvider {
632 public ChangesBrowserTree() {
633 super(buildTreeModel(myFilteringStrategy.filterChangeLists(myChangeLists)));
636 @Override
637 public boolean getScrollableTracksViewportWidth() {
638 return true;
641 public void calcData(final DataKey key, final DataSink sink) {
642 if (key.equals(PlatformDataKeys.COPY_PROVIDER)) {
643 sink.put(PlatformDataKeys.COPY_PROVIDER, myCopyProvider);
645 else if (key.equals(PlatformDataKeys.TREE_EXPANDER)) {
646 sink.put(PlatformDataKeys.TREE_EXPANDER, myTreeExpander);
651 private static class WiseSplitter {
652 private final Runnable myRefresher;
653 private final Splitter myParentSplitter;
654 private final ThreeComponentsSplitter myInnerSplitter;
655 private final Map<String, Integer> myInnerSplitterContents;
657 private WiseSplitter(final Runnable refresher, final Splitter parentSplitter) {
658 myRefresher = refresher;
659 myParentSplitter = parentSplitter;
661 myInnerSplitter = new ThreeComponentsSplitter(false);
662 myInnerSplitter.setHonorComponentsMinimumSize(true);
663 myInnerSplitterContents = new HashMap<String, Integer>();
666 public boolean canAdd() {
667 return myInnerSplitterContents.size() <= 3;
670 public void add(final String key, final JComponent comp) {
671 final int idx = myInnerSplitterContents.size();
672 myInnerSplitterContents.put(key, idx);
673 if (idx == 0) {
674 myParentSplitter.setFirstComponent(myInnerSplitter);
675 if (myParentSplitter.getProportion() < 0.05f) {
676 myParentSplitter.setProportion(0.25f);
678 myInnerSplitter.setFirstComponent(comp);
679 myInnerSplitter.setFirstSize((int) (myParentSplitter.getSize().getWidth() * myParentSplitter.getProportion()));
680 } else if (idx == 1) {
681 final Dimension dimension = myInnerSplitter.getSize();
682 final double width = dimension.getWidth() / 2;
683 myInnerSplitter.setInnerComponent(comp);
684 myInnerSplitter.setFirstSize((int) width);
685 } else {
686 final Dimension dimension = myInnerSplitter.getSize();
687 final double width = dimension.getWidth() / 3;
688 myInnerSplitter.setLastComponent(comp);
689 myInnerSplitter.setFirstSize((int) width);
690 myInnerSplitter.setLastSize((int) width);
693 myRefresher.run();
696 public void remove(final String key) {
697 final Integer idx = myInnerSplitterContents.remove(key);
698 if (idx == null) {
699 return;
701 final Map<String, Integer> tmp = new HashMap<String, Integer>();
702 for (Map.Entry<String, Integer> entry : myInnerSplitterContents.entrySet()) {
703 if (entry.getValue() < idx) {
704 tmp.put(entry.getKey(), entry.getValue());
705 } else {
706 tmp.put(entry.getKey(), entry.getValue() - 1);
709 myInnerSplitterContents.clear();
710 myInnerSplitterContents.putAll(tmp);
712 if (idx == 0) {
713 final JComponent inner = myInnerSplitter.getInnerComponent();
714 myInnerSplitter.setInnerComponent(null);
715 myInnerSplitter.setFirstComponent(inner);
716 lastToInner();
717 } else if (idx == 1) {
718 lastToInner();
719 } else {
720 myInnerSplitter.setLastComponent(null);
722 myRefresher.run();
725 private void lastToInner() {
726 final JComponent last = myInnerSplitter.getLastComponent();
727 myInnerSplitter.setLastComponent(null);
728 myInnerSplitter.setInnerComponent(last);
732 public interface CommittedChangesReloadListener {
733 void itemsReloaded();
734 void emptyRefresh();
737 public void setLoading(final boolean value) {
738 new AbstractCalledLater(myProject, ModalityState.NON_MODAL) {
739 public void run() {
740 myLoadingPanel.setVisible(value);
741 myToolbarPanel.revalidate();
742 myToolbarPanel.repaint();
744 }.callMe();