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