/******************************************************************************* * Copyright (c) 2000, 2016 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 * Mickael Istria (Red Hat Inc.) - Bug 478587 *******************************************************************************/ package org.eclipse.ui.forms.editor; import java.util.Vector; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorActionBarContributor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.part.MultiPageEditorActionBarContributor; import org.eclipse.ui.part.MultiPageEditorPart; import org.eclipse.ui.part.MultiPageSelectionProvider; /** * This class forms a base of multi-page form editors that typically use one or * more pages with forms and one page for raw source of the editor input. * <p> * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does * not cause the page control to be created. Page control is created when an * attempt is made to select the page in question. This allows editors with * several tabs and complex pages to open quickly. * <p> * Subclasses should extend this class and implement <code>addPages</code> * method. One of the two <code>addPage</code> methods should be called to * contribute pages to the editor. One adds complete (standalone) editors as * nested tabs. These editors will be created right away and will be hooked so * that key bindings, selection service etc. is compatible with the one for the * standalone case. The other method adds classes that implement * <code>IFormPage</code> interface. These pages will be created lazily and * they will share the common key binding and selection service. Since 3.1, * FormEditor is a page change provider. It allows listeners to attach to it and * get notified when pages are changed. This new API in JFace allows dynamic * help to update on page changes. * * @since 3.0 */ public abstract class FormEditor extends MultiPageEditorPart { /** * An array of pages currently in the editor. Page objects are not limited * to those that implement <code>IFormPage</code>, hence the size of this * array matches the number of pages as viewed by the user. * <p> * Subclasses can access this field but should not modify it. */ protected Vector<Object> pages = new Vector<>(); private FormToolkit toolkit; private int currentPage = -1; private static class FormEditorSelectionProvider extends MultiPageSelectionProvider { private ISelection globalSelection; /** * @param formEditor the editor */ public FormEditorSelectionProvider(FormEditor formEditor) { super(formEditor); } @Override public ISelection getSelection() { IEditorPart activeEditor = ((FormEditor) getMultiPageEditor()) .getActiveEditor(); if (activeEditor != null) { ISelectionProvider selectionProvider = activeEditor.getSite() .getSelectionProvider(); if (selectionProvider != null) return selectionProvider.getSelection(); } if (globalSelection != null) { return globalSelection; } return StructuredSelection.EMPTY; } @Override public void setSelection(ISelection selection) { IEditorPart activeEditor = ((FormEditor) getMultiPageEditor()) .getActiveEditor(); if (activeEditor != null) { ISelectionProvider selectionProvider = activeEditor.getSite() .getSelectionProvider(); if (selectionProvider != null) selectionProvider.setSelection(selection); } else { this.globalSelection = selection; fireSelectionChanged(new SelectionChangedEvent(this, globalSelection)); } } } /** * The constructor. */ public FormEditor() { } /** * Overrides super to plug in a different selection provider. */ @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { setSite(site); setInput(input); site.setSelectionProvider(new FormEditorSelectionProvider(this)); } /** * Creates the common toolkit for this editor and adds pages to the editor. * * @see #addPages */ @Override protected void createPages() { addPages(); } @Override protected Composite createPageContainer(Composite parent) { parent = super.createPageContainer(parent); toolkit = createToolkit(parent.getDisplay()); return parent; } /** * Creates the form toolkit. The method can be implemented to substitute a * subclass of the toolkit that should be used for this editor. A typical * use of this method would be to create the form toolkit using one shared * <code>FormColors</code> object to share resources across the multiple * editor instances. * * @param display * the display to use when creating the toolkit * @return the newly created toolkit instance */ protected FormToolkit createToolkit(Display display) { return new FormToolkit(display); } /** * Subclass should implement this method to add pages to the editor using * 'addPage(IFormPage)' method. */ protected abstract void addPages(); @Override public Object getSelectedPage() { int index = getActivePage(); if (index != -1) { return pages.get(index); } return null; } /** * Adds the form page to this editor. Form page will be loaded lazily. Its * part control will not be created until it is activated for the first * time. * * @param page * the form page to add */ public int addPage(IFormPage page) throws PartInitException { int i = super.addPage(page.getPartControl()); configurePage(i, page); return i; } /** * Adds the form page to this editor at the specified index (0-based). Form * page will be loaded lazily. Its part control will not be created until it * is activated for the first time. * * @param index * the position to add the page at (0-based) * @param page * the form page to add * @since 3.1 */ public void addPage(int index, IFormPage page) throws PartInitException { super.addPage(index, page.getPartControl()); configurePage(index, page); updatePageIndices(index+1); } /** * Adds a simple SWT control as a page. Overrides superclass implementation * to keep track of pages. * * @param control * the page control to add * @return the 0-based index of the newly added page */ @Override public int addPage(Control control) { int i = super.addPage(control); try { registerPage(-1, control); } catch (PartInitException e) { // cannot happen for controls } return i; } /** * Adds a simple SWT control as a page. Overrides superclass implementation * to keep track of pages. * * @param control * the page control to add * @param index * the index at which to add the page (0-based) * @since 3.1 */ @Override public void addPage(int index, Control control) { super.addPage(index, control); try { registerPage(index, control); } catch (PartInitException e) { // cannot happen for controls } updatePageIndices(index+1); } /** * Tests whether the editor is dirty by checking all the pages that * implement <code>IFormPage</code>. If none of them is dirty, the method * delegates further processing to <code>super.isDirty()</code>. * * @return <code>true</code> if any of the pages in the editor are dirty, * <code>false</code> otherwise. * @since 3.1 */ @Override public boolean isDirty() { if (pages != null) { for (int i = 0; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; if (fpage.isDirty()) return true; } else if (page instanceof IEditorPart) { IEditorPart editor = (IEditorPart) page; if (editor.isDirty()) { return true; } } } } return super.isDirty(); } /** * Commits all dirty pages in the editor. This method should * be called as a first step of a 'save' operation. * @param onSave <code>true</code> if commit is performed as part * of the 'save' operation, <code>false</code> otherwise. * @since 3.3 */ protected void commitPages(boolean onSave) { if (pages != null) { for (int i = 0; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage)page; IManagedForm mform = fpage.getManagedForm(); if (mform != null && mform.isDirty()) mform.commit(onSave); } } } } /** * Adds a complete editor part to the multi-page editor. * * @see MultiPageEditorPart#addPage(IEditorPart, IEditorInput) */ @Override public int addPage(IEditorPart editor, IEditorInput input) throws PartInitException { int index = super.addPage(editor, input); if (editor instanceof IFormPage) configurePage(index, (IFormPage) editor); else registerPage(-1, editor); return index; } /** * Adds a complete editor part to the multi-page editor at the specified * position. * * @see MultiPageEditorPart#addPage(int, IEditorPart, IEditorInput) * @since 3.1 */ @Override public void addPage(int index, IEditorPart editor, IEditorInput input) throws PartInitException { super.addPage(index, editor, input); if (editor instanceof IFormPage) configurePage(index, (IFormPage) editor); else registerPage(index, editor); updatePageIndices(index+1); } /** * Configures the form page. * * @param index * the page index * @param page * the page to configure * @throws PartInitException * if there are problems in configuring the page */ protected void configurePage(int index, IFormPage page) throws PartInitException { setPageText(index, page.getTitle()); // setPageImage(index, page.getTitleImage()); page.setIndex(index); registerPage(index, page); } /** * Overrides the superclass to remove the page from the page table. * * @param pageIndex * the 0-based index of the page in the editor */ @Override public void removePage(int pageIndex) { if (pageIndex >= 0 && pageIndex < pages.size()) { Object page = pages.get(pageIndex); pages.remove(page); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; if (!fpage.isEditor()) fpage.dispose(); } updatePageIndices(pageIndex); } super.removePage(pageIndex); } // fix the page indices after the removal/insertion private void updatePageIndices(int start) { for (int i = start; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; fpage.setIndex(i); } } } /** * Called to indicate that the editor has been made dirty or the changes * have been saved. */ public void editorDirtyStateChanged() { firePropertyChange(PROP_DIRTY); } /** * Disposes the pages and the toolkit after disposing the editor itself. * Subclasses must call 'super' when reimplementing the method. */ @Override public void dispose() { super.dispose(); for (int i = 0; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; // don't dispose source pages because they will // be disposed as nested editors by the superclass if (!fpage.isEditor()) fpage.dispose(); } } pages = null; // toolkit may be null if editor has been instantiated // but never created - see defect #62190 if (toolkit != null) { toolkit.dispose(); toolkit = null; } } /** * Returns the toolkit owned by this editor. * * @return the toolkit object */ public FormToolkit getToolkit() { return toolkit; } /** * Widens the visibility of the method in the superclass. * * @return the active nested editor */ @Override public IEditorPart getActiveEditor() { return super.getActiveEditor(); } /** * Returns the current page index. The value is identical to the value of * 'getActivePage()' except during the page switch, when this method still * has the old active page index. * <p> * Another important difference is during the editor closing. When the tab * folder is disposed, 'getActivePage()' will return -1, while this method * will still return the last active page. * * @see #getActivePage * @return the currently selected page or -1 if no page is currently * selected */ protected int getCurrentPage() { return currentPage; } /** * @see MultiPageEditorPart#pageChange(int) */ @Override protected void pageChange(int newPageIndex) { // fix for windows handles int oldPageIndex = getCurrentPage(); if (oldPageIndex != -1 && pages.size() > oldPageIndex && pages.get(oldPageIndex) instanceof IFormPage && oldPageIndex != newPageIndex) { // Check the old page IFormPage oldFormPage = (IFormPage) pages.get(oldPageIndex); if (oldFormPage.canLeaveThePage() == false) { setActivePage(oldPageIndex); return; } } // Now is the absolute last moment to create the page control. Object page = pages.get(newPageIndex); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; if (fpage.getPartControl() == null) { fpage.createPartControl(getContainer()); setControl(newPageIndex, fpage.getPartControl()); fpage.getPartControl().setMenu(getContainer().getMenu()); } } if (oldPageIndex != -1 && pages.size() > oldPageIndex && pages.get(oldPageIndex) instanceof IFormPage) { // Commit old page before activating the new one IFormPage oldFormPage = (IFormPage) pages.get(oldPageIndex); IManagedForm mform = oldFormPage.getManagedForm(); if (mform != null) mform.commit(false); } if (pages.size() > newPageIndex && pages.get(newPageIndex) instanceof IFormPage) ((IFormPage) pages.get(newPageIndex)).setActive(true); if (oldPageIndex != -1 && pages.size() > oldPageIndex && newPageIndex != oldPageIndex && pages.get(oldPageIndex) instanceof IFormPage) ((IFormPage) pages.get(oldPageIndex)).setActive(false); // Call super - this will cause pages to switch super.pageChange(newPageIndex); this.currentPage = newPageIndex; } /** * Sets the active page using the unique page identifier. * * @param pageId * the id of the page to switch to * @return page that was set active or <samp>null </samp> if not found. */ public IFormPage setActivePage(String pageId) { for (int i = 0; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; if (fpage.getId().equals(pageId)) { setActivePage(i); return fpage; } } } return null; } /** * Finds the page instance that has the provided id. * * @param pageId * the id of the page to find * @return page with the matching id or <code>null</code> if not found. */ public IFormPage findPage(String pageId) { for (int i = 0; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) pages.get(i); if (fpage.getId().equals(pageId)) return fpage; } } return null; } /** * Sets the active page using the unique page identifier and sets its input * to the provided object. * * @param pageId * the id of the page to switch to * @param pageInput * the page input * @return page that was set active or <samp>null </samp> if not found. */ public IFormPage setActivePage(String pageId, Object pageInput) { IFormPage page = setActivePage(pageId); if (page != null) { IManagedForm mform = page.getManagedForm(); if (mform != null) mform.setInput(pageInput); } return page; } /** * Iterates through the pages calling similar method until a page is found * that contains the desired page input. * * @param pageInput * the object to select and reveal * @return the page that accepted the request or <code>null</code> if no * page has the desired object. * @see #setActivePage(String, Object) */ public IFormPage selectReveal(Object pageInput) { for (int i = 0; i < pages.size(); i++) { Object page = pages.get(i); if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; if (fpage.selectReveal(pageInput)) return fpage; } } return null; } /** * Returns active page instance if the currently selected page index is not * -1 and active page is a IFormPage, or <code>null</code> otherwise. * * @see MultiPageEditorPart#getSelectedPage() * @return active page instance if selected, or <code>null</code> if no * page is currently active. */ public IFormPage getActivePageInstance() { int index = getActivePage(); if (index != -1) { Object page = pages.get(index); if (page instanceof IFormPage) return (IFormPage) page; } return null; } /** * @see MultiPageEditorPart#setActivePage(int) */ @Override protected void setActivePage(int pageIndex) { // fix for window handles problem // this should be called only when the editor is first opened if (pages.size() > pageIndex && pages.get(pageIndex) instanceof IFormPage) { super.setActivePage(pageIndex); IFormPage activePage = (IFormPage) pages.get(pageIndex); activePage.setActive(true); } else super.setActivePage(pageIndex); updateActionBarContributor(pageIndex); } /** * Notifies action bar contributor about page change. * * @param pageIndex * the index of the new page */ protected void updateActionBarContributor(int pageIndex) { // this is to enable the undo/redo actions before a page change has // occurred IEditorActionBarContributor contributor = getEditorSite() .getActionBarContributor(); if (contributor != null && contributor instanceof MultiPageEditorActionBarContributor) { ((MultiPageEditorActionBarContributor) contributor) .setActivePage(getEditor(pageIndex)); } } /** * Closes the editor programmatically. * * @param save * if <code>true</code>, the content should be saved before * closing. */ public void close(final boolean save) { Display display = getSite().getShell().getDisplay(); display.asyncExec(() -> { if (toolkit != null) { getSite().getPage().closeEditor(FormEditor.this, save); } }); } private void registerPage(int index, Object page) throws PartInitException { if (!pages.contains(page)) { if (index == -1) pages.add(page); else pages.add(index, page); } if (page instanceof IFormPage) { IFormPage fpage = (IFormPage) page; if (fpage.isEditor() == false) fpage.init(getEditorSite(), getEditorInput()); } } }