/******************************************************************************* * Copyright (c) 2006 Sybase, Inc. 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: * Sybase, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.jst.pagedesigner.ui.common.sash; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.draw2d.ColorConstants; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jst.jsf.common.ui.internal.guiutils.SWTUtils; import org.eclipse.jst.pagedesigner.PDPlugin; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.ui.IEditorActionBarContributor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IKeyBindingService; import org.eclipse.ui.INestableKeyBindingService; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.part.EditorPart; import org.eclipse.ui.part.MultiPageEditorActionBarContributor; import org.eclipse.ui.part.MultiPageEditorSite; /** * This class emulates the MultiPageEditorPart. But instead of using multipage, * it use SashForm to separate the editors. * * @author mengbo */ public abstract class SashEditorPart extends EditorPart { private int _orientation = SWT.VERTICAL; private SashForm _sashForm; /** * List of nested editors. Element type: IEditorPart. Need to hang onto them * here, in addition to using get/setData on the items, because dispose() * needs to access them, but widgetry has already been disposed at that * point. */ private ArrayList _nestedEditors = new ArrayList(3); private Map _editorToComposite = new HashMap(); private IEditorPart _activeEditor = null; /** * Creates and adds a new page containing the given editor to this * multi-page editor. The page is added at the given index. This also hooks * a property change listener on the nested editor. * * @param editor * the nested editor * @param input * the input for the nested editor * @exception PartInitException * if a new page could not be created * @see org.eclipse.ui.part.MultiPageEditorPart#handlePropertyChange(int) the handler for * property change events from the nested editor */ public void addPage(final IEditorPart editor, IEditorInput input) throws PartInitException { IEditorSite site = createSite(editor); // call init first so that if an exception is thrown, we have created no // new widgets editor.init(site, input); final Composite parent1 = new Composite(getContainer(), SWT.NONE); FillLayout fillLayout = new FillLayout(); fillLayout.marginWidth = fillLayout.marginHeight = 1; parent1.setLayout(fillLayout); parent1.addListener(SWT.Activate, new Listener() { public void handleEvent(Event event) { if (event.type == SWT.Activate) { activeEditorChanged(editor); parent1.setBackground(ColorConstants.green); } } }); parent1.addListener(SWT.Deactivate, new Listener() { public void handleEvent(Event event) { parent1.setBackground(ColorConstants.titleInactiveBackground); } }); SWTUtils.workaroundResize(parent1); editor.createPartControl(parent1); editor.addPropertyListener(new IPropertyListener() { public void propertyChanged(Object source, int propertyId) { SashEditorPart.this.handlePropertyChange(propertyId); } }); _nestedEditors.add(editor); _editorToComposite.put(editor, parent1); connectPage(editor); } /** * @param editor */ protected void connectPage(IEditorPart editor) { ISelectionProvider editSelectionProvider = editor.getSite() .getSelectionProvider(); if (editSelectionProvider instanceof IPostSelectionProvider) { ((IPostSelectionProvider) editSelectionProvider) .addPostSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { ((SashEditorSelectionProvider) getSite() .getSelectionProvider()) .firePostSelectionChanged(event); } }); } else { editSelectionProvider .addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { ((SashEditorSelectionProvider) getSite() .getSelectionProvider()) .fireSelectionChanged(event); } }); } } /** * Creates an empty container. Creates a CTabFolder with no style bits set, * and hooks a selection listener which calls <code>pageChange()</code> * whenever the selected tab changes. * * @param parent * The composite in which the container tab folder should be * created; must not be <code>null</code>. * @return a new container */ private SashForm createContainer(Composite parent) { // use SWT.FLAT style so that an extra 1 pixel border is not reserved // inside the folder SashForm newContainer = new SashForm(parent, SWT.NONE); SWTUtils.workaroundResize(newContainer); newContainer.setOrientation(_orientation); return newContainer; } /** * @throws PartInitException */ abstract protected void createPages() throws PartInitException; /** * The <code>MultiPageEditor</code> implementation of this * <code>IWorkbenchPart</code> method creates the control for the * multi-page editor by calling <code>createContainer</code>, then * <code>createPages</code>. Subclasses should implement * <code>createPages</code> rather than overriding this method. * * @param parent * The parent in which the editor should be created; must not be * <code>null</code>. */ public final void createPartControl(Composite parent) { this._sashForm = createContainer(parent); try { createPages(); } catch (PartInitException ex) { ex.printStackTrace(); } // set the active page (page 0 by default), unless it has already been // done if (getActiveEditor() == null) { if (!_nestedEditors.isEmpty()) { setActiveEditor((IEditorPart) _nestedEditors.get(0)); } } } /** * Creates the site for the given nested editor. The * <code>MultiPageEditorPart</code> implementation of this method creates * an instance of <code>MultiPageEditorSite</code>. Subclasses may * reimplement to create more specialized sites. * * @param editor * the nested editor * @return the editor site */ protected IEditorSite createSite(IEditorPart editor) { return new SashEditorSite(this, editor); } /** * The <code>MultiPageEditorPart</code> implementation of this * <code>IWorkbenchPart</code> method disposes all nested editors. * Subclasses may extend. */ public void dispose() { _activeEditor = null; for (int i = 0; i < _nestedEditors.size(); ++i) { IEditorPart editor = (IEditorPart) _nestedEditors.get(i); disposePart(editor); } _nestedEditors.clear(); _editorToComposite.clear(); } /** * Returns the active nested editor if there is one. * <p> * Subclasses should not override this method * </p> * * @return the active nested editor, or <code>null</code> if none */ public IEditorPart getActiveEditor() { return _activeEditor; } /** * Returns the composite control containing this multi-page editor's pages. * This should be used as the parent when creating controls for the * individual pages. That is, when calling <code>addPage(Control)</code>, * the passed control should be a child of this container. * <p> * Warning: Clients should not assume that the container is any particular * subclass of Composite. The actual class used may change in order to * improve the look and feel of multi-page editors. Any code making * assumptions on the particular subclass would thus be broken. * </p> * <p> * Subclasses should not override this method * </p> * * @return the composite, or <code>null</code> if * <code>createPartControl</code> has not been called yet */ protected Composite getContainer() { return _sashForm; } /** * Returns the editor for the given page index. The page index must be * valid. * * @param pageIndex * the index of the page * @return the editor for the specified page, or <code>null</code> if the * specified page was not created with * <code>addPage(IEditorPart,IEditorInput)</code> */ protected IEditorPart getEditor(int pageIndex) { return (IEditorPart) _nestedEditors.get(pageIndex); } /** * Handles a property change notification from a nested editor. The default * implementation simply forwards the change to listeners on this multi-page * editor by calling <code>firePropertyChange</code> with the same * property id. For example, if the dirty state of a nested editor changes * (property id <code>IEditorPart.PROP_DIRTY</code>), this method handles * it by firing a property change event for * <code>IEditorPart.PROP_DIRTY</code> to property listeners on this * multi-page editor. * <p> * Subclasses may extend or reimplement this method. * </p> * * @param propertyId * the id of the property that changed */ protected void handlePropertyChange(int propertyId) { firePropertyChange(propertyId); } /** * The <code>MultiPageEditorPart</code> implementation of this * <code>IEditorPart</code> method sets its site to the given site, its * input to the given input, and the site's selection provider to a * <code>MultiPageSelectionProvider</code>. Subclasses may extend this * method. * * @param site * The site for which this part is being created; must not be * <code>null</code>. * @param input * The input on which this editor should be created; must not be * <code>null</code>. * @throws PartInitException * If the initialization of the part fails -- currently never. */ public void init(IEditorSite site, IEditorInput input) throws PartInitException { setSite(site); setInput(input); site.setSelectionProvider(new SashEditorSelectionProvider(this)); } /** * The <code>MultiPageEditorPart</code> implementation of this * <code>IEditorPart</code> method returns whether the contents of any of * this multi-page editor's nested editors have changed since the last save. * Pages created with <code>addPage(Control)</code> are ignored. * <p> * Subclasses may extend or reimplement this method. * </p> * * @return <code>true</code> if any of the nested editors are dirty; * <code>false</code> otherwise. */ public boolean isDirty() { // use nestedEditors to avoid SWT requests; see bug 12996 for (Iterator i = _nestedEditors.iterator(); i.hasNext();) { IEditorPart editor = (IEditorPart) i.next(); if (editor.isDirty()) { return true; } } return false; } /** * Notifies this multi-page editor that the page with the given id has been * activated. This method is called when the user selects a different tab. * <p> * The <code>MultiPageEditorPart</code> implementation of this method sets * focus to the new page, and notifies the action bar contributor (if there * is one). This checks whether the action bar contributor is an instance of * <code>MultiPageEditorActionBarContributor</code>, and, if so, calls * <code>setActivePage</code> with the active nested editor. This also * fires a selection change event if required. * </p> * <p> * Subclasses may extend this method. * </p> * @param activeEditor * */ protected void activeEditorChanged(IEditorPart activeEditor) { setActiveEditor(activeEditor); setFocus(); IEditorSite site = getEditorSite(); while (site != null) { IEditorActionBarContributor contributor = site .getActionBarContributor(); if (contributor instanceof MultiPageEditorActionBarContributor) { ((MultiPageEditorActionBarContributor) contributor) .setActivePage(activeEditor); } if (site instanceof MultiPageEditorSite) { site = (IEditorSite) ((MultiPageEditorSite) site) .getMultiPageEditor().getSite(); } else if (site instanceof SashEditorSite) { site = (IEditorSite) ((SashEditorSite) site).getSashEditor() .getSite(); } else { site = null; } } if (activeEditor != null) { // Workaround for 1GAUS7C: ITPUI:ALL - Editor not activated when // restored from previous session // do not need second if once fixed ISelectionProvider selectionProvider = activeEditor.getSite() .getSelectionProvider(); if (selectionProvider != null) { SelectionChangedEvent event = new SelectionChangedEvent( selectionProvider, selectionProvider.getSelection()); ((SashEditorSelectionProvider) getSite().getSelectionProvider()) .fireSelectionChanged(event); } } } /** * Disposes the given part and its site. * * @param part * The part to dispose; must not be <code>null</code>. */ private void disposePart(final IWorkbenchPart part) { SafeRunner.run(new SafeRunnable() { public void run() { if (part.getSite() instanceof SashEditorSite) { SashEditorSite partSite = (SashEditorSite) part.getSite(); partSite.dispose(); } part.dispose(); } public void handleException(Throwable e) { // Exception has already being logged by Core. Do nothing. } }); } /** * Sets the currently active page. * @param part * */ protected void setActiveEditor(IEditorPart part) { _activeEditor = part; } /** * The <code>MultiPageEditor</code> implementation of this * <code>IWorkbenchPart</code> method sets focus on the active nested * editor, if there is one. * <p> * Subclasses may extend or reimplement. * </p> */ public void setFocus() { setFocus(getActiveEditor()); } /** * Sets focus to the control for the given page. If the page has an editor, * this calls its <code>setFocus()</code> method. Otherwise, this calls * <code>setFocus</code> on the control for the page. * * @param pageIndex * the index of the page */ private void setFocus(IEditorPart editor) { final IKeyBindingService service = getSite().getKeyBindingService(); if (editor == null) { // There is no selected page, so deactivate the active service. if (service instanceof INestableKeyBindingService) { final INestableKeyBindingService nestableService = (INestableKeyBindingService) service; nestableService.activateKeyBindingService(null); } else { //WorkbenchPlugin PDPlugin .getLogger(getClass()).error("MultiPageEditorPart.setFocus() Parent key binding service was not an instance of INestableKeyBindingService. It was an instance of " + service.getClass().getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$ } return; } editor.setFocus(); // There is no selected page, so deactivate the active service. if (service instanceof INestableKeyBindingService) { final INestableKeyBindingService nestableService = (INestableKeyBindingService) service; if (editor != null) { nestableService.activateKeyBindingService(editor .getEditorSite()); } else { nestableService.activateKeyBindingService(null); } } else { PDPlugin .getLogger(getClass()).error("MultiPageEditorPart.setFocus() Parent key binding service was not an instance of INestableKeyBindingService. It was an instance of " + service.getClass().getName() + " instead."); //$NON-NLS-1$ //$NON-NLS-2$ } } public void doSave(IProgressMonitor monitor) { if (_activeEditor != null) { _activeEditor.doSave(monitor); } } public void doSaveAs() { if (_activeEditor != null) { _activeEditor.doSaveAs(); } } public boolean isSaveAsAllowed() { if (_activeEditor != null) { return _activeEditor.isSaveAsAllowed(); } return false; } /** * @param orientation */ public void setOrientation(int orientation) { this._orientation = orientation; if (_sashForm != null && !_sashForm.isDisposed()) { _sashForm.setMaximizedControl(null); _sashForm.setOrientation(_orientation); } } /** * @param part */ public void setMaximizedEditor(IEditorPart part) { if (part != null) { Composite c = (Composite) _editorToComposite.get(part); if (c != null && _sashForm != null && !_sashForm.isDisposed()) { _sashForm.setMaximizedControl(c); part.setFocus(); } } else { if (_sashForm != null && !_sashForm.isDisposed()) { _sashForm.setMaximizedControl(null); } } } }