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        super(CmsGwtConstants.TAG_OC_EDITPOINT);
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}