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.apps; 029 030import org.opencms.db.CmsUserSettings; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProject; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.CmsVfsResourceNotFoundException; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.security.CmsPermissionSet; 040import org.opencms.ui.A_CmsUI; 041import org.opencms.ui.CmsVaadinUtils; 042import org.opencms.ui.FontOpenCms; 043import org.opencms.ui.I_CmsDialogContext; 044import org.opencms.ui.I_CmsDialogContext.ContextType; 045import org.opencms.ui.I_CmsUpdateListener; 046import org.opencms.ui.actions.CmsCopyDialogAction; 047import org.opencms.ui.actions.CmsPropertiesDialogAction; 048import org.opencms.ui.actions.I_CmsWorkplaceAction; 049import org.opencms.ui.components.A_CmsFocusShortcutListener; 050import org.opencms.ui.components.CmsErrorDialog; 051import org.opencms.ui.components.CmsFileTable; 052import org.opencms.ui.components.CmsResourceIcon; 053import org.opencms.ui.components.CmsResourceTableProperty; 054import org.opencms.ui.components.CmsToolBar; 055import org.opencms.ui.components.CmsUploadButton; 056import org.opencms.ui.components.CmsUploadButton.I_UploadListener; 057import org.opencms.ui.components.I_CmsWindowCloseListener; 058import org.opencms.ui.components.OpenCmsTheme; 059import org.opencms.ui.components.extensions.CmsUploadAreaExtension; 060import org.opencms.ui.contextmenu.CmsResourceContextMenuBuilder; 061import org.opencms.ui.dialogs.CmsCopyMoveDialog; 062import org.opencms.ui.dialogs.CmsDeleteDialog; 063import org.opencms.ui.dialogs.CmsNewDialog; 064import org.opencms.util.CmsStringUtil; 065import org.opencms.util.CmsUUID; 066import org.opencms.workplace.CmsWorkplace; 067import org.opencms.workplace.explorer.CmsResourceUtil; 068 069import java.util.ArrayList; 070import java.util.Arrays; 071import java.util.Collection; 072import java.util.Collections; 073import java.util.HashMap; 074import java.util.HashSet; 075import java.util.List; 076import java.util.Map; 077import java.util.Set; 078 079import org.apache.commons.lang.RandomStringUtils; 080import org.apache.commons.logging.Log; 081 082import com.google.common.collect.Lists; 083import com.google.common.collect.Sets; 084import com.vaadin.event.Action; 085import com.vaadin.event.FieldEvents.BlurEvent; 086import com.vaadin.event.FieldEvents.FocusEvent; 087import com.vaadin.event.ShortcutAction; 088import com.vaadin.event.ShortcutAction.KeyCode; 089import com.vaadin.event.dd.DragAndDropEvent; 090import com.vaadin.event.dd.DropHandler; 091import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; 092import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; 093import com.vaadin.navigator.ViewChangeListener; 094import com.vaadin.server.Page; 095import com.vaadin.server.Sizeable.Unit; 096import com.vaadin.ui.Button; 097import com.vaadin.ui.Button.ClickEvent; 098import com.vaadin.ui.Button.ClickListener; 099import com.vaadin.ui.CssLayout; 100import com.vaadin.ui.HorizontalSplitPanel; 101import com.vaadin.ui.Notification; 102import com.vaadin.ui.UI; 103import com.vaadin.ui.themes.ValoTheme; 104import com.vaadin.v7.data.Container; 105import com.vaadin.v7.data.Item; 106import com.vaadin.v7.data.Property.ValueChangeEvent; 107import com.vaadin.v7.data.Property.ValueChangeListener; 108import com.vaadin.v7.data.util.HierarchicalContainer; 109import com.vaadin.v7.data.util.IndexedContainer; 110import com.vaadin.v7.event.FieldEvents.TextChangeEvent; 111import com.vaadin.v7.event.FieldEvents.TextChangeListener; 112import com.vaadin.v7.event.ItemClickEvent; 113import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; 114import com.vaadin.v7.shared.ui.combobox.FilteringMode; 115import com.vaadin.v7.ui.AbstractSelect.AbstractSelectTargetDetails; 116import com.vaadin.v7.ui.ComboBox; 117import com.vaadin.v7.ui.HorizontalLayout; 118import com.vaadin.v7.ui.Table; 119import com.vaadin.v7.ui.Table.TableDragMode; 120import com.vaadin.v7.ui.TextField; 121import com.vaadin.v7.ui.Tree; 122import com.vaadin.v7.ui.Tree.CollapseEvent; 123import com.vaadin.v7.ui.Tree.CollapseListener; 124import com.vaadin.v7.ui.Tree.ExpandEvent; 125import com.vaadin.v7.ui.Tree.ExpandListener; 126import com.vaadin.v7.ui.Tree.ItemStyleGenerator; 127import com.vaadin.v7.ui.Tree.TreeDragMode; 128 129/** 130 * The file explorer app.<p> 131 */ 132@SuppressWarnings("deprecation") 133public class CmsFileExplorer 134implements I_CmsWorkplaceApp, I_CmsCachableApp, ViewChangeListener, I_CmsWindowCloseListener, I_CmsHasShortcutActions, 135I_CmsContextProvider, CmsFileTable.I_FolderSelectHandler { 136 137 /** The drop handler for copy/move operations. */ 138 public class ExplorerDropHandler implements DropHandler { 139 140 /** The serial version id. */ 141 private static final long serialVersionUID = 5392136127699472654L; 142 143 /** The copy move action. */ 144 transient final I_CmsWorkplaceAction m_copyMoveAction = new CmsCopyDialogAction(); 145 146 /** 147 * @see com.vaadin.event.dd.DropHandler#drop(com.vaadin.event.dd.DragAndDropEvent) 148 */ 149 public void drop(DragAndDropEvent dragEvent) { 150 151 try { 152 CmsExplorerDialogContext context = getContext(dragEvent); 153 if (m_copyMoveAction.isActive(context)) { 154 CmsCopyMoveDialog dialog = new CmsCopyMoveDialog( 155 context, 156 CmsCopyMoveDialog.DialogMode.copy_and_move); 157 dialog.setTargetFolder(getTargetId(dragEvent)); 158 context.start( 159 CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_DIALOGTITLE_COPYMOVE_0), 160 dialog); 161 } 162 } catch (Exception e) { 163 LOG.error("Moving resource failed", e); 164 } 165 166 } 167 168 /** 169 * @see com.vaadin.event.dd.DropHandler#getAcceptCriterion() 170 */ 171 public AcceptCriterion getAcceptCriterion() { 172 173 return new ServerSideCriterion() { 174 175 private static final long serialVersionUID = 1L; 176 177 public boolean accept(DragAndDropEvent dragEvent) { 178 179 try { 180 if (!m_copyMoveAction.isActive(getContext(dragEvent))) { 181 return false; 182 } 183 } catch (CmsException e) { 184 LOG.error("Drag an drop evaluation failed", e); 185 return false; 186 } 187 CmsUUID targetId = getTargetId(dragEvent); 188 return mayDrop(targetId); 189 } 190 }; 191 } 192 193 /** 194 * Returns the drag target id.<p> 195 * 196 * @param dragEvent the drag event 197 * 198 * @return the drag target id 199 */ 200 protected CmsUUID getTargetId(DragAndDropEvent dragEvent) { 201 202 CmsUUID targetId = null; 203 if (dragEvent.getTargetDetails() instanceof AbstractSelectTargetDetails) { 204 AbstractSelectTargetDetails target = (AbstractSelectTargetDetails)dragEvent.getTargetDetails(); 205 Object itemOverId = target.getItemIdOver(); 206 if (itemOverId instanceof CmsUUID) { 207 targetId = (CmsUUID)itemOverId; 208 } else if (itemOverId instanceof String) { 209 targetId = m_fileTable.getUUIDFromItemID((String)itemOverId); 210 } 211 } 212 try { 213 CmsObject cms = A_CmsUI.getCmsObject(); 214 CmsResource target = cms.readResource(targetId); 215 if (target.isFile()) { 216 targetId = null; 217 } 218 } catch (CmsException e) { 219 targetId = null; 220 LOG.debug("Checking drop target failed, use current folder.", e); 221 } 222 223 if (targetId == null) { 224 targetId = getCurrentFolder(); 225 } 226 return targetId; 227 } 228 229 /** 230 * Evaluates if a drop on the given target is allowed.<p> 231 * 232 * @param targetId the target id 233 * 234 * @return <code>true</code> if the resources may be dropped to the given target 235 */ 236 protected boolean mayDrop(CmsUUID targetId) { 237 238 boolean result = false; 239 try { 240 CmsObject cms = A_CmsUI.getCmsObject(); 241 CmsResource target = cms.readResource(targetId); 242 result = cms.hasPermissions( 243 target, 244 CmsPermissionSet.ACCESS_WRITE, 245 false, 246 CmsResourceFilter.ONLY_VISIBLE_NO_DELETED); 247 } catch (Exception e) { 248 LOG.debug("Checking folder write permissions failed", e); 249 } 250 return result; 251 } 252 253 /** 254 * Returns the dialog context to use.<p> 255 * 256 * @param dragEvent the drag event 257 * 258 * @return the dialog context 259 * 260 * @throws CmsException if reading the drag resource fails 261 */ 262 CmsExplorerDialogContext getContext(DragAndDropEvent dragEvent) throws CmsException { 263 264 List<CmsResource> resources; 265 if ((dragEvent.getTransferable().getSourceComponent() instanceof Table) 266 && !m_fileTable.getSelectedResources().isEmpty()) { 267 resources = m_fileTable.getSelectedResources(); 268 } else { 269 CmsObject cms = A_CmsUI.getCmsObject(); 270 CmsUUID sourceId = m_fileTable.getUUIDFromItemID((String)dragEvent.getTransferable().getData("itemId")); 271 CmsResource source = cms.readResource(sourceId); 272 resources = Collections.singletonList(source); 273 } 274 CmsExplorerDialogContext context = new CmsExplorerDialogContext( 275 ContextType.fileTable, 276 m_fileTable, 277 CmsFileExplorer.this, 278 resources); 279 return context; 280 } 281 } 282 283 /** 284 * File tree expand listener.<p> 285 */ 286 public class TreeExpandListener implements ExpandListener { 287 288 /** The serial version id. */ 289 private static final long serialVersionUID = 1L; 290 /** 291 * The path fragment being opened. 292 * Will override folder visibility in case the the target path is visible to the user. 293 **/ 294 private String m_openPathFragment; 295 296 /** 297 * @see com.vaadin.ui.Tree.ExpandListener#nodeExpand(com.vaadin.ui.Tree.ExpandEvent) 298 */ 299 public void nodeExpand(ExpandEvent event) { 300 301 selectTreeItem((CmsUUID)event.getItemId()); 302 readTreeLevel((CmsUUID)event.getItemId(), m_openPathFragment); 303 } 304 305 /** 306 * Sets the open path fragment.<p> 307 * 308 * @param openPathFragment the open path fragment 309 */ 310 public void setOpenPathFragment(String openPathFragment) { 311 312 m_openPathFragment = openPathFragment; 313 } 314 } 315 316 /** Bean representing the file explorer navigation substate. */ 317 static class StateBean { 318 319 /** Current folder. */ 320 private String m_folder; 321 322 /** Project id. */ 323 private String m_projectId; 324 325 /**selected resource.*/ 326 private String m_selectedResource; 327 328 /** The site root. */ 329 private String m_siteRoot; 330 331 /** 332 * Creates a new state bean.<p> 333 * 334 * @param siteRoot the site root 335 * @param folder the folder 336 * @param projectId the project id 337 */ 338 public StateBean(String siteRoot, String folder, String projectId) { 339 340 m_siteRoot = siteRoot; 341 m_folder = folder; 342 m_projectId = projectId; 343 if ("".equals(m_siteRoot)) { 344 m_siteRoot = "/"; 345 } 346 } 347 348 /** 349 * Parses the state bean from a string.<p> 350 * 351 * @param state the state string 352 * @return the state bean 353 */ 354 public static StateBean parse(String state) { 355 356 List<String> fields = CmsStringUtil.splitAsList(state, "!!"); 357 if (fields.size() >= 3) { 358 String projectId = fields.get(0); 359 String siteRoot = fields.get(1); 360 String folder = fields.get(2); 361 StateBean ret = new StateBean(siteRoot, folder, projectId); 362 if (fields.size() == 4) { 363 ret.setSelectedResource(fields.get(3)); 364 } 365 return ret; 366 } else { 367 return new StateBean(null, null, null); 368 } 369 } 370 371 /** 372 * Converts state bean to a string.<p> 373 * 374 * @return the string format of the state 375 */ 376 public String asString() { 377 378 String result = m_projectId + "!!" + m_siteRoot + "!!" + m_folder + "!!"; 379 return result; 380 } 381 382 /** 383 * Returns the folderId.<p> 384 * 385 * @return the folderId 386 */ 387 public String getFolder() { 388 389 return m_folder; 390 } 391 392 /** 393 * Returns the projectId.<p> 394 * 395 * @return the projectId 396 */ 397 public String getProjectId() { 398 399 return m_projectId; 400 } 401 402 /** 403 * Returns the resource to select, empty if no one was set.<p> 404 * 405 * @return UUID as string 406 */ 407 public String getSelectedResource() { 408 409 return m_selectedResource == null ? "" : m_selectedResource; 410 } 411 412 /** 413 * Returns the siteRoot.<p> 414 * 415 * @return the siteRoot 416 */ 417 public String getSiteRoot() { 418 419 return m_siteRoot; 420 } 421 422 /** 423 * Sets a resource to be selected.<p> 424 * 425 * @param resource to get selected 426 */ 427 public void setSelectedResource(String resource) { 428 429 m_selectedResource = resource; 430 } 431 } 432 433 /** The file explorer attribute key. */ 434 public static final String ATTR_KEY = "CmsFileExplorer"; 435 436 /** The in line editable resource properties. */ 437 public static final Collection<CmsResourceTableProperty> INLINE_EDIT_PROPERTIES = Arrays.asList( 438 CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, 439 CmsResourceTableProperty.PROPERTY_TITLE, 440 CmsResourceTableProperty.PROPERTY_NAVIGATION_TEXT, 441 CmsResourceTableProperty.PROPERTY_COPYRIGHT, 442 CmsResourceTableProperty.PROPERTY_CACHE); 443 444 /** The initial split position between folder tree and file table. */ 445 public static final int LAYOUT_SPLIT_POSITION = 399; 446 447 /** The opened paths session attribute name. */ 448 public static final String OPENED_PATHS = "explorer-opened-paths"; 449 450 /** Site selector caption property. */ 451 public static final String SITE_CAPTION = "site_caption"; 452 453 /** Site selector site root property. */ 454 public static final String SITE_ROOT = "site_root"; 455 456 /** The state separator string. */ 457 public static final String STATE_SEPARATOR = "!!"; 458 459 /** Threshold for updating the complete folder after file changes. */ 460 public static final int UPDATE_FOLDER_THRESHOLD = 200; 461 462 /** Logger instance for this class. */ 463 static final Log LOG = CmsLog.getLog(CmsFileExplorer.class); 464 465 /** The delete shortcut. */ 466 private static final Action ACTION_DELETE = new ShortcutAction("Del", ShortcutAction.KeyCode.DELETE, null); 467 468 /** The open parent folder shortcut. */ 469 private static final Action ACTION_FOLDER_UP = new ShortcutAction( 470 "Alt+ArrowUp", 471 ShortcutAction.KeyCode.ARROW_UP, 472 new int[] {ShortcutAction.ModifierKey.ALT}); 473 474 /** The edit properties shortcut. */ 475 private static final Action ACTION_PROPERTIES = new ShortcutAction( 476 "Alt+Enter", 477 ShortcutAction.KeyCode.ENTER, 478 new int[] {ShortcutAction.ModifierKey.ALT}); 479 480 /** The rename shortcut. */ 481 private static final Action ACTION_RENAME = new ShortcutAction("F2", ShortcutAction.KeyCode.F2, null); 482 483 /** The select all shortcut. */ 484 private static final Action ACTION_SELECT_ALL = new ShortcutAction( 485 "Ctrl+A", 486 ShortcutAction.KeyCode.A, 487 new int[] {ShortcutAction.ModifierKey.CTRL}); 488 489 /** The select all shortcut, (using Apple CMD as modifier). */ 490 private static final Action ACTION_SELECT_ALL_CMD = new ShortcutAction( 491 "CMD+A", 492 ShortcutAction.KeyCode.A, 493 new int[] {ShortcutAction.ModifierKey.META}); 494 495 /** The switch online shortcut. */ 496 private static final Action ACTION_SWITCH_ONLINE = new ShortcutAction( 497 "Ctrl+O", 498 ShortcutAction.KeyCode.O, 499 new int[] {ShortcutAction.ModifierKey.CTRL}); 500 501 /** The switch online shortcut, (using Apple CMD as modifier). */ 502 private static final Action ACTION_SWITCH_ONLINE_CMD = new ShortcutAction( 503 "CMD+O", 504 ShortcutAction.KeyCode.O, 505 new int[] {ShortcutAction.ModifierKey.META}); 506 507 /** The files and folder resource filter. */ 508 private static final CmsResourceFilter FILES_N_FOLDERS = CmsResourceFilter.ONLY_VISIBLE; 509 510 /** The folders resource filter. */ 511 private static final CmsResourceFilter FOLDERS = CmsResourceFilter.ONLY_VISIBLE_NO_DELETED.addRequireFolder(); 512 513 /** The serial version id. */ 514 private static final long serialVersionUID = 1L; 515 516 /** The UI context. */ 517 protected I_CmsAppUIContext m_appContext; 518 519 /** Saved explorer state used by dialogs after they have finished. */ 520 protected String m_savedExplorerState = ""; 521 522 /** The table containing the contents of the current folder. */ 523 CmsFileTable m_fileTable; 524 525 /** The info path. */ 526 TextField m_infoPath; 527 528 /** The explorer shortcuts. */ 529 Map<Action, Runnable> m_shortcutActions; 530 531 /** The bread crumb click listener. */ 532 private ClickListener m_crumbListener; 533 534 /** The path bread crumb container. */ 535 private CssLayout m_crumbs; 536 537 /** The currently viewed folder. */ 538 private CmsUUID m_currentFolder; 539 540 /** The current app state. */ 541 private String m_currentState; 542 543 /** The tree expand listener. */ 544 private TreeExpandListener m_expandListener; 545 546 /** The folder tree. */ 547 private Tree m_fileTree; 548 549 /** The first visible file table item index. */ 550 private int m_firstVisibleTableItemIndex; 551 552 /** The last context menu resources. */ 553 private List<CmsResource> m_lastDialogContextResources; 554 555 /** The quick launch location cache. */ 556 private CmsQuickLaunchLocationCache m_locationCache; 557 558 /** The new button. */ 559 private Button m_newButton; 560 561 /** The publish button. */ 562 private Button m_publishButton; 563 564 /** The search field. */ 565 private TextField m_searchField; 566 567 /** the currently selected file tree folder, may be null. */ 568 private CmsUUID m_selectTreeFolder; 569 570 /** The site selector. */ 571 private ComboBox m_siteSelector; 572 573 /** The folder tree data container. */ 574 private HierarchicalContainer m_treeContainer; 575 576 /** The upload drop area extension. */ 577 private CmsUploadAreaExtension m_uploadArea; 578 579 /** The upload button. */ 580 private CmsUploadButton m_uploadButton; 581 582 /** 583 * Constructor.<p> 584 */ 585 public CmsFileExplorer() { 586 587 m_shortcutActions = new HashMap<Action, Runnable>(); 588 m_shortcutActions.put(ACTION_DELETE, new Runnable() { 589 590 public void run() { 591 592 if (!m_fileTable.getSelectedIds().isEmpty()) { 593 I_CmsDialogContext context1 = getDialogContext(); 594 context1.start("Delete", new CmsDeleteDialog(context1)); 595 } 596 } 597 }); 598 599 m_shortcutActions.put(ACTION_FOLDER_UP, new Runnable() { 600 601 public void run() { 602 603 showParentFolder(); 604 } 605 }); 606 607 m_shortcutActions.put(ACTION_PROPERTIES, new Runnable() { 608 609 public void run() { 610 611 I_CmsWorkplaceAction propAction = new CmsPropertiesDialogAction(); 612 I_CmsDialogContext context = getDialogContext(); 613 if (propAction.getVisibility(context).isActive()) { 614 propAction.executeAction(context); 615 } 616 } 617 }); 618 619 m_shortcutActions.put(ACTION_RENAME, new Runnable() { 620 621 public void run() { 622 623 CmsExplorerDialogContext context = getDialogContext(); 624 if (context.isPropertyEditable(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME)) { 625 context.editProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME); 626 } 627 } 628 }); 629 Runnable selectAll = new Runnable() { 630 631 public void run() { 632 633 m_fileTable.selectAll(); 634 } 635 }; 636 m_shortcutActions.put(ACTION_SELECT_ALL, selectAll); 637 m_shortcutActions.put(ACTION_SELECT_ALL_CMD, selectAll); 638 639 Runnable switchOnline = new Runnable() { 640 641 public void run() { 642 643 toggleOnlineOffline(); 644 } 645 }; 646 m_shortcutActions.put(ACTION_SWITCH_ONLINE, switchOnline); 647 m_shortcutActions.put(ACTION_SWITCH_ONLINE_CMD, switchOnline); 648 649 m_fileTable = new CmsFileTable(this); 650 m_fileTable.setSizeFull(); 651 m_fileTable.setMenuBuilder(new CmsResourceContextMenuBuilder()); 652 m_fileTable.setFolderSelectHandler(this); 653 m_uploadArea = new CmsUploadAreaExtension(m_fileTable); 654 m_uploadArea.addUploadListener(new I_UploadListener() { 655 656 public void onUploadFinished(List<String> uploadedFiles) { 657 658 updateAll(true); 659 } 660 }); 661 m_treeContainer = new HierarchicalContainer(); 662 addTreeContainerProperties( 663 CmsResourceTableProperty.PROPERTY_RESOURCE_NAME, 664 CmsResourceTableProperty.PROPERTY_STATE, 665 CmsResourceTableProperty.PROPERTY_TREE_CAPTION, 666 CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT, 667 CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED, 668 CmsResourceTableProperty.PROPERTY_DISABLED); 669 m_fileTree = new Tree(); 670 m_fileTree.addStyleName(OpenCmsTheme.SIMPLE_DRAG); 671 m_fileTree.addStyleName(OpenCmsTheme.FULL_WIDTH_PADDING); 672 m_fileTree.setWidth("100%"); 673 m_fileTree.setContainerDataSource(m_treeContainer); 674 // m_fileTree.setItemIconPropertyId(CmsResourceTableProperty.PROPERTY_TYPE_ICON_RESOURCE); 675 m_fileTree.setItemCaptionPropertyId(CmsResourceTableProperty.PROPERTY_TREE_CAPTION); 676 // m_fileTree.setCaptionAsHtml(true); 677 m_fileTree.setHtmlContentAllowed(true); 678 m_expandListener = new TreeExpandListener(); 679 m_fileTree.addExpandListener(m_expandListener); 680 m_fileTree.addCollapseListener(new CollapseListener() { 681 682 private static final long serialVersionUID = 1L; 683 684 public void nodeCollapse(CollapseEvent event) { 685 686 selectTreeItem((CmsUUID)event.getItemId()); 687 clearTreeLevel((CmsUUID)event.getItemId()); 688 } 689 }); 690 691 m_fileTree.addItemClickListener(new ItemClickListener() { 692 693 private static final long serialVersionUID = 1L; 694 695 public void itemClick(ItemClickEvent event) { 696 697 handleFileTreeClick(event); 698 } 699 }); 700 701 m_fileTree.setItemStyleGenerator(new ItemStyleGenerator() { 702 703 private static final long serialVersionUID = 1L; 704 705 public String getStyle(Tree source, Object itemId) { 706 707 return CmsFileTable.getStateStyle(source.getContainerDataSource().getItem(itemId)); 708 } 709 }); 710 m_fileTree.addValueChangeListener(new ValueChangeListener() { 711 712 private static final long serialVersionUID = 1L; 713 714 public void valueChange(ValueChangeEvent event) { 715 716 handleFileTreeValueChange(); 717 } 718 719 }); 720 721 m_fileTree.setNullSelectionAllowed(false); 722 723 // init drag and drop 724 ExplorerDropHandler handler = new ExplorerDropHandler(); 725 m_fileTable.setDropHandler(handler); 726 m_fileTable.setDragMode(TableDragMode.MULTIROW); 727 m_fileTree.setDropHandler(handler); 728 m_fileTree.setDragMode(TreeDragMode.NONE); 729 730 m_siteSelector = createSiteSelect(A_CmsUI.getCmsObject()); 731 m_infoPath = new TextField(); 732 A_CmsFocusShortcutListener shortcutListener = new A_CmsFocusShortcutListener("Open path", KeyCode.ENTER, null) { 733 734 private static final long serialVersionUID = 1L; 735 736 @Override 737 public void blur(BlurEvent event) { 738 739 super.blur(event); 740 showCrumbs(true); 741 } 742 743 @Override 744 public void focus(FocusEvent event) { 745 746 super.focus(event); 747 showCrumbs(false); 748 } 749 750 @Override 751 public void handleAction(Object sender, Object target) { 752 753 openPath(m_infoPath.getValue()); 754 } 755 }; 756 shortcutListener.installOn(m_infoPath); 757 758 m_crumbs = new CssLayout(); 759 m_crumbs.setPrimaryStyleName(OpenCmsTheme.CRUMBS); 760 m_crumbListener = new ClickListener() { 761 762 private static final long serialVersionUID = 1L; 763 764 public void buttonClick(ClickEvent event) { 765 766 openPath((String)event.getButton().getData()); 767 } 768 }; 769 770 m_searchField = new TextField(); 771 m_searchField.setIcon(FontOpenCms.FILTER); 772 m_searchField.setInputPrompt( 773 Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_FILTER_0)); 774 m_searchField.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); 775 m_searchField.addTextChangeListener(new TextChangeListener() { 776 777 private static final long serialVersionUID = 1L; 778 779 public void textChange(TextChangeEvent event) { 780 781 filterTable(event.getText()); 782 783 } 784 }); 785 786 m_locationCache = CmsQuickLaunchLocationCache.getLocationCache(CmsAppWorkplaceUi.get().getHttpSession()); 787 String startSite = CmsWorkplace.getStartSiteRoot( 788 A_CmsUI.getCmsObject(), 789 CmsAppWorkplaceUi.get().getWorkplaceSettings()); 790 // remove trailing slashes 791 while (startSite.endsWith("/")) { 792 startSite = startSite.substring(0, startSite.length() - 1); 793 } 794 if (m_locationCache.getFileExplorerLocation(startSite) == null) { 795 // add the configured start folder for the start site 796 String startFolder = CmsAppWorkplaceUi.get().getWorkplaceSettings().getUserSettings().getStartFolder(); 797 m_locationCache.setFileExplorerLocation(startSite, startFolder); 798 } 799 } 800 801 /** 802 * @see com.vaadin.navigator.ViewChangeListener#afterViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent) 803 */ 804 public void afterViewChange(ViewChangeEvent event) { 805 806 m_fileTable.setFirstVisibleItemIndex(m_firstVisibleTableItemIndex); 807 } 808 809 /** 810 * @see com.vaadin.navigator.ViewChangeListener#beforeViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent) 811 */ 812 public boolean beforeViewChange(ViewChangeEvent event) { 813 814 m_firstVisibleTableItemIndex = m_fileTable.getFirstVisibleItemIndex(); 815 816 OpenCms.getWorkplaceAppManager().storeAppSettings( 817 A_CmsUI.getCmsObject(), 818 CmsFileExplorerSettings.class, 819 m_fileTable.getTableSettings()); 820 return true; 821 } 822 823 /** 824 * Changes to the given site and path.<p> 825 * 826 * @param siteRoot the site root 827 * @param path the path inside the site 828 */ 829 public void changeSite(String siteRoot, String path) { 830 831 changeSite(siteRoot, path, false); 832 833 } 834 835 /** 836 * Switches to the requested site.<p> 837 * 838 * @param siteRoot the site root 839 * @param path the folder path to open 840 * @param force force the path change, even if we are currently in the same site 841 */ 842 public void changeSite(String siteRoot, String path, boolean force) { 843 844 CmsObject cms = A_CmsUI.getCmsObject(); 845 String currentSiteRoot = cms.getRequestContext().getSiteRoot(); 846 if (force || !currentSiteRoot.equals(siteRoot)) { 847 CmsAppWorkplaceUi.get().changeSite(siteRoot); 848 if (path == null) { 849 path = m_locationCache.getFileExplorerLocation(siteRoot); 850 if (CmsStringUtil.isEmptyOrWhitespaceOnly(siteRoot) 851 && ((path == null) || !path.startsWith("/system"))) { 852 // switching to the root site and previous root site folder is not below /system 853 // -> stay in the current folder 854 path = m_locationCache.getFileExplorerLocation(currentSiteRoot); 855 if (path != null) { 856 path = CmsStringUtil.joinPaths(currentSiteRoot, path); 857 } 858 } 859 } 860 openPath(path, true); 861 Container container = m_siteSelector.getContainerDataSource(); 862 for (Object id : container.getItemIds()) { 863 String key = (String)id; 864 if (CmsStringUtil.comparePaths(key, siteRoot)) { 865 siteRoot = key; 866 break; 867 } 868 } 869 m_siteSelector.select(siteRoot); 870 } 871 } 872 873 /** 874 * Gets all ids of resources in current folder.<p> 875 * 876 * @return the 877 */ 878 public List<CmsUUID> getAllIds() { 879 880 return m_fileTable.getAllIds(); 881 } 882 883 /** 884 * Returns the current folder id.<p> 885 * 886 * @return the current folder structure id 887 */ 888 public CmsUUID getCurrentFolder() { 889 890 return m_currentFolder; 891 } 892 893 /** 894 * @see org.opencms.ui.apps.I_CmsContextProvider#getDialogContext() 895 */ 896 public CmsExplorerDialogContext getDialogContext() { 897 898 List<CmsResource> resources = m_fileTable.getSelectedResources(); 899 m_lastDialogContextResources = resources; 900 CmsExplorerDialogContext context = new CmsExplorerDialogContext( 901 ContextType.fileTable, 902 m_fileTable, 903 this, 904 resources); 905 context.setEditableProperties(INLINE_EDIT_PROPERTIES); 906 return context; 907 } 908 909 /** 910 * @see org.opencms.ui.apps.I_CmsHasShortcutActions#getShortcutActions() 911 */ 912 public Map<Action, Runnable> getShortcutActions() { 913 914 return m_shortcutActions; 915 } 916 917 /** 918 * @see org.opencms.ui.apps.I_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext) 919 */ 920 public void initUI(I_CmsAppUIContext context) { 921 922 m_appContext = context; 923 m_appContext.setAttribute(ATTR_KEY, this); 924 m_appContext.setMenuDialogContext( 925 new CmsExplorerDialogContext(ContextType.appToolbar, m_fileTable, this, null)); 926 HorizontalSplitPanel sp = new HorizontalSplitPanel(); 927 sp.setSizeFull(); 928 sp.setFirstComponent(m_fileTree); 929 CmsFileExplorerSettings settings; 930 try { 931 settings = OpenCms.getWorkplaceAppManager().getAppSettings( 932 A_CmsUI.getCmsObject(), 933 CmsFileExplorerSettings.class); 934 935 m_fileTable.setTableState(settings); 936 } catch (Exception e) { 937 LOG.error("Error while reading file explorer settings from user.", e); 938 } 939 sp.setSecondComponent(m_fileTable); 940 941 sp.setSplitPosition(LAYOUT_SPLIT_POSITION, Unit.PIXELS); 942 943 context.setAppContent(sp); 944 context.showInfoArea(true); 945 HorizontalLayout inf = new HorizontalLayout(); 946 inf.setSizeFull(); 947 inf.setSpacing(true); 948 inf.setMargin(true); 949 m_siteSelector.setWidth("379px"); 950 inf.addComponent(m_siteSelector); 951 CssLayout crumbWrapper = new CssLayout(); 952 crumbWrapper.setSizeFull(); 953 crumbWrapper.setPrimaryStyleName(OpenCmsTheme.CRUMB_WRAPPER); 954 crumbWrapper.addComponent(m_crumbs); 955 956 m_infoPath.setWidth("100%"); 957 crumbWrapper.addComponent(m_infoPath); 958 inf.addComponent(crumbWrapper); 959 inf.setExpandRatio(crumbWrapper, 1); 960 961 m_searchField.setWidth("200px"); 962 inf.addComponent(m_searchField); 963 context.setAppInfo(inf); 964 965 initToolbarButtons(context); 966 m_fileTable.updateColumnWidths(A_CmsUI.get().getPage().getBrowserWindowWidth() - LAYOUT_SPLIT_POSITION); 967 } 968 969 /** 970 * @see org.opencms.ui.apps.I_CmsCachableApp#isCachable() 971 */ 972 public boolean isCachable() { 973 974 return true; 975 } 976 977 /** 978 * @see org.opencms.ui.components.CmsFileTable.I_FolderSelectHandler#onFolderSelect(org.opencms.util.CmsUUID) 979 */ 980 public void onFolderSelect(CmsUUID itemId) { 981 982 expandCurrentFolder(); 983 if (m_fileTree.getItem(itemId) != null) { 984 m_fileTree.select(itemId); 985 } 986 try { 987 readFolder(itemId); 988 } catch (CmsException e) { 989 CmsErrorDialog.showErrorDialog( 990 CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, itemId), 991 e); 992 LOG.error(e.getLocalizedMessage(), e); 993 } 994 } 995 996 /** 997 * @see org.opencms.ui.apps.I_CmsCachableApp#onRestoreFromCache() 998 */ 999 public void onRestoreFromCache() { 1000 1001 if (m_lastDialogContextResources != null) { 1002 List<CmsUUID> updateIds = Lists.newArrayList(); 1003 for (CmsResource resource : m_lastDialogContextResources) { 1004 updateIds.add(resource.getStructureId()); 1005 } 1006 update(updateIds); 1007 } 1008 1009 } 1010 1011 /** 1012 * Call if site and or project have been changed.<p> 1013 * 1014 * @param project the project 1015 * @param siteRoot the site root 1016 */ 1017 public void onSiteOrProjectChange(CmsProject project, String siteRoot) { 1018 1019 if ((siteRoot != null) && !siteRoot.equals(getSiteRootFromState())) { 1020 changeSite(siteRoot, null, true); 1021 } else if ((project != null) && !project.getUuid().equals(getProjectIdFromState())) { 1022 openPath(getPathFromState(), true); 1023 } 1024 m_appContext.updateOnChange(); 1025 setToolbarButtonsEnabled(!CmsAppWorkplaceUi.isOnlineProject()); 1026 } 1027 1028 /** 1029 * @see org.opencms.ui.apps.I_CmsWorkplaceApp#onStateChange(java.lang.String) 1030 */ 1031 public void onStateChange(String state) { 1032 1033 state = normalizeState(state); 1034 if ((m_currentState == null) || !m_currentState.equals(state)) { 1035 m_currentState = state; 1036 CmsObject cms = A_CmsUI.getCmsObject(); 1037 String siteRoot = getSiteRootFromState(); 1038 String path = getPathFromState(); 1039 1040 CmsUUID projectId = getProjectIdFromState(); 1041 if ((projectId != null) && !cms.getRequestContext().getCurrentProject().getUuid().equals(projectId)) { 1042 try { 1043 CmsProject project = cms.readProject(projectId); 1044 cms.getRequestContext().setCurrentProject(project); 1045 CmsAppWorkplaceUi.get().getWorkplaceSettings().setProject(project.getUuid()); 1046 } catch (CmsException e) { 1047 LOG.warn("Error reading project from history state", e); 1048 } 1049 changeSite(siteRoot, path, true); 1050 } else if ((siteRoot != null) 1051 && !CmsStringUtil.comparePaths(siteRoot, cms.getRequestContext().getSiteRoot())) { 1052 String saveState = m_currentState; 1053 changeSite(siteRoot, path); 1054 if (!getSelectionFromState(saveState).isEmpty()) { 1055 m_fileTable.setValue(Collections.singleton(getSelectionFromState(saveState))); 1056 } 1057 } else { 1058 String saveState = m_currentState; 1059 openPath(path, true); 1060 if (!getSelectionFromState(saveState).isEmpty()) { 1061 m_fileTable.setValue(Collections.singleton(getSelectionFromState(saveState))); 1062 } 1063 } 1064 } 1065 } 1066 1067 /** 1068 * @see org.opencms.ui.components.I_CmsWindowCloseListener#onWindowClose() 1069 */ 1070 public void onWindowClose() { 1071 1072 OpenCms.getWorkplaceAppManager().storeAppSettings( 1073 A_CmsUI.getCmsObject(), 1074 CmsFileExplorerSettings.class, 1075 m_fileTable.getTableSettings()); 1076 } 1077 1078 /** 1079 * Fills the file table with the resources from the given path.<p> 1080 * 1081 * @param sitePath a folder site path 1082 */ 1083 public void populateFileTable(String sitePath) { 1084 1085 CmsObject cms = A_CmsUI.getCmsObject(); 1086 m_searchField.clear(); 1087 m_firstVisibleTableItemIndex = 0; 1088 try { 1089 List<CmsResource> folderResources = cms.readResources(sitePath, FILES_N_FOLDERS, false); 1090 m_fileTable.fillTable(cms, folderResources); 1091 } catch (CmsException e) { 1092 CmsErrorDialog.showErrorDialog(e); 1093 LOG.error(e.getLocalizedMessage(), e); 1094 } 1095 } 1096 1097 /** 1098 * Updates the table entries with the given ids.<p> 1099 * 1100 * @param ids the ids of the table entries to update 1101 */ 1102 public void update(Collection<CmsUUID> ids) { 1103 1104 try { 1105 CmsResource currentFolderRes = A_CmsUI.getCmsObject().readResource(m_currentFolder, CmsResourceFilter.ALL); 1106 boolean updateFolder = m_fileTable.getItemCount() < UPDATE_FOLDER_THRESHOLD; 1107 Set<CmsUUID> removeIds = new HashSet<CmsUUID>(); 1108 for (CmsUUID id : ids) { 1109 boolean remove = false; 1110 try { 1111 CmsResource resource = A_CmsUI.getCmsObject().readResource(id, CmsResourceFilter.ALL); 1112 1113 remove = !CmsResource.getParentFolder(resource.getRootPath()).equals( 1114 currentFolderRes.getRootPath()); 1115 1116 } catch (CmsVfsResourceNotFoundException e) { 1117 remove = true; 1118 LOG.debug("Could not read update resource " + id, e); 1119 } 1120 if (remove) { 1121 removeIds.add(id); 1122 } 1123 1124 } 1125 for (CmsUUID id : ids) { 1126 updateTree(id); 1127 } 1128 if (updateFolder) { 1129 updateCurrentFolder(removeIds); 1130 } else { 1131 m_fileTable.update(removeIds, true); 1132 HashSet<CmsUUID> updateIds = new HashSet<CmsUUID>(ids); 1133 ids.removeAll(removeIds); 1134 m_fileTable.update(updateIds, false); 1135 } 1136 m_fileTable.updateSorting(); 1137 m_fileTable.clearSelection(); 1138 } catch (CmsException e) { 1139 CmsErrorDialog.showErrorDialog(e); 1140 } 1141 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_searchField.getValue())) { 1142 filterTable(m_searchField.getValue()); 1143 } 1144 } 1145 1146 /** 1147 * Updates display for all contents of the current folder.<p> 1148 * 1149 * @param clearFilter <code>true</code> to clear the search filter 1150 */ 1151 public void updateAll(boolean clearFilter) { 1152 1153 try { 1154 readFolder(m_currentFolder, clearFilter); 1155 Set<Object> ids = Sets.newHashSet(); 1156 ids.addAll(m_fileTree.getItemIds()); 1157 for (Object id : ids) { 1158 updateTree((CmsUUID)id); 1159 } 1160 } catch (CmsException e) { 1161 CmsErrorDialog.showErrorDialog( 1162 CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, m_currentFolder), 1163 e); 1164 LOG.error(e.getLocalizedMessage(), e); 1165 } 1166 } 1167 1168 /** 1169 * Updates the give tree item.<p> 1170 * 1171 * @param cms the cms context 1172 * @param id the item id 1173 */ 1174 public void updateResourceInTree(CmsObject cms, CmsUUID id) { 1175 1176 try { 1177 CmsResource resource = cms.readResource(id, CmsResourceFilter.IGNORE_EXPIRATION); 1178 if (resource.isFile()) { 1179 return; 1180 } 1181 // ensuring to only add items belonging to the current site 1182 if (!resource.getRootPath().startsWith(cms.getRequestContext().getSiteRoot())) { 1183 m_treeContainer.removeItemRecursively(id); 1184 return; 1185 } 1186 1187 CmsResource parent = cms.readParentFolder(id); 1188 CmsUUID parentId = null; 1189 if (parent != null) { 1190 parentId = parent.getStructureId(); 1191 } else { 1192 LOG.debug("Parent for resource '" + resource.getRootPath() + "' is null"); 1193 } 1194 Item resourceItem = m_treeContainer.getItem(id); 1195 if (resourceItem != null) { 1196 // use the root path as name in case of the root item 1197 String resName = parentId == null ? resource.getRootPath() : resource.getName(); 1198 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).setValue(resName); 1199 CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource); 1200 String iconHTML = CmsResourceIcon.getTreeCaptionHTML(resName, resUtil, null, false); 1201 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_TREE_CAPTION).setValue(iconHTML); 1202 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_STATE).setValue(resource.getState()); 1203 if (parentId != null) { 1204 m_treeContainer.setParent(resource.getStructureId(), parentId); 1205 } 1206 } else { 1207 addTreeItem(cms, resource, parentId, false, m_treeContainer); 1208 } 1209 m_fileTree.markAsDirty(); 1210 } catch (CmsVfsResourceNotFoundException e) { 1211 m_treeContainer.removeItemRecursively(id); 1212 LOG.debug(e.getLocalizedMessage(), e); 1213 } catch (CmsException e) { 1214 CmsErrorDialog.showErrorDialog(e); 1215 LOG.error(e.getLocalizedMessage(), e); 1216 } 1217 } 1218 1219 /** 1220 * Updates the tree items with the given ids.<p> 1221 * 1222 * @param id the 1223 */ 1224 public void updateTree(CmsUUID id) { 1225 1226 CmsObject cms = A_CmsUI.getCmsObject(); 1227 updateResourceInTree(cms, id); 1228 1229 } 1230 1231 /** 1232 * Clears the given tree level.<p> 1233 * 1234 * @param parentId the parent id 1235 */ 1236 protected void clearTreeLevel(CmsUUID parentId) { 1237 1238 // create a new list to avoid concurrent modifications 1239 Collection<?> children = m_treeContainer.getChildren(parentId); 1240 // may be null when monkey clicking 1241 if (children != null) { 1242 List<Object> childIds = new ArrayList<Object>(m_treeContainer.getChildren(parentId)); 1243 for (Object childId : childIds) { 1244 m_treeContainer.removeItemRecursively(childId); 1245 } 1246 } 1247 } 1248 1249 /** 1250 * Reads the given folder.<p> 1251 * 1252 * @param folderId the folder id 1253 * 1254 * @throws CmsException in case reading the folder fails 1255 */ 1256 protected void readFolder(CmsUUID folderId) throws CmsException { 1257 1258 readFolder(folderId, true); 1259 } 1260 1261 /** 1262 * Reads the given folder.<p> 1263 * 1264 * @param folderId the folder id 1265 * @param clearFilter <code>true</code> to clear the search filter 1266 * 1267 * @throws CmsException in case reading the folder fails 1268 */ 1269 protected void readFolder(CmsUUID folderId, boolean clearFilter) throws CmsException { 1270 1271 CmsObject cms = A_CmsUI.getCmsObject(); 1272 if (clearFilter) { 1273 m_searchField.clear(); 1274 } 1275 CmsResource folder = cms.readResource(folderId, FOLDERS); 1276 m_currentFolder = folderId; 1277 String folderPath = cms.getSitePath(folder); 1278 if (OpenCms.getSiteManager().isSharedFolder(cms.getRequestContext().getSiteRoot())) { 1279 folderPath = folderPath.substring(cms.getRequestContext().getSiteRoot().length()); 1280 } 1281 setPathInfo(folderPath); 1282 List<CmsResource> childResources = cms.readResources(folder, FILES_N_FOLDERS, false); 1283 m_fileTable.fillTable(cms, childResources, clearFilter); 1284 boolean hasFolderChild = false; 1285 for (CmsResource child : childResources) { 1286 if (child.isFolder()) { 1287 hasFolderChild = true; 1288 break; 1289 } 1290 } 1291 m_treeContainer.setChildrenAllowed(folderId, hasFolderChild); 1292 String sitePath = folder.getRootPath().equals(cms.getRequestContext().getSiteRoot() + "/") ? "" : folderPath; 1293 String state = new StateBean( 1294 cms.getRequestContext().getSiteRoot(), 1295 sitePath, 1296 cms.getRequestContext().getCurrentProject().getUuid().toString()).asString(); 1297 if (LOG.isDebugEnabled()) { 1298 String p = RandomStringUtils.randomAlphanumeric(5) + " readFolder "; 1299 LOG.debug(p + "siteRoot = " + cms.getRequestContext().getSiteRoot()); 1300 LOG.debug(p + "folder = " + folder.getRootPath()); 1301 LOG.debug(p + "folderPath = " + folderPath); 1302 LOG.debug(p + "sitePath = " + sitePath); 1303 LOG.debug(p + "state = " + state); 1304 LOG.debug(p + "m_currentState = " + m_currentState); 1305 LOG.debug(p + "m_currentState.asString = " + StateBean.parse(m_currentState).asString()); 1306 } 1307 if ((m_currentState == null) || !(state).equals(StateBean.parse(m_currentState).asString())) { 1308 m_currentState = state; 1309 CmsAppWorkplaceUi.get().changeCurrentAppState(m_currentState); 1310 } 1311 m_locationCache.setFileExplorerLocation(cms.getRequestContext().getSiteRoot(), sitePath); 1312 m_uploadButton.setTargetFolder(folder.getRootPath()); 1313 m_uploadArea.setTargetFolder(folder.getRootPath()); 1314 if (!m_fileTree.isExpanded(folderId)) { 1315 expandCurrentFolder(); 1316 } 1317 if (!m_fileTree.isSelected(folderId)) { 1318 m_fileTree.select(folderId); 1319 } 1320 } 1321 1322 /** 1323 * Updates the current folder and removes the given resource items.<p> 1324 * 1325 * @param removeIds the resource item ids to remove 1326 */ 1327 protected void updateCurrentFolder(Collection<CmsUUID> removeIds) { 1328 1329 m_fileTable.update(removeIds, true); 1330 CmsObject cms = A_CmsUI.getCmsObject(); 1331 try { 1332 // current folder may be filtered, so we clear the filters and restore them later 1333 // to make updates work for filtered out resources 1334 m_fileTable.saveFilters(); 1335 m_fileTable.clearFilters(); 1336 CmsResource folder = cms.readResource(m_currentFolder, FOLDERS); 1337 List<CmsResource> childResources = cms.readResources(cms.getSitePath(folder), FILES_N_FOLDERS, false); 1338 Set<CmsUUID> ids = new HashSet<CmsUUID>(); 1339 for (CmsResource child : childResources) { 1340 ids.add(child.getStructureId()); 1341 } 1342 m_fileTable.update(ids, false); 1343 } catch (CmsException e) { 1344 CmsErrorDialog.showErrorDialog(e); 1345 LOG.error(e.getLocalizedMessage(), e); 1346 } finally { 1347 m_fileTable.restoreFilters(); 1348 } 1349 } 1350 1351 /** 1352 * Expands the currently viewed folder in the tree.<p> 1353 */ 1354 void expandCurrentFolder() { 1355 1356 if (m_currentFolder != null) { 1357 m_treeContainer.setChildrenAllowed(m_currentFolder, true); 1358 m_fileTree.expandItem(m_currentFolder); 1359 } 1360 } 1361 1362 /** 1363 * Filters the file table.<p> 1364 * 1365 * @param search the search term 1366 */ 1367 void filterTable(String search) { 1368 1369 m_fileTable.filterTable(search); 1370 } 1371 1372 /** 1373 * Handles the file tree click events.<p> 1374 * 1375 * @param event the event 1376 */ 1377 void handleFileTreeClick(ItemClickEvent event) { 1378 1379 Item resourceItem = m_treeContainer.getItem(event.getItemId()); 1380 if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() == null) 1381 || !((Boolean)resourceItem.getItemProperty( 1382 CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) { 1383 // don't handle disabled item clicks 1384 try { 1385 readFolder((CmsUUID)event.getItemId()); 1386 } catch (CmsException e) { 1387 CmsErrorDialog.showErrorDialog( 1388 CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, event.getItemId()), 1389 e); 1390 LOG.error(e.getLocalizedMessage(), e); 1391 } 1392 } 1393 } 1394 1395 /** 1396 * Handles the file tree value changes.<p> 1397 */ 1398 void handleFileTreeValueChange() { 1399 1400 Item resourceItem = m_treeContainer.getItem(m_fileTree.getValue()); 1401 if (resourceItem != null) { 1402 if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() != null) 1403 && ((Boolean)resourceItem.getItemProperty( 1404 CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) { 1405 // in case the folder is disabled due to missing visibility, reset the value to the previous one 1406 m_fileTree.setValue(m_selectTreeFolder); 1407 1408 } else { 1409 m_selectTreeFolder = (CmsUUID)m_fileTree.getValue(); 1410 } 1411 } 1412 } 1413 1414 /** 1415 * Opens the new resource dialog.<p> 1416 */ 1417 void onClickNew() { 1418 1419 try { 1420 CmsObject cms = A_CmsUI.getCmsObject(); 1421 1422 CmsResource folderRes = cms.readResource(m_currentFolder, CmsResourceFilter.IGNORE_EXPIRATION); 1423 I_CmsDialogContext newDialogContext = getDialogContext(); 1424 1425 CmsNewDialog newDialog = new CmsNewDialog(folderRes, newDialogContext); 1426 newDialogContext.start( 1427 CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_NEWRESOURCEDIALOG_TITLE_0), 1428 newDialog); 1429 1430 } catch (Exception e) { 1431 LOG.error(e.getLocalizedMessage(), e); 1432 } 1433 } 1434 1435 /** 1436 * Opens the given site path.<p> 1437 * 1438 * @param path the path 1439 */ 1440 void openPath(String path) { 1441 1442 openPath(path, false); 1443 } 1444 1445 /** 1446 * Opens the given site path.<p> 1447 * 1448 * @param path the path 1449 * @param initTree <code>true</code> in case the tree needs to be initialized 1450 */ 1451 void openPath(String path, boolean initTree) { 1452 1453 if (path == null) { 1454 String siteRoot = A_CmsUI.getCmsObject().getRequestContext().getSiteRoot(); 1455 path = m_locationCache.getFileExplorerLocation(siteRoot); 1456 if (path == null) { 1457 path = ""; 1458 } else if (OpenCms.getSiteManager().startsWithShared(path)) { 1459 path = path.substring(siteRoot.length()); 1460 } 1461 } 1462 boolean existsPath = CmsStringUtil.isNotEmptyOrWhitespaceOnly(path) 1463 && A_CmsUI.getCmsObject().existsResource(path, FILES_N_FOLDERS); 1464 1465 if (path.startsWith("/")) { 1466 // remove a leading slash to avoid an empty first path fragment 1467 path = path.substring(1); 1468 } 1469 1470 String[] pathItems = path.split("/"); 1471 1472 if (initTree) { 1473 try { 1474 initFolderTree(existsPath); 1475 } catch (CmsException e) { 1476 CmsErrorDialog.showErrorDialog( 1477 CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_OPEN_PATH_1, path), 1478 e); 1479 LOG.error(e.getLocalizedMessage(), e); 1480 return; 1481 } 1482 } 1483 1484 Collection<?> rootItems = m_treeContainer.rootItemIds(); 1485 if (rootItems.size() != 1) { 1486 throw new RuntimeException("Illeagal state, folder tree has " + rootItems.size() + " children"); 1487 } 1488 1489 CmsUUID folderId = (CmsUUID)rootItems.iterator().next(); 1490 if (initTree) { 1491 if (existsPath) { 1492 m_expandListener.setOpenPathFragment(pathItems[0]); 1493 } 1494 m_fileTree.expandItem(folderId); 1495 m_expandListener.setOpenPathFragment(null); 1496 } 1497 Collection<?> children = m_treeContainer.getChildren(folderId); 1498 for (int i = 0; i < pathItems.length; i++) { 1499 if (CmsStringUtil.isEmptyOrWhitespaceOnly(pathItems[i])) { 1500 continue; 1501 } 1502 CmsUUID level = null; 1503 if (children != null) { 1504 for (Object id : children) { 1505 if (m_treeContainer.getItem(id).getItemProperty( 1506 CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).getValue().equals(pathItems[i])) { 1507 level = (CmsUUID)id; 1508 m_expandListener.setOpenPathFragment( 1509 existsPath && (i < (pathItems.length - 1)) ? pathItems[i + 1] : null); 1510 m_fileTree.expandItem(level); 1511 m_expandListener.setOpenPathFragment(null); 1512 break; 1513 } 1514 } 1515 } 1516 if ((level == null) || level.equals(folderId)) { 1517 break; 1518 } 1519 folderId = level; 1520 children = m_treeContainer.getChildren(folderId); 1521 } 1522 try { 1523 readFolder(folderId); 1524 } catch (CmsException e) { 1525 CmsErrorDialog.showErrorDialog( 1526 CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_OPEN_PATH_1, path), 1527 e); 1528 LOG.error(e.getLocalizedMessage(), e); 1529 return; 1530 } 1531 } 1532 1533 /** 1534 * Reads the given tree level.<p> 1535 * 1536 * @param parentId the parent id 1537 * @param openPathFragment the open path fragment 1538 */ 1539 void readTreeLevel(CmsUUID parentId, String openPathFragment) { 1540 1541 CmsObject cms = A_CmsUI.getCmsObject(); 1542 boolean openedFragment = false; 1543 try { 1544 CmsResource parent = cms.readResource(parentId, CmsResourceFilter.IGNORE_EXPIRATION); 1545 String folderPath = cms.getSitePath(parent); 1546 List<CmsResource> folderResources = cms.readResources(folderPath, FOLDERS, false); 1547 1548 // sets the parent to leaf mode, in case no child folders are present 1549 m_treeContainer.setChildrenAllowed(parentId, !folderResources.isEmpty()); 1550 1551 for (CmsResource resource : folderResources) { 1552 addTreeItem(cms, resource, parentId, false, m_treeContainer); 1553 openedFragment = openedFragment || resource.getName().equals(openPathFragment); 1554 } 1555 if (!openedFragment && (openPathFragment != null)) { 1556 CmsResource resource = cms.readResource( 1557 CmsStringUtil.joinPaths(folderPath, openPathFragment), 1558 CmsResourceFilter.IGNORE_EXPIRATION); 1559 addTreeItem(cms, resource, parentId, true, m_treeContainer); 1560 } 1561 1562 m_fileTree.markAsDirtyRecursive(); 1563 } catch (CmsException e) { 1564 CmsErrorDialog.showErrorDialog(e); 1565 LOG.error(e.getLocalizedMessage(), e); 1566 } 1567 } 1568 1569 /** 1570 * Selects the item with the given id.<p> 1571 * 1572 * @param itemId the item id 1573 */ 1574 void selectTreeItem(CmsUUID itemId) { 1575 1576 m_fileTree.select(itemId); 1577 } 1578 1579 /** 1580 * Shows and hides the path bread crumb.<p> 1581 * 1582 * @param show <code>true</code> to show the bread crumb 1583 */ 1584 void showCrumbs(boolean show) { 1585 1586 if (show) { 1587 m_crumbs.removeStyleName(OpenCmsTheme.HIDDEN); 1588 CmsAppWorkplaceUi.get().enableGlobalShortcuts(); 1589 } else { 1590 m_crumbs.addStyleName(OpenCmsTheme.HIDDEN); 1591 CmsAppWorkplaceUi.get().disableGlobalShortcuts(); 1592 } 1593 } 1594 1595 /** 1596 * Shows the parent folder, if available.<p> 1597 */ 1598 void showParentFolder() { 1599 1600 CmsUUID parentId = (CmsUUID)m_treeContainer.getParent(m_currentFolder); 1601 if (parentId != null) { 1602 Item resourceItem = m_treeContainer.getItem(parentId); 1603 if ((resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).getValue() == null) 1604 || !((Boolean)resourceItem.getItemProperty( 1605 CmsResourceTableProperty.PROPERTY_DISABLED).getValue()).booleanValue()) { 1606 // don't open disabled parent folders 1607 try { 1608 readFolder(parentId); 1609 m_fileTree.select(parentId); 1610 } catch (CmsException e) { 1611 CmsErrorDialog.showErrorDialog( 1612 CmsVaadinUtils.getMessageText(Messages.ERR_EXPLORER_CAN_NOT_READ_RESOURCE_1, parentId), 1613 e); 1614 LOG.error(e.getLocalizedMessage(), e); 1615 } 1616 } 1617 } 1618 } 1619 1620 /** 1621 * Toggles between the online and the offline project.<p> 1622 */ 1623 void toggleOnlineOffline() { 1624 1625 CmsObject cms = A_CmsUI.getCmsObject(); 1626 CmsProject targetProject = null; 1627 if (cms.getRequestContext().getCurrentProject().isOnlineProject()) { 1628 targetProject = A_CmsUI.get().getLastOfflineProject(); 1629 if (targetProject == null) { 1630 CmsUserSettings userSettings = new CmsUserSettings(cms); 1631 try { 1632 CmsProject project = cms.readProject(userSettings.getStartProject()); 1633 if (!project.isOnlineProject() 1634 && OpenCms.getOrgUnitManager().getAllAccessibleProjects( 1635 cms, 1636 project.getOuFqn(), 1637 false).contains(project)) { 1638 targetProject = project; 1639 } 1640 } catch (CmsException e) { 1641 LOG.debug("Error reading user start project.", e); 1642 } 1643 if (targetProject == null) { 1644 List<CmsProject> availableProjects = CmsVaadinUtils.getAvailableProjects(cms); 1645 for (CmsProject project : availableProjects) { 1646 if (!project.isOnlineProject()) { 1647 targetProject = project; 1648 break; 1649 } 1650 } 1651 } 1652 } 1653 } else { 1654 try { 1655 targetProject = cms.readProject(CmsProject.ONLINE_PROJECT_ID); 1656 } catch (CmsException e) { 1657 LOG.debug("Error reading online project.", e); 1658 } 1659 } 1660 if (targetProject != null) { 1661 A_CmsUI.get().changeProject(targetProject); 1662 onSiteOrProjectChange(targetProject, null); 1663 Notification.show( 1664 CmsVaadinUtils.getMessageText(Messages.GUI_SWITCHED_TO_PROJECT_1, targetProject.getName())); 1665 } 1666 } 1667 1668 /** 1669 * Adds the given properties to the tree container.<p> 1670 * 1671 * @param properties the properties tom add 1672 */ 1673 private void addTreeContainerProperties(CmsResourceTableProperty... properties) { 1674 1675 for (CmsResourceTableProperty property : properties) { 1676 m_treeContainer.addContainerProperty(property, property.getColumnType(), property.getDefaultValue()); 1677 } 1678 } 1679 1680 /** 1681 * Adds an item to the folder tree.<p> 1682 * 1683 * @param cms the cms context 1684 * @param resource the folder resource 1685 * @param parentId the parent folder id 1686 * @param disabled in case the item is disabled, used for non visible parent folders 1687 * @param container the data container 1688 */ 1689 private void addTreeItem( 1690 CmsObject cms, 1691 CmsResource resource, 1692 CmsUUID parentId, 1693 boolean disabled, 1694 HierarchicalContainer container) { 1695 1696 CmsResourceUtil resUtil = new CmsResourceUtil(cms, resource); 1697 Item resourceItem = container.getItem(resource.getStructureId()); 1698 if (resourceItem == null) { 1699 resourceItem = container.addItem(resource.getStructureId()); 1700 } 1701 1702 // use the root path as name in case of the root item 1703 String resName = parentId == null ? resource.getRootPath() : resource.getName(); 1704 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RESOURCE_NAME).setValue(resName); 1705 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_STATE).setValue(resource.getState()); 1706 1707 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_INSIDE_PROJECT).setValue( 1708 Boolean.valueOf(resUtil.isInsideProject())); 1709 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_RELEASED_NOT_EXPIRED).setValue( 1710 Boolean.valueOf(resUtil.isReleasedAndNotExpired())); 1711 String iconHTML = CmsResourceIcon.getTreeCaptionHTML(resName, resUtil, null, false); 1712 1713 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_TREE_CAPTION).setValue(iconHTML); 1714 if (disabled) { 1715 resourceItem.getItemProperty(CmsResourceTableProperty.PROPERTY_DISABLED).setValue(Boolean.TRUE); 1716 } 1717 if (parentId != null) { 1718 container.setChildrenAllowed(parentId, true); 1719 container.setParent(resource.getStructureId(), parentId); 1720 } 1721 } 1722 1723 /** 1724 * Creates the site selector combo box.<p> 1725 * 1726 * @param cms the current cms context 1727 * 1728 * @return the combo box 1729 */ 1730 private ComboBox createSiteSelect(CmsObject cms) { 1731 1732 final IndexedContainer availableSites = CmsVaadinUtils.getAvailableSitesContainer(cms, SITE_CAPTION); 1733 ComboBox combo = new ComboBox(null, availableSites); 1734 combo.setTextInputAllowed(true); 1735 combo.setNullSelectionAllowed(false); 1736 combo.setWidth("200px"); 1737 combo.setInputPrompt( 1738 Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_CLICK_TO_EDIT_0)); 1739 combo.setItemCaptionPropertyId(SITE_CAPTION); 1740 combo.select(cms.getRequestContext().getSiteRoot()); 1741 combo.setFilteringMode(FilteringMode.CONTAINS); 1742 combo.addValueChangeListener(new ValueChangeListener() { 1743 1744 private static final long serialVersionUID = 1L; 1745 1746 public void valueChange(ValueChangeEvent event) { 1747 1748 String value = (String)event.getProperty().getValue(); 1749 if (availableSites.containsId(value)) { 1750 changeSite(value, null); 1751 m_appContext.updateOnChange(); 1752 availableSites.removeAllContainerFilters(); 1753 } 1754 } 1755 }); 1756 if (Page.getCurrent().getBrowserWindowHeight() > 650) { 1757 combo.setPageLength(20); 1758 } 1759 return combo; 1760 } 1761 1762 /** 1763 * Returns the site path from the current state.<p> 1764 * 1765 * @return the site path 1766 */ 1767 private String getPathFromState() { 1768 1769 return StateBean.parse(m_currentState).getFolder(); 1770 } 1771 1772 /** 1773 * Returns the project id from the current state.<p> 1774 * 1775 * @return the project id 1776 */ 1777 private CmsUUID getProjectIdFromState() { 1778 1779 String projectIdStr = StateBean.parse(m_currentState).getProjectId(); 1780 if (CmsUUID.isValidUUID(projectIdStr)) { 1781 return new CmsUUID(projectIdStr); 1782 1783 } 1784 return null; 1785 1786 } 1787 1788 /** 1789 * Returns selected resource UUID from state (empty if not set).<p> 1790 * 1791 * @param state current state 1792 * @return uuid as string 1793 */ 1794 private String getSelectionFromState(String state) { 1795 1796 return StateBean.parse(state).getSelectedResource(); 1797 } 1798 1799 /** 1800 * Returns the site root from the current state.<p> 1801 * 1802 * @return the site root 1803 */ 1804 private String getSiteRootFromState() { 1805 1806 String siteRoot = StateBean.parse(m_currentState).getSiteRoot(); 1807 return siteRoot; 1808 } 1809 1810 /** 1811 * Populates the folder tree.<p> 1812 * 1813 * @param showInvisible to show non visible root folder as disabled 1814 * 1815 * @throws CmsException in case reading the root folder fails 1816 */ 1817 private void initFolderTree(boolean showInvisible) throws CmsException { 1818 1819 CmsObject cms = A_CmsUI.getCmsObject(); 1820 m_treeContainer.removeAllItems(); 1821 try { 1822 CmsResource siteRoot = cms.readResource("/", FOLDERS); 1823 addTreeItem(cms, siteRoot, null, false, m_treeContainer); 1824 } catch (CmsException e) { 1825 if (showInvisible) { 1826 CmsResource siteRoot = cms.readResource("/", CmsResourceFilter.IGNORE_EXPIRATION); 1827 addTreeItem(cms, siteRoot, null, true, m_treeContainer); 1828 } else { 1829 throw e; 1830 } 1831 } 1832 1833 } 1834 1835 /** 1836 * Initializes the app specific toolbar buttons.<p> 1837 * 1838 * @param context the UI context 1839 */ 1840 private void initToolbarButtons(I_CmsAppUIContext context) { 1841 1842 m_publishButton = context.addPublishButton(new I_CmsUpdateListener<String>() { 1843 1844 public void onUpdate(List<String> updatedItems) { 1845 1846 updateAll(false); 1847 } 1848 1849 }); 1850 1851 m_newButton = CmsToolBar.createButton( 1852 FontOpenCms.WAND, 1853 CmsVaadinUtils.getMessageText(Messages.GUI_NEW_RESOURCE_TITLE_0)); 1854 if (CmsAppWorkplaceUi.isOnlineProject()) { 1855 m_newButton.setEnabled(false); 1856 m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0)); 1857 } 1858 m_newButton.addClickListener(new ClickListener() { 1859 1860 private static final long serialVersionUID = 1L; 1861 1862 public void buttonClick(ClickEvent event) { 1863 1864 onClickNew(); 1865 } 1866 1867 }); 1868 1869 context.addToolbarButton(m_newButton); 1870 1871 m_uploadButton = new CmsUploadButton(FontOpenCms.UPLOAD, "/"); 1872 m_uploadButton.addStyleName(ValoTheme.BUTTON_BORDERLESS); 1873 m_uploadButton.addStyleName(OpenCmsTheme.TOOLBAR_BUTTON); 1874 if (CmsAppWorkplaceUi.isOnlineProject()) { 1875 m_uploadButton.setEnabled(false); 1876 m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0)); 1877 } else { 1878 m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0)); 1879 } 1880 m_uploadButton.addUploadListener(new I_UploadListener() { 1881 1882 public void onUploadFinished(List<String> uploadedFiles) { 1883 1884 updateAll(true); 1885 } 1886 }); 1887 1888 context.addToolbarButton(m_uploadButton); 1889 } 1890 1891 /** 1892 * Normalizes the state string. Ensuring it starts with the right number of slashes resolving an issue with the vaadin state handling.<p> 1893 * 1894 * @param state the state string 1895 * 1896 * @return the normalized state string 1897 */ 1898 private String normalizeState(String state) { 1899 1900 String result = ""; 1901 if (state.contains(STATE_SEPARATOR)) { 1902 result = state; 1903 while (result.startsWith("/")) { 1904 result = result.substring(1); 1905 } 1906 } 1907 return result; 1908 } 1909 1910 /** 1911 * Sets the current path info.<p> 1912 * 1913 * @param path the path 1914 */ 1915 private void setPathInfo(String path) { 1916 1917 m_infoPath.setValue(path); 1918 1919 // generate the path bread crumb 1920 m_crumbs.removeAllComponents(); 1921 int i = path.indexOf("/"); 1922 String openPath = ""; 1923 while (i >= 0) { 1924 String fragment = path.substring(0, i + 1); 1925 openPath += fragment; 1926 path = path.substring(i + 1); 1927 i = path.indexOf("/"); 1928 Button crumb = new Button(fragment, m_crumbListener); 1929 crumb.setData(openPath); 1930 crumb.addStyleName(ValoTheme.BUTTON_LINK); 1931 m_crumbs.addComponent(crumb); 1932 } 1933 } 1934 1935 /** 1936 * Sets the toolbar buttons enabled.<p> 1937 * 1938 * @param enabled the enabled flag 1939 */ 1940 private void setToolbarButtonsEnabled(boolean enabled) { 1941 1942 m_publishButton.setEnabled(enabled); 1943 m_newButton.setEnabled(enabled); 1944 m_uploadButton.setEnabled(enabled); 1945 if (enabled) { 1946 m_publishButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_PUBLISH_BUTTON_TITLE_0)); 1947 m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_NEW_RESOURCE_TITLE_0)); 1948 m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_UPLOAD_BUTTON_TITLE_0)); 1949 } else { 1950 m_publishButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0)); 1951 m_newButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0)); 1952 m_uploadButton.setDescription(CmsVaadinUtils.getMessageText(Messages.GUI_TOOLBAR_NOT_AVAILABLE_ONLINE_0)); 1953 } 1954 } 1955}