Implement icon and text decorations of various resource states
[egit/torarne.git] / org.spearce.egit.ui / src / org / spearce / egit / ui / internal / decorators / GitLightweightDecorator.java
blobc23ce241e36f1a5db4e9468a7be6a047b12f5020
1 /*******************************************************************************
2 * Copyright (C) 2007, IBM Corporation and others
3 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
4 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
5 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Copyright (C) 2008, Google Inc.
7 * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com>
9 * All rights reserved. This program and the accompanying materials
10 * are made available under the terms of the Eclipse Public License v1.0
11 * See LICENSE for the full license text, also available.
12 *******************************************************************************/
14 package org.spearce.egit.ui.internal.decorators;
16 import java.io.IOException;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IResource;
27 import org.eclipse.core.resources.IResourceChangeEvent;
28 import org.eclipse.core.resources.IResourceChangeListener;
29 import org.eclipse.core.resources.IResourceDelta;
30 import org.eclipse.core.resources.IResourceDeltaVisitor;
31 import org.eclipse.core.resources.IResourceVisitor;
32 import org.eclipse.core.resources.ResourcesPlugin;
33 import org.eclipse.core.resources.mapping.ResourceMapping;
34 import org.eclipse.core.runtime.CoreException;
35 import org.eclipse.core.runtime.IAdaptable;
36 import org.eclipse.core.runtime.IStatus;
37 import org.eclipse.jface.preference.IPreferenceStore;
38 import org.eclipse.jface.resource.ImageDescriptor;
39 import org.eclipse.jface.util.IPropertyChangeListener;
40 import org.eclipse.jface.util.PropertyChangeEvent;
41 import org.eclipse.jface.viewers.IDecoration;
42 import org.eclipse.jface.viewers.ILightweightLabelDecorator;
43 import org.eclipse.jface.viewers.LabelProvider;
44 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
45 import org.eclipse.osgi.util.TextProcessor;
46 import org.eclipse.swt.graphics.ImageData;
47 import org.eclipse.swt.widgets.Display;
48 import org.eclipse.team.core.Team;
49 import org.eclipse.team.ui.ISharedImages;
50 import org.eclipse.team.ui.TeamImages;
51 import org.eclipse.team.ui.TeamUI;
52 import org.eclipse.ui.IContributorResourceAdapter;
53 import org.eclipse.ui.PlatformUI;
54 import org.spearce.egit.core.GitException;
55 import org.spearce.egit.core.internal.util.ExceptionCollector;
56 import org.spearce.egit.core.project.GitProjectData;
57 import org.spearce.egit.core.project.RepositoryChangeListener;
58 import org.spearce.egit.core.project.RepositoryMapping;
59 import org.spearce.egit.ui.Activator;
60 import org.spearce.egit.ui.UIIcons;
61 import org.spearce.egit.ui.UIPreferences;
62 import org.spearce.egit.ui.UIText;
63 import org.spearce.egit.ui.internal.decorators.IDecoratableResource.Staged;
64 import org.spearce.jgit.dircache.DirCache;
65 import org.spearce.jgit.dircache.DirCacheEntry;
66 import org.spearce.jgit.dircache.DirCacheIterator;
67 import org.spearce.jgit.lib.Constants;
68 import org.spearce.jgit.lib.FileMode;
69 import org.spearce.jgit.lib.IndexChangedEvent;
70 import org.spearce.jgit.lib.ObjectId;
71 import org.spearce.jgit.lib.RefsChangedEvent;
72 import org.spearce.jgit.lib.Repository;
73 import org.spearce.jgit.lib.RepositoryChangedEvent;
74 import org.spearce.jgit.lib.RepositoryListener;
75 import org.spearce.jgit.revwalk.RevWalk;
76 import org.spearce.jgit.treewalk.EmptyTreeIterator;
77 import org.spearce.jgit.treewalk.TreeWalk;
78 import org.spearce.jgit.treewalk.filter.PathFilterGroup;
80 /**
81 * Supplies annotations for displayed resources
83 * This decorator provides annotations to indicate the status of each resource
84 * when compared to <code>HEAD</code>, as well as the index in the relevant
85 * repository.
87 * TODO: Add support for colors and font decoration
89 public class GitLightweightDecorator extends LabelProvider implements
90 ILightweightLabelDecorator, IPropertyChangeListener,
91 IResourceChangeListener, RepositoryChangeListener, RepositoryListener {
93 /**
94 * Property constant pointing back to the extension point id of the
95 * decorator
97 public static final String DECORATOR_ID = "org.spearce.egit.ui.internal.decorators.GitLightweightDecorator"; //$NON-NLS-1$
99 /**
100 * Bit-mask describing interesting changes for IResourceChangeListener
101 * events
103 private static int INTERESTING_CHANGES = IResourceDelta.CONTENT
104 | IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO
105 | IResourceDelta.OPEN | IResourceDelta.REPLACED
106 | IResourceDelta.TYPE;
109 * Collector for keeping the error view from filling up with exceptions
111 private static ExceptionCollector exceptions = new ExceptionCollector(
112 UIText.Decorator_exceptionMessage, Activator.getPluginId(),
113 IStatus.ERROR, Activator.getDefault().getLog());
116 * Constructs a new Git resource decorator
118 public GitLightweightDecorator() {
119 TeamUI.addPropertyChangeListener(this);
120 Activator.addPropertyChangeListener(this);
121 PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
122 .addPropertyChangeListener(this);
123 Repository.addAnyRepositoryChangedListener(this);
124 GitProjectData.addRepositoryChangeListener(this);
125 ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
126 IResourceChangeEvent.POST_CHANGE);
130 * (non-Javadoc)
132 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
134 @Override
135 public void dispose() {
136 super.dispose();
137 PlatformUI.getWorkbench().getThemeManager().getCurrentTheme()
138 .removePropertyChangeListener(this);
139 TeamUI.removePropertyChangeListener(this);
140 Activator.removePropertyChangeListener(this);
141 Repository.removeAnyRepositoryChangedListener(this);
142 GitProjectData.removeRepositoryChangeListener(this);
143 ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
147 * This method should only be called by the decorator thread.
149 * @see org.eclipse.jface.viewers.ILightweightLabelDecorator#decorate(java.lang.Object,
150 * org.eclipse.jface.viewers.IDecoration)
152 public void decorate(Object element, IDecoration decoration) {
153 final IResource resource = getResource(element);
154 if (resource == null)
155 return;
157 // Don't decorate the workspace root
158 if (resource.getType() == IResource.ROOT)
159 return;
161 // Don't decorate non-existing resources
162 if (!resource.exists() && !resource.isPhantom())
163 return;
165 // Make sure we're dealing with a project under Git revision control
166 final RepositoryMapping mapping = RepositoryMapping
167 .getMapping(resource);
168 if (mapping == null)
169 return;
171 // Cannot decorate linked resources
172 if (mapping.getRepoRelativePath(resource) == null)
173 return;
175 // Don't decorate if UI plugin is not running
176 Activator activator = Activator.getDefault();
177 if (activator == null)
178 return;
180 try {
181 DecorationHelper helper = new DecorationHelper(activator
182 .getPreferenceStore());
183 helper.decorate(decoration,
184 new DecoratableResourceAdapter(resource));
185 } catch (IOException e) {
186 handleException(resource, GitException.wrapException(e));
190 private class DecoratableResourceAdapter implements IDecoratableResource {
192 private final IResource resource;
194 private final RepositoryMapping mapping;
196 private final Repository repository;
198 private final ObjectId headId;
200 private String branch = "";
202 private boolean tracked = false;
204 private boolean ignored = false;
206 private boolean dirty = false;
208 private boolean conflicts = false;
210 private boolean assumeValid = false;
212 private Staged staged = Staged.NOT_STAGED;
214 static final int T_HEAD = 0;
216 static final int T_INDEX = 1;
218 static final int T_WORKSPACE = 2;
220 public DecoratableResourceAdapter(IResource resourceToWrap)
221 throws IOException {
222 resource = resourceToWrap;
223 mapping = RepositoryMapping.getMapping(resource);
224 repository = mapping.getRepository();
225 headId = repository.resolve(Constants.HEAD);
227 switch (resource.getType()) {
228 case IResource.FILE:
229 extractFileProperties();
230 break;
231 case IResource.FOLDER:
232 extractContainerProperties();
233 break;
234 case IResource.PROJECT:
235 extractProjectProperties();
236 break;
240 private void extractFileProperties() throws IOException {
241 TreeWalk treeWalk = createHeadVsIndexTreeWalk();
242 if (treeWalk == null)
243 return;
245 if (treeWalk.next())
246 tracked = true;
247 else
248 return;
250 // TODO: Also read ignores from .git/info/excludes et al.
251 if (Team.isIgnoredHint(resource)) {
252 ignored = true;
253 return;
256 final DirCacheIterator indexIterator = treeWalk.getTree(T_INDEX,
257 DirCacheIterator.class);
258 final DirCacheEntry indexEntry = indexIterator != null ? indexIterator
259 .getDirCacheEntry()
260 : null;
262 if (indexEntry == null) {
263 staged = Staged.REMOVED;
264 } else {
265 if (indexEntry.isAssumeValid()) {
266 dirty = false;
267 assumeValid = true;
268 } else if (indexEntry.getStage() > 0) {
269 conflicts = true;
270 } else if (treeWalk.getRawMode(T_HEAD) == FileMode.MISSING
271 .getBits()) {
272 staged = Staged.ADDED;
273 } else {
274 long indexEntryLastModified = indexEntry.getLastModified();
275 long resourceLastModified = resource.getLocalTimeStamp();
277 // C-Git under Windows stores timestamps with 1-seconds
278 // resolution, so we need to check to see if this is the
279 // case here, and possibly fix the timestamp of the resource
280 // to match the resolution of the index.
281 if (indexEntryLastModified % 1000 == 0) {
282 resourceLastModified -= resourceLastModified % 1000;
285 if (resourceLastModified != indexEntryLastModified) {
286 // TODO: Consider doing a content check here, to rule
287 // out false positives, as we might get mismatch between
288 // timestamps, even if the content is the same
289 dirty = true;
292 if (treeWalk.getRawMode(T_HEAD) != treeWalk
293 .getRawMode(T_INDEX)
294 || !treeWalk.idEqual(T_HEAD, T_INDEX)) {
295 staged = Staged.MODIFIED;
302 private void extractContainerProperties() throws IOException {
303 TreeWalk treeWalk = createHeadVsIndexTreeWalk();
304 if (treeWalk == null)
305 return;
307 if (treeWalk.next())
308 tracked = true;
309 else
310 return;
312 // TODO: Also read ignores from .git/info/excludes et al.
313 if (Team.isIgnoredHint(resource)) {
314 ignored = true;
315 return;
318 // TODO: Compute dirty state for folder, using ContainerTreeIterator
319 // and ContainerDiffFilter
323 private void extractProjectProperties() throws IOException {
324 branch = repository.getBranch();
325 tracked = true;
327 // TODO: Compute dirty state for folder, using ContainerTreeIterator
328 // and ContainerDiffFilter
333 * Adds a filter to the specified tree walk limiting the results to only
334 * those matching the resource specified by
335 * <code>resourceToFilterBy</code>
336 * <p>
337 * If the resource does not exists in the current repository, or it has
338 * an empty path (it is the project itself), the filter is not added,
339 * and the method returns <code>null</code>.
341 * @param treeWalk
342 * the tree walk to add the filter to
343 * @param resourceToFilterBy
344 * the resource to filter by
346 * @return <code>true</code> if the filter could be added,
347 * <code>false</code> otherwise
349 private boolean addResourceFilter(final TreeWalk treeWalk,
350 final IResource resourceToFilterBy) {
351 Set<String> repositoryPaths = Collections.singleton(mapping
352 .getRepoRelativePath(resourceToFilterBy));
353 if (repositoryPaths.isEmpty() || repositoryPaths.contains(""))
354 return false;
356 treeWalk.setFilter(PathFilterGroup
357 .createFromStrings(repositoryPaths));
358 return true;
362 * Helper method to create a new tree walk between HEAD and the index.
364 * @return the created tree walk, or null if it could not be created
365 * @throws IOException
366 * if there were errors when creating the tree walk
368 private TreeWalk createHeadVsIndexTreeWalk() throws IOException {
369 final TreeWalk treeWalk = new TreeWalk(repository);
370 if (!addResourceFilter(treeWalk, resource))
371 return null;
373 treeWalk.setRecursive(treeWalk.getFilter().shouldBeRecursive());
374 treeWalk.reset();
376 if (headId != null)
377 treeWalk.addTree(new RevWalk(repository).parseTree(headId));
378 else
379 treeWalk.addTree(new EmptyTreeIterator());
381 treeWalk.addTree(new DirCacheIterator(DirCache.read(repository)));
382 return treeWalk;
385 public String getName() {
386 return resource.getName();
389 public int getType() {
390 return resource.getType();
393 public String getBranch() {
394 return branch;
397 public boolean isTracked() {
398 return tracked;
401 public boolean isIgnored() {
402 return ignored;
405 public boolean isDirty() {
406 return dirty;
409 public Staged staged() {
410 return staged;
413 public boolean hasConflicts() {
414 return conflicts;
417 public boolean isAssumeValid() {
418 return assumeValid;
423 * Helper class for doing resource decoration, based on the given
424 * preferences
426 * Used for real-time decoration, as well as in the decorator preview
427 * preferences page
429 public static class DecorationHelper {
431 /** */
432 public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$
434 /** */
435 public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$
437 /** */
438 public static final String BINDING_DIRTY_FLAG = "dirty"; //$NON-NLS-1$
440 /** */
441 public static final String BINDING_STAGED_FLAG = "staged"; //$NON-NLS-1$
443 private IPreferenceStore store;
446 * Define a cached image descriptor which only creates the image data
447 * once
449 private static class CachedImageDescriptor extends ImageDescriptor {
450 ImageDescriptor descriptor;
452 ImageData data;
454 public CachedImageDescriptor(ImageDescriptor descriptor) {
455 this.descriptor = descriptor;
458 public ImageData getImageData() {
459 if (data == null) {
460 data = descriptor.getImageData();
462 return data;
466 private static ImageDescriptor trackedImage;
468 private static ImageDescriptor untrackedImage;
470 private static ImageDescriptor stagedImage;
472 private static ImageDescriptor stagedAddedImage;
474 private static ImageDescriptor stagedRemovedImage;
476 private static ImageDescriptor conflictImage;
478 private static ImageDescriptor assumeValidImage;
480 static {
481 trackedImage = new CachedImageDescriptor(TeamImages
482 .getImageDescriptor(ISharedImages.IMG_CHECKEDIN_OVR));
483 untrackedImage = new CachedImageDescriptor(UIIcons.OVR_UNTRACKED);
484 stagedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED);
485 stagedAddedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED_ADD);
486 stagedRemovedImage = new CachedImageDescriptor(
487 UIIcons.OVR_STAGED_REMOVE);
488 conflictImage = new CachedImageDescriptor(UIIcons.OVR_CONFLICT);
489 assumeValidImage = new CachedImageDescriptor(UIIcons.OVR_ASSUMEVALID);
493 * Constructs a decorator using the rules from the given
494 * <code>preferencesStore</code>
496 * @param preferencesStore
497 * the preferences store with the preferred decorator rules
499 public DecorationHelper(IPreferenceStore preferencesStore) {
500 store = preferencesStore;
504 * Decorates the given <code>decoration</code> based on the state of the
505 * given <code>resource</code>, using the preferences passed when
506 * constructing this decoration helper.
508 * @param decoration
509 * the decoration to decorate
510 * @param resource
511 * the resource to retrieve state from
513 public void decorate(IDecoration decoration,
514 IDecoratableResource resource) {
515 if (resource.isIgnored())
516 return;
518 decorateText(decoration, resource);
519 decorateIcons(decoration, resource);
522 private void decorateText(IDecoration decoration,
523 IDecoratableResource resource) {
524 String format = "";
525 switch (resource.getType()) {
526 case IResource.FILE:
527 format = store
528 .getString(UIPreferences.DECORATOR_FILETEXT_DECORATION);
529 break;
530 case IResource.FOLDER:
531 format = store
532 .getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION);
533 break;
534 case IResource.PROJECT:
535 format = store
536 .getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION);
537 break;
540 Map<String, String> bindings = new HashMap<String, String>();
541 bindings.put(BINDING_RESOURCE_NAME, resource.getName());
542 bindings.put(BINDING_BRANCH_NAME, resource.getBranch());
543 bindings.put(BINDING_DIRTY_FLAG, resource.isDirty() ? ">" : null);
544 bindings.put(BINDING_STAGED_FLAG,
545 resource.staged() != Staged.NOT_STAGED ? "*" : null);
547 decorate(decoration, format, bindings);
550 private void decorateIcons(IDecoration decoration,
551 IDecoratableResource resource) {
552 ImageDescriptor overlay = null;
554 if (resource.isTracked()) {
555 if (store.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON))
556 overlay = trackedImage;
558 if (store
559 .getBoolean(UIPreferences.DECORATOR_SHOW_ASSUME_VALID_ICON)
560 && resource.isAssumeValid())
561 overlay = assumeValidImage;
563 // Staged overrides tracked
564 Staged staged = resource.staged();
565 if (store.getBoolean(UIPreferences.DECORATOR_SHOW_STAGED_ICON)
566 && staged != Staged.NOT_STAGED) {
567 if (staged == Staged.ADDED)
568 overlay = stagedAddedImage;
569 else if (staged == Staged.REMOVED)
570 overlay = stagedRemovedImage;
571 else
572 overlay = stagedImage;
575 // Conflicts override everything
576 if (store.getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON)
577 && resource.hasConflicts())
578 overlay = conflictImage;
580 } else if (store.getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) {
581 overlay = untrackedImage;
584 // Overlays can only be added once, so do it at the end
585 decoration.addOverlay(overlay);
589 * Decorates the given <code>decoration</code>, using the specified text
590 * <code>format</code>, and mapped using the variable bindings from
591 * <code>bindings</code>
593 * @param decoration
594 * the decoration to decorate
595 * @param format
596 * the format to base the decoration on
597 * @param bindings
598 * the bindings between variables in the format and actual
599 * values
601 public static void decorate(IDecoration decoration, String format,
602 Map<String, String> bindings) {
603 StringBuffer prefix = new StringBuffer();
604 StringBuffer suffix = new StringBuffer();
605 StringBuffer output = prefix;
607 int length = format.length();
608 int start = -1;
609 int end = length;
610 while (true) {
611 if ((end = format.indexOf('{', start)) > -1) {
612 output.append(format.substring(start + 1, end));
613 if ((start = format.indexOf('}', end)) > -1) {
614 String key = format.substring(end + 1, start);
615 String s;
617 // Allow users to override the binding
618 if (key.indexOf(':') > -1) {
619 String[] keyAndBinding = key.split(":", 2);
620 key = keyAndBinding[0];
621 if (keyAndBinding.length > 1
622 && bindings.get(key) != null)
623 bindings.put(key, keyAndBinding[1]);
626 // We use the BINDING_RESOURCE_NAME key to determine if
627 // we are doing the prefix or suffix. The name isn't
628 // actually part of either.
629 if (key.equals(BINDING_RESOURCE_NAME)) {
630 output = suffix;
631 s = null;
632 } else {
633 s = bindings.get(key);
636 if (s != null) {
637 output.append(s);
638 } else {
639 // Support removing prefix character if binding is
640 // null
641 int curLength = output.length();
642 if (curLength > 0) {
643 char c = output.charAt(curLength - 1);
644 if (c == ':' || c == '@') {
645 output.deleteCharAt(curLength - 1);
649 } else {
650 output.append(format.substring(end, length));
651 break;
653 } else {
654 output.append(format.substring(start + 1, length));
655 break;
659 String prefixString = prefix.toString().replaceAll("^\\s+", "");
660 if (prefixString != null) {
661 decoration.addPrefix(TextProcessor.process(prefixString,
662 "()[].")); //$NON-NLS-1$
664 String suffixString = suffix.toString().replaceAll("\\s+$", "");
665 if (suffixString != null) {
666 decoration.addSuffix(TextProcessor.process(suffixString,
667 "()[].")); //$NON-NLS-1$
672 // -------- Refresh handling --------
675 * Perform a blanket refresh of all decorations
677 public static void refresh() {
678 Display.getDefault().asyncExec(new Runnable() {
679 public void run() {
680 Activator.getDefault().getWorkbench().getDecoratorManager()
681 .update(DECORATOR_ID);
687 * Callback for IPropertyChangeListener events
689 * If any of the relevant preferences has been changed we refresh all
690 * decorations (all projects and their resources).
692 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
694 public void propertyChange(PropertyChangeEvent event) {
695 final String prop = event.getProperty();
696 // If the property is of any interest to us
697 if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED)
698 || prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED)
699 || prop.equals(Activator.DECORATORS_CHANGED)) {
700 postLabelEvent(new LabelProviderChangedEvent(this, null /* all */));
705 * Callback for IResourceChangeListener events
707 * Schedules a refresh of the changed resource
709 * If the preference for computing deep dirty states has been set we walk
710 * the ancestor tree of the changed resource and update all parents as well.
712 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
714 public void resourceChanged(IResourceChangeEvent event) {
715 final Set<IResource> resourcesToUpdate = new HashSet<IResource>();
717 try { // Compute the changed resources by looking at the delta
718 event.getDelta().accept(new IResourceDeltaVisitor() {
719 public boolean visit(IResourceDelta delta) throws CoreException {
720 final IResource resource = delta.getResource();
722 // If the resource is not part of a project under Git
723 // revision control
724 final RepositoryMapping mapping = RepositoryMapping
725 .getMapping(resource);
726 if (mapping == null) {
727 // Ignore the change
728 return true;
730 if (resource.getType() == IResource.ROOT) {
731 // Continue with the delta
732 return true;
735 if (resource.getType() == IResource.PROJECT) {
736 // If the project is not accessible, don't process it
737 if (!resource.isAccessible())
738 return false;
741 // If the file has changed but not in a way that we care
742 // about
743 // (e.g. marker changes to files) then ignore the change
744 if (delta.getKind() == IResourceDelta.CHANGED
745 && (delta.getFlags() & INTERESTING_CHANGES) == 0) {
746 return true;
749 // All seems good, schedule the resource for update
750 resourcesToUpdate.add(resource);
751 return true;
753 }, true /* includePhantoms */);
754 } catch (final CoreException e) {
755 handleException(null, e);
758 // If deep decorator calculation is enabled in the preferences we
759 // walk the ancestor tree of each of the changed resources and add
760 // their parents to the update set
761 final IPreferenceStore store = Activator.getDefault()
762 .getPreferenceStore();
763 if (store.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY)) {
764 final IResource[] changedResources = resourcesToUpdate
765 .toArray(new IResource[resourcesToUpdate.size()]);
766 for (int i = 0; i < changedResources.length; i++) {
767 IResource current = changedResources[i];
768 while (current.getType() != IResource.ROOT) {
769 current = current.getParent();
770 resourcesToUpdate.add(current);
775 postLabelEvent(new LabelProviderChangedEvent(this, resourcesToUpdate
776 .toArray()));
780 * Callback for RepositoryListener events
782 * We resolve the repository mapping for the changed repository and forward
783 * that to repositoryChanged(RepositoryMapping).
785 * @param e
786 * The original change event
788 private void repositoryChanged(RepositoryChangedEvent e) {
789 final Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>();
790 for (final IProject p : ResourcesPlugin.getWorkspace().getRoot()
791 .getProjects()) {
792 final RepositoryMapping mapping = RepositoryMapping.getMapping(p);
793 if (mapping != null && mapping.getRepository() == e.getRepository())
794 ms.add(mapping);
796 for (final RepositoryMapping m : ms) {
797 repositoryChanged(m);
802 * (non-Javadoc)
804 * @see
805 * org.spearce.jgit.lib.RepositoryListener#indexChanged(org.spearce.jgit
806 * .lib.IndexChangedEvent)
808 public void indexChanged(IndexChangedEvent e) {
809 repositoryChanged(e);
813 * (non-Javadoc)
815 * @see
816 * org.spearce.jgit.lib.RepositoryListener#refsChanged(org.spearce.jgit.
817 * lib.RefsChangedEvent)
819 public void refsChanged(RefsChangedEvent e) {
820 repositoryChanged(e);
824 * Callback for RepositoryChangeListener events, as well as
825 * RepositoryListener events via repositoryChanged()
827 * We resolve the project and schedule a refresh of each resource in the
828 * project.
830 * @see org.spearce.egit.core.project.RepositoryChangeListener#repositoryChanged(org.spearce.egit.core.project.RepositoryMapping)
832 public void repositoryChanged(RepositoryMapping mapping) {
833 final IProject project = mapping.getContainer().getProject();
834 if (project == null)
835 return;
837 final List<IResource> resources = new ArrayList<IResource>();
838 try {
839 project.accept(new IResourceVisitor() {
840 public boolean visit(IResource resource) {
841 resources.add(resource);
842 return true;
845 postLabelEvent(new LabelProviderChangedEvent(this, resources
846 .toArray()));
847 } catch (final CoreException e) {
848 handleException(project, e);
852 // -------- Helper methods --------
854 private static IResource getResource(Object element) {
855 if (element instanceof ResourceMapping) {
856 element = ((ResourceMapping) element).getModelObject();
859 IResource resource = null;
860 if (element instanceof IResource) {
861 resource = (IResource) element;
862 } else if (element instanceof IAdaptable) {
863 final IAdaptable adaptable = (IAdaptable) element;
864 resource = (IResource) adaptable.getAdapter(IResource.class);
865 if (resource == null) {
866 final IContributorResourceAdapter adapter = (IContributorResourceAdapter) adaptable
867 .getAdapter(IContributorResourceAdapter.class);
868 if (adapter != null)
869 resource = adapter.getAdaptedResource(adaptable);
873 return resource;
877 * Post the label event to the UI thread
879 * @param event
880 * The event to post
882 private void postLabelEvent(final LabelProviderChangedEvent event) {
883 Display.getDefault().asyncExec(new Runnable() {
884 public void run() {
885 fireLabelProviderChanged(event);
891 * Handle exceptions that occur in the decorator. Exceptions are only logged
892 * for resources that are accessible (i.e. exist in an open project).
894 * @param resource
895 * The resource that triggered the exception
896 * @param e
897 * The exception that occurred
899 private static void handleException(IResource resource, CoreException e) {
900 if (resource == null || resource.isAccessible())
901 exceptions.handleException(e);