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.db.CmsResourceState; 031import org.opencms.file.CmsObject; 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.CmsResourceTypeImage; 037import org.opencms.lock.CmsLockActionRecord; 038import org.opencms.lock.CmsLockUtil; 039import org.opencms.main.CmsException; 040import org.opencms.main.CmsLog; 041import org.opencms.main.CmsPermalinkResourceHandler; 042import org.opencms.main.OpenCms; 043import org.opencms.relations.CmsRelation; 044import org.opencms.relations.CmsRelationFilter; 045import org.opencms.ui.A_CmsUI; 046import org.opencms.ui.CmsCssIcon; 047import org.opencms.ui.CmsVaadinUtils; 048import org.opencms.ui.FontOpenCms; 049import org.opencms.ui.I_CmsDialogContext; 050import org.opencms.ui.apps.CmsAppWorkplaceUi; 051import org.opencms.ui.components.CmsBasicDialog; 052import org.opencms.ui.components.CmsConfirmationDialog; 053import org.opencms.ui.components.CmsGwtContextMenuButton; 054import org.opencms.ui.components.CmsOkCancelActionHandler; 055import org.opencms.ui.components.CmsResourceInfo; 056import org.opencms.ui.components.OpenCmsTheme; 057import org.opencms.ui.shared.rpc.I_CmsGwtContextMenuServerRpc; 058import org.opencms.util.CmsDateUtil; 059import org.opencms.util.CmsStringUtil; 060import org.opencms.util.CmsUUID; 061import org.opencms.workplace.explorer.CmsResourceUtil; 062 063import java.text.DateFormat; 064import java.util.ArrayList; 065import java.util.Arrays; 066import java.util.Collections; 067import java.util.Comparator; 068import java.util.Date; 069import java.util.HashSet; 070import java.util.List; 071import java.util.Set; 072import java.util.concurrent.TimeUnit; 073import java.util.stream.Collectors; 074 075import org.apache.commons.logging.Log; 076 077import com.vaadin.data.Binder; 078import com.vaadin.data.ValidationException; 079import com.vaadin.data.provider.ListDataProvider; 080import com.vaadin.data.provider.Query; 081import com.vaadin.server.ExternalResource; 082import com.vaadin.server.Page; 083import com.vaadin.server.SerializableComparator; 084import com.vaadin.server.SerializablePredicate; 085import com.vaadin.server.VaadinService; 086import com.vaadin.server.WrappedSession; 087import com.vaadin.shared.Position; 088import com.vaadin.shared.ui.ContentMode; 089import com.vaadin.shared.ui.MarginInfo; 090import com.vaadin.ui.AbsoluteLayout; 091import com.vaadin.ui.Alignment; 092import com.vaadin.ui.Button; 093import com.vaadin.ui.CheckBox; 094import com.vaadin.ui.Component; 095import com.vaadin.ui.CssLayout; 096import com.vaadin.ui.FormLayout; 097import com.vaadin.ui.GridLayout; 098import com.vaadin.ui.HorizontalLayout; 099import com.vaadin.ui.ItemCaptionGenerator; 100import com.vaadin.ui.Label; 101import com.vaadin.ui.Link; 102import com.vaadin.ui.NativeSelect; 103import com.vaadin.ui.Notification; 104import com.vaadin.ui.Panel; 105import com.vaadin.ui.TextField; 106import com.vaadin.ui.VerticalLayout; 107import com.vaadin.ui.Window; 108import com.vaadin.ui.themes.ValoTheme; 109 110/** 111 * Class representing a dialog for optimizing galleries.<p> 112 */ 113public class CmsGalleryOptimizeDialog extends CmsBasicDialog { 114 115 /** 116 * The context used for child dialogs.<p> 117 */ 118 public class ContextMenu implements I_CmsGwtContextMenuServerRpc { 119 120 /** Default serial version uid. */ 121 private static final long serialVersionUID = 1L; 122 123 /** 124 * The data item handled by this dialog context. 125 */ 126 private final DataItem m_dataItem; 127 128 /** 129 * Creates a new instance. 130 131 * @param dataItem the data item 132 */ 133 public ContextMenu(DataItem dataItem) { 134 135 m_dataItem = dataItem; 136 } 137 138 /** 139 * @see org.opencms.ui.shared.rpc.I_CmsGwtContextMenuServerRpc#refresh(java.lang.String) 140 */ 141 public void refresh(String uuid) { 142 143 if (uuid != null) { 144 try { 145 TimeUnit.SECONDS.sleep(1); 146 CmsResource resource = getCms().readResource(new CmsUUID(uuid), CmsResourceFilter.ONLY_VISIBLE); 147 boolean deleted = resource.getState() == CmsResourceState.STATE_DELETED; 148 if (deleted) { 149 CmsGalleryOptimizeDialog.this.handleDataListDelete(Arrays.asList(m_dataItem)); 150 } else { 151 CmsGalleryOptimizeDialog.this.handleDataListUpdate(Arrays.asList(m_dataItem)); 152 } 153 String message = CmsVaadinUtils.getMessageText( 154 Messages.GUI_GALLERY_OPTIMIZE_LABEL_SUCCESSFULLY_SAVED_0); 155 Notification notification = new Notification(message, "", Notification.Type.HUMANIZED_MESSAGE); 156 notification.setPosition(Position.TOP_CENTER); 157 notification.show(Page.getCurrent()); 158 CmsAppWorkplaceUi.get().enableGlobalShortcuts(); 159 } catch (CmsException | InterruptedException e) { 160 LOG.error(e.getLocalizedMessage(), e); 161 Notification notification = new Notification( 162 "", 163 e.getLocalizedMessage(), 164 Notification.Type.ERROR_MESSAGE); 165 notification.setHtmlContentAllowed(true); 166 notification.setPosition(Position.TOP_CENTER); 167 notification.show(Page.getCurrent()); 168 } 169 } 170 } 171 } 172 173 /** 174 * Class representing an editable gallery item.<p> 175 */ 176 private class DataItem { 177 178 /** The data binder of this editable gallery item. */ 179 private Binder<DataItem> m_binder = new Binder<DataItem>(); 180 181 /** The file composite of this editable gallery item. */ 182 private FileComposite m_compositeFile; 183 184 /** The file delete composite of this editable gallery item. */ 185 private FileDeleteComposite m_compositeFileDelete; 186 187 /** The form composite of this editable gallery item. */ 188 private FormComposite m_compositeForm; 189 190 /** The copyright information of this editable gallery item. */ 191 private String m_copyright; 192 193 /** Date when this editable gallery item was last modified. */ 194 private Long m_dateLastModified; 195 196 /** Whether this editable gallery item shall be deleted. */ 197 private Boolean m_deleteFlag = Boolean.valueOf(false); 198 199 /** The description of this editable gallery item. */ 200 private String m_description; 201 202 /** Whether this editable gallery item is used. */ 203 private Boolean m_isUsed; 204 205 /** The file name of this editable gallery item. */ 206 private String m_name; 207 208 /** The full path of this editable gallery item. */ 209 private String m_path; 210 211 /** The CMS resource of this editable gallery item. */ 212 private CmsResource m_resource; 213 214 /** The CMS resource utility of this editable gallery item. */ 215 private CmsResourceUtil m_resourceUtil; 216 217 /** The title of this editable gallery item. */ 218 private String m_title; 219 220 /** 221 * Creates a new editable gallery item for a given CMS resource.<p> 222 * 223 * @param resource the CMS resource 224 */ 225 public DataItem(CmsResource resource) { 226 227 m_resource = resource; 228 initData(); 229 initComponent(); 230 } 231 232 /** 233 * Returns the binder of this editable gallery item.<p> 234 * 235 * @return the binder 236 */ 237 public Binder<DataItem> getBinder() { 238 239 return m_binder; 240 } 241 242 /** 243 * Returns the file composite of this editable gallery item.<p> 244 * 245 * @return the file composite 246 */ 247 public FileComposite getCompositeFile() { 248 249 return m_compositeFile; 250 } 251 252 /** 253 * Returns the file delete composite of this editable gallery item.<p> 254 * 255 * @return the file delete composite 256 */ 257 public FileDeleteComposite getCompositeFileDelete() { 258 259 return m_compositeFileDelete; 260 } 261 262 /** 263 * Returns the form composite of this editable gallery item.<p> 264 * 265 * @return the form composite 266 */ 267 public FormComposite getCompositeForm() { 268 269 return m_compositeForm; 270 } 271 272 /** 273 * Returns the copyright information of this editable gallery item.<p> 274 * 275 * @return the copyright information 276 */ 277 public String getCopyright() { 278 279 return m_copyright; 280 } 281 282 /** 283 * Returns the date when this editable gallery item was last modified.<p> 284 * 285 * @return the date 286 */ 287 public Long getDateLastModified() { 288 289 return m_dateLastModified; 290 } 291 292 /** 293 * Returns whether this editable gallery item shall be deleted.<p> 294 * 295 * @return whether delete or not 296 */ 297 public Boolean getDeleteFlag() { 298 299 return m_deleteFlag; 300 } 301 302 /** 303 * Returns the description of this editable gallery item.<p> 304 * 305 * @return the description 306 */ 307 public String getDescription() { 308 309 return m_description; 310 } 311 312 /** 313 * Returns the filter text.<p> 314 * 315 * @return the filter text 316 */ 317 public String getFilterText() { 318 319 return (m_name + " " + m_title + " " + m_copyright + " " + m_description).toLowerCase(); 320 } 321 322 /** 323 * Returns whether this editable gallery item is used.<p> 324 * 325 * @return whether used or not 326 */ 327 public Boolean getIsUsed() { 328 329 return m_isUsed; 330 } 331 332 /** 333 * Returns the name of this editable gallery item.<p> 334 * 335 * @return the name 336 */ 337 public String getName() { 338 339 return m_name; 340 } 341 342 /** 343 * Returns whether this data item has no copyright information.<p> 344 * 345 * @return whether this data item has no copyright information 346 */ 347 public Boolean getNoCopyright() { 348 349 return Boolean.valueOf(CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_copyright)); 350 } 351 352 /** 353 * Returns whether this data item has no description.<p> 354 * 355 * @return whether this data item has no description 356 */ 357 public Boolean getNoDescription() { 358 359 return Boolean.valueOf(CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_description)); 360 } 361 362 /** 363 * Returns the full path of this editable gallery item.<p> 364 * 365 * @return the full path 366 */ 367 public String getPath() { 368 369 return m_path; 370 } 371 372 /** 373 * Converts the form data of this editable gallery item into a list of CMS properties.<p> 374 * 375 * @return the CMS property list 376 */ 377 public List<CmsProperty> getPropertyList() { 378 379 List<CmsProperty> propertyList = new ArrayList<CmsProperty>(); 380 propertyList.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, m_title, null)); 381 propertyList.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_COPYRIGHT, m_copyright, null)); 382 propertyList.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_DESCRIPTION, m_description, null)); 383 return propertyList; 384 } 385 386 /** 387 * Returns the CMS resource this editable gallery item was created from.<p> 388 * 389 * @return the CMS resource 390 */ 391 public CmsResource getResource() { 392 393 return m_resource; 394 } 395 396 /** 397 * Returns the CMS resource utility class for this editable gallery item.<p> 398 * 399 * @return the CMS resource utility class 400 */ 401 public CmsResourceUtil getResourceUtil() { 402 403 return m_resourceUtil; 404 } 405 406 /** 407 * Returns the title of this editable gallery item.<p> 408 * 409 * @return the title 410 */ 411 public String getTitle() { 412 413 return m_title; 414 } 415 416 /** 417 * Returns whether this editable gallery item has value changes compared 418 * to the property values actually persisted.<p> 419 * 420 * @return whether changes or not 421 */ 422 public boolean hasChanges() { 423 424 boolean hasChanges = false; 425 try { 426 if (!hasChanges) { 427 hasChanges = !m_title.equals(readPropertyTitle()); 428 } 429 if (!hasChanges) { 430 hasChanges = !m_copyright.equals(readPropertyCopyright()); 431 } 432 if (!hasChanges) { 433 hasChanges = !m_description.equals(readPropertyDescription()); 434 } 435 } catch (CmsException e) { 436 hasChanges = true; 437 LOG.warn(e.getLocalizedMessage(), e); 438 } 439 return hasChanges; 440 } 441 442 /** 443 * Returns whether this editable gallery item is renamed compared to 444 * the resource actually persisted.<p> 445 * 446 * @return whether renamed or not 447 */ 448 public boolean isRenamed() { 449 450 boolean isRenamed = false; 451 try { 452 isRenamed = !m_name.equals(readName()); 453 } catch (CmsException e) { 454 LOG.warn(e.getLocalizedMessage(), e); 455 } 456 return isRenamed; 457 } 458 459 /** 460 * Returns whether this editable gallery item is an image.<p> 461 * 462 * @return whether an image or not 463 */ 464 public boolean isTypeImage() { 465 466 return OpenCms.getResourceManager().getResourceType(m_resource) instanceof CmsResourceTypeImage; 467 } 468 469 /** 470 * Sets the copyright information of this editable gallery item.<p> 471 * 472 * @param copyright the copyright information 473 */ 474 public void setCopyright(String copyright) { 475 476 m_copyright = copyright; 477 } 478 479 /** 480 * Sets the flag that states whether this editable gallery item shall be deleted.<p> 481 * 482 * @param deleteFlag the flag 483 */ 484 public void setDeleteFlag(Boolean deleteFlag) { 485 486 m_deleteFlag = deleteFlag; 487 } 488 489 /** 490 * Sets the description of this editable gallery item.<p> 491 * 492 * @param description the description 493 */ 494 public void setDescription(String description) { 495 496 m_description = description; 497 } 498 499 /** 500 * Sets the name of this editable gallery item.<p> 501 * 502 * @param name the name 503 */ 504 public void setName(String name) { 505 506 m_name = name; 507 } 508 509 /** 510 * Sets the CMS resource of this editable gallery item and re-initializes all data and components.<p> 511 * 512 * @param resource the CMS resource 513 */ 514 public void setResource(CmsResource resource) { 515 516 m_resource = resource; 517 initData(); 518 initComponent(); 519 } 520 521 /** 522 * Sets the title of this editable gallery item.<p> 523 * 524 * @param title the title 525 */ 526 public void setTitle(String title) { 527 528 m_title = title; 529 } 530 531 /** 532 * Initializes all UI components of this editable gallery item and initializes all data fields.<p> 533 */ 534 private void initComponent() { 535 536 m_compositeFile = new FileComposite(this); 537 m_compositeFileDelete = new FileDeleteComposite(this); 538 m_compositeForm = new FormComposite(this); 539 m_binder.readBean(this); 540 } 541 542 /** 543 * Initializes all data of this editable gallery item.<p> 544 */ 545 private void initData() { 546 547 m_resourceUtil = new CmsResourceUtil(A_CmsUI.getCmsObject(), m_resource); 548 try { 549 List<CmsRelation> relations = getCms().getRelationsForResource(m_resource, CmsRelationFilter.SOURCES); 550 m_name = m_resource.getName(); 551 m_path = m_resource.getRootPath(); 552 m_title = readPropertyTitle(); 553 m_copyright = readPropertyCopyright(); 554 m_description = readPropertyDescription(); 555 m_dateLastModified = Long.valueOf(m_resource.getDateLastModified()); 556 m_isUsed = Boolean.valueOf(!((relations == null) || relations.isEmpty())); 557 } catch (CmsException e) { 558 LOG.error(e.getLocalizedMessage(), e); 559 } 560 } 561 562 /** 563 * Reads the persisted resource name.<p> 564 * 565 * @return the resource name 566 * @throws CmsException thrown if reading the resource fails 567 */ 568 private String readName() throws CmsException { 569 570 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireFile(); 571 return getCms().readResource(m_resource.getStructureId(), resourceFilter).getName(); 572 } 573 574 /** 575 * Reads the persisted copyright property value.<p> 576 * 577 * @return the copyright property value 578 * @throws CmsException thrown if the property read fails 579 */ 580 private String readPropertyCopyright() throws CmsException { 581 582 String value = getCms().readPropertyObject( 583 m_resource, 584 CmsPropertyDefinition.PROPERTY_COPYRIGHT, 585 false).getValue(); 586 return value == null ? "" : value; 587 } 588 589 /** 590 * Reads the persisted description property value.<p> 591 * 592 * @return the description property value 593 * @throws CmsException thrown if the property read fails 594 */ 595 private String readPropertyDescription() throws CmsException { 596 597 String value = getCms().readPropertyObject( 598 m_resource, 599 CmsPropertyDefinition.PROPERTY_DESCRIPTION, 600 false).getValue(); 601 return value == null ? "" : value; 602 } 603 604 /** 605 * Reads the persisted title property value.<p> 606 * 607 * @return the title property value 608 * @throws CmsException thrown if the property read fails 609 */ 610 private String readPropertyTitle() throws CmsException { 611 612 String value = getCms().readPropertyObject( 613 m_resource, 614 CmsPropertyDefinition.PROPERTY_TITLE, 615 false).getValue(); 616 return value == null ? "" : value; 617 } 618 } 619 620 /** 621 * Class representing the data list header view with components for 622 * sorting, paging, and filtering the gallery item list.<p> 623 */ 624 private class DataListHeaderComposite extends AbsoluteLayout { 625 626 /** The default serial version UID. */ 627 private static final long serialVersionUID = 1L; 628 629 /** The page info label. */ 630 private Label m_labelPageInfo; 631 632 /** The select box for page selection. */ 633 private NativeSelect<Integer> m_selectPage; 634 635 /** The select box for sort order selection. */ 636 private NativeSelect<String> m_selectSortOrder; 637 638 /** The text field for filtering. */ 639 private TextField m_textFieldFilter; 640 641 /** 642 * Creates a new data list header composite.<p> 643 */ 644 public DataListHeaderComposite() { 645 646 setHeight("34px"); 647 setWidthFull(); 648 m_selectSortOrder = createSelectSortOrder(); 649 m_textFieldFilter = createTextFieldFilter(); 650 651 addComponent(m_selectSortOrder, "left: 0px; top: 2px;"); 652 addComponent(m_textFieldFilter, "right: 0px; top: 2px;"); 653 addStyleName("o-optimize-gallery-header"); 654 refresh(); 655 } 656 657 /** 658 * Refreshes this component.<p> 659 */ 660 public void refresh() { 661 662 NativeSelect<Integer> selectPage = createSelectPage(); 663 Label pageInfo = createLabelPageInfo(); 664 if (m_selectPage == null) { 665 m_selectPage = selectPage; 666 addComponent(m_selectPage, "left: 436px; top: 2px;"); 667 } else { 668 replaceComponent(m_selectPage, selectPage); 669 m_selectPage = selectPage; 670 } 671 if (m_labelPageInfo == null) { 672 m_labelPageInfo = pageInfo; 673 addComponent(m_labelPageInfo, "right: 228px; top: 6px;"); 674 } else { 675 replaceComponent(m_labelPageInfo, pageInfo); 676 m_labelPageInfo = pageInfo; 677 } 678 } 679 680 /** 681 * Programmatically selects a page according to a given index.<p> 682 * 683 * @param index the page index 684 */ 685 public void selectPage(int index) { 686 687 m_selectPage.setValue(null); 688 handlePageChange(index, false); 689 } 690 691 /** 692 * Creates a page info label.<p> 693 * 694 * @return the page info label 695 */ 696 @SuppressWarnings("synthetic-access") 697 private Label createLabelPageInfo() { 698 699 String text = ""; 700 if (m_pageHandler.hasPages()) { 701 text = CmsVaadinUtils.getMessageText( 702 Messages.GUI_GALLERY_OPTIMIZE_LABEL_PAGE_INFO_3, 703 String.valueOf(m_pageHandler.getNumFirstItem()), 704 String.valueOf(m_pageHandler.getNumLastItem()), 705 String.valueOf(m_pageHandler.getSizeItem())); 706 } else if (m_pageHandler.getSizeItem() == 1) { 707 text = CmsVaadinUtils.getMessageText( 708 Messages.GUI_GALLERY_OPTIMIZE_LABEL_PAGE_INFO_ONE_0, 709 String.valueOf(m_pageHandler.getSizeItem())); 710 } else { 711 text = CmsVaadinUtils.getMessageText( 712 Messages.GUI_GALLERY_OPTIMIZE_LABEL_PAGE_INFO_1, 713 String.valueOf(m_pageHandler.getSizeItem())); 714 } 715 Label label = new Label(text); 716 label.setWidthUndefined(); 717 return label; 718 } 719 720 /** 721 * Creates a select box for page select.<p> 722 * 723 * @return the page select box 724 */ 725 @SuppressWarnings("synthetic-access") 726 private NativeSelect<Integer> createSelectPage() { 727 728 NativeSelect<Integer> selectPage = new NativeSelect<Integer>(); 729 selectPage.setWidthUndefined(); 730 int numPages = m_pageHandler.getNumPages(); 731 selectPage.setItemCaptionGenerator(new ItemCaptionGenerator<Integer>() { 732 733 private static final long serialVersionUID = 1L; 734 735 public String apply(Integer item) { 736 737 return CmsVaadinUtils.getMessageText( 738 Messages.GUI_GALLERY_OPTIMIZE_SELECTED_PAGE_2, 739 String.valueOf(item.intValue() + 1), 740 String.valueOf(numPages)); 741 } 742 743 }); 744 Integer firstItem = Integer.valueOf(0); 745 List<Integer> items = new ArrayList<Integer>(); 746 for (int i = 0; i < numPages; i++) { 747 if (i > 0) { 748 items.add(Integer.valueOf(i)); 749 } 750 } 751 selectPage.setEmptySelectionCaption(selectPage.getItemCaptionGenerator().apply(firstItem)); 752 selectPage.setItems(items); 753 selectPage.addValueChangeListener(event -> { 754 if (event.isUserOriginated()) { 755 int index = event.getValue() != null ? event.getValue().intValue() : 0; 756 handlePageChange(index, true); 757 } 758 }); 759 selectPage.setVisible(m_pageHandler.hasPages()); 760 return selectPage; 761 } 762 763 /** 764 * Creates a select box for sort order select.<p> 765 * 766 * @return the sort order select box 767 */ 768 @SuppressWarnings("synthetic-access") 769 private NativeSelect<String> createSelectSortOrder() { 770 771 NativeSelect<String> selectSortOrder = new NativeSelect<String>(); 772 selectSortOrder.setWidthUndefined(); 773 selectSortOrder.setEmptySelectionCaption(m_messageSortTitleAscending); 774 selectSortOrder.setItems( 775 m_messageSortTitleDescending, 776 m_messageSortDateLastModifiedAscending, 777 m_messageSortDateLastModifiedDescending, 778 m_messageSortPathAscending, 779 m_messageSortPathDescending, 780 m_messageSortUnusedFirst, 781 m_messageSortNoCopyrightFirst, 782 m_messageSortNoDescriptionFirst); 783 selectSortOrder.addValueChangeListener(event -> { 784 if (event.isUserOriginated()) { 785 selectPage(0); 786 m_provider.refreshAll(); 787 displayDataListViewSorted(event.getValue()); 788 } 789 }); 790 selectSortOrder.setValue(getSessionSortOrder()); 791 return selectSortOrder; 792 } 793 794 /** 795 * Creates a text field for filtering.<p> 796 * 797 * @return the filter text field 798 */ 799 private TextField createTextFieldFilter() { 800 801 TextField textField = new TextField(); 802 textField.setPlaceholder(CmsVaadinUtils.getMessageText(org.opencms.ui.apps.Messages.GUI_EXPLORER_FILTER_0)); 803 textField.setWidth("200px"); 804 textField.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); 805 textField.setIcon(FontOpenCms.FILTER); 806 textField.addValueChangeListener(event -> { 807 handleFilterChange(event.getValue()); 808 }); 809 return textField; 810 } 811 812 /** 813 * Filter change event handler. Updates the page info label and the gallery list.<p> 814 * 815 * @param query the filter query string 816 */ 817 @SuppressWarnings("synthetic-access") 818 private void handleFilterChange(String query) { 819 820 String clean = query.trim(); 821 m_filterHandler.setQuery(clean); 822 m_pageHandler.setCurrentPage(0); 823 refresh(); 824 displayDataListView(true); 825 } 826 827 /** 828 * Page change event handler. Updates the page info label.<p> 829 * 830 * @param index the index of the page to select 831 * @param display whether to re-render the data item list 832 */ 833 @SuppressWarnings("synthetic-access") 834 private void handlePageChange(int index, boolean display) { 835 836 m_pageHandler.setCurrentPage(index); 837 Label label = createLabelPageInfo(); 838 replaceComponent(m_labelPageInfo, label); 839 m_labelPageInfo = label; 840 if (display) { 841 displayDataListView(true); 842 } 843 } 844 } 845 846 /** 847 * Class representing a file composite offering a file preview.<p> 848 */ 849 private class FileComposite extends HorizontalLayout { 850 851 /** The panel height. */ 852 private static final String PANEL_HEIGHT = "172px"; 853 854 /** The panel width. */ 855 private static final String PANEL_WIDTH = "202px"; 856 857 /** The default serial version UID. */ 858 private static final long serialVersionUID = 1L; 859 860 /** The data item of this file composite. */ 861 private DataItem m_dataItem; 862 863 /** The main panel of this file composite. */ 864 private AbsoluteLayout m_panel; 865 866 /** 867 * Creates a new file composite for a given data item.<p> 868 * 869 * @param dataItem the data item 870 */ 871 public FileComposite(DataItem dataItem) { 872 873 m_dataItem = dataItem; 874 setSizeUndefined(); 875 setMargin(true); 876 m_panel = new AbsoluteLayout(); 877 m_panel.setWidth(PANEL_WIDTH); 878 m_panel.setHeight(PANEL_HEIGHT); 879 m_panel.addStyleName("v-panel"); 880 Component link = createClickableFile(); 881 m_panel.addComponent(link, "left: 0px; top: 0px;"); 882 m_panel.addStyleName("o-optimize-gallery-preview-panel"); 883 addComponent(m_panel); 884 } 885 886 /** 887 * Creates a clickable file preview.<p> 888 * 889 * @return the clickable file preview 890 */ 891 private Component createClickableFile() { 892 893 Component link = m_dataItem.isTypeImage() ? createClickableImage() : createClickableOther(); 894 link.setWidth(IMAGE_WIDTH + "px"); 895 link.setHeight(IMAGE_HEIGHT + "px"); 896 return link; 897 } 898 899 /** 900 * Utility function to create a clickable image.<p> 901 * 902 * @return the clickable image 903 */ 904 private Label createClickableImage() { 905 906 CmsResource resource = m_dataItem.getResource(); 907 String image = "<img width=\"" 908 + IMAGE_WIDTH 909 + "px\" height=\"" 910 + IMAGE_HEIGHT 911 + "px\" src=\"" 912 + getScaleUri(resource, false) 913 + "\"" 914 + " srcset=\"" 915 + getScaleUri(resource, true) 916 + " 2x" 917 + "\" " 918 + " onerror='cmsJsFunctions.handleBrokenImage(this)' " 919 + " >"; 920 String a = "<a target=\"_blank\" href=\"" + getPermanentUri(resource) + "\">" + image + "</a>"; 921 String div = "<div class=\"" 922 + OpenCmsTheme.GALLERY_PREVIEW_IMAGE 923 + "\" style=\"width:" 924 + IMAGE_WIDTH 925 + "px;height:" 926 + IMAGE_HEIGHT 927 + "px;\">" 928 + a 929 + "</div>"; 930 Label label = new Label(div); 931 label.setContentMode(ContentMode.HTML); 932 return label; 933 } 934 935 /** 936 * Utility function to create a clickable preview for files that are not images. 937 * 938 * @return the clickable preview 939 */ 940 private Link createClickableOther() { 941 942 CmsResource resource = m_dataItem.getResource(); 943 CmsCssIcon cssIcon = (CmsCssIcon)m_dataItem.getResourceUtil().getSmallIconResource(); 944 String caption = "<div style=\"width:" 945 + IMAGE_WIDTH 946 + "px;height:" 947 + IMAGE_HEIGHT 948 + "px;display: flex; justify-content: center; align-items: center;\"><span class=\"" 949 + cssIcon.getStyleName() 950 + "\" style=\"transform: scale(4);\"></span></div>"; 951 Link link = new Link(caption, new ExternalResource(getPermanentUri(resource))); 952 link.setCaptionAsHtml(true); 953 link.setTargetName("_blank"); 954 return link; 955 } 956 957 /** 958 * Utility function to create a permanent URI for a file preview.<p> 959 * 960 * @param resource the CMS resource 961 * @return the permanent URI 962 */ 963 private String getPermanentUri(CmsResource resource) { 964 965 String structureId = resource.getStructureId().toString(); 966 String extension = CmsResource.getExtension(resource.getRootPath()); 967 String suffix = (extension != null) ? "." + extension : ""; 968 String permalink = CmsStringUtil.joinPaths( 969 OpenCms.getSystemInfo().getOpenCmsContext(), 970 CmsPermalinkResourceHandler.PERMALINK_HANDLER, 971 structureId) + suffix; 972 return permalink; 973 } 974 975 /** 976 * Utility function to create a permanent URI for a scaled preview image.<p> 977 * 978 * @param resource the CMS resource 979 * @param highres if true, generate high resolution scaling uri 980 * @return the scale URI 981 */ 982 private String getScaleUri(CmsResource resource, boolean highres) { 983 984 String paramTimestamp = "×tamp=" + System.currentTimeMillis(); 985 return getPermanentUri(resource) + getScaleQueryString(highres) + paramTimestamp; 986 } 987 } 988 989 /** 990 * Class representing a file delete composite with a check box to mark a file as deleted.<p> 991 */ 992 private class FileDeleteComposite extends VerticalLayout { 993 994 /** The default serial version UID. */ 995 private static final long serialVersionUID = 1L; 996 997 /** The component width. */ 998 private static final String WIDTH = "206px"; 999 1000 /** The data item of this file delete composite. */ 1001 private DataItem m_dataItem; 1002 1003 /** 1004 * Creates a new file delete composite for a given data item.<p> 1005 * 1006 * @param dataItem the data item 1007 */ 1008 public FileDeleteComposite(DataItem dataItem) { 1009 1010 m_dataItem = dataItem; 1011 setMargin(new MarginInfo(true, true, true, false)); 1012 setSpacing(false); 1013 setWidth(WIDTH); 1014 setHeightFull(); 1015 Label fileSize = createDisplayFileSize(); 1016 addComponent(fileSize); 1017 Label dimension = createDisplayDimension(); 1018 if (m_dataItem.isTypeImage()) { 1019 addComponent(dimension); 1020 } 1021 if (!m_dataItem.getIsUsed().booleanValue()) { 1022 CssLayout layout = new CssLayout(); 1023 Label labelInUse = createDisplayInUseInfo(); 1024 CheckBox fieldDeleteFlag = createFieldDeleteFlag(); 1025 layout.addComponent(labelInUse); 1026 layout.addComponent(fieldDeleteFlag); 1027 addComponent(layout); 1028 setExpandRatio(layout, 1.0f); 1029 setComponentAlignment(layout, Alignment.BOTTOM_LEFT); 1030 } else if (m_dataItem.isTypeImage()) { 1031 setExpandRatio(dimension, 1.0f); 1032 } else { 1033 setExpandRatio(fileSize, 1.0f); 1034 } 1035 } 1036 1037 /** 1038 * Creates a component displaying the dimension of this editable gallery item.<p> 1039 * 1040 * @return the display component 1041 */ 1042 private Label createDisplayDimension() { 1043 1044 return new Label(getFormattedDimension()); 1045 } 1046 1047 /** 1048 * Creates a component displaying the size of this editable gallery item.<p> 1049 * 1050 * @return the display component 1051 */ 1052 private Label createDisplayFileSize() { 1053 1054 return new Label(getFormattedFileSize()); 1055 } 1056 1057 /** 1058 * Creates a component displaying whether this editable gallery item is used.<p> 1059 * 1060 * @return the display component 1061 */ 1062 private Label createDisplayInUseInfo() { 1063 1064 String notInUse = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_LABEL_NOT_IN_USE_0); 1065 return new Label(notInUse); 1066 } 1067 1068 /** 1069 * Creates a check box to mark an item as deleted.<p> 1070 * 1071 * @return the check box 1072 */ 1073 private CheckBox createFieldDeleteFlag() { 1074 1075 String caption = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_INPUT_DELETE_UNUSED_0); 1076 CheckBox field = new CheckBox(caption); 1077 field.setCaptionAsHtml(true); 1078 field.setWidthFull(); 1079 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1080 m_dataItem.getBinder().bind(field, DataItem::getDeleteFlag, DataItem::setDeleteFlag); 1081 return field; 1082 } 1083 1084 /** 1085 * Returns the dimension of this file in the case the file is an image.<p> 1086 * 1087 * @return the formatted dimension 1088 */ 1089 private String getFormattedDimension() { 1090 1091 String imageSize = null; 1092 try { 1093 imageSize = getCms().readPropertyObject( 1094 m_dataItem.getResource(), 1095 CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, 1096 false).getValue(); 1097 } catch (CmsException e) { 1098 LOG.warn(e.getLocalizedMessage(), e); 1099 } 1100 String dimension = "? x ?"; 1101 if (imageSize != null) { 1102 String[] tokens = imageSize.split(","); 1103 if ((tokens.length == 2) && (tokens[0].length() > 2) && (tokens[1].length() > 2)) { 1104 dimension = tokens[0].substring(2) + " x " + tokens[1].substring(2); 1105 } 1106 } 1107 return dimension; 1108 } 1109 1110 /** 1111 * Returns the size of this editable gallery item in a formatted way.<p> 1112 * 1113 * @return the formatted file size 1114 */ 1115 private String getFormattedFileSize() { 1116 1117 return (m_dataItem.getResource().getLength() / 1024) + " kb"; 1118 } 1119 } 1120 1121 /** 1122 * Filter handler. Keeps track of the query string with which the user 1123 * currently filters the gallery. 1124 */ 1125 private class FilterHandler { 1126 1127 /** The filter query string. */ 1128 private String m_query; 1129 1130 /** 1131 * Creates a new filter handler.<p> 1132 */ 1133 public FilterHandler() { 1134 1135 } 1136 1137 /** 1138 * Returns the filter query string.<p> 1139 * 1140 * @return the filter query string 1141 */ 1142 public String getQuery() { 1143 1144 return m_query != null ? m_query.toLowerCase() : null; 1145 } 1146 1147 /** 1148 * Sets the filter query string.<p> 1149 * 1150 * @param query the filter query string 1151 */ 1152 public void setQuery(String query) { 1153 1154 m_query = query; 1155 } 1156 } 1157 1158 /** 1159 * Class representing a form composite to edit gallery item data.<p> 1160 */ 1161 private class FormComposite extends VerticalLayout { 1162 1163 /** The default serial version UID. */ 1164 private static final long serialVersionUID = 1L; 1165 1166 /** The data item of this form composite. */ 1167 DataItem m_dataItem; 1168 1169 /** 1170 * Creates a new form composite for a given data item.<p> 1171 * 1172 * @param dataItem the data item 1173 */ 1174 public FormComposite(DataItem dataItem) { 1175 1176 m_dataItem = dataItem; 1177 setSizeFull(); 1178 setMargin(true); 1179 setSpacing(false); 1180 addComponent(createCompositeResourceInfo()); 1181 addComponent(createDisplayResourceDate()); 1182 FormLayout formLayout = new FormLayout(); 1183 formLayout.setMargin(false); 1184 formLayout.setSpacing(false); 1185 formLayout.addStyleName(OpenCmsTheme.GALLERY_FORM); 1186 formLayout.addComponent(createFieldTitle()); 1187 formLayout.addComponent(createFieldCopyright()); 1188 formLayout.addComponent(createFieldDescription()); 1189 addComponent(formLayout); 1190 setComponentAlignment(formLayout, Alignment.BOTTOM_LEFT); 1191 setExpandRatio(formLayout, 1.0f); 1192 } 1193 1194 /** 1195 * Returns a composite to display and edit resource information.<p> 1196 * 1197 * @return the component 1198 */ 1199 private CmsResourceInfo createCompositeResourceInfo() { 1200 1201 CmsResourceInfo resourceInfo = new CmsResourceInfo(m_dataItem.getResource()); 1202 resourceInfo.setTopLineText(m_dataItem.getName()); 1203 resourceInfo.decorateTopInput(); 1204 TextField field = resourceInfo.getTopInput(); 1205 m_dataItem.getBinder().bind(field, DataItem::getName, DataItem::setName); 1206 CmsGwtContextMenuButton contextMenu = new CmsGwtContextMenuButton( 1207 m_dataItem.getResource().getStructureId(), 1208 new ContextMenu(m_dataItem)); 1209 contextMenu.addStyleName("o-gwt-contextmenu-button-margin"); 1210 resourceInfo.setButtonWidget(contextMenu); 1211 return resourceInfo; 1212 } 1213 1214 /** 1215 * Returns a component to display resource date information.<p> 1216 * 1217 * @return the component 1218 */ 1219 private Label createDisplayResourceDate() { 1220 1221 String lastModified = formatDateTime(m_dataItem.getDateLastModified().longValue()); 1222 String lastModifiedBy = m_dataItem.getResourceUtil().getUserLastModified(); 1223 String message = CmsVaadinUtils.getMessageText( 1224 Messages.GUI_GALLERY_OPTIMIZE_LASTMODIFIED_BY_2, 1225 lastModified, 1226 lastModifiedBy); 1227 Label label = new Label(message); 1228 label.addStyleNames(ValoTheme.LABEL_LIGHT, ValoTheme.LABEL_TINY); 1229 return label; 1230 } 1231 1232 /** 1233 * Creates the copyright form field.<p> 1234 * 1235 * @return the copyright form field. 1236 */ 1237 private TextField createFieldCopyright() { 1238 1239 String caption = CmsVaadinUtils.getMessageText( 1240 org.opencms.workplace.explorer.Messages.GUI_INPUT_COPYRIGHT_0); 1241 TextField field = new TextField(caption); 1242 field.setWidthFull(); 1243 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1244 m_dataItem.getBinder().bind(field, DataItem::getCopyright, DataItem::setCopyright); 1245 return field; 1246 } 1247 1248 /** 1249 * Creates the description form field.<p> 1250 * 1251 * @return the description form field. 1252 */ 1253 private TextField createFieldDescription() { 1254 1255 String caption = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_INPUT_DESCRIPTION_0); 1256 TextField field = new TextField(caption); 1257 field.setWidthFull(); 1258 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1259 m_dataItem.getBinder().bind(field, DataItem::getDescription, DataItem::setDescription); 1260 return field; 1261 } 1262 1263 /** 1264 * Creates the title form field.<p> 1265 * 1266 * @return the title form field. 1267 */ 1268 private TextField createFieldTitle() { 1269 1270 String caption = CmsVaadinUtils.getMessageText(org.opencms.workplace.explorer.Messages.GUI_INPUT_TITLE_0); 1271 TextField field = new TextField(caption); 1272 field.setWidthFull(); 1273 field.setEnabled(!CmsGalleryOptimizeDialog.this.isReadOnly()); 1274 m_dataItem.getBinder().bind(field, DataItem::getTitle, DataItem::setTitle); 1275 return field; 1276 } 1277 1278 /** 1279 * Utility function for date formatting.<p> 1280 * 1281 * @param date the date to format 1282 * @return the formatted date 1283 */ 1284 private String formatDateTime(long date) { 1285 1286 return CmsDateUtil.getDateTime( 1287 new Date(date), 1288 DateFormat.SHORT, 1289 OpenCms.getWorkplaceManager().getWorkplaceLocale(getCms())); 1290 } 1291 } 1292 1293 /** 1294 * Page handler. Keeps track of the page currently selected by the user. 1295 */ 1296 private class PageHandler { 1297 1298 /** The maximum number of items per page. */ 1299 public static final int LIMIT = 50; 1300 1301 /** The index of the page currently selected by the user. */ 1302 private int m_currentPage = 0; 1303 1304 /** 1305 * Creates a new page handler.<p> 1306 */ 1307 public PageHandler() {} 1308 1309 /** 1310 * Returns the maximum number of items per page.<p> 1311 * 1312 * @return the maximum number of items per page 1313 */ 1314 public int getLimit() { 1315 1316 return LIMIT; 1317 } 1318 1319 /** 1320 * Returns the number of the first item on the page currently selected. 1321 * The first item on the first page has number 1.<p> 1322 * 1323 * @return the number of the first item currently selected 1324 */ 1325 public int getNumFirstItem() { 1326 1327 return (LIMIT * m_currentPage) + 1; 1328 } 1329 1330 /** 1331 * Returns the number of the last item on the page currently selected. 1332 * The number of the last item on the last page is equal to the total number of items. 1333 * 1334 * @return the number of the last item currently selected 1335 */ 1336 public int getNumLastItem() { 1337 1338 int lastItem = ((LIMIT * m_currentPage) + LIMIT); 1339 int sizeItem = getSizeItem(); 1340 if (lastItem > sizeItem) { 1341 lastItem = sizeItem; 1342 } 1343 return lastItem; 1344 } 1345 1346 /** 1347 * Returns the number of available pages.<p> 1348 * 1349 * @return the number of available pages 1350 */ 1351 public int getNumPages() { 1352 1353 return (int)Math.ceil((double)getSizeItem() / PageHandler.LIMIT); 1354 } 1355 1356 /** 1357 * Returns the index of the first item on the page currently selected. 1358 * The index of the first item on the first page is 0.<p> 1359 * 1360 * @return the index of the first item ob the page currently selected 1361 */ 1362 public int getOffset() { 1363 1364 return LIMIT * m_currentPage; 1365 } 1366 1367 /** 1368 * Returns the total number of items.<p> 1369 * 1370 * @return the total number of items 1371 */ 1372 @SuppressWarnings("synthetic-access") 1373 public int getSizeItem() { 1374 1375 return m_provider.size(m_filterHandler); 1376 } 1377 1378 /** 1379 * Returns whether the current data list has pages. 1380 * 1381 * @return whether has pages 1382 */ 1383 public boolean hasPages() { 1384 1385 return getSizeItem() > LIMIT; 1386 } 1387 1388 /** 1389 * Sets the current page index to a given value. 1390 * The index of the first page is 0.<p> 1391 * 1392 * @param index the page index to set 1393 */ 1394 public void setCurrentPage(int index) { 1395 1396 m_currentPage = index; 1397 } 1398 } 1399 1400 /** 1401 * Class representing a data provider for sorting and paging the in-memory data list. 1402 */ 1403 private class Provider extends ListDataProvider<DataItem> { 1404 1405 /** The default serial version UID. */ 1406 private static final long serialVersionUID = 1L; 1407 1408 /** Comparator. */ 1409 final SerializableComparator<DataItem> SORT_DATE_ASCENDING = Comparator.comparing( 1410 DataItem::getDateLastModified)::compare; 1411 1412 /** Comparator. */ 1413 final SerializableComparator<DataItem> SORT_DATE_DESCENDING = Comparator.comparing( 1414 DataItem::getDateLastModified).reversed()::compare; 1415 1416 /** Comparator. */ 1417 final SerializableComparator<DataItem> SORT_NOCOPYRIGHT_FIRST = Comparator.comparing( 1418 DataItem::getNoCopyright)::compare; 1419 1420 /** Comparator. */ 1421 final SerializableComparator<DataItem> SORT_NODESCRIPTION_FIRST = Comparator.comparing( 1422 DataItem::getNoDescription)::compare; 1423 1424 /** Comparator. */ 1425 final SerializableComparator<DataItem> SORT_PATH_ASCENDING = Comparator.comparing( 1426 DataItem::getPath, 1427 String.CASE_INSENSITIVE_ORDER)::compare; 1428 1429 /** Comparator. */ 1430 final SerializableComparator<DataItem> SORT_PATH_DESCENDING = Comparator.comparing( 1431 DataItem::getPath, 1432 String.CASE_INSENSITIVE_ORDER).reversed()::compare; 1433 1434 /** Comparator. */ 1435 final SerializableComparator<DataItem> SORT_TITLE_ASCENDING = Comparator.comparing( 1436 DataItem::getTitle, 1437 String.CASE_INSENSITIVE_ORDER)::compare; 1438 1439 /** Comparator. */ 1440 final SerializableComparator<DataItem> SORT_TITLE_DESCENDING = Comparator.comparing( 1441 DataItem::getTitle, 1442 String.CASE_INSENSITIVE_ORDER).reversed()::compare; 1443 1444 /** Comparator. */ 1445 final SerializableComparator<DataItem> SORT_UNUSED_FIRST = Comparator.comparing(DataItem::getIsUsed)::compare; 1446 1447 /** 1448 * Create a new provider for a given data item list. 1449 * 1450 * @param dataItemList the data item list 1451 */ 1452 public Provider(List<DataItem> dataItemList) { 1453 1454 super(dataItemList); 1455 } 1456 1457 /** 1458 * Fetches one page of the sorted, filtered in-memory data item list.<p> 1459 * 1460 * @param pageHandler the page handler containing offset and limit information 1461 * @param filterHandler the filter handler containing the actual filter query string 1462 * @return the sorted data item page 1463 */ 1464 public List<DataItem> fetch(PageHandler pageHandler, FilterHandler filterHandler) { 1465 1466 SerializablePredicate<DataItem> filter = null; 1467 Query<DataItem, SerializablePredicate<DataItem>> filterQuery = composeFilterQuery(filterHandler); 1468 if (filterQuery != null) { 1469 filter = filterQuery.getFilter().orElse(null); 1470 } 1471 Query<DataItem, SerializablePredicate<DataItem>> query = new Query<DataItem, SerializablePredicate<DataItem>>( 1472 pageHandler.getOffset(), 1473 pageHandler.getLimit(), 1474 Collections.emptyList(), 1475 getSortComparator(), 1476 filter); 1477 return super.fetch(query).collect(Collectors.toList()); 1478 } 1479 1480 /** 1481 * @see com.vaadin.data.provider.ListDataProvider#getItems() 1482 */ 1483 @Override 1484 public List<DataItem> getItems() { 1485 1486 return (List<DataItem>)super.getItems(); 1487 } 1488 1489 /** 1490 * Returns the size of the list respecting the current filter. 1491 * 1492 * @param filterHandler the filter handler 1493 * @return the size 1494 */ 1495 public int size(FilterHandler filterHandler) { 1496 1497 Query<DataItem, SerializablePredicate<DataItem>> filterQuery = composeFilterQuery(filterHandler); 1498 return filterQuery == null ? getItems().size() : super.size(filterQuery); 1499 } 1500 1501 /** 1502 * Composes a provider query for a given filter handler.<p> 1503 * 1504 * @param filterHandler the given filter handler 1505 * @return the provider query 1506 */ 1507 private Query<DataItem, SerializablePredicate<DataItem>> composeFilterQuery(FilterHandler filterHandler) { 1508 1509 Query<DataItem, SerializablePredicate<DataItem>> filterQuery = null; 1510 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(filterHandler.getQuery())) { 1511 filterQuery = new Query<DataItem, SerializablePredicate<DataItem>>( 1512 dataItem -> dataItem.getFilterText().contains(filterHandler.getQuery())); 1513 } 1514 return filterQuery; 1515 } 1516 } 1517 1518 /** 1519 * Utility class to handle dialog save actions. Keeps track of the changes the 1520 * user has made since opening the dialog on the one hand and the changes since 1521 * the last save action on the other. 1522 */ 1523 private class SaveHandler { 1524 1525 /** Data items modified or marked deleted since opening the dialog. */ 1526 Set<DataItem> m_changed = new HashSet<DataItem>(); 1527 1528 /** Data items modified or marked deleted since the last save action. */ 1529 Set<DataItem> m_changedCurrent = new HashSet<DataItem>(); 1530 1531 /** IDs of the resources modified or marked as deleted since opening the dialog. */ 1532 Set<CmsUUID> m_changedIds = new HashSet<CmsUUID>(); 1533 1534 /** Resource IDs modified or marked as deleted since the last save action. */ 1535 Set<CmsUUID> m_changedIdsCurrent = new HashSet<CmsUUID>(); 1536 1537 /** Data items marked deleted since opening the dialog. */ 1538 Set<DataItem> m_deleted = new HashSet<DataItem>(); 1539 1540 /** Data items marked deleted since the last save action. */ 1541 Set<DataItem> m_deletedCurrent = new HashSet<DataItem>(); 1542 1543 /** Resources deleted since the last save action. */ 1544 Set<CmsResource> m_deletedCurrentResource = new HashSet<CmsResource>(); 1545 1546 /** Whether the user has cancelled the last save action. */ 1547 boolean m_flagCancelSave = false; 1548 1549 /** 1550 * Creates a new save handler.<p> 1551 */ 1552 public SaveHandler() {} 1553 1554 /** 1555 * Marks the last save action as cancelled.<p> 1556 * 1557 * @param flagCancelSave Whether to mark as cancelled 1558 */ 1559 public void setFlagCancelSave(boolean flagCancelSave) { 1560 1561 m_flagCancelSave = flagCancelSave; 1562 } 1563 1564 /** 1565 * Returns the data items modified or marked deleted since the last save action.<p> 1566 * 1567 * @return the data items 1568 */ 1569 Set<DataItem> getChangedCurrent() { 1570 1571 return m_changedCurrent; 1572 } 1573 1574 /** 1575 * Returns the IDs of the resources modified or marked as deleted since opening the dialog. 1576 * 1577 * @return the resource IDs 1578 */ 1579 List<CmsUUID> getChangedIds() { 1580 1581 return new ArrayList<CmsUUID>(m_changedIds); 1582 } 1583 1584 /** 1585 * Returns the data items marked deleted since the last save action.<p> 1586 * 1587 * @return the data items 1588 */ 1589 Set<DataItem> getDeletedCurrent() { 1590 1591 return m_deletedCurrent; 1592 } 1593 1594 /** 1595 * Returns the resources marked deleted since the last save action.<p> 1596 * 1597 * @return the resources 1598 */ 1599 List<CmsResource> getDeletedCurrentResource() { 1600 1601 return new ArrayList<CmsResource>(m_deletedCurrentResource); 1602 } 1603 1604 /** 1605 * Whether data items have been marked as deleted since the last save action.<p> 1606 * 1607 * @return whether data items have been marked as deleted or not 1608 */ 1609 boolean hasDeletedCurrent() { 1610 1611 return m_deletedCurrent.size() > 0; 1612 } 1613 1614 /** 1615 * Handles a save action for a given list of data items.<p> 1616 * 1617 * @param dataList the data item list 1618 */ 1619 void save(List<DataItem> dataList) { 1620 1621 if (!m_flagCancelSave) { 1622 flush(); 1623 } 1624 for (DataItem dataItem : dataList) { 1625 boolean dataItemHasChanges = dataItem.getBinder().hasChanges(); 1626 if (dataItemHasChanges) { 1627 try { 1628 dataItem.getBinder().writeBean(dataItem); 1629 m_changedCurrent.add(dataItem); 1630 if (dataItem.getDeleteFlag().booleanValue()) { 1631 m_deletedCurrent.add(dataItem); 1632 m_deletedCurrentResource.add(dataItem.getResource()); 1633 } else if ((dataItem.getDeleteFlag().booleanValue() == false) 1634 && m_deletedCurrent.contains(dataItem)) { 1635 m_deletedCurrent.remove(dataItem); 1636 m_deletedCurrentResource.remove(dataItem.getResource()); 1637 } 1638 } catch (ValidationException e) { 1639 LOG.warn(e.getLocalizedMessage(), e); 1640 } 1641 } 1642 } 1643 } 1644 1645 /** 1646 * Secures and resets the current changes. 1647 */ 1648 private void flush() { 1649 1650 m_deleted.addAll(m_deletedCurrent); 1651 m_changed.addAll(m_changedCurrent); 1652 m_changedIds.addAll(m_changedIdsCurrent); 1653 m_deletedCurrent.clear(); 1654 m_changedCurrent.clear(); 1655 m_changedIdsCurrent.clear(); 1656 m_deletedCurrentResource.clear(); 1657 } 1658 } 1659 1660 /** The height of the preview images. */ 1661 public static final int IMAGE_HEIGHT = 170; 1662 1663 /** The width of the preview images. */ 1664 public static final int IMAGE_WIDTH = 200; 1665 1666 /** The sort order session attribute. */ 1667 static final String GALLERY_OPTIMIZE_ATTR_SORT_ORDER = "GALLERY_OPTIMIZE_ATTR_SORT_ORDER"; 1668 1669 /** Logger instance for this class. */ 1670 static final Log LOG = CmsLog.getLog(CmsGalleryOptimizeDialog.class); 1671 1672 /** The default serial version UID. */ 1673 private static final long serialVersionUID = 1L; 1674 1675 /** The save button. */ 1676 private Button m_buttonSave; 1677 1678 /** The save and exit button. */ 1679 private Button m_buttonSaveAndExit; 1680 1681 /** The UI composite representing the gallery item list header view. */ 1682 private Object m_compositeDataListHeader; 1683 1684 /** The dialog context. */ 1685 private I_CmsDialogContext m_context; 1686 1687 /** The UI component representing the gallery item list header view. */ 1688 private VerticalLayout m_dataListHeaderView; 1689 1690 /** The UI component representing the gallery item list view. */ 1691 private GridLayout m_dataListView; 1692 1693 /** The UI component representing the scrollable wrapper around the gallery item list view. */ 1694 private Panel m_dataListViewScrollable; 1695 1696 /** The filter handler. */ 1697 private FilterHandler m_filterHandler = new FilterHandler(); 1698 1699 /** The gallery */ 1700 private CmsResource m_gallery; 1701 1702 /** The lock action record for the gallery folder. */ 1703 private CmsLockActionRecord m_lockActionRecord; 1704 1705 /** Localized message. */ 1706 private String m_messageSortDateLastModifiedAscending; 1707 1708 /** Localized message. */ 1709 private String m_messageSortDateLastModifiedDescending; 1710 1711 /** Localized message. */ 1712 private String m_messageSortNoCopyrightFirst; 1713 1714 /** Localized message. */ 1715 private String m_messageSortNoDescriptionFirst; 1716 1717 /** Localized message. */ 1718 private String m_messageSortPathAscending; 1719 1720 /** Localized message. */ 1721 private String m_messageSortPathDescending; 1722 1723 /** Localized message. */ 1724 private String m_messageSortTitleAscending; 1725 1726 /** Localized message. */ 1727 private String m_messageSortTitleDescending; 1728 1729 /** Localized message. */ 1730 private String m_messageSortUnusedFirst; 1731 1732 /** The page handler. */ 1733 private PageHandler m_pageHandler = new PageHandler(); 1734 1735 /** The data provider. */ 1736 private Provider m_provider; 1737 1738 /** The save handler. */ 1739 private SaveHandler m_saveHandler = new SaveHandler(); 1740 1741 /** Contains information about unused images. */ 1742 private CssLayout m_unusedInfo = new CssLayout(); 1743 1744 /** 1745 * Creates a new instance of a gallery optimize dialog.<p> 1746 * 1747 * @param context the dialog context 1748 * @param gallery the gallery folder to optimize 1749 */ 1750 public CmsGalleryOptimizeDialog(I_CmsDialogContext context, CmsResource gallery) { 1751 1752 m_context = context; 1753 m_gallery = gallery; 1754 initMessages(); 1755 initDialog(); 1756 initLock(); 1757 initEvents(); 1758 m_unusedInfo.setWidth("100%"); 1759 dataListLoad(); 1760 displayDataListHeaderView(); 1761 displayDataListViewSorted(getSessionSortOrder()); 1762 } 1763 1764 /** 1765 * Gets the scaling parameters for the preview. 1766 * 1767 * @param highres if true, generates high-resolution scaling parameters 1768 * @return the scaling parameters 1769 */ 1770 public static String getScaleParameter(boolean highres) { 1771 1772 int m = highres ? 2 : 1; 1773 String suffix = highres ? ",q:85" : ""; 1774 return "t:9,w:" + (m * IMAGE_WIDTH) + ",h:" + (m * IMAGE_HEIGHT) + suffix; 1775 1776 } 1777 1778 /** 1779 * Gets the scaling query string for the preview. 1780 * @param highres if true, generates high-resolution scaling query string 1781 * @return the scaling parameters 1782 */ 1783 public static String getScaleQueryString(boolean highres) { 1784 1785 return "?__scale=" + getScaleParameter(highres); 1786 } 1787 1788 /** 1789 * Returns the CMS object of this dialog.<p> 1790 * 1791 * @return the CMS object 1792 */ 1793 public CmsObject getCms() { 1794 1795 return m_context.getCms(); 1796 } 1797 1798 /** 1799 * Whether this dialog was opened from a read-only context. 1800 * 1801 * @see com.vaadin.ui.AbstractComponent#isReadOnly() 1802 */ 1803 @Override 1804 protected boolean isReadOnly() { 1805 1806 return m_context.getCms().getRequestContext().getCurrentProject().isOnlineProject(); 1807 } 1808 1809 /** 1810 * Finishes this dialog.<p> 1811 * 1812 * @param changedIds list of IDs of the changed resources 1813 */ 1814 void finishDialog(List<CmsUUID> changedIds) { 1815 1816 m_context.finish(changedIds); 1817 } 1818 1819 /** 1820 * Refreshes the UI state after a list of data items has been successfully deleted.<p> 1821 * 1822 * @param dataItemList the list of deleted data items 1823 */ 1824 void handleDataListDelete(List<DataItem> dataItemList) { 1825 1826 m_provider.getItems().removeAll(dataItemList); 1827 ((DataListHeaderComposite)m_compositeDataListHeader).refresh(); 1828 displayDataListView(false); 1829 } 1830 1831 /** 1832 * Refreshes the UI state after a list of data items has been successfully updated.<p> 1833 * 1834 * @param dataItemList the list of updated data items 1835 * @throws CmsException thrown if reading a persisted CMS resource fails 1836 */ 1837 void handleDataListUpdate(List<DataItem> dataItemList) throws CmsException { 1838 1839 for (DataItem dataItem : dataItemList) { 1840 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireFile(); 1841 CmsResource reload = getCms().readResource(dataItem.getResource().getStructureId(), resourceFilter); 1842 dataItem.setResource(reload); 1843 } 1844 displayDataListView(false); 1845 } 1846 1847 /** 1848 * Event handler handling the dialog attach event. 1849 */ 1850 void handleDialogAttach() { 1851 1852 Window window = CmsVaadinUtils.getWindow(CmsGalleryOptimizeDialog.this); 1853 window.removeAllCloseShortcuts(); // this is because Vaadin by default adds an ESC shortcut to every window 1854 // this is because the grid view unintentionally catches the focus whenever the height of the grid view 1855 // gets larger / smaller than it's containing layout, i.e., whenever the scroll-bar appears or disappears 1856 Page.getCurrent().getStyles().add(".o-gallery-grid-force-scroll { min-height: 1000px; }"); 1857 m_dataListView.addStyleName("o-gallery-grid-force-scroll"); 1858 } 1859 1860 /** 1861 * Event handler that discards all data edited and closes the 1862 * dialog. Asks the user for confirmation beforehand.<p> 1863 */ 1864 void handleDialogCancel() { 1865 1866 if (dataListHasChanges()) { 1867 String title = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_CANCEL_TITLE_0); 1868 String message = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_CANCEL_0); 1869 CmsConfirmationDialog.show(title, message, new Runnable() { 1870 1871 @Override 1872 public void run() { 1873 1874 finishDialog(new ArrayList<CmsUUID>()); 1875 } 1876 }); 1877 } else { 1878 finishDialog(new ArrayList<CmsUUID>()); 1879 } 1880 } 1881 1882 /** 1883 * Event handler that handles the dialog detach event. 1884 */ 1885 void handleDialogDetach() { 1886 1887 unlock(); 1888 } 1889 1890 /** 1891 * Event handler that saves all changes and optionally closes the dialog. If there are data items 1892 * marked as deleted the user is asked for confirmation beforehand. 1893 * 1894 * @param exit whether to exit the dialog 1895 */ 1896 void handleDialogSave(boolean exit) { 1897 1898 m_saveHandler.save(m_provider.getItems()); 1899 if (m_saveHandler.hasDeletedCurrent()) { 1900 String title = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_DELETE_TITLE_0); 1901 String message = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_CONFIRM_DELETE_0); 1902 CmsConfirmationDialog confirmationDialog = CmsConfirmationDialog.show(title, message, new Runnable() { 1903 1904 @SuppressWarnings("synthetic-access") 1905 @Override 1906 public void run() { 1907 1908 persist(exit); 1909 m_saveHandler.setFlagCancelSave(false); 1910 if (exit) { 1911 finishDialog(m_saveHandler.getChangedIds()); 1912 } 1913 } 1914 }, new Runnable() { 1915 1916 @SuppressWarnings("synthetic-access") 1917 @Override 1918 public void run() { 1919 1920 m_saveHandler.setFlagCancelSave(true); 1921 } 1922 1923 }); 1924 confirmationDialog.displayResourceInfo( 1925 m_saveHandler.getDeletedCurrentResource(), 1926 org.opencms.ui.Messages.GUI_SELECTED_0); 1927 } else { 1928 persist(exit); 1929 if (exit) { 1930 finishDialog(m_saveHandler.getChangedIds()); 1931 } 1932 } 1933 } 1934 1935 /** 1936 * For a given gallery folder resource, creates a panel with information whether 1937 * this gallery is in use.<p> 1938 * 1939 * @return the gallery in use panel 1940 * @throws CmsException the CMS exception 1941 */ 1942 private HorizontalLayout createDisplayGalleryInUse() throws CmsException { 1943 1944 String galleryTitle = getGalleryTitle(); 1945 String text = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_DIRECTLY_USED_1, galleryTitle); 1946 HorizontalLayout layout1 = new HorizontalLayout(); 1947 layout1.setWidthFull(); 1948 layout1.addStyleNames("v-panel", "o-error-dialog", OpenCmsTheme.GALLERY_ALERT_IN_USE); 1949 HorizontalLayout layout2 = new HorizontalLayout(); 1950 layout2.setWidthUndefined(); 1951 Label icon = new Label(FontOpenCms.WARNING.getHtml()); 1952 icon.setContentMode(ContentMode.HTML); 1953 icon.setWidthUndefined(); 1954 icon.setStyleName("o-warning-icon"); 1955 Label message = new Label(text); 1956 message.setContentMode(ContentMode.HTML); 1957 message.setWidthUndefined(); 1958 layout2.addComponent(icon); 1959 layout2.addComponent(message); 1960 layout2.setComponentAlignment(message, Alignment.MIDDLE_LEFT); 1961 layout1.addComponent(layout2); 1962 layout1.setComponentAlignment(layout2, Alignment.MIDDLE_CENTER); 1963 return layout1; 1964 } 1965 1966 /** 1967 * Creates a component showing a warning if this dialog was opened from the online project context.<p> 1968 * 1969 * @return the component 1970 */ 1971 private HorizontalLayout createDisplayInOnlineProject() { 1972 1973 String text = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_LABEL_IN_ONLINE_PROJECT_0); 1974 return createNote(text); 1975 } 1976 1977 /** 1978 * Creates an info box. 1979 * 1980 * @param text the text (HTML) to display in the box 1981 * @param styles the additional CSS styles for the info box 1982 * @return 1983 */ 1984 private HorizontalLayout createNote(String text, String... styles) { 1985 1986 HorizontalLayout layout = new HorizontalLayout(); 1987 layout.setWidthFull(); 1988 layout.addStyleNames("v-panel", "o-error-dialog"); 1989 layout.addStyleNames(styles); 1990 Label icon = new Label(FontOpenCms.WARNING.getHtml()); 1991 icon.setContentMode(ContentMode.HTML); 1992 icon.setWidthUndefined(); 1993 icon.setStyleName("o-warning-icon"); 1994 Label message = new Label(text); 1995 message.setContentMode(ContentMode.HTML); 1996 message.setWidthUndefined(); 1997 layout.addComponent(icon); 1998 layout.addComponent(message); 1999 layout.setComponentAlignment(message, Alignment.MIDDLE_LEFT); 2000 layout.setExpandRatio(message, 1.0f); 2001 return layout; 2002 } 2003 2004 /** 2005 * Creates a note widget to display above the list. 2006 * 2007 * @param html the HTML content 2008 * @param styles the additional CSS classes 2009 * @return the created widget 2010 */ 2011 private HorizontalLayout createSimpleNote(String html, String... styles) { 2012 2013 HorizontalLayout layout1 = new HorizontalLayout(); 2014 layout1.setWidthFull(); 2015 layout1.addStyleNames("v-panel", "o-error-dialog"); 2016 layout1.addStyleName("o-optimize-gallery-note"); 2017 layout1.addStyleNames(styles); 2018 Label message = new Label(html); 2019 message.setContentMode(ContentMode.HTML); 2020 layout1.addComponent(message); 2021 return layout1; 2022 } 2023 2024 /** 2025 * Whether one of the editable gallery items has been modified by the user.<p> 2026 * 2027 * @return whether has changes 2028 */ 2029 private boolean dataListHasChanges() { 2030 2031 boolean hasChanges = false; 2032 for (DataItem dataItem : m_provider.getItems()) { 2033 if (dataItem.getBinder().hasChanges()) { 2034 hasChanges = true; 2035 } 2036 } 2037 return hasChanges; 2038 } 2039 2040 /** 2041 * Loads the gallery item list.<p> 2042 */ 2043 private void dataListLoad() { 2044 2045 List<DataItem> dataList = new ArrayList<DataItem>(); 2046 CmsObject cms = A_CmsUI.getCmsObject(); 2047 try { 2048 CmsResourceFilter resourceFilter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireFile(); 2049 List<CmsResource> resources = cms.readResources(cms.getSitePath(m_gallery), resourceFilter); 2050 for (CmsResource resource : resources) { 2051 DataItem dataItem = new DataItem(resource); 2052 dataList.add(dataItem); 2053 } 2054 } catch (CmsException exception) { 2055 m_context.error(exception); 2056 } 2057 m_provider = new Provider(dataList); 2058 } 2059 2060 /** 2061 * Displays the UI component representing the dialog header view.<p> 2062 */ 2063 private void displayDataListHeaderView() { 2064 2065 if (isReadOnly()) { 2066 m_dataListHeaderView.addComponent(createDisplayInOnlineProject()); 2067 } else { 2068 try { 2069 List<CmsRelation> relations = getCms().getRelationsForResource(m_gallery, CmsRelationFilter.SOURCES); 2070 if ((relations != null) && !relations.isEmpty()) { 2071 m_dataListHeaderView.addComponent(createDisplayGalleryInUse()); 2072 } 2073 } catch (CmsException e) { 2074 LOG.warn(e.getLocalizedMessage(), e); 2075 } 2076 } 2077 m_compositeDataListHeader = new DataListHeaderComposite(); 2078 2079 m_dataListHeaderView.addComponent((Component)m_compositeDataListHeader); 2080 m_dataListHeaderView.addComponent(m_unusedInfo); 2081 } 2082 2083 /** 2084 * Displays the UI component representing the scrollable gallery item list.<p> 2085 * 2086 * @param scrollToTop whether to scroll to top after displaying the gallery item list 2087 */ 2088 private void displayDataListView(boolean scrollToTop) { 2089 2090 m_dataListView.removeAllComponents(); 2091 List<DataItem> dataItemList = m_provider.fetch(m_pageHandler, m_filterHandler); 2092 m_dataListView.setColumns(3); 2093 m_dataListView.setRows(dataItemList.size() + 2); 2094 m_dataListView.setColumnExpandRatio(2, 1.0f); 2095 int i = 1; 2096 Label dummy = new Label(" "); 2097 dummy.setId("scrollToTop"); 2098 dummy.setHeight("0px"); 2099 m_dataListView.addComponent(dummy, 0, 0); 2100 for (DataItem dataItem : dataItemList) { 2101 dataItem.getCompositeFile().removeStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 2102 dataItem.getCompositeFileDelete().removeStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 2103 dataItem.getCompositeForm().removeStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 2104 if ((i % 2) == 0) { 2105 dataItem.getCompositeFile().addStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 2106 dataItem.getCompositeFileDelete().addStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 2107 dataItem.getCompositeForm().addStyleName(OpenCmsTheme.GALLERY_GRID_ROW_ODD); 2108 } 2109 m_dataListView.addComponent(dataItem.getCompositeFile(), 0, i); 2110 m_dataListView.addComponent(dataItem.getCompositeFileDelete(), 1, i); 2111 m_dataListView.addComponent(dataItem.getCompositeForm(), 2, i); 2112 i++; 2113 } 2114 updateUnusedInfo(); 2115 if (scrollToTop) { 2116 m_dataListViewScrollable.setScrollTop(0); 2117 } 2118 } 2119 2120 /** 2121 * Sorts the gallery item list according to a given sort order and re-renders the 2122 * gallery item list view.<p> 2123 * 2124 * @param sortOrder the sort order 2125 */ 2126 private void displayDataListViewSorted(String sortOrder) { 2127 2128 SerializableComparator<DataItem> defaultSortOrder = m_provider.SORT_PATH_ASCENDING; 2129 if (sortOrder == null) { 2130 m_provider.setSortComparator(m_provider.SORT_TITLE_ASCENDING); 2131 } else if (sortOrder == m_messageSortTitleAscending) { 2132 m_provider.setSortComparator(m_provider.SORT_TITLE_ASCENDING); 2133 } else if (sortOrder == m_messageSortTitleDescending) { 2134 m_provider.setSortComparator(m_provider.SORT_TITLE_DESCENDING); 2135 } else if (sortOrder == m_messageSortDateLastModifiedAscending) { 2136 m_provider.setSortComparator(m_provider.SORT_DATE_ASCENDING); 2137 } else if (sortOrder == m_messageSortDateLastModifiedDescending) { 2138 m_provider.setSortComparator(m_provider.SORT_DATE_DESCENDING); 2139 } else if (sortOrder == m_messageSortPathAscending) { 2140 m_provider.setSortComparator(m_provider.SORT_PATH_ASCENDING); 2141 } else if (sortOrder == m_messageSortPathDescending) { 2142 m_provider.setSortComparator(m_provider.SORT_PATH_DESCENDING); 2143 } else if (sortOrder == m_messageSortUnusedFirst) { 2144 m_provider.setSortComparator(m_provider.SORT_UNUSED_FIRST); 2145 } else if (sortOrder == m_messageSortNoCopyrightFirst) { 2146 m_provider.setSortComparator(m_provider.SORT_NOCOPYRIGHT_FIRST); 2147 } else if (sortOrder == m_messageSortNoDescriptionFirst) { 2148 m_provider.setSortComparator(m_provider.SORT_NODESCRIPTION_FIRST); 2149 } else { 2150 m_provider.setSortComparator(defaultSortOrder); 2151 } 2152 setSessionSortOrder(sortOrder); 2153 displayDataListView(true); 2154 } 2155 2156 /** 2157 * Returns the gallery title for a given gallery folder resource.<p> 2158 * 2159 * @return the title 2160 * @throws CmsException the CMS exception 2161 */ 2162 private String getGalleryTitle() throws CmsException { 2163 2164 String galleryTitle = getCms().readPropertyObject( 2165 m_gallery, 2166 CmsPropertyDefinition.PROPERTY_TITLE, 2167 false).getValue(); 2168 if (CmsStringUtil.isEmptyOrWhitespaceOnly(galleryTitle)) { 2169 galleryTitle = m_gallery.getName(); 2170 } 2171 return galleryTitle; 2172 } 2173 2174 /** 2175 * Returns the current sort order saved in the user session with lazy initialization.<p> 2176 * 2177 * @return the sort order 2178 */ 2179 private String getSessionSortOrder() { 2180 2181 WrappedSession wrappedSession = VaadinService.getCurrentRequest().getWrappedSession(); 2182 String currentSortOrder = (String)wrappedSession.getAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER); 2183 if (currentSortOrder == null) { 2184 wrappedSession.setAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER, m_messageSortPathAscending); 2185 } 2186 return (String)wrappedSession.getAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER); 2187 } 2188 2189 /** 2190 * Initializes this dialog.<p> 2191 */ 2192 private void initDialog() { 2193 2194 CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null); 2195 displayResourceInfo(m_gallery); 2196 Button buttonCancel = createButtonCancel(); 2197 buttonCancel.addClickListener(event -> { 2198 CmsGalleryOptimizeDialog.this.handleDialogCancel(); 2199 }); 2200 addButton(buttonCancel, false); 2201 m_buttonSave.setEnabled(!isReadOnly()); 2202 m_buttonSaveAndExit.setEnabled(!isReadOnly()); 2203 } 2204 2205 /** 2206 * Initializes the events of this dialog.<p> 2207 */ 2208 private void initEvents() { 2209 2210 m_buttonSave.addClickListener(event -> { 2211 CmsGalleryOptimizeDialog.this.handleDialogSave(false); 2212 }); 2213 m_buttonSaveAndExit.addClickListener(event -> { 2214 CmsGalleryOptimizeDialog.this.handleDialogSave(true); 2215 }); 2216 setActionHandler(new CmsOkCancelActionHandler() { 2217 2218 private static final long serialVersionUID = 1L; 2219 2220 @Override 2221 protected void cancel() { 2222 2223 CmsGalleryOptimizeDialog.this.handleDialogCancel(); 2224 } 2225 2226 @Override 2227 protected void ok() { 2228 2229 CmsGalleryOptimizeDialog.this.handleDialogSave(true); 2230 } 2231 }); 2232 addAttachListener(event -> { 2233 CmsGalleryOptimizeDialog.this.handleDialogAttach(); 2234 }); 2235 addDetachListener(event -> { 2236 CmsGalleryOptimizeDialog.this.handleDialogDetach(); 2237 }); 2238 } 2239 2240 /** 2241 * Locks the gallery folder.<p> 2242 */ 2243 private void initLock() { 2244 2245 try { 2246 m_lockActionRecord = CmsLockUtil.ensureLock(getCms(), m_gallery); 2247 } catch (CmsException e) { 2248 LOG.warn(e.getLocalizedMessage(), e); 2249 } 2250 } 2251 2252 /** 2253 * Initializes the localized messages of this dialog.<p> 2254 */ 2255 private void initMessages() { 2256 2257 m_messageSortDateLastModifiedAscending = CmsVaadinUtils.getMessageText( 2258 Messages.GUI_GALLERY_OPTIMIZE_SORT_DATE_MODIFIED_ASCENDING_0); 2259 m_messageSortDateLastModifiedDescending = CmsVaadinUtils.getMessageText( 2260 Messages.GUI_GALLERY_OPTIMIZE_SORT_DATE_MODIFIED_DESCENDING_0); 2261 m_messageSortPathAscending = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_SORT_PATH_ASCENDING_0); 2262 m_messageSortPathDescending = CmsVaadinUtils.getMessageText( 2263 Messages.GUI_GALLERY_OPTIMIZE_SORT_PATH_DESCENDING_0); 2264 m_messageSortTitleAscending = CmsVaadinUtils.getMessageText( 2265 Messages.GUI_GALLERY_OPTIMIZE_SORT_TITLE_ASCENDING_0); 2266 m_messageSortTitleDescending = CmsVaadinUtils.getMessageText( 2267 Messages.GUI_GALLERY_OPTIMIZE_SORT_TITLE_DESCENDING_0); 2268 m_messageSortUnusedFirst = CmsVaadinUtils.getMessageText(Messages.GUI_GALLERY_OPTIMIZE_SORT_UNUSED_FIRST_0); 2269 m_messageSortNoCopyrightFirst = CmsVaadinUtils.getMessageText( 2270 Messages.GUI_GALLERY_OPTIMIZE_SORT_NOCOPYRIGHT_FIRST_0); 2271 m_messageSortNoDescriptionFirst = CmsVaadinUtils.getMessageText( 2272 Messages.GUI_GALLERY_OPTIMIZE_SORT_NODESCRIPTION_FIRST_0); 2273 } 2274 2275 /** 2276 * Persists all data changes that have not been saved yet. Refreshes the UI. 2277 * Informs the user about failed updates, failed renames and failed deletes.<p> 2278 * 2279 * @param exit true if we exit the dialog after saving 2280 */ 2281 private void persist(boolean exit) { 2282 2283 StringBuilder errorMessageList = new StringBuilder(); 2284 persistUpdateAndRename(errorMessageList); 2285 persistDelete(errorMessageList); 2286 2287 // In the embedded dialog case, using the Vaadin notifications when exiting the dialog doesn't work (the iframe 2288 // is hidden before the notifications show up). 2289 // We don't really need notifications for the successful case, but when errors occur, we send them to the 2290 // ADE notification mechanism. 2291 boolean embeddedExit = (m_context instanceof CmsEmbeddedDialogContext) && exit; 2292 2293 if (errorMessageList.length() == 0) { 2294 2295 if (!embeddedExit) { 2296 String message = CmsVaadinUtils.getMessageText( 2297 Messages.GUI_GALLERY_OPTIMIZE_LABEL_SUCCESSFULLY_SAVED_0); 2298 Notification notification = new Notification(message, "", Notification.Type.HUMANIZED_MESSAGE); 2299 notification.setPosition(Position.TOP_CENTER); 2300 notification.show(Page.getCurrent()); 2301 } 2302 } else { 2303 if (embeddedExit) { 2304 ((CmsEmbeddedDialogContext)m_context).sendNotification(true, errorMessageList.toString()); 2305 } else { 2306 Notification notification = new Notification( 2307 "", 2308 errorMessageList.toString(), 2309 Notification.Type.ERROR_MESSAGE); 2310 notification.setHtmlContentAllowed(true); 2311 notification.setPosition(Position.TOP_CENTER); 2312 notification.show(Page.getCurrent()); 2313 } 2314 } 2315 } 2316 2317 /** 2318 * Persists all deleted gallery items.<p> 2319 * 2320 * @param errorMessageList string builder to append error messages 2321 */ 2322 private void persistDelete(StringBuilder errorMessageList) { 2323 2324 List<DataItem> deleted = new ArrayList<DataItem>(); 2325 for (DataItem dataItem : m_saveHandler.getDeletedCurrent()) { 2326 CmsResource resource = dataItem.getResource(); 2327 try { 2328 getCms().deleteResource(getCms().getSitePath(resource), CmsResource.DELETE_PRESERVE_SIBLINGS); 2329 deleted.add(dataItem); 2330 } catch (CmsException e) { 2331 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2332 LOG.warn(e.getLocalizedMessage(), e); 2333 } 2334 } 2335 handleDataListDelete(deleted); 2336 if (m_saveHandler.hasDeletedCurrent()) { 2337 displayDataListView(true); 2338 } 2339 } 2340 2341 /** 2342 * Persists all updated and renamed gallery items.<p> 2343 * 2344 * @param errorMessageList string builder to append error messages 2345 */ 2346 private void persistUpdateAndRename(StringBuilder errorMessageList) { 2347 2348 List<DataItem> updated = new ArrayList<DataItem>(); 2349 for (DataItem dataItem : m_saveHandler.getChangedCurrent()) { 2350 CmsResource resource = dataItem.getResource(); 2351 if (dataItem.hasChanges()) { 2352 try { 2353 getCms().writePropertyObjects(resource, dataItem.getPropertyList()); 2354 getCms().writeResource(resource); 2355 updated.add(dataItem); 2356 } catch (CmsException e) { 2357 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2358 LOG.warn(e.getLocalizedMessage(), e); 2359 } 2360 } 2361 if (dataItem.isRenamed()) { 2362 String source = getCms().getSitePath(resource); 2363 String destination = CmsStringUtil.joinPaths(CmsResource.getParentFolder(source), dataItem.getName()); 2364 try { 2365 getCms().renameResource(source, destination); 2366 if (!updated.contains(dataItem)) { 2367 updated.add(dataItem); 2368 } 2369 } catch (CmsException e) { 2370 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2371 LOG.warn(e.getLocalizedMessage(), e); 2372 } 2373 } 2374 } 2375 try { 2376 handleDataListUpdate(updated); 2377 } catch (CmsException e) { 2378 errorMessageList.append("<div>" + e.getLocalizedMessage() + "</div>"); 2379 LOG.warn(e.getLocalizedMessage(), e); 2380 } 2381 } 2382 2383 /** 2384 * Saves the selected sort order in the user session.<p> 2385 * 2386 * @param sortOrder the sort order 2387 */ 2388 private void setSessionSortOrder(String sortOrder) { 2389 2390 WrappedSession wrappedSession = VaadinService.getCurrentRequest().getWrappedSession(); 2391 wrappedSession.setAttribute(GALLERY_OPTIMIZE_ATTR_SORT_ORDER, sortOrder); 2392 } 2393 2394 /** 2395 * Unlocks the gallery folder.<p> 2396 */ 2397 private void unlock() { 2398 2399 if (m_lockActionRecord != null) { 2400 try { 2401 getCms().unlockResource(m_gallery); 2402 } catch (CmsException e) { 2403 LOG.warn(e.getLocalizedMessage(), e); 2404 } 2405 } 2406 } 2407 2408 /** 2409 * Updates the 'unused elements' information. 2410 */ 2411 private void updateUnusedInfo() { 2412 2413 m_unusedInfo.removeAllComponents(); 2414 m_unusedInfo.setVisible(true); 2415 boolean isImageGallery = OpenCms.getResourceManager().matchResourceType("imagegallery", m_gallery.getTypeId()); 2416 if (m_provider.getSortComparator() == m_provider.SORT_UNUSED_FIRST) { 2417 long unusedCount = m_provider.getItems().stream().filter(item -> !item.getIsUsed()).count(); 2418 if (unusedCount == 0) { 2419 String text = CmsVaadinUtils.getMessageText( 2420 isImageGallery 2421 ? Messages.GUI_GALLERY_OPTIMIZE_NO_UNUSED_0 2422 : Messages.GUI_GALLERY_OPTIMIZE_NO_UNUSED_DOWNLOADS_0); 2423 m_unusedInfo.addComponent(createSimpleNote(text, "o-optimize-gallery-warning")); 2424 } else { 2425 String text = CmsVaadinUtils.getMessageText( 2426 isImageGallery 2427 ? Messages.GUI_GALLERY_OPTIMIZE_NUM_UNUSED_1 2428 : Messages.GUI_GALLERY_OPTIMIZE_NUM_UNUSED_DOWNLOADS_1, 2429 unusedCount); 2430 m_unusedInfo.addComponent(createSimpleNote(text)); 2431 } 2432 } else if (m_provider.getSortComparator() == m_provider.SORT_NOCOPYRIGHT_FIRST) { 2433 long noCopyright = m_provider.getItems().stream().filter(item -> !item.getNoCopyright()).count(); 2434 if (noCopyright == 0) { 2435 String text = CmsVaadinUtils.getMessageText( 2436 isImageGallery 2437 ? Messages.GUI_GALLERY_OPTIMIZE_NO_NOCOPYRIGHT_0 2438 : Messages.GUI_GALLERY_OPTIMIZE_NO_NOCOPYRIGHT_DOWNLOADS_0); 2439 m_unusedInfo.addComponent(createSimpleNote(text, "o-optimize-gallery-warning")); 2440 } else if (noCopyright == 1) { 2441 String text = CmsVaadinUtils.getMessageText( 2442 isImageGallery 2443 ? Messages.GUI_GALLERY_OPTIMIZE_ONE_NOCOPYRIGHT_0 2444 : Messages.GUI_GALLERY_OPTIMIZE_ONE_NOCOPYRIGHT_DOWNLOADS_0); 2445 m_unusedInfo.addComponent(createSimpleNote(text)); 2446 } else { 2447 String text = CmsVaadinUtils.getMessageText( 2448 isImageGallery 2449 ? Messages.GUI_GALLERY_OPTIMIZE_NUM_NOCOPYRIGHT_1 2450 : Messages.GUI_GALLERY_OPTIMIZE_NUM_NOCOPYRIGHT_DOWNLOADS_1, 2451 noCopyright); 2452 m_unusedInfo.addComponent(createSimpleNote(text)); 2453 } 2454 } else if (m_provider.getSortComparator() == m_provider.SORT_NODESCRIPTION_FIRST) { 2455 long noDescription = m_provider.getItems().stream().filter(item -> !item.getNoDescription()).count(); 2456 if (noDescription == 0) { 2457 String text = CmsVaadinUtils.getMessageText( 2458 isImageGallery 2459 ? Messages.GUI_GALLERY_OPTIMIZE_NO_NODESCRIPTION_0 2460 : Messages.GUI_GALLERY_OPTIMIZE_NO_NODESCRIPTION_DOWNLOADS_0); 2461 m_unusedInfo.addComponent(createSimpleNote(text, "o-optimize-gallery-warning")); 2462 } else if (noDescription == 1) { 2463 String text = CmsVaadinUtils.getMessageText( 2464 isImageGallery 2465 ? Messages.GUI_GALLERY_OPTIMIZE_ONE_NODESCRIPTION_0 2466 : Messages.GUI_GALLERY_OPTIMIZE_ONE_NODESCRIPTION_DOWNLOADS_0); 2467 m_unusedInfo.addComponent(createSimpleNote(text)); 2468 } else { 2469 String text = CmsVaadinUtils.getMessageText( 2470 isImageGallery 2471 ? Messages.GUI_GALLERY_OPTIMIZE_NUM_NODESCRIPTION_1 2472 : Messages.GUI_GALLERY_OPTIMIZE_NUM_NODESCRIPTION_DOWNLOADS_1, 2473 noDescription); 2474 2475 m_unusedInfo.addComponent(createSimpleNote(text)); 2476 } 2477 } else { 2478 m_unusedInfo.setVisible(false); 2479 } 2480 2481 } 2482 2483}