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