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