/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.custom;
import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.event.DragEvent;
import com.extjs.gxt.ui.client.event.DragListener;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.PortalEvent;
import com.extjs.gxt.ui.client.fx.Draggable;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.Container;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.layout.ColumnData;
import com.extjs.gxt.ui.client.widget.layout.ColumnLayout;
import com.extjs.gxt.ui.client.widget.layout.RowData;
import com.extjs.gxt.ui.client.widget.layout.RowLayout;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
/**
* A Portal container of Portlets. It is required that
* {@link #setColumnWidth(int, double)} be called for each column prior to
* rendering.
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>ValidateDrop</b> : PortalEvent(portal, startColumn, startRow, column,
* row)<br>
* <div>Fires before a dragged portlet can be inserted into a new
* location.</div>
* <ul>
* <li>portal : this</li>
* <li>portlet : the portlet being dragged</li>
* <li>startColumn : the start column</li>
* <li>startRow : the start row</li>
* <li>column : the new column</li>
* <li>row : the new row</li>
* </ul>
* </dd>
*
* <dd><b>Drop</b> : PortalEvent(portal, startColumn, startRow, column, row)<br>
* <div>Fires after a portlet has been moved.</div>
* <ul>
* <li>portal : this</li>
* <li>portlet : the portlet being dropped</li>
* <li>startColumn : the start column</li>
* <li>startRow : the start row</li>
* <li>column : the new column</li>
* <li>row : the new row</li>
* </ul>
* </dd>
* </dl>
*
* <dl>
* <dt>Inherited Events:</dt>
* <dd>Container BeforeAdd</dd>
* <dd>Container Add</dd>
* <dd>Container BeforeRemove</dd>
* <dd>Container Remove</dd>
* <dd>BoxComponent Move</dd>
* <dd>BoxComponent Resize</dd>
* <dd>Component Enable</dd>
* <dd>Component Disable</dd>
* <dd>Component BeforeHide</dd>
* <dd>Component Hide</dd>
* <dd>Component BeforeShow</dd>
* <dd>Component Show</dd>
* <dd>Component Attach</dd>
* <dd>Component Detach</dd>
* <dd>Component BeforeRender</dd>
* <dd>Component Render</dd>
* <dd>Component BrowserEvent</dd>
* <dd>Component BeforeStateRestore</dd>
* <dd>Component StateRestore</dd>
* <dd>Component BeforeStateSave</dd>
* <dd>Component SaveState</dd>
* </dl>
*/
public class Portal extends Container<LayoutContainer> {
private List<LayoutContainer> columns = new ArrayList<LayoutContainer>();
private DragListener listener;
private List<Integer> startColumns;
private int numColumns;
private int startCol, startRow;
private int insertCol = -1, insertRow = -1;
private Portlet active;
private El dummy;
private ColumnLayout cl;
private int spacing = 10;
/**
* Creates a new portal container.
*
* @param numColumns the number of columns
*/
public Portal(int numColumns) {
this.numColumns = numColumns;
cl = new ColumnLayout();
cl.setAdjustForScroll(true);
setLayout(cl);
setStyleAttribute("overflow", "auto");
baseStyle = "x-portal";
enableLayout = true;
for (int i = 0; i < numColumns; i++) {
LayoutContainer l = new LayoutContainer();
l.addStyleName("x-portal x-portal-column");
l.setStyleAttribute("minHeight", "20px");
l.setStyleAttribute("padding", spacing + "px 0 0 " + spacing + "px");
l.setLayout(new RowLayout());
l.setLayoutOnChange(true);
add(l);
columns.add(l);
}
listener = new DragListener() {
@Override
public void dragCancel(DragEvent de) {
onDragCancel(de);
}
@Override
public void dragEnd(DragEvent de) {
onDragEnd(de);
}
@Override
public void dragMove(DragEvent de) {
onDragMove(de);
}
@Override
public void dragStart(DragEvent de) {
onDragStart(de);
}
};
}
/**
* Adds a portlet to the portal.
*
* @param portlet the portlet to add
* @param column the column to insert into
*/
public void add(Portlet portlet, int column) {
insert(portlet, columns.get(column).getItemCount(), column);
}
/**
* Returns the column of the given porlet.
*
* @param portlet the portlet
* @return the column or -1 if not found
*/
public int getPortletColumn(Portlet portlet) {
LayoutContainer c = (LayoutContainer) portlet.getParent();
if (c != null) {
return columns.indexOf(c);
}
return -1;
}
/**
* Returns the index of the column for the given portlet.
*
* @param portlet the portlet
* @return the index or -1 if not found
*/
public int getPortletIndex(Portlet portlet) {
LayoutContainer c = (LayoutContainer) portlet.getParent();
if (c != null) {
return c.indexOf(portlet);
}
return -1;
}
/**
* Returns the spacing between portlets.
*
* @return the spacing the spacing in pixels
*/
public int getSpacing() {
return spacing;
}
/**
* Inserts a portlet.
*
* @param portlet the portlet to add
* @param index the insert index
* @param column the column to insert into
*/
public void insert(Portlet portlet, int index, int column) {
Draggable d = portlet.getData("gxt.draggable");
if (d == null) {
d = new Draggable(portlet, portlet.getHeader());
portlet.setData("gxt.draggable", d);
}
d.setUseProxy(true);
d.removeDragListener(listener);
d.addDragListener(listener);
d.setMoveAfterProxyDrag(false);
d.setSizeProxyToSource(true);
d.setEnabled(!portlet.isPinned());
columns.get(column).insert(portlet, index, new RowData(1, -1));
columns.get(column).layout();
}
/**
* Removes a portlet from the portal.
*
* @param portlet the porlet to remove
* @param column the column
*/
public void remove(Portlet portlet, int column) {
Draggable d = portlet.getData("gxt.draggable");
if (d != null) {
d.release();
}
portlet.setData("gxt.draggable", null);
columns.get(column).remove(portlet);
}
/**
* True to adjust the layout for a vertical scroll bar (defaults to true).
*
* @param adjust true to adjust
*/
public void setAjustForScroll(boolean adjust) {
cl.setAdjustForScroll(adjust);
}
/**
* Sets the column's width.
*
* @param colIndex the column index
* @param width the column width
*/
public void setColumnWidth(int colIndex, double width) {
ComponentHelper.setLayoutData(columns.get(colIndex), new ColumnData(width));
}
/**
* Sets the spacing between portlets (defaults to 10).
*
* @param spacing the spacing in pixels
*/
public void setSpacing(int spacing) {
this.spacing = spacing;
for (LayoutContainer l : columns) {
l.setStyleAttribute("padding", spacing + "px 0 0 " + spacing + "px");
}
}
protected void onDragEnd(DragEvent de) {
dummy.removeFromParent();
if (insertCol != -1 && insertRow != -1) {
if (startCol == insertCol && insertRow > startRow) {
insertRow--;
}
active.setVisible(true);
active.removeFromParent();
columns.get(insertCol).insert(active, insertRow);
active.addStyleName("x-repaint");
fireEvent(Events.Drop, new PortalEvent(this, active, startCol, startRow, insertCol, insertRow));
}
active.setVisible(true);
active = null;
insertCol = -1;
insertRow = -1;
}
protected void onDragMove(DragEvent de) {
int col = getColumn(de.getClientX());
int row = getRowPosition(col, de.getClientY());
if (col != insertCol || row != insertRow) {
PortalEvent pe = new PortalEvent(this, active, startCol, startRow, col, row);
if (fireEvent(Events.ValidateDrop, pe)) {
addInsert(col, row);
} else {
insertCol = startCol;
insertRow = startRow;
}
}
}
@Override
protected void onRender(Element target, int index) {
setElement(DOM.createDiv(), target, index);
}
private void addInsert(int col, int row) {
if (insertCol == col && insertRow < row) {
row++;
}
insertCol = col;
insertRow = row;
LayoutContainer lc = columns.get(insertCol);
dummy.insertInto(lc.el().dom, row);
}
private int getColumn(int x) {
x += XDOM.getBodyScrollLeft();
for (int i = startColumns.size() - 1; i >= 0; i--) {
if (x > startColumns.get(i)) {
return i;
}
}
return 0;
}
private int getRow(int col, int y) {
y += XDOM.getBodyScrollTop();
LayoutContainer con = columns.get(col);
int count = con.getItemCount();
for (int i = 0; i < count; i++) {
Component c = con.getItem(i);
int b = c.getAbsoluteTop();
int t = b + c.getOffsetHeight();
if (y < t) {
return i;
}
}
return 0;
}
private int getRowPosition(int col, int y) {
y += XDOM.getBodyScrollTop();
LayoutContainer con = columns.get(col);
List<Component> list = new ArrayList<Component>(con.getItems());
list.remove(dummy);
int count = list.size();
if (count == 0) {
return 0;
}
for (int i = 0; i < count; i++) {
Component c = list.get(i);
int b = c.getAbsoluteTop();
int t = b + c.getOffsetHeight();
int m = b + (c.getOffsetHeight() / 2);
if (y < t) {
if (y < m) {
return i;
} else {
return i + 1;
}
}
}
return list.size();
}
private void onDragCancel(DragEvent event) {
active.setVisible(true);
active = null;
insertCol = -1;
insertRow = -1;
dummy.removeFromParent();
}
private void onDragStart(DragEvent de) {
active = (Portlet) de.getComponent();
if (dummy == null) {
dummy = new El("<div class='x-portal-insert' style='margin-bottom: 10px'><div></div></div>");
dummy.setStyleName("x-portal-insert");
}
dummy.setStyleAttribute("padding", active.el().getStyleAttribute("padding"));
int h = active.el().getHeight() - active.el().getFrameWidth("tb");
dummy.firstChild().setHeight(h);
startColumns = new ArrayList<Integer>();
for (int i = 0; i < numColumns; i++) {
LayoutContainer con = columns.get(i);
int x = con.getAbsoluteLeft();
startColumns.add(x);
}
startCol = getColumn(de.getClientX());
startRow = getRow(startCol, de.getClientY());
active.setVisible(false);
addInsert(startCol, startRow);
}
}