/******************************************************************************* * Copyright (c) 2005, 2007 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 * Stefan Xenos, IBM - bug 51580 * Chris Torrence, ITT Visual Information Solutions - bugs 51580 202208 *******************************************************************************/ package org.eclipse.ui.internal.presentations.util; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.Plugin; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IMemento; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.preferences.IDynamicPropertyMap; import org.eclipse.ui.internal.preferences.PreferenceStoreAdapter; import org.eclipse.ui.internal.preferences.PreferencesAdapter; import org.eclipse.ui.internal.preferences.PropertyMapAdapter; import org.eclipse.ui.internal.preferences.ThemeManagerAdapter; import org.eclipse.ui.internal.presentations.defaultpresentation.DefaultPartList; import org.eclipse.ui.internal.util.PrefUtil; import org.eclipse.ui.presentations.IPartMenu; import org.eclipse.ui.presentations.IPresentablePart; import org.eclipse.ui.presentations.IPresentationSerializer; import org.eclipse.ui.presentations.IStackPresentationSite; import org.eclipse.ui.presentations.StackDropResult; import org.eclipse.ui.presentations.StackPresentation; /** * @since 3.0 */ public final class TabbedStackPresentation extends StackPresentation { private PresentablePartFolder folder; private ISystemMenu systemMenu; private ISystemMenu partList; private PreferenceStoreAdapter apiPreferences = new PreferenceStoreAdapter(PrefUtil .getAPIPreferenceStore()); private ThemeManagerAdapter themePreferences = new ThemeManagerAdapter( PlatformUI.getWorkbench().getThemeManager()); private TabOrder tabs; private TabDragHandler dragBehavior; private boolean initializing = true; private int ignoreSelectionChanges = 0; private TabFolderListener tabFolderListener = new TabFolderListener() { public void handleEvent(TabFolderEvent e) { switch (e.type) { case TabFolderEvent.EVENT_MINIMIZE: { getSite().setState(IStackPresentationSite.STATE_MINIMIZED); break; } case TabFolderEvent.EVENT_MAXIMIZE: { getSite().setState(IStackPresentationSite.STATE_MAXIMIZED); break; } case TabFolderEvent.EVENT_RESTORE: { getSite().setState(IStackPresentationSite.STATE_RESTORED); break; } case TabFolderEvent.EVENT_CLOSE: { IPresentablePart part = folder.getPartForTab(e.tab); if (part != null) { getSite().close(new IPresentablePart[] { part }); } break; } case TabFolderEvent.EVENT_SHOW_LIST: { showPartList(); break; } case TabFolderEvent.EVENT_GIVE_FOCUS_TO_PART: { IPresentablePart part = getSite().getSelectedPart(); if (part != null) { part.setFocus(); } break; } case TabFolderEvent.EVENT_PANE_MENU: { IPresentablePart part = getSite().getSelectedPart(); if (part != null) { part.setFocus(); } TabbedStackPresentation.this.showPaneMenu(folder .getPartForTab(e.tab), new Point(e.x, e.y)); break; } case TabFolderEvent.EVENT_DRAG_START: { AbstractTabItem beingDragged = e.tab; Point initialLocation = new Point(e.x, e.y); if (beingDragged == null) { getSite().dragStart(initialLocation, false); } else { IPresentablePart part = folder.getPartForTab(beingDragged); try { dragStart = folder.indexOf(part); getSite().dragStart(part, initialLocation, false); } finally { dragStart = -1; } } break; } case TabFolderEvent.EVENT_TAB_SELECTED: { if (ignoreSelectionChanges > 0) { return; } IPresentablePart part = folder.getPartForTab(e.tab); if (part != null) { getSite().selectPart(part); } break; } case TabFolderEvent.EVENT_SYSTEM_MENU: { IPresentablePart part = folder.getPartForTab(e.tab); if (part == null) { part = getSite().getSelectedPart(); } if (part != null) { showSystemMenu(new Point(e.x, e.y), part); } break; } case TabFolderEvent.EVENT_PREFERRED_SIZE: { IPresentablePart part = folder.getPartForTab(e.tab); if (part == null) { // Standalone views with no title have no tab, so just get the part. IPresentablePart[] parts = getSite().getPartList(); if (parts.length > 0) part = parts[0]; } if (part == getSite().getSelectedPart()) { getSite().flushLayout(); } break; } } } }; private int dragStart = -1; private Map prefs = new HashMap(); public TabbedStackPresentation(IStackPresentationSite site, AbstractTabFolder widget, ISystemMenu systemMenu) { this(site, new PresentablePartFolder(widget), systemMenu); } public TabbedStackPresentation(IStackPresentationSite site, PresentablePartFolder folder, ISystemMenu systemMenu) { this(site, folder, new LeftToRightTabOrder(folder), new ReplaceDragHandler(folder.getTabFolder()), systemMenu); } public TabbedStackPresentation(IStackPresentationSite site, PresentablePartFolder newFolder, TabOrder tabs, TabDragHandler dragBehavior, ISystemMenu systemMenu) { super(site); this.systemMenu = systemMenu; this.folder = newFolder; this.tabs = tabs; this.dragBehavior = dragBehavior; // Add a dispose listener. This will call the presentationDisposed() // method when the widget is destroyed. folder.getTabFolder().getControl().addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { presentationDisposed(); } }); folder.getTabFolder().addListener(tabFolderListener); this.partList = new DefaultPartList(site, newFolder); } /** * Restores a presentation from a previously stored state * * @param serializer (not null) * @param savedState (not null) */ public void restoreState(IPresentationSerializer serializer, IMemento savedState) { tabs.restoreState(serializer, savedState); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#saveState(org.eclipse.ui.presentations.IPresentationSerializer, org.eclipse.ui.IMemento) */ public void saveState(IPresentationSerializer context, IMemento memento) { super.saveState(context, memento); tabs.saveState(context, memento); } /** * Returns true iff the presentation has been disposed * * @return true iff the presentation has been disposed */ private boolean isDisposed() { return folder == null || folder.isDisposed(); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#setBounds(org.eclipse.swt.graphics.Rectangle) */ public void setBounds(Rectangle bounds) { folder.setBounds(bounds); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#computeMinimumSize() */ public Point computeMinimumSize() { return folder.getTabFolder().computeSize(SWT.DEFAULT, SWT.DEFAULT); } /** * Returns the minimum size for this stack, taking into account * the available perpendicular space. * @param width indicates whether a width (=true) or a height (=false) is being computed * @param availablePerpendicular available space perpendicular to the direction being measured * or INFINITE if unbounded (pixels). * @return returns the preferred minimum size (pixels). * This is a width if width == true or a height if width == false. */ private int computePreferredMinimumSize(boolean width, int availablePerpendicular) { int minSize; int hint = availablePerpendicular == INFINITE ? SWT.DEFAULT : availablePerpendicular; if (width) { minSize = folder.getTabFolder().computeSize(SWT.DEFAULT, hint).x; } else { minSize = folder.getTabFolder().computeSize(hint, SWT.DEFAULT).y; } return minSize; } /* (non-Javadoc) * @see org.eclipse.ui.ISizeProvider#computePreferredSize(boolean, int, int, int) */ public int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredResult) { // If there is exactly one part in the stack, this just returns the // preferred size of the part as the preferred size of the stack. IPresentablePart[] parts = getSite().getPartList(); if (parts.length == 1 && parts[0] != null && !(getSite().getState() == IStackPresentationSite.STATE_MINIMIZED)) { int partSize = parts[0].computePreferredSize(width, availableParallel, availablePerpendicular, preferredResult); if (partSize == INFINITE) return partSize; // Adjust preferred size to take into account tab and border trim. int minSize = computePreferredMinimumSize(width, availablePerpendicular); if (width) { // PaneFolder adds some bogus tab spacing, so just find the maximum width. partSize = Math.max(minSize, partSize); } else { // Add them (but only if there's enough room) if (INFINITE-minSize > partSize) partSize += minSize; } return partSize; } if (preferredResult != INFINITE || getSite().getState() == IStackPresentationSite.STATE_MINIMIZED) { int minSize = computePreferredMinimumSize(width, availablePerpendicular); if (getSite().getState() == IStackPresentationSite.STATE_MINIMIZED) { return minSize; } return Math.max(minSize, preferredResult); } return INFINITE; } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean) */ public int getSizeFlags(boolean width) { int flags = 0; // If there is exactly one part in the stack, // then take into account the size flags of the part. IPresentablePart[] parts = getSite().getPartList(); if (parts.length == 1 && parts[0] != null) { flags |= parts[0].getSizeFlags(width); } return flags | super.getSizeFlags(width); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#showPartList() */ public void showPartList() { if (partList != null) { final int numberOfParts = folder.getTabFolder().getItemCount(); if (numberOfParts > 0) { partList.show(getControl(), folder.getTabFolder() .getPartListLocation(), getSite().getSelectedPart()); } } } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#dispose() */ public void dispose() { // Dispose the tab folder's widgetry folder.getTabFolder().getControl().dispose(); } /** * Called when the tab folder is disposed. */ private void presentationDisposed() { apiPreferences.dispose(); themePreferences.dispose(); Iterator iter = prefs.values().iterator(); while(iter.hasNext()) { PropertyMapAdapter next = (PropertyMapAdapter)iter.next(); next.dispose(); } if (systemMenu != null) { systemMenu.dispose(); } if (partList != null) { partList.dispose(); } systemMenu = null; partList = null; } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#setActive(int) */ public void setActive(int newState) { folder.getTabFolder().setActive(newState); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#setVisible(boolean) */ public void setVisible(boolean isVisible) { IPresentablePart current = getSite().getSelectedPart(); if (current != null) { current.setVisible(isVisible); } folder.setVisible(isVisible); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#setState(int) */ public void setState(int state) { folder.getTabFolder().setState(state); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#getControl() */ public Control getControl() { return folder.getTabFolder().getControl(); } /** * @return AbstractTabFolder the presentation's tab folder */ public AbstractTabFolder getTabFolder() { return folder.getTabFolder(); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#addPart(org.eclipse.ui.presentations.IPresentablePart, java.lang.Object) */ public void addPart(IPresentablePart newPart, Object cookie) { ignoreSelectionChanges++; try { if (initializing) { tabs.addInitial(newPart); } else { if (cookie == null) { tabs.add(newPart); } else { int insertionPoint = dragBehavior .getInsertionPosition(cookie); tabs.insert(newPart, insertionPoint); } } } finally { ignoreSelectionChanges--; } if (tabs.getPartList().length == 1) { if (newPart.getSizeFlags(true) != 0 || newPart.getSizeFlags(false) != 0) { getSite().flushLayout(); } } } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#movePart(org.eclipse.ui.presentations.IPresentablePart, java.lang.Object) */ public void movePart(IPresentablePart toMove, Object cookie) { ignoreSelectionChanges++; try { int insertionPoint = dragBehavior.getInsertionPosition(cookie); if (insertionPoint == folder.indexOf(toMove)) { return; } tabs.move(toMove, insertionPoint); } finally { ignoreSelectionChanges--; } } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#removePart(org.eclipse.ui.presentations.IPresentablePart) */ public void removePart(IPresentablePart oldPart) { ignoreSelectionChanges++; try { tabs.remove(oldPart); } finally { ignoreSelectionChanges--; } } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#selectPart(org.eclipse.ui.presentations.IPresentablePart) */ public void selectPart(IPresentablePart toSelect) { initializing = false; tabs.select(toSelect); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#dragOver(org.eclipse.swt.widgets.Control, org.eclipse.swt.graphics.Point) */ public StackDropResult dragOver(Control currentControl, Point location) { return dragBehavior.dragOver(currentControl, location, dragStart); } public void showSystemMenu() { showSystemMenu(folder.getTabFolder().getSystemMenuLocation(), getSite().getSelectedPart()); } public void showSystemMenu(Point displayCoordinates, IPresentablePart context) { if (context != getSite().getSelectedPart()) { getSite().selectPart(context); } systemMenu.show(getControl(), displayCoordinates, context); } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#showPaneMenu() */ public void showPaneMenu() { IPresentablePart part = getSite().getSelectedPart(); if (part != null) { showPaneMenu(part, folder.getTabFolder().getPaneMenuLocation()); } } public void showPaneMenu(IPresentablePart part, Point location) { Assert.isTrue(!isDisposed()); IPartMenu menu = part.getMenu(); if (menu != null) { menu.showMenu(location); } } /* (non-Javadoc) * @see org.eclipse.ui.presentations.StackPresentation#getTabList(org.eclipse.ui.presentations.IPresentablePart) */ public Control[] getTabList(IPresentablePart part) { ArrayList list = new ArrayList(); if (folder.getTabFolder().getTabPosition() == SWT.BOTTOM) { if (part.getControl() != null) { list.add(part.getControl()); } } list.add(folder.getTabFolder().getControl()); if (part.getToolBar() != null) { list.add(part.getToolBar()); } if (folder.getTabFolder().getTabPosition() == SWT.TOP) { if (part.getControl() != null) { list.add(part.getControl()); } } return (Control[]) list.toArray(new Control[list.size()]); } public void setPartList(ISystemMenu menu) { this.partList = menu; } public IDynamicPropertyMap getTheme() { return themePreferences; } public IDynamicPropertyMap getApiPreferences() { return apiPreferences; } public IDynamicPropertyMap getPluginPreferences(Plugin toQuery) { String id = toQuery.getBundle().getSymbolicName(); IDynamicPropertyMap result = (IDynamicPropertyMap)prefs.get(id); if (result != null) { return result; } result = new PreferencesAdapter(toQuery.getPluginPreferences()); prefs.put(id, result); return result; } /** * Move the tabs around. This is for testing <b>ONLY</b>. * @param part the part to move * @param index the new index * @since 3.2 */ public void moveTab(IPresentablePart part, int index) { tabs.move(part, index); folder.layout(true); } /** * Get the tab list. This is for testing <b>ONLY</b>. * @return the presentable parts in order. * @since 3.2 */ public IPresentablePart[] getPartList() { return tabs.getPartList(); } /** * Cause the folder to hide or show its * Minimize and Maximize affordances. * * @param show * <code>true</code> - the min/max buttons are visible. * @since 3.3 */ public void showMinMax(boolean show) { folder.getTabFolder().showMinMax(show); } }