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.ade.configuration.CmsADEManager; 031import org.opencms.ade.configuration.CmsElementView; 032import org.opencms.ade.configuration.CmsResourceTypeConfig; 033import org.opencms.ade.containerpage.CmsAddDialogTypeHelper; 034import org.opencms.ade.galleries.shared.CmsResourceTypeBean; 035import org.opencms.ade.galleries.shared.CmsResourceTypeBean.Origin; 036import org.opencms.configuration.preferences.CmsElementViewPreference; 037import org.opencms.db.CmsUserSettings; 038import org.opencms.file.CmsObject; 039import org.opencms.file.CmsResource; 040import org.opencms.i18n.CmsEncoder; 041import org.opencms.jsp.util.CmsJspElFunctions; 042import org.opencms.main.CmsLog; 043import org.opencms.main.OpenCms; 044import org.opencms.module.CmsModule; 045import org.opencms.ui.A_CmsUI; 046import org.opencms.ui.CmsVaadinUtils; 047import org.opencms.ui.FontOpenCms; 048import org.opencms.ui.I_CmsDialogContext; 049import org.opencms.ui.Messages; 050import org.opencms.ui.components.CmsBasicDialog; 051import org.opencms.ui.components.CmsOkCancelActionHandler; 052import org.opencms.ui.components.CmsResourceInfo; 053import org.opencms.ui.components.OpenCmsTheme; 054import org.opencms.ui.components.extensions.CmsMaxHeightExtension; 055import org.opencms.util.CmsUUID; 056import org.opencms.workplace.explorer.CmsExplorerTypeSettings; 057import org.opencms.workplace.explorer.CmsResourceUtil; 058 059import java.util.ArrayList; 060import java.util.Collections; 061import java.util.Comparator; 062import java.util.HashMap; 063import java.util.HashSet; 064import java.util.LinkedHashMap; 065import java.util.List; 066import java.util.Locale; 067import java.util.Map; 068import java.util.Set; 069 070import org.apache.commons.logging.Log; 071 072import com.google.common.base.Predicate; 073import com.google.common.collect.ComparisonChain; 074import com.google.common.collect.Lists; 075import com.google.common.collect.Maps; 076import com.vaadin.event.LayoutEvents.LayoutClickEvent; 077import com.vaadin.event.LayoutEvents.LayoutClickListener; 078import com.vaadin.server.Resource; 079import com.vaadin.server.VaadinService; 080import com.vaadin.shared.ui.ValueChangeMode; 081import com.vaadin.ui.AbstractComponent; 082import com.vaadin.ui.Button; 083import com.vaadin.ui.Button.ClickEvent; 084import com.vaadin.ui.Button.ClickListener; 085import com.vaadin.ui.Component; 086import com.vaadin.ui.Label; 087import com.vaadin.ui.Layout; 088import com.vaadin.ui.TextField; 089import com.vaadin.ui.UI; 090import com.vaadin.ui.declarative.Design; 091import com.vaadin.ui.themes.ValoTheme; 092import com.vaadin.v7.data.Property.ValueChangeEvent; 093import com.vaadin.v7.data.Property.ValueChangeListener; 094import com.vaadin.v7.shared.ui.label.ContentMode; 095import com.vaadin.v7.ui.AbstractSelect.ItemCaptionMode; 096import com.vaadin.v7.ui.ComboBox; 097import com.vaadin.v7.ui.VerticalLayout; 098 099public abstract class A_CmsSelectResourceTypeDialog extends CmsBasicDialog { 100 101 /** Default value for the 'default location' check box. */ 102 public static final Boolean DEFAULT_LOCATION_DEFAULT = Boolean.TRUE; 103 104 /** Id for the 'All' pseudo-view. */ 105 public static final CmsUUID ID_VIEW_ALL = CmsUUID.getConstantUUID("view-all"); 106 107 /** Setting name for the standard view. */ 108 public static final String SETTING_STANDARD_VIEW = "newDialogStandardView"; 109 110 /** The 'All' pseudo-view. */ 111 public static final CmsElementView VIEW_ALL = new CmsElementView(ID_VIEW_ALL); 112 113 /** Logger instance for this class. */ 114 private static final Log LOG = CmsLog.getLog(CmsNewDialog.class); 115 116 /** Serial version id. */ 117 private static final long serialVersionUID = 1L; 118 119 /** The created resource. */ 120 protected CmsResource m_createdResource; 121 122 /** The current view id. */ 123 protected CmsElementView m_currentView; 124 125 /** The dialog context. */ 126 protected I_CmsDialogContext m_dialogContext; 127 128 /** The filter field, wrapped in an array to prevent the declarative layout mechanism from messing with it. */ 129 protected TextField[] m_filterField = {new TextField()}; 130 131 /** True if we are in filtering mode. */ 132 protected boolean m_filterMode; 133 134 /** The filter string (null if not filtering). */ 135 protected String m_filterString; 136 137 /** The current folder. */ 138 protected CmsResource m_folderResource; 139 140 /** The selected type. */ 141 protected CmsResourceTypeBean m_selectedType; 142 143 /** The type helper. */ 144 protected CmsAddDialogTypeHelper m_typeHelper; 145 146 /** List of all types. */ 147 private List<CmsResourceTypeBean> m_allTypes; 148 149 private Map<CmsResourceTypeBean, CmsResourceInfo> m_resourceInfoMap = new HashMap<CmsResourceTypeBean, CmsResourceInfo>(); 150 151 private Runnable m_selectedRunnable; 152 153 /** 154 * Creates a new instance.<p> 155 * 156 * @param folderResource the folder resource 157 * @param context the context 158 */ 159 public A_CmsSelectResourceTypeDialog(CmsResource folderResource, I_CmsDialogContext context) { 160 161 m_folderResource = folderResource; 162 m_dialogContext = context; 163 164 Design.read(this); 165 CmsVaadinUtils.visitDescendants(this, new Predicate<Component>() { 166 167 public boolean apply(Component component) { 168 169 component.setCaption(CmsVaadinUtils.localizeString(component.getCaption())); 170 return true; 171 } 172 }); 173 getModeToggle().addStyleName(ValoTheme.BUTTON_BORDERLESS); 174 getModeToggle().addStyleName(OpenCmsTheme.TYPE_FILTER_BUTTON); 175 176 CmsUUID initViewId = (CmsUUID)VaadinService.getCurrentRequest().getWrappedSession().getAttribute( 177 SETTING_STANDARD_VIEW); 178 if (initViewId == null) { 179 try { 180 CmsUserSettings settings = new CmsUserSettings(A_CmsUI.getCmsObject()); 181 String viewSettingStr = settings.getAdditionalPreference( 182 CmsElementViewPreference.EXPLORER_PREFERENCE_NAME, 183 true); 184 if ((viewSettingStr != null) && CmsUUID.isValidUUID(viewSettingStr)) { 185 initViewId = new CmsUUID(viewSettingStr); 186 } 187 } catch (Exception e) { 188 LOG.error(e.getLocalizedMessage(), e); 189 } 190 } 191 if (initViewId == null) { 192 initViewId = CmsUUID.getNullUUID(); 193 } 194 CmsElementView initView = initViews(initViewId); 195 196 getCancelButton().addClickListener(new ClickListener() { 197 198 private static final long serialVersionUID = 1L; 199 200 public void buttonClick(ClickEvent event) { 201 202 finish(new ArrayList<CmsUUID>()); 203 } 204 }); 205 206 getViewSelector().setNullSelectionAllowed(false); 207 getViewSelector().setTextInputAllowed(false); 208 getVerticalLayout().addLayoutClickListener(new LayoutClickListener() { 209 210 private static final long serialVersionUID = 1L; 211 212 public void layoutClick(LayoutClickEvent event) { 213 214 try { 215 CmsResourceTypeBean clickedType = (CmsResourceTypeBean)(((AbstractComponent)(event.getChildComponent())).getData()); 216 if (clickedType != null) { 217 handleSelection(clickedType); 218 } 219 } catch (ClassCastException e) { 220 // ignore 221 } 222 } 223 }); 224 225 m_filterField[0].setValueChangeMode(ValueChangeMode.LAZY); 226 m_filterField[0].setValueChangeTimeout(200); 227 m_filterField[0].setWidth("250px"); 228 m_filterField[0].setIcon(FontOpenCms.FILTER); 229 m_filterField[0].setPlaceholder( 230 org.opencms.ui.apps.Messages.get().getBundle(UI.getCurrent().getLocale()).key( 231 org.opencms.ui.apps.Messages.GUI_EXPLORER_FILTER_0)); 232 m_filterField[0].addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); 233 m_filterField[0].addValueChangeListener(event -> { 234 if (m_filterField[0].getParent() != null) { 235 init(m_currentView, useDefault(), event.getValue()); 236 } 237 }); 238 getModeToggle().addClickListener(e -> setFilterMode(!m_filterMode)); 239 setActionHandler(new CmsOkCancelActionHandler() { 240 241 private static final long serialVersionUID = 1L; 242 243 @Override 244 protected void cancel() { 245 246 finish(new ArrayList<CmsUUID>()); 247 } 248 249 @Override 250 protected void ok() { 251 252 // nothing to do 253 } 254 }); 255 init(initView, true, null); 256 setFilterModeStyle(false); 257 } 258 259 /** 260 * Notifies the context that the given ids have changed.<p> 261 * 262 * @param ids the ids 263 */ 264 public void finish(List<CmsUUID> ids) { 265 266 if (m_selectedRunnable == null) { 267 m_dialogContext.finish(ids); 268 if (ids.size() == 1) { 269 m_dialogContext.focus(ids.get(0)); 270 271 } 272 } else { 273 m_selectedRunnable.run(); 274 } 275 } 276 277 /** 278 * Gets the Cancel button. 279 * 280 * @return the cancel button 281 */ 282 public abstract Button getCancelButton(); 283 284 /** 285 * Gets the mode toggle button. 286 * 287 * @return the mode toggle button 288 */ 289 public abstract Button getModeToggle(); 290 291 /** 292 * Gets the type info widget for the selected type. 293 * 294 * @return the type info widget for the selected type 295 */ 296 public CmsResourceInfo getTypeInfoLayout() { 297 298 return m_selectedType != null ? m_resourceInfoMap.get(m_selectedType) : null; 299 } 300 301 public abstract VerticalLayout getVerticalLayout(); 302 303 public abstract ComboBox getViewSelector(); 304 305 /** 306 * Handles selection of a type.<p> 307 * 308 * @param selectedType the selected type 309 */ 310 public abstract void handleSelection(final CmsResourceTypeBean selectedType); 311 312 /** 313 * Initializes and displays the type list for the given view.<p> 314 * 315 * @param view the element view 316 * @param useDefault true if we should use the default location for resource creation 317 */ 318 public void init(CmsElementView view, boolean useDefault, String filter) { 319 320 m_currentView = view; 321 m_filterString = filter; 322 if (filter != null) { 323 filter = filter.toLowerCase(); 324 } 325 if (!view.getId().equals(getViewSelector().getValue())) { 326 getViewSelector().setValue(view.getId()); 327 } 328 getVerticalLayout().removeAllComponents(); 329 330 List<CmsResourceTypeBean> typeBeans = m_typeHelper.getPrecomputedTypes(view); 331 if (view.getId().equals(ID_VIEW_ALL)) { 332 typeBeans = m_allTypes; 333 } 334 335 if (typeBeans == null) { 336 337 LOG.warn("precomputed type list is null: " + view.getTitle(A_CmsUI.getCmsObject(), Locale.ENGLISH)); 338 return; 339 } 340 if (typeBeans.size() == 0) { 341 Label label = new Label(CmsVaadinUtils.getMessageText(Messages.GUI_NEWRESOURCEDIALOG_NO_TYPES_AVAILABLE_0)); 342 getVerticalLayout().addComponent(label); 343 return; 344 } 345 Set<String> nonstandardTypes = new HashSet<>(); 346 for (CmsModule module : OpenCms.getModuleManager().getAllInstalledModules()) { 347 if (module.getName().equals(CmsADEManager.MODULE_NAME_ADE_CONFIG)) { 348 continue; 349 } 350 for (CmsExplorerTypeSettings expType : module.getExplorerTypes()) { 351 nonstandardTypes.add(expType.getName()); 352 } 353 } 354 355 for (CmsResourceTypeBean type : typeBeans) { 356 final String typeName = type.getType(); 357 String title = typeName; 358 String subtitle = getSubtitle(type, useDefault); 359 360 CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(typeName); 361 362 title = CmsVaadinUtils.getMessageText(explorerType.getKey()); 363 364 if (filter != null) { 365 String filterable = (title + "\n" + typeName).toLowerCase(); 366 if (!filterable.contains(filter)) { 367 continue; 368 } 369 } 370 371 CmsResourceInfo info = new CmsResourceInfo(); 372 info.getTopLine().setContentMode(ContentMode.HTML); 373 String suffix = ""; 374 if (nonstandardTypes.contains(type.getType())) { 375 suffix = " <span class='o-internal-type-name'>" + CmsEncoder.escapeHtml(type.getType()) + "</span>"; 376 } 377 info.getTopLine().setValue(CmsEncoder.escapeHtml(title) + suffix); 378 info.getBottomLine().setValue(CmsJspElFunctions.stripHtml(subtitle)); 379 Resource iconResource = CmsResourceUtil.getBigIconResource(explorerType, null); 380 info.getResourceIcon().initContent(null, iconResource, null, false, true); 381 info.setData(type); 382 m_resourceInfoMap.put(type, info); 383 getVerticalLayout().addComponent(info); 384 } 385 } 386 387 public void setSelectedRunnable(Runnable run) { 388 389 m_selectedRunnable = run; 390 } 391 392 public abstract boolean useDefault(); 393 394 /** 395 * Creates type helper which is responsible for generating the type list.<p> 396 * 397 * @return the type helper 398 */ 399 protected CmsAddDialogTypeHelper createTypeHelper() { 400 401 return new CmsAddDialogTypeHelper(CmsResourceTypeConfig.AddMenuType.workplace) { 402 403 @Override 404 protected boolean exclude(CmsResourceTypeBean type) { 405 406 String typeName = type.getType(); 407 CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(typeName); 408 boolean noCreate = !(type.isCreatableType() && !type.isDeactivated()); 409 return noCreate || (explorerType == null) || !explorerType.isCreatable(); 410 } 411 }; 412 413 } 414 415 /** 416 * Gets the subtitle for the type info widget.<p> 417 * 418 * @param type the type 419 * @param useDefault true if we are in 'use default' mode 420 * 421 * @return the subtitle 422 */ 423 protected String getSubtitle(CmsResourceTypeBean type, boolean useDefault) { 424 425 String subtitle = ""; 426 CmsExplorerTypeSettings explorerType = OpenCms.getWorkplaceManager().getExplorerTypeSetting(type.getType()); 427 if ((explorerType != null) && (explorerType.getInfo() != null)) { 428 subtitle = CmsVaadinUtils.getMessageText(explorerType.getInfo()); 429 } 430 if (useDefault && (type.getOrigin() == Origin.config) && (type.getCreatePath() != null)) { 431 String path = type.getCreatePath(); 432 CmsObject cms = A_CmsUI.getCmsObject(); 433 path = cms.getRequestContext().removeSiteRoot(path); 434 subtitle = CmsVaadinUtils.getMessageText(Messages.GUI_NEW_CREATE_IN_PATH_1, path); 435 } 436 return subtitle; 437 } 438 439 /** 440 * Enables or disables filtering mode. 441 * 442 * @param filterMode true if filtering mode should be enabled 443 */ 444 protected void setFilterMode(boolean filterMode) { 445 446 Component typePanel = getVerticalLayout().getParent(); 447 Layout row = null; 448 if (m_filterField[0].isAttached()) { 449 row = (Layout)(m_filterField[0].getParent()); 450 } else { 451 row = (Layout)(getViewSelector().getParent()); 452 } 453 454 m_filterMode = filterMode; 455 setFilterModeStyle(filterMode); 456 if (filterMode) { 457 getViewSelector().setValue(A_CmsSelectResourceTypeDialog.ID_VIEW_ALL); 458 row.replaceComponent(getViewSelector(), m_filterField[0]); 459 m_filterField[0].focus(); 460 typePanel.setHeight("425px"); 461 462 // We have to disable this because it ultimately results in 463 // calls to setHeight in Horizontal/VerticalLayouts which 464 // can cause the focus on the filter field to be lost (apparently because 465 // the client-side implementations do some tricky DOM manipulation). 466 setMaxHeightEnabled(false); 467 468 CmsVaadinUtils.getWindow(this).center(); 469 } else { 470 row.replaceComponent(m_filterField[0], getViewSelector()); 471 m_filterField[0].clear(); 472 typePanel.setHeight("100%"); 473 setMaxHeightEnabled(true); 474 init(m_currentView, useDefault(), null); 475 CmsVaadinUtils.getWindow(this).center(); 476 } 477 } 478 479 /** 480 * Sets the style of the filter mode toggle button. 481 * 482 * @param filterMode if true, changes the mode toggle to 'filtering' style 483 */ 484 protected void setFilterModeStyle(boolean filterMode) { 485 486 if (filterMode) { 487 getModeToggle().addStyleName(OpenCmsTheme.TYPE_FILTER_BUTTON_ACTIVE); 488 getModeToggle().setIcon(FontOpenCms.FILTER); 489 } else { 490 getModeToggle().setIcon(FontOpenCms.FILTER); 491 getModeToggle().removeStyleName(OpenCmsTheme.TYPE_FILTER_BUTTON_ACTIVE); 492 } 493 } 494 495 /** 496 * Initializes the view selector, using the given view id as an initial value.<p> 497 * 498 * @param startId the start view 499 * 500 * @return the start view 501 */ 502 private CmsElementView initViews(CmsUUID startId) { 503 504 Map<CmsUUID, CmsElementView> viewMap = OpenCms.getADEManager().getElementViews(A_CmsUI.getCmsObject()); 505 List<CmsElementView> viewList = new ArrayList<CmsElementView>(viewMap.values()); 506 Collections.sort(viewList, new Comparator<CmsElementView>() { 507 508 public int compare(CmsElementView arg0, CmsElementView arg1) { 509 510 return ComparisonChain.start().compare(arg0.getOrder(), arg1.getOrder()).result(); 511 } 512 513 }); 514 getViewSelector().setItemCaptionMode(ItemCaptionMode.EXPLICIT); 515 m_typeHelper = createTypeHelper(); 516 m_typeHelper.precomputeTypeLists( 517 A_CmsUI.getCmsObject(), 518 m_folderResource.getRootPath(), 519 A_CmsUI.getCmsObject().getRequestContext().removeSiteRoot(m_folderResource.getRootPath()), 520 viewList, 521 null); 522 523 // also collect types in LinkedHashMap to preserve order and ensure uniqueness 524 LinkedHashMap<String, CmsResourceTypeBean> allTypes = Maps.newLinkedHashMap(); 525 526 for (CmsElementView view : viewList) { 527 528 if (view.hasPermission(A_CmsUI.getCmsObject(), m_folderResource)) { 529 530 List<CmsResourceTypeBean> typeBeans = m_typeHelper.getPrecomputedTypes(view); 531 532 if (typeBeans.isEmpty()) { 533 continue; 534 } 535 for (CmsResourceTypeBean typeBean : typeBeans) { 536 allTypes.put(typeBean.getType(), typeBean); 537 } 538 getViewSelector().addItem(view.getId()); 539 getViewSelector().setItemCaption( 540 view.getId(), 541 view.getTitle(A_CmsUI.getCmsObject(), A_CmsUI.get().getLocale())); 542 } 543 } 544 getViewSelector().addItem(VIEW_ALL.getId()); 545 getViewSelector().setItemCaption(VIEW_ALL.getId(), CmsVaadinUtils.getMessageText(Messages.GUI_VIEW_ALL_0)); 546 m_allTypes = Lists.newArrayList(allTypes.values()); 547 if (allTypes.size() <= 8) { 548 startId = ID_VIEW_ALL; 549 } 550 if (getViewSelector().getItem(startId) == null) { 551 startId = (CmsUUID)(getViewSelector().getItemIds().iterator().next()); 552 } 553 554 getViewSelector().addValueChangeListener(new ValueChangeListener() { 555 556 private static final long serialVersionUID = 1L; 557 558 public void valueChange(ValueChangeEvent event) { 559 560 CmsUUID viewId = (CmsUUID)(event.getProperty().getValue()); 561 CmsElementView selectedView; 562 if (viewId.equals(ID_VIEW_ALL)) { 563 selectedView = VIEW_ALL; 564 } else { 565 selectedView = OpenCms.getADEManager().getElementViews(A_CmsUI.getCmsObject()).get( 566 event.getProperty().getValue()); 567 } 568 init(selectedView, useDefault(), null); 569 if (selectedView != VIEW_ALL) { 570 VaadinService.getCurrentRequest().getWrappedSession().setAttribute( 571 SETTING_STANDARD_VIEW, 572 (event.getProperty().getValue())); 573 } 574 } 575 }); 576 if (startId.equals(ID_VIEW_ALL)) { 577 return VIEW_ALL; 578 } else { 579 return OpenCms.getADEManager().getElementViews(A_CmsUI.getCmsObject()).get(startId); 580 } 581 } 582 583 /** 584 * Enables or disables the max-height extension. 585 * 586 * @param enabled true if the extension should be enabled 587 */ 588 private void setMaxHeightEnabled(boolean enabled) { 589 590 getExtensions().forEach(ext -> { 591 if (ext instanceof CmsMaxHeightExtension) { 592 ((CmsMaxHeightExtension)ext).setEnabled(enabled); 593 } 594 }); 595 } 596 597}