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.components;
029
030import org.opencms.file.CmsResource;
031import org.opencms.ui.A_CmsUI;
032import org.opencms.ui.CmsVaadinUtils;
033import org.opencms.ui.Messages;
034import org.opencms.ui.components.extensions.CmsMaxHeightExtension;
035import org.opencms.util.CmsStringUtil;
036
037import java.util.List;
038
039import org.jsoup.nodes.Attributes;
040import org.jsoup.nodes.Element;
041
042import com.google.common.collect.Lists;
043import com.vaadin.event.Action.Handler;
044import com.vaadin.server.Page;
045import com.vaadin.server.Page.BrowserWindowResizeEvent;
046import com.vaadin.server.Page.BrowserWindowResizeListener;
047import com.vaadin.shared.Registration;
048import com.vaadin.ui.Alignment;
049import com.vaadin.ui.Button;
050import com.vaadin.ui.Component;
051import com.vaadin.ui.HorizontalLayout;
052import com.vaadin.ui.Layout;
053import com.vaadin.ui.Panel;
054import com.vaadin.ui.VerticalLayout;
055import com.vaadin.ui.Window;
056import com.vaadin.ui.Window.CloseEvent;
057import com.vaadin.ui.Window.CloseListener;
058import com.vaadin.ui.declarative.DesignContext;
059import com.vaadin.v7.shared.ui.label.ContentMode;
060import com.vaadin.v7.ui.Label;
061
062/**
063 * Basic dialog class with a content panel and button bar.<p>
064 */
065public class CmsBasicDialog extends VerticalLayout {
066
067    /** The available window widths. */
068    public enum DialogWidth {
069
070        /** Depending on the content. */
071        content,
072
073        /** The maximum width of 90% of the window width. */
074        max,
075
076        /** The default width of 600px. */
077        narrow,
078
079        /** The wide width of 800px. */
080        wide
081    }
082
083    /** Serial version id. */
084    private static final long serialVersionUID = 1L;
085
086    /** Maximum size of the resource list panel. */
087    private static final int RESOURCE_LIST_PANEL_MAX_SIZE = 1000;
088
089    /** The window resize listener registration. */
090    Registration m_resizeListenerRegistration;
091
092    /** The shortcut action handler. */
093    private Handler m_actionHandler;
094
095    /** The left button panel. */
096    private HorizontalLayout m_buttonPanelLeft;
097
098    /** The button bar. */
099    private HorizontalLayout m_buttonPanelRight;
100
101    /** The content panel. */
102    private Panel m_contentPanel;
103
104    /** The resource info component. */
105    private Component m_infoComponent;
106
107    /** The resources for which the resource info boxes should be displayed. */
108    private List<CmsResource> m_infoResources = Lists.newArrayList();
109
110    /** The main panel. */
111    private VerticalLayout m_mainPanel;
112
113    /** Extension used to regulate max height. */
114    private CmsMaxHeightExtension m_maxHeightExtension;
115
116    /** Maximum recorded height. */
117    private int m_maxRecordedHeight = Integer.MIN_VALUE;
118
119    /** The window resize listener. */
120    private BrowserWindowResizeListener m_windowResizeListener;
121
122    /**
123     * Creates new instance.<p>
124     */
125    public CmsBasicDialog() {
126
127        addStyleName(OpenCmsTheme.DIALOG);
128        setMargin(true);
129        setSpacing(true);
130        setWidth("100%");
131
132        m_mainPanel = new VerticalLayout();
133        m_mainPanel.addStyleName(OpenCmsTheme.DIALOG_CONTENT);
134        m_mainPanel.setSpacing(true);
135        m_mainPanel.setSizeFull();
136
137        m_contentPanel = new Panel();
138        m_contentPanel.setSizeFull();
139        m_contentPanel.addStyleName("v-scrollable");
140        m_contentPanel.addStyleName(OpenCmsTheme.DIALOG_CONTENT_PANEL);
141
142        m_mainPanel.addComponent(m_contentPanel);
143        m_mainPanel.setExpandRatio(m_contentPanel, 3);
144
145        Panel panel = new Panel();
146        panel.setContent(m_mainPanel);
147        panel.setSizeFull();
148        addComponent(panel);
149        setExpandRatio(panel, 1);
150        HorizontalLayout buttons = new HorizontalLayout();
151        buttons.setWidth("100%");
152        buttons.addStyleName(OpenCmsTheme.DIALOG_BUTTON_BAR);
153        addComponent(buttons);
154        m_buttonPanelLeft = new HorizontalLayout();
155        m_buttonPanelLeft.setSpacing(true);
156        buttons.addComponent(m_buttonPanelLeft);
157        buttons.setComponentAlignment(m_buttonPanelLeft, Alignment.MIDDLE_LEFT);
158        m_buttonPanelLeft.setVisible(false);
159        m_buttonPanelRight = new HorizontalLayout();
160        m_buttonPanelRight.setSpacing(true);
161        buttons.addComponent(m_buttonPanelRight);
162        buttons.setComponentAlignment(m_buttonPanelRight, Alignment.MIDDLE_RIGHT);
163        enableMaxHeight();
164    }
165
166    /**
167     * Initializes the dialog window.<p>
168     *
169     * @return the window to be used by dialogs
170     */
171    public static Window prepareWindow() {
172
173        return prepareWindow(DialogWidth.narrow);
174    }
175
176    /**
177     * Initializes the dialog window.<p>
178     *
179     * @param width the dialog width
180     *
181     * @return the window to be used by dialogs
182     */
183    public static Window prepareWindow(DialogWidth width) {
184
185        Window window = new Window();
186        window.setModal(true);
187        window.setClosable(true);
188        int pageWidth = Page.getCurrent().getBrowserWindowWidth();
189        if (pageWidth != 0) { // page width 0 can happen for first dialog opened in embedded mode in page editor
190            if (((width == DialogWidth.wide) && (pageWidth < 810))
191                || ((width == DialogWidth.narrow) && (pageWidth < 610))) {
192                // in case the available page width does not allow the desired width, use max
193                width = DialogWidth.max;
194            }
195            if (width == DialogWidth.max) {
196                // in case max width would result in a width very close to wide or narrow, use their static width instead of relative width
197                if ((pageWidth >= 610) && (pageWidth < 670)) {
198                    width = DialogWidth.narrow;
199                } else if ((pageWidth >= 810) && (pageWidth < 890)) {
200                    width = DialogWidth.wide;
201                }
202            }
203        }
204        switch (width) {
205            case content:
206                // do nothing
207                break;
208            case wide:
209                window.setWidth("800px");
210                break;
211            case max:
212                window.setWidth("90%");
213                break;
214            case narrow:
215            default:
216                window.setWidth("600px");
217                break;
218        }
219        window.center();
220        return window;
221    }
222
223    /**
224     * Adds a button to the button bar.<p>
225     *
226     * @param button the button to add
227     */
228    public void addButton(Component button) {
229
230        addButton(button, true);
231    }
232
233    /**
234     * Adds a button to the button bar.<p>
235     *
236     * @param button the button to add
237     * @param right to align the button right
238     */
239    public void addButton(Component button, boolean right) {
240
241        if (right) {
242            m_buttonPanelRight.addComponent(button);
243        } else {
244            m_buttonPanelLeft.addComponent(button);
245            m_buttonPanelLeft.setVisible(true);
246        }
247    }
248
249    /**
250     * Creates an 'Cancel' button.<p>
251     *
252     * @return the button
253     */
254    public Button createButtonCancel() {
255
256        return new Button(CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0));
257    }
258
259    /**
260     * Creates an 'Cancel' button.<p>
261     *
262     * @return the button
263     */
264    public Button createButtonClose() {
265
266        return new Button(CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CLOSE_0));
267    }
268
269    /**
270     * Creates an 'OK' button.<p>
271     *
272     * @return the button
273     */
274    public Button createButtonOK() {
275
276        return new Button(CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0));
277    }
278
279    /**
280     * Creates a resource list panel.<p>
281     *
282     * @param caption the caption to use
283     * @param resources the resources
284     *
285     * @return the panel
286     */
287    public Panel createResourceListPanel(String caption, List<CmsResource> resources) {
288
289        Panel result = null;
290        if (CmsStringUtil.isEmptyOrWhitespaceOnly(caption)) {
291            result = new Panel();
292        } else {
293            result = new Panel(caption);
294        }
295        result.addStyleName("v-scrollable");
296        result.setSizeFull();
297        VerticalLayout resourcePanel = new VerticalLayout();
298        result.setContent(resourcePanel);
299        resourcePanel.addStyleName(OpenCmsTheme.REDUCED_MARGIN);
300        resourcePanel.addStyleName(OpenCmsTheme.REDUCED_SPACING);
301        resourcePanel.setSpacing(true);
302        resourcePanel.setMargin(true);
303        if (resources.size() <= RESOURCE_LIST_PANEL_MAX_SIZE) {
304            for (CmsResource resource : resources) {
305                resourcePanel.addComponent(new CmsResourceInfo(resource));
306            }
307        } else {
308            String message = CmsVaadinUtils.getMessageText(
309                Messages.get(),
310                Messages.GUI_TOO_MANY_RESOURCES_2,
311                String.valueOf(resources.size()),
312                String.valueOf(RESOURCE_LIST_PANEL_MAX_SIZE));
313            Label label = new Label(message);
314            label.setContentMode(ContentMode.HTML);
315            VerticalLayout verticalLayout = new VerticalLayout();
316            verticalLayout.addComponent(label);
317            resourcePanel.addComponent(verticalLayout);
318        }
319        return result;
320    }
321
322    /**
323     * Creates a resource list panel.<p>
324     *
325     * @param caption the caption to use
326     * @param resourceInfo the resource-infos
327     * @return the panel
328     */
329    public Panel createResourceListPanelDirectly(String caption, List<CmsResourceInfo> resourceInfo) {
330
331        Panel result = new Panel(caption);
332        result.addStyleName("v-scrollable");
333        result.setSizeFull();
334        VerticalLayout resourcePanel = new VerticalLayout();
335        result.setContent(resourcePanel);
336        resourcePanel.addStyleName(OpenCmsTheme.REDUCED_MARGIN);
337        resourcePanel.addStyleName(OpenCmsTheme.REDUCED_SPACING);
338        resourcePanel.setSpacing(true);
339        resourcePanel.setMargin(true);
340        for (CmsResourceInfo resource : resourceInfo) {
341            resourcePanel.addComponent(resource);
342        }
343        return result;
344    }
345
346    /**
347     * For a given resource, display the resource info panel.<p>
348     *
349     * @param resource the resource
350     */
351    public void displayResourceInfo(CmsResource resource) {
352
353        m_infoComponent = new CmsResourceInfo(resource);
354        m_mainPanel.addComponent(m_infoComponent, 0);
355    }
356
357    /**
358     * For a given list of resources, displays the resource info panels.<p>
359     *
360     * @param resources the resources
361     */
362    public void displayResourceInfo(List<CmsResource> resources) {
363
364        displayResourceInfo(resources, Messages.GUI_SELECTED_0);
365    }
366
367    /**
368     * For a given list of resources, displays the resource info panels with panel messages.<p>
369     *
370     * @param resources to show info for
371     * @param messageKey of the panel
372     */
373    public void displayResourceInfo(List<CmsResource> resources, String messageKey) {
374
375        m_infoResources = Lists.newArrayList(resources);
376        if (m_infoComponent != null) {
377            m_mainPanel.removeComponent(m_infoComponent);
378            m_infoComponent = null;
379        }
380        if ((resources != null) && !resources.isEmpty()) {
381            if (resources.size() == 1) {
382                displayResourceInfo(resources.get(0));
383            } else {
384                m_infoComponent = createResourceListPanel(
385                    messageKey == null ? null : Messages.get().getBundle(A_CmsUI.get().getLocale()).key(messageKey),
386                    resources);
387                m_mainPanel.addComponent(m_infoComponent, 0);
388                m_mainPanel.setExpandRatio(m_infoComponent, 1);
389
390                // reset expand ratio of the content panel
391                m_contentPanel.setSizeUndefined();
392                m_contentPanel.setWidth("100%");
393                m_mainPanel.setExpandRatio(m_contentPanel, 0);
394            }
395
396        }
397    }
398
399    /**
400     * Displays the resource info panel.<p>
401     *
402     * @param resourceInfos to display
403     */
404    public void displayResourceInfoDirectly(List<CmsResourceInfo> resourceInfos) {
405
406        if (m_infoComponent != null) {
407            m_mainPanel.removeComponent(m_infoComponent);
408            m_infoComponent = null;
409        }
410        if ((resourceInfos != null) && !resourceInfos.isEmpty()) {
411            if (resourceInfos.size() == 1) {
412                m_infoComponent = resourceInfos.get(0);
413                m_mainPanel.addComponent(m_infoComponent, 0);
414            } else {
415                m_infoComponent = createResourceListPanelDirectly(
416                    Messages.get().getBundle(A_CmsUI.get().getLocale()).key(Messages.GUI_SELECTED_0),
417                    resourceInfos);
418                m_mainPanel.addComponent(m_infoComponent, 0);
419                m_mainPanel.setExpandRatio(m_infoComponent, 1);
420
421                // reset expand ratio of the content panel
422                m_contentPanel.setSizeUndefined();
423                m_contentPanel.setWidth("100%");
424                m_mainPanel.setExpandRatio(m_contentPanel, 0);
425            }
426
427        }
428    }
429
430    /**
431     * Gets the resources for which the resource info boxes should be displayed.<p>
432     *
433     * @return the resource info resources
434     */
435    public List<CmsResource> getInfoResources() {
436
437        return m_infoResources;
438    }
439
440    /**
441     * Initializes action handler.<p>
442     *
443     * @param window the parent window
444     */
445    public void initActionHandler(final Window window) {
446
447        if (m_actionHandler != null) {
448            window.addActionHandler(m_actionHandler);
449            window.addCloseListener(new CloseListener() {
450
451                private static final long serialVersionUID = 1L;
452
453                public void windowClose(CloseEvent e) {
454
455                    clearActionHandler(window);
456                }
457            });
458        }
459    }
460
461    /**
462     * @see com.vaadin.ui.AbstractOrderedLayout#readDesign(org.jsoup.nodes.Element, com.vaadin.ui.declarative.DesignContext)
463     */
464    @Override
465    public void readDesign(Element design, DesignContext designContext) {
466
467        for (Element child : design.children()) {
468            boolean contentRead = false;
469            boolean buttonsRead = false;
470            boolean aboveRead = false;
471            boolean belowRead = false;
472            if ("content".equals(child.tagName()) && !contentRead) {
473                Component content = designContext.readDesign(child.child(0));
474                setContent(content);
475                contentRead = true;
476            } else if ("buttons".equals(child.tagName()) && !buttonsRead) {
477                for (Element buttonElement : child.children()) {
478                    Component button = designContext.readDesign(buttonElement);
479                    Attributes attr = buttonElement.attributes();
480                    addButton(button, !attr.hasKey(":left"));
481                }
482                buttonsRead = true;
483            } else if ("above".equals(child.tagName()) && !aboveRead) {
484                Component aboveContent = designContext.readDesign(child.child(0));
485                setAbove(aboveContent);
486                aboveRead = true;
487            } else if ("below".equals(child.tagName()) && !belowRead) {
488                Component belowContent = designContext.readDesign(child.child(0));
489                setBelow(belowContent);
490                belowRead = true;
491            }
492        }
493    }
494
495    /**
496     * Sets the content to be displayed above the main content.<p>
497     *
498     * @param aboveContent the above content
499     */
500    public void setAbove(Component aboveContent) {
501
502        if (m_mainPanel.getComponentIndex(m_contentPanel) == 0) {
503            m_mainPanel.addComponent(aboveContent, 0);
504        } else {
505            m_mainPanel.replaceComponent(m_mainPanel.getComponent(0), aboveContent);
506        }
507    }
508
509    /**
510     * Sets the shortcut action handler.<p>
511     * Set this before opening the window, so it will be initialized properly.<p>
512     *
513     * @param actionHandler the action handler
514     */
515    public void setActionHandler(Handler actionHandler) {
516
517        m_actionHandler = actionHandler;
518    }
519
520    /**
521     * Sets the content to be displayed below the main content.<p>
522     * @param belowContent the below content
523     */
524    public void setBelow(Component belowContent) {
525
526        int i = m_mainPanel.getComponentIndex(m_mainPanel);
527        Component oldBelow = m_mainPanel.getComponent(i + 1);
528        if (oldBelow == null) {
529            m_mainPanel.addComponent(belowContent);
530        } else {
531            m_mainPanel.replaceComponent(oldBelow, belowContent);
532        }
533    }
534
535    /**
536     * Sets the content.<p>
537     *
538     * @param content the content widget
539     */
540    public void setContent(Component content) {
541
542        m_contentPanel.setContent(content);
543        if (content instanceof Layout.MarginHandler) {
544            ((Layout.MarginHandler)content).setMargin(true);
545        }
546    }
547
548    /**
549     * Sets the height of the content to a given min Height or 100%.<p>
550     *
551     * @param height minimal height.
552     */
553    public void setContentMinHeight(int height) {
554
555        if ((0.9 * Page.getCurrent().getBrowserWindowHeight()) < height) {
556            m_contentPanel.getContent().setHeight(height + "px");
557        } else {
558            m_contentPanel.getContent().setHeight("100%");
559        }
560    }
561
562    /**
563     * Sets the visibility of the content panel.<o>
564     *
565     * @param visible visibility of the content.
566     */
567    public void setContentVisibility(boolean visible) {
568
569        m_contentPanel.setVisible(visible);
570    }
571
572    /**
573     * Sets the window which contains this dialog to full height with a given minimal height in pixel.<p>
574     *
575     * @param minHeight minimal height in pixel
576     */
577    public void setWindowMinFullHeight(int minHeight) {
578
579        Window window = CmsVaadinUtils.getWindow(this);
580        if (window == null) {
581            return;
582        }
583        window.setHeight("90%");
584        setHeight("100%");
585        setContentMinHeight(minHeight);
586        window.center();
587    }
588
589    /**
590     * Adds the max height extension to the dialog panel.<p>
591     */
592    protected void enableMaxHeight() {
593
594        // use the window height minus an offset for the window header and some spacing
595        int maxHeight = calculateMaxHeight(A_CmsUI.get().getPage().getBrowserWindowHeight());
596        m_maxHeightExtension = new CmsMaxHeightExtension(this, maxHeight);
597        // only center window for height changes that exceed the maximum height since the last window resize
598        // (window resize handler resets this)
599        m_maxHeightExtension.addHeightChangeHandler(new CmsMaxHeightExtension.I_HeightChangeHandler() {
600
601            @SuppressWarnings("synthetic-access")
602            public void onChangeHeight(int height) {
603
604                boolean center = height > m_maxRecordedHeight;
605                m_maxRecordedHeight = Math.max(m_maxRecordedHeight, height);
606                Window wnd = CmsVaadinUtils.getWindow(CmsBasicDialog.this);
607                if ((wnd != null) && center) {
608                    wnd.center();
609                }
610            }
611        });
612
613        addDetachListener(new DetachListener() {
614
615            private static final long serialVersionUID = 1L;
616
617            public void detach(DetachEvent event) {
618
619                if (m_resizeListenerRegistration != null) {
620                    m_resizeListenerRegistration.remove();
621                    m_resizeListenerRegistration = null;
622                }
623            }
624        });
625
626        m_windowResizeListener = new BrowserWindowResizeListener() {
627
628            private static final long serialVersionUID = 1L;
629
630            @SuppressWarnings("synthetic-access")
631            public void browserWindowResized(BrowserWindowResizeEvent event) {
632
633                m_maxRecordedHeight = Integer.MIN_VALUE;
634                int newHeight = event.getHeight();
635                m_maxHeightExtension.updateMaxHeight(calculateMaxHeight(newHeight));
636            }
637        };
638        m_resizeListenerRegistration = A_CmsUI.get().getPage().addBrowserWindowResizeListener(m_windowResizeListener);
639
640    }
641
642    /**
643     * Removes the action handler.<p>
644     *
645     * @param window the window the action handler is attached to
646     */
647    void clearActionHandler(Window window) {
648
649        if (m_actionHandler != null) {
650            window.removeActionHandler(m_actionHandler);
651        }
652    }
653
654    /**
655     * Calculates max dialog height given the window height.<p>
656     *
657     * @param windowHeight the window height
658     * @return the maximal dialog height
659     */
660    private int calculateMaxHeight(int windowHeight) {
661
662        return (int)((0.95 * windowHeight) - 40);
663    }
664}