/*****************************************************************************
* 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.logging.Logger;
import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.IAbstractPanelModel;
import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.ISashPanelModel;
import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.ITabFolderModel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.internal.dnd.DragUtil;
import org.eclipse.ui.internal.dnd.IDropTarget;
/**
* Controller for a sash panel.
* A sash panel contain 2 children. It shows them in two windows separated by a sash.
* Implementation use one model, a {@link ISashPanelModel}. This model encapsulate the real model which
* is of an unknown type.
*
*
* @author dumoulin
*
* @param T
* Type of the external model representing the sash.
*/
@SuppressWarnings({ "restriction" })
public class SashPanelPart extends AbstractPanelPart implements IPanelParent {
/** Log object */
Logger log = Logger.getLogger(getClass().getName());
/** Interface to the model */
protected ISashPanelModel model;
/** Raw model associated to this part. We store it because the PartModel do not provide it */
private Object rawModel;
/** Ordered set of currently shown diagrams (a left and right child, or upper and lower) TODO rename as children */
protected AbstractPanelPart[] currentChildParts = new AbstractPanelPart[2];
/**
* The container widget.
*/
private ReplaceableSashForm container;
/**
* Direction of the sash: SWT.HORIZONTAL or SWT.VERTICAL. Default = SWT.HORIZONTAL
*/
private int sashDirection = SWT.HORIZONTAL;
/**
* Constructor.
*/
public SashPanelPart(IPanelParent parent, ISashPanelModel model, Object rawModel) {
super(parent);
this.model = model;
this.rawModel = rawModel;
this.sashDirection = model.getSashDirection();
}
/**
* Get the associated model.
*/
public ISashPanelModel getPartModel() {
return model;
}
/**
* Fill the provided part map with this parts and recursively call children to fillin.
*
* @param partMap
*/
public void fillPartMap(PartLists partMap) {
partMap.addPart(this);
for(AbstractPanelPart child : currentChildParts) {
child.fillPartMap(partMap);
}
garbageState = GarbageState.UNVISITED;
}
/**
* Create local control, and the tree of children (TileParts AND controls). Create this TilePart control, and then Tile childs of this TilePart.
*
* @param parent
* @return Control
*/
public void createPartControl(Composite parent) {
createControl(parent);
// activate();
// createChildrenControl();
}
/**
* Create local control. Does not create children.
*
*/
protected void createControl(Composite parent) {
// container = new SashForm(parent, sashDirection);
container = new ReplaceableSashForm(parent, sashDirection);
}
/**
* Create the part for the specified child model.
* The controls are NOT build.
*
* TODO: delegate to sashContainer, remove duplication from here and RootPart.
*
* @param rootPart
* @param partModel
* @return
*/
private AbstractPanelPart createChildPart(Object rawModel) {
// Create the child PartModel. Delegate creation to this part PartModel.
IAbstractPanelModel model = getPartModel().createChildSashModel(rawModel);
AbstractPanelPart createdPart;
if(model instanceof ITabFolderModel) {
createdPart = new TabFolderPart(this, (ITabFolderModel)model, rawModel);
} else if(model instanceof ISashPanelModel) {
createdPart = new SashPanelPart(this, (ISashPanelModel)model, rawModel);
} else {
// error
throw new IllegalArgumentException("Can't create child part for model of type '"
+ model.getClass().getName()
+ "'");
// TODO: Return an error Part showing the exception instead of throwing it ?
}
return createdPart;
}
/**
* Create the part for the specified child model.
* The controls are build.
*
* TODO: delegate to sashContainer, remove duplication from here and RootPart.
*
* @param rootPart
* @param partModel
* @return
*/
private AbstractPanelPart createChildPart(Object rawModel, int childIndex) {
// Create parts
AbstractPanelPart createdPart = createChildPart(rawModel);
// Create controls
createdPart.createPartControl(getChildParent(childIndex));
return createdPart;
}
/**
* Get the sash container.
*
* @see org.eclipse.papyrus.infra.core.sasheditor.internal.AbstractPanelPart#getControl()
*
* @return
*/
public Composite getControl() {
return container;
}
/**
* Change the parent of this method. Reparent the Tile and the control. Normally, the control already exists.
*
*/
@Override
public void reparent(IPanelParent newParent, Composite swtParent) {
parent = newParent;
// Create control if needed
// This can happen if the TilePart is just created after a refresh
// if(getControl() == null)
// {
// container = createContainer(parent.getControl());
// }
// Reparent the control
assert (getControl() != null);
// getControl().setParent(newParent.getControl()) ;
getControl().setParent(swtParent);
garbageState = GarbageState.REPARENTED;
}
/**
* Orphan this node, and children. The parent is set to null, but control is left unchanged. The node can be reattached with reparent().
*
* @see
* @return the parent
*/
public void orphan() {
// orphan only if we are in UNCHANGED state
if(garbageState == GarbageState.UNVISITED) {
garbageState = GarbageState.ORPHANED;
parent = null;
}
}
/**
* Dispose the TilePart and its controls.
*/
@Override
public void dispose() {
if(container != null && !container.isDisposed()) {
container.dispose();
}
container = null;
}
/**
* Dispose this part and all its children.
* The method is called recursively on children of the part.
*/
@Override
public void disposeThisAndChildren() {
// Dispose children if any
for( AbstractPanelPart child : currentChildParts) {
if(child != null) {
child.disposeThisAndChildren();
}
}
// clean up properties to help GC
model = null;
rawModel = null;
}
/**
* Traverses the tree to find the part that intersects the given point
*
* @param toFind
* Point in display coordinate
* @return the part that intersects the given point
* @throws NotFoundException
*/
@Override
public AbstractPart findPart(Point toFind) throws NotFoundException {
Rectangle bounds = DragUtil.getDisplayBounds(container); // container.getBounds();
// Try the left/up pane
bounds = DragUtil.getDisplayBounds(container.getLeftParent());
if(bounds.contains(toFind)) {
return currentChildParts[0].findPart(toFind);
}
bounds = DragUtil.getDisplayBounds(container.getRightParent());
if(bounds.contains(toFind)) {
// Return right part
return currentChildParts[1].findPart(toFind);
}
throw new NotFoundException("Can't find a part at '" + toFind + "'");
}
/**
* Locates the part that intersects the given point and that have the expected type
*
* @param toFind
* Position in Display coordinate.
* @return
*/
public AbstractPart findPartAt(Point toFind, Class<?> expectedTileType) {
if(expectedTileType == this.getClass())
return this;
Rectangle bounds = DragUtil.getDisplayBounds(container); // container.getBounds();
if(isVertical()) {
if(toFind.y < bounds.y + (bounds.height / 2)) {
return currentChildParts[0].findPartAt(toFind, expectedTileType);
}
return currentChildParts[1].findPartAt(toFind, expectedTileType);
} else {
if(toFind.x < bounds.x + (bounds.width / 2)) {
return currentChildParts[0].findPartAt(toFind, expectedTileType);
}
return currentChildParts[1].findPartAt(toFind, expectedTileType);
}
}
/**
* Return true if this sash is vertical, false otherwise.
*/
private boolean isVertical() {
return (container.getOrientation() == SWT.VERTICAL);
}
/**
* Find the part associated to the provided control.
*
* @see org.eclipse.papyrus.infra.core.sasheditor.sash.ITilePart#findPart(org.eclipse.swt.widgets.Control)
*/
@Override
public AbstractPart findPart(Object control) {
if(this.getControl() == control) {
return this;
}
AbstractPart node = currentChildParts[0].findPart(control);
if(node != null) {
return node;
}
node = currentChildParts[1].findPart(control);
if(node != null) {
return node;
}
return null;
}
/**
* SashPanelPart can't be a DropTarget. Do nothing.
*/
public IDropTarget getDropTarget(Object draggedObject, TabFolderPart sourcePart, Point position) {
return null;
}
/**
* Return true if the Part is for the specified real model. Return false otherwise.
*
* @param realModel
* The raw model to check
* @return
*/
public boolean isPartFor(Object realModel) {
return getRawModel() == realModel;
}
/**
* Get the raw model associated to this part.
*
* @return
*/
protected Object getRawModel() {
return rawModel;
}
/**
* Synchronize the sash.
*
* @see org.eclipse.papyrus.infra.core.sasheditor.internal.AbstractPanelPart#synchronize2(org.eclipse.papyrus.infra.core.sasheditor.internal.PartLists)
*
* @param partMap
*/
public void synchronize2(PartLists partMap) {
// Compare currentChildParts and node model
assert (model.getChildren().size() == 2);
// Synchronize each child
for(int i = 0; i < 2 /* model.getChildModels().size() */; i++) {
synchronizeChild(i, partMap);
}
// Now recursively call synchronize on childs.
for(int i = 0; i < currentChildParts.length; i++) {
currentChildParts[i].synchronize2(partMap);
// // Set the child controls at the right place
// if(i==0)
// container.moveAbove(currentChildParts[i].getControl());
// else
// container.moveBelow(currentChildParts[i].getControl());
//
}
}
/**
* Synchronize the specified child.
*
* @param childIndex
* index of the child to be synchronized
* @param existingParts
*/
private void synchronizeChild(int childIndex, PartLists existingParts) {
Object newModel = model.getChildren().get(childIndex);
// Check if old child exist
// If exist, check if the current part is associated to the checked model
//
AbstractPanelPart currentChildPart = currentChildParts[childIndex];
if(currentChildPart != null) {
// If the tile is already for the model, there is nothing to do.
if(currentChildPart.isPartFor(newModel)) {
currentChildPart.unchanged();
return;
}
// The current tile is not for the model: mark it as orphan
currentChildPart.orphan();
}
// The child part need to be updated. Do it.
// First check if already exist in the map
AbstractPanelPart newTile = existingParts.findPartFor(newModel);
if(newTile != null) {
// Reparent the tile
newTile.reparent(this, getChildParent(childIndex));
} else {
// create the tile and its control
newTile = createChildPart(newModel, childIndex);
}
// Now, put the tile on the right side
setChildToSide(newTile, childIndex);
}
/**
* Get the Composite parent that will be provided to the child.
*
* @param childIndex
* @return
*/
private Composite getChildParent(int childIndex) {
// return container;
if(childIndex == 0)
return container.getLeftParent();
else
return container.getRightParent();
}
/**
* Set the provided child at the specified index. If a child already exist at the specified index, it is lost. The controls are set accordingly
*
* @param newTile
* @param childIndex
*/
private void setChildToSide(AbstractPanelPart newTile, int childIndex) {
currentChildParts[childIndex] = newTile;
}
/**
* Accept the provided visitor.
* Call the corresponding accept method in the visitor.
*
* @param visitor
* @return
*/
public boolean visit(IPartVisitor visitor) {
return visitor.accept(this);
}
/**
* Visit the children of this Tile.
*
* @param visitor
*/
public boolean visitChildren(IPartVisitor visitor) {
for(AbstractPanelPart child : currentChildParts) {
if(!child.visit(visitor))
return false;
}
// All children have accepter the visit, continue visiting.
return true;
}
/**
* Show tile status.
* Used for debug purpose
*/
protected void showStatus() {
org.eclipse.papyrus.infra.core.sasheditor.Activator.log.debug("sash[" + currentChildParts.length + "]:"
+ ", disposed=" + container.isDisposed()
+ ", visible=" + container.isVisible()
+ ", garbState=" + garbageState
+ ", " + this);
}
}