blob: a9a072a63054e60a36655833494ae38bea0c3132 [file] [log] [blame]
package org.eclipse.ui.internal;
/**********************************************************************
Copyright (c) 2000, 2001, 2002, International Business Machines Corp and others.
All rights reserved.   This program and the accompanying materials
are made available under the terms of the Common Public License v0.5
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/cpl-v05.html
 
Contributors:
Cagatay Kavukcuoglu <cagatayk@acm.org>
- Fix for bug 10025 - Resizing views should not use height ratios
**********************************************************************/
import org.eclipse.ui.*;
import org.eclipse.ui.internal.registry.IViewDescriptor;
import org.eclipse.ui.internal.registry.IViewRegistry;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.jface.window.Window;
import java.util.*;
public class PartTabFolder extends LayoutPart
implements ILayoutContainer
{
private static int tabLocation = -1; // Initialized in constructor.
private CTabFolder tabFolder;
private Map mapTabToPart = new HashMap();
private LayoutPart current;
private Map mapPartToDragMonitor = new HashMap();
private boolean assignFocusOnSelection = true;
// inactiveCurrent is only used when restoring the persisted state of
// perspective on startup.
private LayoutPart inactiveCurrent;
private Composite parent;
private boolean active = false;
// listen for mouse down on tab to set focus.
private MouseListener mouseListener = new MouseAdapter() {
public void mouseDown(MouseEvent e) {
// PR#1GDEZ25 - If selection will change in mouse up ignore mouse down.
// Else, set focus.
CTabItem newItem = tabFolder.getItem(new Point(e.x, e.y));
if (newItem != null) {
CTabItem oldItem = tabFolder.getSelection();
if (newItem != oldItem)
return;
}
if (PartTabFolder.this.current != null)
PartTabFolder.this.current.setFocus();
}
};
private class TabInfo {
private String tabText;
private LayoutPart part;
}
TabInfo[] invisibleChildren;
/**
* PartTabFolder constructor comment.
*/
public PartTabFolder() {
super("PartTabFolder");//$NON-NLS-1$
setID(this.toString()); // Each folder has a unique ID so relative positioning is unambiguous.
// Get the location of the tabs from the preferences
if (tabLocation == -1)
tabLocation = getPreferenceStore().getInt(
IPreferenceConstants.VIEW_TAB_POSITION);
}
/**
* Add a part at an index.
*/
public void add(String name, int index, LayoutPart part)
{
if (active && !(part instanceof PartPlaceholder)) {
CTabItem tab = createPartTab(part, name, index);
index = tabFolder.indexOf(tab);
setSelection(index);
}
else {
TabInfo info = new TabInfo();
info.tabText = name;
info.part = part;
invisibleChildren = arrayAdd(invisibleChildren, info, index);
if (active)
part.setContainer(this);
}
}
/**
* See IVisualContainer#add
*/
public void add(LayoutPart child) {
int index = getItemCount();
String label = "";//$NON-NLS-1$
if (child instanceof PartPane) {
String id = ((PartPane)child).getPartReference().getId();
IViewRegistry reg = WorkbenchPlugin.getDefault().getViewRegistry();
IViewDescriptor desc = reg.find(id);
if(desc != null)
label = desc.getLabel();
else
label = ((PartPane)child).getPartReference().getTitle();
}
add(label, index, child);
}
/**
* See ILayoutContainer::allowBorder
*
* There is already a border around the tab
* folder so no need for one from the parts.
*/
public boolean allowsBorder() {
return mapTabToPart.size() <= 1;
}
private TabInfo[] arrayAdd(TabInfo[] array, TabInfo item, int index) {
if (item == null) return array;
TabInfo[] result = null;
if (array == null) {
result = new TabInfo[1];
result[0] = item;
} else {
if (index >= array.length) index = array.length;
result = new TabInfo[array.length + 1];
System.arraycopy(array, 0, result, 0, index);
result[index] = item;
System.arraycopy(array, index, result, index + 1, array.length - index);
}
return result;
}
private TabInfo[] arrayRemove(TabInfo[] array, LayoutPart item) {
if (item == null) return array;
TabInfo[] result = null;
int index = -1;
for (int i = 0, length = array.length; i < length; i++){
if (item == array[i].part){
index = i;
break;
}
}
if (index == -1) return array;
if (array.length > 1) {
result = new TabInfo[array.length - 1];
System.arraycopy(array, 0, result, 0, index);
System.arraycopy(array, index+1, result, index, result.length - index);
}
return result;
}
/**
* Set the default bounds of a page in a CTabFolder.
*/
protected static Rectangle calculatePageBounds(CTabFolder folder) {
if (folder == null)
return new Rectangle(0,0,0,0);
Rectangle bounds = folder.getBounds();
Rectangle offset = folder.getClientArea();
bounds.x += offset.x;
bounds.y += offset.y;
bounds.width = offset.width;
bounds.height = offset.height;
return bounds;
}
public void createControl(Composite parent) {
if (tabFolder != null) return;
// Create control.
this.parent = parent;
tabFolder = new CTabFolder(parent, tabLocation | SWT.BORDER);
// listener to switch between visible tabItems
tabFolder.addListener(SWT.Selection, new Listener(){
public void handleEvent(Event e){
LayoutPart item = (LayoutPart)mapTabToPart.get(e.item);
// Item can be null when tab is just created but not map yet.
if (item != null) {
setSelection(item);
if (assignFocusOnSelection)
item.setFocus();
}
}
});
// listener to resize visible components
tabFolder.addListener(SWT.Resize, new Listener(){
public void handleEvent(Event e){
setControlSize(current);
}
});
// listen for mouse down on tab to set focus.
tabFolder.addMouseListener(this.mouseListener);
// enable for drag & drop target
tabFolder.setData((IPartDropTarget)this);
// Create pages.
if (invisibleChildren != null) {
TabInfo[] stillInactive = new TabInfo[0];
int tabCount = 0;
for (int i = 0, length = invisibleChildren.length; i < length; i++){
if (invisibleChildren[i].part instanceof PartPlaceholder) {
invisibleChildren[i].part.setContainer(this);
TabInfo[] newStillInactive = new TabInfo[stillInactive.length + 1];
System.arraycopy(stillInactive, 0, newStillInactive, 0, stillInactive.length);
newStillInactive[stillInactive.length] = invisibleChildren[i];
stillInactive = newStillInactive;
}
else {
createPartTab(invisibleChildren[i].part, invisibleChildren[i].tabText, tabCount);
++ tabCount;
}
}
invisibleChildren = stillInactive;
}
active = true;
// Set current page.
if (getItemCount() > 0) {
int newPage = 0;
if (current != null)
newPage = indexOf(current);
setSelection(newPage);
}
}
private CTabItem createPartTab(LayoutPart part, String tabName, int tabIndex) {
CTabItem tabItem;
if (tabIndex < 0)
tabItem = new CTabItem(this.tabFolder, SWT.NONE);
else
tabItem = new CTabItem(this.tabFolder, SWT.NONE, tabIndex);
tabItem.setText(tabName);
mapTabToPart.put(tabItem, part);
part.createControl(this.parent);
part.setContainer(this);
// Because the container's allow border api
// is dependent on the number of tabs it has,
// reset the container so the parts can update.
if (mapTabToPart.size() == 2) {
Iterator parts = mapTabToPart.values().iterator();
((LayoutPart)parts.next()).setContainer(this);
((LayoutPart)parts.next()).setContainer(this);
}
return tabItem;
}
/**
* Remove the ability to d&d using the tab
*
* See PerspectivePresentation
*/
public void disableDrag(LayoutPart part) {
PartDragDrop partDragDrop = (PartDragDrop)mapPartToDragMonitor.get(part);
if (partDragDrop != null) {
partDragDrop.dispose();
mapPartToDragMonitor.remove(part);
}
// remove d&d on folder when no tabs left
if (mapPartToDragMonitor.size() == 1) {
partDragDrop = (PartDragDrop)mapPartToDragMonitor.get(this);
if (partDragDrop != null) {
partDragDrop.dispose();
mapPartToDragMonitor.remove(this);
}
}
}
/**
* See LayoutPart#dispose
*/
public void dispose() {
if (!active) return;
// combine active and inactive entries into one
TabInfo[] newInvisibleChildren = new TabInfo[mapTabToPart.size()];
if (invisibleChildren != null){
// tack the inactive ones on at the end
newInvisibleChildren = new TabInfo[newInvisibleChildren.length + invisibleChildren.length];
System.arraycopy(invisibleChildren, 0, newInvisibleChildren, mapTabToPart.size(), invisibleChildren.length);
}
Iterator keys = mapTabToPart.keySet().iterator();
while(keys.hasNext()) {
CTabItem item = (CTabItem)keys.next();
LayoutPart part = (LayoutPart)mapTabToPart.get(item);
TabInfo info = new TabInfo();
info.tabText = item.getText();
info.part = part;
newInvisibleChildren[tabFolder.indexOf(item)] = info;
disableDrag(part);
}
invisibleChildren = newInvisibleChildren;
if (invisibleChildren != null) {
for (int i = 0, length = invisibleChildren.length; i < length; i++){
invisibleChildren[i].part.setContainer(null);
}
}
mapTabToPart.clear();
if (tabFolder != null)
tabFolder.dispose();
tabFolder = null;
active = false;
}
/**
* Enable the view pane to be d&d via its tab
*
* See PerspectivePresentation::enableDrag
*/
public void enableDrag(ViewPane pane, IPartDropListener listener) {
// make sure its not already registered
if (mapPartToDragMonitor.containsKey(pane))
return;
CTabItem tab = getTab(pane);
if (tab == null)
return;
CTabPartDragDrop dragSource = new CTabPartDragDrop(pane, this.tabFolder, tab);
mapPartToDragMonitor.put(pane, dragSource);
dragSource.addDropListener(listener);
// register d&d on empty tab area the first time thru
if (mapPartToDragMonitor.size() == 1) {
dragSource = new CTabPartDragDrop(this, this.tabFolder, null);
mapPartToDragMonitor.put(this, dragSource);
dragSource.addDropListener(listener);
}
}
/**
* Open the tracker to allow the user to move
* the specified part using keyboard.
*/
public void openTracker(LayoutPart part) {
CTabPartDragDrop dnd = (CTabPartDragDrop)mapPartToDragMonitor.get(part);
dnd.openTracker();
}
/**
* Gets the presentation bounds.
*/
public Rectangle getBounds() {
return tabFolder.getBounds();
}
// getMinimumHeight() added by cagatayk@acm.org
/**
* @see LayoutPart#getMinimumHeight()
*/
public int getMinimumHeight() {
if (current == null || tabFolder == null || tabFolder.isDisposed())
return super.getMinimumHeight();
if (getItemCount() > 1) {
Rectangle trim = tabFolder.computeTrim(0, 0, 0, current.getMinimumHeight());
return trim.height;
}
else
return current.getMinimumHeight();
}
/**
* See IVisualContainer#getChildren
*/
public LayoutPart[] getChildren() {
LayoutPart [] children = new LayoutPart[0];
if (invisibleChildren != null) {
children = new LayoutPart[invisibleChildren.length];
for (int i = 0, length = invisibleChildren.length; i < length; i++){
children[i] = invisibleChildren[i].part;
}
}
int count = mapTabToPart.size();
if (count > 0) {
int index = children.length;
LayoutPart [] newChildren = new LayoutPart[children.length + count];
System.arraycopy(children, 0, newChildren, 0, children.length);
children = newChildren;
for (int nX = 0; nX < count; nX ++) {
CTabItem tabItem = tabFolder.getItem(nX);
children[index] = (LayoutPart)mapTabToPart.get(tabItem);
index++;
}
}
return children;
}
public Control getControl() {
return tabFolder;
}
/**
* Answer the number of children.
*/
public int getItemCount() {
if (active)
return tabFolder.getItemCount();
else if (invisibleChildren != null)
return invisibleChildren.length;
else
return 0;
}
/**
* Get the parent control.
*/
public Composite getParent() {
return tabFolder.getParent();
}
public int getSelection() {
if (!active) return 0;
return tabFolder.getSelectionIndex();
}
/**
* Returns the tab for a part.
*/
private CTabItem getTab(LayoutPart child) {
Iterator tabs = mapTabToPart.keySet().iterator();
while (tabs.hasNext()) {
CTabItem tab = (CTabItem) tabs.next();
if (mapTabToPart.get(tab) == child)
return tab;
}
return null;
}
/**
* Returns the visible child.
*/
public LayoutPart getVisiblePart() {
return current;
}
public int indexOf (LayoutPart item) {
Iterator keys = mapTabToPart.keySet().iterator();
while (keys.hasNext()) {
CTabItem tab = (CTabItem)keys.next();
LayoutPart part = (LayoutPart)mapTabToPart.get(tab);
if (part.equals(item))
return tabFolder.indexOf(tab);
}
return 0;
}
/**
* Returns true if this part is visible. A part is visible if it has a control.
*/
public boolean isVisible() {
return (tabFolder != null);
}
/**
* See IVisualContainer#remove
*/
public void remove(LayoutPart child) {
if (active && !(child instanceof PartPlaceholder)) {
Iterator keys = mapTabToPart.keySet().iterator();
while (keys.hasNext()) {
CTabItem key = (CTabItem) keys.next();
if (mapTabToPart.get(key).equals(child)) {
removeTab(key);
break;
}
}
} else if (invisibleChildren != null) {
invisibleChildren = arrayRemove(invisibleChildren, child);
}
if (active) {
child.setBounds(0, 0, 0, 0);
child.setContainer(null);
}
}
private void removeTab(CTabItem tab) {
// disable any d&d based on this tab
LayoutPart part = (LayoutPart)mapTabToPart.get(tab);
if (part != null)
disableDrag(part);
// remove the tab now
// Note, that disposing of the tab causes the
// tab folder to select the next tab and fires
// a selection event. In this situation, do
// not assign focus.
assignFocusOnSelection = false;
mapTabToPart.remove(tab);
tab.dispose();
assignFocusOnSelection = true;
// Because the container's allow border api
// is dependent on the number of tabs it has,
// reset the container so the parts can update.
if (mapTabToPart.size() == 1) {
Iterator parts = mapTabToPart.values().iterator();
((LayoutPart)parts.next()).setContainer(this);
}
}
/**
* Reorder the tab representing the specified pane.
* If a tab exists under the specified x,y location,
* then move the tab before it, otherwise place it
* as the last tab.
*/
public void reorderTab(ViewPane pane, int x, int y) {
CTabItem sourceTab = getTab(pane);
if (sourceTab == null)
return;
// adjust the y coordinate to fall within the tab area
Point location = new Point(1, 1);
if ((tabFolder.getStyle() & SWT.BOTTOM) != 0)
location.y = tabFolder.getSize().y - 4; // account for 3 pixel border
// adjust the x coordinate to be within the tab area
if (x > location.x)
location.x = x;
// find the tab under the adjusted location.
CTabItem targetTab = tabFolder.getItem(location);
// no tab under location so move view's tab to end
if (targetTab == null) {
// do nothing if already at the end
if (tabFolder.indexOf(sourceTab) != tabFolder.getItemCount() - 1)
reorderTab(pane, sourceTab, -1);
return;
}
// do nothing if over view's own tab
if (targetTab == sourceTab)
return;
// do nothing if already before target tab
int sourceIndex = tabFolder.indexOf(sourceTab);
int targetIndex = tabFolder.indexOf(targetTab);
if (sourceIndex == targetIndex - 1)
return;
reorderTab(pane, sourceTab, targetIndex);
}
/**
* Reorder the tab representing the specified pane.
*/
private void reorderTab(ViewPane pane, CTabItem sourceTab, int newIndex) {
// remember if the source tab was the visible one
boolean wasVisible = (tabFolder.getSelection() == sourceTab);
// create the new tab at the specified index
CTabItem newTab;
if (newIndex < 0)
newTab = new CTabItem(tabFolder, SWT.NONE);
else
newTab = new CTabItem(tabFolder, SWT.NONE, newIndex);
// map it now before events start coming in...
mapTabToPart.put(newTab, pane);
// update the drag & drop
CTabPartDragDrop partDragDrop = (CTabPartDragDrop)mapPartToDragMonitor.get(pane);
partDragDrop.setTab(newTab);
// dispose of the old tab and remove it
String sourceLabel = sourceTab.getText();
mapTabToPart.remove(sourceTab);
assignFocusOnSelection = false;
sourceTab.dispose();
assignFocusOnSelection = true;
// update the new tab's title and visibility
newTab.setText(sourceLabel);
if (wasVisible) {
tabFolder.setSelection(newTab);
setSelection(pane);
pane.setFocus();
}
}
/**
* Reparent a part. Also reparent visible children...
*/
public void reparent(Composite newParent) {
if (!newParent.isReparentable())
return;
Control control = getControl();
if ((control == null) || (control.getParent() == newParent))
return;
super.reparent(newParent);
// reparent also the visible children.
Iterator enum = mapTabToPart.values().iterator();
while (enum.hasNext())
((LayoutPart)enum.next()).reparent(newParent);
}
/**
* See IVisualContainer#replace
*/
public void replace(LayoutPart oldChild, LayoutPart newChild) {
if ((oldChild instanceof PartPlaceholder) && !(newChild instanceof PartPlaceholder)){
replaceChild((PartPlaceholder)oldChild, newChild);
return;
}
if (!(oldChild instanceof PartPlaceholder) && (newChild instanceof PartPlaceholder)){
replaceChild(oldChild, (PartPlaceholder)newChild);
return;
}
}
private void replaceChild(LayoutPart oldChild, PartPlaceholder newChild) {
// remove old child from display
if (active) {
Iterator keys = mapTabToPart.keySet().iterator();
while (keys.hasNext()) {
CTabItem key = (CTabItem)keys.next();
LayoutPart part = (LayoutPart)mapTabToPart.get(key);
if (part == oldChild) {
boolean partIsActive = (current == oldChild);
TabInfo info = new TabInfo();
info.part = newChild;
info.tabText = key.getText();
removeTab(key);
int index = 0;
if (invisibleChildren != null)
index = invisibleChildren.length;
invisibleChildren = arrayAdd(invisibleChildren, info, index);
oldChild.setBounds(new Rectangle(0, 0, 0, 0));
oldChild.setContainer(null);
newChild.setContainer(this);
if (tabFolder.getItemCount() > 0 && !partIsActive) {
setControlSize(current);
}
break;
}
}
} else if (invisibleChildren != null) {
for (int i = 0, length = invisibleChildren.length; i < length; i++){
if (invisibleChildren[i].part == oldChild) {
invisibleChildren[i].part = newChild;
}
}
}
}
private void replaceChild(PartPlaceholder oldChild, LayoutPart newChild) {
if (invisibleChildren == null) return;
for (int i = 0, length = invisibleChildren.length; i < length; i++) {
if (invisibleChildren[i].part == oldChild) {
if (active) {
TabInfo info = invisibleChildren[i];
invisibleChildren = arrayRemove(invisibleChildren, oldChild);
oldChild.setContainer(null);
if (newChild instanceof PartPane)
info.tabText = ((PartPane)newChild).getPartReference().getTitle();
CTabItem item = createPartTab(newChild, info.tabText, -1);
int index = tabFolder.indexOf(item);
setSelection(index);
} else {
invisibleChildren[i].part = newChild;
// On restore, all views are initially represented by placeholders and then
// they are replaced with the real views. The following code is used to preserve the active
// tab when a prespective is restored from its persisted state.
if (inactiveCurrent != null && inactiveCurrent == oldChild){
current = newChild;
inactiveCurrent = null;
}
}
break;
}
}
}
/**
* @see IPersistable
*/
public void restoreState(IMemento memento)
{
// Read the active tab.
String activeTabID = memento.getString(IWorkbenchConstants.TAG_ACTIVE_PAGE_ID);
// Read the page elements.
IMemento [] children = memento.getChildren(IWorkbenchConstants.TAG_PAGE);
if(children != null) {
// Loop through the page elements.
for (int i = 0; i < children.length; i ++) {
// Get the info details.
IMemento childMem = children[i];
String partID = childMem.getString(IWorkbenchConstants.TAG_CONTENT);
String tabText = childMem.getString(IWorkbenchConstants.TAG_LABEL);
IViewDescriptor descriptor = (IViewDescriptor)WorkbenchPlugin.getDefault().
getViewRegistry().find(partID);
if(descriptor != null)
tabText = descriptor.getLabel();
// Create the part.
LayoutPart part = new PartPlaceholder(partID);
add(tabText, i, part);
//1FUN70C: ITPUI:WIN - Shouldn't set Container when not active
part.setContainer(this);
if (partID.equals(activeTabID)) {
// Mark this as the active part.
inactiveCurrent = part;
}
}
}
}
/**
* @see IPersistable
*/
public void saveState(IMemento memento)
{
// Save the active tab.
if (current != null)
memento.putString(IWorkbenchConstants.TAG_ACTIVE_PAGE_ID, current.getID());
if(mapTabToPart.size() == 0) {
// Loop through the invisible children.
if(invisibleChildren != null) {
for (int i = 0; i < invisibleChildren.length; i ++) {
// Save the info.
// Fields in TabInfo ..
// private String tabText;
// private LayoutPart part;
TabInfo info = invisibleChildren[i];
IMemento childMem = memento.createChild(IWorkbenchConstants.TAG_PAGE);
childMem.putString(IWorkbenchConstants.TAG_LABEL, info.tabText);
childMem.putString(IWorkbenchConstants.TAG_CONTENT, info.part.getID());
}
}
} else {
LayoutPart [] children = getChildren();
CTabItem keys[] = new CTabItem[mapTabToPart.size()];
mapTabToPart.keySet().toArray(keys);
if(children != null) {
for (int i = 0; i < children.length; i ++){
IMemento childMem = memento.createChild(IWorkbenchConstants.TAG_PAGE);
childMem.putString(IWorkbenchConstants.TAG_CONTENT,children[i].getID());
boolean found = false;
for (int j = 0; j < keys.length; j++){
if(mapTabToPart.get(keys[j]) == children[i]) {
childMem.putString(IWorkbenchConstants.TAG_LABEL, keys[j].getText());
found = true;
break;
}
}
if(!found) {
for (int j = 0; j < invisibleChildren.length; j++){
if(invisibleChildren[j].part == children[i]) {
childMem.putString(IWorkbenchConstants.TAG_LABEL,invisibleChildren[j].tabText);
found = true;
break;
}
}
}
if(!found) {
childMem.putString(IWorkbenchConstants.TAG_LABEL,"LabelNotFound");//$NON-NLS-1$
}
}
}
}
}
/**
* Sets the presentation bounds.
*/
public void setBounds(Rectangle r) {
if (tabFolder != null)
tabFolder.setBounds(r);
setControlSize(current);
}
/**
* Set the size of a page in the folder.
*/
private void setControlSize(LayoutPart part) {
if (part == null || tabFolder == null)
return;
Rectangle bounds;
if (mapTabToPart.size() > 1)
bounds = calculatePageBounds(tabFolder);
else
bounds = tabFolder.getBounds();
part.setBounds(bounds);
part.moveAbove(tabFolder);
}
public void setSelection(int index) {
if (!active) return;
if (mapTabToPart.size() == 0) {
setSelection(null);
return;
}
// make sure the index is in the right range
if (index < 0) index = 0;
if (index > mapTabToPart.size() - 1) index = mapTabToPart.size() - 1;
tabFolder.setSelection(index);
CTabItem item = tabFolder.getItem(index);
LayoutPart part = (LayoutPart)mapTabToPart.get(item);
setSelection(part);
}
private void setSelection(LayoutPart part) {
if (!active) return;
if (part instanceof PartPlaceholder) return;
// Deactivate old / Activate new.
if (current != null && current != part){
current.setBounds(0, 0, 0, 0);
}
current = part;
if (current != null) {
setControlSize(current);
}
/*
* Detached window no longer supported - remove when confirmed
*
* // set the title of the detached window to reflact the active tab
* Window window = getWindow();
* if (window instanceof DetachedWindow) {
* if (current == null || !(current instanceof PartPane))
* window.getShell().setText("");//$NON-NLS-1$
* else
* window.getShell().setText(((PartPane)current).getPart().getTitle());
* }
*/
}
/**
* @see IPartDropTarget::targetPartFor
*/
public LayoutPart targetPartFor(LayoutPart dragSource) {
return this;
}
}