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}