Currently the only binding available is the resource name, but this commit enables a framework for adding more bindings. Signed-off-by: Tor Arne Vestbø <torarnv@xxxxxxxxx> --- org.spearce.egit.ui/plugin.properties | 1 + org.spearce.egit.ui/plugin.xml | 12 +- .../src/org/spearce/egit/ui/Activator.java | 16 + .../egit/ui/PluginPreferenceInitializer.java | 8 + .../src/org/spearce/egit/ui/UIPreferences.java | 9 + .../src/org/spearce/egit/ui/UIText.java | 63 ++- .../egit/ui/internal/actions/BranchAction.java | 4 +- .../egit/ui/internal/actions/Disconnect.java | 4 +- .../egit/ui/internal/actions/ResetAction.java | 4 +- .../decorators/GitLightweightDecorator.java | 538 ++++++++++++++ .../internal/decorators/GitResourceDecorator.java | 454 ------------ .../internal/decorators/IDecoratableResource.java | 31 + .../preferences/GitDecoratorPreferencePage.java | 735 ++++++++++++++++++++ .../src/org/spearce/egit/ui/uitext.properties | 25 +- 14 files changed, 1438 insertions(+), 466 deletions(-) create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java delete mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java create mode 100644 org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java diff --git a/org.spearce.egit.ui/plugin.properties b/org.spearce.egit.ui/plugin.properties index fa043f1..58b879f 100644 --- a/org.spearce.egit.ui/plugin.properties +++ b/org.spearce.egit.ui/plugin.properties @@ -64,3 +64,4 @@ Theme_CommitMessageFont_description=This font is used to show a commit message. GitPreferences_name=Git GitPreferences_HistoryPreferencePage_name=History GitPreferences_WindowCachePreferencePage_name=Window Cache +GitPreferences_DecoratorPreferencePage_name=Label Decorations diff --git a/org.spearce.egit.ui/plugin.xml b/org.spearce.egit.ui/plugin.xml index 869108c..2f23559 100644 --- a/org.spearce.egit.ui/plugin.xml +++ b/org.spearce.egit.ui/plugin.xml @@ -200,6 +200,14 @@ id="org.spearce.egit.ui.keyword.git"> </keywordReference> </page> + <page name="%GitPreferences_DecoratorPreferencePage_name" + category="org.spearce.egit.ui.GitPreferences" + class="org.spearce.egit.ui.internal.preferences.GitDecoratorPreferencePage" + id="org.spearce.egit.ui.internal.preferences.GitDecoratorPreferencePage" > + <keywordReference + id="org.spearce.egit.ui.keyword.git"> + </keywordReference> + </page> </extension> <extension point="org.eclipse.ui.propertyPages"> @@ -233,10 +241,10 @@ lightweight="true" adaptable="true" label="%Decorator_name" - class="org.spearce.egit.ui.internal.decorators.GitResourceDecorator" + class="org.spearce.egit.ui.internal.decorators.GitLightweightDecorator" state="true" location="BOTTOM_RIGHT" - id="org.spearce.egit.ui.internal.decorators.GitResourceDecorator"> + id="org.spearce.egit.ui.internal.decorators.GitLightweightDecorator"> <enablement> <objectClass name="org.eclipse.core.resources.IResource"/> </enablement> diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java index d4a9e8e..9d03c70 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/Activator.java @@ -33,6 +33,7 @@ import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jsch.core.IJSchService; import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.widgets.Display; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.ui.themes.ITheme; import org.osgi.framework.BundleContext; @@ -80,6 +81,21 @@ public static String getPluginId() { } /** + * Returns the standard display to be used. The method first checks, if the + * thread calling this method has an associated display. If so, this display + * is returned. Otherwise the method returns the default display. + * + * @return the display to use + */ + public static Display getStandardDisplay() { + Display display = Display.getCurrent(); + if (display == null) { + display = Display.getDefault(); + } + return display; + } + + /** * Instantiate an error exception. * * @param message diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java index bb7381f..79c2665 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/PluginPreferenceInitializer.java @@ -35,6 +35,14 @@ public void initializeDefaultPreferences() { prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT, true); prefs.setDefault(UIPreferences.RESOURCEHISTORY_SHOW_TOOLTIPS, false); + prefs.setDefault(UIPreferences.DECORATOR_FILETEXT_DECORATION, + UIText.DecoratorPreferencesPage_fileFormatDefault); + prefs.setDefault(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION, + UIText.DecoratorPreferencesPage_folderFormatDefault); + prefs.setDefault(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION, + UIText.DecoratorPreferencesPage_projectFormatDefault); + prefs.setDefault(UIPreferences.DECORATOR_CALCULATE_DIRTY, true); + w = new int[] { 500, 500 }; UIPreferences.setDefault(prefs, UIPreferences.RESOURCEHISTORY_GRAPH_SPLIT, w); diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java index 5ab6b25..a6168a0 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIPreferences.java @@ -52,6 +52,15 @@ /** */ public final static String THEME_CommitMessageFont = "org.spearce.egit.ui.CommitMessageFont"; + /** */ + public final static String DECORATOR_CALCULATE_DIRTY = "decorator_calculate_dirty"; + /** */ + public final static String DECORATOR_FILETEXT_DECORATION = "decorator_filetext_decoration"; + /** */ + public final static String DECORATOR_FOLDERTEXT_DECORATION = "decorator_foldertext_decoration"; + /** */ + public final static String DECORATOR_PROJECTTEXT_DECORATION = "decorator_projecttext_decoration"; + /** * Get the preference values associated with a fixed integer array. * diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java index 7a7d3ef..23498c8 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java @@ -446,9 +446,6 @@ public static String RefSpecPage_annotatedTagsNoTags; /** */ - public static String Decorator_failedLazyLoading; - - /** */ public static String QuickDiff_failedLoading; /** */ @@ -913,6 +910,66 @@ /** */ public static String BranchSelectionDialog_Refs; + /** */ + public static String Decorator_exceptionMessage; + + /** */ + public static String DecoratorPreferencesPage_addVariablesTitle; + + /** */ + public static String DecoratorPreferencesPage_addVariablesAction; + + /** */ + public static String DecoratorPreferencesPage_computeDeep; + + /** */ + public static String DecoratorPreferencesPage_description; + + /** */ + public static String DecoratorPreferencesPage_decorationSettings; + + /** */ + public static String DecoratorPreferencesPage_preview; + + /** */ + public static String DecoratorPreferencesPage_fileFormatLabel; + + /** */ + public static String DecoratorPreferencesPage_folderFormatLabel; + + /** */ + public static String DecoratorPreferencesPage_projectFormatLabel; + + /** */ + public static String DecoratorPreferencesPage_fileFormatDefault; + + /** */ + public static String DecoratorPreferencesPage_projectFormatDefault; + + /** */ + public static String DecoratorPreferencesPage_folderFormatDefault; + + /** */ + public static String DecoratorPreferencesPage_generalTabFolder; + + /** */ + public static String DecoratorPreferencesPage_nameResourceVariable; + + /** */ + public static String DecoratorPreferencesPage_selectFormats; + + /** */ + public static String DecoratorPreferencesPage_selectVariablesToAdd; + + /** */ + public static String DecoratorPreferencesPage_textLabel; + + /** */ + public static String DecoratorPreferencesPage_iconLabel; + + /** */ + public static String DecoratorPreferencesPage_labelDecorationsLink; + static { initializeMessages(UIText.class.getPackage().getName() + ".uitext", UIText.class); diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java index 7ca4d10..38ee3d8 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/BranchAction.java @@ -19,7 +19,7 @@ import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.widgets.Display; import org.spearce.egit.core.op.BranchOperation; -import org.spearce.egit.ui.internal.decorators.GitResourceDecorator; +import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator; import org.spearce.egit.ui.internal.dialogs.BranchSelectionDialog; import org.spearce.jgit.lib.Repository; @@ -56,7 +56,7 @@ public void run(final IProgressMonitor monitor) throws InvocationTargetException { try { new BranchOperation(repository, refName).run(monitor); - GitResourceDecorator.refresh(); + GitLightweightDecorator.refresh(); } catch (final CoreException ce) { ce.printStackTrace(); Display.getDefault().asyncExec(new Runnable() { diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java index 18d6b4b..4201822 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/Disconnect.java @@ -13,7 +13,7 @@ import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.jface.action.IAction; import org.spearce.egit.core.op.DisconnectProviderOperation; -import org.spearce.egit.ui.internal.decorators.GitResourceDecorator; +import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator; /** * Action to disassociate a project from its Git repository. @@ -27,6 +27,6 @@ protected IWorkspaceRunnable createOperation(final IAction act, } protected void postOperation() { - GitResourceDecorator.refresh(); + GitLightweightDecorator.refresh(); } } diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java index b05cdd3..a329925 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/actions/ResetAction.java @@ -19,7 +19,7 @@ import org.eclipse.jface.operation.IRunnableWithProgress; import org.spearce.egit.core.op.ResetOperation; import org.spearce.egit.core.op.ResetOperation.ResetType; -import org.spearce.egit.ui.internal.decorators.GitResourceDecorator; +import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator; import org.spearce.egit.ui.internal.dialogs.BranchSelectionDialog; import org.spearce.jgit.lib.Repository; @@ -55,7 +55,7 @@ public void run(final IProgressMonitor monitor) throws InvocationTargetException { try { new ResetOperation(repository, refName, type).run(monitor); - GitResourceDecorator.refresh(); + GitLightweightDecorator.refresh(); } catch (CoreException ce) { ce.printStackTrace(); throw new InvocationTargetException(ce); diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java new file mode 100644 index 0000000..85b9173 --- /dev/null +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitLightweightDecorator.java @@ -0,0 +1,538 @@ +/******************************************************************************* + * Copyright (C) 2007, IBM Corporation and others + * Copyright (C) 2007, Dave Watson <dwatson@xxxxxxxxxxxx> + * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@xxxxxxxxxx> + * Copyright (C) 2008, Shawn O. Pearce <spearce@xxxxxxxxxxx> + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Tor Arne Vestbø <torarnv@xxxxxxxxx> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * See LICENSE for the full license text, also available. + *******************************************************************************/ + +package org.spearce.egit.ui.internal.decorators; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.IResourceVisitor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.mapping.ResourceMapping; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.jface.viewers.ILightweightLabelDecorator; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.LabelProviderChangedEvent; +import org.eclipse.osgi.util.TextProcessor; +import org.eclipse.swt.widgets.Display; +import org.eclipse.team.ui.TeamUI; +import org.eclipse.ui.IContributorResourceAdapter; +import org.eclipse.ui.PlatformUI; +import org.spearce.egit.core.internal.util.ExceptionCollector; +import org.spearce.egit.core.project.GitProjectData; +import org.spearce.egit.core.project.RepositoryChangeListener; +import org.spearce.egit.core.project.RepositoryMapping; +import org.spearce.egit.ui.Activator; +import org.spearce.egit.ui.UIPreferences; +import org.spearce.egit.ui.UIText; +import org.spearce.jgit.lib.IndexChangedEvent; +import org.spearce.jgit.lib.RefsChangedEvent; +import org.spearce.jgit.lib.Repository; +import org.spearce.jgit.lib.RepositoryChangedEvent; +import org.spearce.jgit.lib.RepositoryListener; + +/** + * Supplies annotations for displayed resources + * + * This decorator provides annotations to indicate the status of each resource + * when compared to <code>HEAD</code>, as well as the index in the relevant + * repository. + * + * TODO: Add support for colors and font decoration + */ +public class GitLightweightDecorator extends LabelProvider implements + ILightweightLabelDecorator, IPropertyChangeListener, + IResourceChangeListener, RepositoryChangeListener, RepositoryListener { + + /** + * Property constant pointing back to the extension point id of the + * decorator + */ + public static final String DECORATOR_ID = "org.spearce.egit.ui.internal.decorators.GitLightweightDecorator"; //$NON-NLS-1$ + + /** + * Bit-mask describing interesting changes for IResourceChangeListener + * events + */ + private static int INTERESTING_CHANGES = IResourceDelta.CONTENT + | IResourceDelta.MOVED_FROM | IResourceDelta.MOVED_TO + | IResourceDelta.OPEN | IResourceDelta.REPLACED + | IResourceDelta.TYPE; + + /** + * Collector for keeping the error view from filling up with exceptions + */ + private static ExceptionCollector exceptions = new ExceptionCollector( + UIText.Decorator_exceptionMessage, Activator.getPluginId(), + IStatus.ERROR, Activator.getDefault().getLog()); + + /** + * Constructs a new Git resource decorator + */ + public GitLightweightDecorator() { + TeamUI.addPropertyChangeListener(this); + Activator.addPropertyChangeListener(this); + PlatformUI.getWorkbench().getThemeManager().getCurrentTheme() + .addPropertyChangeListener(this); + Repository.addAnyRepositoryChangedListener(this); + GitProjectData.addRepositoryChangeListener(this); + ResourcesPlugin.getWorkspace().addResourceChangeListener(this, + IResourceChangeEvent.POST_CHANGE); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose() + */ + @Override + public void dispose() { + super.dispose(); + PlatformUI.getWorkbench().getThemeManager().getCurrentTheme() + .removePropertyChangeListener(this); + TeamUI.removePropertyChangeListener(this); + Activator.removePropertyChangeListener(this); + Repository.removeAnyRepositoryChangedListener(this); + GitProjectData.removeRepositoryChangeListener(this); + ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); + } + + /** + * This method should only be called by the decorator thread. + * + * @see org.eclipse.jface.viewers.ILightweightLabelDecorator#decorate(java.lang.Object, + * org.eclipse.jface.viewers.IDecoration) + */ + public void decorate(Object element, IDecoration decoration) { + final IResource resource = getResource(element); + if (resource == null) + return; + + // Don't decorate the workspace root + if (resource.getType() == IResource.ROOT) + return; + + // Don't decorate non-existing resources + if (!resource.exists() && !resource.isPhantom()) + return; + + // Make sure we're dealing with a Git project + final RepositoryMapping mapping = RepositoryMapping + .getMapping(resource); + if (mapping == null) + return; + + // Cannot decorate linked resources + if (mapping.getRepoRelativePath(resource) == null) + return; + + // Don't decorate if UI plugin is not running + Activator activator = Activator.getDefault(); + if (activator == null) + return; + + DecorationHelper helper = new DecorationHelper(activator + .getPreferenceStore()); + helper.decorate(decoration, new DecoratableResourceAdapter(resource)); + } + + private class DecoratableResourceAdapter implements IDecoratableResource { + + private IResource resource; + + public DecoratableResourceAdapter(IResource resourceToWrap) { + resource = resourceToWrap; + } + + public String getName() { + return resource.getName(); + } + + public int getType() { + return resource.getType(); + } + } + + /** + * Helper class for doing resource decoration, based on the given + * preferences + * + * Used for real-time decoration, as well as in the decorator preview + * preferences page + */ + public static class DecorationHelper { + + private IPreferenceStore store; + + /** */ + public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$ + + /** + * Constructs a decorator using the rules from the given + * <code>preferencesStore</code> + * + * @param preferencesStore + * the preferences store with the preferred decorator rules + */ + public DecorationHelper(IPreferenceStore preferencesStore) { + store = preferencesStore; + } + + /** + * Decorates the given <code>decoration</code> based on the state of the + * given <code>resource</code>, using the preferences passed when + * constructing this decoration helper. + * + * @param decoration + * the decoration to decorate + * @param resource + * the resource to retrieve state from + */ + public void decorate(IDecoration decoration, + IDecoratableResource resource) { + String format = ""; + switch (resource.getType()) { + case IResource.FILE: + format = store + .getString(UIPreferences.DECORATOR_FILETEXT_DECORATION); + break; + case IResource.FOLDER: + format = store + .getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION); + break; + case IResource.PROJECT: + format = store + .getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION); + break; + } + + Map<String, String> bindings = new HashMap<String, String>(); + bindings.put(BINDING_RESOURCE_NAME, resource.getName()); + + decorate(decoration, format, bindings); + } + + /** + * Decorates the given <code>decoration</code>, using the given + * <code>format</code>, and mapped using <code>bindings</code> + * + * @param decoration + * the decoration to decorate + * @param format + * the format to base the decoration on + * @param bindings + * the bindings between variables in the format and actual + * values + */ + public static void decorate(IDecoration decoration, String format, + Map bindings) { + StringBuffer prefix = new StringBuffer(); + StringBuffer suffix = new StringBuffer(); + StringBuffer output = prefix; + + int length = format.length(); + int start = -1; + int end = length; + while (true) { + if ((end = format.indexOf('{', start)) > -1) { + output.append(format.substring(start + 1, end)); + if ((start = format.indexOf('}', end)) > -1) { + String key = format.substring(end + 1, start); + String s; + + // We use the BINDING_RESOURCE_NAME key to determine if + // we are doing the prefix or suffix. The name isn't + // actually part of either. + if (key.equals(BINDING_RESOURCE_NAME)) { + output = suffix; + s = null; + } else { + s = (String) bindings.get(key); + } + + if (s != null) { + output.append(s); + } else { + // Support removing prefix character if binding is + // null + int curLength = output.length(); + if (curLength > 0) { + char c = output.charAt(curLength - 1); + if (c == ':' || c == '@') { + output.deleteCharAt(curLength - 1); + } + } + } + } else { + output.append(format.substring(end, length)); + break; + } + } else { + output.append(format.substring(start + 1, length)); + break; + } + } + + String prefixString = prefix.toString().replaceAll("^\\s+", ""); + if (prefixString != null) { + decoration.addPrefix(TextProcessor.process(prefixString, + "()[].")); //$NON-NLS-1$ + } + String suffixString = suffix.toString().replaceAll("\\s+$", ""); + if (suffixString != null) { + decoration.addSuffix(TextProcessor.process(suffixString, + "()[].")); //$NON-NLS-1$ + } + } + } + + // -------- Refresh handling -------- + + /** + * Perform a blanket refresh of all decorations + */ + public static void refresh() { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + Activator.getDefault().getWorkbench().getDecoratorManager() + .update(DECORATOR_ID); + } + }); + } + + /** + * Callback for IPropertyChangeListener events + * + * If any of the relevant preferences has been changed we refresh all + * decorations (all projects and their resources). + * + * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + final String prop = event.getProperty(); + // If the property is of any interest to us + if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED) + || prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED) + || prop.equals(Activator.DECORATORS_CHANGED)) { + postLabelEvent(new LabelProviderChangedEvent(this, null /* all */)); + } + } + + /** + * Callback for IResourceChangeListener events + * + * Schedules a refresh of the changed resource + * + * If the preference for computing deep dirty states has been set we walk + * the ancestor tree of the changed resource and update all parents as well. + * + * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) + */ + public void resourceChanged(IResourceChangeEvent event) { + final Set<IResource> resourcesToUpdate = new HashSet<IResource>(); + + try { // Compute the changed resources by looking at the delta + event.getDelta().accept(new IResourceDeltaVisitor() { + public boolean visit(IResourceDelta delta) throws CoreException { + final IResource resource = delta.getResource(); + + if (resource.getType() == IResource.ROOT) { + // Continue with the delta + return true; + } + + if (resource.getType() == IResource.PROJECT) { + // If the project is not accessible, don't process it + if (!resource.isAccessible()) + return false; + } + + // If the file has changed but not in a way that we care + // about + // (e.g. marker changes to files) then ignore the change + if (delta.getKind() == IResourceDelta.CHANGED + && (delta.getFlags() & INTERESTING_CHANGES) == 0) { + return true; + } + + // All seems good, schedule the resource for update + resourcesToUpdate.add(resource); + return true; + } + }, true /* includePhantoms */); + } catch (final CoreException e) { + handleException(null, e); + } + + // If deep decorator calculation is enabled in the preferences we + // walk the ancestor tree of each of the changed resources and add + // their parents to the update set + final IPreferenceStore store = Activator.getDefault() + .getPreferenceStore(); + if (store.getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY)) { + final IResource[] changedResources = resourcesToUpdate + .toArray(new IResource[resourcesToUpdate.size()]); + for (int i = 0; i < changedResources.length; i++) { + IResource current = changedResources[i]; + while (current.getType() != IResource.ROOT) { + current = current.getParent(); + resourcesToUpdate.add(current); + } + } + } + + postLabelEvent(new LabelProviderChangedEvent(this, resourcesToUpdate + .toArray())); + } + + /** + * Callback for RepositoryListener events + * + * We resolve the repository mapping for the changed repository and forward + * that to repositoryChanged(RepositoryMapping). + * + * @param e + * The original change event + */ + private void repositoryChanged(RepositoryChangedEvent e) { + final Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>(); + for (final IProject p : ResourcesPlugin.getWorkspace().getRoot() + .getProjects()) { + final RepositoryMapping mapping = RepositoryMapping.getMapping(p); + if (mapping != null && mapping.getRepository() == e.getRepository()) + ms.add(mapping); + } + for (final RepositoryMapping m : ms) { + repositoryChanged(m); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.spearce.jgit.lib.RepositoryListener#indexChanged(org.spearce.jgit + * .lib.IndexChangedEvent) + */ + public void indexChanged(IndexChangedEvent e) { + repositoryChanged(e); + } + + /* + * (non-Javadoc) + * + * @see + * org.spearce.jgit.lib.RepositoryListener#refsChanged(org.spearce.jgit. + * lib.RefsChangedEvent) + */ + public void refsChanged(RefsChangedEvent e) { + repositoryChanged(e); + } + + /** + * Callback for RepositoryChangeListener events, as well as + * RepositoryListener events via repositoryChanged() + * + * We resolve the project and schedule a refresh of each resource in the + * project. + * + * @see org.spearce.egit.core.project.RepositoryChangeListener#repositoryChanged(org.spearce.egit.core.project.RepositoryMapping) + */ + public void repositoryChanged(RepositoryMapping mapping) { + final IProject project = mapping.getContainer().getProject(); + if (project == null) + return; + + final List<IResource> resources = new ArrayList<IResource>(); + try { + project.accept(new IResourceVisitor() { + public boolean visit(IResource resource) { + resources.add(resource); + return true; + } + }); + postLabelEvent(new LabelProviderChangedEvent(this, resources + .toArray())); + } catch (final CoreException e) { + handleException(project, e); + } + } + + // -------- Helper methods -------- + + private static IResource getResource(Object element) { + if (element instanceof ResourceMapping) { + element = ((ResourceMapping) element).getModelObject(); + } + + IResource resource = null; + if (element instanceof IResource) { + resource = (IResource) element; + } else if (element instanceof IAdaptable) { + final IAdaptable adaptable = (IAdaptable) element; + resource = (IResource) adaptable.getAdapter(IResource.class); + if (resource == null) { + final IContributorResourceAdapter adapter = (IContributorResourceAdapter) adaptable + .getAdapter(IContributorResourceAdapter.class); + if (adapter != null) + resource = adapter.getAdaptedResource(adaptable); + } + } + + return resource; + } + + /** + * Post the label event to the UI thread + * + * @param event + * The event to post + */ + private void postLabelEvent(final LabelProviderChangedEvent event) { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + fireLabelProviderChanged(event); + } + }); + } + + /** + * Handle exceptions that occur in the decorator. Exceptions are only logged + * for resources that are accessible (i.e. exist in an open project). + * + * @param resource + * The resource that triggered the exception + * @param e + * The exception that occurred + */ + private static void handleException(IResource resource, CoreException e) { + if (resource == null || resource.isAccessible()) + exceptions.handleException(e); + } +} diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java deleted file mode 100644 index f24b1eb..0000000 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/GitResourceDecorator.java +++ /dev/null @@ -1,454 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2007, Dave Watson <dwatson@xxxxxxxxxxxx> - * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@xxxxxxxxxx> - * Copyright (C) 2008, Shawn O. Pearce <spearce@xxxxxxxxxxx> - * Copyright (C) 2008, Google Inc. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * See LICENSE for the full license text, also available. - *******************************************************************************/ -package org.spearce.egit.ui.internal.decorators; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IResourceDeltaVisitor; -import org.eclipse.core.resources.IResourceVisitor; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.ISchedulingRule; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jface.viewers.IDecoration; -import org.eclipse.jface.viewers.ILightweightLabelDecorator; -import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.viewers.LabelProviderChangedEvent; -import org.eclipse.swt.widgets.Display; -import org.eclipse.team.core.Team; -import org.eclipse.ui.IDecoratorManager; -import org.spearce.egit.core.project.GitProjectData; -import org.spearce.egit.core.project.RepositoryChangeListener; -import org.spearce.egit.core.project.RepositoryMapping; -import org.spearce.egit.ui.Activator; -import org.spearce.egit.ui.UIIcons; -import org.spearce.egit.ui.UIText; -import org.spearce.jgit.lib.Constants; -import org.spearce.jgit.lib.GitIndex; -import org.spearce.jgit.lib.IndexChangedEvent; -import org.spearce.jgit.lib.RefsChangedEvent; -import org.spearce.jgit.lib.Repository; -import org.spearce.jgit.lib.RepositoryChangedEvent; -import org.spearce.jgit.lib.RepositoryListener; -import org.spearce.jgit.lib.RepositoryState; -import org.spearce.jgit.lib.Tree; -import org.spearce.jgit.lib.TreeEntry; -import org.spearce.jgit.lib.GitIndex.Entry; - -/** - * Supplies annotations for displayed resources. - * <p> - * This decorator provides annotations to indicate the status of each resource - * when compared to <code>HEAD</code> as well as the index in the relevant - * repository. - * - * When either the index or the working directory is different from HEAD an - * indicator is set. - * - * </p> - */ -public class GitResourceDecorator extends LabelProvider implements - ILightweightLabelDecorator { - - static final String decoratorId = "org.spearce.egit.ui.internal.decorators.GitResourceDecorator"; - static class ResCL extends Job implements IResourceChangeListener, RepositoryChangeListener, RepositoryListener { - - ResCL() { - super("Git resource decorator trigger"); - } - - GitResourceDecorator getActiveDecorator() { - IDecoratorManager decoratorManager = Activator.getDefault() - .getWorkbench().getDecoratorManager(); - if (decoratorManager.getEnabled(decoratorId)) - return (GitResourceDecorator) decoratorManager - .getLightweightLabelDecorator(decoratorId); - return null; - } - - private Set<IResource> resources = new LinkedHashSet<IResource>(); - - public void refsChanged(RefsChangedEvent e) { - repositoryChanged(e); - } - - public void indexChanged(IndexChangedEvent e) { - repositoryChanged(e); - } - - private void repositoryChanged(RepositoryChangedEvent e) { - Set<RepositoryMapping> ms = new HashSet<RepositoryMapping>(); - for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { - RepositoryMapping mapping = RepositoryMapping.getMapping(p); - if (mapping != null && mapping.getRepository() == e.getRepository()) - ms.add(mapping); - } - for (RepositoryMapping m : ms) { - repositoryChanged(m); - } - } - - public void repositoryChanged(final RepositoryMapping which) { - synchronized (resources) { - resources.add(which.getContainer()); - } - schedule(); - } - - @Override - protected IStatus run(IProgressMonitor arg0) { - try { - if (resources.size() > 0) { - IResource m; - synchronized(resources) { - Iterator<IResource> i = resources.iterator(); - m = i.next(); - i.remove(); - - while (!m.isAccessible()) { - if (!i.hasNext()) - return Status.OK_STATUS; - m = i.next(); - i.remove(); - } - - if (resources.size() > 0) - schedule(); - } - ISchedulingRule markerRule = m.getWorkspace().getRuleFactory().markerRule(m); - getJobManager().beginRule(markerRule, arg0); - try { - m.accept(new IResourceVisitor() { - public boolean visit(IResource resource) throws CoreException { - GitResourceDecorator decorator = getActiveDecorator(); - if (decorator != null) - decorator.clearDecorationState(resource); - return true; - } - }, - IResource.DEPTH_INFINITE, - true); - } finally { - getJobManager().endRule(markerRule); - } - } - return Status.OK_STATUS; - } catch (Exception e) { - // We must be silent here or the UI will panic with lots of error messages - Activator.logError("Failed to trigger resource re-decoration", e); - return Status.OK_STATUS; - } - } - - public void resourceChanged(IResourceChangeEvent event) { - if (event.getType() != IResourceChangeEvent.POST_CHANGE) { - return; - } - try { - event.getDelta().accept(new IResourceDeltaVisitor() { - public boolean visit(IResourceDelta delta) - throws CoreException { - for (IResource r = delta.getResource(); r.getType() != IResource.ROOT; r = r - .getParent()) { - synchronized (resources) { - resources.add(r); - } - } - return true; - } - }, - true - ); - } catch (Exception e) { - Activator.logError("Problem during decorations. Stopped", e); - } - schedule(); - } - - void force() { - for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { - synchronized (resources) { - resources.add(p); - } - } - schedule(); - } - } // End ResCL - - void clearDecorationState(IResource r) throws CoreException { - if (r.isAccessible()) { - r.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, null); - fireLabelProviderChanged(new LabelProviderChangedEvent(this, r)); - } - } - - static ResCL myrescl = new ResCL(); - - static { - Repository.addAnyRepositoryChangedListener(myrescl); - GitProjectData.addRepositoryChangeListener(myrescl); - ResourcesPlugin.getWorkspace().addResourceChangeListener(myrescl, - IResourceChangeEvent.POST_CHANGE); - } - - /** - * Request that the decorator be updated, to reflect any recent changes. - * <p> - * Can be invoked any any thread. If the current thread is not the UI - * thread, an async update will be scheduled. - * </p> - */ - public static void refresh() { - myrescl.force(); - } - - private static IResource toIResource(final Object e) { - if (e instanceof IResource) - return (IResource) e; - if (e instanceof IAdaptable) { - final Object c = ((IAdaptable) e).getAdapter(IResource.class); - if (c instanceof IResource) - return (IResource) c; - } - return null; - } - - static QualifiedName GITFOLDERDIRTYSTATEPROPERTY = new QualifiedName( - "org.spearce.egit.ui.internal.decorators.GitResourceDecorator", - "dirty"); - - static final int UNCHANGED = 0; - - static final int CHANGED = 1; - - private Boolean isDirty(IResource rsrc) { - try { - if (rsrc.getType() == IResource.FILE && Team.isIgnored((IFile)rsrc)) - return Boolean.FALSE; - - RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc); - if (mapped != null) { - if (rsrc instanceof IContainer) { - for (IResource r : ((IContainer) rsrc) - .members(IContainer.EXCLUDE_DERIVED)) { - Boolean f = isDirty(r); - if (f == null || f.booleanValue()) - return Boolean.TRUE; - } - return Boolean.FALSE; - } - - return Boolean.valueOf(mapped.isResourceChanged(rsrc)); - } - return null; // not mapped - } catch (CoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; - } - - public void decorate(final Object element, final IDecoration decoration) { - final IResource rsrc = toIResource(element); - if (rsrc == null) - return; - - // If the workspace has not been refreshed properly a resource might - // not actually exist, so we ignore these and do not decorate them - if (!rsrc.exists() && !rsrc.isPhantom()) { - Activator.trace("Tried to decorate non-existent resource "+rsrc); - return; - } - - RepositoryMapping mapped = RepositoryMapping.getMapping(rsrc); - - // TODO: How do I see a renamed resource? - // TODO: Even trickier: when a path change from being blob to tree? - try { - if (mapped != null) { - Repository repository = mapped.getRepository(); - GitIndex index = repository.getIndex(); - String repoRelativePath = mapped.getRepoRelativePath(rsrc); - - if (repoRelativePath == null) { - Activator.trace("Cannot decorate linked resource " + rsrc); - return; - } - - Tree headTree = repository.mapTree(Constants.HEAD); - TreeEntry blob = headTree!=null ? headTree.findBlobMember(repoRelativePath) : null; - Entry entry = index.getEntry(repoRelativePath); - if (entry == null) { - if (blob == null) { - if (rsrc instanceof IContainer) { - Integer df = (Integer) rsrc - .getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY); - Boolean f = df == null ? isDirty(rsrc) - : Boolean.valueOf(df.intValue() == CHANGED); - if (f != null) { - if (f.booleanValue()) { - decoration.addPrefix(">"); // Have not - // seen - orState(rsrc, CHANGED); - } else { - orState(rsrc, UNCHANGED); - // decoration.addSuffix("=?"); - } - } else { - decoration.addSuffix(" ?* "); - } - - if (rsrc instanceof IProject) { - Repository repo = mapped.getRepository(); - try { - String branch = repo.getBranch(); - RepositoryState repositoryState = repo.getRepositoryState(); - String statename; - if (repositoryState.equals(RepositoryState.SAFE)) - statename = ""; - else - statename = repositoryState.getDescription() + " "; - decoration.addSuffix(" [Git " + statename + "@ " + branch + "]"); - } catch (IOException e) { - e.printStackTrace(); - decoration.addSuffix(" [Git ?]"); - } - decoration.addOverlay(UIIcons.OVR_SHARED); - } - - } else { - if (Team.isIgnoredHint(rsrc)) { - decoration.addSuffix("(ignored)"); - } else { - decoration.addPrefix(">"); - decoration.addSuffix("(untracked)"); - orState(rsrc.getParent(), CHANGED); - } - } - } else { - if (!(rsrc instanceof IContainer)) { - decoration.addSuffix("(deprecated)"); // Will drop on - // commit - decoration.addOverlay(UIIcons.OVR_PENDING_REMOVE); - orState(rsrc.getParent(), CHANGED); - } - } - } else { - if (entry.getStage() != GitIndex.STAGE_0) { - decoration.addSuffix("(conflict)"); - decoration.addOverlay(UIIcons.OVR_CONFLICT); - orState(rsrc.getParent(), CHANGED); - return; - } - - if (blob == null) { - decoration.addOverlay(UIIcons.OVR_PENDING_ADD); - orState(rsrc.getParent(), CHANGED); - } else { - - if (entry.isAssumedValid()) { - decoration.addOverlay(UIIcons.OVR_ASSUMEVALID); - return; - } - - decoration.addOverlay(UIIcons.OVR_SHARED); - - if (entry.isModified(mapped.getWorkDir(), true)) { - decoration.addPrefix(">"); - decoration.addSuffix("(not updated)"); - orState(rsrc.getParent(), CHANGED); - } else { - if (!entry.getObjectId().equals(blob.getId())) - decoration.addPrefix(">"); - else - decoration.addPrefix(""); // set it to avoid further calls - } - } - } - } - } catch (IOException e) { - decoration.addSuffix("?"); - // If we throw an exception Eclipse will log the error and - // unregister us thereby preventing us from dragging down the - // entire workbench because we are crashing. - // - throw new RuntimeException(UIText.Decorator_failedLazyLoading, e); - } catch (CoreException e) { - throw new RuntimeException(UIText.Decorator_failedLazyLoading, e); - } - } - - private void orState(final IResource rsrc, int flag) { - if (rsrc == null || rsrc.getType() == IResource.ROOT) { - return; - } - - try { - Integer dirty = (Integer) rsrc.getSessionProperty(GITFOLDERDIRTYSTATEPROPERTY); - Runnable runnable = new Runnable() { - public void run() { - // Async could be called after a - // project is closed or a - // resource is deleted - if (!rsrc.isAccessible()) - return; - fireLabelProviderChanged(new LabelProviderChangedEvent( - GitResourceDecorator.this, rsrc)); - } - }; - if (dirty == null) { - rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, new Integer(flag)); - orState(rsrc.getParent(), flag); -// if (Thread.currentThread() == Display.getDefault().getThread()) -// runnable.run(); -// else - Display.getDefault().asyncExec(runnable); - } else { - if ((dirty.intValue() | flag) != dirty.intValue()) { - dirty = new Integer(dirty.intValue() | flag); - rsrc.setSessionProperty(GITFOLDERDIRTYSTATEPROPERTY, dirty); - orState(rsrc.getParent(), dirty.intValue()); -// if (Thread.currentThread() == Display.getDefault().getThread()) -// runnable.run(); -// else - Display.getDefault().asyncExec(runnable); - } - } - } catch (CoreException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Override - public boolean isLabelProperty(Object element, String property) { - return super.isLabelProperty(element, property); - } -} diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java new file mode 100644 index 0000000..8d6c741 --- /dev/null +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/decorators/IDecoratableResource.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (C) 2008, Tor Arne Vestbø <torarnv@xxxxxxxxx> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * See LICENSE for the full license text, also available. + *******************************************************************************/ + +package org.spearce.egit.ui.internal.decorators; + +import org.eclipse.core.resources.IResource; + +/** + * Represents the state of a resource that can be used as a basis for decoration + */ +public interface IDecoratableResource { + + /** + * Gets the type of the resource as defined by {@link IResource} + * + * @return the type of the resource + */ + int getType(); + + /** + * Gets the name of the resource + * + * @return the name of the resource + */ + String getName(); +} diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java new file mode 100644 index 0000000..2ef0292 --- /dev/null +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/internal/preferences/GitDecoratorPreferencePage.java @@ -0,0 +1,735 @@ +/******************************************************************************* + * Copyright (C) 2003, 2006 Subclipse project and others. + * Copyright (C) 2008, Tor Arne Vestbø <torarnv@xxxxxxxxx> + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * See LICENSE for the full license text, also available. + *******************************************************************************/ +package org.spearce.egit.ui.internal.preferences; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; + +import org.eclipse.core.resources.IResource; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.preference.PreferenceStore; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.DecorationContext; +import org.eclipse.jface.viewers.DecorationOverlayIcon; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.jface.viewers.IDecorationContext; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.ListSelectionDialog; +import org.eclipse.ui.ide.IDE.SharedImages; +import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer; +import org.spearce.egit.ui.Activator; +import org.spearce.egit.ui.UIPreferences; +import org.spearce.egit.ui.UIText; +import org.spearce.egit.ui.internal.SWTUtils; +import org.spearce.egit.ui.internal.decorators.GitLightweightDecorator.DecorationHelper; +import org.spearce.egit.ui.internal.decorators.IDecoratableResource; + +/** + * Preference page for customizing Git label decorations + */ +public class GitDecoratorPreferencePage extends PreferencePage implements + IWorkbenchPreferencePage { + + private Text fileTextFormat; + + private Text folderTextFormat; + + private Text projectTextFormat; + + private Button showDirty; + + private Preview fPreview; + + private static final Collection PREVIEW_FILESYSTEM_ROOT; + + private static ThemeListener fThemeListener; + + static { + final PreviewResource project = new PreviewResource( + "Project", IResource.PROJECT); //$NON-NLS-1$1 + final ArrayList<PreviewResource> children = new ArrayList<PreviewResource>(); + children.add(new PreviewResource("folder", IResource.FOLDER)); //$NON-NLS-1$ + children.add(new PreviewResource("file.txt", IResource.FILE)); //$NON-NLS-1$ + project.children = children; + PREVIEW_FILESYSTEM_ROOT = Collections.singleton(project); + } + + /** + * Constructs a decorator preference page + */ + public GitDecoratorPreferencePage() { + setDescription(UIText.DecoratorPreferencesPage_description); + } + + /** + * @see PreferencePage#createContents(Composite) + */ + protected Control createContents(Composite parent) { + Composite headerGroup = new Composite(parent, SWT.NULL); + GridLayout layout = new GridLayout(); + layout.marginWidth = 0; + layout.marginHeight = 0; + layout.verticalSpacing = 10; + headerGroup.setLayout(layout); + GridData data = new GridData(); + data.horizontalAlignment = GridData.FILL; + headerGroup.setLayoutData(data); + + SWTUtils.createPreferenceLink( + (IWorkbenchPreferenceContainer) getContainer(), headerGroup, + "org.eclipse.ui.preferencePages.Decorators", + UIText.DecoratorPreferencesPage_labelDecorationsLink); //$NON-NLS-1$ + + SWTUtils.createLabel(headerGroup, + UIText.DecoratorPreferencesPage_decorationSettings); + + TabFolder tabFolder = new TabFolder(parent, SWT.NONE); + GridData tabData = SWTUtils.createHVFillGridData(); + tabData.heightHint = 100; + tabFolder.setLayoutData(tabData); + + TabItem tabItem = new TabItem(tabFolder, SWT.NONE); + tabItem.setText(UIText.DecoratorPreferencesPage_generalTabFolder); + tabItem.setControl(createGeneralDecoratorPage(tabFolder)); + + tabItem = new TabItem(tabFolder, SWT.NONE); + tabItem.setText(UIText.DecoratorPreferencesPage_textLabel); + tabItem.setControl(createTextDecoratorPage(tabFolder)); + + tabItem = new TabItem(tabFolder, SWT.NONE); + tabItem.setText(UIText.DecoratorPreferencesPage_iconLabel); + tabItem.setControl(createIconDecoratorPage(tabFolder)); + + initializeValues(); + + fPreview = new Preview(parent); + fPreview.refresh(); + + // TODO: Add help text for this preference page + + fThemeListener = new ThemeListener(fPreview); + PlatformUI.getWorkbench().getThemeManager().addPropertyChangeListener( + fThemeListener); + Dialog.applyDialogFont(parent); + return tabFolder; + } + + private Control createGeneralDecoratorPage(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + + GridLayout layout = new GridLayout(); + composite.setLayout(layout); + GridData data = new GridData(); + data.horizontalAlignment = GridData.FILL; + composite.setLayoutData(data); + + showDirty = SWTUtils.createCheckBox(composite, + UIText.DecoratorPreferencesPage_computeDeep); + + return composite; + } + + /** + * Creates the controls for the first tab folder + * + * @param parent + * + * @return the control + */ + private Control createTextDecoratorPage(Composite parent) { + Composite fileTextGroup = new Composite(parent, SWT.NULL); + GridLayout layout = new GridLayout(); + layout.numColumns = 3; + fileTextGroup.setLayout(layout); + GridData data = new GridData(); + data.horizontalAlignment = GridData.FILL; + fileTextGroup.setLayoutData(data); + + TextPair format = createFormatEditorControl(fileTextGroup, + UIText.DecoratorPreferencesPage_fileFormatLabel, + UIText.DecoratorPreferencesPage_addVariablesAction, + getFileBindingDescriptions()); + fileTextFormat = format.t1; + + format = createFormatEditorControl(fileTextGroup, + UIText.DecoratorPreferencesPage_folderFormatLabel, + UIText.DecoratorPreferencesPage_addVariablesAction, + getFolderBindingDescriptions()); + folderTextFormat = format.t1; + + format = createFormatEditorControl(fileTextGroup, + UIText.DecoratorPreferencesPage_projectFormatLabel, + UIText.DecoratorPreferencesPage_addVariablesAction, + getProjectBindingDescriptions()); + projectTextFormat = format.t1; + + return fileTextGroup; + } + + private Control createIconDecoratorPage(Composite parent) { + Composite imageGroup = new Composite(parent, SWT.NULL); + GridLayout layout = new GridLayout(); + imageGroup.setLayout(layout); + GridData data = new GridData(); + data.horizontalAlignment = GridData.FILL; + imageGroup.setLayoutData(data); + + return imageGroup; + } + + private TextPair createFormatEditorControl(Composite composite, + String title, String buttonText, final Map supportedBindings) { + + SWTUtils.createLabel(composite, title); + + Text format = new Text(composite, SWT.BORDER); + format.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + format.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + updatePreview(); + } + }); + Button b = new Button(composite, SWT.NONE); + b.setText(buttonText); + GridData data = new GridData(); + data.horizontalAlignment = GridData.FILL; + int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); + data.widthHint = Math.max(widthHint, b.computeSize(SWT.DEFAULT, + SWT.DEFAULT, true).x); + b.setLayoutData(data); + final Text formatToInsert = format; + b.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + addVariables(formatToInsert, supportedBindings); + } + }); + + return new TextPair(format, null); + } + + /** + * Initializes states of the controls from the preference store. + */ + private void initializeValues() { + final IPreferenceStore store = getPreferenceStore(); + + fileTextFormat.setText(store + .getString(UIPreferences.DECORATOR_FILETEXT_DECORATION)); + folderTextFormat.setText(store + .getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION)); + projectTextFormat.setText(store + .getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION)); + + showDirty.setSelection(store + .getBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY)); + + setValid(true); + } + + /** + * @see IWorkbenchPreferencePage#init(IWorkbench) + */ + public void init(IWorkbench workbench) { + // No-op + } + + /** + * OK was clicked. Store the preferences to the plugin store + * + * @return whether it is okay to close the preference page + */ + public boolean performOk() { + IPreferenceStore store = getPreferenceStore(); + final boolean okToClose = performOk(store); + if (store.needsSaving()) { + Activator.getDefault().savePluginPreferences(); + Activator.broadcastPropertyChange(new PropertyChangeEvent(this, + Activator.DECORATORS_CHANGED, null, null)); + } + return okToClose; + } + + /** + * Store the preferences to the given preference store + * + * @param store + * the preference store to store the preferences to + * + * @return whether it operation succeeded + */ + private boolean performOk(IPreferenceStore store) { + + store.setValue(UIPreferences.DECORATOR_FILETEXT_DECORATION, + fileTextFormat.getText()); + store.setValue(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION, + folderTextFormat.getText()); + store.setValue(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION, + projectTextFormat.getText()); + + store.setValue(UIPreferences.DECORATOR_CALCULATE_DIRTY, showDirty + .getSelection()); + + return true; + } + + /** + * Defaults was clicked. Restore the Git decoration preferences to their + * default values + */ + protected void performDefaults() { + super.performDefaults(); + IPreferenceStore store = getPreferenceStore(); + + fileTextFormat.setText(store + .getDefaultString(UIPreferences.DECORATOR_FILETEXT_DECORATION)); + folderTextFormat + .setText(store + .getDefaultString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION)); + projectTextFormat + .setText(store + .getDefaultString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION)); + + showDirty.setSelection(store + .getDefaultBoolean(UIPreferences.DECORATOR_CALCULATE_DIRTY)); + } + + /** + * Returns the preference store that belongs to the our plugin. + * + * This is important because we want to store our preferences separately + * from the desktop. + * + * @return the preference store for this plugin + */ + protected IPreferenceStore doGetPreferenceStore() { + return Activator.getDefault().getPreferenceStore(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.DialogPage#dispose() + */ + public void dispose() { + PlatformUI.getWorkbench().getThemeManager() + .removePropertyChangeListener(fThemeListener); + super.dispose(); + } + + private static class ThemeListener implements IPropertyChangeListener { + private final Preview preview; + + ThemeListener(Preview preview) { + this.preview = preview; + } + + public void propertyChange(PropertyChangeEvent event) { + preview.refresh(); + } + } + + /** + * Adds another variable to the given target text + * + * A ListSelectionDialog pops up and allow the user to choose the variable, + * which is then inserted at current position in <code>text</code> + * + * @param target + * the target to add the variable to + * @param bindings + * the map of bindings + */ + private void addVariables(Text target, Map bindings) { + + final List<StringPair> variables = new ArrayList<StringPair>(bindings + .size()); + + ILabelProvider labelProvider = new LabelProvider() { + public String getText(Object element) { + return ((StringPair) element).s1 + + " - " + ((StringPair) element).s2; //$NON-NLS-1$ + } + }; + + IStructuredContentProvider contentsProvider = new IStructuredContentProvider() { + public Object[] getElements(Object inputElement) { + return variables.toArray(new StringPair[variables.size()]); + } + + public void dispose() { + // No-op + } + + public void inputChanged(Viewer viewer, Object oldInput, + Object newInput) { + // No-op + } + }; + + for (Iterator it = bindings.keySet().iterator(); it.hasNext();) { + StringPair variable = new StringPair(); + variable.s1 = (String) it.next(); // variable + variable.s2 = (String) bindings.get(variable.s1); // description + variables.add(variable); + } + + ListSelectionDialog dialog = new ListSelectionDialog(this.getShell(), + this, contentsProvider, labelProvider, + UIText.DecoratorPreferencesPage_selectVariablesToAdd); + dialog.setTitle(UIText.DecoratorPreferencesPage_addVariablesTitle); + if (dialog.open() != Window.OK) + return; + + Object[] result = dialog.getResult(); + + for (int i = 0; i < result.length; i++) { + target.insert("{" + ((StringPair) result[i]).s1 + "}"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + class StringPair { + String s1; + + String s2; + } + + class TextPair { + TextPair(Text t1, Text t2) { + this.t1 = t1; + this.t2 = t2; + } + + Text t1; + + Text t2; + } + + /** + * Gets the map of bindings between variables and description, to use for + * the format editors for files + * + * @return the bindings + */ + private Map getFileBindingDescriptions() { + Map<String, String> bindings = new HashMap<String, String>(); + bindings.put(DecorationHelper.BINDING_RESOURCE_NAME, + UIText.DecoratorPreferencesPage_nameResourceVariable); + return bindings; + } + + /** + * Gets the map of bindings between variables and description, to use for + * the format editors for folders + * + * @return the bindings + */ + private Map getFolderBindingDescriptions() { + Map<String, String> bindings = new HashMap<String, String>(); + bindings.put(DecorationHelper.BINDING_RESOURCE_NAME, + UIText.DecoratorPreferencesPage_nameResourceVariable); + return bindings; + } + + /** + * Gets the map of bindings between variables and description, to use for + * the format editors for projects + * + * @return the bindings + */ + private Map getProjectBindingDescriptions() { + Map<String, String> bindings = new HashMap<String, String>(); + bindings.put(DecorationHelper.BINDING_RESOURCE_NAME, + UIText.DecoratorPreferencesPage_nameResourceVariable); + return bindings; + } + + private void updatePreview() { + if (fPreview != null) + fPreview.refresh(); + } + + /** + * Preview control for showing how changes in the dialog will affect + * decoration + */ + private class Preview extends LabelProvider implements Observer, + ITreeContentProvider { + + private final ResourceManager fImageCache; + + private final TreeViewer fViewer; + + private DecorationHelper fHelper; + + public Preview(Composite composite) { + reloadDecorationHelper(); // Has to happen before the tree control + // is constructed + SWTUtils.createLabel(composite, + UIText.DecoratorPreferencesPage_preview); + fImageCache = new LocalResourceManager(JFaceResources + .getResources()); + fViewer = new TreeViewer(composite); + GridData previewGridData = SWTUtils.createHVFillGridData(); + previewGridData.heightHint = 100; + fViewer.getControl().setLayoutData(previewGridData); + fViewer.setContentProvider(this); + fViewer.setLabelProvider(this); + fViewer.setInput(PREVIEW_FILESYSTEM_ROOT); + fViewer.expandAll(); + fHelper = new DecorationHelper(new PreferenceStore()); + } + + private void reloadDecorationHelper() { + PreferenceStore store = new PreferenceStore(); + performOk(store); + fHelper = new DecorationHelper(store); + } + + public void refresh() { + reloadDecorationHelper(); + fViewer.refresh(true); + setColorsAndFonts(fViewer.getTree().getItems()); + } + + @SuppressWarnings("unused") + private void setColorsAndFonts(TreeItem[] items) { + // TODO: Implement colors and fonts + } + + public void update(Observable o, Object arg) { + refresh(); + } + + public Object[] getChildren(Object parentElement) { + return ((PreviewResource) parentElement).children.toArray(); + } + + public Object getParent(Object element) { + return null; + } + + public boolean hasChildren(Object element) { + return !((PreviewResource) element).children.isEmpty(); + } + + public Object[] getElements(Object inputElement) { + return ((Collection) inputElement).toArray(); + } + + public void dispose() { + fImageCache.dispose(); + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // No-op + } + + public Color getBackground(Object element) { + return getDecoration(element).getBackgroundColor(); + } + + public Color getForeground(Object element) { + return getDecoration(element).getForegroundColor(); + } + + public Font getFont(Object element) { + return getDecoration(element).getFont(); + } + + public String getText(Object element) { + final PreviewDecoration decoration = getDecoration(element); + final StringBuffer buffer = new StringBuffer(); + final String prefix = decoration.getPrefix(); + if (prefix != null) + buffer.append(prefix); + buffer.append(((PreviewResource) element).getName()); + final String suffix = decoration.getSuffix(); + if (suffix != null) + buffer.append(suffix); + return buffer.toString(); + } + + public Image getImage(Object element) { + final String s; + switch (((PreviewResource) element).type) { + case IResource.PROJECT: + s = SharedImages.IMG_OBJ_PROJECT; + break; + case IResource.FOLDER: + s = ISharedImages.IMG_OBJ_FOLDER; + break; + default: + s = ISharedImages.IMG_OBJ_FILE; + break; + } + final Image baseImage = PlatformUI.getWorkbench().getSharedImages() + .getImage(s); + final ImageDescriptor overlay = getDecoration(element).getOverlay(); + if (overlay == null) + return baseImage; + try { + return fImageCache.createImage(new DecorationOverlayIcon( + baseImage, overlay, IDecoration.BOTTOM_RIGHT)); + } catch (Exception e) { + Activator.logError(e.getMessage(), e); + } + + return null; + } + + private PreviewDecoration getDecoration(Object element) { + PreviewDecoration decoration = new PreviewDecoration(); + fHelper.decorate(decoration, (PreviewResource) element); + return decoration; + } + } + + private static class PreviewResource implements IDecoratableResource { + public final String name; + + public final int type; + + public Collection children; + + public PreviewResource(String name, int type) { + this.name = name; + this.type = type; + this.children = Collections.EMPTY_LIST; + } + + public String getName() { + return name; + } + + public int getType() { + return type; + } + } + + private class PreviewDecoration implements IDecoration { + + private List<String> prefixes = new ArrayList<String>(); + + private List<String> suffixes = new ArrayList<String>(); + + private ImageDescriptor overlay = null; + + private Font font; + + private Color backgroundColor; + + private Color foregroundColor; + + public void addOverlay(ImageDescriptor overlayImage) { + overlay = overlayImage; + } + + public void addOverlay(ImageDescriptor overlayImage, int quadrant) { + overlay = overlayImage; + } + + public void addPrefix(String prefix) { + prefixes.add(prefix); + } + + public void addSuffix(String suffix) { + suffixes.add(suffix); + } + + public IDecorationContext getDecorationContext() { + return new DecorationContext(); + } + + public void setBackgroundColor(Color color) { + backgroundColor = color; + } + + public void setForegroundColor(Color color) { + foregroundColor = color; + } + + public void setFont(Font font) { + this.font = font; + } + + public ImageDescriptor getOverlay() { + return overlay; + } + + public String getPrefix() { + StringBuffer sb = new StringBuffer(); + for (Iterator<String> iter = prefixes.iterator(); iter.hasNext();) { + sb.append(iter.next()); + } + return sb.toString(); + } + + public String getSuffix() { + StringBuffer sb = new StringBuffer(); + for (Iterator<String> iter = suffixes.iterator(); iter.hasNext();) { + sb.append(iter.next()); + } + return sb.toString(); + } + + public Font getFont() { + return font; + } + + public Color getBackgroundColor() { + return backgroundColor; + } + + public Color getForegroundColor() { + return foregroundColor; + } + } +} diff --git a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties index a86e58b..4a0a387 100644 --- a/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties +++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/uitext.properties @@ -169,7 +169,6 @@ RefSpecPage_annotatedTagsAutoFollow=Automatically follow tags if we fetch the th RefSpecPage_annotatedTagsFetchTags=Always fetch tags, even if we do not have the thing it points at RefSpecPage_annotatedTagsNoTags=Never fetch tags, even if we have the thing it points at -Decorator_failedLazyLoading=Resource decorator failed to load tree contents on demand. QuickDiff_failedLoading=Quick diff failed to obtain file data. ResourceHistory_toggleCommentWrap=Wrap Comments @@ -340,3 +339,27 @@ BranchSelectionDialog_ResetTypeMixed=&Mixed (working directory unmodified) BranchSelectionDialog_ResetTypeSoft=&Soft (Index and working directory unmodified) BranchSelectionDialog_Tags=Tags BranchSelectionDialog_Refs=&Refs + +Decorator_exceptionMessage=Errors occurred while applying Git decorations to resources. + +DecoratorPreferencesPage_addVariablesTitle=Add Variables +DecoratorPreferencesPage_addVariablesAction=Add &Variables... +DecoratorPreferencesPage_computeDeep=Include &ancestors when re-decorating changed resources +DecoratorPreferencesPage_description=Shows Git specific information on resources in projects under version control. + +DecoratorPreferencesPage_decorationSettings=Decoration &settings: +DecoratorPreferencesPage_preview=Preview: +DecoratorPreferencesPage_fileFormatLabel=&Files: +DecoratorPreferencesPage_folderFormatLabel=F&olders: +DecoratorPreferencesPage_projectFormatLabel=&Projects: +DecoratorPreferencesPage_fileFormatDefault={name} +DecoratorPreferencesPage_folderFormatDefault={name} +DecoratorPreferencesPage_projectFormatDefault={name} +DecoratorPreferencesPage_labelDecorationsLink=See <a>''{0}''</a> to enable or disable Git decorations. +DecoratorPreferencesPage_generalTabFolder=&General +DecoratorPreferencesPage_nameResourceVariable=name of the resource being decorated +DecoratorPreferencesPage_selectFormats=Select the format for file, folders, and project text labels: +DecoratorPreferencesPage_selectVariablesToAdd=Select the &variables to add to the decoration format: +DecoratorPreferencesPage_textLabel=T&ext Decorations +DecoratorPreferencesPage_iconLabel=&Icon Decorations + -- 1.6.1.2.309.g2ea3 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html