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.sitemap;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProject;
032import org.opencms.file.CmsProperty;
033import org.opencms.file.CmsPropertyDefinition;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.gwt.CmsCoreService;
037import org.opencms.i18n.CmsLocaleGroupService;
038import org.opencms.i18n.CmsLocaleManager;
039import org.opencms.lock.CmsLockActionRecord;
040import org.opencms.lock.CmsLockActionRecord.LockChange;
041import org.opencms.lock.CmsLockUtil;
042import org.opencms.main.CmsException;
043import org.opencms.main.CmsLog;
044import org.opencms.main.OpenCms;
045import org.opencms.site.CmsSite;
046import org.opencms.ui.A_CmsUI;
047import org.opencms.ui.CmsVaadinUtils;
048import org.opencms.ui.FontOpenCms;
049import org.opencms.ui.I_CmsDialogContext;
050import org.opencms.ui.Messages;
051import org.opencms.ui.actions.CmsResourceInfoAction;
052import org.opencms.ui.apps.CmsSitemapEditorConfiguration;
053import org.opencms.ui.components.CmsBasicDialog;
054import org.opencms.ui.components.CmsBasicDialog.DialogWidth;
055import org.opencms.ui.components.CmsErrorDialog;
056import org.opencms.ui.components.CmsResourceIcon;
057import org.opencms.ui.components.CmsResourceIcon.IconMode;
058import org.opencms.ui.components.CmsResourceInfo;
059import org.opencms.ui.components.OpenCmsTheme;
060import org.opencms.ui.contextmenu.CmsContextMenu;
061import org.opencms.ui.contextmenu.CmsMenuItemVisibilityMode;
062import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry;
063import org.opencms.util.CmsUUID;
064
065import java.util.Arrays;
066import java.util.Collection;
067import java.util.IdentityHashMap;
068import java.util.List;
069import java.util.Locale;
070
071import org.apache.commons.logging.Log;
072
073import com.google.common.base.Joiner;
074import com.google.common.base.Predicate;
075import com.google.common.collect.Lists;
076import com.vaadin.event.LayoutEvents.LayoutClickEvent;
077import com.vaadin.event.LayoutEvents.LayoutClickListener;
078import com.vaadin.server.FontAwesome;
079import com.vaadin.server.Resource;
080import com.vaadin.ui.AbstractComponent;
081import com.vaadin.ui.Button.ClickEvent;
082import com.vaadin.ui.Button.ClickListener;
083import com.vaadin.ui.Component;
084import com.vaadin.ui.ComponentContainer;
085import com.vaadin.ui.CssLayout;
086import com.vaadin.ui.MenuBar;
087import com.vaadin.ui.MenuBar.Command;
088import com.vaadin.ui.MenuBar.MenuItem;
089import com.vaadin.ui.Notification;
090import com.vaadin.ui.Notification.Type;
091import com.vaadin.ui.UI;
092import com.vaadin.ui.Window;
093
094/**
095 * Manages the sitemap tree in the 'locale comparison' view in the sitemap editor.<p>
096 */
097public class CmsSitemapTreeController {
098
099    /**
100     * The context used for child dialogs.<p>
101     */
102    public class DialogContext implements I_CmsDialogContext {
103
104        /** The tree node. */
105        private CmsSitemapTreeNode m_node;
106
107        /** The resource. */
108        private CmsResource m_resource;
109
110        /**
111         * Creates a new instance.<p>
112         *
113         * @param resource the resource
114         * @param node the tree node
115         */
116        public DialogContext(CmsResource resource, CmsSitemapTreeNode node) {
117            m_resource = resource;
118            m_node = node;
119        }
120
121        /**
122         * Closes the dialog window.<p>
123         */
124        public void closeWindow() {
125
126            if (m_window != null) {
127                m_window.close();
128                m_window = null;
129            }
130        }
131
132        /**
133         * @see org.opencms.ui.I_CmsDialogContext#error(java.lang.Throwable)
134         */
135        public void error(Throwable error) {
136
137            getTreeControllerLog().error(error.getLocalizedMessage(), error);
138            CmsErrorDialog.showErrorDialog(error);
139        }
140
141        /**
142         * @see org.opencms.ui.I_CmsDialogContext#finish(org.opencms.file.CmsProject, java.lang.String)
143         */
144        public void finish(CmsProject project, String siteRoot) {
145
146            closeWindow();
147        }
148
149        /**
150         * @see org.opencms.ui.I_CmsDialogContext#finish(java.util.Collection)
151         */
152        @SuppressWarnings("synthetic-access")
153        public void finish(Collection<CmsUUID> result) {
154
155            closeWindow();
156            if (result.isEmpty()) {
157                return;
158            }
159            if (m_node != null) {
160                if (m_node == m_currentRootNode) {
161                    m_localeContext.refreshAll();
162                } else {
163                    updateNode(m_node);
164                }
165            }
166        }
167
168        /**
169         * @see org.opencms.ui.I_CmsDialogContext#focus(org.opencms.util.CmsUUID)
170         */
171        public void focus(CmsUUID structureId) {
172            // not used
173        }
174
175        /**
176         * @see org.opencms.ui.I_CmsDialogContext#getAllStructureIdsInView()
177         */
178        public List<CmsUUID> getAllStructureIdsInView() {
179
180            return null;
181        }
182
183        /**
184         * @see org.opencms.ui.I_CmsDialogContext#getAppId()
185         */
186        public String getAppId() {
187
188            return CmsSitemapEditorConfiguration.APP_ID;
189        }
190
191        /**
192         * @see org.opencms.ui.I_CmsDialogContext#getCms()
193         */
194        public CmsObject getCms() {
195
196            return A_CmsUI.getCmsObject();
197        }
198
199        /**
200         * @see org.opencms.ui.I_CmsDialogContext#getContextType()
201         */
202        public ContextType getContextType() {
203
204            return null;
205        }
206
207        /**
208         * @see org.opencms.ui.I_CmsDialogContext#getResources()
209         */
210        public List<CmsResource> getResources() {
211
212            return Arrays.asList(m_resource);
213        }
214
215        /**
216         * @see org.opencms.ui.I_CmsDialogContext#navigateTo(java.lang.String)
217         */
218        public void navigateTo(String appId) {
219            // not used
220        }
221
222        /**
223         * @see org.opencms.ui.I_CmsDialogContext#onViewChange()
224         */
225        public void onViewChange() {
226            // do nothing
227        }
228
229        /**
230         * @see org.opencms.ui.I_CmsDialogContext#reload()
231         */
232        public void reload() {
233
234            // do nothing
235
236        }
237
238        /**
239         * @see org.opencms.ui.I_CmsDialogContext#setWindow(com.vaadin.ui.Window)
240         */
241        public void setWindow(Window window) {
242
243            m_window = window;
244        }
245
246        /**
247         * @see org.opencms.ui.I_CmsDialogContext#start(java.lang.String, com.vaadin.ui.Component)
248         */
249        public void start(String title, Component dialog) {
250
251            start(title, dialog, DialogWidth.narrow);
252        }
253
254        /**
255         * @see org.opencms.ui.I_CmsDialogContext#start(java.lang.String, com.vaadin.ui.Component, org.opencms.ui.components.CmsBasicDialog.DialogWidth)
256         */
257        public void start(String title, Component dialog, DialogWidth width) {
258
259            if (dialog != null) {
260                m_window = CmsBasicDialog.prepareWindow(width);
261                m_window.setCaption(title);
262                m_window.setContent(dialog);
263                UI.getCurrent().addWindow(m_window);
264                if (dialog instanceof CmsBasicDialog) {
265                    ((CmsBasicDialog)dialog).initActionHandler(m_window);
266                }
267            }
268        }
269
270        /**
271         * @see org.opencms.ui.I_CmsDialogContext#updateUserInfo()
272         */
273        public void updateUserInfo() {
274
275            // not supported
276        }
277    }
278
279    /**
280     * Copy menu entry.
281     */
282    class EntryCopy implements I_CmsSimpleContextMenuEntry<MenuContext> {
283
284        /**
285         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
286         */
287        public void executeAction(MenuContext context) {
288
289            openPageCopyDialog(context.getNode(), context.getData());
290
291        }
292
293        /**
294         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
295         */
296        public String getTitle(Locale locale) {
297
298            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_COPY_PAGE_0);
299        }
300
301        /**
302         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
303         */
304        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
305
306            return visibleIfTrue(context.getData().isCopyable());
307
308        }
309    }
310
311    /**
312     * Menu entry for opening the explorer.<p>
313     */
314    class EntryExplorer implements I_CmsSimpleContextMenuEntry<MenuContext> {
315
316        /**
317         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
318         */
319        public void executeAction(MenuContext context) {
320
321            String link = CmsCoreService.getVaadinWorkplaceLink(
322                A_CmsUI.getCmsObject(),
323                context.getData().getResource().getStructureId());
324            A_CmsUI.get().getPage().setLocation(link);
325
326        }
327
328        /**
329         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
330         */
331        public String getTitle(Locale locale) {
332
333            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_EXPLORER_0);
334        }
335
336        /**
337         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
338         */
339        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
340
341            return visibleIfTrue(true);
342        }
343
344    }
345
346    /**
347     * Menu entry for opening the info dialog.<p>
348     */
349    class EntryInfo implements I_CmsSimpleContextMenuEntry<MenuContext> {
350
351        /**
352
353         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
354         */
355        public void executeAction(MenuContext context) {
356
357            CmsResourceInfoAction infoAction = new CmsResourceInfoAction();
358            infoAction.executeAction(new DialogContext(context.getData().getResource(), context.getNode()));
359        }
360
361        /**
362         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
363         */
364        public String getTitle(Locale locale) {
365
366            return CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCE_INFO_0);
367        }
368
369        /**
370         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
371         */
372        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
373
374            return visibleIfTrue(true);
375        }
376
377    }
378
379    /**
380     * Menu entry for opening the 'LInk locale' dialog.<p>
381     */
382    class EntryLink implements I_CmsSimpleContextMenuEntry<MenuContext> {
383
384        /**
385         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
386         */
387        @SuppressWarnings("synthetic-access")
388        public void executeAction(MenuContext data) {
389
390            try {
391                DialogContext dialogContext = new DialogContext(
392                    A_CmsUI.getCmsObject().readResource(
393                        data.getData().getClientEntry().getId(),
394                        CmsResourceFilter.IGNORE_EXPIRATION),
395                    data.getNode());
396                CmsLocaleLinkTargetSelectionDialog dialog = new CmsLocaleLinkTargetSelectionDialog(
397                    dialogContext,
398                    m_localeContext);
399                dialogContext.start(
400                    CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_LINK_LOCALE_VARIANT_0),
401                    dialog,
402                    DialogWidth.narrow);
403            } catch (CmsException e) {
404                LOG.error(e.getLocalizedMessage(), e);
405                CmsErrorDialog.showErrorDialog(e);
406            }
407        }
408
409        /**
410         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
411         */
412        public String getTitle(Locale locale) {
413
414            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_LINK_LOCALE_VARIANT_0);
415        }
416
417        /**
418         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
419         */
420        @SuppressWarnings("synthetic-access")
421        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
422
423            return activeIfTrue(
424                !context.getData().isLinked()
425                    && !context.getData().isMarkedNoTranslation(m_localeContext.getComparisonLocale()));
426        }
427    }
428
429    /**
430     * Context menu entry for the 'Do not translate' mark.<p>
431     */
432    class EntryMark implements I_CmsSimpleContextMenuEntry<MenuContext> {
433
434        /**
435         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
436         */
437        @SuppressWarnings("synthetic-access")
438        public void executeAction(MenuContext context) {
439
440            CmsSitemapTreeNodeData entry = context.getData();
441            CmsSitemapTreeNode node = context.getNode();
442
443            CmsObject cms = A_CmsUI.getCmsObject();
444            CmsLockActionRecord actionRecord = null;
445            CmsResource fileToModify2 = null;
446            try {
447
448                CmsResource primary = A_CmsUI.getCmsObject().readResource(
449                    entry.getClientEntry().getId(),
450                    CmsResourceFilter.IGNORE_EXPIRATION);
451                if (primary.isFolder()) {
452                    CmsResource defaultFile = A_CmsUI.getCmsObject().readDefaultFile(
453                        primary,
454                        CmsResourceFilter.IGNORE_EXPIRATION);
455                    if (defaultFile != null) {
456                        primary = defaultFile;
457                    }
458                }
459                final CmsResource primaryFinal = primary;
460
461                fileToModify2 = primaryFinal;
462                if (fileToModify2.isFolder()) {
463                    try {
464                        fileToModify2 = A_CmsUI.getCmsObject().readDefaultFile(
465                            fileToModify2,
466                            CmsResourceFilter.IGNORE_EXPIRATION);
467                    } catch (CmsException e) {
468                        LOG.error(e.getLocalizedMessage(), e);
469                    }
470                }
471
472                actionRecord = CmsLockUtil.ensureLock(cms, fileToModify2);
473                m_localeContext.getComparisonLocale().toString();
474                CmsProperty prop = cms.readPropertyObject(
475                    fileToModify2,
476                    CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
477                    false);
478                String propValue = prop.getValue();
479                if (propValue == null) {
480                    propValue = ""; // make getLocales not return null
481                }
482                List<Locale> currentLocales = CmsLocaleManager.getLocales(propValue);
483                if (!currentLocales.contains(m_localeContext.getComparisonLocale())) {
484                    currentLocales.add(m_localeContext.getComparisonLocale());
485                    String newPropValue = Joiner.on(",").join(currentLocales);
486                    CmsProperty newProp = new CmsProperty(
487                        CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
488                        newPropValue,
489                        null);
490                    cms.writePropertyObjects(fileToModify2, Arrays.asList(newProp));
491                    DialogContext dialogContext = new DialogContext(
492                        A_CmsUI.getCmsObject().readResource(
493                            entry.getClientEntry().getId(),
494                            CmsResourceFilter.IGNORE_EXPIRATION),
495                        node);
496                    dialogContext.finish(Arrays.asList(fileToModify2.getStructureId()));
497
498                }
499
500            } catch (CmsException e) {
501                LOG.error(e.getLocalizedMessage(), e);
502                CmsErrorDialog.showErrorDialog(e);
503
504            } finally {
505                if ((actionRecord != null) && (actionRecord.getChange() == LockChange.locked)) {
506                    try {
507                        cms.unlockResource(fileToModify2);
508                    } catch (CmsException e) {
509                        LOG.error(e.getLocalizedMessage(), e);
510                        CmsErrorDialog.showErrorDialog(e);
511                    }
512                }
513            }
514
515        }
516
517        /**
518         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
519         */
520        public String getTitle(Locale locale) {
521
522            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_ADD_DONT_TRANSLATE_0);
523        }
524
525        /**
526         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
527         */
528        @SuppressWarnings("synthetic-access")
529        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
530
531            CmsSitemapTreeNodeData entry = context.getData();
532            boolean result = context.isMainLocale()
533                && !entry.isMarkedNoTranslation(m_localeContext.getComparisonLocale())
534                && !entry.isLinked();
535            return visibleIfTrue(result);
536
537        }
538
539    }
540
541    /**
542     * 'Open page' menu entry.<p>
543     */
544    class EntryOpen implements I_CmsSimpleContextMenuEntry<MenuContext> {
545
546        /**
547         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
548         */
549        @SuppressWarnings("synthetic-access")
550        public void executeAction(MenuContext context) {
551
552            openTargetPage((CmsSitemapTreeNodeData)(context.getNode().getData()), false);
553
554        }
555
556        /**
557         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
558         */
559        public String getTitle(Locale locale) {
560
561            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_OPEN_PAGE_0);
562
563        }
564
565        /**
566         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
567         */
568        public CmsMenuItemVisibilityMode getVisibility(MenuContext data) {
569
570            return visibleIfTrue(true);
571        }
572
573    }
574
575    /**
576     * 'Properties' menu entry.<p>
577     */
578    class EntryProperties implements I_CmsSimpleContextMenuEntry<MenuContext> {
579
580        /**
581         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
582         */
583        @SuppressWarnings("synthetic-access")
584        public void executeAction(MenuContext context) {
585
586            ((CmsSitemapUI)A_CmsUI.get()).getSitemapExtension().openPropertyDialog(
587                context.getData().getResource().getStructureId(),
588                m_root.getStructureId());
589        }
590
591        /**
592         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
593         */
594        public String getTitle(Locale locale) {
595
596            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_PROPERTIES_0);
597
598        }
599
600        /**
601         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
602         */
603        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
604
605            return visibleIfTrue(true);
606        }
607
608    }
609
610    /**
611     * 'Remove mark' menu entry.<p>
612     */
613    class EntryRemoveMark implements I_CmsSimpleContextMenuEntry<MenuContext> {
614
615        /**
616         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
617         */
618        @SuppressWarnings("synthetic-access")
619        public void executeAction(MenuContext context) {
620
621            CmsSitemapTreeNodeData entry = context.getData();
622            CmsSitemapTreeNode node = context.getNode();
623
624            CmsObject cms = A_CmsUI.getCmsObject();
625            CmsLockActionRecord actionRecord = null;
626            CmsResource fileToModify2 = null;
627            try {
628                CmsResource primary = A_CmsUI.getCmsObject().readResource(
629                    entry.getClientEntry().getId(),
630                    CmsResourceFilter.IGNORE_EXPIRATION);
631                if (primary.isFolder()) {
632                    CmsResource defaultFile = A_CmsUI.getCmsObject().readDefaultFile(
633                        primary,
634                        CmsResourceFilter.IGNORE_EXPIRATION);
635                    if (defaultFile != null) {
636                        primary = defaultFile;
637                    }
638                }
639                final CmsResource primaryFinal = primary;
640
641                fileToModify2 = primaryFinal;
642                if (fileToModify2.isFolder()) {
643                    try {
644                        fileToModify2 = A_CmsUI.getCmsObject().readDefaultFile(
645                            fileToModify2,
646                            CmsResourceFilter.IGNORE_EXPIRATION);
647                    } catch (CmsException e) {
648                        LOG.error(e.getLocalizedMessage(), e);
649                    }
650                }
651
652                actionRecord = CmsLockUtil.ensureLock(cms, fileToModify2);
653                m_localeContext.getComparisonLocale().toString();
654                CmsProperty prop = cms.readPropertyObject(
655                    fileToModify2,
656                    CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
657                    false);
658                String propValue = prop.getValue();
659                if (propValue == null) {
660                    propValue = ""; // make getLocales not return null
661                }
662                List<Locale> currentLocales = CmsLocaleManager.getLocales(propValue);
663                if (currentLocales.contains(m_localeContext.getComparisonLocale())) {
664                    currentLocales.remove(m_localeContext.getComparisonLocale());
665                    String newPropValue = Joiner.on(",").join(currentLocales);
666                    CmsProperty newProp = new CmsProperty(
667                        CmsPropertyDefinition.PROPERTY_LOCALE_NOTRANSLATION,
668                        newPropValue,
669                        null);
670                    cms.writePropertyObjects(primaryFinal, Arrays.asList(newProp));
671                    DialogContext dialogContext = new DialogContext(
672                        A_CmsUI.getCmsObject().readResource(
673                            entry.getClientEntry().getId(),
674                            CmsResourceFilter.IGNORE_EXPIRATION),
675                        node);
676                    dialogContext.finish(Arrays.asList(fileToModify2.getStructureId()));
677                }
678
679            } catch (CmsException e) {
680                LOG.error(e.getLocalizedMessage(), e);
681                CmsErrorDialog.showErrorDialog(e);
682
683            } finally {
684                if ((actionRecord != null) && (actionRecord.getChange() == LockChange.locked)) {
685                    try {
686                        cms.unlockResource(fileToModify2);
687                    } catch (CmsException e) {
688                        LOG.error(e.getLocalizedMessage(), e);
689                        CmsErrorDialog.showErrorDialog(e);
690                    }
691                }
692            }
693
694        }
695
696        /**
697         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
698         */
699        public String getTitle(Locale locale) {
700
701            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_REMOVE_DONT_TRANSLATE_0);
702        }
703
704        /**
705         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
706         */
707        @SuppressWarnings("synthetic-access")
708        public CmsMenuItemVisibilityMode getVisibility(MenuContext context) {
709
710            boolean result = context.isMainLocale()
711                && context.getData().isMarkedNoTranslation(m_localeContext.getComparisonLocale());
712            return visibleIfTrue(result);
713
714        }
715    }
716
717    /**
718     * 'Unlink' menu entry.<p>
719     */
720    class EntryUnlink implements I_CmsSimpleContextMenuEntry<MenuContext> {
721
722        /**
723         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#executeAction(java.lang.Object)
724         */
725        @SuppressWarnings("synthetic-access")
726        public void executeAction(MenuContext context) {
727
728            try {
729                CmsResource secondary = context.getData().getLinkedResource();
730                DialogContext dialogContext = new DialogContext(
731                    A_CmsUI.getCmsObject().readResource(
732                        context.getData().getClientEntry().getId(),
733                        CmsResourceFilter.IGNORE_EXPIRATION),
734                    context.getNode());
735                CmsUnlinkDialog dialog = new CmsUnlinkDialog(dialogContext, secondary);
736                dialogContext.start(
737                    CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_UNLINK_LOCALE_VARIANT_0),
738                    dialog,
739                    DialogWidth.wide);
740            } catch (CmsException e) {
741                LOG.error(e.getLocalizedMessage(), e);
742                CmsErrorDialog.showErrorDialog(e);
743            }
744        }
745
746        /**
747         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getTitle(java.util.Locale)
748         */
749        public String getTitle(Locale locale) {
750
751            return CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_UNLINK_LOCALE_VARIANT_0);
752        }
753
754        /**
755         * @see org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry#getVisibility(java.lang.Object)
756         */
757        @SuppressWarnings("synthetic-access")
758        public CmsMenuItemVisibilityMode getVisibility(final MenuContext context) {
759
760            if (!context.getData().isLinked()) {
761                return visibleIfTrue(false);
762            }
763            try {
764                CmsResource primary = A_CmsUI.getCmsObject().readResource(
765                    context.getData().getClientEntry().getId(),
766                    CmsResourceFilter.IGNORE_EXPIRATION);
767                if (primary.isFolder()) {
768                    CmsResource defaultFile = A_CmsUI.getCmsObject().readDefaultFile(
769                        primary,
770                        CmsResourceFilter.IGNORE_EXPIRATION);
771                    if (defaultFile != null) {
772                        primary = defaultFile;
773                    }
774                }
775                CmsLocaleGroupService groupService = A_CmsUI.getCmsObject().getLocaleGroupService();
776                Locale mainLocale = groupService.getMainLocale(m_localeContext.getRoot().getRootPath());
777                int mainLocaleCount = 0;
778                for (Locale testLocale : Arrays.asList(
779                    m_localeContext.getRootLocale(),
780                    m_localeContext.getComparisonLocale())) {
781                    mainLocaleCount += mainLocale.equals(testLocale) ? 1 : 0;
782                }
783                return visibleIfTrue(mainLocaleCount == 1);
784            } catch (Exception e) {
785                return visibleIfTrue(false);
786
787            }
788
789        }
790    }
791
792    /**
793     * Context object for the context menu.<p>
794     */
795    class MenuContext {
796
797        /** The tree node data. */
798        private CmsSitemapTreeNodeData m_data;
799
800        /** The tree node widget. */
801        private CmsSitemapTreeNode m_node;
802
803        /**
804         * Creates a new instance.<p>
805         *
806         * @param data the sitemap tree data
807         * @param node the tree node widget
808         */
809        public MenuContext(CmsSitemapTreeNodeData data, CmsSitemapTreeNode node) {
810            m_node = node;
811            m_data = data;
812        }
813
814        /**
815         * Gets the tree node data.<p>
816         *
817         * @return the tree node data
818         */
819        public CmsSitemapTreeNodeData getData() {
820
821            return m_data;
822        }
823
824        /**
825         * Gets the tree node widget.<p>
826         *
827         * @return the tree node widget
828         */
829        public CmsSitemapTreeNode getNode() {
830
831            return m_node;
832        }
833
834        /**
835         * Checks if the currently selected locale is the main locale.<p>
836         *
837         * @return true if we are in the main locale
838         */
839        @SuppressWarnings("synthetic-access")
840        public boolean isMainLocale() {
841
842            return m_localeContext.getRootLocale().equals(
843                A_CmsUI.getCmsObject().getLocaleGroupService().getMainLocale(m_localeContext.getRoot().getRootPath()));
844        }
845
846    }
847
848    /** Default width for linked items displayed on the right side of tree items. */
849    public static final int RHS_WIDTH = 420;
850
851    /** The log isntance for this class. */
852    private static final Log LOG = CmsLog.getLog(CmsSitemapTreeController.class);
853
854    /** The context menu. */
855    CmsContextMenu m_menu = new CmsContextMenu();
856
857    /** The currently opened window. */
858    Window m_window;
859
860    /** Map of already loaded nodes. */
861    private IdentityHashMap<CmsSitemapTreeNode, Void> m_alreadyLoaded = new IdentityHashMap<>();
862
863    /** Current root node widget. */
864    private CmsSitemapTreeNode m_currentRootNode;
865
866    /** The locale context. */
867    private I_CmsLocaleCompareContext m_localeContext;
868
869    /** The resource corresponding to the tree's root. */
870    private CmsResource m_root;
871
872    /** The tree data provider. */
873    private CmsSitemapTreeDataProvider m_treeDataProvider;
874
875    /**
876     * Creates a new instance.<p>
877     *
878     * @param cms the CMS  context
879     * @param root the tree's root resource
880     * @param context the locale comparison context
881     * @param parent the parent widget in which the tree will be rendered
882     */
883    public CmsSitemapTreeController(
884        CmsObject cms,
885        CmsResource root,
886        I_CmsLocaleCompareContext context,
887        Component parent) {
888        m_treeDataProvider = new CmsSitemapTreeDataProvider(cms, root, context);
889        m_localeContext = context;
890        m_root = root;
891        m_menu.extend((AbstractComponent)parent);
892
893    }
894
895    /**
896     * Returns VISIBILITY_ACTIVE if the given parameter is true, and VISIBILITY_INACTIVE otherwise.<p>
897     *
898     * @param condition a boolean value
899     * @return the visibility based on the condition value
900     */
901    public static CmsMenuItemVisibilityMode activeIfTrue(boolean condition) {
902
903        return condition ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE : CmsMenuItemVisibilityMode.VISIBILITY_INACTIVE;
904    }
905
906    /**
907     * Returns VISIBILITY_ACTIVE if the given parameter is true, and VISIBILITY_INVISIBLE otherwise.<p>
908     *
909     * @param condition a boolean value
910     * @return the visibility based on the condition value
911     */
912    public static CmsMenuItemVisibilityMode visibleIfTrue(boolean condition) {
913
914        return condition ? CmsMenuItemVisibilityMode.VISIBILITY_ACTIVE : CmsMenuItemVisibilityMode.VISIBILITY_INVISIBLE;
915    }
916
917    /**
918     * Creates a sitemap tree node widget from a tree node bean.<p>
919     *
920     * @param entry the tree node bean
921     * @return the tree node widget
922     */
923    public CmsSitemapTreeNode createNode(final CmsSitemapTreeNodeData entry) {
924
925        final CmsSitemapTreeNode node = new CmsSitemapTreeNode();
926
927        node.addLayoutClickListener(new LayoutClickListener() {
928
929            private static final long serialVersionUID = 1L;
930
931            @SuppressWarnings("synthetic-access")
932            public void layoutClick(LayoutClickEvent event) {
933
934                Component currentComponent = event.getClickedComponent();
935                if (currentComponent != null) {
936                    boolean linked = false;
937                    do {
938                        currentComponent = currentComponent.getParent();
939                        if ((currentComponent != null)
940                            && "linked".equals(((AbstractComponent)currentComponent).getData())) {
941                            linked = true;
942                        }
943                        if (event.getClickedComponent() instanceof CmsResourceIcon) {
944                            if (currentComponent == node) {
945                                openTargetPage((CmsSitemapTreeNodeData)(node.getData()), linked);
946                            } else if (currentComponent instanceof CmsSitemapTreeNode) {
947                                break;
948                            }
949                        }
950                    } while (currentComponent != null);
951                }
952
953            }
954
955        });
956        Resource icon = CmsResourceIcon.getSitemapResourceIcon(
957            A_CmsUI.getCmsObject(),
958            entry.getResource(),
959            IconMode.localeCompare);
960        CmsResourceInfo info = new CmsResourceInfo(
961            entry.getClientEntry().getTitle(),
962            entry.getClientEntry().getSitePath(),
963            icon);
964        info = CmsResourceInfo.createSitemapResourceInfo(
965            entry.getResource(),
966            OpenCms.getSiteManager().getSiteForRootPath(m_localeContext.getRoot().getRootPath()));
967        info.getResourceIcon().addStyleName(OpenCmsTheme.POINTER);
968        info.getResourceIcon().setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_OPEN_PAGE_0));
969
970        if (entry.getClientEntry().isHiddenNavigationEntry()) {
971            info.addStyleName(OpenCmsTheme.RESOURCE_INFO_WEAK);
972        }
973        final MenuBar menu = new MenuBar();
974        boolean noTranslation = false;
975        noTranslation = entry.isMarkedNoTranslation(m_localeContext.getComparisonLocale());
976
977        final MenuItem main = menu.addItem("", null);
978        main.setIcon(FontOpenCms.CONTEXT_MENU);
979        CssLayout rightSide = new CssLayout();
980        info.setButtonWidget(rightSide);
981        rightSide.addComponent(menu);
982        main.setCommand(new Command() {
983
984            /** Serial version id. */
985            private static final long serialVersionUID = 1L;
986
987            public void menuSelected(MenuItem selectedItem) {
988
989                List<I_CmsSimpleContextMenuEntry<MenuContext>> entries = Arrays.asList(
990
991                    new EntryOpen(),
992                    new EntryExplorer(),
993                    new EntryProperties(),
994                    new EntryLink(),
995                    new EntryUnlink(),
996                    new EntryMark(),
997                    new EntryRemoveMark(),
998                    new EntryCopy(),
999                    new EntryInfo());
1000
1001                MenuContext context = new MenuContext(entry, node);
1002                m_menu.setEntries(entries, context);
1003                m_menu.open(menu);
1004
1005            }
1006
1007        });
1008
1009        menu.addStyleName("borderless o-toolbar-button o-resourceinfo-toolbar");
1010        if (entry.isLinked()) {
1011            CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(m_localeContext.getRoot().getRootPath());
1012            CmsResourceInfo linkedInfo = CmsResourceInfo.createSitemapResourceInfo(
1013                readSitemapEntryFolderIfPossible(entry.getLinkedResource()),
1014                site);
1015            linkedInfo.addStyleName(OpenCmsTheme.RESOURCE_INFO_DIRECTLINK);
1016            rightSide.addComponent(linkedInfo, 0);
1017            linkedInfo.setWidth(RHS_WIDTH + "px");
1018            node.setContent(info);
1019            linkedInfo.setData("linked"); // Data used by click handler to distinguish clicked resource icons
1020            linkedInfo.getResourceIcon().setDescription(
1021                CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_OPEN_PAGE_0));
1022            linkedInfo.getResourceIcon().addStyleName(OpenCmsTheme.POINTER);
1023        } else {
1024            if (noTranslation) {
1025                CmsResourceInfo noTranslationInfo = new CmsResourceInfo();
1026                String topMessage = CmsVaadinUtils.getMessageText(Messages.GUI_LOCALECOMPARE_NO_TRANSLATION_TOP_0);
1027                String bottomMessage = CmsVaadinUtils.getMessageText(
1028                    Messages.GUI_LOCALECOMPARE_NO_TRANSLATION_BOTTOM_0);
1029                noTranslationInfo.getTopLine().setValue(topMessage);
1030                noTranslationInfo.getBottomLine().setValue(bottomMessage);
1031                noTranslationInfo.getResourceIcon().setValue(
1032                    "<span class=\""
1033                        + OpenCmsTheme.RESOURCE_ICON
1034                        + " "
1035                        + OpenCmsTheme.NO_TRANSLATION_ICON
1036                        + "\">"
1037                        + FontAwesome.BAN.getHtml()
1038                        + "</span>");
1039                noTranslationInfo.addStyleName(OpenCmsTheme.RESOURCE_INFO_DIRECTLINK);
1040                noTranslationInfo.setWidth(RHS_WIDTH + "px");
1041                rightSide.addComponent(noTranslationInfo, 0);
1042            }
1043            node.setContent(info);
1044        }
1045
1046        if (entry.hasNoChildren()) {
1047            node.setOpen(true);
1048            node.setOpenerVisible(false);
1049        }
1050        node.setData(entry);
1051        return node;
1052
1053    }
1054
1055    /**
1056     * Creates the root node of the tree.<p>
1057     *
1058     * @return the root node of the tree
1059     */
1060    public CmsSitemapTreeNode createRootNode() {
1061
1062        m_currentRootNode = createNode(m_treeDataProvider.getRoot());
1063        return m_currentRootNode;
1064    }
1065
1066    /**
1067     * Gets the resource corresponding to the tree's root.<p>
1068     *
1069     * @return the resource for the root node
1070     */
1071    public CmsResource getRoot() {
1072
1073        return m_root;
1074    }
1075
1076    /**
1077     * Initializes the event handlers for a tree node widget.<p>
1078     *
1079     * @param node the node for which to initialize the event handlers
1080     */
1081    public void initEventHandlers(final CmsSitemapTreeNode node) {
1082
1083        node.getOpener().addClickListener(new ClickListener() {
1084
1085            private static final long serialVersionUID = 1L;
1086
1087            public void buttonClick(ClickEvent event) {
1088
1089                CmsSitemapTreeController.this.onClickOpen(node);
1090            }
1091        });
1092    }
1093
1094    /**
1095     * Called when the user clicks on the 'opener' icon of a sitemap tree entry.<p>
1096     *
1097     * @param node the sitemap node widget
1098     */
1099    public void onClickOpen(CmsSitemapTreeNode node) {
1100
1101        if (node.isOpen()) {
1102            node.setOpen(false);
1103        } else {
1104            if (!m_alreadyLoaded.containsKey(node)) {
1105                Object nodeData = node.getData();
1106                List<CmsSitemapTreeNodeData> children = m_treeDataProvider.getChildren(
1107                    (CmsSitemapTreeNodeData)nodeData);
1108                m_alreadyLoaded.put(node, null);
1109                if (children.isEmpty()) {
1110                    node.setOpenerVisible(false);
1111                } else {
1112                    for (CmsSitemapTreeNodeData child : children) {
1113                        CmsSitemapTreeNode childNode = createNode(child);
1114                        childNode.setData(child);
1115                        initEventHandlers(childNode);
1116                        node.getChildren().addComponent(childNode);
1117                    }
1118                }
1119            }
1120            node.setOpen(true);
1121        }
1122    }
1123
1124    /**
1125     * Opens the page copy dialog for a tree entry.<p>
1126     *
1127     * @param node the tree node widget
1128     * @param entry the tree entry
1129     */
1130    public void openPageCopyDialog(CmsSitemapTreeNode node, CmsSitemapTreeNodeData entry) {
1131
1132        CmsObject cms = A_CmsUI.getCmsObject();
1133        try {
1134            CmsResource resource = cms.readResource(
1135                entry.getClientEntry().getId(),
1136                CmsResourceFilter.IGNORE_EXPIRATION);
1137            DialogContext context = new DialogContext(resource, node);
1138            CmsCopyPageDialog dialog = new CmsCopyPageDialog(context);
1139            String title = CmsVaadinUtils.getMessageText(Messages.GUI_COPYPAGE_DIALOG_TITLE_0);
1140            context.start(title, dialog);
1141        } catch (CmsException e) {
1142            LOG.error(e.getLocalizedMessage(), e);
1143            CmsErrorDialog.showErrorDialog(e);
1144        }
1145    }
1146
1147    /**
1148     * Updates a sitemap node widget after the resource it corresponds to has changed.<p>
1149     *
1150     * @param node the sitemap node
1151     */
1152    public void updateNode(CmsSitemapTreeNode node) {
1153
1154        CmsSitemapTreeNodeData data = (CmsSitemapTreeNodeData)node.getData();
1155        try {
1156            CmsSitemapTreeNodeData changedData = m_treeDataProvider.getData(
1157                A_CmsUI.getCmsObject().readResource(
1158                    data.getClientEntry().getId(),
1159                    CmsResourceFilter.IGNORE_EXPIRATION));
1160            CmsSitemapTreeNode changedNode = createNode(changedData);
1161            initEventHandlers(changedNode);
1162            ComponentContainer parent = (ComponentContainer)(node.getParent());
1163            parent.replaceComponent(node, changedNode);
1164        } catch (CmsException e) {
1165            LOG.error(e.getLocalizedMessage(), e);
1166        }
1167    }
1168
1169    /**
1170     * Updates the tree node for the resource with the given structure id, if it exists.<p>
1171     *
1172     * @param id the structure id of a resource
1173     */
1174    public void updateNodeForId(final CmsUUID id) {
1175
1176        final List<CmsSitemapTreeNode> nodes = Lists.newArrayList();
1177        CmsVaadinUtils.visitDescendants(m_currentRootNode, new Predicate<Component>() {
1178
1179            public boolean apply(Component input) {
1180
1181                if (input instanceof CmsSitemapTreeNode) {
1182                    CmsSitemapTreeNode node = (CmsSitemapTreeNode)input;
1183                    CmsSitemapTreeNodeData data = (CmsSitemapTreeNodeData)node.getData();
1184                    if (data.getResource().getStructureId().equals(id)) {
1185                        nodes.add(node);
1186                        return false;
1187                    }
1188                }
1189                return true;
1190            }
1191        });
1192        if (nodes.size() == 1) {
1193            updateNode(nodes.get(0));
1194        }
1195
1196    }
1197
1198    /**
1199     * If the given resource is the default file of a sitmeap entry folder, then returns that
1200     * folder, else the original file.<p>
1201     *
1202     * @param resource a resource
1203     * @return the resource or its parent folder
1204     */
1205    protected CmsResource readSitemapEntryFolderIfPossible(CmsResource resource) {
1206
1207        CmsObject cms = A_CmsUI.getCmsObject();
1208        try {
1209            if (resource.isFolder()) {
1210                return resource;
1211            }
1212            CmsResource parent = cms.readParentFolder(resource.getStructureId());
1213            CmsResource defaultFile = cms.readDefaultFile(parent, CmsResourceFilter.IGNORE_EXPIRATION);
1214            if ((defaultFile != null) && defaultFile.equals(resource)) {
1215                return parent;
1216            }
1217            return resource;
1218        } catch (CmsException e) {
1219            LOG.error(e.getLocalizedMessage(), e);
1220            return resource;
1221        }
1222    }
1223
1224    /**
1225     * Gets the logger for the tree controller.<p>
1226     *
1227     * @return the logger
1228     */
1229    Log getTreeControllerLog() {
1230
1231        return LOG;
1232    }
1233
1234    /**
1235     * Opens the page corresponding to a sitemap entry.<p>
1236     *
1237     * @param nodeData the node bean
1238     * @param second true if the user has clicked on the second resource box in a tree node
1239     */
1240    private void openTargetPage(CmsSitemapTreeNodeData nodeData, boolean second) {
1241
1242        CmsUUID id = nodeData.getClientEntry().getId();
1243        CmsUUID defaultFileId = nodeData.getClientEntry().getDefaultFileId();
1244        CmsUUID targetId = defaultFileId;
1245        if (targetId == null) {
1246            targetId = id;
1247        }
1248        try {
1249            CmsResource resource = A_CmsUI.getCmsObject().readResource(targetId, CmsResourceFilter.IGNORE_EXPIRATION);
1250            String link = OpenCms.getLinkManager().substituteLink(A_CmsUI.getCmsObject(), resource);
1251            if (second) {
1252                resource = A_CmsUI.getCmsObject().readResource(
1253                    nodeData.getLinkedResource().getStructureId(),
1254                    CmsResourceFilter.IGNORE_EXPIRATION);
1255                link = OpenCms.getLinkManager().substituteLink(A_CmsUI.getCmsObject(), resource);
1256            }
1257
1258            String mySiteRoot = A_CmsUI.getCmsObject().getRequestContext().getSiteRoot();
1259            final boolean sameSite = mySiteRoot.equals(OpenCms.getSiteManager().getSiteRoot(resource.getRootPath()));
1260
1261            if (sameSite) {
1262                A_CmsUI.get().getPage().setLocation(link);
1263            } else {
1264                String message = CmsVaadinUtils.getMessageText(
1265                    Messages.GUI_LOCALECOMPARE_SHOW_WRONGSITE_1,
1266                    resource.getRootPath());
1267
1268                Notification.show(message, Type.ERROR_MESSAGE);
1269            }
1270        } catch (CmsException e) {
1271            LOG.error(e.getLocalizedMessage(), e);
1272        }
1273
1274    }
1275
1276}