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.dialogs;
029
030import org.opencms.ade.configuration.CmsADEManager;
031import org.opencms.ade.configuration.CmsElementView;
032import org.opencms.ade.configuration.CmsResourceTypeConfig;
033import org.opencms.ade.containerpage.CmsAddDialogTypeHelper;
034import org.opencms.ade.galleries.shared.CmsResourceTypeBean;
035import org.opencms.ade.galleries.shared.CmsResourceTypeBean.Origin;
036import org.opencms.configuration.preferences.CmsElementViewPreference;
037import org.opencms.db.CmsUserSettings;
038import org.opencms.file.CmsObject;
039import org.opencms.file.CmsResource;
040import org.opencms.i18n.CmsEncoder;
041import org.opencms.jsp.util.CmsJspElFunctions;
042import org.opencms.main.CmsLog;
043import org.opencms.main.OpenCms;
044import org.opencms.module.CmsModule;
045import org.opencms.ui.A_CmsUI;
046import org.opencms.ui.CmsVaadinUtils;
047import org.opencms.ui.FontOpenCms;
048import org.opencms.ui.I_CmsDialogContext;
049import org.opencms.ui.Messages;
050import org.opencms.ui.components.CmsBasicDialog;
051import org.opencms.ui.components.CmsOkCancelActionHandler;
052import org.opencms.ui.components.CmsResourceInfo;
053import org.opencms.ui.components.OpenCmsTheme;
054import org.opencms.ui.components.extensions.CmsMaxHeightExtension;
055import org.opencms.util.CmsUUID;
056import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
057import org.opencms.workplace.explorer.CmsResourceUtil;
058
059import java.util.ArrayList;
060import java.util.Collections;
061import java.util.Comparator;
062import java.util.HashMap;
063import java.util.HashSet;
064import java.util.LinkedHashMap;
065import java.util.List;
066import java.util.Locale;
067import java.util.Map;
068import java.util.Set;
069
070import org.apache.commons.logging.Log;
071
072import com.google.common.base.Predicate;
073import com.google.common.collect.ComparisonChain;
074import com.google.common.collect.Lists;
075import com.google.common.collect.Maps;
076import com.vaadin.event.LayoutEvents.LayoutClickEvent;
077import com.vaadin.event.LayoutEvents.LayoutClickListener;
078import com.vaadin.server.Resource;
079import com.vaadin.server.VaadinService;
080import com.vaadin.shared.ui.ValueChangeMode;
081import com.vaadin.ui.AbstractComponent;
082import com.vaadin.ui.Button;
083import com.vaadin.ui.Button.ClickEvent;
084import com.vaadin.ui.Button.ClickListener;
085import com.vaadin.ui.Component;
086import com.vaadin.ui.Label;
087import com.vaadin.ui.Layout;
088import com.vaadin.ui.TextField;
089import com.vaadin.ui.UI;
090import com.vaadin.ui.declarative.Design;
091import com.vaadin.ui.themes.ValoTheme;
092import com.vaadin.v7.data.Property.ValueChangeEvent;
093import com.vaadin.v7.data.Property.ValueChangeListener;
094import com.vaadin.v7.shared.ui.label.ContentMode;
095import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode;
096import com.vaadin.v7.ui.ComboBox;
097import com.vaadin.v7.ui.VerticalLayout;
098
099public abstract class A_CmsSelectResourceTypeDialog extends CmsBasicDialog {
100
101    /** Default value for the 'default location' check box. */
102    public static final Boolean DEFAULT_LOCATION_DEFAULT = Boolean.TRUE;
103
104    /** Id for the 'All' pseudo-view. */
105    public static final CmsUUID ID_VIEW_ALL = CmsUUID.getConstantUUID("view-all");
106
107    /** Setting name for the standard view. */
108    public static final String SETTING_STANDARD_VIEW = "newDialogStandardView";
109
110    /** The 'All' pseudo-view. */
111    public static final CmsElementView VIEW_ALL = new CmsElementView(ID_VIEW_ALL);
112
113    /** Logger instance for this class. */
114    private static final Log LOG = CmsLog.getLog(CmsNewDialog.class);
115
116    /** Serial version id. */
117    private static final long serialVersionUID = 1L;
118
119    /** The created resource. */
120    protected CmsResource m_createdResource;
121
122    /** The current view id. */
123    protected CmsElementView m_currentView;
124
125    /** The dialog context. */
126    protected I_CmsDialogContext m_dialogContext;
127
128    /** The filter field, wrapped in an array to prevent the declarative layout mechanism from messing with it. */
129    protected TextField[] m_filterField = {new TextField()};
130
131    /** True if we are in filtering mode. */
132    protected boolean m_filterMode;
133
134    /** The filter string (null if not filtering). */
135    protected String m_filterString;
136
137    /** The current folder. */
138    protected CmsResource m_folderResource;
139
140    /** The selected type. */
141    protected CmsResourceTypeBean m_selectedType;
142
143    /** The type helper. */
144    protected CmsAddDialogTypeHelper m_typeHelper;
145
146    /** List of all types. */
147    private List<CmsResourceTypeBean> m_allTypes;
148
149    private Map<CmsResourceTypeBean, CmsResourceInfo> m_resourceInfoMap = new HashMap<CmsResourceTypeBean, CmsResourceInfo>();
150
151    private Runnable m_selectedRunnable;
152
153    /**
154     * Creates a new instance.<p>
155     *
156     * @param folderResource the folder resource
157     * @param context the context
158     */
159    public A_CmsSelectResourceTypeDialog(CmsResource folderResource, I_CmsDialogContext context) {
160
161        m_folderResource = folderResource;
162        m_dialogContext = context;
163
164        Design.read(this);
165        CmsVaadinUtils.visitDescendants(this, new Predicate<Component>() {
166
167            public boolean apply(Component component) {
168
169                component.setCaption(CmsVaadinUtils.localizeString(component.getCaption()));
170                return true;
171            }
172        });
173        getModeToggle().addStyleName(ValoTheme.BUTTON_BORDERLESS);
174        getModeToggle().addStyleName(OpenCmsTheme.TYPE_FILTER_BUTTON);
175
176        CmsUUID initViewId = (CmsUUID)VaadinService.getCurrentRequest().getWrappedSession().getAttribute(
177            SETTING_STANDARD_VIEW);
178        if (initViewId == null) {
179            try {
180                CmsUserSettings settings = new CmsUserSettings(A_CmsUI.getCmsObject());
181                String viewSettingStr = settings.getAdditionalPreference(
182                    CmsElementViewPreference.EXPLORER_PREFERENCE_NAME,
183                    true);
184                if ((viewSettingStr != null) && CmsUUID.isValidUUID(viewSettingStr)) {
185                    initViewId = new CmsUUID(viewSettingStr);
186                }
187            } catch (Exception e) {
188                LOG.error(e.getLocalizedMessage(), e);
189            }
190        }
191        if (initViewId == null) {
192            initViewId = CmsUUID.getNullUUID();
193        }
194        CmsElementView initView = initViews(initViewId);
195
196        getCancelButton().addClickListener(new ClickListener() {
197
198            private static final long serialVersionUID = 1L;
199
200            public void buttonClick(ClickEvent event) {
201
202                finish(new ArrayList<CmsUUID>());
203            }
204        });
205
206        getViewSelector().setNullSelectionAllowed(false);
207        getViewSelector().setTextInputAllowed(false);
208        getVerticalLayout().addLayoutClickListener(new LayoutClickListener() {
209
210            private static final long serialVersionUID = 1L;
211
212            public void layoutClick(LayoutClickEvent event) {
213
214                try {
215                    CmsResourceTypeBean clickedType = (CmsResourceTypeBean)(((AbstractComponent)(event.getChildComponent())).getData());
216                    if (clickedType != null) {
217                        handleSelection(clickedType);
218                    }
219                } catch (ClassCastException e) {
220                    // ignore
221                }
222            }
223        });
224
225        m_filterField[0].setValueChangeMode(ValueChangeMode.LAZY);
226        m_filterField[0].setValueChangeTimeout(200);
227        m_filterField[0].setWidth("250px");
228        m_filterField[0].setIcon(FontOpenCms.FILTER);
229        m_filterField[0].setPlaceholder(
230            org.opencms.ui.apps.Messages.get().getBundle(UI.getCurrent().getLocale()).key(
231                org.opencms.ui.apps.Messages.GUI_EXPLORER_FILTER_0));
232        m_filterField[0].addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON);
233        m_filterField[0].addValueChangeListener(event -> {
234            if (m_filterField[0].getParent() != null) {
235                init(m_currentView, useDefault(), event.getValue());
236            }
237        });
238        getModeToggle().addClickListener(e -> setFilterMode(!m_filterMode));
239        setActionHandler(new CmsOkCancelActionHandler() {
240
241            private static final long serialVersionUID = 1L;
242
243            @Override
244            protected void cancel() {
245
246                finish(new ArrayList<CmsUUID>());
247            }
248
249            @Override
250            protected void ok() {
251
252                // nothing to do
253            }
254        });
255        init(initView, true, null);
256        setFilterModeStyle(false);
257    }
258
259    /**
260     * Notifies the context that the given ids have changed.<p>
261     *
262     * @param ids the ids
263     */
264    public void finish(List<CmsUUID> ids) {
265
266        if (m_selectedRunnable == null) {
267            m_dialogContext.finish(ids);
268            if (ids.size() == 1) {
269                m_dialogContext.focus(ids.get(0));
270
271            }
272        } else {
273            m_selectedRunnable.run();
274        }
275    }
276
277    /**
278     * Gets the Cancel button.
279     *
280     * @return the cancel button
281     */
282    public abstract Button getCancelButton();
283
284    /**
285     * Gets the mode toggle button.
286     *
287     * @return the mode toggle button
288     */
289    public abstract Button getModeToggle();
290
291    /**
292     * Gets the type info widget for the selected type.
293     *
294     * @return the type info widget for the selected type
295     */
296    public CmsResourceInfo getTypeInfoLayout() {
297
298        return m_selectedType != null ? m_resourceInfoMap.get(m_selectedType) : null;
299    }
300
301    public abstract VerticalLayout getVerticalLayout();
302
303    public abstract ComboBox getViewSelector();
304
305    /**
306     * Handles selection of a type.<p>
307     *
308     * @param selectedType the selected type
309     */
310    public abstract void handleSelection(final CmsResourceTypeBean selectedType);
311
312    /**
313     * Initializes and displays the type list for the given view.<p>
314     *
315     * @param view the element view
316     * @param useDefault true if we should use the default location for resource creation
317     */
318    public void init(CmsElementView view, boolean useDefault, String filter) {
319
320        m_currentView = view;
321        m_filterString = filter;
322        if (filter != null) {
323            filter = filter.toLowerCase();
324        }
325        if (!view.getId().equals(getViewSelector().getValue())) {
326            getViewSelector().setValue(view.getId());
327        }
328        getVerticalLayout().removeAllComponents();
329
330        List<CmsResourceTypeBean> typeBeans = m_typeHelper.getPrecomputedTypes(view);
331        if (view.getId().equals(ID_VIEW_ALL)) {
332            typeBeans = m_allTypes;
333        }
334
335        if (typeBeans == null) {
336
337            LOG.warn("precomputed type list is null: " + view.getTitle(A_CmsUI.getCmsObject(), Locale.ENGLISH));
338            return;
339        }
340        if (typeBeans.size() == 0) {
341            Label label = new Label(CmsVaadinUtils.getMessageText(Messages.GUI_NEWRESOURCEDIALOG_NO_TYPES_AVAILABLE_0));
342            getVerticalLayout().addComponent(label);
343            return;
344        }
345        Set<String> nonstandardTypes = new HashSet<>();
346        for (CmsModule module : OpenCms.getModuleManager().getAllInstalledModules()) {
347            if (module.getName().equals(CmsADEManager.MODULE_NAME_ADE_CONFIG)) {
348                continue;
349            }
350            for (CmsExplorerTypeSettings expType : module.getExplorerTypes()) {
351                nonstandardTypes.add(expType.getName());
352            }
353        }
354
355        for (CmsResourceTypeBean type : typeBeans) {
356            final String typeName = type.getType();
357            String title = typeName;
358            String subtitle = getSubtitle(type, useDefault);
359
360            CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(typeName);
361
362            title = CmsVaadinUtils.getMessageText(explorerType.getKey());
363
364            if (filter != null) {
365                String filterable = (title + "\n" + typeName).toLowerCase();
366                if (!filterable.contains(filter)) {
367                    continue;
368                }
369            }
370
371            CmsResourceInfo info = new CmsResourceInfo();
372            info.getTopLine().setContentMode(ContentMode.HTML);
373            String suffix = "";
374            if (nonstandardTypes.contains(type.getType())) {
375                suffix = " <span class='o-internal-type-name'>" + CmsEncoder.escapeHtml(type.getType()) + "</span>";
376            }
377            info.getTopLine().setValue(CmsEncoder.escapeHtml(title) + suffix);
378            info.getBottomLine().setValue(CmsJspElFunctions.stripHtml(subtitle));
379            Resource iconResource = CmsResourceUtil.getBigIconResource(explorerType, null);
380            info.getResourceIcon().initContent(null, iconResource, null, false, true);
381            info.setData(type);
382            m_resourceInfoMap.put(type, info);
383            getVerticalLayout().addComponent(info);
384        }
385    }
386
387    public void setSelectedRunnable(Runnable run) {
388
389        m_selectedRunnable = run;
390    }
391
392    public abstract boolean useDefault();
393
394    /**
395     * Creates type helper which is responsible for generating the type list.<p>
396     *
397     * @return the type helper
398     */
399    protected CmsAddDialogTypeHelper createTypeHelper() {
400
401        return new CmsAddDialogTypeHelper(CmsResourceTypeConfig.AddMenuType.workplace) {
402
403            @Override
404            protected boolean exclude(CmsResourceTypeBean type) {
405
406                String typeName = type.getType();
407                CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(typeName);
408                boolean noCreate = !(type.isCreatableType() && !type.isDeactivated());
409                return noCreate || (explorerType == null) || !explorerType.isCreatable();
410            }
411        };
412
413    }
414
415    /**
416     * Gets the subtitle for the type info widget.<p>
417     *
418     * @param type the type
419     * @param useDefault true if we are in 'use default' mode
420     *
421     * @return the subtitle
422     */
423    protected String getSubtitle(CmsResourceTypeBean type, boolean useDefault) {
424
425        String subtitle = "";
426        CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(type.getType());
427        if ((explorerType != null) && (explorerType.getInfo() != null)) {
428            subtitle = CmsVaadinUtils.getMessageText(explorerType.getInfo());
429        }
430        if (useDefault && (type.getOrigin() == Origin.config) && (type.getCreatePath() != null)) {
431            String path = type.getCreatePath();
432            CmsObject cms = A_CmsUI.getCmsObject();
433            path = cms.getRequestContext().removeSiteRoot(path);
434            subtitle = CmsVaadinUtils.getMessageText(Messages.GUI_NEW_CREATE_IN_PATH_1, path);
435        }
436        return subtitle;
437    }
438
439    /**
440     * Enables or disables filtering mode.
441     *
442     * @param filterMode true if filtering mode should be enabled
443     */
444    protected void setFilterMode(boolean filterMode) {
445
446        Component typePanel = getVerticalLayout().getParent();
447        Layout row = null;
448        if (m_filterField[0].isAttached()) {
449            row = (Layout)(m_filterField[0].getParent());
450        } else {
451            row = (Layout)(getViewSelector().getParent());
452        }
453
454        m_filterMode = filterMode;
455        setFilterModeStyle(filterMode);
456        if (filterMode) {
457            getViewSelector().setValue(A_CmsSelectResourceTypeDialog.ID_VIEW_ALL);
458            row.replaceComponent(getViewSelector(), m_filterField[0]);
459            m_filterField[0].focus();
460            typePanel.setHeight("425px");
461
462            // We have to disable this because it ultimately results in
463            // calls to setHeight in Horizontal/VerticalLayouts which
464            // can cause the focus on the filter field to be lost (apparently because
465            // the client-side implementations do some tricky DOM manipulation).
466            setMaxHeightEnabled(false);
467
468            CmsVaadinUtils.getWindow(this).center();
469        } else {
470            row.replaceComponent(m_filterField[0], getViewSelector());
471            m_filterField[0].clear();
472            typePanel.setHeight("100%");
473            setMaxHeightEnabled(true);
474            init(m_currentView, useDefault(), null);
475            CmsVaadinUtils.getWindow(this).center();
476        }
477    }
478
479    /**
480     * Sets the style of the filter mode toggle button.
481     *
482     * @param filterMode if true, changes the mode toggle to 'filtering' style
483     */
484    protected void setFilterModeStyle(boolean filterMode) {
485
486        if (filterMode) {
487            getModeToggle().addStyleName(OpenCmsTheme.TYPE_FILTER_BUTTON_ACTIVE);
488            getModeToggle().setIcon(FontOpenCms.FILTER);
489        } else {
490            getModeToggle().setIcon(FontOpenCms.FILTER);
491            getModeToggle().removeStyleName(OpenCmsTheme.TYPE_FILTER_BUTTON_ACTIVE);
492        }
493    }
494
495    /**
496     * Initializes the view selector, using the given view id as an initial value.<p>
497     *
498     * @param startId the start view
499     *
500     * @return the start view
501     */
502    private CmsElementView initViews(CmsUUID startId) {
503
504        Map<CmsUUID, CmsElementView> viewMap = OpenCms.getADEManager().getElementViews(A_CmsUI.getCmsObject());
505        List<CmsElementView> viewList = new ArrayList<CmsElementView>(viewMap.values());
506        Collections.sort(viewList, new Comparator<CmsElementView>() {
507
508            public int compare(CmsElementView arg0, CmsElementView arg1) {
509
510                return ComparisonChain.start().compare(arg0.getOrder(), arg1.getOrder()).result();
511            }
512
513        });
514        getViewSelector().setItemCaptionMode(ItemCaptionMode.EXPLICIT);
515        m_typeHelper = createTypeHelper();
516        m_typeHelper.precomputeTypeLists(
517            A_CmsUI.getCmsObject(),
518            m_folderResource.getRootPath(),
519            A_CmsUI.getCmsObject().getRequestContext().removeSiteRoot(m_folderResource.getRootPath()),
520            viewList,
521            null);
522
523        // also collect types in LinkedHashMap to preserve order and ensure uniqueness
524        LinkedHashMap<String, CmsResourceTypeBean> allTypes = Maps.newLinkedHashMap();
525
526        for (CmsElementView view : viewList) {
527
528            if (view.hasPermission(A_CmsUI.getCmsObject(), m_folderResource)) {
529
530                List<CmsResourceTypeBean> typeBeans = m_typeHelper.getPrecomputedTypes(view);
531
532                if (typeBeans.isEmpty()) {
533                    continue;
534                }
535                for (CmsResourceTypeBean typeBean : typeBeans) {
536                    allTypes.put(typeBean.getType(), typeBean);
537                }
538                getViewSelector().addItem(view.getId());
539                getViewSelector().setItemCaption(
540                    view.getId(),
541                    view.getTitle(A_CmsUI.getCmsObject(), A_CmsUI.get().getLocale()));
542            }
543        }
544        getViewSelector().addItem(VIEW_ALL.getId());
545        getViewSelector().setItemCaption(VIEW_ALL.getId(), CmsVaadinUtils.getMessageText(Messages.GUI_VIEW_ALL_0));
546        m_allTypes = Lists.newArrayList(allTypes.values());
547        if (allTypes.size() <= 8) {
548            startId = ID_VIEW_ALL;
549        }
550        if (getViewSelector().getItem(startId) == null) {
551            startId = (CmsUUID)(getViewSelector().getItemIds().iterator().next());
552        }
553
554        getViewSelector().addValueChangeListener(new ValueChangeListener() {
555
556            private static final long serialVersionUID = 1L;
557
558            public void valueChange(ValueChangeEvent event) {
559
560                CmsUUID viewId = (CmsUUID)(event.getProperty().getValue());
561                CmsElementView selectedView;
562                if (viewId.equals(ID_VIEW_ALL)) {
563                    selectedView = VIEW_ALL;
564                } else {
565                    selectedView = OpenCms.getADEManager().getElementViews(A_CmsUI.getCmsObject()).get(
566                        event.getProperty().getValue());
567                }
568                init(selectedView, useDefault(), null);
569                if (selectedView != VIEW_ALL) {
570                    VaadinService.getCurrentRequest().getWrappedSession().setAttribute(
571                        SETTING_STANDARD_VIEW,
572                        (event.getProperty().getValue()));
573                }
574            }
575        });
576        if (startId.equals(ID_VIEW_ALL)) {
577            return VIEW_ALL;
578        } else {
579            return OpenCms.getADEManager().getElementViews(A_CmsUI.getCmsObject()).get(startId);
580        }
581    }
582
583    /**
584     * Enables or disables the max-height extension.
585     *
586     * @param enabled true if the extension should be enabled
587     */
588    private void setMaxHeightEnabled(boolean enabled) {
589
590        getExtensions().forEach(ext -> {
591            if (ext instanceof CmsMaxHeightExtension) {
592                ((CmsMaxHeightExtension)ext).setEnabled(enabled);
593            }
594        });
595    }
596
597}