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.CmsCoreProvider; 031import org.opencms.gwt.client.CmsEditableDataJSO; 032import org.opencms.gwt.client.CmsPageEditorTouchHandler; 033import org.opencms.gwt.client.I_CmsElementToolbarContext; 034import org.opencms.gwt.client.Messages; 035import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 036import org.opencms.gwt.client.ui.resourceinfo.CmsResourceInfoDialog; 037import org.opencms.gwt.client.util.CmsDomUtil; 038import org.opencms.gwt.client.util.CmsNewLinkFunctionTable; 039import org.opencms.gwt.client.util.CmsPositionBean; 040import org.opencms.gwt.client.util.CmsScriptCallbackHelper; 041import org.opencms.gwt.client.util.I_CmsUniqueActiveItem; 042import org.opencms.gwt.shared.CmsGwtConstants; 043import org.opencms.util.CmsStringUtil; 044 045import java.util.Collections; 046import java.util.HashMap; 047import java.util.Map; 048import java.util.TreeMap; 049 050import com.google.common.collect.Maps; 051import com.google.gwt.dom.client.Element; 052import com.google.gwt.dom.client.Style; 053import com.google.gwt.dom.client.Style.Unit; 054import com.google.gwt.event.dom.client.ClickEvent; 055import com.google.gwt.event.dom.client.ClickHandler; 056import com.google.gwt.event.dom.client.HasMouseOutHandlers; 057import com.google.gwt.event.dom.client.HasMouseOverHandlers; 058import com.google.gwt.event.dom.client.MouseOutEvent; 059import com.google.gwt.event.dom.client.MouseOutHandler; 060import com.google.gwt.event.dom.client.MouseOverEvent; 061import com.google.gwt.event.dom.client.MouseOverHandler; 062import com.google.gwt.event.shared.HandlerRegistration; 063import com.google.gwt.user.client.DOM; 064import com.google.gwt.user.client.Timer; 065import com.google.gwt.user.client.ui.FlowPanel; 066import com.google.gwt.user.client.ui.RootPanel; 067 068/** 069 * Class to provide direct edit buttons.<p> 070 * 071 * @since 8.0.0 072 */ 073public abstract class A_CmsDirectEditButtons extends FlowPanel 074implements HasMouseOverHandlers, HasMouseOutHandlers, I_CmsUniqueActiveItem, I_CmsElementToolbarContext { 075 076 /** 077 * Button handler for this class.<p> 078 */ 079 private class MouseHandler extends A_CmsHoverHandler implements ClickHandler { 080 081 /** 082 * Constructor.<p> 083 */ 084 protected MouseHandler() { 085 086 // nothing to do 087 } 088 089 /** 090 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) 091 */ 092 public void onClick(ClickEvent event) { 093 094 if (!CmsPageEditorTouchHandler.get().eatClick(A_CmsDirectEditButtons.this)) { 095 removeHighlightingAndBar(); 096 Object source = event.getSource(); 097 if (source == m_delete) { 098 onClickDelete(); 099 } 100 if (source == m_edit) { 101 onClickEdit(); 102 } 103 if (source == m_new) { 104 if (m_editableData.getExtensions().isUploadEnabled()) { 105 onClickUpload(); 106 } else { 107 onClickNew(true); 108 } 109 } 110 } 111 } 112 113 /** 114 * @see org.opencms.gwt.client.ui.A_CmsHoverHandler#onHoverIn(com.google.gwt.event.dom.client.MouseOverEvent) 115 */ 116 @Override 117 public void onHoverIn(MouseOverEvent event) { 118 119 if (!CmsPageEditorTouchHandler.get().ignoreHover()) { 120 CmsCoreProvider.get().getFlyoutMenuContainer().setActiveItem(A_CmsDirectEditButtons.this); 121 addHighlightingAndBar(); 122 } 123 } 124 125 /** 126 * @see org.opencms.gwt.client.ui.A_CmsHoverHandler#onHoverOut(com.google.gwt.event.dom.client.MouseOutEvent) 127 */ 128 @Override 129 public void onHoverOut(MouseOutEvent event) { 130 131 if (!CmsPageEditorTouchHandler.get().ignoreHover()) { 132 timer = new Timer() { 133 134 @Override 135 public void run() { 136 137 if (timer == this) { 138 removeHighlightingAndBar(); 139 } 140 } 141 }; 142 timer.schedule(750); 143 } 144 } 145 146 } 147 148 /** The timer used for hiding the option bar. */ 149 /*default */static Timer timer; 150 151 /** The delete button. */ 152 protected CmsPushButton m_delete; 153 154 /** The edit button. */ 155 protected CmsPushButton m_edit; 156 157 /** The editable data. */ 158 protected CmsEditableDataJSO m_editableData; 159 160 /** The expired resources overlay element. */ 161 protected Element m_expiredOverlay; 162 163 /** Highlighting border for this element. */ 164 protected CmsHighlightingBorder m_highlighting; 165 166 /** The editable marker tag. */ 167 protected Element m_markerTag; 168 169 /** The new button. */ 170 protected CmsPushButton m_new; 171 172 /** The parent element id. */ 173 protected String m_parentResourceId; 174 175 /** The editable element position. */ 176 protected CmsPositionBean m_position; 177 178 /** 179 * Constructor.<p> 180 * 181 * @param editable the editable marker tag 182 * @param parentId the parent element id 183 */ 184 public A_CmsDirectEditButtons(Element editable, String parentId) { 185 186 try { 187 setStyleName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.directEditCss().directEditButtons()); 188 addStyleName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.directEditCss().optionBar()); 189 addStyleName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll()); 190 m_markerTag = editable; 191 m_parentResourceId = parentId; 192 193 String jsonText = editable.getAttribute(CmsGwtConstants.ATTR_DATA_EDITABLE); 194 m_editableData = CmsEditableDataJSO.parseEditableData(jsonText); 195 CmsScriptCallbackHelper callbackForElement = new CmsScriptCallbackHelper() { 196 197 @Override 198 public void run() { 199 200 A_CmsDirectEditButtons.this.onClickNew(false); 201 } 202 }; 203 editable.setPropertyJSO("cmsOnClickNew", callbackForElement.createCallback()); 204 CmsNewLinkFunctionTable.INSTANCE.setHandler(m_editableData.getContextId(), new Runnable() { 205 206 public void run() { 207 208 A_CmsDirectEditButtons.this.onClickNew(false); 209 } 210 }); 211 212 MouseHandler handler = new MouseHandler(); 213 addMouseOutHandler(handler); 214 addMouseOverHandler(handler); 215 TreeMap<Integer, CmsPushButton> buttonMap = Maps.newTreeMap(); 216 217 if (m_editableData.hasDelete() && CmsStringUtil.isEmptyOrWhitespaceOnly(m_editableData.getNoEditReason())) { 218 m_delete = new CmsPushButton(); 219 m_delete.setImageClass(I_CmsButton.TRASH_SMALL); 220 m_delete.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_DELETE_0)); 221 m_delete.setButtonStyle(I_CmsButton.ButtonStyle.FONT_ICON, null); 222 buttonMap.put(Integer.valueOf(100), m_delete); 223 m_delete.addClickHandler(handler); 224 } 225 if (m_editableData.hasNew()) { 226 m_new = new CmsPushButton(); 227 if (m_editableData.getExtensions().isUploadEnabled()) { 228 m_new.setImageClass(I_CmsButton.UPLOAD_SELECTION); 229 m_new.setTitle(getUploadButtonTitle(m_editableData.getExtensions().getUploadFolder())); 230 } else { 231 m_new.setImageClass(I_CmsButton.ADD_SMALL); 232 m_new.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_NEW_0)); 233 } 234 235 m_new.setButtonStyle(I_CmsButton.ButtonStyle.FONT_ICON, null); 236 buttonMap.put(Integer.valueOf(200), m_new); 237 m_new.addClickHandler(handler); 238 } 239 Map<Integer, CmsPushButton> additionalButtons = getAdditionalButtons(); 240 buttonMap.putAll(additionalButtons); 241 if ((buttonMap.size() > 0) || m_editableData.hasEdit()) { 242 if (!m_editableData.getExtensions().isUploadEnabled()) { // for the upload case, the edit button is not needed, the bullseye edit point is displayed on the upload button instead 243 m_edit = new CmsPushButton(); 244 m_edit.setImageClass(I_CmsButton.ButtonData.SELECTION.getIconClass()); 245 m_edit.setButtonStyle(I_CmsButton.ButtonStyle.FONT_ICON, null); 246 buttonMap.put(Integer.valueOf(300), m_edit); 247 if (m_editableData.hasEdit()) { 248 m_edit.setTitle(I_CmsButton.ButtonData.EDIT.getTitle()); 249 m_edit.addStyleName(I_CmsLayoutBundle.INSTANCE.directEditCss().editableElement()); 250 m_edit.addClickHandler(handler); 251 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_editableData.getNoEditReason())) { 252 m_edit.disable(m_editableData.getNoEditReason()); 253 } 254 } else if (m_editableData.hasNew()) { 255 String message = Messages.get().key(Messages.GUI_DIRECTEDIT_ONLY_CREATE_0); 256 m_edit.disable(message); 257 } 258 } 259 } 260 261 if (CmsCoreProvider.isTouchOnly()) { 262 for (CmsPushButton button : additionalButtons.values()) { 263 button.addClickHandler(e -> { 264 removeHighlightingAndBar(); 265 }); 266 } 267 } 268 269 for (CmsPushButton button : buttonMap.values()) { 270 add(button); 271 button.addClickHandler(new ClickHandler() { 272 273 public void onClick(ClickEvent e) { 274 275 CmsCoreProvider.get().getFlyoutMenuContainer().clearIfMatches(A_CmsDirectEditButtons.this); 276 277 } 278 }); 279 } 280 281 if (m_editableData.isUnreleasedOrExpired()) { 282 m_expiredOverlay = DOM.createDiv(); 283 m_expiredOverlay.setClassName( 284 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.directEditCss().expiredListElementOverlay()); 285 m_markerTag.getParentElement().insertBefore(m_expiredOverlay, m_markerTag); 286 } 287 288 } catch (Exception e) { 289 throw new UnsupportedOperationException("Error while parsing editable tag information: " + e.getMessage()); 290 } 291 } 292 293 /** 294 * @see org.opencms.gwt.client.I_CmsElementToolbarContext#activateToolbarContext() 295 */ 296 public void activateToolbarContext() { 297 298 addHighlightingAndBar(); 299 300 } 301 302 /** 303 * @see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler) 304 */ 305 public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) { 306 307 return addDomHandler(handler, MouseOutEvent.getType()); 308 309 } 310 311 /** 312 * @see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler) 313 */ 314 public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) { 315 316 return addDomHandler(handler, MouseOverEvent.getType()); 317 } 318 319 /** 320 * Creates the button for displaying element information.<p> 321 * 322 * @return the created button 323 */ 324 public CmsPushButton createInfoButton() { 325 326 CmsPushButton infoButton = new CmsPushButton(); 327 infoButton.setImageClass(I_CmsButton.ButtonData.INFO_BUTTON.getSmallIconClass()); 328 infoButton.setTitle(I_CmsButton.ButtonData.INFO_BUTTON.getTitle()); 329 infoButton.setButtonStyle(I_CmsButton.ButtonStyle.FONT_ICON, null); 330 add(infoButton); 331 infoButton.addClickHandler(new ClickHandler() { 332 333 public void onClick(ClickEvent event) { 334 335 CmsResourceInfoDialog.load(m_editableData.getStructureId(), true, null, getInfoContext(), null); 336 } 337 }); 338 return infoButton; 339 } 340 341 /** 342 * @see org.opencms.gwt.client.I_CmsElementToolbarContext#deactivateToolbarContext() 343 */ 344 public void deactivateToolbarContext() { 345 346 removeHighlightingAndBar(); 347 348 } 349 350 /** 351 * Returns the marker tag.<p> 352 * 353 * @return the marker tag 354 */ 355 public Element getMarkerTag() { 356 357 return m_markerTag; 358 } 359 360 /** 361 * Puts a highlighting border around the element.<p> 362 */ 363 public void highlightElement() { 364 365 if (m_highlighting == null) { 366 m_highlighting = new CmsHighlightingBorder(m_position, CmsHighlightingBorder.BorderColor.red); 367 RootPanel.get().add(m_highlighting); 368 } else { 369 m_highlighting.setPosition(CmsPositionBean.getBoundingClientRect(getElement())); 370 } 371 } 372 373 /** 374 * Returns if this edit button is still valid.<p> 375 * 376 * @return <code>true</code> if this edit button is valid 377 */ 378 public boolean isValid() { 379 380 return RootPanel.getBodyElement().isOrHasChild(m_markerTag); 381 } 382 383 /** 384 * @see org.opencms.gwt.client.util.I_CmsUniqueActiveItem#onDeactivate() 385 */ 386 public void onDeactivate() { 387 388 removeHighlightingAndBar(); 389 } 390 391 /** 392 * @see com.google.gwt.user.client.ui.Widget#removeFromParent() 393 */ 394 @Override 395 public void removeFromParent() { 396 397 removeHighlighting(); 398 super.removeFromParent(); 399 } 400 401 /** 402 * Removes the highlighting border.<p> 403 */ 404 public void removeHighlighting() { 405 406 if (m_highlighting != null) { 407 m_highlighting.removeFromParent(); 408 m_highlighting = null; 409 } 410 } 411 412 /** 413 * Sets the position. Make sure the widget is attached to the DOM.<p> 414 * 415 * @param position the absolute position 416 * @param containerElement the parent container element 417 */ 418 public void setPosition(CmsPositionBean position, Element containerElement) { 419 420 m_position = position; 421 Element parent = CmsDomUtil.getPositioningParent(getElement()); 422 423 Style style = getElement().getStyle(); 424 style.setRight( 425 parent.getOffsetWidth() - ((m_position.getLeft() + m_position.getWidth()) - parent.getAbsoluteLeft()), 426 Unit.PX); 427 int top = m_position.getTop() - parent.getAbsoluteTop(); 428 if (m_position.getHeight() < 24) { 429 // if the highlighted area has a lesser height than the buttons, center vertically 430 top -= (24 - m_position.getHeight()) / 2; 431 } 432 style.setTop(top, Unit.PX); 433 434 updateExpiredOverlayPosition(parent); 435 } 436 437 /** 438 * Adds the highlighting and option bar.<p> 439 */ 440 protected void addHighlightingAndBar() { 441 442 timer = null; 443 highlightElement(); 444 getElement().addClassName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.stateCss().cmsHovering()); 445 } 446 447 /** 448 * Returns a map of additional buttons in a map, with the button position as key (buttons will be ordered by their position).<p> 449 * 450 * @return the map of additional buttons 451 */ 452 protected Map<Integer, CmsPushButton> getAdditionalButtons() { 453 454 return Collections.emptyMap(); 455 456 } 457 458 /** 459 * Provides context parameters for the resource info dialog.<p> 460 * 461 * @return the map of context parameters 462 */ 463 protected Map<String, String> getInfoContext() { 464 465 return new HashMap<String, String>(); 466 } 467 468 /** 469 * Gets the upload button title. 470 * 471 * @param uploadFolder the upload folder 472 * @return the upload button title 473 */ 474 protected abstract String getUploadButtonTitle(String uploadFolder); 475 476 /** 477 * This method should be executed when the "delete" direct edit button is clicked.<p> 478 */ 479 protected abstract void onClickDelete(); 480 481 /** 482 * This method should be executed when the "edit" direct edit button is clicked.<p> 483 */ 484 protected abstract void onClickEdit(); 485 486 /** 487 * This method should be executed when the "new" direct edit button is clicked.<p> 488 * 489 * @param askCreateMode true if the user should be asked for the 'content create mode' 490 */ 491 protected abstract void onClickNew(boolean askCreateMode); 492 493 /** 494 * Method to be executed when the "new" direct edit button is clicked, and the corresponding file has a type for which the upload dialog should be triggered. 495 */ 496 protected void onClickUpload() { 497 498 // empty 499 } 500 501 /** 502 * Removes the highlighting and option bar.<p> 503 */ 504 protected void removeHighlightingAndBar() { 505 506 removeHighlighting(); 507 getElement().removeClassName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.stateCss().cmsHovering()); 508 } 509 510 /** 511 * Updates the position of the expired resources overlay if present.<p> 512 * 513 * @param positioningParent the positioning parent element 514 */ 515 protected void updateExpiredOverlayPosition(Element positioningParent) { 516 517 if (m_expiredOverlay != null) { 518 Style expiredStyle = m_expiredOverlay.getStyle(); 519 expiredStyle.setHeight(m_position.getHeight() + 4, Unit.PX); 520 expiredStyle.setWidth(m_position.getWidth() + 4, Unit.PX); 521 expiredStyle.setTop(m_position.getTop() - positioningParent.getAbsoluteTop() - 2, Unit.PX); 522 expiredStyle.setLeft(m_position.getLeft() - positioningParent.getAbsoluteLeft() - 2, Unit.PX); 523 } 524 } 525 526}