Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/ui/part/MultiPageEditorPart.java


1   /*******************************************************************************
2    * Copyright (c) 2000, 2004 IBM Corporation and others.
3    * All rights reserved. This program and the accompanying materials 
4    * are made available under the terms of the Common Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/cpl-v10.html
7    * 
8    * Contributors:
9    *     IBM Corporation - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.ui.part;
12  
13   
14  import java.util.ArrayList;
15  import java.util.Iterator;
16  
17  import org.eclipse.core.runtime.Platform;
18  
19  import org.eclipse.swt.SWT;
20  import org.eclipse.swt.custom.CTabFolder;
21  import org.eclipse.swt.custom.CTabItem;
22  import org.eclipse.swt.events.*;
23  import org.eclipse.swt.graphics.Image;
24  import org.eclipse.swt.layout.FillLayout;
25  import org.eclipse.swt.widgets.Composite;
26  import org.eclipse.swt.widgets.Control;
27  import org.eclipse.swt.widgets.Item;
28  
29  import org.eclipse.jface.util.Assert;
30  import org.eclipse.jface.util.SafeRunnable;
31  import org.eclipse.jface.viewers.ISelectionProvider;
32  import org.eclipse.jface.viewers.SelectionChangedEvent;
33  
34  import org.eclipse.ui.IEditorActionBarContributor;
35  import org.eclipse.ui.IEditorInput;
36  import org.eclipse.ui.IEditorPart;
37  import org.eclipse.ui.IEditorSite;
38  import org.eclipse.ui.IKeyBindingService;
39  import org.eclipse.ui.INestableKeyBindingService;
40  import org.eclipse.ui.IPropertyListener;
41  import org.eclipse.ui.IWorkbenchPart;
42  import org.eclipse.ui.PartInitException;
43  
44  import org.eclipse.ui.internal.WorkbenchPlugin;
45  
46  /**
47   * A multi-page editor is an editor with multiple pages, each of which may
48   * contain an editor or an arbitrary SWT control.
49   * <p>
50   * Subclasses must implement the following methods:
51   * <ul>
52   *   <li><code>createPages</code> - to create the required pages by calling one
53   *     of the <code>addPage</code> methods</li>
54   *   <li><code>IEditorPart.doSave</code> - to save contents of editor</li>
55   *   <li><code>IEditorPart.doSaveAs</code> - to save contents of editor</li>
56   *   <li><code>IEditorPart.isSaveAsAllowed</code> - to enable Save As</li>
57   *   <li><code>IEditorPart.gotoMarker</code> - to scroll to a marker</li>
58   * </ul>
59   * </p>
60   * <p>
61   * Multi-page editors have a single action bar contributor, which manages 
62   * contributions for all the pages. The contributor must be a subclass of
63   * <code>AbstractMultiPageEditorActionBarContributor</code>.
64   * Note that since any nested editors are created directly in code by callers of 
65   * <code>addPage(IEditorPart,IEditorInput)</code>, nested editors do not have 
66   * their own contributors.
67   * </p>
68   *
69   * @see org.eclipse.ui.part.MultiPageEditorActionBarContributor
70   */
71  public abstract class MultiPageEditorPart extends EditorPart {
72    /**
73     * The container widget.
74     */
75    private CTabFolder container;
76  
77    /**
78     * List of nested editors. Element type: IEditorPart.
79     * Need to hang onto them here, in addition to using get/setData on the items,
80     * because dispose() needs to access them, but widgetry has already been disposed at that point.
81     */
82    private ArrayList nestedEditors = new ArrayList(3);
83  /**
84   * Creates an empty multi-page editor with no pages.
85   */
86  protected MultiPageEditorPart() {
87    super();
88  }
89  /**
90   * Creates and adds a new page containing the given control to this multi-page 
91   * editor.  The control may be <code>null</code>, allowing it to be created
92   * and set later using <code>setControl</code>.
93   *
94   * @param control the control, or <code>null</code>
95   * @return the index of the new page
96   *
97   * @see MultiPageEditorPart#setControl(int, Control)
98   */
99  public int addPage(Control control) {
100   createItem(control);
101   return getPageCount() - 1;
102 }
103 /**
104  * Creates and adds a new page containing the given editor to this multi-page 
105  * editor. This also hooks a property change listener on the nested editor.
106  *
107  * @param editor the nested editor
108  * @param input the input for the nested editor
109  * @return the index of the new page
110  * @exception PartInitException if a new page could not be created
111  *
112  * @see MultiPageEditorPart#handlePropertyChange(int) the handler for property change events from the nested editor
113  */
114 public int addPage(IEditorPart editor, IEditorInput input) throws PartInitException {
115   IEditorSite site = createSite(editor);
116   // call init first so that if an exception is thrown, we have created no new widgets
117   editor.init(site, input);
118   Composite parent2 = new Composite(getContainer(), SWT.NONE);
119   parent2.setLayout(new FillLayout());
120   editor.createPartControl(parent2);
121   editor.addPropertyListener(
122     new IPropertyListener() {
123       public void propertyChanged(Object source, int propertyId) {
124         MultiPageEditorPart.this.handlePropertyChange(propertyId);
125       }
126     });
127   // create item for page only after createPartControl has succeeded
128   Item item = createItem(parent2);
129   // remember the editor, as both data on the item, and in the list of editors (see field comment)
130   item.setData(editor);
131   nestedEditors.add(editor);
132   return getPageCount() - 1;
133 }
134 /**
135  * Creates an empty container. Creates a CTabFolder with no style bits set, and
136  * hooks a selection listener which calls <code>pageChange()</code> whenever
137  * the selected tab changes.
138  * 
139  * @param parent
140  *            The composite in which the container tab folder should be
141  *            created; must not be <code>null</code>.
142  * @return a new container
143  */
144 private CTabFolder createContainer(Composite parent) {
145   // use SWT.FLAT style so that an extra 1 pixel border is not reserved
146   // inside the folder
147   final CTabFolder newContainer = new CTabFolder(parent, SWT.BOTTOM | SWT.FLAT);
148   newContainer.addSelectionListener(new SelectionAdapter() {
149     public void widgetSelected(SelectionEvent e) {
150       int newPageIndex = newContainer.indexOf((CTabItem) e.item);
151       pageChange(newPageIndex);
152     }
153   });
154   return newContainer;
155 }
156 /**
157  * Creates a tab item and places control in the new item.
158  * The item is a CTabItem with no style bits set.
159  *
160  * @param control is the control to be placed in an item
161  * @return a new item
162  */
163 private CTabItem createItem(Control control) {
164   CTabItem item = new CTabItem(getTabFolder(), SWT.NONE);
165   item.setControl(control);
166   return item;
167 }
168 /**
169  * Creates the pages of this multi-page editor.
170  * <p>
171  * Subclasses must implement this method.
172  * </p>
173  */
174 protected abstract void createPages();
175 /**
176  * The <code>MultiPageEditor</code> implementation of this <code>IWorkbenchPart</code>
177  * method creates the control for the multi-page editor by calling <code>createContainer</code>,
178  * then <code>createPages</code>. Subclasses should implement <code>createPages</code>
179  * rather than overriding this method.
180  * 
181  * @param parent
182  *            The parent in which the editor should be created; must not be
183  *            <code>null</code>.
184  */
185 public final void createPartControl(Composite parent) {
186   this.container = createContainer(parent);
187   createPages();
188   // set the active page (page 0 by default), unless it has already been done
189   if (getActivePage() == -1)
190     setActivePage(0);
191 }
192 /**
193  * Creates the site for the given nested editor.
194  * The <code>MultiPageEditorPart</code> implementation of this method creates an 
195  * instance of <code>MultiPageEditorSite</code>. Subclasses may reimplement
196  * to create more specialized sites.
197  *
198  * @param editor the nested editor
199  * @return the editor site
200  */
201 protected IEditorSite createSite(IEditorPart editor){
202   return new MultiPageEditorSite(this, editor);
203 }
204 /**
205  * The <code>MultiPageEditorPart</code> implementation of this 
206  * <code>IWorkbenchPart</code> method disposes all nested editors.
207  * Subclasses may extend.
208  */
209 public void dispose() {
210   for (int i = 0; i < nestedEditors.size(); ++i) {
211     IEditorPart editor = (IEditorPart) nestedEditors.get(i);
212     disposePart(editor);    
213   }
214   nestedEditors.clear();
215 }
216 /**
217  * Returns the active nested editor if there is one.
218  * <p>
219  * Subclasses should not override this method
220  * </p>
221  * 
222  * @return the active nested editor, or <code>null</code> if none
223  */
224 protected IEditorPart getActiveEditor() {
225   int index = getActivePage();
226   if (index != -1)
227     return getEditor(index);
228   return null;
229 }
230 /**
231  * Returns the index of the currently active page,
232  * or -1 if there is no active page.
233  * <p>
234  * Subclasses should not override this method
235  * </p>
236  *
237  * @return the index of the active page, or -1 if there is no active page
238  */
239 protected int getActivePage() {
240   CTabFolder tabFolder = getTabFolder();
241   if (tabFolder != null && !tabFolder.isDisposed())
242     return tabFolder.getSelectionIndex();
243   return -1;
244 }
245 /**
246  * Returns the composite control containing this multi-page editor's pages.
247  * This should be used as the parent when creating controls for the individual pages.
248  * That is, when calling <code>addPage(Control)</code>, the passed control should be
249  * a child of this container.
250  * <p>
251  * Warning: Clients should not assume that the container is any particular subclass
252  * of Composite.  The actual class used may change in order to improve the look and feel of
253  * multi-page editors.  Any code making assumptions on the particular subclass would thus be broken.
254  * </p>
255  * <p>
256  * Subclasses should not override this method
257  * </p>
258  *
259  * @return the composite, or <code>null</code> if <code>createPartControl</code>
260  *   has not been called yet
261  */
262 protected Composite getContainer() {
263   return container;
264 }
265 /**
266  * Returns the control for the given page index, or <code>null</code>
267  * if no control has been set for the page.
268  * The page index must be valid.
269  * <p>
270  * Subclasses should not override this method
271  * </p>
272  *
273  * @param pageIndex the index of the page
274  * @return the control for the specified page, or <code>null</code> if none has been set
275  */
276 protected Control getControl(int pageIndex) {
277   return getItem(pageIndex).getControl();
278 }
279 /**
280  * Returns the editor for the given page index.
281  * The page index must be valid.
282  *
283  * @param pageIndex the index of the page
284  * @return the editor for the specified page, or <code>null</code> if the
285  *   specified page was not created with 
286  *   <code>addPage(IEditorPart,IEditorInput)</code>
287  */
288 protected IEditorPart getEditor(int pageIndex) {
289   Item item = getItem(pageIndex);
290   if (item != null) {
291     Object data = item.getData();
292     if (data instanceof IEditorPart) {
293       return (IEditorPart) data;
294     }
295   }
296   return null;
297 }
298 /**
299  * Returns the tab item for the given page index (page index is 0-based).
300  * The page index must be valid.
301  *
302  * @param pageIndex the index of the page
303  * @return the tab item for the given page index
304  */
305 private CTabItem getItem(int pageIndex) {
306   return getTabFolder().getItem(pageIndex);
307 }
308 /**
309  * Returns the number of pages in this multi-page editor.
310  *
311  * @return the number of pages
312  */
313 protected int getPageCount() {
314   CTabFolder folder = getTabFolder();
315   // May not have been created yet, or may have been disposed.
316   if (folder != null && !folder.isDisposed())
317     return folder.getItemCount();
318   return 0;
319 }
320 /**
321  * Returns the image for the page with the given index,
322  * or <code>null</code> if no image has been set for the page.
323  * The page index must be valid.
324  *
325  * @param pageIndex the index of the page
326  * @return the image, or <code>null</code> if none
327  */
328 protected Image getPageImage(int pageIndex) {
329   return getItem(pageIndex).getImage();
330 }
331 /**
332  * Returns the text label for the page with the given index.
333  * Returns the empty string if no text label has been set for the page.
334  * The page index must be valid.
335  *
336  * @param pageIndex the index of the page
337  * @return the text label for the page
338  */
339 protected String getPageText(int pageIndex) {
340   return getItem(pageIndex).getText();
341 }
342 /**
343  * Returns the tab folder containing this multi-page editor's pages.
344  *
345  * @return the tab folder, or <code>null</code> if <code>createPartControl</code>
346  *   has not been called yet
347  */
348 private CTabFolder getTabFolder() {
349   return container;
350 }
351 /**
352  * Handles a property change notification from a nested editor.
353  * The default implementation simply forwards the change to listeners
354  * on this multi-page editor by calling <code>firePropertyChange</code>
355  * with the same property id.  For example, if the dirty state of a nested editor
356  * changes (property id <code>IEditorPart.PROP_DIRTY</code>), this method
357  * handles it by firing a property change event for <code>IEditorPart.PROP_DIRTY</code>
358  * to property listeners on this multi-page editor.
359  * <p>
360  * Subclasses may extend or reimplement this method.
361  * </p>
362  *
363  * @param propertyId the id of the property that changed
364  */
365 protected void handlePropertyChange (int propertyId) {
366   firePropertyChange(propertyId);
367 }
368 /**
369  * The <code>MultiPageEditorPart</code> implementation of this <code>IEditorPart</code>
370  * method sets its site to the given site, its input to the given input, and
371  * the site's selection provider to a <code>MultiPageSelectionProvider</code>.
372  * Subclasses may extend this method.
373  * 
374  * @param site
375  *            The site for which this part is being created; must not be <code>null</code>.
376  * @param input
377  *            The input on which this editor should be created; must not be
378  *            <code>null</code>.
379  * @throws PartInitException
380  *             If the initialization of the part fails -- currently never.
381  */
382 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
383   setSite(site);
384   setInput(input);
385   site.setSelectionProvider(new MultiPageSelectionProvider(this));
386 }
387 /**
388  * The <code>MultiPageEditorPart</code> implementation of this <code>IEditorPart</code>
389  * method returns whether the contents of any of this multi-page editor's
390  * nested editors have changed since the last save. Pages created with <code>addPage(Control)</code>
391  * are ignored.
392  * <p>
393  * Subclasses may extend or reimplement this method.
394  * </p>
395  * 
396  * @return <code>true</code> if any of the nested editors are dirty; <code>false</code>
397  *         otherwise.
398  */
399 public boolean isDirty() {
400   // use nestedEditors to avoid SWT requests; see bug 12996
401   for (Iterator i = nestedEditors.iterator(); i.hasNext();) {
402     IEditorPart editor = (IEditorPart) i.next();
403     if (editor.isDirty()) {
404       return true;
405     }
406   }
407   return false;
408 }
409 /**
410  * Notifies this multi-page editor that the page with the given id has been
411  * activated. This method is called when the user selects a different tab.
412  * <p>
413  * The <code>MultiPageEditorPart</code> implementation of this method 
414  * sets focus to the new page, and notifies the action bar contributor (if there is one).
415  * This checks whether the action bar contributor is an instance of 
416  * <code>MultiPageEditorActionBarContributor</code>, and, if so,
417  * calls <code>setActivePage</code> with the active nested editor.
418  * This also fires a selection change event if required.
419  * </p>
420  * <p>
421  * Subclasses may extend this method.
422  * </p>
423  *
424  * @param newPageIndex the index of the activated page 
425  */
426 protected void pageChange(int newPageIndex) {
427   // XXX: Workaround for 1GCN531: SWT:WIN2000 - CTabFolder child's visibility is false on notification
428   Control control = getControl(newPageIndex);
429   if (control != null) {
430     control.setVisible(true);
431   }
432   // XXX: End workaround
433   setFocus();
434   IEditorPart activeEditor = getEditor(newPageIndex);
435   IEditorActionBarContributor contributor = getEditorSite().getActionBarContributor();
436   if (contributor != null && contributor instanceof MultiPageEditorActionBarContributor) {
437     ((MultiPageEditorActionBarContributor) contributor).setActivePage(activeEditor);
438   }
439   if (activeEditor != null) {
440     //Workaround for 1GAUS7C: ITPUI:ALL - Editor not activated when restored from previous session
441     //do not need second if once fixed
442     ISelectionProvider selectionProvider = activeEditor.getSite().getSelectionProvider();
443     if (selectionProvider != null) {
444       SelectionChangedEvent event = new SelectionChangedEvent(selectionProvider, selectionProvider.getSelection());
445       ((MultiPageSelectionProvider) getSite().getSelectionProvider()).fireSelectionChanged(event);
446     }
447   }
448 }
449 /**
450  * Disposes the given part and its site.
451  * 
452  * @param part
453  *            The part to dispose; must not be <code>null</code>.
454  */
455 private void disposePart(final IWorkbenchPart part) {
456   Platform.run(new SafeRunnable() {
457     public void run() {
458       if (part.getSite() instanceof MultiPageEditorSite) {
459         MultiPageEditorSite partSite = (MultiPageEditorSite) part.getSite();
460         partSite.dispose();
461       }
462       part.dispose();
463     }
464     public void handleException(Throwable e) {
465       //Exception has already being logged by Core. Do nothing.
466     }
467   });
468 }
469 /**
470  * Removes the page with the given index from this multi-page editor. The
471  * controls for the page are disposed of; if the page has an editor, it is
472  * disposed of too. The page index must be valid.
473  * 
474  * @param pageIndex
475  *            the index of the page
476  * @see MultiPageEditorPart#addPage(Control)
477  * @see MultiPageEditorPart#addPage(IEditorPart, IEditorInput)
478  */
479 public void removePage(int pageIndex) {
480   Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
481   // get editor (if any) before disposing item
482   IEditorPart editor = getEditor(pageIndex);
483   // dispose item before disposing editor, in case there's an exception in editor's dispose
484   getItem(pageIndex).dispose();
485   // dispose editor (if any)
486   if (editor != null) {
487     nestedEditors.remove(editor);
488     disposePart(editor);
489   }
490 }
491 /**
492  * Sets the currently active page.
493  *
494  * @param pageIndex the index of the page to be activated; the index must be valid
495  */
496 protected void setActivePage(int pageIndex) {
497   Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
498   getTabFolder().setSelection(pageIndex);
499 }
500 /**
501  * Sets the control for the given page index.
502  * The page index must be valid.
503  *
504  * @param pageIndex the index of the page
505  * @param control the control for the specified page, or <code>null</code> to clear the control
506  */
507 protected void setControl(int pageIndex, Control control) {
508   getItem(pageIndex).setControl(control);
509 }
510 /**
511  * The <code>MultiPageEditor</code> implementation of this 
512  * <code>IWorkbenchPart</code> method sets focus on the active nested editor,
513  * if there is one.
514  * <p>
515  * Subclasses may extend or reimplement.
516  * </p>
517  */
518 public void setFocus() {
519   setFocus(getActivePage());
520 }
521 /**
522  * Sets focus to the control for the given page. If the page has an editor,
523  * this calls its <code>setFocus()</code> method. Otherwise, this calls
524  * <code>setFocus</code> on the control for the page.
525  * 
526  * @param pageIndex
527  *            the index of the page
528  */
529 private void setFocus(int pageIndex) {
530   final IKeyBindingService service = getSite().getKeyBindingService();
531         if (pageIndex < 0 || pageIndex >= getPageCount()) {
532             // There is no selected page, so deactivate the active service.
533             if (service instanceof INestableKeyBindingService) {
534                 final INestableKeyBindingService nestableService = (INestableKeyBindingService) service;
535                 nestableService.activateKeyBindingService(null);
536             } else {
537                 WorkbenchPlugin
538                         .log("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$
539             }
540             return;
541         }
542 
543         final IEditorPart editor = getEditor(pageIndex);
544         if (editor != null) {
545             editor.setFocus();
546             // There is no selected page, so deactivate the active service.
547             if (service instanceof INestableKeyBindingService) {
548                 final INestableKeyBindingService nestableService = (INestableKeyBindingService) service;
549                 if (editor != null) {
550                     nestableService.activateKeyBindingService(editor
551                             .getEditorSite());
552                 } else {
553                     nestableService.activateKeyBindingService(null);
554                 }
555             } else {
556                 WorkbenchPlugin
557                         .log("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$
558             }
559         } else {
560             // There is no selected editor, so deactivate the active service.
561             if (service instanceof INestableKeyBindingService) {
562                 final INestableKeyBindingService nestableService = (INestableKeyBindingService) service;
563                 nestableService.activateKeyBindingService(null);
564             } else {
565                 WorkbenchPlugin
566                         .log("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$
567             }
568 
569             // Give the page's control focus.
570             final Control control = getControl(pageIndex);
571             if (control != null) {
572                 control.setFocus();
573             }
574         }
575 }
576 /**
577  * Sets the image for the page with the given index, or <code>null</code>
578  * to clear the image for the page.
579  * The page index must be valid.
580  *
581  * @param pageIndex the index of the page
582  * @param image the image, or <code>null</code>
583  */
584 protected void setPageImage(int pageIndex, Image image) {
585   getItem(pageIndex).setImage(image);
586 }
587 /**
588  * Sets the text label for the page with the given index.
589  * The page index must be valid.
590  * The text label must not be null.
591  *
592  * @param pageIndex the index of the page
593  * @param text the text label
594  */
595 protected void setPageText(int pageIndex, String text) {
596   getItem(pageIndex).setText(text);
597 }
598 }