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