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.ade.containerpage.client.ui;
029
030import org.opencms.acacia.client.CmsEditorBase;
031import org.opencms.acacia.client.I_CmsInlineFormParent;
032import org.opencms.ade.containerpage.client.CmsContainerpageController;
033import org.opencms.ade.containerpage.client.ui.css.I_CmsLayoutBundle;
034import org.opencms.ade.containerpage.shared.CmsElementLockInfo;
035import org.opencms.ade.containerpage.shared.CmsInheritanceInfo;
036import org.opencms.ade.contenteditor.client.CmsContentEditor;
037import org.opencms.gwt.client.I_CmsElementToolbarContext;
038import org.opencms.gwt.client.dnd.I_CmsDraggable;
039import org.opencms.gwt.client.dnd.I_CmsDropTarget;
040import org.opencms.gwt.client.ui.CmsHighlightingBorder;
041import org.opencms.gwt.client.ui.CmsListItemWidget;
042import org.opencms.gwt.client.ui.I_CmsButton;
043import org.opencms.gwt.client.util.CmsDebugLog;
044import org.opencms.gwt.client.util.CmsDomUtil;
045import org.opencms.gwt.client.util.CmsDomUtil.Tag;
046import org.opencms.gwt.client.util.CmsPositionBean;
047import org.opencms.gwt.shared.CmsGwtConstants;
048import org.opencms.gwt.shared.CmsListInfoBean;
049import org.opencms.util.CmsStringUtil;
050import org.opencms.util.CmsUUID;
051
052import java.util.Arrays;
053import java.util.HashMap;
054import java.util.Iterator;
055import java.util.List;
056import java.util.Map;
057import java.util.Map.Entry;
058
059import com.google.common.base.Optional;
060import com.google.common.collect.Lists;
061import com.google.gwt.core.client.JavaScriptObject;
062import com.google.gwt.core.client.Scheduler;
063import com.google.gwt.core.client.Scheduler.ScheduledCommand;
064import com.google.gwt.dom.client.Element;
065import com.google.gwt.dom.client.NodeList;
066import com.google.gwt.dom.client.Style;
067import com.google.gwt.dom.client.Style.Display;
068import com.google.gwt.dom.client.Style.Position;
069import com.google.gwt.dom.client.Style.Unit;
070import com.google.gwt.event.dom.client.ClickEvent;
071import com.google.gwt.event.dom.client.ClickHandler;
072import com.google.gwt.event.dom.client.HasClickHandlers;
073import com.google.gwt.event.shared.HandlerRegistration;
074import com.google.gwt.regexp.shared.MatchResult;
075import com.google.gwt.regexp.shared.RegExp;
076import com.google.gwt.user.client.DOM;
077import com.google.gwt.user.client.Event;
078import com.google.gwt.user.client.Event.NativePreviewEvent;
079import com.google.gwt.user.client.Event.NativePreviewHandler;
080import com.google.gwt.user.client.Timer;
081import com.google.gwt.user.client.ui.AbsolutePanel;
082import com.google.gwt.user.client.ui.IsWidget;
083import com.google.gwt.user.client.ui.RootPanel;
084
085/**
086 * Content element within a container-page.<p>
087 *
088 * @since 8.0.0
089 */
090public class CmsContainerPageElementPanel extends AbsolutePanel
091implements I_CmsDraggable, HasClickHandlers, I_CmsInlineFormParent {
092
093    /**
094     * Parses CSS classess of the form 'oc-point-TY_LX', where X and Y are strings
095     * of decimal digits possibly preceded by a minus sign.<p>
096     *
097     * The numeric values of Y and X will be available after a successful parse using the
098     * methods getOffsetTop() and getOffsetLeft().
099     *
100     * This is used to offer the formatter developer some control over the edit point
101     * positioning.
102     */
103    public static class PointPositioningParser {
104
105        /** Regular expression used to match the special CSS classes. */
106        private static final RegExp REGEX = RegExp.compile("^oc-point-T(-?[0-9]+)_L(-?[0-9]+)$");
107
108        /** The left offset. */
109        private int m_left;
110
111        /** The top offset. */
112        private int m_top;
113
114        /**
115         * Gets the left offset after a CSS class has successfully been parsed.<p>
116         *
117         * @return the left offset
118         */
119        int getOffsetLeft() {
120
121            return m_left;
122        }
123
124        /**
125         * Gets the top offset after a CSS class has successfully been parsed.<p>
126         *
127         * @return the top offset
128         */
129        int getOffsetTop() {
130
131            return m_top;
132        }
133
134        /**
135         * Tries to parse a point positioning instruction from an element's class attribute
136         * and returns true when successful.<p>
137         *
138         * @param cssClass the value of a class attribute
139         *
140         * @return true if a positioning instruction was found
141         */
142        boolean tryParse(String cssClass) {
143
144            m_left = 0;
145            m_top = 0;
146            if (cssClass == null) {
147                cssClass = "";
148            }
149            for (String token : cssClass.trim().split(" +")) {
150                if (tryParseSingleClass(token)) {
151                    return true;
152                }
153            }
154            return false;
155        }
156
157        /**
158         * Parses a single token from a class attribute.<p>
159         *
160         * @param token the token
161         * @return true if the token was a point positioning instruction
162         */
163        private boolean tryParseSingleClass(String token) {
164
165            MatchResult result = REGEX.exec(token);
166            if (result != null) {
167                m_top = Integer.parseInt(result.getGroup(1));
168                m_left = Integer.parseInt(result.getGroup(2));
169                return true;
170            } else {
171                if (token.startsWith("oc-point")) {
172                    CmsDebugLog.consoleLog("Malformed oc-point class: " + token);
173                }
174                return false;
175            }
176        }
177
178    }
179
180    /** The is model group property key. */
181    public static final String PROP_IS_MODEL_GROUP = "is_model_group";
182
183    /** The former copy model property. */
184    public static final String PROP_WAS_MODEL_GROUP = "was_model_group";
185
186    /** Property used to mark an element as belonging to this widget. */
187    public static final String PROP_ELEMENT_OBJECT_ID = "element_object_id";
188
189    /** Highlighting border for this element. */
190    protected CmsHighlightingBorder m_highlighting;
191
192    /** A flag which indicates whether the height has already been checked. */
193    private boolean m_checkedHeight;
194
195    /** Flag indicating the the editables are currently being checked. */
196    private boolean m_checkingEditables;
197
198    /** The elements client id. */
199    private String m_clientId;
200
201    /** The 'create new' flag. */
202    private boolean m_createNew;
203
204    /**
205     * Flag which indicates whether the new editor is disabled for this element.<p>
206     */
207    private boolean m_disableNewEditor;
208
209    /** The direct edit bar instances. */
210    private Map<Element, CmsListCollectorEditor> m_editables;
211
212    /** The editor click handler registration. */
213    private HandlerRegistration m_editorClickHandlerRegistration;
214
215    /** The option bar, holding optional function buttons. */
216    private CmsElementOptionBar m_elementOptionBar;
217
218    /** The element element view. */
219    private CmsUUID m_elementView;
220
221    /** The overlay for expired elements. */
222    private Element m_expiredOverlay;
223
224    /** Indicates an edit handler is configured for the element resource type. */
225    private boolean m_hasEditHandler;
226
227    /** Indicates whether this element has settings to edit. */
228    private boolean m_hasSettings;
229
230    /** The inheritance info for this element. */
231    private CmsInheritanceInfo m_inheritanceInfo;
232
233    /** The model group id. */
234    private CmsUUID m_modelGroupId;
235
236    /** The is new element type. */
237    private String m_newType;
238
239    /** The registered node insert event handler. */
240    private JavaScriptObject m_nodeInsertHandler;
241
242    /** The no edit reason, if empty editing is allowed. */
243    private String m_noEditReason;
244
245    /** The parent drop target. */
246    private I_CmsDropContainer m_parent;
247
248    /** Parser for point positioning isntructions. */
249    private PointPositioningParser m_positioningInstructionParser = new PointPositioningParser();
250
251    /** The drag and drop parent div. */
252    private Element m_provisionalParent;
253
254    /** Flag indicating if the element resource is currently released and not expired. */
255    private boolean m_releasedAndNotExpired;
256
257    /** The resource type. */
258    private String m_resourceType;
259
260    /** The element resource site-path. */
261    private String m_sitePath;
262
263    /** The sub title. */
264    private String m_subTitle;
265
266    /** The resource title. */
267    private String m_title;
268
269    /**
270     * Indicates if the current user has view permissions on the element resource.
271     * Without view permissions, the element can neither be edited, nor moved.
272     **/
273    private boolean m_viewPermission;
274
275    /** The former copy model status. */
276    private boolean m_wasModelGroup;
277    /**
278     * Indicates if the current user has write permissions on the element resource.
279     * Without write permissions, the element can not be edited.
280     **/
281    private boolean m_writePermission;
282
283    /** A random id, which is also stored as a property on the HTML element for this widget. */
284    private String m_objectId;
285
286    /** The resource type icon CSS classes. */
287    private String m_iconClasses;
288
289    /** The lock information. */
290    private CmsElementLockInfo m_lockInfo;
291
292    /**
293     * Constructor.<p>
294     *
295     * @param element the DOM element
296     * @param parent the drag parent
297     * @param clientId the client id
298     * @param sitePath the element site-path
299     * @param noEditReason the no edit reason, if empty, editing is allowed
300     * @param lockInfo the lock information
301     * @param title the resource title
302     * @param subTitle the sub title
303     * @param resourceType the resource type
304     * @param hasSettings should be true if the element has settings which can be edited
305     * @param hasViewPermission indicates if the current user has view permissions on the element resource
306     * @param hasWritePermission indicates if the current user has write permissions on the element resource
307     * @param releasedAndNotExpired <code>true</code> if the element resource is currently released and not expired
308     * @param disableNewEditor flag to disable the new editor for this element
309     * @param hasEditHandler indicates an edit handler is configured for the element resource type
310     * @param modelGroupId the model group id
311     * @param wasModelGroup in case of a former copy model group
312     * @param elementView the element view of the element
313     * @param iconClasses the resource type icon CSS classes
314     */
315    public CmsContainerPageElementPanel(
316        Element element,
317        I_CmsDropContainer parent,
318        String clientId,
319        String sitePath,
320        String noEditReason,
321        CmsElementLockInfo lockInfo,
322        String title,
323        String subTitle,
324        String resourceType,
325        boolean hasSettings,
326        boolean hasViewPermission,
327        boolean hasWritePermission,
328        boolean releasedAndNotExpired,
329        boolean disableNewEditor,
330        boolean hasEditHandler,
331        CmsUUID modelGroupId,
332        boolean wasModelGroup,
333        CmsUUID elementView,
334        String iconClasses) {
335
336        super(element);
337        m_clientId = clientId;
338        m_objectId = "" + Math.random();
339        m_sitePath = sitePath;
340        m_title = title;
341        m_subTitle = subTitle;
342        m_resourceType = resourceType;
343        m_noEditReason = noEditReason;
344        m_lockInfo = lockInfo;
345        m_hasSettings = hasSettings;
346        m_parent = parent;
347        m_disableNewEditor = disableNewEditor;
348        m_modelGroupId = modelGroupId;
349        m_wasModelGroup = wasModelGroup;
350        m_hasEditHandler = hasEditHandler;
351        setViewPermission(hasViewPermission);
352        setWritePermission(hasWritePermission);
353        setReleasedAndNotExpired(releasedAndNotExpired);
354        m_elementView = elementView;
355        getElement().setPropertyBoolean(PROP_IS_MODEL_GROUP, modelGroupId != null);
356        getElement().setPropertyBoolean(PROP_WAS_MODEL_GROUP, wasModelGroup);
357        getElement().setPropertyString(PROP_ELEMENT_OBJECT_ID, m_objectId);
358        m_iconClasses = iconClasses;
359    }
360
361    /**
362     * Checks if the element is an overlay for a container page element.<p>
363     *
364     * @param element the element to check
365     * @return true if the element is an overlay
366     */
367    public static boolean isOverlay(Element element) {
368
369        for (String overlayClass : Arrays.asList(
370            I_CmsLayoutBundle.INSTANCE.containerpageCss().expiredOverlay(),
371            I_CmsElementToolbarContext.ELEMENT_OPTION_BAR_CSS_CLASS)) {
372            if (element.hasClassName(overlayClass)) {
373                return true;
374            }
375        }
376        return false;
377
378    }
379
380    /**
381     * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(com.google.gwt.event.dom.client.ClickHandler)
382     */
383    public HandlerRegistration addClickHandler(ClickHandler handler) {
384
385        return addDomHandler(handler, ClickEvent.getType());
386    }
387
388    /**
389     * @see org.opencms.acacia.client.I_CmsInlineFormParent#adoptWidget(com.google.gwt.user.client.ui.IsWidget)
390     */
391    public void adoptWidget(IsWidget widget) {
392
393        getChildren().add(widget.asWidget());
394        adopt(widget.asWidget());
395    }
396
397    /**
398     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getCursorOffsetDelta()
399     */
400    public Optional<int[]> getCursorOffsetDelta() {
401
402        return Optional.absent();
403    }
404
405    /**
406     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getDragHelper(org.opencms.gwt.client.dnd.I_CmsDropTarget)
407     */
408    public Element getDragHelper(I_CmsDropTarget target) {
409
410        CmsListInfoBean info = new CmsListInfoBean(m_title, m_subTitle, null);
411        info.setResourceType(m_resourceType);
412        info.setBigIconClasses(m_iconClasses);
413        CmsListItemWidget helperWidget = new CmsListItemWidget(info);
414        helperWidget.setWidth("600px");
415        helperWidget.truncate("ggg", 550);
416        Element helper = helperWidget.getElement();
417        Element button = DOM.createDiv();
418        button.addClassName("opencms-icon");
419        button.addClassName(I_CmsButton.MOVE_SMALL);
420        button.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragHandle());
421        helper.appendChild(button);
422        helper.addClassName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().shadow());
423        Element parentElement = getElement().getParentElement();
424        int elementTop = getElement().getAbsoluteTop();
425        int parentTop = parentElement.getAbsoluteTop();
426        m_provisionalParent = DOM.createElement(parentElement.getTagName());
427        RootPanel.getBodyElement().appendChild(m_provisionalParent);
428        m_provisionalParent.addClassName(
429            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().clearStyles());
430        m_provisionalParent.addClassName(
431            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().opencms());
432        m_provisionalParent.getStyle().setWidth(parentElement.getOffsetWidth(), Unit.PX);
433        m_provisionalParent.appendChild(helper);
434        Style style = helper.getStyle();
435        style.setWidth(helper.getOffsetWidth(), Unit.PX);
436        // the dragging class will set position absolute
437        helper.addClassName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().dragging());
438        style.setTop(elementTop - parentTop, Unit.PX);
439        m_provisionalParent.getStyle().setPosition(Position.ABSOLUTE);
440        m_provisionalParent.getStyle().setTop(parentTop, Unit.PX);
441        m_provisionalParent.getStyle().setLeft(parentElement.getAbsoluteLeft(), Unit.PX);
442        m_provisionalParent.getStyle().setZIndex(I_CmsLayoutBundle.INSTANCE.constants().css().zIndexDND());
443
444        return helper;
445    }
446
447    /**
448     * Returns the option bar of this element.<p>
449     *
450     * @return the option bar widget
451     */
452    public CmsElementOptionBar getElementOptionBar() {
453
454        return m_elementOptionBar;
455    }
456
457    /**
458     * Returns the elements element view.<p>
459     *
460     * @return the element view
461     */
462    public CmsUUID getElementView() {
463
464        return m_elementView;
465    }
466
467    /**
468     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getId()
469     */
470    public String getId() {
471
472        return m_clientId;
473    }
474
475    /**
476     * Returns the inheritance info for this element.<p>
477     *
478     * @return the inheritance info for this element
479     */
480    public CmsInheritanceInfo getInheritanceInfo() {
481
482        return m_inheritanceInfo;
483    }
484
485    /**
486     * Gets the lock information.
487     *
488     * @return the lock information
489     */
490    public CmsElementLockInfo getLockInfo() {
491
492        return m_lockInfo != null ? m_lockInfo : new CmsElementLockInfo(null, false);
493    }
494
495    /**
496     * Returns the model group id.<p>
497     *
498     * @return the model group id
499     */
500    public CmsUUID getModelGroupId() {
501
502        return m_modelGroupId;
503    }
504
505    /**
506     * Returns the new element type.
507     *
508     * @return the new element type
509     */
510    public String getNewType() {
511
512        return m_newType;
513    }
514
515    /**
516     * Returns the no edit reason.<p>
517     *
518     * @return the no edit reason
519     */
520    public String getNoEditReason() {
521
522        return m_noEditReason;
523    }
524
525    /**
526     * Gets the random id identifying this widget.
527     *
528     * <p>The id is also stored in the element_object_id property of the DOM element for this widget.
529     *
530     * @return the random id identifying this widget
531     */
532    public String getObjectId() {
533
534        return m_objectId;
535    }
536
537    /**
538     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getParentTarget()
539     */
540    public I_CmsDropContainer getParentTarget() {
541
542        return m_parent;
543    }
544
545    /**
546     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#getPlaceholder(org.opencms.gwt.client.dnd.I_CmsDropTarget)
547     */
548    public Element getPlaceholder(I_CmsDropTarget target) {
549
550        Element placeholder = CmsDomUtil.clone(getElement());
551        placeholder.addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragPlaceholder());
552        return placeholder;
553    }
554
555    /**
556     * Returns if the element resource is currently released and not expired.<p>
557     *
558     * @return <code>true</code> if the element resource is currently released and not expired
559     */
560    public boolean getReleasedAndNotExpired() {
561
562        return m_releasedAndNotExpired;
563    }
564
565    /**
566     * Returns the element resource type name.<p>
567     *
568     * @return the resource type name
569     */
570    public String getResourceType() {
571
572        return m_resourceType;
573    }
574
575    /**
576     * Returns the site-path.<p>
577     *
578     * @return the site-path
579     */
580    public String getSitePath() {
581
582        return m_sitePath;
583    }
584
585    /**
586     * Returns the structure id of the element.<p>
587     *
588     * @return the structure id of the element
589     */
590    public CmsUUID getStructureId() {
591
592        if (m_clientId == null) {
593            return null;
594        }
595        return new CmsUUID(CmsContainerpageController.getServerId(m_clientId));
596    }
597
598    /**
599     * Indicates an edit handler is configured for the element resource type.<p>
600     *
601     * @return indicates an edit handler is configured for the element resource type
602     */
603    public boolean hasEditHandler() {
604
605        return m_hasEditHandler;
606    }
607
608    /**
609     * Returns whether this element has a model group parent.<p>
610     *
611     * @return <code>true</code> if this element has a model group parent
612     */
613    public boolean hasModelGroupParent() {
614
615        boolean result = false;
616        Element parent = getElement().getParentElement();
617        while (parent != null) {
618            if (parent.getPropertyBoolean(PROP_IS_MODEL_GROUP)) {
619                result = true;
620                break;
621            }
622            parent = parent.getParentElement();
623        }
624        return result;
625    }
626
627    /**
628     * In case the inner HTML contains the reload marker.<p>
629     *
630     * @return <code>true</code> in case the inner HTML contains the reload marker
631     */
632    public boolean hasReloadMarker() {
633
634        return getElement().getInnerHTML().contains(CmsGwtConstants.FORMATTER_RELOAD_MARKER);
635    }
636
637    /**
638     * Returns true if the element has settings to edit.<p>
639     *
640     * @return true if the element has settings to edit
641     */
642    public boolean hasSettings() {
643
644        return m_hasSettings;
645    }
646
647    /**
648     * Returns if the current user has view permissions for the element resource.<p>
649     *
650     * @return <code>true</code> if the current user has view permissions for the element resource
651     */
652    public boolean hasViewPermission() {
653
654        return m_viewPermission;
655    }
656
657    /**
658     * Returns if the user has write permission.<p>
659     *
660     * @return <code>true</code> if the user has write permission
661     */
662    public boolean hasWritePermission() {
663
664        return m_writePermission;
665    }
666
667    /**
668     * Hides list collector direct edit buttons, if present.<p>
669     */
670    public void hideEditableListButtons() {
671
672        if (m_editables != null) {
673            for (CmsListCollectorEditor editor : m_editables.values()) {
674                editor.getElement().getStyle().setDisplay(Display.NONE);
675            }
676        }
677    }
678
679    /**
680     * Puts a highlighting border around the element.<p>
681     */
682    public void highlightElement() {
683
684        CmsPositionBean position = CmsPositionBean.getBoundingClientRect(getElement());
685        if (m_highlighting == null) {
686            m_highlighting = new CmsHighlightingBorder(
687                position,
688                isNew() || (CmsContainerpageController.get().getData().isModelPage() && isCreateNew())
689                ? CmsHighlightingBorder.BorderColor.blue
690                : CmsHighlightingBorder.BorderColor.red);
691            RootPanel.get().add(m_highlighting);
692        } else {
693            m_highlighting.setPosition(position);
694        }
695    }
696
697    /**
698     * Initializes the editor click handler.<p>
699     *
700     * @param controller the container page controller instance
701     */
702    public void initInlineEditor(final CmsContainerpageController controller) {
703
704        if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_noEditReason)
705            && !m_disableNewEditor
706            && CmsContentEditor.setEditable(getElement(), CmsContainerpageController.getServerId(m_clientId), true)) {
707            if (m_editorClickHandlerRegistration != null) {
708                m_editorClickHandlerRegistration.removeHandler();
709            }
710            m_editorClickHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
711
712                public void onPreviewNativeEvent(NativePreviewEvent event) {
713
714                    if (event.getTypeInt() == Event.ONCLICK) {
715                        // if another content is already being edited, don't start another editor
716                        if (controller.isContentEditing()) {
717                            return;
718                        }
719                        Element eventTarget = event.getNativeEvent().getEventTarget().cast();
720                        // check if the event target is a child
721                        if (getElement().isOrHasChild(eventTarget)) {
722                            Element target = event.getNativeEvent().getEventTarget().cast();
723                            // check if the target closest ancestor drag element is this element
724                            Element parentContainerElement = CmsDomUtil.getAncestor(
725                                target,
726                                I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement());
727                            if (parentContainerElement == getElement()) {
728                                while ((target != null)
729                                    && !target.getTagName().equalsIgnoreCase("a")
730                                    && (target != getElement())) {
731                                    if (CmsContentEditor.isEditable(target)) {
732                                        CmsEditorBase.markForInlineFocus(target);
733                                        controller.getHandler().openEditorForElement(
734                                            CmsContainerPageElementPanel.this,
735                                            true,
736                                            isNew());
737                                        removeEditorHandler();
738                                        event.cancel();
739                                        break;
740                                    } else {
741                                        target = target.getParentElement();
742                                    }
743                                }
744                            }
745                        }
746                    }
747                }
748            });
749        }
750    }
751
752    /**
753     * Checks if this element has 'createNew' status, i.e. will be copied when using this page as a model for a new container page.<p>
754     *
755     * @return true if this element has createNew status
756     */
757    public boolean isCreateNew() {
758
759        return m_createNew;
760    }
761
762    /**
763     * Returns whether the element is used as a model group.<p>
764     *
765     * @return <code>true</code> if the element is used as a model group
766     */
767    public boolean isModelGroup() {
768
769        return m_modelGroupId != null;
770    }
771
772    /**
773     * Returns if this is e newly created element.<p>
774     *
775     * @return <code>true</code> if the element is new
776     */
777    public boolean isNew() {
778
779        return m_newType != null;
780    }
781
782    /**
783     * Returns true if the new content editor is disabled for this element.<p>
784     *
785     * @return true if the new editor is disabled for this element
786     */
787    public boolean isNewEditorDisabled() {
788
789        return m_disableNewEditor;
790    }
791
792    /**
793     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDragCancel()
794     */
795    public void onDragCancel() {
796
797        clearDrag();
798        resetOptionbar();
799    }
800
801    /**
802     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onDrop(org.opencms.gwt.client.dnd.I_CmsDropTarget)
803     */
804    public void onDrop(I_CmsDropTarget target) {
805
806        clearDrag();
807    }
808
809    /**
810     * @see org.opencms.gwt.client.dnd.I_CmsDraggable#onStartDrag(org.opencms.gwt.client.dnd.I_CmsDropTarget)
811     */
812    public void onStartDrag(I_CmsDropTarget target) {
813
814        CmsDomUtil.addDisablingOverlay(getElement());
815        getElement().getStyle().setOpacity(0.7);
816        removeHighlighting();
817    }
818
819    /**
820     * @see com.google.gwt.user.client.ui.Widget#removeFromParent()
821     */
822    @Override
823    public void removeFromParent() {
824
825        if (m_highlighting != null) {
826            m_highlighting.removeFromParent();
827            m_highlighting = null;
828        }
829        super.removeFromParent();
830    }
831
832    /**
833     * Removes the highlighting border.<p>
834     */
835    public void removeHighlighting() {
836
837        if (m_highlighting != null) {
838            m_highlighting.removeFromParent();
839            m_highlighting = null;
840        }
841    }
842
843    /**
844     * Removes the inline editor.<p>
845     */
846    public void removeInlineEditor() {
847
848        CmsContentEditor.setEditable(getElement(), CmsContainerpageController.getServerId(m_clientId), false);
849        removeEditorHandler();
850    }
851
852    /**
853     * @see org.opencms.acacia.client.I_CmsInlineFormParent#replaceHtml(java.lang.String)
854     */
855    public void replaceHtml(String html) {
856
857        // detach all children first
858        while (getChildren().size() > 0) {
859            getChildren().get(getChildren().size() - 1).removeFromParent();
860        }
861        Element tempDiv = DOM.createDiv();
862        tempDiv.setInnerHTML(html);
863        getElement().setInnerHTML(tempDiv.getFirstChildElement().getInnerHTML());
864    }
865
866    /**
867     * Sets the 'create new' status of the element.<p>
868     *
869     * @param createNew the new value for the 'create new' status
870     */
871    public void setCreateNew(boolean createNew) {
872
873        m_createNew = createNew;
874    }
875
876    /**
877     * Sets the element option bar.<p>
878     *
879     * @param elementOptionBar the element option bar to set
880     */
881    public void setElementOptionBar(CmsElementOptionBar elementOptionBar) {
882
883        if ((m_elementOptionBar != null) && (getWidgetIndex(m_elementOptionBar) >= 0)) {
884            m_elementOptionBar.removeFromParent();
885        }
886        m_elementOptionBar = elementOptionBar;
887        if (m_elementOptionBar != null) {
888            getElement().addClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement());
889            insert(m_elementOptionBar, 0);
890            updateOptionBarPosition();
891        } else {
892            getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.dragdropCss().dragElement());
893        }
894    }
895
896    /**
897     * Sets the element id.<p>
898     *
899     * @param id the id
900     */
901    public void setId(String id) {
902
903        m_clientId = id;
904    }
905
906    /**
907     * Sets the inheritance info for this element.<p>
908     *
909     * @param inheritanceInfo the inheritance info for this element to set
910     */
911    public void setInheritanceInfo(CmsInheritanceInfo inheritanceInfo) {
912
913        m_inheritanceInfo = inheritanceInfo;
914    }
915
916    /**
917     * Sets the new-type of the element.<p>
918     *
919     * @param newType the new-type
920     */
921    public void setNewType(String newType) {
922
923        m_newType = newType;
924    }
925
926    /**
927     * Sets the no edit reason.<p>
928     *
929     * @param noEditReason the no edit reason to set
930     */
931    public void setNoEditReason(String noEditReason) {
932
933        m_noEditReason = noEditReason;
934    }
935
936    /**
937     * Sets if the element resource is currently released and not expired.<p>
938     *
939     * @param releasedAndNotExpired <code>true</code> if the element resource is currently released and not expired
940     */
941    public void setReleasedAndNotExpired(boolean releasedAndNotExpired) {
942
943        m_releasedAndNotExpired = releasedAndNotExpired;
944        if (m_releasedAndNotExpired) {
945            removeStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().expired());
946            if (m_expiredOverlay != null) {
947                m_expiredOverlay.removeFromParent();
948                m_expiredOverlay = null;
949            }
950
951        } else {
952            addStyleName(I_CmsLayoutBundle.INSTANCE.containerpageCss().expired());
953            m_expiredOverlay = DOM.createDiv();
954            m_expiredOverlay.setTitle("Expired resource");
955            m_expiredOverlay.addClassName(I_CmsLayoutBundle.INSTANCE.containerpageCss().expiredOverlay());
956            getElement().appendChild(m_expiredOverlay);
957        }
958    }
959
960    /**
961     * Sets the site path.<p>
962     *
963     * @param sitePath the site path to set
964     */
965    public void setSitePath(String sitePath) {
966
967        m_sitePath = sitePath;
968    }
969
970    /**
971     * Sets if the current user has view permissions for the element resource.<p>
972     *
973     * @param viewPermission the view permission to set
974     */
975    public void setViewPermission(boolean viewPermission) {
976
977        m_viewPermission = viewPermission;
978    }
979
980    /**
981     * Sets the user write permission.<p>
982     *
983     * @param writePermission the user write permission to set
984     */
985    public void setWritePermission(boolean writePermission) {
986
987        m_writePermission = writePermission;
988    }
989
990    /**
991     * Shows list collector direct edit buttons (old direct edit style), if present.<p>
992     */
993    public void showEditableListButtons() {
994
995        m_checkingEditables = true;
996        if (m_editables == null) {
997            m_editables = new HashMap<Element, CmsListCollectorEditor>();
998            List<Element> editables = getEditableElements();
999            if ((editables != null) && (editables.size() > 0)) {
1000                for (Element editable : editables) {
1001                    addListCollectorEditorButtons(editable);
1002                }
1003            }
1004        } else {
1005
1006            Iterator<Entry<Element, CmsListCollectorEditor>> it = m_editables.entrySet().iterator();
1007            while (it.hasNext()) {
1008                Entry<Element, CmsListCollectorEditor> entry = it.next();
1009                CmsListCollectorEditor editor = entry.getValue();
1010                if (!editor.isValid()) {
1011                    editor.removeFromParent();
1012                    it.remove();
1013                } else {
1014                    if (CmsDomUtil.hasDimension(editor.getElement().getParentElement())) {
1015                        editor.setParentHasDimensions(true);
1016                        editor.setPosition(
1017                            CmsDomUtil.getEditablePosition(entry.getValue().getMarkerTag()),
1018                            getElement());
1019                    } else {
1020                        editor.setParentHasDimensions(false);
1021                    }
1022                }
1023            }
1024            List<Element> editables = getEditableElements();
1025            if (editables.size() > m_editables.size()) {
1026                for (Element editable : editables) {
1027                    if (!m_editables.containsKey(editable)) {
1028                        addListCollectorEditorButtons(editable);
1029                    }
1030                }
1031            }
1032        }
1033
1034        boolean editableContainer = true;
1035        if (m_parent instanceof CmsContainerPageContainer) {
1036            editableContainer = CmsContainerpageController.get().isContainerEditable(m_parent);
1037        }
1038        for (CmsListCollectorEditor editor : m_editables.values()) {
1039            editor.updateVisibility(editableContainer);
1040        }
1041
1042        m_checkingEditables = false;
1043        resetNodeInsertedHandler();
1044    }
1045
1046    /**
1047     * Updates the option bar position.<p>
1048     */
1049    public void updateOptionBarPosition() {
1050
1051        // only if attached to the DOM
1052        if ((m_elementOptionBar != null) && RootPanel.getBodyElement().isOrHasChild(getElement())) {
1053            int absoluteTop = getElement().getAbsoluteTop();
1054            int absoluteRight = getElement().getAbsoluteRight();
1055            CmsPositionBean dimensions = CmsPositionBean.getBoundingClientRect(getElement());
1056
1057            int top = 0;
1058            int right = 0;
1059            int offsetLeft = 0;
1060            int offsetTop = 0;
1061
1062            final Style style = m_elementOptionBar.getElement().getStyle();
1063
1064            if (m_positioningInstructionParser.tryParse(getElement().getClassName())) {
1065                offsetLeft = m_positioningInstructionParser.getOffsetLeft();
1066                offsetTop = m_positioningInstructionParser.getOffsetTop();
1067            }
1068
1069            if (Math.abs(absoluteTop - dimensions.getTop()) > 20) {
1070                absoluteTop = (dimensions.getTop() - absoluteTop) + 2;
1071                top = absoluteTop;
1072            }
1073            if (Math.abs(absoluteRight - dimensions.getLeft() - dimensions.getWidth()) > 20) {
1074                absoluteRight = (absoluteRight - dimensions.getLeft() - dimensions.getWidth()) + 2;
1075                right = absoluteRight;
1076            }
1077
1078            top += offsetTop;
1079            right -= offsetLeft;
1080
1081            if (top != 0) {
1082                style.setTop(top, Unit.PX);
1083            } else {
1084                style.clearTop();
1085            }
1086
1087            if (right != 0) {
1088                style.setRight(right, Unit.PX);
1089            } else {
1090                style.clearRight();
1091            }
1092
1093            if (isOptionbarIFrameCollision(absoluteTop, m_elementOptionBar.getCalculatedWidth())) {
1094                style.setPosition(Position.RELATIVE);
1095                int marginLeft = getElement().getClientWidth() - m_elementOptionBar.getCalculatedWidth();
1096                if (marginLeft > 0) {
1097                    style.setMarginLeft(marginLeft, Unit.PX);
1098                }
1099            } else {
1100                style.clearPosition();
1101                style.clearMarginLeft();
1102            }
1103        }
1104    }
1105
1106    /**
1107     * Checks for changes in the list collector direct edit content.<p>
1108     */
1109    protected void checkForEditableChanges() {
1110
1111        if (!m_checkingEditables) {
1112            m_checkingEditables = true;
1113            Timer timer = new Timer() {
1114
1115                @Override
1116                public void run() {
1117
1118                    showEditableListButtons();
1119                }
1120            };
1121            timer.schedule(500);
1122        }
1123    }
1124
1125    /**
1126     * Gets the editable list elements.<p>
1127     *
1128     * @return the editable list elements
1129     */
1130    protected List<Element> getEditableElements() {
1131
1132        List<Element> elems = CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_EDITABLE, Tag.div, getElement());
1133        List<Element> result = Lists.newArrayList();
1134        for (Element currentElem : elems) {
1135            // don't return elements which are contained in nested containers
1136            if (m_parent.getContainerId().equals(getParentContainerId(currentElem))) {
1137                result.add(currentElem);
1138            }
1139        }
1140        return result;
1141    }
1142
1143    /**
1144     * Returns if the list collector direct edit content has changed.<p>
1145     *
1146     * @return <code>true</code> if the list collector direct edit content has changed
1147     */
1148    protected boolean hasChangedEditables() {
1149
1150        if (m_editables == null) {
1151            return true;
1152        }
1153        for (CmsListCollectorEditor editor : m_editables.values()) {
1154            if (!editor.isValid()) {
1155                return true;
1156            }
1157        }
1158        return getEditableElements().size() > m_editables.size();
1159    }
1160
1161    /**
1162     * @see com.google.gwt.user.client.ui.Widget#onDetach()
1163     */
1164    @Override
1165    protected void onDetach() {
1166
1167        super.onDetach();
1168        removeEditorHandler();
1169    }
1170
1171    /**
1172     * @see com.google.gwt.user.client.ui.Widget#onLoad()
1173     */
1174    @Override
1175    protected void onLoad() {
1176
1177        if ((getParentTarget() instanceof CmsContainerPageContainer)
1178            && ((CmsContainerPageContainer)getParentTarget()).isEditable()
1179            && !hasCheckedHeight()) {
1180            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
1181
1182                public void execute() {
1183
1184                    CmsContainerPageElementPanel thisElement = CmsContainerPageElementPanel.this;
1185                    if (!hasCheckedHeight() && CmsSmallElementsHandler.isSmall(thisElement)) {
1186                        CmsContainerpageController.get().getSmallElementsHandler().prepareSmallElement(thisElement);
1187                    }
1188                    setCheckedHeight(true);
1189                }
1190            });
1191        }
1192        resetOptionbar();
1193    }
1194
1195    /**
1196     * Removes the inline editor handler.<p>
1197     */
1198    protected void removeEditorHandler() {
1199
1200        if (m_editorClickHandlerRegistration != null) {
1201            m_editorClickHandlerRegistration.removeHandler();
1202            m_editorClickHandlerRegistration = null;
1203        }
1204    }
1205
1206    /**
1207     * Returns if the minimum element height has been checked.<p>
1208     *
1209     * @return <code>true</code> if the minimum element height has been checked
1210     */
1211    boolean hasCheckedHeight() {
1212
1213        return m_checkedHeight;
1214    }
1215
1216    /**
1217     * Sets the checked height flag.<p>
1218     *
1219     * @param checked the checked height flag
1220     */
1221    void setCheckedHeight(boolean checked) {
1222
1223        m_checkedHeight = checked;
1224    }
1225
1226    /**
1227     * Adds the collector edit buttons.<p>
1228     *
1229     * @param editable the marker element for an editable list element
1230     */
1231    private void addListCollectorEditorButtons(Element editable) {
1232
1233        CmsListCollectorEditor editor = new CmsListCollectorEditor(editable, m_clientId);
1234        add(editor, editable.getParentElement());
1235        if (CmsDomUtil.hasDimension(editable.getParentElement())) {
1236            editor.setParentHasDimensions(true);
1237            editor.setPosition(CmsDomUtil.getEditablePosition(editable), getElement());
1238        } else {
1239            editor.setParentHasDimensions(false);
1240        }
1241        m_editables.put(editable, editor);
1242    }
1243
1244    /**
1245     * Removes all styling done during drag and drop.<p>
1246     */
1247    private void clearDrag() {
1248
1249        CmsDomUtil.removeDisablingOverlay(getElement());
1250        m_elementOptionBar.getElement().removeClassName(
1251            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.stateCss().cmsHovering());
1252        getElement().getStyle().clearOpacity();
1253        getElement().getStyle().clearDisplay();
1254        updateOptionBarPosition();
1255        if (m_provisionalParent != null) {
1256            m_provisionalParent.removeFromParent();
1257            m_provisionalParent = null;
1258        }
1259    }
1260
1261    /**
1262     * Gets the container id of the most deeply nested container containing the given element, or null if no such container can be found.<p>
1263     *
1264     * @param elem the element
1265     * @return the container id of the deepest container containing the element
1266     */
1267    private String getParentContainerId(Element elem) {
1268
1269        String attr = CmsContainerPageContainer.PROP_CONTAINER_MARKER;
1270        Element lastElem;
1271        do {
1272            String propValue = elem.getPropertyString(attr);
1273            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(propValue)) {
1274                return propValue;
1275            }
1276            lastElem = elem;
1277            elem = elem.getParentElement();
1278        } while ((elem != null) && (elem != lastElem));
1279        return null;
1280    }
1281
1282    /**
1283     * Returns if the option bar position collides with any iframe child elements.<p>
1284     *
1285     * @param optionTop the option bar absolute top
1286     * @param optionWidth the option bar width
1287     *
1288     * @return <code>true</code> if there are iframe child elements located no less than 25px below the upper edge of the element
1289     */
1290    private boolean isOptionbarIFrameCollision(int optionTop, int optionWidth) {
1291
1292        if (RootPanel.getBodyElement().isOrHasChild(getElement())) {
1293            NodeList<Element> frames = getElement().getElementsByTagName(CmsDomUtil.Tag.iframe.name());
1294            for (int i = 0; i < frames.getLength(); i++) {
1295                int frameTop = frames.getItem(i).getAbsoluteTop();
1296                int frameHeight = frames.getItem(i).getOffsetHeight();
1297                int frameRight = frames.getItem(i).getAbsoluteRight();
1298                if (((frameTop - optionTop) < 25)
1299                    && (((frameTop + frameHeight) - optionTop) > 0)
1300                    && ((frameRight - getElement().getAbsoluteRight()) < optionWidth)) {
1301                    return true;
1302                }
1303
1304            }
1305        }
1306        return false;
1307    }
1308
1309    /**
1310     * Resets the node inserted handler.<p>
1311     */
1312    private native void resetNodeInsertedHandler()/*-{
1313                var $this = this;
1314                var element = $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::getElement()();
1315                var handler = $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::m_nodeInsertHandler;
1316                if (handler == null) {
1317                        handler = function(event) {
1318                                $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::checkForEditableChanges()();
1319                        };
1320                        $this.@org.opencms.ade.containerpage.client.ui.CmsContainerPageElementPanel::m_nodeInsertHandler = handler;
1321                } else {
1322                        if (element.removeEventLister) {
1323                                element.removeEventListener("DOMNodeInserted", handler);
1324                        } else if (element.detachEvent) {
1325                                // IE specific
1326                                element.detachEvent("onDOMNodeInserted", handler);
1327                        }
1328                }
1329                if (element.addEventListener) {
1330                        element.addEventListener("DOMNodeInserted", handler, false);
1331                } else if (element.attachEvent) {
1332                        // IE specific
1333                        element.attachEvent("onDOMNodeInserted", handler);
1334                }
1335    }-*/;
1336
1337    /**
1338     * This method removes the option-bar widget from DOM and re-attaches it at it's original position.<p>
1339     * Use to avoid mouse-over and mouse-down malfunction.<p>
1340     */
1341    private void resetOptionbar() {
1342
1343        if (m_elementOptionBar != null) {
1344            if (getWidgetIndex(m_elementOptionBar) >= 0) {
1345                m_elementOptionBar.removeFromParent();
1346            }
1347            updateOptionBarPosition();
1348            insert(m_elementOptionBar, 0);
1349        }
1350    }
1351}