2 * Copyright (c) 2007, Your Corporation. All Rights Reserved.
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
;
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
;
60 import java
.awt
.event
.ActionListener
;
61 import java
.text
.DateFormat
;
63 import java
.util
.List
;
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());
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() {
151 myFilterSplitter
.doLayout();
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
)),
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() {
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() {
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
));
219 public void setHelpId(final String 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
;
254 myFilteringStrategy
.setFilterBase(items
);
256 myProject
.getMessageBus().syncPublisher(ITEMS_RELOADED
).itemsReloaded();
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
;
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
);
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()));
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
);
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
)),
372 collapseAllAction
.registerCustomShortcutSet(
373 new CustomShortcutSet(KeymapManager
.getInstance().getActiveKeymap().getShortcuts(IdeActions
.ACTION_COLLAPSE_ALL
)),
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
);
430 public void remove(final CommittedChangeListDecorator decorator
) {
431 myDecorators
.remove(decorator
);
435 public void reportLoadedLists(final CommittedChangeListsListener listener
) {
436 ApplicationManager
.getApplication().executeOnPooledThread(new Runnable() {
438 listener
.onBeforeStartReport();
439 for (CommittedChangeList list
: myChangeLists
) {
440 listener
.report(list
);
442 listener
.onAfterEndReport();
447 // for appendable view
448 public void reset() {
449 myChangeLists
.clear();
450 myFilteringStrategy
.resetFilterBase();
452 myState
= TreeState
.createOn(myChangesTree
, (DefaultMutableTreeNode
)myChangesTree
.getModel().getRoot());
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
) {
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");
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
+ " ");
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
);
531 int descMaxWidth
= parentWidth
- size
- 8;
532 boolean partial
= (changeList
instanceof ReceivedChangeList
) && ((ReceivedChangeList
)changeList
).isPartial();
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
);
542 final AbstractVcs vcs
= changeList
.getVcs();
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
);
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(' ');
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
) {
613 ChangeListDetailsAction
.showDetailsPopup(myProject
, myList
);
617 private class FilterChangeListener
implements ChangeListener
{
618 public void stateChanged(ChangeEvent e
) {
619 if (ApplicationManager
.getApplication().isDispatchThread()) {
622 ApplicationManager
.getApplication().invokeLater(new Runnable() {
631 private class ChangesBrowserTree
extends TreeWithEmptyText
implements TypeSafeDataProvider
{
632 public ChangesBrowserTree() {
633 super(buildTreeModel(myFilteringStrategy
.filterChangeLists(myChangeLists
)));
637 public boolean getScrollableTracksViewportWidth() {
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
);
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
);
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
);
696 public void remove(final String key
) {
697 final Integer idx
= myInnerSplitterContents
.remove(key
);
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());
706 tmp
.put(entry
.getKey(), entry
.getValue() - 1);
709 myInnerSplitterContents
.clear();
710 myInnerSplitterContents
.putAll(tmp
);
713 final JComponent inner
= myInnerSplitter
.getInnerComponent();
714 myInnerSplitter
.setInnerComponent(null);
715 myInnerSplitter
.setFirstComponent(inner
);
717 } else if (idx
== 1) {
720 myInnerSplitter
.setLastComponent(null);
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();
737 public void setLoading(final boolean value
) {
738 new AbstractCalledLater(myProject
, ModalityState
.NON_MODAL
) {
740 myLoadingPanel
.setVisible(value
);
741 myToolbarPanel
.revalidate();
742 myToolbarPanel
.repaint();