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