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.ui.editors.messagebundle;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.types.I_CmsResourceType;
033import org.opencms.i18n.CmsMessages;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.CmsUIServlet;
037import org.opencms.main.OpenCms;
038import org.opencms.ui.CmsVaadinUtils;
039import org.opencms.ui.FontOpenCms;
040import org.opencms.ui.apps.CmsEditor;
041import org.opencms.ui.apps.I_CmsAppUIContext;
042import org.opencms.ui.apps.I_CmsHasShortcutActions;
043import org.opencms.ui.components.CmsConfirmationDialog;
044import org.opencms.ui.components.CmsErrorDialog;
045import org.opencms.ui.components.CmsToolBar;
046import org.opencms.ui.components.I_CmsWindowCloseListener;
047import org.opencms.ui.contextmenu.CmsContextMenu;
048import org.opencms.ui.editors.I_CmsEditor;
049import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorModel.ConfigurableMessages;
050import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorModel.KeyChangeResult;
051import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.BundleType;
052import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EditMode;
053import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EntryChangeEvent;
054import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_EntryChangeListener;
055import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_ItemDeletionListener;
056import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_OptionListener;
057import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.ItemDeletionEvent;
058import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.TableProperty;
059import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.TranslateTableFieldFactory;
060
061import java.io.IOException;
062import java.util.Collection;
063import java.util.HashMap;
064import java.util.Locale;
065import java.util.Map;
066
067import org.apache.commons.logging.Log;
068
069import org.tepi.filtertable.FilterTable;
070
071import com.vaadin.annotations.Theme;
072import com.vaadin.event.Action;
073import com.vaadin.event.ContextClickEvent;
074import com.vaadin.event.ContextClickEvent.ContextClickListener;
075import com.vaadin.event.ShortcutAction;
076import com.vaadin.navigator.ViewChangeListener;
077import com.vaadin.server.VaadinServlet;
078import com.vaadin.ui.Button;
079import com.vaadin.ui.Button.ClickEvent;
080import com.vaadin.ui.Button.ClickListener;
081import com.vaadin.ui.Component;
082import com.vaadin.ui.Component.Focusable;
083import com.vaadin.ui.Notification;
084import com.vaadin.ui.Notification.Type;
085import com.vaadin.ui.Panel;
086import com.vaadin.ui.UI;
087import com.vaadin.v7.data.Item;
088import com.vaadin.v7.data.Property;
089import com.vaadin.v7.data.util.IndexedContainer;
090import com.vaadin.v7.ui.AbstractTextField;
091import com.vaadin.v7.ui.VerticalLayout;
092
093/**
094 * Controller for the VAADIN UI of the Message Bundle Editor.
095 */
096@Theme("opencms")
097public class CmsMessageBundleEditor
098implements I_CmsEditor, I_CmsWindowCloseListener, ViewChangeListener, I_EntryChangeListener, I_ItemDeletionListener,
099I_OptionListener, I_CmsHasShortcutActions {
100
101    /** Name of the keyfilter parameter. */
102    public static final String PARAM_KEYFILTER = "keyfilter";
103
104    /** Used to implement {@link java.io.Serializable}. */
105    private static final long serialVersionUID = 5366955716462191580L;
106
107    /** The log object for this class. */
108    private static final Log LOG = CmsLog.getLog(CmsMessageBundleEditor.class);
109
110    /** Exit shortcut. */
111    private static final Action ACTION_EXIT = new ShortcutAction(
112        "Ctrl+Shift+X",
113        ShortcutAction.KeyCode.X,
114        new int[] {ShortcutAction.ModifierKey.CTRL, ShortcutAction.ModifierKey.SHIFT});
115
116    /** Exit shortcut, (using Apple CMD as modifier). */
117    private static final Action ACTION_EXIT_CMD = new ShortcutAction(
118        "CMD+Shift+X",
119        ShortcutAction.KeyCode.X,
120        new int[] {ShortcutAction.ModifierKey.META, ShortcutAction.ModifierKey.SHIFT});
121
122    /** Save shortcut. */
123    private static final Action ACTION_SAVE = new ShortcutAction(
124        "Ctrl+S",
125        ShortcutAction.KeyCode.S,
126        new int[] {ShortcutAction.ModifierKey.CTRL});
127
128    /** Save shortcut, (using Apple CMD as modifier). */
129    private static final Action ACTION_SAVE_CMD = new ShortcutAction(
130        "CMD+S",
131        ShortcutAction.KeyCode.S,
132        new int[] {ShortcutAction.ModifierKey.META});
133
134    /** Save & Exit shortcut. */
135    private static final Action ACTION_SAVE_AND_EXIT = new ShortcutAction(
136        "Ctrl+Shift+S",
137        ShortcutAction.KeyCode.S,
138        new int[] {ShortcutAction.ModifierKey.CTRL, ShortcutAction.ModifierKey.SHIFT});
139
140    /** Save & Exit shortcut, (using Apple CMD as modifier). */
141    private static final Action ACTION_SAVE_AND_EXIT_CMD = new ShortcutAction(
142        "CMD+Shift+S",
143        ShortcutAction.KeyCode.S,
144        new int[] {ShortcutAction.ModifierKey.META, ShortcutAction.ModifierKey.SHIFT});
145
146    /** The bundle editor shortcuts. */
147    Map<Action, Runnable> m_shortcutActions;
148
149    /** Messages used by the GUI. */
150    CmsMessages m_messages;
151
152    /** Configurable Messages. */
153    ConfigurableMessages m_configurableMessages;
154
155    /** The field factories for the different modes. */
156    private final Map<CmsMessageBundleEditorTypes.EditMode, CmsMessageBundleEditorTypes.TranslateTableFieldFactory> m_fieldFactories = new HashMap<CmsMessageBundleEditorTypes.EditMode, CmsMessageBundleEditorTypes.TranslateTableFieldFactory>(
157        2);
158    /** The cell style generators for the different modes. */
159    private final Map<CmsMessageBundleEditorTypes.EditMode, CmsMessageBundleEditorTypes.TranslateTableCellStyleGenerator> m_styleGenerators = new HashMap<CmsMessageBundleEditorTypes.EditMode, CmsMessageBundleEditorTypes.TranslateTableCellStyleGenerator>(
160        2);
161
162    /** Flag, indicating if leaving the editor is confirmed. */
163    boolean m_leaving;
164
165    /** The context of the UI. */
166    I_CmsAppUIContext m_context;
167
168    /** The model behind the UI. */
169    CmsMessageBundleEditorModel m_model;
170
171    /** CmsObject for read / write actions. */
172    CmsObject m_cms;
173
174    /** The resource that was opened with the editor. */
175    CmsResource m_resource;
176
177    /** The table component that is shown. */
178    FilterTable m_table;
179
180    /** The save button. */
181    Button m_saveBtn;
182    /** The save and exit button. */
183    Button m_saveExitBtn;
184
185    /** The options column, optionally shown in the table. */
186    CmsMessageBundleEditorTypes.OptionColumnGenerator m_optionsColumn;
187
188    /** The place where to go when the editor is closed. */
189    private String m_backLink;
190
191    /** The options view of the editor. */
192    private CmsMessageBundleEditorOptions m_options;
193
194    /**
195     * Default constructor.
196     */
197    public CmsMessageBundleEditor() {
198
199        m_shortcutActions = new HashMap<Action, Runnable>();
200        m_shortcutActions = new HashMap<Action, Runnable>();
201        Runnable save = new Runnable() {
202
203            public void run() {
204
205                saveAction();
206            }
207        };
208        m_shortcutActions.put(ACTION_SAVE, save);
209        m_shortcutActions.put(ACTION_SAVE_CMD, save);
210        Runnable saveExit = new Runnable() {
211
212            public void run() {
213
214                saveAction();
215                closeAction();
216            }
217        };
218        m_shortcutActions.put(ACTION_SAVE_AND_EXIT, saveExit);
219        m_shortcutActions.put(ACTION_SAVE_AND_EXIT_CMD, saveExit);
220        Runnable exit = new Runnable() {
221
222            public void run() {
223
224                closeAction();
225            }
226        };
227        m_shortcutActions.put(ACTION_EXIT, exit);
228        m_shortcutActions.put(ACTION_EXIT_CMD, exit);
229    }
230
231    /**
232     * @see com.vaadin.navigator.ViewChangeListener#afterViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
233     */
234    public void afterViewChange(ViewChangeEvent event) {
235
236        // do nothing
237
238    }
239
240    /**
241     * @see com.vaadin.navigator.ViewChangeListener#beforeViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
242     */
243    public boolean beforeViewChange(final ViewChangeEvent event) {
244
245        if (!m_leaving && m_model.hasChanges()) {
246            CmsConfirmationDialog.show(
247                CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_EDITOR_CLOSE_CAPTION_0),
248                CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_EDITOR_CLOSE_TEXT_0),
249                new Runnable() {
250
251                    public void run() {
252
253                        m_leaving = true;
254                        event.getNavigator().navigateTo(event.getViewName());
255                    }
256                });
257            return false;
258        }
259
260        cleanUpAction();
261        return true;
262    }
263
264    /**
265     * @see org.opencms.ui.editors.I_CmsEditor#getPriority()
266     */
267    public int getPriority() {
268
269        return 200;
270    }
271
272    /**
273     * @see org.opencms.ui.apps.I_CmsHasShortcutActions#getShortcutActions()
274     */
275    public Map<Action, Runnable> getShortcutActions() {
276
277        return m_shortcutActions;
278    }
279
280    /**
281     * @see org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_OptionListener#handleAddKey(java.lang.String)
282     */
283    public boolean handleAddKey(final String newKey) {
284
285        Map<Object, Object> filters = getFilters();
286        m_table.clearFilters();
287        boolean canAdd = !keyAlreadyExists(newKey);
288        if (canAdd) {
289            Object copyEntryId = m_table.addItem();
290            Item copyEntry = m_table.getItem(copyEntryId);
291            copyEntry.getItemProperty(TableProperty.KEY).setValue(newKey);
292        }
293        setFilters(filters);
294
295        if (m_model.hasDescriptor()
296            | m_model.getBundleType().equals(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR)) {
297            handleChange(TableProperty.KEY);
298            handleChange(TableProperty.DESCRIPTION);
299        }
300
301        return canAdd;
302    }
303
304    /**
305     * @see org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_EntryChangeListener#handleEntryChange(org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EntryChangeEvent)
306     */
307    public void handleEntryChange(EntryChangeEvent event) {
308
309        if (event.getPropertyId().equals(TableProperty.KEY)) {
310            KeyChangeResult result = m_model.handleKeyChange(event, true);
311            String captionKey = null;
312            String descriptionKey = null;
313            switch (result) {
314                case SUCCESS:
315                    handleChange(event.getPropertyId());
316                    return;
317                case FAILED_DUPLICATED_KEY:
318                    captionKey = Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_KEY_ALREADY_EXISTS_CAPTION_0;
319                    descriptionKey = Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_KEY_ALREADY_EXISTS_DESCRIPTION_0;
320                    break;
321                case FAILED_FOR_OTHER_LANGUAGE:
322                    captionKey = Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_KEY_RENAMING_FAILED_CAPTION_0;
323                    descriptionKey = Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_KEY_RENAMING_FAILED_DESCRIPTION_0;
324                    break;
325                default:
326                    throw new IllegalArgumentException();
327            }
328            CmsMessageBundleEditorTypes.showWarning(m_messages.key(captionKey), m_messages.key(descriptionKey));
329            event.getSource().focus();
330        }
331        handleChange(event.getPropertyId());
332
333    }
334
335    /**
336     * @see org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_ItemDeletionListener#handleItemDeletion(org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.ItemDeletionEvent)
337     */
338    public boolean handleItemDeletion(ItemDeletionEvent e) {
339
340        Item it = m_table.getItem(e.getItemId());
341        Property<?> keyProp = it.getItemProperty(TableProperty.KEY);
342        String key = (String)keyProp.getValue();
343        if (m_model.handleKeyDeletion(key)) {
344            if (m_model.hasDescriptor()
345                | m_model.getBundleType().equals(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR)) {
346                handleChange(TableProperty.DESCRIPTION);
347            }
348            handleChange(TableProperty.KEY);
349            return true;
350        }
351        CmsMessageBundleEditorTypes.showWarning(
352            m_messages.key(Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_REMOVE_ENTRY_FAILED_CAPTION_0),
353            m_messages.key(Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_REMOVE_ENTRY_FAILED_DESCRIPTION_0));
354        return false;
355
356    }
357
358    /**
359     * @see org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_OptionListener#handleLanguageChange(java.util.Locale)
360     */
361    public void handleLanguageChange(final Locale locale) {
362
363        if (!locale.equals(m_model.getLocale())) {
364            Object sortProperty = m_table.getSortContainerPropertyId();
365            boolean isAcending = m_table.isSortAscending();
366            Map<Object, Object> filters = getFilters();
367            m_table.clearFilters();
368            if (m_model.setLocale(locale)) {
369                m_options.setEditedFilePath(m_model.getEditedFilePath());
370                m_table.sort(new Object[] {sortProperty}, new boolean[] {isAcending});
371            } else {
372                String caption = m_messages.key(
373                    Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_LOCALE_SWITCHING_FAILED_CAPTION_0);
374                String description = m_messages.key(
375                    Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_LOCALE_SWITCHING_FAILED_DESCRIPTION_0);
376                Notification warning = new Notification(caption, description, Type.WARNING_MESSAGE, true);
377                warning.setDelayMsec(-1);
378                warning.show(UI.getCurrent().getPage());
379                m_options.setLanguage(m_model.getLocale());
380            }
381            setFilters(filters);
382            m_table.select(m_table.getCurrentPageFirstItemId());
383        }
384    }
385
386    /**
387     * @see org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.I_OptionListener#handleModeChange(org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EditMode)
388     */
389    public void handleModeChange(final EditMode mode) {
390
391        setEditMode(mode);
392
393    }
394
395    /**
396     * @see org.opencms.ui.editors.I_CmsEditor#initUI(org.opencms.ui.apps.I_CmsAppUIContext, org.opencms.file.CmsResource, java.lang.String, java.util.Map)
397     */
398    public void initUI(I_CmsAppUIContext context, CmsResource resource, String backLink, Map<String, String> params) {
399
400        m_cms = ((CmsUIServlet)VaadinServlet.getCurrent()).getCmsObject();
401        m_messages = Messages.get().getBundle(UI.getCurrent().getLocale());
402        m_resource = resource;
403        m_backLink = backLink;
404        m_context = context;
405
406        try {
407            m_model = new CmsMessageBundleEditorModel(m_cms, m_resource);
408            m_options = new CmsMessageBundleEditorOptions(
409                m_model.getLocales(),
410                m_model.getLocale(),
411                m_model.getEditMode(),
412                this);
413            m_options.setEditedFilePath(m_model.getEditedFilePath());
414            m_configurableMessages = m_model.getConfigurableMessages(m_messages, UI.getCurrent().getLocale());
415
416            fillToolBar(context);
417            context.showInfoArea(false);
418
419            Component main = createMainComponent();
420
421            initFieldFactories();
422            initStyleGenerators();
423
424            m_table.setTableFieldFactory(m_fieldFactories.get(m_model.getEditMode()));
425            m_table.setCellStyleGenerator(m_styleGenerators.get(m_model.getEditMode()));
426
427            m_optionsColumn.registerItemDeletionListener(this);
428
429            adjustVisibleColumns();
430
431            context.setAppContent(main);
432
433            adjustFocus();
434
435            initKeyFilter(params.get(PARAM_KEYFILTER));
436
437            if (m_model.getSwitchedLocaleOnOpening()) {
438                CmsMessageBundleEditorTypes.showWarning(
439                    m_messages.key(Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_SWITCHED_LOCALE_CAPTION_0),
440                    m_messages.key(Messages.GUI_NOTIFICATION_MESSAGEBUNDLEEDITOR_SWITCHED_LOCALE_DESCRIPTION_0));
441            }
442
443        } catch (IOException | CmsException e) {
444            LOG.error(m_messages.key(Messages.ERR_LOADING_RESOURCES_0), e);
445            Notification.show(m_messages.key(Messages.ERR_LOADING_RESOURCES_0), Type.ERROR_MESSAGE);
446            closeAction();
447        }
448
449    }
450
451    /**
452     * @see org.opencms.ui.editors.I_CmsEditor#matchesResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource, boolean)
453     */
454    public boolean matchesResource(CmsObject cms, CmsResource resource, boolean plainText) {
455
456        I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource);
457        return matchesType(type, plainText);
458    }
459
460    /**
461     * @see org.opencms.ui.editors.I_CmsEditor#matchesResource(org.opencms.file.CmsResource, boolean)
462     */
463    public boolean matchesType(I_CmsResourceType type, boolean plainText) {
464
465        return !plainText && (CmsMessageBundleEditorTypes.BundleType.toBundleType(type.getTypeName()) != null);
466    }
467
468    /**
469     * @see org.opencms.ui.editors.I_CmsEditor#newInstance()
470     */
471    public I_CmsEditor newInstance() {
472
473        return new CmsMessageBundleEditor();
474    }
475
476    /**
477     * @see org.opencms.ui.components.I_CmsWindowCloseListener#onWindowClose()
478     */
479    public void onWindowClose() {
480
481        cleanUpAction();
482
483    }
484
485    /**
486     * Unlocks all resources. Call when closing the editor.
487     */
488    void closeAction() {
489
490        CmsEditor.openBackLink(m_backLink);
491    }
492
493    /**
494     * Returns the currently set filters in a map column -> filter.
495     *
496     * @return the currently set filters in a map column -> filter.
497     */
498    Map<Object, Object> getFilters() {
499
500        Map<Object, Object> result = new HashMap<Object, Object>(4);
501        result.put(TableProperty.KEY, m_table.getFilterFieldValue(TableProperty.KEY));
502        result.put(TableProperty.DEFAULT, m_table.getFilterFieldValue(TableProperty.DEFAULT));
503        result.put(TableProperty.DESCRIPTION, m_table.getFilterFieldValue(TableProperty.DESCRIPTION));
504        result.put(TableProperty.TRANSLATION, m_table.getFilterFieldValue(TableProperty.TRANSLATION));
505        return result;
506    }
507
508    /**
509     * Publish the changes.
510     */
511    void publishAction() {
512
513        //save first
514        saveAction();
515
516        //publish
517        m_model.publish();
518
519    }
520
521    /**
522     * Save the changes.
523     */
524    void saveAction() {
525
526        Map<Object, Object> filters = getFilters();
527        m_table.clearFilters();
528
529        try {
530
531            m_model.save();
532            disableSaveButtons();
533
534        } catch (CmsException e) {
535            LOG.error(m_messages.key(Messages.ERR_SAVING_CHANGES_0), e);
536            CmsErrorDialog.showErrorDialog(m_messages.key(Messages.ERR_SAVING_CHANGES_0), e);
537        }
538
539        setFilters(filters);
540
541    }
542
543    /**
544     * Set the edit mode.
545     * @param newMode the edit mode to set.
546     * @return Flag, indicating if mode switching was successful.
547     */
548    boolean setEditMode(CmsMessageBundleEditorTypes.EditMode newMode) {
549
550        CmsMessageBundleEditorTypes.EditMode oldMode = m_model.getEditMode();
551        boolean success = false;
552        if (!newMode.equals(oldMode)) {
553            Map<Object, Object> filters = getFilters();
554            m_table.clearFilters();
555            if (m_model.setEditMode(newMode)) {
556                m_table.setTableFieldFactory(m_fieldFactories.get(newMode));
557                m_table.setCellStyleGenerator(m_styleGenerators.get(newMode));
558                adjustOptionsColumn(oldMode, newMode);
559                m_options.updateShownOptions(m_model.hasMasterMode(), m_model.canAddKeys());
560                m_options.setEditMode(newMode);
561                success = true;
562            } else {
563                Notification.show(m_messages.key(Messages.ERR_MODE_CHANGE_NOT_POSSIBLE_0), Type.ERROR_MESSAGE);
564
565            }
566            setFilters(filters);
567            adjustFocus();
568        }
569        return success;
570
571    }
572
573    /**
574     * Sets the provided filters.
575     * @param filters a map "column id -> filter".
576     */
577    void setFilters(Map<Object, Object> filters) {
578
579        for (Object column : filters.keySet()) {
580            Object filterValue = filters.get(column);
581            if ((filterValue != null) && !filterValue.toString().isEmpty() && !m_table.isColumnCollapsed(column)) {
582                m_table.setFilterFieldValue(column, filterValue);
583            }
584        }
585    }
586
587    /**
588     * Sets the focus to the first editable field of the table.
589     * If entries can be added, it is set to the first field of the "Add entries" row.
590     */
591    private void adjustFocus() {
592
593        // Put the focus on the "Filter key" field first
594        ((Focusable)m_table.getFilterField(TableProperty.KEY)).focus();
595    }
596
597    /**
598     * Show or hide the options column dependent on the provided edit mode.
599     * @param oldMode the old edit mode
600     * @param newMode the edit mode for which the options column's visibility should be adjusted.
601     */
602    private void adjustOptionsColumn(
603        CmsMessageBundleEditorTypes.EditMode oldMode,
604        CmsMessageBundleEditorTypes.EditMode newMode) {
605
606        if (m_model.isShowOptionsColumn(oldMode) != m_model.isShowOptionsColumn(newMode)) {
607            m_table.removeGeneratedColumn(TableProperty.OPTIONS);
608            if (m_model.isShowOptionsColumn(newMode)) {
609                // Don't know why exactly setting the filter field invisible is necessary here,
610                // it should be already set invisible - but apparently not setting it invisible again
611                // will result in the field being visible.
612                m_table.setFilterFieldVisible(TableProperty.OPTIONS, false);
613                m_table.addGeneratedColumn(TableProperty.OPTIONS, m_optionsColumn);
614            }
615        }
616    }
617
618    /**
619     * Adjust the visible columns.
620     */
621    private void adjustVisibleColumns() {
622
623        if (m_table.isColumnCollapsingAllowed()) {
624            if ((m_model.hasDefaultValues()) || m_model.getBundleType().equals(BundleType.DESCRIPTOR)) {
625                m_table.setColumnCollapsed(TableProperty.DEFAULT, false);
626            } else {
627                m_table.setColumnCollapsed(TableProperty.DEFAULT, true);
628            }
629
630            if (((m_model.getEditMode().equals(EditMode.MASTER) || m_model.hasDescriptionValues()))
631                || m_model.getBundleType().equals(BundleType.DESCRIPTOR)) {
632                m_table.setColumnCollapsed(TableProperty.DESCRIPTION, false);
633            } else {
634                m_table.setColumnCollapsed(TableProperty.DESCRIPTION, true);
635            }
636        }
637    }
638
639    /**
640     * Unlock all edited resources.
641     */
642    private void cleanUpAction() {
643
644        try {
645            m_model.deleteDescriptorIfNecessary();
646        } catch (CmsException e) {
647            LOG.error(m_messages.key(Messages.ERR_DELETING_DESCRIPTOR_0), e);
648        }
649        // unlock resource
650        m_model.unlock();
651    }
652
653    /**
654     * Returns a button component. On click, it triggers adding a bundle descriptor.
655     * @return a button for adding a descriptor to a bundle.
656     */
657    @SuppressWarnings("serial")
658    private Component createAddDescriptorButton() {
659
660        Button addDescriptorButton = CmsToolBar.createButton(
661            FontOpenCms.COPY_LOCALE,
662            m_messages.key(Messages.GUI_ADD_DESCRIPTOR_0));
663
664        addDescriptorButton.setDisableOnClick(true);
665
666        addDescriptorButton.addClickListener(new ClickListener() {
667
668            @SuppressWarnings("synthetic-access")
669            public void buttonClick(ClickEvent event) {
670
671                Map<Object, Object> filters = getFilters();
672                m_table.clearFilters();
673                if (!m_model.addDescriptor()) {
674                    CmsVaadinUtils.showAlert(
675                        m_messages.key(Messages.ERR_BUNDLE_DESCRIPTOR_CREATION_FAILED_0),
676                        m_messages.key(Messages.ERR_BUNDLE_DESCRIPTOR_CREATION_FAILED_DESCRIPTION_0),
677                        null);
678                } else {
679                    IndexedContainer newContainer = null;
680                    try {
681                        newContainer = m_model.getContainerForCurrentLocale();
682                        m_table.setContainerDataSource(newContainer);
683                        initFieldFactories();
684                        initStyleGenerators();
685                        setEditMode(EditMode.MASTER);
686                        m_table.setColumnCollapsingAllowed(true);
687                        adjustVisibleColumns();
688                        m_options.updateShownOptions(m_model.hasMasterMode(), m_model.canAddKeys());
689                        m_options.setEditMode(m_model.getEditMode());
690                    } catch (IOException | CmsException e) {
691                        // Can never appear here, since container is created by addDescriptor already.
692                        LOG.error(e.getLocalizedMessage(), e);
693                    }
694                }
695                setFilters(filters);
696            }
697        });
698        return addDescriptorButton;
699    }
700
701    /**
702     * Create the close button UI Component.
703     * @return the close button.
704     */
705    @SuppressWarnings("serial")
706    private Component createCloseButton() {
707
708        Button closeBtn = CmsToolBar.createButton(
709            FontOpenCms.CIRCLE_INV_CANCEL,
710            m_messages.key(Messages.GUI_BUTTON_CANCEL_0));
711        closeBtn.addClickListener(new ClickListener() {
712
713            public void buttonClick(ClickEvent event) {
714
715                closeAction();
716            }
717
718        });
719        return closeBtn;
720    }
721
722    /**
723     * Creates the button for converting an XML bundle in a property bundle.
724     * @return the created button.
725     */
726    private Component createConvertToPropertyBundleButton() {
727
728        Button addDescriptorButton = CmsToolBar.createButton(
729            FontOpenCms.SETTINGS,
730            m_messages.key(Messages.GUI_CONVERT_TO_PROPERTY_BUNDLE_0));
731
732        addDescriptorButton.setDisableOnClick(true);
733
734        addDescriptorButton.addClickListener(new ClickListener() {
735
736            private static final long serialVersionUID = 1L;
737
738            public void buttonClick(ClickEvent event) {
739
740                try {
741                    m_model.saveAsPropertyBundle();
742                    Notification.show("Conversion successful.");
743                } catch (CmsException | IOException e) {
744                    CmsVaadinUtils.showAlert("Conversion failed", e.getLocalizedMessage(), null);
745                }
746            }
747        });
748        addDescriptorButton.setDisableOnClick(true);
749        return addDescriptorButton;
750    }
751
752    /**
753     * Creates the main component of the editor with all sub-components.
754     * @return the completely filled main component of the editor.
755     * @throws IOException thrown if setting the table's content data source fails.
756     * @throws CmsException thrown if setting the table's content data source fails.
757     */
758    private Component createMainComponent() throws IOException, CmsException {
759
760        VerticalLayout mainComponent = new VerticalLayout();
761        mainComponent.setSizeFull();
762        mainComponent.addStyleName("o-message-bundle-editor");
763        m_table = createTable();
764        Panel navigator = new Panel();
765        navigator.setSizeFull();
766        navigator.setContent(m_table);
767        navigator.addActionHandler(new CmsMessageBundleEditorTypes.TableKeyboardHandler(m_table));
768        navigator.addStyleName("v-panel-borderless");
769
770        mainComponent.addComponent(m_options.getOptionsComponent());
771        mainComponent.addComponent(navigator);
772        mainComponent.setExpandRatio(navigator, 1f);
773        m_options.updateShownOptions(m_model.hasMasterMode(), m_model.canAddKeys());
774        return mainComponent;
775    }
776
777    /** Creates the save button UI Component.
778     * @return the save button.
779     */
780    @SuppressWarnings("serial")
781    private Component createPublishButton() {
782
783        Button publishBtn = CmsToolBar.createButton(FontOpenCms.PUBLISH, m_messages.key(Messages.GUI_BUTTON_PUBLISH_0));
784        publishBtn.addClickListener(new ClickListener() {
785
786            public void buttonClick(ClickEvent event) {
787
788                publishAction();
789            }
790
791        });
792        return publishBtn;
793    }
794
795    /** Creates the save button UI Component.
796     * @return the save button.
797     */
798    @SuppressWarnings("serial")
799    private Button createSaveButton() {
800
801        Button saveBtn = CmsToolBar.createButton(FontOpenCms.SAVE, m_messages.key(Messages.GUI_BUTTON_SAVE_0));
802        saveBtn.addClickListener(new ClickListener() {
803
804            public void buttonClick(ClickEvent event) {
805
806                saveAction();
807            }
808
809        });
810        saveBtn.setEnabled(false);
811        return saveBtn;
812    }
813
814    /** Creates the save and exit button UI Component.
815     * @return the save and exit button.
816     */
817    @SuppressWarnings("serial")
818    private Button createSaveExitButton() {
819
820        Button saveExitBtn = CmsToolBar.createButton(
821            FontOpenCms.SAVE_EXIT,
822            m_messages.key(Messages.GUI_BUTTON_SAVE_AND_EXIT_0));
823        saveExitBtn.addClickListener(new ClickListener() {
824
825            public void buttonClick(ClickEvent event) {
826
827                saveAction();
828                closeAction();
829
830            }
831        });
832        saveExitBtn.setEnabled(false);
833        return saveExitBtn;
834    }
835
836    /** Creates the (filled) table UI component.
837     * @return the (filled) table
838     * @throws IOException thrown if reading the properties file fails.
839     * @throws CmsException thrown if some read action for getting the table contentFilter fails.
840     */
841    private FilterTable createTable() throws IOException, CmsException {
842
843        final FilterTable table = new FilterTable();
844        table.setSizeFull();
845
846        table.setContainerDataSource(m_model.getContainerForCurrentLocale());
847
848        table.setColumnHeader(TableProperty.KEY, m_configurableMessages.getColumnHeader(TableProperty.KEY));
849        table.setColumnCollapsible(TableProperty.KEY, false);
850
851        table.setColumnHeader(TableProperty.DEFAULT, m_configurableMessages.getColumnHeader(TableProperty.DEFAULT));
852        table.setColumnCollapsible(TableProperty.DEFAULT, true);
853
854        table.setColumnHeader(
855            TableProperty.DESCRIPTION,
856            m_configurableMessages.getColumnHeader(TableProperty.DESCRIPTION));
857        table.setColumnCollapsible(TableProperty.DESCRIPTION, true);
858
859        table.setColumnHeader(
860            TableProperty.TRANSLATION,
861            m_configurableMessages.getColumnHeader(TableProperty.TRANSLATION));
862        table.setColumnCollapsible(TableProperty.TRANSLATION, false);
863
864        table.setColumnHeader(TableProperty.OPTIONS, m_configurableMessages.getColumnHeader(TableProperty.OPTIONS));
865        table.setFilterDecorator(new CmsMessageBundleEditorFilterDecorator());
866
867        table.setFilterBarVisible(true);
868        table.setColumnCollapsible(TableProperty.OPTIONS, false);
869
870        table.setSortEnabled(true);
871        table.setEditable(true);
872
873        table.setSelectable(true);
874        table.setImmediate(true);
875        table.setMultiSelect(false);
876
877        table.setColumnCollapsingAllowed(m_model.hasDescriptor());
878
879        table.setColumnReorderingAllowed(false);
880
881        m_optionsColumn = generateOptionsColumn(table);
882
883        if (m_model.isShowOptionsColumn(m_model.getEditMode())) {
884            table.setFilterFieldVisible(TableProperty.OPTIONS, false);
885            table.addGeneratedColumn(TableProperty.OPTIONS, m_optionsColumn);
886        }
887        table.setColumnWidth(TableProperty.OPTIONS, CmsMessageBundleEditorTypes.OPTION_COLUMN_WIDTH);
888        table.setColumnExpandRatio(TableProperty.KEY, 1f);
889        table.setColumnExpandRatio(TableProperty.DESCRIPTION, 1f);
890        table.setColumnExpandRatio(TableProperty.DEFAULT, 1f);
891        table.setColumnExpandRatio(TableProperty.TRANSLATION, 1f);
892
893        table.setPageLength(30);
894        table.setCacheRate(1);
895        table.sort(new Object[] {TableProperty.KEY}, new boolean[] {true});
896        table.addContextClickListener(new ContextClickListener() {
897
898            private static final long serialVersionUID = 1L;
899
900            public void contextClick(ContextClickEvent event) {
901
902                Object itemId = m_table.getValue();
903                CmsContextMenu contextMenu = m_model.getContextMenuForItem(itemId);
904                if (null != contextMenu) {
905                    contextMenu.setAsContextMenuOf(m_table);
906                    contextMenu.setOpenAutomatically(false);
907                    contextMenu.open(event.getClientX(), event.getClientY());
908                }
909            }
910        });
911        table.setNullSelectionAllowed(false);
912        table.select(table.getCurrentPageFirstItemId());
913        return table;
914    }
915
916    /**
917     * Disable the save buttons, e.g., after saving.
918     */
919    private void disableSaveButtons() {
920
921        if (m_saveBtn.isEnabled()) {
922            m_saveBtn.setEnabled(false);
923            m_saveExitBtn.setEnabled(false);
924        }
925
926    }
927
928    /** Adds Editor specific UI components to the toolbar.
929     * @param context The context that provides access to the toolbar.
930     */
931    private void fillToolBar(final I_CmsAppUIContext context) {
932
933        context.setAppTitle(m_messages.key(Messages.GUI_APP_TITLE_0));
934
935        // create components
936        Component publishBtn = createPublishButton();
937        m_saveBtn = createSaveButton();
938        m_saveExitBtn = createSaveExitButton();
939        Component closeBtn = createCloseButton();
940
941        context.enableDefaultToolbarButtons(false);
942        context.addToolbarButtonRight(closeBtn);
943        context.addToolbarButton(publishBtn);
944        context.addToolbarButton(m_saveExitBtn);
945        context.addToolbarButton(m_saveBtn);
946
947        Component addDescriptorBtn = createAddDescriptorButton();
948        if (m_model.hasDescriptor() || m_model.getBundleType().equals(BundleType.DESCRIPTOR)) {
949            addDescriptorBtn.setEnabled(false);
950        }
951        context.addToolbarButton(addDescriptorBtn);
952        if (m_model.getBundleType().equals(BundleType.XML)) {
953            Component convertToPropertyBundleBtn = createConvertToPropertyBundleButton();
954            context.addToolbarButton(convertToPropertyBundleBtn);
955        }
956    }
957
958    /** Generates the options column for the table.
959     * @param table table instance passed to the option column generator
960     * @return the options column
961     */
962    private CmsMessageBundleEditorTypes.OptionColumnGenerator generateOptionsColumn(FilterTable table) {
963
964        return new CmsMessageBundleEditorTypes.OptionColumnGenerator(table);
965    }
966
967    /**
968     * Handle a value change.
969     * @param propertyId the column in which the value has changed.
970     */
971    private void handleChange(Object propertyId) {
972
973        if (!m_saveBtn.isEnabled()) {
974            m_saveBtn.setEnabled(true);
975            m_saveExitBtn.setEnabled(true);
976        }
977        m_model.handleChange(propertyId);
978
979    }
980
981    /**
982     * Initialize the field factories for the messages table.
983     */
984    private void initFieldFactories() {
985
986        if (m_model.hasMasterMode()) {
987            TranslateTableFieldFactory masterFieldFactory = new CmsMessageBundleEditorTypes.TranslateTableFieldFactory(
988                m_table,
989                m_model.getEditableColumns(CmsMessageBundleEditorTypes.EditMode.MASTER));
990            masterFieldFactory.registerKeyChangeListener(this);
991            m_fieldFactories.put(CmsMessageBundleEditorTypes.EditMode.MASTER, masterFieldFactory);
992        }
993        TranslateTableFieldFactory defaultFieldFactory = new CmsMessageBundleEditorTypes.TranslateTableFieldFactory(
994            m_table,
995            m_model.getEditableColumns(CmsMessageBundleEditorTypes.EditMode.DEFAULT));
996        defaultFieldFactory.registerKeyChangeListener(this);
997        m_fieldFactories.put(CmsMessageBundleEditorTypes.EditMode.DEFAULT, defaultFieldFactory);
998
999    }
1000
1001    /**
1002     * Initializes the key filter.
1003     * @param the value to set as filter
1004     */
1005    private void initKeyFilter(String keyFilter) {
1006
1007        if (null != keyFilter) {
1008            ((AbstractTextField)m_table.getFilterField(TableProperty.KEY)).setValue(keyFilter);
1009        }
1010    }
1011
1012    /**
1013     * Initialize the style generators for the messages table.
1014     */
1015    private void initStyleGenerators() {
1016
1017        if (m_model.hasMasterMode()) {
1018            m_styleGenerators.put(
1019                CmsMessageBundleEditorTypes.EditMode.MASTER,
1020                new CmsMessageBundleEditorTypes.TranslateTableCellStyleGenerator(
1021                    m_model.getEditableColumns(CmsMessageBundleEditorTypes.EditMode.MASTER)));
1022        }
1023        m_styleGenerators.put(
1024            CmsMessageBundleEditorTypes.EditMode.DEFAULT,
1025            new CmsMessageBundleEditorTypes.TranslateTableCellStyleGenerator(
1026                m_model.getEditableColumns(CmsMessageBundleEditorTypes.EditMode.DEFAULT)));
1027
1028    }
1029
1030    /**
1031     * Checks if a key already exists.
1032     * @param newKey the key to check for.
1033     * @return <code>true</code> if the key already exists, <code>false</code> otherwise.
1034     */
1035    private boolean keyAlreadyExists(String newKey) {
1036
1037        Collection<?> itemIds = m_table.getItemIds();
1038        for (Object itemId : itemIds) {
1039            if (m_table.getItem(itemId).getItemProperty(TableProperty.KEY).getValue().equals(newKey)) {
1040                return true;
1041            }
1042        }
1043        return false;
1044    }
1045
1046}