| /******************************************************************************* |
| * Copyright (c) 2004, 2006 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 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.presentations.util; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.jface.util.Geometry; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.ShellAdapter; |
| import org.eclipse.swt.events.ShellEvent; |
| import org.eclipse.swt.events.ShellListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.ui.IPropertyListener; |
| import org.eclipse.ui.internal.dnd.DragUtil; |
| import org.eclipse.ui.internal.dnd.SwtUtil; |
| import org.eclipse.ui.presentations.IPresentablePart; |
| import org.eclipse.ui.presentations.IStackPresentationSite; |
| |
| /** |
| * @since 3.1 |
| */ |
| public final class PresentablePartFolder implements IPresentablePartList { |
| private AbstractTabFolder folder; |
| private IPresentablePart current; |
| //private ProxyControl toolbarProxy; |
| private Control contentProxy; |
| private static PartInfo tempPartInfo = new PartInfo(); |
| |
| /** |
| * Movement listener. Updates the bounds of the target to match the |
| * bounds of the dummy control. |
| */ |
| private ControlListener contentListener = new ControlListener() { |
| |
| public void controlMoved(ControlEvent e) { |
| layoutContent(); |
| } |
| |
| public void controlResized(ControlEvent e) { |
| } |
| |
| }; |
| |
| private ShellListener shellListener = new ShellAdapter() { |
| public void shellActivated(ShellEvent e) { |
| folder.shellActive(true); |
| } |
| |
| public void shellDeactivated(ShellEvent e) { |
| folder.shellActive(false); |
| } |
| }; |
| |
| /** |
| * Listener attached to all child parts. It responds to changes in part properties |
| */ |
| private IPropertyListener childPropertyChangeListener = new IPropertyListener() { |
| public void propertyChanged(Object source, int property) { |
| |
| if (source instanceof IPresentablePart) { |
| IPresentablePart part = (IPresentablePart) source; |
| |
| childPropertyChanged(part, property); |
| } |
| } |
| }; |
| |
| /** |
| * Dispose listener that is attached to the main control. It triggers cleanup of |
| * any listeners. This is required to prevent memory leaks. |
| */ |
| private DisposeListener tabDisposeListener = new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| if (e.widget == folder.getControl()) { |
| // If we're disposing the main control... |
| disposed(); |
| } |
| } |
| }; |
| private List partList = new ArrayList(4); |
| |
| public PresentablePartFolder(AbstractTabFolder folder) { |
| this.folder = folder; |
| |
| folder.getControl().getShell().addShellListener(shellListener); |
| folder.shellActive(folder.getControl().getDisplay() |
| .getActiveShell() == folder.getControl().getShell()); |
| |
| folder.getControl().addDisposeListener(tabDisposeListener); |
| |
| //toolbarProxy = new ProxyControl(folder.getToolbarParent()); |
| |
| // NOTE: if the shape of contentProxy changes, the fix for bug 85899 in EmptyTabFolder.computeSize may need adjustment. |
| contentProxy = new Composite(folder.getContentParent(), SWT.NONE); |
| contentProxy.setVisible(false); |
| for (Control current = contentProxy; current != folder.getControl().getParent(); current = current.getParent()) { |
| current.addControlListener(contentListener); |
| } |
| folder.setContent(contentProxy); |
| |
| } |
| |
| /** |
| * |
| * @since 3.1 |
| */ |
| private void layoutContent() { |
| if (current != null) { |
| Rectangle clientArea = DragUtil.getDisplayBounds(contentProxy); |
| |
| current.setBounds(Geometry.toControl(folder.getControl().getParent(), clientArea)); |
| } |
| } |
| |
| /** |
| * |
| * @since 3.1 |
| */ |
| protected void disposed() { |
| folder.getControl().getShell().removeShellListener(shellListener); |
| Iterator iter = partList.iterator(); |
| while(iter.hasNext()) { |
| IPresentablePart next = (IPresentablePart)iter.next(); |
| |
| next.removePropertyListener(childPropertyChangeListener); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.internal.presentations.util.IPresentablePartList#getPartList() |
| */ |
| public IPresentablePart[] getPartList() { |
| AbstractTabItem[] items = folder.getItems(); |
| IPresentablePart[] result = new IPresentablePart[items.length]; |
| |
| for (int i = 0; i < items.length; i++) { |
| result[i] = getPartForTab(items[i]); |
| |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Adds the given presentable part directly into this presentation at the |
| * given index. Does nothing if a tab already exists for the given part. |
| * This is intended to be called by TabOrder and its subclasses. |
| * |
| * @param part part to add |
| * @param idx index to insert at |
| */ |
| public void insert(IPresentablePart part, int idx) { |
| Assert.isTrue(!folder.getControl().isDisposed()); |
| |
| if (getTab(part) != null) { |
| if(indexOf(part) != idx) |
| move(part, idx); |
| return; |
| } |
| |
| idx = Math.min(idx, folder.getItemCount()); |
| |
| AbstractTabItem item; |
| |
| int style = SWT.NONE; |
| |
| if (part.isCloseable()) { |
| style |= SWT.CLOSE; |
| } |
| |
| item = folder.add(idx, style); |
| |
| item.setData(part); |
| |
| initTab(item, part); |
| |
| part.addPropertyListener(childPropertyChangeListener); |
| partList.add(part); |
| } |
| |
| public void remove(IPresentablePart toRemove) { |
| if (toRemove == current) { |
| select(null); |
| } |
| |
| internalRemove(toRemove); |
| } |
| |
| private void internalRemove(IPresentablePart toRemove) { |
| AbstractTabItem item = getTab(toRemove); |
| if (item != null) { |
| item.dispose(); |
| } |
| if (partList.contains(toRemove)) { |
| toRemove.removePropertyListener(childPropertyChangeListener); |
| partList.remove(toRemove); |
| } |
| } |
| |
| /** |
| * Moves the given part to the given index. When this method returns, |
| * indexOf(part) will return newIndex. |
| * |
| * @param part |
| * @param newIndex |
| */ |
| public void move(IPresentablePart part, int newIndex) { |
| int currentIndex = indexOf(part); |
| |
| if (currentIndex == newIndex) { |
| return; |
| } |
| |
| internalRemove(part); |
| insert(part, newIndex); |
| |
| if (current == part) { |
| folder.setSelection(getTab(part)); |
| } |
| } |
| |
| /** |
| * Returns the number of parts in this folder |
| */ |
| public int size() { |
| return folder.getItemCount(); |
| } |
| |
| public void setBounds(Rectangle bounds) { |
| Point minSize = folder.computeSize(bounds.width, SWT.DEFAULT); |
| |
| if (folder.getState() == IStackPresentationSite.STATE_MINIMIZED && minSize.y < bounds.height) { |
| bounds = Geometry.copy(bounds); |
| bounds.height = minSize.y; |
| } |
| |
| // Set the tab folder's bounds |
| folder.getControl().setBounds(bounds); |
| |
| layout(false); |
| } |
| |
| public void select(IPresentablePart toSelect) { |
| |
| if (toSelect == current) { |
| return; |
| } |
| |
| if (toSelect != null) { |
| toSelect.setVisible(true); |
| } |
| |
| if (current != null) { |
| current.setVisible(false); |
| } |
| |
| current = toSelect; |
| |
| AbstractTabItem selectedItem = getTab(toSelect); |
| |
| folder.setSelection(selectedItem); |
| |
| if (selectedItem != null) { |
| // Determine if we need to un-bold this tab |
| selectedItem.setBold(false); |
| initTab(selectedItem, toSelect); |
| } else { |
| setToolbar(null); |
| } |
| |
| layout(true); |
| } |
| |
| private void setToolbar(Control newToolbar) { |
| if (folder.getToolbar() != newToolbar) { |
| folder.setToolbar(newToolbar); |
| } |
| } |
| private boolean isVisible = true; |
| |
| |
| private void childPropertyChanged(IPresentablePart part, int property) { |
| AbstractTabItem tab = getTab(part); |
| // If we're in the process of removing this part, it's possible that we might still receive |
| // some events for it. If everything is working perfectly, this should never happen... however, |
| // we check for this case just to be safe. |
| if (tab == null) { |
| return; |
| } |
| |
| switch (property) { |
| case IPresentablePart.PROP_HIGHLIGHT_IF_BACK: |
| if (getCurrent() != part) {//Set bold if it doesn't currently have focus |
| tab.setBold(true); |
| initTab(tab, part); |
| } |
| break; |
| |
| case IPresentablePart.PROP_TOOLBAR: |
| if (getCurrent() == part) { |
| folder.flushToolbarSize(); |
| } |
| /* falls through */ |
| case IPresentablePart.PROP_CONTENT_DESCRIPTION: |
| case IPresentablePart.PROP_PANE_MENU: |
| case IPresentablePart.PROP_TITLE: |
| initTab(tab, part); |
| if (getCurrent() == part) { |
| layout(true); |
| } |
| break; |
| default: |
| initTab(tab, part); |
| } |
| } |
| |
| protected void initTab(AbstractTabItem item, IPresentablePart part) { |
| tempPartInfo.set(part); |
| item.setInfo(tempPartInfo); |
| |
| item.setBusy(part.isBusy()); |
| if (part == getCurrent()) { |
| folder.setSelectedInfo(tempPartInfo); |
| folder.enablePaneMenu(part.getMenu() != null); |
| |
| setToolbar(part.getToolBar()); |
| } |
| } |
| |
| public boolean isDisposed() { |
| return SwtUtil.isDisposed(folder.getControl()); |
| } |
| |
| public IPresentablePart getPartForTab(AbstractTabItem tab) { |
| Assert.isTrue(!isDisposed()); |
| |
| if (tab == null) { |
| return null; |
| } |
| |
| IPresentablePart part = (IPresentablePart) tab.getData(); |
| |
| return part; |
| } |
| |
| /** |
| * Returns the tab for the given part, or null if there is no such tab |
| * |
| * @param part the part being searched for |
| * @return the tab for the given part, or null if there is no such tab |
| */ |
| public AbstractTabItem getTab(IPresentablePart part) { |
| Assert.isTrue(!isDisposed()); |
| |
| return folder.findItem(part); |
| } |
| |
| |
| |
| public int indexOf(IPresentablePart part) { |
| AbstractTabItem item = getTab(part); |
| |
| if (item == null) { |
| return -1; |
| } |
| |
| return folder.indexOf(item); |
| } |
| |
| public AbstractTabFolder getTabFolder() { |
| return folder; |
| } |
| |
| public void setVisible(boolean isVisible) { |
| this.isVisible = isVisible; |
| getTabFolder().setVisible(isVisible); |
| if (isVisible) { |
| layout(true); |
| } |
| } |
| |
| public void layout(boolean changed) { |
| if (!isVisible) { |
| // Don't bother with layout if we're not visible |
| return; |
| } |
| // Lay out the tab folder and compute the client area |
| folder.layout(changed); |
| |
| //toolbarProxy.layout(); |
| |
| layoutContent(); |
| } |
| |
| public IPresentablePart getCurrent() { |
| return current; |
| } |
| } |