/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.gwt.client.ui;
import org.opencms.gwt.client.Messages;
import org.opencms.gwt.client.dnd.CmsDNDHandler;
import org.opencms.gwt.client.dnd.I_CmsDragHandle;
import org.opencms.gwt.client.dnd.I_CmsDraggable;
import org.opencms.gwt.client.dnd.I_CmsDropTarget;
import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
import org.opencms.gwt.client.ui.css.I_CmsImageBundle;
import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.I_CmsListItemCss;
import org.opencms.gwt.client.ui.input.CmsCheckBox;
import org.opencms.gwt.client.util.CmsDomUtil;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* List item which uses a float panel for layout.<p>
*
* @since 8.0.0
*/
public class CmsListItem extends Composite implements I_CmsListItem {
/**
* @see com.google.gwt.uibinder.client.UiBinder
*/
protected interface I_CmsSimpleListItemUiBinder extends UiBinder<CmsFlowPanel, CmsListItem> {
// GWT interface, nothing to do here
}
/** The move handle. */
protected class MoveHandle extends CmsPushButton implements I_CmsDragHandle {
/** The draggable. */
private CmsListItem m_draggable;
/**
* Constructor.<p>
*
* @param draggable the draggable
*/
MoveHandle(CmsListItem draggable) {
setImageClass(I_CmsImageBundle.INSTANCE.style().moveIcon());
setButtonStyle(ButtonStyle.TRANSPARENT, null);
setTitle(Messages.get().key(Messages.GUI_TOOLBAR_MOVE_TO_0));
m_draggable = draggable;
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDragHandle#getDraggable()
*/
public I_CmsDraggable getDraggable() {
return m_draggable;
}
}
/** The width of a checkbox. */
private static final int CHECKBOX_WIDTH = 20;
/** The CSS bundle used for this widget. */
private static final I_CmsListItemCss CSS = I_CmsLayoutBundle.INSTANCE.listItemCss();
/** The ui-binder instance for this class. */
private static I_CmsSimpleListItemUiBinder uiBinder = GWT.create(I_CmsSimpleListItemUiBinder.class);
/** The checkbox of this list item, or null if there is no checkbox. */
protected CmsCheckBox m_checkbox;
/** The panel which contains both the decorations (checkbox, etc.) and the main widget. */
protected CmsSimpleDecoratedPanel m_decoratedPanel;
/** A list of decoration widgets which is used to initialize {@link CmsListItem#m_decoratedPanel}. */
protected LinkedList<Widget> m_decorationWidgets = new LinkedList<Widget>();
/** The decoration width which should be used to initialize {@link CmsListItem#m_decoratedPanel}. */
protected int m_decorationWidth;
/** The logical id, it is not the HTML id. */
protected String m_id;
/** The list item widget, if this widget has one. */
protected CmsListItemWidget m_listItemWidget;
/** The main widget of the list item. */
protected Widget m_mainWidget;
/** This widgets panel. */
protected CmsFlowPanel m_panel;
/** The drag'n drop place holder element. */
protected Element m_placeholder;
/** The provisional drag parent. */
protected Element m_provisionalParent;
/** The drag helper. */
private Element m_helper;
/** The move handle. */
private MoveHandle m_moveHandle;
/** The list item's set of tags. */
private Set<String> m_tags;
/**
* Default constructor.<p>
*/
public CmsListItem() {
m_panel = uiBinder.createAndBindUi(this);
initWidget(m_panel);
}
/**
* Default constructor.<p>
*
* @param checkBox the checkbox
* @param widget the widget to use
*/
public CmsListItem(CmsCheckBox checkBox, CmsListItemWidget widget) {
this();
initContent(checkBox, widget);
}
/**
* Default constructor.<p>
*
* @param widget the widget to use
*/
public CmsListItem(CmsListItemWidget widget) {
this();
initContent(widget);
}
/**
* @see org.opencms.gwt.client.ui.I_CmsListItem#add(com.google.gwt.user.client.ui.Widget)
*/
public void add(Widget w) {
throw new UnsupportedOperationException();
}
/**
* Adds a decoration widget to the list item.<p>
*
* @param widget the widget
* @param width the widget width
*/
public void addDecorationWidget(Widget widget, int width) {
addDecoration(widget, width, false);
initContent();
}
/**
* Adds a tag to the widget.<p>
*
* @param tag the tag which should be added
*/
public void addTag(String tag) {
if (m_tags == null) {
m_tags = new HashSet<String>();
}
m_tags.add(tag);
}
/**
* Gets the checkbox of this list item.<p>
*
* This method will return a checkbox if this list item has one, or null if it doesn't.
*
* @return a check box or null
*/
public CmsCheckBox getCheckBox() {
return m_checkbox;
}
/**
* Returns the decoration widgets of this list item.<p>
*
* @return the decoration widgets
*/
public List<Widget> getDecorationWidgets() {
return Collections.unmodifiableList(m_decorationWidgets);
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#getDragHelper(I_CmsDropTarget)
*/
public Element getDragHelper(I_CmsDropTarget target) {
if (m_helper == null) {
if (m_listItemWidget != null) {
m_listItemWidget.setAdditionalInfoVisible(false);
Iterator<Widget> buttonIterator = m_listItemWidget.getButtonPanel().iterator();
while (buttonIterator.hasNext()) {
Widget button = buttonIterator.next();
if (button != m_moveHandle) {
button.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
}
}
m_helper = CmsDomUtil.clone(getElement());
// remove all decorations
List<com.google.gwt.dom.client.Element> elems = CmsDomUtil.getElementsByClass(
I_CmsLayoutBundle.INSTANCE.floatDecoratedPanelCss().decorationBox(),
CmsDomUtil.Tag.div,
m_helper);
for (com.google.gwt.dom.client.Element elem : elems) {
elem.removeFromParent();
}
// we append the drag helper to the body to prevent any kind of issues
// (ie when the parent is styled with overflow:hidden)
// and we put it additionally inside a absolute positioned provisional parent
// ON the original parent for the eventual animation when releasing
Element parentElement = getElement().getParentElement();
if (parentElement == null) {
parentElement = target.getElement();
}
int elementTop = getElement().getAbsoluteTop();
int parentTop = parentElement.getAbsoluteTop();
m_provisionalParent = DOM.createElement(parentElement.getTagName());
RootPanel.getBodyElement().appendChild(m_provisionalParent);
m_provisionalParent.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().clearStyles());
m_provisionalParent.getStyle().setWidth(parentElement.getOffsetWidth(), Unit.PX);
m_provisionalParent.appendChild(m_helper);
Style style = m_helper.getStyle();
style.setWidth(m_helper.getOffsetWidth(), Unit.PX);
// the dragging class will set position absolute
m_helper.addClassName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().dragging());
style.setTop(elementTop - parentTop, Unit.PX);
m_provisionalParent.getStyle().setPosition(Position.ABSOLUTE);
m_provisionalParent.getStyle().setTop(parentTop, Unit.PX);
m_provisionalParent.getStyle().setLeft(parentElement.getAbsoluteLeft(), Unit.PX);
m_provisionalParent.getStyle().setZIndex(I_CmsLayoutBundle.INSTANCE.constants().css().zIndexDND());
}
// ensure mouse out
if (m_listItemWidget != null) {
m_listItemWidget.forceMouseOut();
}
CmsDomUtil.ensureMouseOut(this);
return m_helper;
}
/**
* @see org.opencms.gwt.client.ui.I_CmsListItem#getId()
*/
public String getId() {
return m_id;
}
/**
* Returns the list item widget of this list item, or null if this item doesn't have a list item widget.<p>
*
* @return a list item widget or null
*/
public CmsListItemWidget getListItemWidget() {
if ((m_mainWidget == null) || !(m_mainWidget instanceof CmsListItemWidget)) {
return null;
}
return (CmsListItemWidget)m_mainWidget;
}
/**
* Returns the parent list.<p>
*
* @return the parent list
*/
@SuppressWarnings("unchecked")
public CmsList<CmsListItem> getParentList() {
Widget parent = getParent();
if (parent == null) {
return null;
}
return (CmsList<CmsListItem>)parent;
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#getParentTarget()
*/
public I_CmsDropTarget getParentTarget() {
return getParentList();
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#getPlaceholder(I_CmsDropTarget)
*/
public Element getPlaceholder(I_CmsDropTarget target) {
if (m_placeholder == null) {
if (m_listItemWidget != null) {
m_listItemWidget.setAdditionalInfoVisible(false);
}
m_placeholder = cloneForPlaceholder(this);
}
return m_placeholder;
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#hasTag(java.lang.String)
*/
public boolean hasTag(String tag) {
return (m_tags != null) && m_tags.contains(tag);
}
/**
* Initializes the move handle with the given drag and drop handler and adds it to the list item widget.<p>
*
* This method will not work for list items that don't have a list-item-widget.<p>
*
* @param dndHandler the drag and drop handler
*
* @return <code>true</code> if initialization was successful
*/
public boolean initMoveHandle(CmsDNDHandler dndHandler) {
return initMoveHandle(dndHandler, false);
}
/**
* Initializes the move handle with the given drag and drop handler and adds it to the list item widget.<p>
*
* This method will not work for list items that don't have a list-item-widget.<p>
*
* @param dndHandler the drag and drop handler
*
* @param addFirst if true, adds the move handle as first child
*
* @return <code>true</code> if initialization was successful
*/
public boolean initMoveHandle(CmsDNDHandler dndHandler, boolean addFirst) {
if (m_moveHandle != null) {
return true;
}
if (m_listItemWidget == null) {
return false;
}
m_moveHandle = new MoveHandle(this);
if (addFirst) {
m_listItemWidget.addButtonToFront(m_moveHandle);
} else {
m_listItemWidget.addButton(m_moveHandle);
}
m_moveHandle.addMouseDownHandler(dndHandler);
return true;
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDragCancel()
*/
public void onDragCancel() {
clearDrag();
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDrop(org.opencms.gwt.client.dnd.I_CmsDropTarget)
*/
public void onDrop(I_CmsDropTarget target) {
clearDrag();
}
/**
* @see org.opencms.gwt.client.dnd.I_CmsDraggable#onStartDrag(org.opencms.gwt.client.dnd.I_CmsDropTarget)
*/
public void onStartDrag(I_CmsDropTarget target) {
CmsDomUtil.ensureMouseOut(getMoveHandle().getElement());
setVisible(false);
}
/**
* @see org.opencms.gwt.client.ui.I_CmsListItem#setId(java.lang.String)
*/
public void setId(String id) {
CmsList<CmsListItem> parentList = getParentList();
if (parentList != null) {
parentList.changeId(this, id);
}
m_id = id;
}
/**
* @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int)
*/
public void truncate(String textMetricsPrefix, int widgetWidth) {
for (Widget widget : m_panel) {
if (!(widget instanceof I_CmsTruncable)) {
continue;
}
int width = widgetWidth - 4; // just to be on the safe side
if (widget instanceof CmsList<?>) {
width -= 25; // 25px left margin
}
((I_CmsTruncable)widget).truncate(textMetricsPrefix, width);
}
}
/**
* Adds a check box to this list item.<p>
*
* @param checkbox the check box
*/
protected void addCheckBox(CmsCheckBox checkbox) {
assert m_checkbox == null;
m_checkbox = checkbox;
addDecoration(m_checkbox, CHECKBOX_WIDTH, false);
m_checkbox.addStyleName(CSS.listItemCheckbox());
}
/**
* Helper method for adding a decoration widget and updating the decoration width accordingly.<p>
*
* @param widget the decoration widget to add
* @param width the intended width of the decoration widget
* @param first if true, inserts the widget at the front of the decorations, else at the end.
*/
protected void addDecoration(Widget widget, int width, boolean first) {
m_decorationWidgets.add(widget);
m_decorationWidth += width;
}
/**
* Adds the main widget to the list item.<p>
*
* In most cases, the widget will be a list item widget. If this is the case, then further calls to {@link CmsListItem#getListItemWidget()} will
* return the widget which was passed as a parameter to this method. Otherwise, the method will return null.<p>
*
* @param widget
*/
protected void addMainWidget(Widget widget) {
assert m_mainWidget == null;
assert m_listItemWidget == null;
if (widget instanceof CmsListItemWidget) {
m_listItemWidget = (CmsListItemWidget)widget;
}
m_mainWidget = widget;
}
/**
* Clones the given item to be used as a place holder.<p>
*
* @param listItem the item to clone
*
* @return the cloned item
*/
protected Element cloneForPlaceholder(CmsListItem listItem) {
Element clone = CmsDomUtil.clone(listItem.getElement());
clone.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder());
// remove hoverbar
List<Element> elems = CmsDomUtil.getElementsByClass(
I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().buttonPanel(),
CmsDomUtil.Tag.div,
clone);
for (com.google.gwt.dom.client.Element elem : elems) {
elem.removeFromParent();
}
return clone;
}
/**
* Returns the move handle.<p>
*
* @return the move handle
*/
protected I_CmsDragHandle getMoveHandle() {
return m_moveHandle;
}
/**
* This internal helper method creates the actual contents of the widget by combining the decorators and the main widget.<p>
*/
protected void initContent() {
if (m_decoratedPanel != null) {
m_decoratedPanel.removeFromParent();
}
m_decoratedPanel = new CmsSimpleDecoratedPanel(m_decorationWidth, m_mainWidget, m_decorationWidgets);
m_panel.insert(m_decoratedPanel, 0);
}
/**
* This method is a convenience method which sets the checkbox and main widget of this widget, and then calls {@link CmsListItem#initContent()}.<p>
*
* @param checkbox the checkbox to add
* @param mainWidget the mainWidget to add
*/
protected void initContent(CmsCheckBox checkbox, Widget mainWidget) {
addCheckBox(checkbox);
addMainWidget(mainWidget);
initContent();
}
/**
* This method is a convenience method which sets the main widget of this widget, and then calls {@link CmsListItem#initContent()}.<p>
*
* @param mainWidget the main widget to add
*/
protected void initContent(Widget mainWidget) {
addMainWidget(mainWidget);
initContent();
}
/**
* Called when a drag operation for this widget is stopped.<p>
*/
private void clearDrag() {
if (m_listItemWidget != null) {
Iterator<Widget> buttonIterator = m_listItemWidget.getButtonPanel().iterator();
while (buttonIterator.hasNext()) {
Widget button = buttonIterator.next();
button.getElement().getStyle().clearVisibility();
}
}
if (m_helper != null) {
m_helper.removeFromParent();
m_helper = null;
}
if (m_provisionalParent != null) {
m_provisionalParent.removeFromParent();
m_provisionalParent = null;
}
setVisible(true);
}
}