001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.contenteditor.client;
029
030import org.opencms.acacia.client.CmsAttributeHandler;
031import org.opencms.acacia.client.CmsEditorBase;
032import org.opencms.acacia.client.CmsUndoRedoHandler;
033import org.opencms.acacia.client.CmsUndoRedoHandler.UndoRedoState;
034import org.opencms.acacia.client.CmsValidationContext;
035import org.opencms.acacia.client.CmsValidationHandler;
036import org.opencms.acacia.client.CmsValueFocusHandler;
037import org.opencms.acacia.client.I_CmsEntityRenderer;
038import org.opencms.acacia.client.I_CmsInlineFormParent;
039import org.opencms.acacia.client.I_CmsWidgetService;
040import org.opencms.acacia.client.entity.CmsEntityBackend;
041import org.opencms.acacia.shared.CmsEntity;
042import org.opencms.acacia.shared.CmsEntityAttribute;
043import org.opencms.acacia.shared.CmsEntityChangeEvent;
044import org.opencms.acacia.shared.CmsEntityChangeEvent.ChangeType;
045import org.opencms.acacia.shared.CmsTabInfo;
046import org.opencms.acacia.shared.CmsType;
047import org.opencms.acacia.shared.CmsValidationResult;
048import org.opencms.ade.containerpage.client.CmsContainerpageController;
049import org.opencms.ade.containerpage.client.CmsContentEditorHandler;
050import org.opencms.ade.contenteditor.client.css.I_CmsLayoutBundle;
051import org.opencms.ade.contenteditor.shared.CmsComplexWidgetData;
052import org.opencms.ade.contenteditor.shared.CmsContentDefinition;
053import org.opencms.ade.contenteditor.shared.CmsEditHandlerData;
054import org.opencms.ade.contenteditor.shared.CmsSaveResult;
055import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService;
056import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentServiceAsync;
057import org.opencms.ade.contenteditor.widgetregistry.client.WidgetRegistry;
058import org.opencms.ade.publish.client.CmsPublishDialog;
059import org.opencms.ade.publish.shared.CmsPublishOptions;
060import org.opencms.gwt.client.CmsCoreProvider;
061import org.opencms.gwt.client.I_CmsEditableData;
062import org.opencms.gwt.client.rpc.CmsRpcAction;
063import org.opencms.gwt.client.rpc.CmsRpcPrefetcher;
064import org.opencms.gwt.client.ui.CmsConfirmDialog;
065import org.opencms.gwt.client.ui.CmsErrorDialog;
066import org.opencms.gwt.client.ui.CmsInfoHeader;
067import org.opencms.gwt.client.ui.CmsModelSelectDialog;
068import org.opencms.gwt.client.ui.CmsNotification;
069import org.opencms.gwt.client.ui.CmsNotification.Type;
070import org.opencms.gwt.client.ui.CmsPushButton;
071import org.opencms.gwt.client.ui.CmsToggleButton;
072import org.opencms.gwt.client.ui.CmsToolbar;
073import org.opencms.gwt.client.ui.I_CmsButton;
074import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
075import org.opencms.gwt.client.ui.I_CmsButton.Size;
076import org.opencms.gwt.client.ui.I_CmsConfirmDialogHandler;
077import org.opencms.gwt.client.ui.I_CmsModelSelectHandler;
078import org.opencms.gwt.client.ui.input.CmsSelectBox;
079import org.opencms.gwt.client.util.CmsDebugLog;
080import org.opencms.gwt.client.util.CmsDomUtil;
081import org.opencms.gwt.client.util.I_CmsSimpleCallback;
082import org.opencms.gwt.shared.CmsGwtConstants;
083import org.opencms.gwt.shared.CmsGwtLog;
084import org.opencms.gwt.shared.CmsListInfoBean;
085import org.opencms.util.CmsPair;
086import org.opencms.util.CmsStringUtil;
087import org.opencms.util.CmsUUID;
088
089import java.util.ArrayList;
090import java.util.Collection;
091import java.util.Collections;
092import java.util.HashMap;
093import java.util.HashSet;
094import java.util.LinkedHashMap;
095import java.util.List;
096import java.util.Map;
097import java.util.Map.Entry;
098import java.util.Set;
099import java.util.TreeMap;
100
101import com.google.gwt.core.client.GWT;
102import com.google.gwt.core.client.JavaScriptObject;
103import com.google.gwt.core.client.Scheduler;
104import com.google.gwt.core.client.Scheduler.ScheduledCommand;
105import com.google.gwt.dom.client.Element;
106import com.google.gwt.dom.client.NodeList;
107import com.google.gwt.dom.client.Style;
108import com.google.gwt.dom.client.Style.Overflow;
109import com.google.gwt.dom.client.Style.Position;
110import com.google.gwt.dom.client.Style.Unit;
111import com.google.gwt.dom.client.Style.VerticalAlign;
112import com.google.gwt.event.dom.client.ClickEvent;
113import com.google.gwt.event.dom.client.ClickHandler;
114import com.google.gwt.event.dom.client.KeyCodes;
115import com.google.gwt.event.logical.shared.CloseEvent;
116import com.google.gwt.event.logical.shared.CloseHandler;
117import com.google.gwt.event.logical.shared.SelectionEvent;
118import com.google.gwt.event.logical.shared.SelectionHandler;
119import com.google.gwt.event.logical.shared.ValueChangeEvent;
120import com.google.gwt.event.logical.shared.ValueChangeHandler;
121import com.google.gwt.event.shared.HandlerRegistration;
122import com.google.gwt.user.client.Command;
123import com.google.gwt.user.client.Event;
124import com.google.gwt.user.client.Event.NativePreviewEvent;
125import com.google.gwt.user.client.Event.NativePreviewHandler;
126import com.google.gwt.user.client.Window;
127import com.google.gwt.user.client.Window.ClosingEvent;
128import com.google.gwt.user.client.Window.ClosingHandler;
129import com.google.gwt.user.client.rpc.AsyncCallback;
130import com.google.gwt.user.client.rpc.SerializationException;
131import com.google.gwt.user.client.rpc.ServiceDefTarget;
132import com.google.gwt.user.client.ui.FlowPanel;
133import com.google.gwt.user.client.ui.Label;
134import com.google.gwt.user.client.ui.PopupPanel;
135import com.google.gwt.user.client.ui.RootPanel;
136import com.google.gwt.user.client.ui.SimplePanel;
137import com.google.gwt.user.client.ui.TextBox;
138
139import elemental2.dom.DomGlobal;
140import jsinterop.base.Js;
141import jsinterop.base.JsPropertyMap;
142
143/**
144 * The content editor.<p>
145 */
146public final class CmsContentEditor extends CmsEditorBase {
147
148    /**
149     * CmsEntity change handler to watch for changes within given scopes and call the editor change handlers accordingly.<p>
150     */
151    class EditorChangeHandler implements ValueChangeHandler<CmsEntity> {
152
153        /** The scope values. */
154        Map<String, String> m_scopeValues;
155
156        private Set<String> m_changedScopes = new HashSet<>();
157
158        /** The change handler registration. */
159        private HandlerRegistration m_handlerRegistration;
160
161        /** The observed entity. */
162        private CmsEntity m_observerdEntity;
163
164        /**
165         * Constructor.<p>
166         *
167         * @param entity the entity to observe
168         * @param changeScopes the value scopes to watch for changes
169         */
170        public EditorChangeHandler(CmsEntity entity, Collection<String> changeScopes) {
171
172            m_observerdEntity = entity;
173            m_handlerRegistration = entity.addValueChangeHandler(this);
174            m_scopeValues = new HashMap<String, String>();
175            for (String scope : changeScopes) {
176                m_scopeValues.put(scope, CmsContentDefinition.getValueForPath(m_observerdEntity, scope));
177            }
178        }
179
180        /**
181         * Removes this observer from the entities change handler registration and clears registered listeners.<p>
182         */
183        public void clear() {
184
185            if (m_handlerRegistration != null) {
186                m_handlerRegistration.removeHandler();
187                m_handlerRegistration = null;
188            }
189            m_scopeValues.clear();
190            m_observerdEntity = null;
191        }
192
193        /**
194         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
195         */
196        public void onValueChange(ValueChangeEvent<CmsEntity> event) {
197
198            final CmsEntity entity = event.getValue();
199            if (event instanceof CmsEntityChangeEvent) {
200
201                CmsEntityChangeEvent.ChangeType type = ((CmsEntityChangeEvent)event).getChangeType();
202                for (String scope : m_scopeValues.keySet()) {
203                    String scopeValue = CmsContentDefinition.getValueForPath(entity, scope);
204                    String previousValue = m_scopeValues.get(scope);
205                    if (((scopeValue != null) && !scopeValue.equals(previousValue))
206                        || ((scopeValue == null) && (previousValue != null))) {
207                        if (type == ChangeType.change) {
208                            m_changedScopes.add(scope);
209                        }
210                        m_scopeValues.put(scope, scopeValue);
211                    }
212                }
213
214            }
215            Scheduler.get().scheduleFinally(new ScheduledCommand() {
216
217                public void execute() {
218
219                    if (m_changedScopes.size() > 0) {
220                        Set<String> changedScopesCopy = new HashSet<>(m_changedScopes);
221                        m_changedScopes.clear();
222                        if (!changedScopesCopy.isEmpty()) {
223                            callEditorChangeHandlers(changedScopesCopy);
224                        }
225                    }
226                }
227            });
228        }
229    }
230
231    /** CSS marker class added to the html element when  the editor is active. */
232    public static final String EDITOR_MARKER_CLASS = "opencms-editor-active";
233
234    /** The add change listener method name. */
235    private static final String ADD_CHANGE_LISTENER_METHOD = "cmsAddEntityChangeListener";
236
237    /** The entity id selector prefix. */
238    private static final String ENTITY_ID_SELECTOR_PREFIX = "[" + CmsGwtConstants.ATTR_DATA_ID + "*=\"";
239
240    /** The entity id selector suffix. */
241    private static final String ENTITY_ID_SELECTOR_SUFFIX = "\"]";
242
243    /** The editable field selector. */
244    private static final String FIELD_SELECTOR = "[" + CmsGwtConstants.ATTR_DATA_FIELD + "*=\"opencms://\"]";
245
246    /** The get current entity method name. */
247    private static final String GET_CURRENT_ENTITY_METHOD = "cmsGetCurrentEntity";
248
249    /** The in-line editor instance. */
250    private static CmsContentEditor INSTANCE;
251
252    /** Flag indicating that an AJAX call for the editor change handler is running. */
253    protected boolean m_callingChangeHandlers;
254
255    /** The current content locale. */
256    protected String m_locale;
257
258    /** The on close call back. */
259    protected I_CmsEditorCloseHandler m_onClose;
260
261    /** The edit tool-bar. */
262    protected CmsToolbar m_toolbar;
263
264    /** The container element client id if available. */
265    String m_clientId;
266
267    /** The editor context. */
268    CmsEditorContext m_context;
269
270    /** Indicates if any settings attributes have been changed. */
271    boolean m_hasChangedSettings;
272
273    /** Value of the auto-unlock option from the configuration. */
274    private boolean m_autoUnlock;
275
276    /** The available locales. */
277    private Map<String, String> m_availableLocales;
278
279    /** The form editing base panel. */
280    private FlowPanel m_basePanel;
281
282    /** The cancel button. */
283    private CmsPushButton m_cancelButton;
284
285    /** The id's of the changed entities. */
286    private Set<String> m_changedEntityIds;
287
288    /** Changed scopes which still haven't been sent to the editor change handlers. */
289    private Set<String> m_changedScopes = new HashSet<String>();
290
291    /** The window closing handler registration. */
292    private HandlerRegistration m_closingHandlerRegistration;
293
294    /** The content info panel. */
295    private CmsInfoHeader m_contentInfoHeader;
296
297    /** The locales present within the edited content. */
298    private Set<String> m_contentLocales;
299
300    /** The copy locale button. */
301    private CmsPushButton m_copyLocaleButton;
302
303    /** The loaded content definitions by locale. */
304    private Map<String, CmsContentDefinition> m_definitions;
305
306    /** The entities to delete. */
307    private Set<String> m_deletedEntities;
308
309    /** The delete locale button. */
310    private CmsPushButton m_deleteLocaleButton;
311
312    /** Flag indicating the resource needs to removed on cancel. */
313    private boolean m_deleteOnCancel;
314
315    /** The entity value change handler calling configured editor change handlers. */
316    private EditorChangeHandler m_editorChangeHandler;
317
318    /** The entity observer instance. */
319    private CmsEntityObserver m_entityObserver;
320
321    /** Flag, indicating if save is enabled */
322    private boolean m_hasChanges;
323
324    /** Flag, indicating if there are errors */
325    private boolean m_hasErrors;
326
327    /** Flag, indicating if there are warnings */
328    private boolean m_hasWarnings;
329
330    /** The hide help bubbles button. */
331    private CmsToggleButton m_hideHelpBubblesButton;
332
333    /** The resource icon classes. */
334    private String m_iconClasses;
335
336    /** Flag which indicate whether the directedit parameter was set to true when loading the editor. */
337    private boolean m_isDirectEdit;
338
339    /** Flag, indicating if save is enabled */
340    private boolean m_isSaveDisabled;
341
342    /** Flag indicating the editor was opened as the stand alone version, not from within any other module. */
343    private boolean m_isStandAlone;
344
345    /** The locale select box. */
346    private CmsSelectBox m_localeSelect;
347
348    /** The open form button. */
349    private CmsPushButton m_openFormButton;
350
351    /** The page info panel. */
352    private CmsInfoHeader m_pageInfoHeader;
353
354    /** The event preview handler registration. */
355    private HandlerRegistration m_previewHandlerRegistration;
356
357    /** The publish button. */
358    private CmsPushButton m_publishButton;
359
360    /** The redo button. */
361    private CmsPushButton m_redoButton;
362
363    /** The registered entity id's. */
364    private Set<String> m_registeredEntities;
365
366    /** The resource type name. */
367    private String m_resourceTypeName;
368
369    /** The save button. */
370    private CmsPushButton m_saveButton;
371
372    /** The save and exit button. */
373    private CmsPushButton m_saveExitButton;
374
375    /** The content service. */
376    private I_CmsContentServiceAsync m_service;
377
378    /** The resource site path. */
379    private String m_sitePath;
380
381    /** The tab informations for this form. */
382    private List<CmsTabInfo> m_tabInfos;
383
384    /** The resource title. */
385    private String m_title;
386
387    /** The undo button. */
388    private CmsPushButton m_undoButton;
389
390    /** The undo redo event handler registration. */
391    private HandlerRegistration m_undoRedoHandlerRegistration;
392
393    /**
394     * Constructor.<p>
395     */
396    private CmsContentEditor() {
397
398        super((I_CmsContentServiceAsync)GWT.create(I_CmsContentService.class), new CmsDefaultWidgetService());
399        m_service = (I_CmsContentServiceAsync)super.getService();
400        String serviceUrl = CmsCoreProvider.get().link("org.opencms.ade.contenteditor.CmsContentService.gwt");
401        ((ServiceDefTarget)m_service).setServiceEntryPoint(serviceUrl);
402        getWidgetService().setWidgetFactories(WidgetRegistry.getInstance().getWidgetFactories());
403        for (I_CmsEntityRenderer renderer : WidgetRegistry.getInstance().getRenderers()) {
404            getWidgetService().addRenderer(renderer);
405        }
406        // set the acacia editor message bundle
407        setDictionary(Messages.get().getDictionary());
408        I_CmsLayoutBundle.INSTANCE.editorCss().ensureInjected();
409        m_changedEntityIds = new HashSet<String>();
410        m_registeredEntities = new HashSet<String>();
411        m_availableLocales = new LinkedHashMap<String, String>();
412        m_contentLocales = new HashSet<String>();
413        m_deletedEntities = new HashSet<String>();
414        m_definitions = new HashMap<String, CmsContentDefinition>();
415        addValidationChangeHandler(new ValueChangeHandler<CmsValidationContext>() {
416
417            public void onValueChange(ValueChangeEvent<CmsValidationContext> event) {
418
419                handleValidationChange(event.getValue());
420            }
421        });
422    }
423
424    /**
425     * Adds an entity change listener.<p>
426     *
427     * @param changeListener the change listener
428     * @param changeScope the change scope
429     */
430    public static void addEntityChangeListener(I_CmsEntityChangeListener changeListener, String changeScope) {
431
432        CmsDebugLog.getInstance().printLine("trying to ad change listener for scope: " + changeScope);
433        if ((INSTANCE == null) || (INSTANCE.m_entityObserver == null)) {
434            CmsDebugLog.getInstance().printLine("handling external registration");
435            if (isObserverExported()) {
436                CmsDebugLog.getInstance().printLine("registration is available");
437                try {
438                    addNativeListener(changeListener, changeScope);
439                } catch (Exception e) {
440
441                    CmsDebugLog.getInstance().printLine(
442                        "Exception occured during listener registration" + e.getMessage());
443                }
444            } else {
445                throw new RuntimeException("Editor is not initialized yet.");
446            }
447        } else {
448            INSTANCE.m_entityObserver.addEntityChangeListener(changeListener, changeScope);
449        }
450    }
451
452    /**
453     * Gets the client id for the editable element.<p>
454     *
455     * @param editableData the editable element
456     *
457     * @return the client id for the editable element
458     */
459    public static String getClientIdForEditable(final I_CmsEditableData editableData) {
460
461        return ((editableData.getElementName() != null)
462            && editableData.getElementName().startsWith(editableData.getStructureId().toString()))
463            ? editableData.getElementName()
464            : editableData.getStructureId().toString();
465    }
466
467    /**
468     * Returns the currently edited entity.<p>
469     *
470     * @return the currently edited entity
471     */
472    public static CmsEntity getEntity() {
473
474        if ((INSTANCE == null) || (INSTANCE.m_entityObserver == null)) {
475            CmsDebugLog.getInstance().printLine("handling external registration");
476            if (isObserverExported()) {
477                return CmsEntityBackend.createFromNativeWrapper(nativeGetEntity());
478            } else {
479                throw new RuntimeException("Editor is not initialized yet.");
480            }
481        } else {
482            return INSTANCE.getCurrentEntity();
483        }
484    }
485
486    /**
487     * Returns the in-line editor instance.<p>
488     *
489     * @return the in-line editor instance
490     */
491    public static CmsContentEditor getInstance() {
492
493        if (INSTANCE == null) {
494            INSTANCE = new CmsContentEditor();
495        }
496        return INSTANCE;
497    }
498
499    /**
500     * Returns if the given element or it's descendants are inline editable.<p>
501     *
502     * @param element the element
503     *
504     * @return <code>true</code> if the element has editable descendants
505     */
506    public static boolean hasEditable(Element element) {
507
508        NodeList<Element> children = CmsDomUtil.querySelectorAll(FIELD_SELECTOR, element);
509        return (children != null) && (children.getLength() > 0);
510    }
511
512    /**
513     * Checks whether the given element is annotated for inline editing.<p>
514     *
515     * @param element the element to check
516     *
517     * @return <code>true</code> if the given element is annotated for inline editing
518     */
519    public static boolean isEditable(Element element) {
520
521        String field = element.getAttribute(CmsGwtConstants.ATTR_DATA_FIELD);
522        return (field != null) && field.contains("opencms://");
523    }
524
525    /**
526     * Replaces the id's within data-oc-id attributes of the given element and all it's children.<p>
527     *
528     * @param element the element
529     * @param oldId the old id
530     * @param newId the new id
531     */
532    public static void replaceResourceIds(Element element, String oldId, String newId) {
533
534        NodeList<Element> children = CmsDomUtil.querySelectorAll(
535            FIELD_SELECTOR + ENTITY_ID_SELECTOR_PREFIX + oldId + ENTITY_ID_SELECTOR_SUFFIX,
536            element);
537        if (children.getLength() > 0) {
538            for (int i = 0; i < children.getLength(); i++) {
539                Element child = children.getItem(i);
540                String idData = child.getAttribute(CmsGwtConstants.ATTR_DATA_ID);
541                idData = idData.replace(oldId, newId);
542                child.setAttribute(CmsGwtConstants.ATTR_DATA_ID, idData);
543            }
544        }
545    }
546
547    /**
548     * Sets all annotated child elements editable.<p>
549     *
550     * @param element the element
551     * @param serverId the editable resource structure id
552     * @param editable <code>true</code> to enable editing
553     *
554     * @return <code>true</code> if the element had editable elements
555     */
556    public static boolean setEditable(Element element, String serverId, boolean editable) {
557
558        I_CmsLayoutBundle.INSTANCE.editorCss().ensureInjected();
559        NodeList<Element> children = CmsDomUtil.querySelectorAll(
560            FIELD_SELECTOR + ENTITY_ID_SELECTOR_PREFIX + serverId + ENTITY_ID_SELECTOR_SUFFIX,
561            element);
562        if (children.getLength() > 0) {
563            for (int i = 0; i < children.getLength(); i++) {
564                Element child = children.getItem(i);
565                if (editable) {
566                    child.addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().inlineEditable());
567                } else {
568                    child.removeClassName(I_CmsLayoutBundle.INSTANCE.editorCss().inlineEditable());
569                }
570            }
571            return true;
572        }
573        return false;
574    }
575
576    /**
577     * Adds the change listener.<p>
578     *
579     * @param changeListener the change listener
580     * @param changeScope the change scope
581     */
582    static native void addNativeListener(I_CmsEntityChangeListener changeListener, String changeScope)/*-{
583        var instance = changeListener;
584        var nat = {
585            onChange : function(entity) {
586                var cmsEntity = @org.opencms.acacia.client.entity.CmsEntityBackend::createFromNativeWrapper(Lcom/google/gwt/core/client/JavaScriptObject;)(entity);
587                instance.@org.opencms.ade.contenteditor.client.I_CmsEntityChangeListener::onEntityChange(Lorg/opencms/acacia/shared/CmsEntity;)(cmsEntity);
588            }
589        }
590        var method = $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD];
591        if (typeof method == 'function') {
592            method(nat, changeScope);
593        }
594    }-*/;
595
596    /**
597     * Checks whether the add entity change listener method has been exported.<p>
598     *
599     * @return <code>true</code> if the add entity change listener method has been exported
600     */
601    private static native boolean isObserverExported()/*-{
602        var method = $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD];
603        if (typeof method == 'function') {
604            return true;
605        } else {
606            return false;
607        }
608    }-*/;
609
610    /**
611     * Returns the current entity.<p>
612     *
613     * @return the current entity
614     */
615    private static native JavaScriptObject nativeGetEntity()/*-{
616        return $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD]
617                ();
618    }-*/;
619
620    /**
621     * Closes the editor.<p>
622     * May be used from outside the editor module.<p>
623     */
624    public void closeEditor() {
625
626        if (m_saveButton != null) {
627            if (m_saveButton.isEnabled()) {
628                CmsConfirmDialog dialog = new CmsConfirmDialog(
629                    org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0),
630                    Messages.get().key(Messages.GUI_CONFIRM_LEAVING_EDITOR_0));
631                dialog.setHandler(new I_CmsConfirmDialogHandler() {
632
633                    public void onClose() {
634
635                        cancelEdit();
636                    }
637
638                    public void onOk() {
639
640                        saveAndExit();
641                    }
642                });
643                dialog.center();
644            } else {
645                cancelEdit();
646            }
647        }
648    }
649
650    /**
651     * Bypasses a focus bug in IE which can happen if the user opens the HTML code editor from the WYSIWYG editor.<p>
652     *
653     * The next time they open the editor form from the same container page, the user may be unable to focus on any input
654     * fields. To prevent this, we create a dummy input field outside the visible screen region and focus it when opening
655     * the editor.
656     */
657    public void fixFocus() {
658
659        TextBox invisibleTextBox = new TextBox();
660        Style style = invisibleTextBox.getElement().getStyle();
661        style.setPosition(Position.FIXED);
662        style.setLeft(-99999, Unit.PX);
663        style.setTop(-99999, Unit.PX);
664        m_basePanel.add(invisibleTextBox);
665        // base panel is already attached at this point, so we can just set the focus
666        invisibleTextBox.setFocus(true);
667    }
668
669    /**
670     * @see org.opencms.acacia.client.CmsEditorBase#getService()
671     */
672    @Override
673    public I_CmsContentServiceAsync getService() {
674
675        return m_service;
676    }
677
678    /**
679     * Loads the content definition for the given entity and executes the callback on success.<p>
680     *
681     * @param entityId the entity id
682     * @param editedEntity the currently edited entity
683     * @param callback the callback
684     */
685    public void loadDefinition(
686        final String entityId,
687        final CmsEntity editedEntity,
688        final I_CmsSimpleCallback<CmsContentDefinition> callback) {
689
690        CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {
691
692            @Override
693            public void execute() {
694
695                start(0, true);
696                getService().loadDefinition(
697                    entityId,
698                    m_clientId,
699                    editedEntity,
700                    getSkipPaths(),
701                    m_context.getSettingPresets(),
702                    this);
703            }
704
705            @Override
706            protected void onResponse(final CmsContentDefinition result) {
707
708                registerContentDefinition(result);
709                WidgetRegistry.getInstance().registerExternalWidgets(
710                    result.getExternalWidgetConfigurations(),
711                    new Command() {
712
713                        public void execute() {
714
715                            stop(false);
716                            callback.execute(result);
717                        }
718                    });
719            }
720        };
721        action.execute();
722    }
723
724    /**
725     * Loads the content definition for the given entity and executes the callback on success.<p>
726     *
727     * @param entityId the entity id
728     * @param newLink the new link
729     * @param modelFileId  the model file id
730     * @param postCreateHandler the post-create handler class name (optional)
731     * @param mode the content creation mode
732     * @param mainLocale the main language to copy in case the element language node does not exist yet
733     * @param editHandlerData the data for the edit handler, if one is used to create a new content; null otherwise
734     * @param settingPresets the presets for container element settings
735     * @param editorStylesheet the path for the editor style sheet (may be null)
736     * @param callback the callback
737     */
738    public void loadInitialDefinition(
739        final String entityId,
740        final String newLink,
741        final CmsUUID modelFileId,
742        final String postCreateHandler,
743        final String mode,
744        final String mainLocale,
745        final CmsEditHandlerData editHandlerData,
746        Map<String, String> settingPresets,
747        String editorStylesheet,
748        final I_CmsSimpleCallback<CmsContentDefinition> callback) {
749
750        CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {
751
752            @Override
753            public void execute() {
754
755                start(0, true);
756                getService().loadInitialDefinition(
757                    entityId,
758                    m_clientId,
759                    newLink,
760                    modelFileId,
761                    CmsCoreProvider.get().getUri(),
762                    mainLocale,
763                    postCreateHandler,
764                    mode,
765                    editHandlerData,
766                    settingPresets,
767                    editorStylesheet,
768                    this);
769            }
770
771            @Override
772            protected void onResponse(final CmsContentDefinition result) {
773
774                if (result.isModelInfo()) {
775                    stop(false);
776                    callback.execute(result);
777                } else {
778                    registerContentDefinition(result);
779                    WidgetRegistry.getInstance().registerExternalWidgets(
780                        result.getExternalWidgetConfigurations(),
781                        new Command() {
782
783                            public void execute() {
784
785                                stop(false);
786                                callback.execute(result);
787                            }
788                        });
789                }
790            }
791        };
792        action.execute();
793    }
794
795    /**
796     * Loads the content definition for the given entity and executes the callback on success.<p>
797     *
798     * @param entityId the entity id
799     * @param editedEntity the currently edited entity
800     * @param callback the callback
801     */
802    public void loadNewDefinition(
803        final String entityId,
804        final CmsEntity editedEntity,
805        final I_CmsSimpleCallback<CmsContentDefinition> callback) {
806
807        final Map<String, String> presets = new HashMap<>();
808        if ((m_context != null) && (m_context.getSettingPresets() != null)) {
809            presets.putAll(m_context.getSettingPresets());
810        }
811        CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {
812
813            @Override
814            public void execute() {
815
816                start(0, true);
817                getService().loadNewDefinition(entityId, m_clientId, editedEntity, getSkipPaths(), presets, this);
818            }
819
820            @Override
821            protected void onResponse(final CmsContentDefinition result) {
822
823                registerContentDefinition(result);
824                WidgetRegistry.getInstance().registerExternalWidgets(
825                    result.getExternalWidgetConfigurations(),
826                    new Command() {
827
828                        public void execute() {
829
830                            stop(false);
831                            callback.execute(result);
832                        }
833                    });
834            }
835        };
836        action.execute();
837    }
838
839    /**
840     * Opens the content editor dialog.<p>
841     *
842     * @param context the editor context
843     * @param locale the content locale
844     * @param elementId the element id
845     * @param clientId the container element client id if available
846     * @param newLink the new link
847     * @param modelFileId the model file id
848     * @param postCreateHandler the post-create handler class (optional)
849     * @param mode the content creation mode
850     * @param mainLocale the main language to copy in case the element language node does not exist yet
851     * @param editHandlerData the edit handler data, if we are using an edit handler to create a new element; null otherwise
852     * @param onClose the command executed on dialog close
853     */
854    public void openFormEditor(
855        final CmsEditorContext context,
856        final String locale,
857        String elementId,
858        String clientId,
859        final String newLink,
860        final CmsUUID modelFileId,
861        final String postCreateHandler,
862        final String mode,
863        final String mainLocale,
864        final CmsEditHandlerData editHandlerData,
865        final I_CmsEditorCloseHandler onClose) {
866
867        m_onClose = onClose;
868        m_clientId = clientId;
869        initEventPreviewHandler();
870        final CmsUUID structureId = new CmsUUID(elementId);
871        m_context = context;
872
873        I_CmsSimpleCallback<Boolean> callback = new I_CmsSimpleCallback<Boolean>() {
874
875            public void execute(Boolean arg) {
876
877                if (arg.booleanValue()) {
878                    loadInitialDefinition(
879                        CmsContentDefinition.uuidToEntityId(structureId, locale),
880                        newLink,
881                        modelFileId,
882                        mode,
883                        postCreateHandler,
884                        mainLocale,
885                        editHandlerData,
886                        context.getSettingPresets(),
887                        context.getEditorStylesheet(),
888                        new I_CmsSimpleCallback<CmsContentDefinition>() {
889
890                            public void execute(CmsContentDefinition contentDefinition) {
891
892                                if (contentDefinition.isModelInfo()) {
893                                    openModelSelectDialog(context, contentDefinition);
894                                } else {
895                                    initEditor(context, contentDefinition, null, false, null);
896                                }
897                            }
898                        });
899                } else {
900                    showLockedResourceMessage();
901                }
902
903            }
904        };
905        // make sure the resource is locked, if we are not creating a new one
906        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink)) {
907            callback.execute(Boolean.TRUE);
908        } else {
909            CmsCoreProvider.get().lock(structureId, callback);
910        }
911    }
912
913    /**
914     * Renders the in-line editor for the given element.<p>
915     *
916     * @param context the editor context
917     * @param elementId the element id
918     * @param locale the content locale
919     * @param panel the element panel
920     * @param mainLocale the main language to copy in case the element language node does not exist yet
921     * @param loadTime the time when the requested resource was loaded
922     * @param onClose the command to execute on close
923     */
924    public void openInlineEditor(
925        final CmsEditorContext context,
926        CmsUUID elementId,
927        String locale,
928        final I_CmsInlineFormParent panel,
929        final String mainLocale,
930        long loadTime,
931        I_CmsEditorCloseHandler onClose) {
932
933        initEventPreviewHandler();
934        m_context = context;
935        final String entityId = CmsContentDefinition.uuidToEntityId(elementId, locale);
936        internalSetLocale(locale);
937        m_onClose = onClose;
938        CmsCoreProvider.get().lock(elementId, loadTime, new I_CmsSimpleCallback<Boolean>() {
939
940            public void execute(Boolean arg) {
941
942                if (arg.booleanValue()) {
943                    loadInitialDefinition(
944                        entityId,
945                        null,
946                        null,
947                        null,
948                        null,
949                        mainLocale,
950                        null,
951                        Collections.emptyMap(),
952                        context.getEditorStylesheet(),
953                        new I_CmsSimpleCallback<CmsContentDefinition>() {
954
955                            public void execute(CmsContentDefinition contentDefinition) {
956
957                                initEditor(context, contentDefinition, panel, true, mainLocale);
958                            }
959                        });
960                } else {
961                    showLockedResourceMessage();
962                }
963            }
964        });
965    }
966
967    /**
968     * Opens the form based editor. Used within the stand alone acacia/editor.jsp.<p>
969     *
970     * @param context the editor context
971     */
972    public void openStandAloneFormEditor(final CmsEditorContext context) {
973
974        initEventPreviewHandler();
975        final CmsContentDefinition definition;
976        try {
977            definition = (CmsContentDefinition)CmsRpcPrefetcher.getSerializedObjectFromDictionary(
978                getService(),
979                I_CmsContentService.DICT_CONTENT_DEFINITION);
980        } catch (SerializationException e) {
981            RootPanel.get().add(new Label(e.getMessage()));
982            return;
983        }
984        context.setReusedElement(definition.isReusedElement());
985        m_isStandAlone = true;
986        if (definition.isModelInfo()) {
987            openModelSelectDialog(context, definition);
988        } else {
989            CmsCoreProvider.get().lock(
990                CmsContentDefinition.entityIdToUuid(definition.getEntityId()),
991                new I_CmsSimpleCallback<Boolean>() {
992
993                    public void execute(Boolean arg) {
994
995                        if (arg.booleanValue()) {
996
997                            registerContentDefinition(definition);
998                            // register all external widgets
999                            WidgetRegistry.getInstance().registerExternalWidgets(
1000                                definition.getExternalWidgetConfigurations(),
1001                                new Command() {
1002
1003                                    public void execute() {
1004
1005                                        initEditor(context, definition, null, false, null);
1006                                    }
1007                                });
1008
1009                        } else {
1010                            showLockedResourceMessage();
1011                        }
1012                    }
1013                });
1014        }
1015    }
1016
1017    /**
1018     * Registers a deep copy of the source entity with the given target entity id.<p>
1019     *
1020     * @param sourceEntityId the source entity id
1021     * @param targetEntityId the target entity id
1022     */
1023    public void registerClonedEntity(String sourceEntityId, String targetEntityId) {
1024
1025        CmsEntityBackend.getInstance().getEntity(sourceEntityId).createDeepCopy(targetEntityId);
1026    }
1027
1028    /**
1029     * Registers the given content definition.<p>
1030     *
1031     * @param definition the content definition
1032     */
1033    public void registerContentDefinition(CmsContentDefinition definition) {
1034
1035        getWidgetService().addConfigurations(definition.getConfigurations());
1036        CmsType baseType = definition.getTypes().get(definition.getEntityTypeName());
1037        m_entityBackend.registerTypes(baseType, definition.getTypes());
1038        for (CmsEntity entity : definition.getEntities().values()) {
1039            CmsEntity previousValue = m_entityBackend.getEntity(entity.getId());
1040            if (previousValue != null) {
1041                m_entityBackend.changeEntityContentValues(previousValue, entity);
1042            } else {
1043                m_entityBackend.registerEntity(entity);
1044                m_registeredEntities.add(entity.getId());
1045            }
1046        }
1047        for (Map.Entry<String, CmsComplexWidgetData> entry : definition.getComplexWidgetData().entrySet()) {
1048            String attrName = entry.getKey();
1049            CmsComplexWidgetData widgetData = entry.getValue();
1050            getWidgetService().registerComplexWidgetAttribute(
1051                attrName,
1052                widgetData.getRendererName(),
1053                widgetData.getConfiguration());
1054        }
1055    }
1056
1057    /**
1058     * Saves the given entities.<p>
1059     *
1060     * @param clearOnSuccess <code>true</code> to clear the VIE instance on success
1061     * @param callback the call back command
1062     */
1063    public void saveAndDeleteEntities(final boolean clearOnSuccess, final I_CmsSimpleCallback<Boolean> callback) {
1064
1065        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
1066        saveAndDeleteEntities(entity, new ArrayList<>(m_deletedEntities), clearOnSuccess, true, callback);
1067    }
1068
1069    /**
1070     * Saves the given entities.<p>
1071     *
1072     * @param lastEditedEntity the last edited entity
1073     * @param deletedEntites the deleted entity id's
1074     * @param clearOnSuccess <code>true</code> to clear the VIE instance on success
1075     * @param failOnWarnings <code>true</code> to prevent saving when warnings exist
1076     * @param callback the call back command
1077     */
1078    public void saveAndDeleteEntities(
1079        final CmsEntity lastEditedEntity,
1080        final List<String> deletedEntites,
1081        final boolean clearOnSuccess,
1082        final boolean failOnWarnings,
1083        final I_CmsSimpleCallback<Boolean> callback) {
1084
1085        CmsRpcAction<CmsSaveResult> asyncCallback = new CmsRpcAction<CmsSaveResult>() {
1086
1087            @Override
1088            public void execute() {
1089
1090                start(200, true);
1091                getService().saveAndDeleteEntities(
1092                    lastEditedEntity,
1093                    m_clientId,
1094                    deletedEntites,
1095                    getSkipPaths(),
1096                    m_locale,
1097                    clearOnSuccess,
1098                    failOnWarnings,
1099                    this);
1100            }
1101
1102            @Override
1103            protected void onResponse(CmsSaveResult result) {
1104
1105                stop(false);
1106                if ((result != null) && result.hasErrors()) {
1107                    if (failOnWarnings && !result.getValidationResult().hasErrors()) {
1108                        CmsContentEditor.this.showSaveValidationWarningDialog(
1109                            result.getIssueInformation(),
1110                            new I_CmsSimpleCallback<Boolean>() {
1111
1112                                @Override
1113                                public void execute(Boolean arg) {
1114
1115                                    saveAndDeleteEntities(
1116                                        lastEditedEntity,
1117                                        deletedEntites,
1118                                        clearOnSuccess,
1119                                        false,
1120                                        callback);
1121                                }
1122                            });
1123                    } else {
1124                        showValidationErrorDialog(result.getIssueInformation());
1125                    }
1126                } else {
1127                    callback.execute(Boolean.valueOf((result != null) && result.isHasChangedSettings()));
1128                    if (clearOnSuccess) {
1129                        destroyForm(true);
1130                    }
1131                }
1132            }
1133        };
1134        asyncCallback.execute();
1135    }
1136
1137    /**
1138     * Saves a value in an Xml content.<p>
1139     *
1140     * @param contentId the structure id of the content
1141     * @param contentPath the xpath for which to set the value
1142     * @param locale the locale for which to set the value
1143     * @param value the new value
1144     * @param asyncCallback the callback for the result
1145     */
1146    public void saveValue(
1147        final String contentId,
1148        final String contentPath,
1149        final String locale,
1150        final String value,
1151        final AsyncCallback<String> asyncCallback) {
1152
1153        CmsRpcAction<String> action = new CmsRpcAction<String>() {
1154
1155            @Override
1156            public void execute() {
1157
1158                start(0, false);
1159                getService().saveValue(contentId, contentPath, locale, value, this);
1160
1161            }
1162
1163            @Override
1164            protected void onResponse(String result) {
1165
1166                stop(false);
1167                asyncCallback.onSuccess(result);
1168
1169            }
1170        };
1171        action.execute();
1172    }
1173
1174    /**
1175     * Sets the show editor help flag to the user session.<p>
1176     *
1177     * @param show the show editor help flag
1178     */
1179    public void setShowEditorHelp(final boolean show) {
1180
1181        CmsCoreProvider.get().setShowEditorHelp(show);
1182    }
1183
1184    /**
1185     * Removes the given entity from the entity VIE store.<p>
1186     *
1187     * @param entityId the entity id
1188     */
1189    public void unregistereEntity(String entityId) {
1190
1191        CmsEntityBackend.getInstance().removeEntity(entityId);
1192    }
1193
1194    /**
1195     * @see org.opencms.acacia.client.CmsEditorBase#clearEditor()
1196     */
1197    @Override
1198    protected void clearEditor() {
1199
1200        super.clearEditor();
1201        m_context = null;
1202        if (m_undoRedoHandlerRegistration != null) {
1203            m_undoRedoHandlerRegistration.removeHandler();
1204        }
1205        if (m_toolbar != null) {
1206            m_toolbar.removeFromParent();
1207            m_toolbar = null;
1208        }
1209        m_cancelButton = null;
1210        m_localeSelect = null;
1211        m_deleteLocaleButton = null;
1212        m_copyLocaleButton = null;
1213        m_openFormButton = null;
1214        m_saveButton = null;
1215        m_onClose = null;
1216        internalSetLocale(null);
1217        if (m_basePanel != null) {
1218            m_basePanel.removeFromParent();
1219            m_basePanel = null;
1220        }
1221        if (m_entityObserver != null) {
1222            m_entityObserver.clear();
1223            m_entityObserver = null;
1224        }
1225        m_changedEntityIds.clear();
1226        m_registeredEntities.clear();
1227        m_availableLocales.clear();
1228        m_contentLocales.clear();
1229        m_deletedEntities.clear();
1230        m_definitions.clear();
1231        m_title = null;
1232        m_sitePath = null;
1233        m_resourceTypeName = null;
1234        if (m_closingHandlerRegistration != null) {
1235            m_closingHandlerRegistration.removeHandler();
1236            m_closingHandlerRegistration = null;
1237        }
1238        if (m_isStandAlone) {
1239            closeEditorWidow();
1240        } else {
1241            RootPanel.getBodyElement().getParentElement().getStyle().clearOverflow();
1242        }
1243        if (m_previewHandlerRegistration != null) {
1244            m_previewHandlerRegistration.removeHandler();
1245            m_previewHandlerRegistration = null;
1246        }
1247        CmsDomUtil.getHtmlElement().removeClassName(EDITOR_MARKER_CLASS);
1248    }
1249
1250    /**
1251     * Gets the editor context.<p>
1252     *
1253     * @return the editor context
1254     */
1255    protected CmsEditorContext getContext() {
1256
1257        return m_context;
1258    }
1259
1260    /**
1261     * @see org.opencms.acacia.client.CmsEditorBase#getContextUri()
1262     */
1263    @Override
1264    protected String getContextUri() {
1265
1266        return CmsCoreProvider.get().getUri();
1267    }
1268
1269    /**
1270     * Gets the entity id.<p>
1271     *
1272     * @return the entity id
1273     */
1274    protected String getEntityId() {
1275
1276        return m_entityId;
1277    }
1278
1279    /**
1280     * @see org.opencms.acacia.client.CmsEditorBase#getHtmlContextInfo()
1281     */
1282    @Override
1283    protected String getHtmlContextInfo() {
1284
1285        return m_context.getHtmlContextInfo();
1286    }
1287
1288    /**
1289     * Returns the paths to be skipped when synchronizing locale independent fields.<p>
1290     *
1291     * @return the paths to be skipped when synchronizing locale independent fields
1292     */
1293    protected Collection<String> getSkipPaths() {
1294
1295        return ((CmsDefaultWidgetService)getWidgetService()).getSkipPaths();
1296    }
1297
1298    /**
1299     * Adds a content definition to the internal store.<p>
1300     *
1301     * @param definition the definition to add
1302     */
1303    void addContentDefinition(CmsContentDefinition definition) {
1304
1305        m_definitions.put(definition.getLocale(), definition);
1306        m_contentLocales.add(definition.getLocale());
1307    }
1308
1309    /**
1310     * Calls the editor change handlers.<p>
1311     *
1312     * @param changedScopes the changed content value scopes
1313     */
1314    void callEditorChangeHandlers(final Set<String> changedScopes) {
1315
1316        m_changedScopes.addAll(changedScopes);
1317        if (!m_callingChangeHandlers && (m_changedScopes.size() > 0)) {
1318            m_callingChangeHandlers = true;
1319            final Set<String> scopesToSend = new HashSet<String>(m_changedScopes);
1320            m_changedScopes.clear();
1321            final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
1322            final org.opencms.acacia.shared.CmsEntity currentState = entity.createDeepCopy(m_entityId);
1323            CmsRpcAction<CmsContentDefinition> action = new CmsRpcAction<CmsContentDefinition>() {
1324
1325                @Override
1326                public void execute() {
1327
1328                    start(200, true);
1329                    getService().callEditorChangeHandlers(
1330                        getEntityId(),
1331                        currentState,
1332                        getSkipPaths(),
1333                        scopesToSend,
1334                        this);
1335                }
1336
1337                @Override
1338                public void onFailure(Throwable t) {
1339
1340                    m_callingChangeHandlers = false;
1341                    super.onFailure(t);
1342
1343                }
1344
1345                @Override
1346                protected void onResponse(CmsContentDefinition result) {
1347
1348                    m_callingChangeHandlers = false;
1349                    stop(false);
1350                    updateEditorValues(currentState, result.getEntity());
1351                    callEditorChangeHandlers(new HashSet<String>());
1352                }
1353            };
1354            action.execute();
1355        }
1356    }
1357
1358    /**
1359     * Cancels the editing process.<p>
1360     */
1361    void cancelEdit() {
1362
1363        // store the scroll position
1364        int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop();
1365        CmsContentEditorHandler.scrollPosition = Integer.valueOf(scrollTop);
1366        setEditorState(false);
1367        unlockResource();
1368        if (m_onClose != null) {
1369            m_onClose.onClose(m_hasChangedSettings, /*publishDialog=*/false);
1370        }
1371        destroyForm(true);
1372        clearEditor();
1373        // restore the scroll position
1374        RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop);
1375    }
1376
1377    /**
1378     * Checks if the content is valid and sends a notification if not.<p>
1379     * This will not trigger a validation run, but will use the latest state.<p>
1380     *
1381     * @return <code>true</code> in case there are no validation issues
1382     */
1383    boolean checkValidation() {
1384
1385        boolean result;
1386        if (m_changedEntityIds.isEmpty()) {
1387            result = true;
1388        } else if (m_saveButton.isEnabled()) {
1389            result = true;
1390        } else {
1391            result = false;
1392            CmsNotification.get().send(Type.ERROR, m_saveButton.getDisabledReason());
1393        }
1394        return result;
1395    }
1396
1397    /**
1398     * Asks the user to confirm resetting all changes.<p>
1399     */
1400    void confirmCancel() {
1401
1402        if (m_hasChanges) {
1403            CmsConfirmDialog dialog = new CmsConfirmDialog(
1404                org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TITLE_0),
1405                org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_DIALOG_RESET_TEXT_0));
1406            dialog.setHandler(new I_CmsConfirmDialogHandler() {
1407
1408                public void onClose() {
1409
1410                    // nothing to do
1411                }
1412
1413                public void onOk() {
1414
1415                    cancelEdit();
1416                }
1417            });
1418            dialog.center();
1419        } else {
1420            cancelEdit();
1421        }
1422    }
1423
1424    /**
1425     * Opens the confirm delete locale dialog.<p>
1426     */
1427    void confirmDeleteLocale() {
1428
1429        CmsConfirmDialog dialog = new CmsConfirmDialog(
1430            Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TITLE_0),
1431            Messages.get().key(Messages.GUI_CONFIRM_DELETE_LOCALE_TEXT_0));
1432        dialog.setHandler(new I_CmsConfirmDialogHandler() {
1433
1434            public void onClose() {
1435
1436                // nothing to do
1437            }
1438
1439            public void onOk() {
1440
1441                deleteCurrentLocale();
1442            }
1443        });
1444        dialog.center();
1445    }
1446
1447    /**
1448     * Copies the current entity values to the given locales.<p>
1449     *
1450     * @param targetLocales the target locales
1451     */
1452    void copyLocales(final Set<String> targetLocales) {
1453
1454        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
1455        CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
1456
1457            @Override
1458            public void execute() {
1459
1460                start(200, true);
1461                getService().copyLocale(targetLocales, entity, this);
1462            }
1463
1464            @Override
1465            protected void onResponse(Void result) {
1466
1467                stop(false);
1468            }
1469        };
1470        action.execute();
1471        for (String targetLocale : targetLocales) {
1472            String targetId = getIdForLocale(targetLocale);
1473            if (!m_entityId.equals(targetId)) {
1474                if (m_registeredEntities.contains(targetId)) {
1475                    unregistereEntity(targetId);
1476                }
1477                registerClonedEntity(m_entityId, targetId);
1478                m_registeredEntities.add(targetId);
1479                m_changedEntityIds.add(targetId);
1480                m_contentLocales.add(targetLocale);
1481                m_deletedEntities.remove(targetId);
1482                m_hasChanges = true;
1483                enableSave();
1484            }
1485        }
1486        updateValidation();
1487        initLocaleSelect();
1488    }
1489
1490    /**
1491     * Deferrers the save action to the end of the browser event queue.<p>
1492     */
1493    void deferSave() {
1494
1495        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
1496
1497            public void execute() {
1498
1499                save();
1500            }
1501        });
1502    }
1503
1504    /**
1505     * Deferrers the save and exit action to the end of the browser event queue.<p>
1506     */
1507    void deferSaveAndExit() {
1508
1509        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
1510
1511            public void execute() {
1512
1513                saveAndExit();
1514            }
1515        });
1516    }
1517
1518    /**
1519     * Deletes the current locale.<p>
1520     */
1521    void deleteCurrentLocale() {
1522
1523        // there has to remain at least one content locale
1524        if (m_contentLocales.size() > 1) {
1525            String deletedLocale = m_locale;
1526            m_contentLocales.remove(deletedLocale);
1527            m_registeredEntities.remove(m_entityId);
1528            m_changedEntityIds.remove(m_entityId);
1529            m_deletedEntities.add(m_entityId);
1530            getValidationHandler().getValidationContext().removeEntityId(m_entityId);
1531            unregistereEntity(m_entityId);
1532            m_hasChanges = true;
1533            enableSave();
1534            String nextLocale = null;
1535            if (m_registeredEntities.isEmpty()) {
1536                nextLocale = m_contentLocales.iterator().next();
1537            } else {
1538                nextLocale = CmsContentDefinition.getLocaleFromId(m_registeredEntities.iterator().next());
1539            }
1540            switchLocale(nextLocale);
1541        }
1542    }
1543
1544    /**
1545     * Disables the save buttons with the given message.<p>
1546     *
1547     * @param message the disabled message
1548     */
1549    void disableSave(String message) {
1550
1551        // If there are warnings or errors, we keep the buttons enabled
1552        // to display a hint dialog on the warnings/errors on click.
1553        if (!(m_hasWarnings || m_hasErrors)) {
1554            m_saveButton.disable(message);
1555            m_saveExitButton.disable(message);
1556            m_isSaveDisabled = true;
1557        }
1558    }
1559
1560    /**
1561     * Leaves the editor saving the content if necessary.<p>
1562     */
1563    void exitWithSaving() {
1564
1565        if (checkValidation()) {
1566            if (m_saveExitButton.isEnabled()) {
1567                saveAndExit();
1568            } else {
1569                cancelEdit();
1570            }
1571        }
1572    }
1573
1574    /**
1575     * Handles validation changes.<p>
1576     *
1577     * @param validationContext the changed validation context
1578     */
1579    void handleValidationChange(CmsValidationContext validationContext) {
1580
1581        // adjust the css classes for the save buttons depending on the validation info.
1582        updateSaveButtons(validationContext.hasValidationErrors(), validationContext.hasValidationWarnings());
1583    }
1584
1585    /**
1586     * Hides the editor help bubbles.<p>
1587     *
1588     * @param hide <code>true</code> to hide the help bubbles
1589     */
1590    void hideHelpBubbles(boolean hide) {
1591
1592        setShowEditorHelp(!hide);
1593        CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), hide);
1594        if (!hide) {
1595            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0));
1596        } else {
1597            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0));
1598        }
1599    }
1600
1601    /**
1602     * Initializes the editor.<p>
1603     *
1604     * @param context the editor context
1605     * @param contentDefinition the content definition
1606     * @param formParent the inline form parent panel, used for inline editing only
1607     * @param inline <code>true</code> to render the editor for inline editing
1608     * @param mainLocale the main language to copy in case the element language node does not exist yet
1609     */
1610    void initEditor(
1611        CmsEditorContext context,
1612        CmsContentDefinition contentDefinition,
1613        I_CmsInlineFormParent formParent,
1614        boolean inline,
1615        String mainLocale) {
1616
1617        m_context = context;
1618        internalSetLocale(contentDefinition.getLocale());
1619        m_entityId = contentDefinition.getEntityId();
1620        m_deleteOnCancel = contentDefinition.isDeleteOnCancel();
1621        m_autoUnlock = contentDefinition.isAutoUnlock();
1622        m_isDirectEdit = contentDefinition.isDirectEdit();
1623        CmsDomUtil.getHtmlElement().addClassName(EDITOR_MARKER_CLASS);
1624
1625        initClosingHandler();
1626        setContentDefinition(contentDefinition);
1627        initToolbar(inline);
1628        updateValidation();
1629        if (inline && (formParent != null)) {
1630            if ((mainLocale != null)
1631                && (CmsDomUtil.querySelector(
1632                    "[" + CmsGwtConstants.ATTR_DATA_ID + "^='" + m_entityId + "']",
1633                    formParent.getElement()) == null)) {
1634                // in case a main locale is given and there are not any HTML elements attributed to the current entity id,
1635                // check if the content was rendered for the main locale
1636                CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId);
1637                String mainLocaleEntityId = CmsContentDefinition.uuidToEntityId(structureId, mainLocale);
1638                NodeList<Element> elements = CmsDomUtil.querySelectorAll(
1639                    "[" + CmsGwtConstants.ATTR_DATA_ID + "^='" + mainLocaleEntityId + "']",
1640                    formParent.getElement());
1641                if (elements.getLength() > 0) {
1642                    for (int i = 0; i < elements.getLength(); i++) {
1643                        Element element = elements.getItem(i);
1644                        element.setAttribute(
1645                            CmsGwtConstants.ATTR_DATA_ID,
1646                            element.getAttribute(CmsGwtConstants.ATTR_DATA_ID).replace(mainLocaleEntityId, m_entityId));
1647                    }
1648                }
1649
1650            }
1651            initEditOverlay(formParent.getElement());
1652            addOverlayClickHandler(new ClickHandler() {
1653
1654                public void onClick(ClickEvent event) {
1655
1656                    exitWithSaving();
1657                }
1658            });
1659            m_hideHelpBubblesButton.setVisible(false);
1660            setNativeResourceInfo(m_sitePath, m_locale);
1661            initEntityObserver();
1662            if (m_definitions.get(m_locale).hasEditorChangeHandlers()) {
1663                initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes());
1664            }
1665            renderInlineEntity(m_entityId, formParent);
1666        } else {
1667            initFormPanel();
1668            renderFormContent();
1669            fixFocus();
1670        }
1671        getValidationHandler().setSynchronizedValues(m_definitions.get(m_locale).getSynchronizations());
1672        if (contentDefinition.isPerformedAutocorrection()) {
1673            CmsNotification.get().send(
1674                CmsNotification.Type.NORMAL,
1675                Messages.get().key(Messages.GUI_WARN_INVALID_XML_STRUCTURE_0));
1676            setChanged();
1677        }
1678    }
1679
1680    /**
1681     * Initializes the editor change handler.<p>
1682     *
1683     * @param changeScopes the scopes to watch for changes
1684     */
1685    void initEditorChangeHandlers(Collection<String> changeScopes) {
1686
1687        if (m_editorChangeHandler != null) {
1688            m_editorChangeHandler.clear();
1689        }
1690        m_editorChangeHandler = new EditorChangeHandler(getEntity(), changeScopes);
1691    }
1692
1693    /**
1694     * Initializes the entity observer.<p>
1695     */
1696    void initEntityObserver() {
1697
1698        if (m_entityObserver != null) {
1699            m_entityObserver.clear();
1700        }
1701        m_entityObserver = new CmsEntityObserver(getCurrentEntity());
1702        exportObserver();
1703    }
1704
1705    /**
1706     * Opens the form based editor.<p>
1707     */
1708    void initFormPanel() {
1709
1710        removeEditOverlays();
1711        m_openFormButton.setVisible(false);
1712        m_saveButton.setVisible(true);
1713        m_hideHelpBubblesButton.setVisible(true);
1714        m_undoButton.setVisible(true);
1715        m_redoButton.setVisible(true);
1716        m_basePanel = new FlowPanel();
1717        m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.editorCss().basePanel());
1718        m_basePanel.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().opencms());
1719        // insert base panel before the tool bar to keep the tool bar visible
1720        RootPanel.get().add(m_basePanel);
1721        if (m_isStandAlone) {
1722            RootPanel.getBodyElement().addClassName(I_CmsLayoutBundle.INSTANCE.editorCss().standAloneEditor());
1723        } else {
1724            RootPanel.getBodyElement().getParentElement().getStyle().setOverflow(Overflow.HIDDEN);
1725        }
1726    }
1727
1728    /**
1729     * Opens the copy locale dialog.<p>
1730     */
1731    void openCopyLocaleDialog() {
1732
1733        CmsCopyLocaleDialog dialog = new CmsCopyLocaleDialog(
1734            m_availableLocales,
1735            m_contentLocales,
1736            m_locale,
1737            m_definitions.get(m_locale).hasSynchronizedElements(),
1738            this);
1739        dialog.center();
1740    }
1741
1742    /**
1743     * Opens the model file select dialog.<p>
1744     *
1745     * @param context the editor context
1746     * @param definition the content definition
1747     */
1748    void openModelSelectDialog(final CmsEditorContext context, final CmsContentDefinition definition) {
1749
1750        I_CmsModelSelectHandler handler = new I_CmsModelSelectHandler() {
1751
1752            public void onModelSelect(CmsUUID modelStructureId) {
1753
1754                if (modelStructureId == null) {
1755                    modelStructureId = CmsUUID.getNullUUID();
1756                }
1757                openFormEditor(
1758                    context,
1759                    definition.getLocale(),
1760                    definition.getReferenceResourceId().toString(),
1761                    m_clientId,
1762                    definition.getNewLink(),
1763                    modelStructureId,
1764                    null,
1765                    null,
1766                    null,
1767
1768                    null,
1769                    m_onClose);
1770            }
1771        };
1772        String title = org.opencms.gwt.client.Messages.get().key(
1773            org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_TITLE_0);
1774        String message = org.opencms.gwt.client.Messages.get().key(
1775            org.opencms.gwt.client.Messages.GUI_MODEL_SELECT_MESSAGE_0);
1776        CmsModelSelectDialog dialog = new CmsModelSelectDialog(handler, definition.getModelInfos(), title, message);
1777        dialog.center();
1778    }
1779
1780    /**
1781     * Previews the native event to enable keyboard short cuts.<p>
1782     *
1783     * @param event the event
1784     */
1785    void previewNativeEvent(NativePreviewEvent event) {
1786
1787        Event nativeEvent = Event.as(event.getNativeEvent());
1788        if (event.getTypeInt() == Event.ONKEYDOWN) {
1789            int keyCode = nativeEvent.getKeyCode();
1790            if (nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) {
1791                // look for short cuts
1792                if (nativeEvent.getShiftKey()) {
1793                    if (keyCode == KeyCodes.KEY_S) {
1794
1795                        exitWithSaving();
1796                        nativeEvent.preventDefault();
1797                        nativeEvent.stopPropagation();
1798                    } else if (keyCode == KeyCodes.KEY_X) {
1799                        confirmCancel();
1800                        nativeEvent.preventDefault();
1801                        nativeEvent.stopPropagation();
1802                    }
1803                } else if (keyCode == KeyCodes.KEY_S) {
1804                    if (checkValidation()) {
1805                        save();
1806                    }
1807                    nativeEvent.preventDefault();
1808                    nativeEvent.stopPropagation();
1809                }
1810
1811            }
1812        }
1813    }
1814
1815    /**
1816     * Renders the form content.<p>
1817     */
1818    void renderFormContent() {
1819
1820        m_contentInfoHeader = new CmsInfoHeader(m_title, null, m_sitePath, m_locale, m_iconClasses);
1821        initLocaleSelect();
1822        setNativeResourceInfo(m_sitePath, m_locale);
1823
1824        m_basePanel.add(m_contentInfoHeader);
1825        if (m_context.isReusedElement()) {
1826            String message = Messages.get().key(Messages.GUI_CONTENT_EDITOR_REUSE_MARKER_0);
1827            Label label = new Label(message);
1828            label.addStyleName("oc-editor-reuse-marker");
1829            m_contentInfoHeader.addWidget(label);
1830        }
1831        FlowPanel localeButtons = m_contentInfoHeader.getPathButtons();
1832        localeButtons.addStyleName(I_CmsLayoutBundle.INSTANCE.editorCss().localeButtons());
1833        SimplePanel content = new SimplePanel();
1834        content.setStyleName(org.opencms.acacia.client.css.I_CmsLayoutBundle.INSTANCE.form().formParent());
1835        m_basePanel.add(content);
1836        initEntityObserver();
1837        if (m_definitions.get(m_locale).hasEditorChangeHandlers()) {
1838            initEditorChangeHandlers(m_definitions.get(m_locale).getEditorChangeScopes());
1839        }
1840        renderEntityForm(m_entityId, m_tabInfos, content, m_basePanel.getElement());
1841        if ((m_clientId != null) && (getFormTabs() != null)) {
1842
1843            CmsListInfoBean pageInfo = CmsContainerpageController.get().getData().getPageInfo();
1844            m_pageInfoHeader = new CmsInfoHeader(
1845                pageInfo.getTitle(),
1846                null,
1847                pageInfo.getSubTitle(),
1848                CmsContainerpageController.get().getData().getLocale(),
1849                pageInfo.getBigIconClasses());
1850            updateInfoHeader(CmsContentDefinition.SETTINGS_TAB_ID.equals(getFormTabs().getSelectedId()));
1851            getFormTabs().addSelectionHandler(new SelectionHandler<Integer>() {
1852
1853                public void onSelection(SelectionEvent<Integer> event) {
1854
1855                    updateInfoHeader(CmsContentDefinition.SETTINGS_TAB_ID.equals(getFormTabs().getSelectedId()));
1856                }
1857            });
1858        }
1859    }
1860
1861    /**
1862     * Saves the content and closes the editor.<p>
1863     */
1864    void save() {
1865
1866        saveAndDeleteEntities(false, new I_CmsSimpleCallback<Boolean>() {
1867
1868            public void execute(Boolean hasChangedSettings) {
1869
1870                setSaved();
1871                setUnchanged();
1872                m_hasChangedSettings = m_hasChangedSettings || hasChangedSettings.booleanValue();
1873            }
1874        });
1875    }
1876
1877    /**
1878     * Saves the content and closes the editor.<p>
1879     */
1880    void saveAndExit() {
1881
1882        boolean unlock = shouldUnlockAutomatically();
1883        // store the scroll position
1884        final int scrollTop = RootPanel.getBodyElement().getOwnerDocument().getScrollTop();
1885        CmsContentEditorHandler.scrollPosition = Integer.valueOf(scrollTop);
1886        saveAndDeleteEntities(unlock, new I_CmsSimpleCallback<Boolean>() {
1887
1888            public void execute(final Boolean hasChangedSettings) {
1889
1890                setSaved();
1891                if (m_onClose != null) {
1892                    m_onClose.onClose(
1893                        m_hasChangedSettings || hasChangedSettings.booleanValue(),
1894                        /*publishDialog=*/false);
1895                }
1896                clearEditor();
1897                // restore the scroll position
1898                RootPanel.getBodyElement().getOwnerDocument().setScrollTop(scrollTop);
1899            }
1900        });
1901    }
1902
1903    /**
1904     * Sets the has changed flag and enables the save button.<p>
1905     */
1906    void setChanged() {
1907
1908        m_hasChanges = true;
1909        enableSave();
1910        m_changedEntityIds.add(m_entityId);
1911        m_deletedEntities.remove(m_entityId);
1912        updateOverlayPosition();
1913        setEditorState(true);
1914    }
1915
1916    /**
1917     * Sets the content definition.<p>
1918     *
1919     * @param definition the content definition
1920     */
1921    void setContentDefinition(CmsContentDefinition definition) {
1922
1923        if (m_availableLocales.isEmpty()) {
1924            // only set the locales when initially setting the content definition
1925            m_availableLocales.putAll(definition.getAvailableLocales());
1926            m_contentLocales.addAll(definition.getContentLocales());
1927        } else {
1928            m_contentLocales.add(definition.getLocale());
1929        }
1930        m_title = definition.getTitle();
1931        m_sitePath = definition.getSitePath();
1932        m_resourceTypeName = definition.getResourceType();
1933        m_registeredEntities.add(definition.getEntityId());
1934        m_tabInfos = definition.getTabInfos();
1935        m_iconClasses = definition.getIconClasses();
1936        addContentDefinition(definition);
1937        CmsDefaultWidgetService service = (CmsDefaultWidgetService)getWidgetService();
1938        service.addConfigurations(definition.getConfigurations());
1939        service.setSyncValues(definition.getSyncValues());
1940        service.setSkipPaths(definition.getSkipPaths());
1941        addEntityChangeHandler(definition.getEntityId(), new ValueChangeHandler<CmsEntity>() {
1942
1943            public void onValueChange(ValueChangeEvent<CmsEntity> event) {
1944
1945                setChanged();
1946            }
1947        });
1948    }
1949
1950    /**
1951     * Removes the delete on cancel flag for new resources.<p>
1952     */
1953    void setSaved() {
1954
1955        setEditorState(false);
1956        m_deleteOnCancel = false;
1957    }
1958
1959    /**
1960     * Call after save.<p>
1961     */
1962    void setUnchanged() {
1963
1964        m_changedEntityIds.clear();
1965        m_deletedEntities.clear();
1966        m_hasChanges = false;
1967        disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0));
1968    }
1969
1970    /**
1971     * Enables and disabler the undo redo buttons according to the state.<p>
1972     *
1973     * @param state the undo redo state
1974     */
1975    void setUndoRedoState(UndoRedoState state) {
1976
1977        if (state.hasUndo()) {
1978            m_undoButton.enable();
1979        } else {
1980            m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0));
1981        }
1982        if (state.hasRedo()) {
1983            m_redoButton.enable();
1984        } else {
1985            m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0));
1986        }
1987    }
1988
1989    /**
1990     * Returns true if the edited resource should be unlocked automatically after pressing Save/Exit.<p>
1991     *
1992     * @return true if the edited resource should be unlocked automatically
1993     */
1994    boolean shouldUnlockAutomatically() {
1995
1996        if (m_isStandAlone) {
1997            if (m_isDirectEdit) {
1998                // Classic direct edit case - always unlock
1999                return true;
2000            } else {
2001                // Workplace case - determined by configuration
2002                return m_autoUnlock;
2003            }
2004        }
2005        // Container page case - always unlock
2006        return true;
2007    }
2008
2009    /**
2010     * Shows the locked resource error message.<p>
2011     */
2012    void showLockedResourceMessage() {
2013
2014        CmsErrorDialog dialog = new CmsErrorDialog(
2015            Messages.get().key(Messages.ERR_RESOURCE_ALREADY_LOCKED_BY_OTHER_USER_0),
2016            null);
2017        dialog.addCloseHandler(new CloseHandler<PopupPanel>() {
2018
2019            public void onClose(CloseEvent<PopupPanel> event) {
2020
2021                cancelEdit();
2022            }
2023        });
2024        dialog.center();
2025    }
2026
2027    /**
2028     * Show the validation warning dialog on save.
2029     * @param issueInformation the validation issues
2030     * @param callback the callback to trigger to save
2031     */
2032    void showSaveValidationWarningDialog(
2033        Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> issueInformation,
2034        I_CmsSimpleCallback<?> callback) {
2035
2036        Map<String, List<String>> issues = createIssueMap(issueInformation);
2037
2038        CmsConfirmSaveDialog dialog = new CmsConfirmSaveDialog(issues, true, m_contentLocales.size() == 1, callback);
2039
2040        dialog.center();
2041
2042    }
2043
2044    /**
2045     * Shows the validation error dialog.<p>
2046     *
2047     * @param issueInformation the validation issues
2048     */
2049    void showValidationErrorDialog(
2050        Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> issueInformation) {
2051
2052        Map<String, List<String>> issues = createIssueMap(issueInformation);
2053
2054        CmsConfirmSaveDialog dialog = new CmsConfirmSaveDialog(issues, false, m_contentLocales.size() == 1, null);
2055
2056        dialog.center();
2057
2058    }
2059
2060    /**
2061     * Switches to the selected locale. Will save changes first.<p>
2062     *
2063     * @param locale the locale to switch to
2064     */
2065    void switchLocale(final String locale) {
2066
2067        if (locale.equals(m_locale)) {
2068            return;
2069        }
2070        final Integer oldTabIndex = getTabIndex();
2071        internalSetLocale(locale);
2072
2073        m_basePanel.clear();
2074        destroyForm(false);
2075        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
2076        m_entityId = getIdForLocale(locale);
2077        // if the content does not contain the requested locale yet, a new node will be created
2078        final boolean addedNewLocale = !m_contentLocales.contains(locale);
2079        if (m_registeredEntities.contains(m_entityId)) {
2080            unregistereEntity(m_entityId);
2081        }
2082        if (addedNewLocale) {
2083            loadNewDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() {
2084
2085                public void execute(final CmsContentDefinition contentDefinition) {
2086
2087                    setContentDefinition(contentDefinition);
2088                    renderFormContent();
2089                    if (oldTabIndex != null) {
2090                        if (oldTabIndex.intValue() < getFormTabs().getTabCount()) {
2091                            getFormTabs().selectTab(oldTabIndex.intValue());
2092                        }
2093                    }
2094                    setChanged();
2095
2096                }
2097            });
2098        } else {
2099            loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() {
2100
2101                public void execute(CmsContentDefinition contentDefinition) {
2102
2103                    setContentDefinition(contentDefinition);
2104                    renderFormContent();
2105                    if (oldTabIndex != null) {
2106                        if (oldTabIndex.intValue() < getFormTabs().getTabCount()) {
2107                            getFormTabs().selectTab(oldTabIndex.intValue());
2108                        }
2109                    }
2110                }
2111
2112            });
2113        }
2114    }
2115
2116    /**
2117     * Synchronizes the locale independent fields to the other locales.<p>
2118     */
2119    void synchronizeCurrentLocale() {
2120
2121        m_basePanel.clear();
2122        destroyForm(false);
2123        CmsEntity entity = m_entityBackend.getEntity(m_entityId);
2124        m_entityId = getIdForLocale(m_locale);
2125        ((CmsDefaultWidgetService)getWidgetService()).setSkipPaths(Collections.<String> emptyList());
2126        loadDefinition(m_entityId, entity, new I_CmsSimpleCallback<CmsContentDefinition>() {
2127
2128            public void execute(CmsContentDefinition contentDefinition) {
2129
2130                setContentDefinition(contentDefinition);
2131                renderFormContent();
2132                setChanged();
2133            }
2134        });
2135    }
2136
2137    /**
2138     * Unlocks the edited resource.<p>
2139     */
2140    void unlockResource() {
2141
2142        if (!shouldUnlockAutomatically()) {
2143            return;
2144        }
2145        if (m_entityId != null) {
2146            final CmsUUID structureId = CmsContentDefinition.entityIdToUuid(m_entityId);
2147            if (m_deleteOnCancel) {
2148                CmsRpcAction<Void> action = new CmsRpcAction<Void>() {
2149
2150                    @Override
2151                    public void execute() {
2152
2153                        CmsCoreProvider.getVfsService().syncDeleteResource(structureId, this);
2154                    }
2155
2156                    @Override
2157                    protected void onResponse(Void result) {
2158
2159                        // nothing to do
2160                    }
2161                };
2162                action.executeSync();
2163            } else {
2164                CmsCoreProvider.get().unlock(structureId);
2165            }
2166        }
2167    }
2168
2169    /**
2170     * Updates the editor values.<p>
2171     *
2172     * @param previous the previous entity state
2173     * @param updated the updated entity state
2174     */
2175    void updateEditorValues(CmsEntity previous, CmsEntity updated) {
2176
2177        if (!m_isDirectEdit && updated.getId().equals(m_entityId)) {
2178            // only apply the changes to the same locale entity
2179            updateEditorValues(previous, updated, getEntity(), Collections.<String> emptyList());
2180        }
2181    }
2182
2183    /**
2184     * Updates the info header.<p>
2185     *
2186     * @param isSettingsTabSelected <code>true</code> if the element settings tab is selected
2187     */
2188    void updateInfoHeader(boolean isSettingsTabSelected) {
2189
2190        m_basePanel.remove(0);
2191        m_basePanel.insert(isSettingsTabSelected ? m_pageInfoHeader : m_contentInfoHeader, 0);
2192    }
2193
2194    /**
2195     * Update the validation context completely.
2196     */
2197    void updateValidation() {
2198
2199        final CmsEntity entity = m_entityBackend.getEntity(m_entityId);
2200        final CmsValidationHandler validationHandler = getValidationHandler();
2201        getService().validateEntities(
2202            entity,
2203            m_clientId,
2204            new ArrayList<>(m_deletedEntities),
2205            getSkipPaths(),
2206            m_locale,
2207            new AsyncCallback<CmsValidationResult>() {
2208
2209                @Override
2210                public void onFailure(Throwable caught) {
2211
2212                    // TODO Auto-generated method stub
2213
2214                }
2215
2216                @Override
2217                public void onSuccess(CmsValidationResult result) {
2218
2219                    CmsDebugLog.consoleLog("Update validation success: " + result);
2220                    validationHandler.updateValidationContext(result);
2221                    updateSaveButtons(result.hasErrors(), result.hasWarnings());
2222
2223                }
2224
2225            });
2226    }
2227
2228    /**
2229     * Adds the change listener to the observer.<p>
2230     *
2231     * @param changeListener the change listener
2232     * @param changeScope the change scope
2233     */
2234    private void addChangeListener(JavaScriptObject changeListener, String changeScope) {
2235
2236        try {
2237            System.out.println("Adding native listener for scope " + changeScope);
2238            m_entityObserver.addEntityChangeListener(new CmsEntityChangeListenerWrapper(changeListener), changeScope);
2239        } catch (Exception e) {
2240
2241            CmsDebugLog.getInstance().printLine("Exception occured during listener registration" + e.getMessage());
2242        }
2243    }
2244
2245    /**
2246     * Changes a simple type entity value.<p>
2247     *
2248     * @param attributeName the attribute name
2249     * @param index the value index
2250     * @param value the value
2251     * @param parentPathElements the parent path elements
2252     */
2253    private void changeSimpleValue(String attributeName, int index, String value, List<String> parentPathElements) {
2254
2255        CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements);
2256        handler.changeValue(value, index);
2257    }
2258
2259    /**
2260     * Closes the editor.<p>
2261     */
2262    private native void closeEditorWidow() /*-{
2263        if ($wnd.top.cms_ade_closeEditorDialog) {
2264            $wnd.top.cms_ade_closeEditorDialog();
2265        } else if ($wnd.parent.parent.cms_ade_closeEditorDialog) {
2266            $wnd.parent.parent.cms_ade_closeEditorDialog();
2267        } else {
2268            var backlink = $wnd[@org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService::PARAM_BACKLINK];
2269            if (backlink) {
2270                $wnd.top.location.href = backlink;
2271            }
2272        }
2273    }-*/;
2274
2275    /**
2276     * Creates a push button for the edit tool-bar.<p>
2277     *
2278     * @param title the button title
2279     * @param imageClass the image class
2280     *
2281     * @return the button
2282     */
2283    private CmsPushButton createButton(String title, String imageClass) {
2284
2285        CmsPushButton result = new CmsPushButton();
2286        result.setTitle(title);
2287        result.setImageClass(imageClass);
2288        result.setButtonStyle(ButtonStyle.FONT_ICON, null);
2289        result.setSize(Size.big);
2290        return result;
2291    }
2292
2293    /**
2294     * Creates the map from languages to localized element path information for all errors/warnings.
2295     * @param issueInformation the information on the warnings/errors
2296     * @return the map from languages to localized element path information for all errors/warnings.
2297     */
2298    private Map<String, List<String>> createIssueMap(
2299        Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> issueInformation) {
2300
2301        I_CmsWidgetService service = getWidgetService();
2302        Map<String, List<String>> resultMap = new TreeMap<>();
2303        for (Entry<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> e : issueInformation.entrySet()) {
2304            String localeName = e.getKey();
2305            List<CmsPair<List<CmsPair<String, Integer>>, String>> items = e.getValue();
2306            List<String> localeItems = new ArrayList<>(items.size());
2307            resultMap.put(localeName, localeItems);
2308            for (CmsPair<List<CmsPair<String, Integer>>, String> it : items) {
2309                String path = "";
2310                for (CmsPair<String, Integer> partInfo : it.getFirst()) {
2311                    String attribute = partInfo.getFirst();
2312                    Integer idx = partInfo.getSecond();
2313                    path += service.getAttributeLabel(attribute);
2314                    if (idx.intValue() > 1) {
2315                        path += " [" + (idx) + "]";
2316                    }
2317                    path += " > ";
2318                }
2319                String issueDisplay = "<strong>"
2320                    + path.substring(0, path.length() - 3)
2321                    + "</strong> - "
2322                    + it.getSecond();
2323                localeItems.add(issueDisplay);
2324            }
2325        }
2326        return resultMap;
2327    }
2328
2329    /**
2330     * Enables the save buttons.<p>
2331     */
2332    private void enableSave() {
2333
2334        m_saveButton.enable();
2335        m_saveExitButton.enable();
2336        m_publishButton.enable();
2337    }
2338
2339    /**
2340     * Exports the add entity change listener method.<p>
2341     */
2342    private native void exportObserver()/*-{
2343        var self = this;
2344        $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::ADD_CHANGE_LISTENER_METHOD] = function(
2345                listener, scope) {
2346            var wrapper = {
2347                onChange : listener.onChange
2348            }
2349            self.@org.opencms.ade.contenteditor.client.CmsContentEditor::addChangeListener(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;)(wrapper, scope);
2350        }
2351        $wnd[@org.opencms.ade.contenteditor.client.CmsContentEditor::GET_CURRENT_ENTITY_METHOD] = function() {
2352            return new $wnd.acacia.CmsEntityWrapper(
2353                    self.@org.opencms.ade.contenteditor.client.CmsContentEditor::getCurrentEntity()());
2354        }
2355    }-*/;
2356
2357    /**
2358     * Returns the attribute handler for the given attribute.<p>
2359     *
2360     * @param attributeName the attribute name
2361     * @param parentPathElements the parent path elements
2362     *
2363     * @return the attribute handler
2364     */
2365    private CmsAttributeHandler getAttributeHandler(String attributeName, List<String> parentPathElements) {
2366
2367        List<String> childPathElements = new ArrayList<String>(parentPathElements);
2368        childPathElements.add(attributeName);
2369        CmsAttributeHandler handler = getRootAttributeHandler().getHandlerByPath(
2370            childPathElements.toArray(new String[childPathElements.size()]));
2371        return handler;
2372    }
2373
2374    /**
2375     * Returns the entity id for the given locale.<p>
2376     *
2377     * @param locale the locale
2378     *
2379     * @return the entity id
2380     */
2381    private String getIdForLocale(String locale) {
2382
2383        return CmsContentDefinition.uuidToEntityId(CmsContentDefinition.entityIdToUuid(m_entityId), locale);
2384    }
2385
2386    /**
2387     * Returns the selected tab index, or null if there are no tabs.
2388     *
2389     * @return the selected tab index or null
2390     */
2391    private Integer getTabIndex() {
2392
2393        if (getFormTabs() != null) {
2394            return Integer.valueOf(getFormTabs().getSelectedIndex());
2395        }
2396        return null;
2397    }
2398
2399    /**
2400     * Initializes the window closing handler to ensure the resource will be unlocked when leaving the editor.<p>
2401     */
2402    private void initClosingHandler() {
2403
2404        m_closingHandlerRegistration = Window.addWindowClosingHandler(new ClosingHandler() {
2405
2406            /**
2407             * @see com.google.gwt.user.client.Window.ClosingHandler#onWindowClosing(com.google.gwt.user.client.Window.ClosingEvent)
2408             */
2409            public void onWindowClosing(ClosingEvent event) {
2410
2411                unlockResource();
2412            }
2413        });
2414    }
2415
2416    /**
2417     * Initializes the event preview handler.<p>
2418     */
2419    private void initEventPreviewHandler() {
2420
2421        m_previewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
2422
2423            public void onPreviewNativeEvent(NativePreviewEvent event) {
2424
2425                previewNativeEvent(event);
2426            }
2427        });
2428    }
2429
2430    /**
2431     * Initializes the locale selector.<p>
2432     */
2433    private void initLocaleSelect() {
2434
2435        if (m_availableLocales.size() < 2) {
2436            return;
2437        }
2438        FlowPanel localeButtons = m_contentInfoHeader.getPathButtons();
2439        localeButtons.clear();
2440        Map<String, String> selectOptions = new HashMap<String, String>();
2441        int buttonLimit = CmsCoreProvider.get().getMaxLocaleButtons();
2442        for (Entry<String, String> localeEntry : m_availableLocales.entrySet()) {
2443            if (m_contentLocales.contains(localeEntry.getKey())) {
2444                final String locale = localeEntry.getKey();
2445                selectOptions.put(locale, localeEntry.getValue());
2446                if ((localeButtons.getWidgetCount() < buttonLimit) && !locale.equals(m_locale)) {
2447                    Label button = new Label(locale.toUpperCase());
2448                    localeButtons.add(button);
2449                    button.addClickHandler(event -> {
2450                        switchLocale(locale);
2451                    });
2452                }
2453            } else {
2454                selectOptions.put(localeEntry.getKey(), localeEntry.getValue() + " [-]");
2455            }
2456        }
2457        if (localeButtons.getWidgetCount() > 0) {
2458            Label label = new Label(m_locale.toUpperCase());
2459            label.addStyleName(I_CmsLayoutBundle.INSTANCE.editorCss().currentLocaleLabel());
2460            localeButtons.add(label);
2461        }
2462        if (m_localeSelect == null) {
2463            m_localeSelect = new CmsSelectBox(selectOptions);
2464            m_toolbar.insertRight(m_localeSelect, 1);
2465            m_localeSelect.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().inlineBlock());
2466            m_localeSelect.getElement().getStyle().setWidth(100, Unit.PX);
2467            m_localeSelect.getElement().getStyle().setVerticalAlign(VerticalAlign.MIDDLE);
2468            m_localeSelect.addValueChangeHandler(new ValueChangeHandler<String>() {
2469
2470                public void onValueChange(ValueChangeEvent<String> event) {
2471
2472                    switchLocale(event.getValue());
2473                }
2474            });
2475        } else {
2476            m_localeSelect.setItems(selectOptions);
2477        }
2478        m_localeSelect.setFormValueAsString(m_locale);
2479        if (m_deleteLocaleButton == null) {
2480            m_deleteLocaleButton = createButton(
2481                Messages.get().key(Messages.GUI_TOOLBAR_DELETE_LOCALE_0),
2482                "opencms-icon-remove-locale");
2483            m_deleteLocaleButton.addClickHandler(new ClickHandler() {
2484
2485                public void onClick(ClickEvent event) {
2486
2487                    confirmDeleteLocale();
2488                }
2489            });
2490            m_toolbar.insertRight(m_deleteLocaleButton, 2);
2491        }
2492        if (m_contentLocales.size() > 1) {
2493            m_deleteLocaleButton.enable();
2494        } else {
2495            m_deleteLocaleButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_CANT_DELETE_LAST_LOCALE_0));
2496        }
2497        if (m_copyLocaleButton == null) {
2498            m_copyLocaleButton = createButton(
2499                I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getTitle(),
2500                I_CmsButton.ButtonData.COPY_LOCALE_BUTTON.getIconClass());
2501            m_copyLocaleButton.addClickHandler(new ClickHandler() {
2502
2503                public void onClick(ClickEvent event) {
2504
2505                    openCopyLocaleDialog();
2506                }
2507            });
2508            m_toolbar.insertRight(m_copyLocaleButton, 3);
2509        }
2510
2511    }
2512
2513    /**
2514     * Generates the button bar displayed beneath the editable fields.<p>
2515     *
2516     * @param inline true if we are in inline editing mode
2517     */
2518    private void initToolbar(boolean inline) {
2519
2520        m_hasErrors = false;
2521        m_hasWarnings = false;
2522        m_toolbar = new CmsToolbar();
2523        if (!inline) {
2524            m_toolbar.setComplexTitle(
2525                Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_0),
2526                Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_BOTTOM_0));
2527        } else {
2528            m_toolbar.setComplexTitle(
2529                Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_0),
2530                Messages.get().key(Messages.GUI_CONTENT_EDITOR_TITLE_BOTTOM_PAGE_EDITOR_0));
2531        }
2532        m_publishButton = createButton(
2533            I_CmsButton.ButtonData.PUBLISH_BUTTON.getTitle(),
2534            I_CmsButton.ButtonData.PUBLISH_BUTTON.getIconClass());
2535        m_toolbar.addLeft(m_publishButton);
2536        m_publishButton.addClickHandler(new ClickHandler() {
2537
2538            public void onClick(ClickEvent event) {
2539
2540                boolean unlock = shouldUnlockAutomatically();
2541
2542                saveAndDeleteEntities(unlock, new I_CmsSimpleCallback<Boolean>() {
2543
2544                    public void execute(final Boolean hasChangedSeetings) {
2545
2546                        setSaved();
2547                        HashMap<String, String> params = new HashMap<String, String>(
2548                            getContext().getPublishParameters());
2549                        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(getEntityId());
2550                        params.put(CmsPublishOptions.PARAM_CONTENT, "" + structureId);
2551                        params.put(CmsPublishOptions.PARAM_START_WITH_CURRENT_PAGE, "");
2552                        CmsPublishDialog.showPublishDialog(params, new CloseHandler<PopupPanel>() {
2553
2554                            public void onClose(CloseEvent<PopupPanel> closeEvent) {
2555
2556                                if (m_onClose != null) {
2557                                    m_onClose.onClose(
2558                                        m_hasChangedSettings || hasChangedSeetings.booleanValue(),
2559                                        /*publishDialog=*/true);
2560                                }
2561                                clearEditor();
2562                            }
2563                        }, new Runnable() {
2564
2565                            public void run() {
2566
2567                                // ignore
2568                            }
2569
2570                        }, null);
2571
2572                    }
2573                });
2574
2575            }
2576        });
2577        m_saveExitButton = createButton(
2578            Messages.get().key(Messages.GUI_TOOLBAR_SAVE_AND_EXIT_0),
2579            "opencms-icon-save-exit");
2580        m_saveExitButton.addClickHandler(new ClickHandler() {
2581
2582            public void onClick(ClickEvent event) {
2583
2584                deferSaveAndExit();
2585            }
2586        });
2587        m_toolbar.addLeft(m_saveExitButton);
2588        m_saveButton = createButton(
2589            Messages.get().key(Messages.GUI_TOOLBAR_SAVE_0),
2590            I_CmsButton.ButtonData.SAVE_BUTTON.getIconClass());
2591        m_saveButton.addClickHandler(new ClickHandler() {
2592
2593            public void onClick(ClickEvent event) {
2594
2595                deferSave();
2596            }
2597        });
2598        m_saveButton.setVisible(false);
2599        m_toolbar.addLeft(m_saveButton);
2600        disableSave(Messages.get().key(Messages.GUI_TOOLBAR_NOTHING_CHANGED_0));
2601        m_hasChanges = false;
2602        m_undoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_0), "opencms-icon-undo");
2603        m_undoButton.addClickHandler(new ClickHandler() {
2604
2605            public void onClick(ClickEvent event) {
2606
2607                if (CmsUndoRedoHandler.getInstance().isIntitalized()) {
2608                    CmsUndoRedoHandler.getInstance().undo();
2609                }
2610            }
2611        });
2612        m_undoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_UNDO_DISABLED_0));
2613        m_undoButton.setVisible(false);
2614        m_toolbar.addLeft(m_undoButton);
2615        m_redoButton = createButton(Messages.get().key(Messages.GUI_TOOLBAR_REDO_0), "opencms-icon-redo");
2616        m_redoButton.addClickHandler(new ClickHandler() {
2617
2618            public void onClick(ClickEvent event) {
2619
2620                if (CmsUndoRedoHandler.getInstance().isIntitalized()) {
2621                    CmsUndoRedoHandler.getInstance().redo();
2622                }
2623            }
2624        });
2625        m_redoButton.disable(Messages.get().key(Messages.GUI_TOOLBAR_REDO_DISABLED_0));
2626        m_redoButton.setVisible(false);
2627        m_toolbar.addLeft(m_redoButton);
2628
2629        m_undoRedoHandlerRegistration = CmsUndoRedoHandler.getInstance().addValueChangeHandler(
2630            new ValueChangeHandler<CmsUndoRedoHandler.UndoRedoState>() {
2631
2632                public void onValueChange(ValueChangeEvent<UndoRedoState> event) {
2633
2634                    setUndoRedoState(event.getValue());
2635                }
2636            });
2637        m_openFormButton = createButton(
2638            Messages.get().key(Messages.GUI_TOOLBAR_OPEN_FORM_0),
2639            I_CmsButton.ButtonData.EDIT.getIconClass());
2640        m_openFormButton.addClickHandler(new ClickHandler() {
2641
2642            public void onClick(ClickEvent event) {
2643
2644                initFormPanel();
2645                renderFormContent();
2646            }
2647        });
2648        m_toolbar.addLeft(m_openFormButton);
2649
2650        m_hideHelpBubblesButton = new CmsToggleButton();
2651
2652        m_hideHelpBubblesButton.setImageClass(I_CmsButton.ButtonData.TOGGLE_HELP.getIconClass());
2653        m_hideHelpBubblesButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
2654        m_hideHelpBubblesButton.setSize(Size.big);
2655        m_hideHelpBubblesButton.addClickHandler(new ClickHandler() {
2656
2657            public void onClick(ClickEvent event) {
2658
2659                CmsToggleButton button = (CmsToggleButton)event.getSource();
2660                hideHelpBubbles(!button.isDown());
2661            }
2662        });
2663        m_hideHelpBubblesButton.setDown(CmsCoreProvider.get().isShowEditorHelp());
2664        CmsValueFocusHandler.getInstance().hideHelpBubbles(RootPanel.get(), !CmsCoreProvider.get().isShowEditorHelp());
2665        if (!CmsCoreProvider.get().isShowEditorHelp()) {
2666            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_HIDDEN_0));
2667        } else {
2668            m_hideHelpBubblesButton.setTitle(Messages.get().key(Messages.GUI_TOOLBAR_HELP_BUBBLES_SHOWN_0));
2669        }
2670        m_toolbar.addRight(m_hideHelpBubblesButton);
2671
2672        m_cancelButton = createButton(
2673            Messages.get().key(Messages.GUI_TOOLBAR_RESET_0),
2674            I_CmsButton.ButtonData.RESET_BUTTON.getIconClass());
2675        m_cancelButton.addClickHandler(new ClickHandler() {
2676
2677            public void onClick(ClickEvent event) {
2678
2679                confirmCancel();
2680            }
2681        });
2682        m_toolbar.addRight(m_cancelButton);
2683        RootPanel.get().add(m_toolbar);
2684    }
2685
2686    /**
2687     * Sets both this object's locale field and a Javascript variable that can be used to access the locale from nested iframes.
2688     *
2689     * @param locale the locale to set
2690     */
2691    private void internalSetLocale(String locale) {
2692
2693        m_locale = locale;
2694        JsPropertyMap<Object> window = Js.cast(DomGlobal.window);
2695        if (locale != null) {
2696            window.set(CmsGwtConstants.ATTR_CONTENT_EDITOR_LOCALE, locale);
2697        } else {
2698            window.delete(CmsGwtConstants.ATTR_CONTENT_EDITOR_LOCALE);
2699        }
2700    }
2701
2702    /**
2703     * Sets the editor state.<p>
2704     *
2705     * @param changed if the content has been changed
2706     */
2707    private native void setEditorState(boolean changed)/*-{
2708        if (typeof $wnd.cmsSetEditorChangedState === 'function') {
2709            $wnd.cmsSetEditorChangedState(changed);
2710        }
2711    }-*/;
2712
2713    /**
2714     * Sets the resource info to native window context variables.<p>
2715     *
2716     * @param sitePath the site path
2717     * @param locale the content locale
2718     */
2719    private native void setNativeResourceInfo(String sitePath, String locale)/*-{
2720        $wnd._editResource = sitePath;
2721        $wnd._editLanguage = locale;
2722    }-*/;
2723
2724    /**
2725     * Updates the editor values according to the given entity.<p>
2726     *
2727     * @param previous the previous entity state
2728     * @param updated the updated entity state
2729     * @param target the target entity
2730     * @param parentPathElements the parent path elements
2731     */
2732    private void updateEditorValues(
2733        CmsEntity previous,
2734        CmsEntity updated,
2735        CmsEntity target,
2736        List<String> parentPathElements) {
2737
2738        for (String attributeName : m_entityBackend.getType(target.getTypeName()).getAttributeNames()) {
2739            CmsAttributeHandler handler = getAttributeHandler(attributeName, parentPathElements);
2740            if (handler == null) {
2741                // non visible attribute, skip it
2742                continue;
2743            }
2744            if (previous.hasAttribute(attributeName)
2745                && updated.hasAttribute(attributeName)
2746                && target.hasAttribute(attributeName)) {
2747                CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName);
2748                CmsEntityAttribute previousAttribute = previous.getAttribute(attributeName);
2749                CmsEntityAttribute targetAttribute = target.getAttribute(attributeName);
2750                if (updatedAttribute.isSimpleValue()) {
2751                    if ((updatedAttribute.getValueCount() == previousAttribute.getValueCount())
2752                        && (updatedAttribute.getValueCount() == targetAttribute.getValueCount())) {
2753                        for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
2754                            if (!updatedAttribute.getSimpleValues().get(i).equals(
2755                                previousAttribute.getSimpleValues().get(i))
2756                                && previousAttribute.getSimpleValues().get(i).equals(
2757                                    targetAttribute.getSimpleValues().get(i))) {
2758
2759                                changeSimpleValue(
2760                                    attributeName,
2761                                    i,
2762                                    updatedAttribute.getSimpleValues().get(i),
2763                                    parentPathElements);
2764                            }
2765                        }
2766                    } else {
2767                        if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) {
2768                            // only act, if the value count has not been altered while executing the server request
2769                            if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) {
2770                                // new values have been added
2771                                for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
2772                                    if (i >= previousAttribute.getSimpleValues().size()) {
2773                                        handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i));
2774                                    } else if (!updatedAttribute.getSimpleValues().get(i).equals(
2775                                        previousAttribute.getSimpleValues().get(i))
2776                                        && previousAttribute.getSimpleValues().get(i).equals(
2777                                            targetAttribute.getSimpleValues().get(i))) {
2778                                        changeSimpleValue(
2779                                            attributeName,
2780                                            i,
2781                                            updatedAttribute.getSimpleValues().get(i),
2782                                            parentPathElements);
2783                                    }
2784                                }
2785                            } else {
2786                                // values have been removed
2787                                for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) {
2788                                    if (i >= updatedAttribute.getSimpleValues().size()) {
2789                                        handler.removeAttributeValue(i);
2790                                    } else if (!updatedAttribute.getSimpleValues().get(i).equals(
2791                                        previousAttribute.getSimpleValues().get(i))
2792                                        && previousAttribute.getSimpleValues().get(i).equals(
2793                                            targetAttribute.getSimpleValues().get(i))) {
2794                                        changeSimpleValue(
2795                                            attributeName,
2796                                            i,
2797                                            updatedAttribute.getSimpleValues().get(i),
2798                                            parentPathElements);
2799                                    }
2800                                }
2801                            }
2802                        }
2803                    }
2804                } else {
2805                    if (targetAttribute.getValueCount() == previousAttribute.getValueCount()) {
2806                        // only act, if the value count has not been altered while executing the server request
2807                        if (updatedAttribute.getValueCount() > previousAttribute.getValueCount()) {
2808                            // new values have been added
2809                            for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
2810                                if (i >= previousAttribute.getSimpleValues().size()) {
2811                                    handler.addNewAttributeValue(
2812                                        m_entityBackend.registerEntity(
2813                                            updatedAttribute.getComplexValues().get(i),
2814                                            true));
2815                                } else {
2816                                    List<String> childPathElements = new ArrayList<String>(parentPathElements);
2817                                    childPathElements.add(attributeName + "[" + i + "]");
2818                                    updateEditorValues(
2819                                        previousAttribute.getComplexValues().get(i),
2820                                        updatedAttribute.getComplexValues().get(i),
2821                                        targetAttribute.getComplexValues().get(i),
2822                                        childPathElements);
2823                                }
2824                            }
2825                        } else {
2826                            // values have been removed
2827                            for (int i = previousAttribute.getValueCount() - 1; i >= 0; i--) {
2828                                if (i >= updatedAttribute.getValueCount()) {
2829
2830                                    handler.removeAttributeValue(i);
2831                                } else {
2832                                    List<String> childPathElements = new ArrayList<String>(parentPathElements);
2833                                    childPathElements.add(attributeName + "[" + i + "]");
2834                                    updateEditorValues(
2835                                        previousAttribute.getComplexValues().get(i),
2836                                        updatedAttribute.getComplexValues().get(i),
2837                                        targetAttribute.getComplexValues().get(i),
2838                                        childPathElements);
2839                                }
2840                            }
2841                        }
2842                    }
2843                }
2844            } else if (previous.hasAttribute(attributeName) && target.hasAttribute(attributeName)) {
2845                for (int i = target.getAttribute(attributeName).getValueCount() - 1; i >= 0; i--) {
2846                    handler.removeAttributeValue(i);
2847                }
2848
2849            } else if (!previous.hasAttribute(attributeName)
2850                && !target.hasAttribute(attributeName)
2851                && updated.hasAttribute(attributeName)) //
2852            {
2853                CmsEntityAttribute updatedAttribute = updated.getAttribute(attributeName);
2854                for (int i = 0; i < updatedAttribute.getValueCount(); i++) {
2855                    if (updatedAttribute.isSimpleValue()) {
2856                        handler.addNewAttributeValue(updatedAttribute.getSimpleValues().get(i));
2857                    } else {
2858                        handler.addNewAttributeValue(
2859                            m_entityBackend.registerEntity(updatedAttribute.getComplexValues().get(i), true));
2860                    }
2861                }
2862            }
2863        }
2864    }
2865
2866    /**
2867     * Updates the styles at the save buttons.
2868     * @param hasErrors flag, indicating if we have errors
2869     * @param hasWarnings flag, indicating if we have warnings
2870     */
2871    private void updateSaveButtons(boolean hasErrors, boolean hasWarnings) {
2872
2873        if (m_hasErrors && !hasErrors) {
2874            m_saveButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsError());
2875            m_saveExitButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsError());
2876            m_publishButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsError());
2877            m_hasErrors = false;
2878        }
2879        if (m_hasWarnings && (hasErrors || !hasWarnings)) {
2880            m_saveButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsWarning());
2881            m_saveExitButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsWarning());
2882            m_publishButton.removeStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsWarning());
2883            m_hasWarnings = false;
2884        }
2885        if (!m_hasErrors && hasErrors) {
2886            m_saveButton.addStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsError());
2887            m_saveExitButton.addStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsError());
2888            m_publishButton.addStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsError());
2889            m_hasErrors = true;
2890        } else if (!m_hasWarnings && !m_hasErrors && hasWarnings) {
2891            m_saveButton.addStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsWarning());
2892            m_saveExitButton.addStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsWarning());
2893            m_publishButton.addStyleName(I_CmsLayoutBundle.INSTANCE.buttonCss().cmsWarning());
2894            m_hasWarnings = true;
2895        }
2896        if (m_isSaveDisabled && (m_hasErrors || m_hasWarnings)) {
2897            enableSave();
2898        }
2899    }
2900}