/******************************************************************************* * Copyright (c) 2012 Arapiki Solutions Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * "Peter Smith <psmith@arapiki.com>" - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.eclipse; import java.io.File; import java.io.IOException; import java.net.URL; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.IElementComparer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.part.EditorPart; import com.buildml.eclipse.bobj.UIInteger; import com.buildml.eclipse.preferences.PreferenceConstants; import com.buildml.eclipse.utils.AlertDialog; import com.buildml.model.IBuildStore; import com.buildml.model.types.PackageSet; import com.buildml.utils.os.SystemUtils; import com.buildml.utils.types.IntegerTreeRecord; import com.buildml.utils.types.IntegerTreeSet; /** * An abstract class that BuildML "import" editor tabs (such as FilesEditor and ActionsEditor) * should be derived from. Editors of this type can be placed within a tab of the top-level * BuildML. They share common features, such as option settings, item visibility, and * the ability to filter based on a selected set of packages. * * @author "Peter Smith <psmith@arapiki.com>" */ public abstract class ImportSubEditor extends EditorPart implements IElementComparer, ISubEditor { /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ /** The BuildStore we're editing */ protected IBuildStore buildStore = null; /** * The set of packages to be displayed (that is, files will be displayed * if they belong to one of these packages). */ private PackageSet filterPackageSet; /** * The current options setting for this editor. The field contains a bitmap of * OPT_* values from the EditorOptions interface. */ private int editorOptionBits = 0; /** * If a new editor tab is created with fewer than this many visible tree entries, * we should auto-expand the entire tree so that all elements are visible. If there * are more than this many elements, only expand the first couple of levels. */ protected static final int AUTO_EXPAND_THRESHOLD = 200; /** * Indicates whether this editor is "removable". That is, can the user close the * tab (the default is "true"), or is this tab permanently fixed to the editor (false) */ private boolean removable = true; /** * Set to true the first time the user is warned about overriding the BUILDML_PATH. * They should only see the message once per Eclipse invocation, so this field is static. */ private static boolean warnedAboutPathOverride = false; /** * Record whether or not we've been disposed. Nobody should be allowed to make this * sub-editor active if it's disposed. */ private boolean editorIsDisposed = false; /*=====================================================================================* * CONSTRUCTOR *=====================================================================================*/ /** * Create a new SubEditor instance, using the specified BuildStore as input * @param buildStore The BuildStore to display/edit. * @param tabTitle The text to appear on the editor's tab. */ public ImportSubEditor(IBuildStore buildStore, String tabTitle) { super(); /* set the name of the tab that this editor appears in */ setPartName(tabTitle); /* Save away our BuildStore information, for later use */ this.buildStore = buildStore; /* create a new package set so we can selectively filter out packages */ filterPackageSet = new PackageSet(buildStore); filterPackageSet.setDefault(true); } /*=====================================================================================* * PUBLIC METHODS *=====================================================================================*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#doSave(org.eclipse.core.runtime.IProgressMonitor) */ @Override public void doSave(IProgressMonitor monitor) { /* not implemented - is handled by MainEditor */ } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#doSaveAs() */ @Override public void doSaveAs() { /* not implemented - is handled by MainEditor */ } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#isDirty() */ @Override public boolean isDirty() { /* not implemented - is handled by MainEditor */ return false; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#isSaveAsAllowed() */ @Override public boolean isSaveAsAllowed() { /* not implemented - is handled by MainEditor */ return false; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#getEditorImage() */ @Override public Image getEditorImage() { /* ask the subeditor instance where its image is (if it has one) */ String path = getEditorImagePath(); if (path != null) { /* * Create a descriptor, and perhaps a new image, if it's not already * available in this plugin's image registry. */ ImageDescriptor imageDescr = Activator.getImageDescriptor(path); ImageRegistry pluginImageRegistry = Activator.getDefault().getImageRegistry(); Image iconImage = pluginImageRegistry.get(imageDescr.toString()); if (iconImage == null) { iconImage = imageDescr.createImage(); pluginImageRegistry.put(imageDescr.toString(), iconImage); } return iconImage; } /* no icon for this editor */ else { return null; } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#setOption(int, boolean) */ @Override public void setOption(int optionBits, boolean enable) { /* if enable is set, then we're adding the new options */ if (enable) { editorOptionBits |= optionBits; } /* else, we're clearing the options */ else { editorOptionBits &= ~optionBits; } /* The sub-editor may now need to react to the change in settings. */ updateEditorWithNewOptions(optionBits, enable); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#setOptions(int) */ @Override public void setOptions(int optionBits) { /* we call setOptions for each option, to ensure that side-effects are triggered */ for (int bitNum = 0; bitNum != EditorOptions.NUM_OPTIONS; bitNum++) { int thisBitMap = (1 << bitNum); /* explicitly enable or disable this option */ setOption(thisBitMap, (optionBits & thisBitMap) != 0); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#getOptions() */ @Override public int getOptions() { return editorOptionBits; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#isOptionSet(int) */ @Override public boolean isOptionSet(int optionBit) { return (editorOptionBits & optionBit) != 0; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#updateOptionsFromPreferenceStore() */ @Override public void updateOptionsFromPreferenceStore() { IPreferenceStore prefStore = Activator.getDefault().getPreferenceStore(); setOption(EditorOptions.OPT_COALESCE_DIRS, prefStore.getBoolean(PreferenceConstants.PREF_COALESCE_DIRS)); /* * Determine where the BuildML binaries and libraries are kept. By default we get * them from within the plugin jar file, although the user is permitted * to override that path. If they do, however, they should be warned. */ String buildMlPath = null; URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path("/files"), null); if (url != null) { try { URL filesDirUrl = FileLocator.toFileURL(url); buildMlPath = filesDirUrl.getPath(); } catch (IOException e) { /* nothing - buildMlPath stays null, which indicates an error */ } } /* * If we can't locate the /files directory within the plugin, that's likely because we're * running this plugin within the eclipse PDE (as opposed to the plugin being installed * and executed in the normal way). If this is the case, then buildMlPath == null. * * In any situation, the user is welcome to override the value of buildMlPath with their * own setting. However, if they're not using the plugin jar's copy of the files, we * should warn them. */ String prefBuildMlPath = prefStore.getString(PreferenceConstants.PREF_BUILDML_HOME); if (!prefBuildMlPath.isEmpty() && !warnedAboutPathOverride) { if (buildMlPath != null) { AlertDialog.displayWarningDialog("Overriding BUILDML_HOME setting", "Although the bin and lib directories have been found in the plugin jar file, " + "you have chosen to override the path. Please go into the BuildML preferences " + "if you wish to remove this override."); warnedAboutPathOverride = true; } buildMlPath = prefBuildMlPath; } /* * Check that the BUILDML_HOME preference is set, is a directory, and contains subdirectories * "lib" and "bin". */ if ((buildMlPath == null) || buildMlPath.isEmpty()) { AlertDialog.displayErrorDialog("Missing Preference Setting", "The preference setting: \"Directory containing BuildML's bin and lib directories\" " + "is not defined. Please go into the BuildML preferences and set a suitable value."); } else { File buildMlPathFile = new File(buildMlPath); if (!(buildMlPathFile.isDirectory()) || (!new File(buildMlPathFile, "bin").isDirectory()) || (!new File(buildMlPathFile, "lib").isDirectory())) { AlertDialog.displayErrorDialog("Invalid Preference Setting", "The preference setting: \"Directory containing BuildML's bin and lib directories\" " + "does not refer to a valid directory."); } /* * Else, the path is good. The only additional requirement is that the 'cfs' command * be executable. This won't be the case if the /files directory has just been * extracted into the Eclipse configuration directory for the first time. Note that * chmod can fail if the files aren't owned by the current user, but that's not a * problem for us. */ else { System.setProperty("BUILDML_HOME", buildMlPath); SystemUtils.chmod(buildMlPath + "/bin/cfs", 0755); } } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ @Override public void createPartControl(final Composite parent) { /* * Update this editor's option by reading the user-specified values in the * preference store. Also, attach a listener so that we hear about future * changes to the preference store and adjust our options accordingly. */ updateOptionsFromPreferenceStore(); Activator.getDefault().getPreferenceStore(). addPropertyChangeListener(preferenceStoreChangeListener); /* * Resizing the top-level shell causes columns to be realigned/redrawn. We need * to schedule this as a UI thread runnable, since we don't want it to run until * after the resizing has finished, at which point we know the new window size. * TODO: add a removeListener. */ parent.addControlListener(new ControlAdapter() { public void controlResized(ControlEvent e) { refreshView(false); } }); } /*-------------------------------------------------------------------------------------*/ /** * Listener to identify changes being made to this plug-in's preference store, typically * as part of editing the BuildMl preferences (this could change how our editor is displayed). */ private IPropertyChangeListener preferenceStoreChangeListener = new IPropertyChangeListener() { /** * Completely redraw the files editor tree, based on the new preference * settings. */ @Override public void propertyChange(PropertyChangeEvent event) { updateOptionsFromPreferenceStore(); } }; /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see org.eclipse.ui.part.WorkbenchPart#dispose() */ @Override public void dispose() { editorIsDisposed = true; /* remove this preference store listener */ Activator.getDefault().getPreferenceStore(). removePropertyChangeListener(preferenceStoreChangeListener); super.dispose(); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#getFilterPackageSet() */ @Override public PackageSet getFilterPackageSet() { return filterPackageSet; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#setFilterPackageSet(com.buildml.model.types.PackageSet) */ @Override public void setFilterPackageSet(PackageSet newSet) { filterPackageSet = newSet; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#setRemovable(boolean) */ @Override public void setRemovable(boolean removable) { this.removable = removable; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#isRemovable() */ @Override public boolean isRemovable() { return removable; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#isDisposed() */ @Override public boolean isDisposed() { return editorIsDisposed; } /*-------------------------------------------------------------------------------------*/ /** * This method is used by TreeViewers to compare elements in the tree for "sameness". * This is useful when refreshing a TreeViewer, to ensure that expanded elements stay * expanded after a refresh. */ @Override public boolean equals(Object a, Object b) { if (a == b) { return true; } /* * Ensure that both a and b have the same class, which must be a class that's * derived from UIInteger. */ if ((a instanceof UIInteger) && (a.getClass() == b.getClass())) { return ((UIInteger)a).getId() == ((UIInteger)b).getId(); } return false; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see org.eclipse.jface.viewers.IElementComparer#hashCode(java.lang.Object) */ @Override public int hashCode(Object element) { if (!(element instanceof IntegerTreeRecord)) { return 0; } return ((IntegerTreeRecord)element).getId(); } /*=====================================================================================* * ABSTRACT METHODS *=====================================================================================*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#expandSubtree(java.lang.Object) */ @Override public abstract void expandSubtree(Object node); /*-------------------------------------------------------------------------------------*/ /** * When a sub-editor's options are modified, this method is called so that the editor * can react appropriately to its now settings. * @param optionBits The option bit(s) that were changed. * @param enable True if the option(s) were added, else false. */ protected abstract void updateEditorWithNewOptions(int optionBits, boolean enable); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#refreshView(boolean) */ @Override public abstract void refreshView(boolean force); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#setVisibilityFilterSet(com.buildml.utils.types.IntegerTreeSet) */ @Override public abstract void setVisibilityFilterSet(IntegerTreeSet visibleActions); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#getVisibilityFilterSet() */ @Override public abstract IntegerTreeSet getVisibilityFilterSet(); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#setItemVisibilityState(java.lang.Object, boolean) */ @Override public abstract void setItemVisibilityState(Object item, boolean state); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#pageChange() */ @Override public abstract void pageChange(); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#hasFeature(java.lang.String) */ @Override public abstract boolean hasFeature(String feature); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#doCopyCommand(org.eclipse.swt.dnd.Clipboard, org.eclipse.jface.viewers.ISelection) */ @Override public abstract void doCopyCommand(Clipboard clipboard, ISelection selection); /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.eclipse.ISubEditor#getEditorImagePath() */ @Override public abstract String getEditorImagePath(); /*-------------------------------------------------------------------------------------*/ }