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