001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.ui; 029 030import org.opencms.gwt.client.Messages; 031import org.opencms.gwt.client.dnd.CmsDNDHandler; 032import org.opencms.gwt.client.dnd.I_CmsDragHandle; 033import org.opencms.gwt.client.dnd.I_CmsDraggable; 034import org.opencms.gwt.client.dnd.I_CmsDropTarget; 035import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; 036import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 037import org.opencms.gwt.client.ui.input.CmsCheckBox; 038import org.opencms.gwt.client.ui.input.category.CmsDataValue; 039import org.opencms.gwt.client.util.CmsDomUtil; 040 041import java.util.Collections; 042import java.util.Iterator; 043import java.util.LinkedList; 044import java.util.List; 045 046import com.google.common.base.Optional; 047import com.google.gwt.dom.client.Element; 048import com.google.gwt.dom.client.Style; 049import com.google.gwt.dom.client.Style.Position; 050import com.google.gwt.dom.client.Style.Unit; 051import com.google.gwt.dom.client.Style.Visibility; 052import com.google.gwt.user.client.DOM; 053import com.google.gwt.user.client.ui.Composite; 054import com.google.gwt.user.client.ui.RootPanel; 055import com.google.gwt.user.client.ui.Widget; 056 057/** 058 * List item which uses a float panel for layout.<p> 059 * 060 * @since 8.0.0 061 */ 062public class CmsListItem extends Composite implements I_CmsListItem { 063 064 /** The move handle. */ 065 public class MoveHandle extends CmsPushButton implements I_CmsDragHandle { 066 067 /** The draggable. */ 068 private CmsListItem m_draggable; 069 070 /** 071 * Constructor.<p> 072 * 073 * @param draggable the draggable 074 */ 075 MoveHandle(CmsListItem draggable) { 076 077 setImageClass(I_CmsButton.MOVE_SMALL); 078 setButtonStyle(ButtonStyle.FONT_ICON, null); 079 setTitle(Messages.get().key(Messages.GUI_TOOLBAR_MOVE_TO_0)); 080 addStyleName(MOVE_HANDLE_MARKER_CLASS); 081 m_draggable = draggable; 082 } 083 084 /** 085 * @see org.opencms.gwt.client.dnd.I_CmsDragHandle#getDraggable() 086 */ 087 public I_CmsDraggable getDraggable() { 088 089 return m_draggable; 090 } 091 092 } 093 094 /** The CSS class to mark the move handle. */ 095 public static final String MOVE_HANDLE_MARKER_CLASS = "cmsMoveHandle"; 096 097 /** The width of a checkbox. */ 098 private static final int CHECKBOX_WIDTH = 20; 099 100 /** The checkbox of this list item, or null if there is no checkbox. */ 101 protected CmsCheckBox m_checkbox; 102 103 /** The panel which contains both the decorations (checkbox, etc.) and the main widget. */ 104 protected CmsSimpleDecoratedPanel m_decoratedPanel; 105 106 /** A list of decoration widgets which is used to initialize {@link CmsListItem#m_decoratedPanel}. */ 107 protected LinkedList<Widget> m_decorationWidgets = new LinkedList<Widget>(); 108 109 /** The decoration width which should be used to initialize {@link CmsListItem#m_decoratedPanel}. */ 110 protected int m_decorationWidth; 111 112 /** The logical id, it is not the HTML id. */ 113 protected String m_id; 114 115 /** The list item widget, if this widget has one. */ 116 protected CmsListItemWidget m_listItemWidget; 117 118 /** The main widget of the list item. */ 119 protected Widget m_mainWidget; 120 121 /** This widgets panel. */ 122 protected CmsFlowPanel m_panel; 123 124 /** The drag'n drop place holder element. */ 125 protected Element m_placeholder; 126 127 /** The provisional drag parent. */ 128 protected Element m_provisionalParent; 129 130 /** Arbitrary data belonging to the list item. */ 131 private Object m_data; 132 133 /** The class to set on the DND helper. */ 134 private String m_dndHelperClass; 135 136 /** The class to set on the DND parent. */ 137 private String m_dndParentClass; 138 139 /** The drag helper. */ 140 private Element m_helper; 141 142 /** The move handle. */ 143 private MoveHandle m_moveHandle; 144 145 /** The offset delta. */ 146 private Optional<int[]> m_offsetDelta = Optional.absent(); 147 148 /** Indicating this box has a reduced height. */ 149 private boolean m_smallView; 150 151 /** 152 * Default constructor.<p> 153 */ 154 public CmsListItem() { 155 156 m_panel = new CmsFlowPanel("li"); 157 m_panel.setStyleName(I_CmsLayoutBundle.INSTANCE.listTreeCss().listTreeItem()); 158 initWidget(m_panel); 159 } 160 161 /** 162 * Default constructor.<p> 163 * 164 * @param checkBox the checkbox 165 * @param widget the widget to use 166 */ 167 public CmsListItem(CmsCheckBox checkBox, CmsListItemWidget widget) { 168 169 this(); 170 initContent(checkBox, widget); 171 } 172 173 /** 174 * Default constructor.<p> 175 * 176 * @param widget the widget to use 177 */ 178 public CmsListItem(CmsListItemWidget widget) { 179 180 this(); 181 initContent(widget); 182 } 183 184 /** 185 * @see org.opencms.gwt.client.ui.I_CmsListItem#add(com.google.gwt.user.client.ui.Widget) 186 */ 187 public void add(Widget w) { 188 189 throw new UnsupportedOperationException(); 190 } 191 192 /** 193 * Adds a decoration widget to the list item.<p> 194 * 195 * @param widget the widget 196 * @param width the widget width 197 */ 198 public void addDecorationWidget(Widget widget, int width) { 199 200 addDecoration(widget, width, false); 201 initContent(); 202 } 203 204 /** 205 * Gets the checkbox of this list item.<p> 206 * 207 * This method will return a checkbox if this list item has one, or null if it doesn't. 208 * 209 * @return a check box or null 210 */ 211 public CmsCheckBox getCheckBox() { 212 213 return m_checkbox; 214 } 215 216 /** 217 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getCursorOffsetDelta() 218 */ 219 public Optional<int[]> getCursorOffsetDelta() { 220 221 return m_offsetDelta; 222 } 223 224 /** 225 * Gets the data belonging to the list item.<p> 226 * 227 * @return the data belonging to the list item 228 */ 229 @SuppressWarnings("unchecked") 230 public <T> T getData() { 231 232 return (T)m_data; 233 } 234 235 /** 236 * Returns the decoration widgets of this list item.<p> 237 * 238 * @return the decoration widgets 239 */ 240 public List<Widget> getDecorationWidgets() { 241 242 return Collections.unmodifiableList(m_decorationWidgets); 243 } 244 245 /** 246 * Gets the class for the DND helper.<p> 247 * 248 * @return the class for the DND helper 249 */ 250 public String getDndHelperClass() { 251 252 return m_dndHelperClass; 253 } 254 255 /** 256 * Gets the class for the DND parent.<p> 257 * 258 * @return the class for the DND parent 259 */ 260 public String getDndParentClass() { 261 262 return m_dndParentClass; 263 } 264 265 /** 266 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getDragHelper(I_CmsDropTarget) 267 */ 268 public Element getDragHelper(I_CmsDropTarget target) { 269 270 if (m_helper == null) { 271 if (m_listItemWidget != null) { 272 m_listItemWidget.setAdditionalInfoVisible(false); 273 Iterator<Widget> buttonIterator = m_listItemWidget.getButtonPanel().iterator(); 274 while (buttonIterator.hasNext()) { 275 Widget button = buttonIterator.next(); 276 if (button != m_moveHandle) { 277 button.getElement().getStyle().setVisibility(Visibility.HIDDEN); 278 } 279 } 280 } 281 int oldMoveHandleLeft = moveHandleLeft(getElement()); 282 int oldElemLeft = getElement().getAbsoluteLeft(); 283 //int oldMoveHandleLeft = m_moveHandle.getAbsoluteLeft(); 284 m_helper = CmsDomUtil.clone(getElement()); 285 if (m_dndHelperClass != null) { 286 m_helper.addClassName(m_dndHelperClass); 287 } 288 // remove all decorations 289 List<com.google.gwt.dom.client.Element> elems = CmsDomUtil.getElementsByClass( 290 I_CmsLayoutBundle.INSTANCE.floatDecoratedPanelCss().decorationBox(), 291 CmsDomUtil.Tag.div, 292 m_helper); 293 for (com.google.gwt.dom.client.Element elem : elems) { 294 elem.removeFromParent(); 295 } 296 297 // we append the drag helper to the body to prevent any kind of issues 298 // (ie when the parent is styled with overflow:hidden) 299 // and we put it additionally inside a absolute positioned provisional parent 300 // ON the original parent for the eventual animation when releasing 301 Element parentElement = getElement().getParentElement(); 302 if (parentElement == null) { 303 parentElement = target.getElement(); 304 } 305 int elementTop = getElement().getAbsoluteTop(); 306 int parentTop = parentElement.getAbsoluteTop(); 307 m_provisionalParent = DOM.createElement(parentElement.getTagName()); 308 if (m_dndParentClass != null) { 309 m_provisionalParent.addClassName(m_dndParentClass); 310 } 311 RootPanel.getBodyElement().appendChild(m_provisionalParent); 312 313 m_provisionalParent.getStyle().setWidth(parentElement.getOffsetWidth(), Unit.PX); 314 m_provisionalParent.appendChild(m_helper); 315 316 m_provisionalParent.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().clearStyles()); 317 m_provisionalParent.addClassName( 318 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().opencms()); 319 Style style = m_helper.getStyle(); 320 style.setWidth(m_helper.getOffsetWidth(), Unit.PX); 321 // the dragging class will set position absolute 322 m_helper.addClassName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().dragging()); 323 style.setTop(elementTop - parentTop, Unit.PX); 324 m_provisionalParent.getStyle().setPosition(Position.ABSOLUTE); 325 m_provisionalParent.getStyle().setTop(parentTop, Unit.PX); 326 m_provisionalParent.getStyle().setLeft(parentElement.getAbsoluteLeft(), Unit.PX); 327 int newMoveHandleLeft = moveHandleLeft(m_helper); 328 int newElemLeft = m_helper.getAbsoluteLeft(); 329 m_offsetDelta = Optional.fromNullable( 330 new int[] {((newMoveHandleLeft - oldMoveHandleLeft) + oldElemLeft) - newElemLeft, 0}); 331 m_provisionalParent.getStyle().setZIndex(I_CmsLayoutBundle.INSTANCE.constants().css().zIndexDND()); 332 } 333 // ensure mouse out 334 if (m_listItemWidget != null) { 335 m_listItemWidget.forceMouseOut(); 336 } 337 CmsDomUtil.ensureMouseOut(this); 338 return m_helper; 339 } 340 341 /** 342 * @see org.opencms.gwt.client.ui.I_CmsListItem#getId() 343 */ 344 public String getId() { 345 346 return m_id; 347 } 348 349 /** 350 * Returns the list item widget of this list item, or null if this item doesn't have a list item widget.<p> 351 * 352 * @return a list item widget or null 353 */ 354 public CmsListItemWidget getListItemWidget() { 355 356 if ((m_mainWidget == null) || !(m_mainWidget instanceof CmsListItemWidget)) { 357 return null; 358 } 359 return (CmsListItemWidget)m_mainWidget; 360 } 361 362 /** 363 * Returns the main widget.<p> 364 * 365 * @return the main widget 366 */ 367 public Widget getMainWidget() { 368 369 return m_mainWidget; 370 } 371 372 /** 373 * Returns the move handle.<p> 374 * 375 * @return the move handle 376 */ 377 public I_CmsDragHandle getMoveHandle() { 378 379 return m_moveHandle; 380 } 381 382 /** 383 * Returns the parent list.<p> 384 * 385 * @return the parent list 386 */ 387 @SuppressWarnings("unchecked") 388 public CmsList<CmsListItem> getParentList() { 389 390 Widget parent = getParent(); 391 if (parent == null) { 392 return null; 393 } 394 return (CmsList<CmsListItem>)parent; 395 } 396 397 /** 398 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getParentTarget() 399 */ 400 public I_CmsDropTarget getParentTarget() { 401 402 return getParentList(); 403 } 404 405 /** 406 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getPlaceholder(I_CmsDropTarget) 407 */ 408 public Element getPlaceholder(I_CmsDropTarget target) { 409 410 if (m_placeholder == null) { 411 if (m_listItemWidget != null) { 412 m_listItemWidget.setAdditionalInfoVisible(false); 413 } 414 m_placeholder = cloneForPlaceholder(this); 415 } 416 return m_placeholder; 417 } 418 419 /** 420 * Initializes the move handle with the given drag and drop handler and adds it to the list item widget.<p> 421 * 422 * This method will not work for list items that don't have a list-item-widget.<p> 423 * 424 * @param dndHandler the drag and drop handler 425 * 426 * @return <code>true</code> if initialization was successful 427 */ 428 public boolean initMoveHandle(CmsDNDHandler dndHandler) { 429 430 return initMoveHandle(dndHandler, false); 431 } 432 433 /** 434 * Initializes the move handle with the given drag and drop handler and adds it to the list item widget.<p> 435 * 436 * This method will not work for list items that don't have a list-item-widget.<p> 437 * 438 * @param dndHandler the drag and drop handler 439 * 440 * @param addFirst if true, adds the move handle as first child 441 * 442 * @return <code>true</code> if initialization was successful 443 */ 444 public boolean initMoveHandle(CmsDNDHandler dndHandler, boolean addFirst) { 445 446 if (m_moveHandle != null) { 447 return true; 448 } 449 if (m_listItemWidget == null) { 450 return false; 451 } 452 m_moveHandle = new MoveHandle(this); 453 if (addFirst) { 454 m_listItemWidget.addButtonToFront(m_moveHandle); 455 } else { 456 m_listItemWidget.addButton(m_moveHandle); 457 } 458 459 m_moveHandle.addMouseDownHandler(dndHandler); 460 return true; 461 } 462 463 /** 464 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDragCancel() 465 */ 466 public void onDragCancel() { 467 468 clearDrag(); 469 } 470 471 /** 472 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDrop(org.opencms.gwt.client.dnd.I_CmsDropTarget) 473 */ 474 public void onDrop(I_CmsDropTarget target) { 475 476 clearDrag(); 477 } 478 479 /** 480 * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onStartDrag(org.opencms.gwt.client.dnd.I_CmsDropTarget) 481 */ 482 public void onStartDrag(I_CmsDropTarget target) { 483 484 CmsDomUtil.ensureMouseOut(getMoveHandle().getElement()); 485 setVisible(false); 486 } 487 488 /** 489 * Sets the data for this list item.<p> 490 * 491 * @param data the data to set 492 */ 493 public void setData(Object data) { 494 495 m_data = data; 496 } 497 498 /** 499 * Sets the class for the DND helper.<p> 500 * 501 * @param dndHelperClass the class for the DND helper 502 */ 503 public void setDndHelperClass(String dndHelperClass) { 504 505 m_dndHelperClass = dndHelperClass; 506 } 507 508 /** 509 * Sets the class for the DND parent.<p> 510 * 511 * @param dndParentClass the class for the DND parent 512 */ 513 public void setDndParentClass(String dndParentClass) { 514 515 m_dndParentClass = dndParentClass; 516 } 517 518 /** 519 * @see org.opencms.gwt.client.ui.I_CmsListItem#setId(java.lang.String) 520 */ 521 public void setId(String id) { 522 523 CmsList<CmsListItem> parentList = getParentList(); 524 if (parentList != null) { 525 parentList.changeId(this, id); 526 } 527 m_id = id; 528 } 529 530 /** 531 * Sets the decoration style to fit with the small view of list items.<p> 532 * 533 * @param smallView true if the decoration has to fit with the small view of list items 534 */ 535 public void setSmallView(boolean smallView) { 536 537 m_smallView = smallView; 538 if (m_smallView) { 539 m_decoratedPanel.addDecorationBoxStyle( 540 I_CmsLayoutBundle.INSTANCE.floatDecoratedPanelCss().decorationBoxSmall()); 541 } 542 } 543 544 /** 545 * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int) 546 */ 547 public void truncate(String textMetricsPrefix, int widgetWidth) { 548 549 boolean hasDataValue = m_mainWidget instanceof CmsDataValue; 550 for (Widget widget : m_panel) { 551 if (!(widget instanceof I_CmsTruncable)) { 552 continue; 553 } 554 int width = widgetWidth - 4; // just to be on the safe side 555 if (widget instanceof CmsList<?>) { 556 if (hasDataValue) { 557 width = widgetWidth; 558 } else { 559 width -= 25; // 25px left margin 560 } 561 } 562 ((I_CmsTruncable)widget).truncate(textMetricsPrefix, width); 563 } 564 } 565 566 /** 567 * Adds a check box to this list item.<p> 568 * 569 * @param checkbox the check box 570 */ 571 protected void addCheckBox(CmsCheckBox checkbox) { 572 573 assert m_checkbox == null; 574 m_checkbox = checkbox; 575 addDecoration(m_checkbox, CHECKBOX_WIDTH, false); 576 } 577 578 /** 579 * Helper method for adding a decoration widget and updating the decoration width accordingly.<p> 580 * 581 * @param widget the decoration widget to add 582 * @param width the intended width of the decoration widget 583 * @param first if true, inserts the widget at the front of the decorations, else at the end. 584 */ 585 protected void addDecoration(Widget widget, int width, boolean first) { 586 587 m_decorationWidgets.add(widget); 588 m_decorationWidth += width; 589 } 590 591 /** 592 * Adds the main widget to the list item.<p> 593 * 594 * In most cases, the widget will be a list item widget. If this is the case, then further calls to {@link CmsListItem#getListItemWidget()} will 595 * return the widget which was passed as a parameter to this method. Otherwise, the method will return null.<p> 596 * 597 * @param widget the main content widget 598 */ 599 protected void addMainWidget(Widget widget) { 600 601 assert m_mainWidget == null; 602 assert m_listItemWidget == null; 603 if (widget instanceof CmsListItemWidget) { 604 m_listItemWidget = (CmsListItemWidget)widget; 605 } 606 m_mainWidget = widget; 607 } 608 609 /** 610 * Clones the given item to be used as a place holder.<p> 611 * 612 * @param listItem the item to clone 613 * 614 * @return the cloned item 615 */ 616 protected Element cloneForPlaceholder(CmsListItem listItem) { 617 618 Element clone = CmsDomUtil.clone(listItem.getElement()); 619 clone.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder()); 620 621 // remove hoverbar 622 List<Element> elems = CmsDomUtil.getElementsByClass( 623 I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().buttonPanel(), 624 CmsDomUtil.Tag.div, 625 clone); 626 for (com.google.gwt.dom.client.Element elem : elems) { 627 elem.removeFromParent(); 628 } 629 630 return clone; 631 } 632 633 /** 634 * This internal helper method creates the actual contents of the widget by combining the decorators and the main widget.<p> 635 */ 636 protected void initContent() { 637 638 if (m_decoratedPanel != null) { 639 m_decoratedPanel.removeFromParent(); 640 } 641 m_decoratedPanel = new CmsSimpleDecoratedPanel(m_decorationWidth, m_mainWidget, m_decorationWidgets); 642 m_panel.insert(m_decoratedPanel, 0); 643 setSmallView(m_smallView); 644 } 645 646 /** 647 * This method is a convenience method which sets the checkbox and main widget of this widget, and then calls {@link CmsListItem#initContent()}.<p> 648 * 649 * @param checkbox the checkbox to add 650 * @param mainWidget the mainWidget to add 651 */ 652 protected void initContent(CmsCheckBox checkbox, Widget mainWidget) { 653 654 addCheckBox(checkbox); 655 addMainWidget(mainWidget); 656 initContent(); 657 } 658 659 /** 660 * This method is a convenience method which sets the main widget of this widget, and then calls {@link CmsListItem#initContent()}.<p> 661 * 662 * @param mainWidget the main widget to add 663 */ 664 protected void initContent(Widget mainWidget) { 665 666 addMainWidget(mainWidget); 667 initContent(); 668 } 669 670 /** 671 * Gets the left edge of the move handle located in the element.<p> 672 * 673 * @param elem the element to search in 674 * 675 * @return the left edge of the move handle 676 */ 677 protected int moveHandleLeft(Element elem) { 678 679 return CmsDomUtil.getElementsByClass(MOVE_HANDLE_MARKER_CLASS, elem).get(0).getAbsoluteLeft(); 680 } 681 682 /** 683 * Removes a decoration widget.<p> 684 * 685 * @param widget the widget to remove 686 * @param width the widget width 687 */ 688 protected void removeDecorationWidget(Widget widget, int width) { 689 690 if ((widget != null) && m_decorationWidgets.remove(widget)) { 691 m_decorationWidth -= width; 692 initContent(); 693 } 694 } 695 696 /** 697 * Called when a drag operation for this widget is stopped.<p> 698 */ 699 private void clearDrag() { 700 701 if (m_listItemWidget != null) { 702 Iterator<Widget> buttonIterator = m_listItemWidget.getButtonPanel().iterator(); 703 while (buttonIterator.hasNext()) { 704 Widget button = buttonIterator.next(); 705 button.getElement().getStyle().clearVisibility(); 706 } 707 } 708 if (m_helper != null) { 709 m_helper.removeFromParent(); 710 m_helper = null; 711 } 712 if (m_provisionalParent != null) { 713 m_provisionalParent.removeFromParent(); 714 m_provisionalParent = null; 715 } 716 setVisible(true); 717 718 } 719}