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