/* * Copyright (c) 2008 Stiftung Deutsches Elektronen-Synchrotron, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY. * * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS. * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION, * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM */ package org.csstudio.sds.cursorservice; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.csstudio.sds.SdsPlugin; import org.csstudio.sds.internal.preferences.PreferenceConstants; import org.csstudio.sds.model.AbstractWidgetModel; 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.IResourceVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.swt.SWT; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Service for using mouse cursors in SDS displays. * * @author Sven Wende, Joerg Rathlev */ public final class CursorService implements ICursorService { /** * The supported file extensions for workspace cursors. */ private static final Set<String> SUPPORTED_FILE_EXTENSIONS; /** * The default cursor selection rule. */ private static final CursorSelectionRule DEFAULT_RULE = new SystemCursorOnlyRule(); /** * Descriptor for the default rule. */ private static final RuleDescriptor DEFAULT_RULE_DESCRIPTOR = new RuleDescriptor( DEFAULT_RULE_ID, "System cursor only", Collections.<CursorState>emptyList(), null); static { SUPPORTED_FILE_EXTENSIONS = new HashSet<String>(); SUPPORTED_FILE_EXTENSIONS.add("gif"); SUPPORTED_FILE_EXTENSIONS.add("jpg"); SUPPORTED_FILE_EXTENSIONS.add("png"); } /** * The singleton instance. */ private static ICursorService _instance; /** * The cursor descriptors. */ private List<AbstractCursor> _cursors; /** * The cursor strategy descriptors. */ private List<RuleDescriptor> _ruleDescriptors; /** * The current cursor preferences. */ private CursorSettings _preferences; private static final Logger LOG = LoggerFactory.getLogger(CursorService.class); /** * Returns the singleton instance. * * @return the singleton instance */ public static synchronized ICursorService getInstance() { if (_instance == null) { _instance = new CursorService(); } return _instance; } /** * Constructor. */ private CursorService() { _cursors = new ArrayList<AbstractCursor>(); addSystemCursors(); addExtensionCursors(); _ruleDescriptors = new ArrayList<RuleDescriptor>(); addDefaultCursorSelectionRule(); addExtensionCursorSelectionRules(); loadPreferences(); } /** * Returns a list of the available cursors. * * @return a list of the available cursors. */ @Override public List<AbstractCursor> availableCursors() { List<AbstractCursor> result = new ArrayList<AbstractCursor>(); result.addAll(_cursors); result.addAll(getWorkSpaceCursors()); return result; } /** * Returns an unmodifiable list of the available cursor selection rules. * * @return an unmodifiable list of rule descriptors for the available cursor * selection rules. */ @Override public List<RuleDescriptor> availableRules() { return Collections.unmodifiableList(_ruleDescriptors); } /** * Applies the cursor to the given widget based on the selection rule * configured by the user. * * @param widget * the widget. */ @Override public void applyCursor(final AbstractWidgetModel widget) { RuleDescriptor ruleDesc = getPreferredRule(); CursorSelectionRule rule = executableRule(ruleDesc); String stateId = rule.determineState(widget); String cursorId = cursorIdFromState(ruleDesc, stateId); if (cursorId != null) { widget.setCursorId(cursorId); } } /** * Returns the executable rule described by the specified descriptor. * * @param ruleDesc * the rule descriptor. * @return the executable rule. If an executable rule for the specified * descriptor cannot be created, this method returns the default * rule. */ private CursorSelectionRule executableRule(final RuleDescriptor ruleDesc) { IConfigurationElement config = ruleDesc.configurationElement(); CursorSelectionRule rule = null; if (config != null && config.isValid()) { try { rule = (CursorSelectionRule) config.createExecutableExtension("class"); } catch (CoreException e) { rule = null; } } if (rule == null) { rule = DEFAULT_RULE; } return rule; } /** * Determines the cursor id from the given cursor state. * * @param rule * the cursor selection rule. * @param stateId * the ID of the state. * @return the cursor id. */ private String cursorIdFromState(final RuleDescriptor rule, final String stateId) { String cursorId = null; if (stateId != null) { CursorState state = rule.state(stateId); AbstractCursor cursor = _preferences.getCursor(rule, state); if (cursor == null) { cursor = ICursorService.SYSTEM_DEFAULT_CURSOR; } cursorId = cursor.getIdentifier(); } return cursorId; } /** * Returns the preferred rule. * * @return a descriptor of the preferred rule. */ @Override public RuleDescriptor getPreferredRule() { RuleDescriptor result = null; String id = Platform.getPreferencesService().getString( SdsPlugin.getDefault().getBundle().getSymbolicName(), PreferenceConstants.CURSOR_SELECTION_RULE, DEFAULT_RULE_ID, null); if (id != null) { result = findRuleDescriptor(id); } return result; } /** * Sets the preferred cursor selection rule. The preferred rule is stored in * the preference store. * * @param rule * the preferred rule. */ @Override public void setPreferredRule(final RuleDescriptor rule) { IEclipsePreferences node = new InstanceScope().getNode(SdsPlugin.PLUGIN_ID); node.put(PreferenceConstants.CURSOR_SELECTION_RULE, rule.getId()); } /** * Finds the rule with the specified id. * * @param id * the id * @return the rule descriptor. If the rule was not found, returns the * descriptor of the default rule. */ private RuleDescriptor findRuleDescriptor(final String id) { assert id != null; RuleDescriptor result = null; for (RuleDescriptor d : _ruleDescriptors) { if (d.getId().equalsIgnoreCase(id)) { result = d; } } if (result == null) { result = defaultRuleDescriptor(); } return result; } /** * Returns a descriptor for the default cursor selection rule. * * @return a descriptor for the default cursor selection rule. */ private RuleDescriptor defaultRuleDescriptor() { return DEFAULT_RULE_DESCRIPTOR; } /** * Find the cursor with the specified id. * * @param id * the id. * @return a cursor, or <code>null</code> if none was found for that id. */ @Override public AbstractCursor findCursor(final String id) { assert id != null; AbstractCursor result = null; for (AbstractCursor d : availableCursors()) { if (d.getIdentifier().equalsIgnoreCase(id)) { result = d; } } return result; } /** * Returns the current cursor preferences. Changes in the returned object * are not immediately reflected in the preference store. Use the * {@link #setPreferences(CursorSettings)} method to change the stored * preferences. * * @return the current cursor settings from the preferences. */ @Override public CursorSettings getPreferences() { return new CursorSettings(_preferences); } /** * Sets the cursor preferences to the specified settings. * * @param settings * the settings. */ @Override public void setPreferences(final CursorSettings settings) { if (settings == null) { throw new NullPointerException(); } _preferences = new CursorSettings(settings); storePreferences(); } /** * Loads the cursor settings from the preference store. */ private void loadPreferences() { _preferences = new CursorSettings(_ruleDescriptors); /* * The structure of the preferences: there is a node for the cursor * preferences. Below that node, there is one node for each cursor * selection rule, and below that node, one preference for each cursor * state. * * Example: * * <SdsPlugin.PLUGIN_ID> * + <PreferenceConstants.CURSOR_SETTINGS> * + de.desy.DesyCursorStrategy * | + default * | + disabled * | + enabled * | + ... * + org.example.OtherCursorStrategy * + enabled * + disabled */ Preferences cursorSettings = new InstanceScope(). getNode(SdsPlugin.PLUGIN_ID). node(PreferenceConstants.CURSOR_SETTINGS); for (RuleDescriptor rule : _ruleDescriptors) { try { if (cursorSettings.nodeExists(rule.getId())) { Preferences ruleNode = cursorSettings.node(rule.getId()); for (String stateId : ruleNode.keys()) { CursorState state = rule.state(stateId); String cursorId = ruleNode.get(stateId, null); if (state != null && cursorId != null) { AbstractCursor cursor = findCursor(cursorId); if (cursor != null) { _preferences.setCursor(rule, state, cursor); } } } } } catch (BackingStoreException e) { LOG.warn("BackingStoreException while reading preferences", e); } } } /** * Stores the cursor settings in the preference store. */ private void storePreferences() { Preferences cursorSettings = new InstanceScope(). getNode(SdsPlugin.PLUGIN_ID). node(PreferenceConstants.CURSOR_SETTINGS); for (RuleDescriptor rule : _ruleDescriptors) { Preferences ruleNode = cursorSettings.node(rule.getId()); for (CursorState state : rule.cursorStates()) { AbstractCursor cursor = _preferences.getCursor(rule, state); if (cursor == null) { cursor = ICursorService.SYSTEM_DEFAULT_CURSOR; } ruleNode.put(state.getId(), cursor.getIdentifier()); } } } /** * Adds the cursors contributed via the {@code cursors} extension point to * the list of available cursors. */ private void addExtensionCursors() { IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry(); IConfigurationElement[] cursorConfigurationElements = extensionRegistry.getConfigurationElementsFor(SdsPlugin.EXPOINT_CURSORS); for (IConfigurationElement element : cursorConfigurationElements) { String bundle = element.getContributor().getName(); String id = element.getAttribute("id"); String name = element.getAttribute("name"); String image = element.getAttribute("image"); _cursors.add(new ContributedCursor(id, name, bundle, image)); } } /** * Adds the cursor selection rules contributed via the * {@code cursorSelectionRules} extension point to the list of available * cursor selection rules. */ private void addExtensionCursorSelectionRules() { IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry(); IConfigurationElement[] ruleConfigurationElements = extensionRegistry.getConfigurationElementsFor(SdsPlugin.EXTPOINT_CURSOR_SELECTION_RULES); for (IConfigurationElement element : ruleConfigurationElements) { String id = element.getAttribute("id"); String label = element.getAttribute("label"); if (label == null) { label = id; } Collection<CursorState> states = readCursorStates(element); RuleDescriptor rule = new RuleDescriptor(id, label, states, element); _ruleDescriptors.add(rule); } } /** * Reads the cursor states of a rule from the extension registry. * * @param rule * the configuration element which declares the rule. * @return a collection of the cursor states declared for the rule. */ private Collection<CursorState> readCursorStates(final IConfigurationElement rule) { Collection<CursorState> result = new ArrayList<CursorState>(); IConfigurationElement[] stateConfigs = rule.getChildren("state"); for (IConfigurationElement stateElement : stateConfigs) { String stateId = stateElement.getAttribute("id"); String stateLabel = stateElement.getAttribute("label"); if (stateLabel == null) { stateLabel = stateId; } CursorState state = new CursorState(stateId, stateLabel); result.add(state); } return result; } /** * Adds the default cursor selection rule to the list of available rules. */ private void addDefaultCursorSelectionRule() { _ruleDescriptors.add(DEFAULT_RULE_DESCRIPTOR); } /** * Adds the system cursors to the list of available cursors. */ private void addSystemCursors() { _cursors.add(SYSTEM_DEFAULT_CURSOR); _cursors.add(new SWTCursor("cursor.system.arrow", "Arrow (System)", SWT.CURSOR_ARROW)); _cursors.add(new SWTCursor("cursor.system.appStart", "Application Startup (System)", SWT.CURSOR_APPSTARTING)); _cursors.add(new SWTCursor("cursor.system.cross", "Cross Hair (System)", SWT.CURSOR_CROSS)); _cursors.add(new SWTCursor("cursor.system.hand", "Hand (System)", SWT.CURSOR_HAND)); _cursors.add(new SWTCursor("cursor.system.help", "Help (System)", SWT.CURSOR_HELP)); _cursors.add(new SWTCursor("cursor.system.ibeam", "i-beam (System)", SWT.CURSOR_IBEAM)); _cursors.add(new SWTCursor("cursor.system.no", "not allowed (System)", SWT.CURSOR_NO)); _cursors .add(new SWTCursor("cursor.system.resizeall", "Resize all directions (System)", SWT.CURSOR_SIZEALL)); _cursors.add(new SWTCursor("cursor.system.uparrow", "Up Arrow (System)", SWT.CURSOR_UPARROW)); _cursors.add(new SWTCursor("cursor.system.wait", "Wait (System)", SWT.CURSOR_WAIT)); } /** * Lookup cursors in the workspace. * * @return a list of cursors found in the workspace. */ private List<AbstractCursor> getWorkSpaceCursors() { final List<AbstractCursor> cursors = new ArrayList<AbstractCursor>(); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject( CURSORS_PROJECT_NAME); if (project != null && project.isOpen()) { try { project.accept(new IResourceVisitor() { @Override public boolean visit(final IResource resource) throws CoreException { if (resource instanceof IFile && SUPPORTED_FILE_EXTENSIONS.contains(resource .getFileExtension())) { IPath path = resource.getProjectRelativePath(); AbstractCursor descriptor = new WorkspaceCursor( path.toPortableString(), path .lastSegment() + " (Workspace local)", resource .getLocation().toPortableString()); cursors.add(descriptor); } return resource instanceof IContainer; } }); } catch (CoreException e) { LOG.warn(e.toString()); } } return cursors; } }