/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation * Jan-Hendrik Diederich, Bredex GmbH - bug 201052 *******************************************************************************/ package org.eclipse.ui.internal.registry; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler; import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.StringConverter; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPerspectiveDescriptor; import org.eclipse.ui.IPerspectiveRegistry; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPreferenceConstants; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.WorkbenchException; import org.eclipse.ui.XMLMemento; import org.eclipse.ui.activities.WorkbenchActivityHelper; import org.eclipse.ui.internal.IPreferenceConstants; import org.eclipse.ui.internal.Workbench; import org.eclipse.ui.internal.WorkbenchMessages; import org.eclipse.ui.internal.WorkbenchPage; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.handlers.ClosePerspectiveHandler; import org.eclipse.ui.internal.misc.StatusUtil; import org.eclipse.ui.internal.util.PrefUtil; import org.eclipse.ui.statushandlers.StatusManager; /** * Perspective registry. */ public class PerspectiveRegistry implements IPerspectiveRegistry, IExtensionChangeHandler { private String defaultPerspID; private static final String EXT = "_persp.xml"; //$NON-NLS-1$ private static final String ID_DEF_PERSP = "PerspectiveRegistry.DEFAULT_PERSP"; //$NON-NLS-1$ private static final String PERSP = "_persp"; //$NON-NLS-1$ private static final char SPACE_DELIMITER = ' '; private List perspectives = new ArrayList(10); // keep track of the perspectives the user has selected to remove or revert private ArrayList perspToRemove = new ArrayList(5); private IPropertyChangeListener preferenceListener; /** * Construct a new registry. */ public PerspectiveRegistry() { IExtensionTracker tracker = PlatformUI.getWorkbench() .getExtensionTracker(); tracker.registerHandler(this, null); initializePreferenceChangeListener(); WorkbenchPlugin.getDefault().getPreferenceStore() .addPropertyChangeListener(preferenceListener); } /** * Initialize the preference change listener. */ private void initializePreferenceChangeListener() { preferenceListener = new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { /* * To ensure the that no custom perspective definitions are * deleted when preferences are imported, merge old and new * values */ if (event.getProperty().endsWith(PERSP)) { /* A Perspective is being changed, merge */ mergePerspectives(event); } else if (event.getProperty().equals( IPreferenceConstants.PERSPECTIVES)) { /* The list of perpsectives is being changed, merge */ updatePreferenceList((IPreferenceStore) event.getSource()); } } private void mergePerspectives(PropertyChangeEvent event) { IPreferenceStore store = (IPreferenceStore) event.getSource(); if (event.getNewValue() == null || event.getNewValue().equals("")) { //$NON-NLS-1$ /* * Perpsective is being removed; if the user has deleted or * reverted a custom perspective, let the change pass * through. Otherwise, restore the custom perspective entry */ // Find the matching descriptor in the registry IPerspectiveDescriptor[] perspectiveList = getPerspectives(); for (int i = 0; i < perspectiveList.length; i++) { String id = perspectiveList[i].getId(); if (event.getProperty().equals(id + PERSP)) { // found // descriptor // see if the perspective has been flagged for // reverting or deleting if (!perspToRemove.contains(id)) { // restore store.setValue(id + PERSP, (String) event .getOldValue()); } else { // remove element from the list perspToRemove.remove(id); } } } } else if ((event.getOldValue() == null || event.getOldValue() .equals(""))) { //$NON-NLS-1$ /* * New perspective is being added, update the * perspectiveRegistry to contain the new custom perspective */ String id = event.getProperty().substring(0, event.getProperty().lastIndexOf(PERSP)); if (findPerspectiveWithId(id) == null) { // perspective does not already exist in registry, add // it PerspectiveDescriptor desc = new PerspectiveDescriptor( null, null, null); StringReader reader = new StringReader((String) event .getNewValue()); try { XMLMemento memento = XMLMemento .createReadRoot(reader); desc.restoreState(memento); addPerspective(desc); } catch (WorkbenchException e) { unableToLoadPerspective(e.getStatus()); } } } /* If necessary, add to the list of perspectives */ updatePreferenceList(store); } /* * Update the list of perspectives from the registry. This will be * called for each perspective during an import preferences, but is * necessary to ensure the perspectives list stays consistent with * the registry */ private void updatePreferenceList(IPreferenceStore store) { IPerspectiveDescriptor[] perspectiveList = getPerspectives(); StringBuffer perspBuffer = new StringBuffer(); for (int i = 0; i < perspectiveList.length; i++) { PerspectiveDescriptor desc = (PerspectiveDescriptor) perspectiveList[i]; if (hasCustomDefinition(desc)) { perspBuffer.append(desc.getId()) .append(SPACE_DELIMITER); } } String newList = perspBuffer.toString().trim(); store.setValue(IPreferenceConstants.PERSPECTIVES, newList); } }; } /** * Adds a perspective. This is typically used by the reader. * * @param desc */ public void addPerspective(PerspectiveDescriptor desc) { if (desc == null) { return; } add(desc); } /** * @param desc */ private void add(PerspectiveDescriptor desc) { perspectives.add(desc); IConfigurationElement element = desc.getConfigElement(); if (element != null) { PlatformUI.getWorkbench().getExtensionTracker().registerObject( element.getDeclaringExtension(), desc, IExtensionTracker.REF_WEAK); } } /** * Create a new perspective. * * @param label * the name of the new descriptor * @param originalDescriptor * the descriptor on which to base the new descriptor * @return a new perspective descriptor or <code>null</code> if the * creation failed. */ public PerspectiveDescriptor createPerspective(String label, PerspectiveDescriptor originalDescriptor) { // Sanity check to avoid invalid or duplicate labels. if (!validateLabel(label)) { return null; } if (findPerspectiveWithLabel(label) != null) { return null; } // Calculate ID. String id = label.replace(' ', '_'); id = id.trim(); // Create descriptor. PerspectiveDescriptor desc = new PerspectiveDescriptor(id, label, originalDescriptor); add(desc); return desc; } /** * Reverts a list of perspectives back to the plugin definition * * @param perspToRevert */ public void revertPerspectives(ArrayList perspToRevert) { // indicate that the user is removing these perspectives for (int i = 0; i < perspToRevert.size(); i++) { PerspectiveDescriptor desc = (PerspectiveDescriptor) perspToRevert .get(i); perspToRemove.add(desc.getId()); desc.revertToPredefined(); } } /** * Deletes a list of perspectives * * @param perspToDelete */ public void deletePerspectives(ArrayList perspToDelete) { for (int i = 0; i < perspToDelete.size(); i++) { deletePerspective((IPerspectiveDescriptor) perspToDelete.get(i)); } } /** * Delete a perspective. Has no effect if the perspective is defined in an * extension. * * @param in */ public void deletePerspective(IPerspectiveDescriptor in) { PerspectiveDescriptor desc = (PerspectiveDescriptor) in; // Don't delete predefined perspectives if (!desc.isPredefined()) { perspToRemove.add(desc.getId()); perspectives.remove(desc); desc.deleteCustomDefinition(); verifyDefaultPerspective(); } } /** * Delete a perspective. This will remove perspectives defined in * extensions. * * @param desc * the perspective to delete * @since 3.1 */ private void internalDeletePerspective(PerspectiveDescriptor desc) { perspToRemove.add(desc.getId()); perspectives.remove(desc); desc.deleteCustomDefinition(); verifyDefaultPerspective(); } /** * Removes the custom definition of a perspective from the preference store * * @param desc */ /* package */ void deleteCustomDefinition(PerspectiveDescriptor desc) { // remove the entry from the preference store. IPreferenceStore store = WorkbenchPlugin.getDefault() .getPreferenceStore(); /* * To delete the perspective definition from the preference store, use * the setToDefault method. Since no default is defined, this will * remove the entry */ store.setToDefault(desc.getId() + PERSP); } /** * Method hasCustomDefinition. * * @param desc */ /* package */ boolean hasCustomDefinition(PerspectiveDescriptor desc) { IPreferenceStore store = WorkbenchPlugin.getDefault() .getPreferenceStore(); return store.contains(desc.getId() + PERSP); } /* * (non-Javadoc) * * @see org.eclipse.ui.IPerspectiveRegistry#findPerspectiveWithId(java.lang.String) */ public IPerspectiveDescriptor findPerspectiveWithId(String id) { for (Iterator i = perspectives.iterator(); i.hasNext();) { PerspectiveDescriptor desc = (PerspectiveDescriptor) i.next(); if (desc.getId().equals(id)) { if (WorkbenchActivityHelper.restrictUseOf(desc)) { return null; } return desc; } } return null; } /* * (non-Javadoc) * * @see org.eclipse.ui.IPerspectiveRegistry#findPerspectiveWithLabel(java.lang.String) */ public IPerspectiveDescriptor findPerspectiveWithLabel(String label) { for (Iterator i = perspectives.iterator(); i.hasNext();) { PerspectiveDescriptor desc = (PerspectiveDescriptor) i.next(); if (desc.getLabel().equals(label)) { if (WorkbenchActivityHelper.restrictUseOf(desc)) { return null; } return desc; } } return null; } /** * @see IPerspectiveRegistry#getDefaultPerspective() */ public String getDefaultPerspective() { return defaultPerspID; } /* * (non-Javadoc) * * @see org.eclipse.ui.IPerspectiveRegistry#getPerspectives() */ public IPerspectiveDescriptor[] getPerspectives() { Collection descs = WorkbenchActivityHelper.restrictCollection(perspectives, new ArrayList()); return (IPerspectiveDescriptor[]) descs.toArray(new IPerspectiveDescriptor[descs.size()]); } /** * Loads the registry. */ public void load() { // Load the registries. loadPredefined(); loadCustom(); // Get default perspective. // Get it from the R1.0 dialog settings first. Fixes bug 17039 IDialogSettings dialogSettings = WorkbenchPlugin.getDefault() .getDialogSettings(); String str = dialogSettings.get(ID_DEF_PERSP); if (str != null && str.length() > 0) { setDefaultPerspective(str); dialogSettings.put(ID_DEF_PERSP, ""); //$NON-NLS-1$ } verifyDefaultPerspective(); } /** * Read children from the file system. */ private void loadCustom() { Reader reader = null; /* Get the entries from the Preference store */ IPreferenceStore store = WorkbenchPlugin.getDefault() .getPreferenceStore(); /* Get the space-delimited list of custom perspective ids */ String customPerspectives = store .getString(IPreferenceConstants.PERSPECTIVES); String[] perspectivesList = StringConverter.asArray(customPerspectives); for (int i = 0; i < perspectivesList.length; i++) { try { String xmlString = store.getString(perspectivesList[i] + PERSP); if (xmlString != null && xmlString.length() != 0) { reader = new StringReader(xmlString); } // Restore the layout state. XMLMemento memento = XMLMemento.createReadRoot(reader); PerspectiveDescriptor newPersp = new PerspectiveDescriptor( null, null, null); newPersp.restoreState(memento); String id = newPersp.getId(); IPerspectiveDescriptor oldPersp = findPerspectiveWithId(id); if (oldPersp == null) { add(newPersp); } reader.close(); } catch (IOException e) { unableToLoadPerspective(null); } catch (WorkbenchException e) { unableToLoadPerspective(e.getStatus()); } } // Get the entries from files, if any // if -data @noDefault specified the state location may not be // initialized IPath path = WorkbenchPlugin.getDefault().getDataLocation(); if (path == null) { return; } File folder = path.toFile(); if (folder.isDirectory()) { File[] fileList = folder.listFiles(); int nSize = fileList.length; for (int nX = 0; nX < nSize; nX++) { File file = fileList[nX]; if (file.getName().endsWith(EXT)) { // get the memento InputStream stream = null; try { stream = new FileInputStream(file); reader = new BufferedReader(new InputStreamReader( stream, "utf-8")); //$NON-NLS-1$ // Restore the layout state. XMLMemento memento = XMLMemento.createReadRoot(reader); PerspectiveDescriptor newPersp = new PerspectiveDescriptor( null, null, null); newPersp.restoreState(memento); IPerspectiveDescriptor oldPersp = findPerspectiveWithId(newPersp .getId()); if (oldPersp == null) { add(newPersp); } // save to the preference store saveCustomPersp(newPersp, memento); // delete the file file.delete(); reader.close(); stream.close(); } catch (IOException e) { unableToLoadPerspective(null); } catch (WorkbenchException e) { unableToLoadPerspective(e.getStatus()); } } } } } /** * @param status */ private void unableToLoadPerspective(IStatus status) { String msg = WorkbenchMessages.Perspective_errorLoadingState; if (status == null) { IStatus errStatus = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, msg); StatusManager.getManager().handle(errStatus, StatusManager.SHOW); } else { IStatus errStatus = StatusUtil.newStatus(status, msg); StatusManager.getManager().handle(errStatus, StatusManager.SHOW); } } /** * Saves a custom perspective definition to the preference store. * * @param desc * the perspective * @param memento * the memento to save to * @throws IOException */ public void saveCustomPersp(PerspectiveDescriptor desc, XMLMemento memento) throws IOException { IPreferenceStore store = WorkbenchPlugin.getDefault() .getPreferenceStore(); // Save it to the preference store. Writer writer = new StringWriter(); memento.save(writer); writer.close(); store.setValue(desc.getId() + PERSP, writer.toString()); } /** * Gets the Custom perspective definition from the preference store. * * @param id * the id of the perspective to find * @return IMemento a memento containing the perspective description * * @throws WorkbenchException * @throws IOException */ public IMemento getCustomPersp(String id) throws WorkbenchException, IOException { Reader reader = null; IPreferenceStore store = WorkbenchPlugin.getDefault() .getPreferenceStore(); String xmlString = store.getString(id + PERSP); if (xmlString != null && xmlString.length() != 0) { // defined in store reader = new StringReader(xmlString); } XMLMemento memento = XMLMemento.createReadRoot(reader); reader.close(); return memento; } /** * Read children from the plugin registry. */ private void loadPredefined() { PerspectiveRegistryReader reader = new PerspectiveRegistryReader(this); reader.readPerspectives(Platform.getExtensionRegistry()); } /** * @see IPerspectiveRegistry#setDefaultPerspective(String) */ public void setDefaultPerspective(String id) { IPerspectiveDescriptor desc = findPerspectiveWithId(id); if (desc != null) { defaultPerspID = id; PrefUtil.getAPIPreferenceStore().setValue( IWorkbenchPreferenceConstants.DEFAULT_PERSPECTIVE_ID, id); } } /** * Return <code>true</code> if a label is valid. This checks only the * given label in isolation. It does not check whether the given label is * used by any existing perspectives. * * @param label * the label to test * @return whether the label is valid */ public boolean validateLabel(String label) { label = label.trim(); if (label.length() <= 0) { return false; } return true; } /** * Verifies the id of the default perspective. If the default perspective is * invalid use the workbench default. */ private void verifyDefaultPerspective() { // Step 1: Try current defPerspId value. IPerspectiveDescriptor desc = null; if (defaultPerspID != null) { desc = findPerspectiveWithId(defaultPerspID); } if (desc != null) { return; } // Step 2. Read default value. String str = PrefUtil.getAPIPreferenceStore().getString( IWorkbenchPreferenceConstants.DEFAULT_PERSPECTIVE_ID); if (str != null && str.length() > 0) { desc = findPerspectiveWithId(str); } if (desc != null) { defaultPerspID = str; return; } // Step 3. Use application-specific default defaultPerspID = Workbench.getInstance().getDefaultPerspectiveId(); } /* * (non-Javadoc) * * @see org.eclipse.ui.IPerspectiveRegistry#clonePerspective(java.lang.String, * java.lang.String, org.eclipse.ui.IPerspectiveDescriptor) */ public IPerspectiveDescriptor clonePerspective(String id, String label, IPerspectiveDescriptor originalDescriptor) { // Check for invalid labels if (label == null || !(label.trim().length() > 0)) { throw new IllegalArgumentException(); } // Check for duplicates IPerspectiveDescriptor desc = findPerspectiveWithId(id); if (desc != null) { throw new IllegalArgumentException(); } // Create descriptor. desc = new PerspectiveDescriptor(id, label, (PerspectiveDescriptor) originalDescriptor); add((PerspectiveDescriptor) desc); return desc; } /* * (non-Javadoc) * * @see org.eclipse.ui.IPerspectiveRegistry#revertPerspective(org.eclipse.ui.IPerspectiveDescriptor) */ public void revertPerspective(IPerspectiveDescriptor perspToRevert) { PerspectiveDescriptor desc = (PerspectiveDescriptor) perspToRevert; perspToRemove.add(desc.getId()); desc.revertToPredefined(); } /** * Dispose the receiver. */ public void dispose() { PlatformUI.getWorkbench().getExtensionTracker().unregisterHandler(this); WorkbenchPlugin.getDefault().getPreferenceStore() .removePropertyChangeListener(preferenceListener); } /* * (non-Javadoc) * * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension, * java.lang.Object[]) */ public void removeExtension(IExtension source, Object[] objects) { for (int i = 0; i < objects.length; i++) { if (objects[i] instanceof PerspectiveDescriptor) { // close the perspective in all windows IWorkbenchWindow[] windows = PlatformUI.getWorkbench() .getWorkbenchWindows(); PerspectiveDescriptor desc = (PerspectiveDescriptor) objects[i]; for (int w = 0; w < windows.length; ++w) { IWorkbenchWindow window = windows[w]; IWorkbenchPage[] pages = window.getPages(); for (int p = 0; p < pages.length; ++p) { WorkbenchPage page = (WorkbenchPage) pages[p]; ClosePerspectiveHandler.closePerspective(page, page .findPerspective(desc)); } } // ((Workbench)PlatformUI.getWorkbench()).getPerspectiveHistory().removeItem(desc); internalDeletePerspective(desc); } } } /* * (non-Javadoc) * * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamicHelpers.IExtensionTracker, * org.eclipse.core.runtime.IExtension) */ public void addExtension(IExtensionTracker tracker, IExtension addedExtension) { IConfigurationElement[] addedElements = addedExtension .getConfigurationElements(); for (int i = 0; i < addedElements.length; i++) { PerspectiveRegistryReader reader = new PerspectiveRegistryReader( this); reader.readElement(addedElements[i]); } } }