/*****************************************************************************
* Copyright (c) 2008 CEA LIST.
*
*
* 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:
* Cedric Dumoulin Cedric.dumoulin@lifl.fr - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.sasheditor.internal;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jface.util.Geometry;
import org.eclipse.papyrus.infra.core.sasheditor.internal.eclipsecopy.PresentationUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.internal.dnd.DragUtil;
/**
* Papyrus wrapper for CTabFolder.
* Provides miscelaneous methods for dragging.
* Provides different fireEvents for: menu detected, pageChange, itemClosed.
* TODO : add listeners mechanism to listen on events ?
*/
@SuppressWarnings("restriction")
public class PTabFolder {
/**
* The underlying tabfolder.
*/
protected CTabFolder tabFolder;
/**
* This object allows to register listeners on event from this class.
*/
private EventsManager listenersManager = new EventsManager();
private Listener dragListener = new Listener() {
public void handleEvent(Event e) {
Point globalPos = ((Control)e.widget).toDisplay(e.x, e.y);
handleDragStarted(globalPos, e);
}
};
/**
* Listener on control activated event.
* This event is used to set the tab as the active page.
*/
private Listener activateListener = new Listener() {
public void handleEvent(Event e) {
Point globalPos = ((Control)e.widget).toDisplay(e.x, e.y);
handleFolderReselected(globalPos, null);
}
};
/**
* Listen on menu detect.
* The event is forwarded.
* TODO [20100417] deprecated ?
*/
private MenuDetectListener menuDetectListener = new MenuDetectListener() {
public void menuDetected(MenuDetectEvent e) {
// Point globalPos = ((Control) e.widget).toDisplay(e.x, e.y);
Point globalPos = new Point(e.x, e.y);
handleMenuDetect(globalPos, e);
}
};
/**
* Get the underlying control.
*/
public Composite getControl() {
return tabFolder;
}
/**
* Create the corresponding SWT Control
*/
public void createPartControl(Composite parent) {
tabFolder = createContainer(parent);
// Attach listeners
attachListeners(tabFolder, false);
}
/**
* Creates an empty container. Creates a CTabFolder with no style bits set, and hooks a selection listener which calls <code>pageChange()</code>
* whenever the selected tab changes.
*
* @param parent
* The composite in which the container tab folder should be created; must not be <code>null</code>.
* @return a new container
*/
private CTabFolder createContainer(Composite parent) {
// use SWT.FLAT style so that an extra 1 pixel border is not reserved
// inside the folder
parent.setLayout(new FillLayout());
final CTabFolder newContainer = new CTabFolder(parent, SWT.BOTTOM | SWT.FLAT | SWT.CLOSE);
// TODO Move listener init in appropriate method.
newContainer.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int newPageIndex = newContainer.indexOf((CTabItem)e.item);
firePageChange(newPageIndex);
}
});
// Test for the close icon. Need style=SWT.CLOSE
// addCTabFolderListener is required :-(
newContainer.setUnselectedCloseVisible(false);
newContainer.addCTabFolder2Listener(new CTabFolder2Adapter() {
@Override
public void close(CTabFolderEvent event) {
int pageIndex = newContainer.indexOf((CTabItem)event.item);
event.doit = false;
fireItemClosed(event, pageIndex);
}
});
return newContainer;
}
/**
* Dispose internal resources.
*/
public void dispose() {
if(tabFolder.isDisposed())
return;
detachListeners(tabFolder);
tabFolder.dispose();
}
/**
* Copied from org.eclipse.ui.internal.presentations.util.AbstractTabFolder.attachListeners(Control, boolean)
*/
protected void attachListeners(CTabFolder theControl, boolean recursive) {
// Listen to menu event
theControl.addMenuDetectListener(menuDetectListener);
// Listen to drag event
PresentationUtil.addDragListener(theControl, dragListener);
theControl.addListener(SWT.Activate, activateListener);
}
/**
* Copied from org.eclipse.ui.internal.presentations.util.AbstractTabFolder.detachListeners(Control, boolean)
*/
private void detachListeners(Control theControl) {
theControl.removeMenuDetectListener(menuDetectListener);
//
PresentationUtil.removeDragListener(theControl, dragListener);
// theControl.removeDragDetectListener(dragDetectListener);
// theControl.removeListener(SWT.MouseUp, mouseUpListener);
theControl.removeListener(SWT.Activate, activateListener);
}
/**
* The context menu event has been fired, handle it.
* Actually, it is forwarded to the {@link EventsManager}.
*
* @param displayPos
* @param e
*/
protected void handleContextMenu(Point displayPos, Event e) {
CTabItem tab = getItem(displayPos);
listenersManager.fireContextMenuEvent(tab, e);
}
/**
* Called when drag start. From here, DragUtil.performDrag() is called, which start the
* dragging process. DragUtil.performDrag() will contains the tabFolder or the dragged tab.
*/
protected void handleDragStarted(Point displayPos, Event e) {
CTabItem tab = getItem(displayPos);
boolean allowSnapping = true;
Rectangle sourceBounds = Geometry.toDisplay(tabFolder.getParent(), tabFolder.getBounds());
if(tab == null) { // drag folder
DragUtil.performDrag(tabFolder, sourceBounds, displayPos, allowSnapping);
} else { // drag item
DragUtil.performDrag(tab, sourceBounds, displayPos, allowSnapping);
}
}
/**
* Handle menu detect.
* TODO Connect menu staff here.
*
* @param displayPos
* @param e
*/
private void handleMenuDetect(Point displayPos, MenuDetectEvent e) {
// if(isOnBorder(displayPos)) {
// return;
// }
CTabItem tab = getItem(displayPos);
listenersManager.fireMenuDetectEvent(tab, e);
}
/**
* Handle folder reselected.
* A folder is reselected by clicking on the active tabs, on the page or on the empty tabs area.
* In each case a PageChangeEvent is fired.
* When mouse click happen on the empty area, or on the page, the last selected tabs is used.
* Used to switch the Active tab when user click on already opened tabs.
*
* @param displayPos
* @param e
*/
private void handleFolderReselected(Point displayPos, MouseEvent e) {
int itemIndex = getItemIndex(displayPos);
// If click is not from an item, it can come from a click on border.
// restore the last selected item
if(itemIndex == -1)
itemIndex = tabFolder.getSelectionIndex();
if(itemIndex == -1)
return;
listenersManager.firePageChange(itemIndex);
}
/**
* Returns true iff the given point is on the border of the folder. By default, double-clicking,
* context menus, and drag/drop are disabled on the folder's border.
*
* @param toTest
* a point (display coordinates)
* @return true iff the point is on the presentation border
* @since 3.1
*/
private boolean isOnBorder(Point toTest) {
Control content = getControl();
if(content != null) {
Rectangle displayBounds = DragUtil.getDisplayBounds(content);
if(tabFolder.getTabPosition() == SWT.TOP) {
return toTest.y >= displayBounds.y;
}
if(toTest.y >= displayBounds.y && toTest.y < displayBounds.y + displayBounds.height) {
return true;
}
}
return false;
}
/**
* Get the item under the specified position.
*/
public CTabItem getItem(Point toFind) {
CTabItem[] items = tabFolder.getItems();
for(CTabItem item : items) {
if(getItemBounds(item).contains(toFind)) {
return item;
}
}
return null;
}
public int getItemIndex(Point pt) {
CTabItem item = tabFolder.getItem(pt);
if(item == null)
return -1;
return getItemIndex(item);
}
/**
* Get the rectangle bounding the item, in the parent coordinates. Utility method. Can be moved somewhere else.
*/
public Rectangle getItemBounds(CTabItem item) {
return Geometry.toDisplay(item.getParent(), item.getBounds());
}
/**
* Fire a page closed event. This event is fired when the close item is pressed. The item is not closed yet. By default, the item is closed after
* the event. The item is not closed if event.doit is
* set to false.
*
*/
protected void fireItemClosed(CTabFolderEvent event, int pageIndex) {
listenersManager.fireItemClosed(event, pageIndex);
}
/**
* Fire a PageChangeEvent.
*/
protected void firePageChange(int newPageIndex) {
listenersManager.firePageChange(newPageIndex);
}
/**
* @return the tabFolder
*/
public CTabFolder getTabFolder() {
return tabFolder;
}
/**
* Get bounds of the tabs area in display coordinate.
*/
public Rectangle getTabArea() {
Rectangle bounds = DragUtil.getDisplayBounds(tabFolder);
//
if(tabFolder.getTabPosition() == SWT.TOP) {
bounds.height = tabFolder.getTabHeight();
} else { // bottom
bounds.y = bounds.y + bounds.height - tabFolder.getTabHeight();
bounds.height = tabFolder.getTabHeight();
}
return bounds;
}
/**
* Get the index of the draggedObject
*
* @param draggedObject
* draggedObject should be of type CTabFolder or CTabItem (as provided by handleDragStarted())
*/
static public int getDraggedObjectTabIndex(Object draggedObject) {
if(draggedObject instanceof CTabItem) {
CTabItem item = (CTabItem)draggedObject;
int index = getItemIndex(item);
return index;
} else if(draggedObject instanceof CTabFolder) {
return -1;
}
return -2;
}
/**
* Get the item index or -1 if not found.
*/
static private int getItemIndex(CTabItem item) {
CTabItem[] items = item.getParent().getItems();
for(int i = 0; i < items.length; i++) {
CTabItem cur = items[i];
if(cur == item) {
return i;
}
}
return -1;
}
/**
* Get the event manager.
* The event manager can be used to listen to events.
*
* @return
*/
public EventsManager getEventManager() {
return listenersManager;
}
/**
* Interface to ne implemented by listeners on PTabFodler events.
*
* @author dumoulin
*
*/
public interface IPTabFolderListener {
/**
*
* @param tab
* @param event
*/
public void menuDetectEvent(CTabItem tab, MenuDetectEvent event);
public void contextMenuDetectEvent(CTabItem tab, Event event);
public void itemClosedEvent(CTabFolderEvent event, int pageIndex);
public void pageChangeEvent(int newPageIndex);
}
/**
* Internal implementations.
* Implements a list of listeners.
*
* @author dumoulin
*
*/
public class EventsManager {
/**
* List of event listeners.
*/
Set<IPTabFolderListener> listeners = new HashSet<IPTabFolderListener>();
/**
* Add a listener
*
* @param listener
*/
public void addListener(IPTabFolderListener listener) {
listeners.add(listener);
}
/**
* Remove a listener
*
* @param listener
*/
public void removeListener(IPTabFolderListener listener) {
listeners.remove(listener);
}
/**
* @param tab
* @param e
*/
public void fireContextMenuEvent(CTabItem tab, Event event) {
for(IPTabFolderListener cur : listeners) {
cur.contextMenuDetectEvent(tab, event);
}
}
/**
* @param event
* @param pageIndex
*/
private void fireItemClosed(CTabFolderEvent event, int pageIndex) {
for(IPTabFolderListener cur : listeners) {
cur.itemClosedEvent(event, pageIndex);
}
}
/**
* @param newPageIndex
*/
private void firePageChange(int newPageIndex) {
for(IPTabFolderListener cur : listeners) {
cur.pageChangeEvent(newPageIndex);
}
}
/**
* Fire the event to all listeners
*
* @param e
* @param tab
*/
private void fireMenuDetectEvent(CTabItem tab, MenuDetectEvent e) {
for(IPTabFolderListener cur : listeners) {
cur.menuDetectEvent(tab, e);
}
}
}
}