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