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