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.favorites; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.file.CmsResourceFilter; 033import org.opencms.i18n.CmsEncoder; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.main.OpenCms; 037import org.opencms.security.CmsRole; 038import org.opencms.site.CmsSite; 039import org.opencms.ui.A_CmsUI; 040import org.opencms.ui.CmsVaadinUtils; 041import org.opencms.ui.Messages; 042import org.opencms.ui.components.CmsBasicDialog; 043import org.opencms.ui.components.CmsErrorDialog; 044import org.opencms.ui.components.CmsExtendedSiteSelector; 045import org.opencms.ui.components.CmsExtendedSiteSelector.SiteSelectorOption; 046import org.opencms.ui.components.CmsResourceIcon; 047import org.opencms.ui.components.OpenCmsTheme; 048import org.opencms.ui.components.editablegroup.CmsDefaultActionHandler; 049import org.opencms.ui.components.editablegroup.CmsEditableGroup; 050import org.opencms.ui.components.editablegroup.CmsEditableGroupButtons; 051import org.opencms.ui.components.editablegroup.I_CmsEditableGroupRow; 052import org.opencms.util.CmsStringUtil; 053import org.opencms.util.CmsUUID; 054import org.opencms.workplace.explorer.CmsResourceUtil; 055 056import java.util.ArrayList; 057import java.util.HashMap; 058import java.util.LinkedHashMap; 059import java.util.List; 060import java.util.Map; 061import java.util.Optional; 062 063import org.apache.commons.logging.Log; 064 065import com.vaadin.data.provider.ListDataProvider; 066import com.vaadin.shared.ui.ContentMode; 067import com.vaadin.ui.Button; 068import com.vaadin.ui.ComboBox; 069import com.vaadin.ui.Component; 070import com.vaadin.ui.Label; 071import com.vaadin.ui.VerticalLayout; 072import com.vaadin.ui.Window; 073 074/** 075 * Dialog which shows the list of favorites for the current user and allows them to jump to individual favorites, 076 * edit the list, or add the current location to the favorite list. 077 */ 078public class CmsFavoriteDialog extends CmsBasicDialog implements CmsEditableGroup.I_RowBuilder { 079 080 /** 081 * Action handler that saves favorites after every change. 082 */ 083 public class SaveAfterChangeActionHandler extends CmsDefaultActionHandler { 084 085 /** 086 * Creates a new instance. 087 * 088 * @param row the row 089 */ 090 @SuppressWarnings("synthetic-access") 091 public SaveAfterChangeActionHandler(I_CmsEditableGroupRow row) { 092 093 super(CmsFavoriteDialog.this.m_group, row); 094 } 095 096 /** 097 * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onAdd() 098 */ 099 @Override 100 public void onAdd() { 101 102 super.onAdd(); 103 doSave(); 104 105 } 106 107 /** 108 * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onDelete() 109 */ 110 @Override 111 public void onDelete() { 112 113 super.onDelete(); 114 doSave(); 115 116 } 117 118 /** 119 * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onDown() 120 */ 121 @Override 122 public void onDown() { 123 124 super.onDown(); 125 doSave(); 126 127 } 128 129 /** 130 * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onEdit() 131 */ 132 @Override 133 public void onEdit() { 134 135 Window window = CmsBasicDialog.prepareWindow(DialogWidth.narrow); 136 window.setCaption(CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_FAVORITES_EDIT_TITLE_0)); 137 CmsFavInfo info = (CmsFavInfo)m_row.getComponent(); 138 139 CmsEditFavoriteDialog dialog = new CmsEditFavoriteDialog(info, entry -> { 140 info.setEntry(entry); 141 doSave(); 142 initEntries(getEntries()); 143 }); 144 window.setContent(dialog); 145 A_CmsUI.get().addWindow(window); 146 147 } 148 149 /** 150 * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onUp() 151 */ 152 @Override 153 public void onUp() { 154 155 super.onUp(); 156 doSave(); 157 } 158 159 } 160 161 /** 162 * Handles changes in empty/not empty state by hiding or displaying a message. 163 */ 164 class EmptyHandler implements CmsEditableGroup.I_EmptyHandler { 165 166 /** The group. */ 167 private CmsEditableGroup m_groupForHandler; 168 169 /** The placeholder. */ 170 private Label m_placeholder; 171 172 /** 173 * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_EmptyHandler#init(org.opencms.ui.components.editablegroup.CmsEditableGroup) 174 */ 175 public void init(CmsEditableGroup group) { 176 177 String message = CmsVaadinUtils.getMessageText(Messages.GUI_FAVORITES_EMPTY_LIST_PLACEHOLDER_0); 178 m_groupForHandler = group; 179 m_placeholder = new Label(); 180 m_placeholder.setContentMode(ContentMode.HTML); 181 String spacer = "<div></div>"; 182 String content = "<div>" + CmsEncoder.escapeXml(message) + "</div>"; 183 m_placeholder.setValue(spacer + content + spacer); 184 m_placeholder.addStyleName(OpenCmsTheme.BOOKMARKS_PLACEHOLDER); 185 m_placeholder.setHeight("100%"); 186 187 } 188 189 /** 190 * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_EmptyHandler#setEmpty(boolean) 191 */ 192 public void setEmpty(boolean empty) { 193 194 if (!m_placeholder.isAttached()) { 195 m_groupForHandler.getContainer().addComponent(m_placeholder); 196 m_groupForHandler.getContainer().setExpandRatio(m_placeholder, 1.0f); 197 } 198 m_groupForHandler.getContainer().setHeight(empty ? "100%" : null); 199 m_placeholder.setVisible(empty); 200 } 201 202 } 203 204 /** Logger instance for this class. */ 205 private static final Log LOG = CmsLog.getLog(CmsFavoriteDialog.class); 206 207 /** Serial version id. */ 208 private static final long serialVersionUID = 1L; 209 210 /** The Add button. */ 211 private Button m_addButton; 212 213 /** The Cancel button. */ 214 private Button m_cancelButton; 215 216 /** The favorite context. */ 217 private I_CmsFavoriteContext m_context; 218 219 /** Current favorite location. */ 220 private Optional<CmsFavoriteEntry> m_currentLocation; 221 222 /** The container layout for the favorite widgets. */ 223 private VerticalLayout m_favContainer; 224 225 /** Load/save handler for favorites. */ 226 private CmsFavoriteDAO m_favDao; 227 228 /** The group for the favorite widgets. */ 229 private CmsEditableGroup m_group; 230 231 /** Project selector. */ 232 private ComboBox<CmsUUID> m_projectBox; 233 234 /** Label for project selector. */ 235 private Label m_projectBoxLabel; 236 237 /** Map of project labels. */ 238 private Map<CmsUUID, String> m_projectLabels = new HashMap<>(); 239 240 /** Site selector. */ 241 private CmsExtendedSiteSelector m_siteBox; 242 243 /** Site labels. */ 244 private Map<String, String> m_siteLabels; 245 246 /** 247 * Creates a new dialog instance. 248 * 249 * @param context the favorite context 250 * @param favDao the favorite load/save handler 251 * 252 * @throws CmsException if something goes wrong 253 */ 254 public CmsFavoriteDialog(I_CmsFavoriteContext context, CmsFavoriteDAO favDao) 255 throws CmsException { 256 257 super(); 258 m_favDao = favDao; 259 m_siteLabels = CmsVaadinUtils.getAvailableSitesMap(A_CmsUI.getCmsObject()); 260 m_context = context; 261 context.setDialog(this); 262 CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null); 263 List<CmsFavoriteEntry> entries = m_favDao.loadFavorites(); 264 m_cancelButton.addClickListener(evt -> m_context.close()); 265 m_favContainer.addLayoutClickListener(evt -> { 266 CmsFavoriteEntry entry = getEntry(evt.getChildComponent()); 267 if (entry != null) { // may be null when user double clicks on delete icon 268 m_context.openFavorite(entry); 269 } 270 }); 271 m_currentLocation = context.getFavoriteForCurrentLocation(); 272 m_addButton.setEnabled(m_currentLocation.isPresent()); 273 m_addButton.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_FAVORITES_ADD_BUTTON_0)); 274 m_addButton.addClickListener(evt -> onClickAdd()); 275 initEntries(entries); 276 CmsObject cms = A_CmsUI.getCmsObject(); 277 m_siteBox.initOptions(cms, true); 278 SiteSelectorOption option = m_siteBox.getOptionForSiteRoot(cms.getRequestContext().getSiteRoot()); 279 m_siteBox.setValue(option != null ? option : new SiteSelectorOption(cms.getRequestContext().getSiteRoot(), null, null)); 280 m_siteBox.addValueChangeListener(evt -> { 281 if (!evt.getOldValue().equals(evt.getValue())) { 282 m_context.changeSite(evt.getValue()); 283 } 284 }); 285 m_siteBox.setPageLength(CmsExtendedSiteSelector.LONG_PAGE_LENGTH); 286 if (OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_USER)) { 287 LinkedHashMap<CmsUUID, String> projects = CmsVaadinUtils.getProjectsMap(cms); 288 m_projectBox.setDataProvider(new ListDataProvider<>(projects.keySet())); 289 m_projectBox.setItemCaptionGenerator(project -> projects.get(project)); 290 m_projectBox.setEmptySelectionAllowed(false); 291 m_projectBox.setValue(cms.getRequestContext().getCurrentProject().getId()); 292 m_projectBox.addValueChangeListener(evt -> { 293 if (!evt.getOldValue().equals(evt.getValue())) { 294 m_context.changeProject(evt.getValue()); 295 } 296 }); 297 } else { 298 m_projectBox.setVisible(false); 299 m_projectBoxLabel.setVisible(false); 300 } 301 } 302 303 /** 304 * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_RowBuilder#buildRow(org.opencms.ui.components.editablegroup.CmsEditableGroup, com.vaadin.ui.Component) 305 */ 306 public CmsFavInfo buildRow(CmsEditableGroup group, Component component) { 307 308 CmsFavInfo info = (CmsFavInfo)component; 309 CmsEditableGroupButtons buttons = new CmsEditableGroupButtons(new SaveAfterChangeActionHandler(info)); 310 info.setButtons(buttons); 311 return info; 312 } 313 314 /** 315 * Saves the list of currently displayed favorites. 316 */ 317 protected void doSave() { 318 319 List<CmsFavoriteEntry> entries = getEntries(); 320 try { 321 m_favDao.saveFavorites(entries); 322 } catch (Exception e) { 323 CmsErrorDialog.showErrorDialog(e); 324 } 325 } 326 327 /** 328 * @see org.opencms.ui.components.CmsBasicDialog#enableMaxHeight() 329 */ 330 @Override 331 protected void enableMaxHeight() { 332 333 // do nothing here, we want to disable the max height mechanism 334 } 335 336 /** 337 * Initializes the bookmark widgets. 338 * 339 * @param entries the list of bookmark entries 340 */ 341 protected void initEntries(List<CmsFavoriteEntry> entries) { 342 343 m_favContainer.removeAllComponents(); 344 m_group = new CmsEditableGroup(m_favContainer, null, new EmptyHandler()); 345 m_group.setEditEnabled(true); 346 m_group.setAddButtonVisible(false); 347 m_group.setRowBuilder(this); 348 m_group.init(); 349 350 for (CmsFavoriteEntry favEntry : entries) { 351 Component favInfo; 352 try { 353 favInfo = createFavInfo(favEntry); 354 m_group.addRow(favInfo); 355 } catch (CmsException e) { 356 LOG.warn(e.getLocalizedMessage(), e); 357 } 358 359 } 360 } 361 362 /** 363 * Gets the favorite entries corresponding to the currently displayed favorite widgets. 364 * 365 * @return the list of favorite entries 366 */ 367 List<CmsFavoriteEntry> getEntries() { 368 369 List<CmsFavoriteEntry> result = new ArrayList<>(); 370 for (I_CmsEditableGroupRow row : m_group.getRows()) { 371 CmsFavoriteEntry entry = ((CmsFavInfo)row).getEntry(); 372 result.add(entry); 373 } 374 return result; 375 } 376 377 /** 378 * Creates a favorite widget for a favorite entry. 379 * 380 * @param entry the favorite entry 381 * @return the favorite widget 382 * 383 * @throws CmsException if something goes wrong 384 */ 385 private CmsFavInfo createFavInfo(CmsFavoriteEntry entry) throws CmsException { 386 387 String title = ""; 388 String subtitle = ""; 389 CmsFavInfo result = new CmsFavInfo(entry); 390 CmsObject cms = A_CmsUI.getCmsObject(); 391 String project = getProject(cms, entry); 392 String site = getSite(cms, entry); 393 CmsResource resource = null; 394 try { 395 CmsUUID idToLoad = entry.getDetailId() != null ? entry.getDetailId() : entry.getStructureId(); 396 resource = cms.readResource(idToLoad, CmsResourceFilter.IGNORE_EXPIRATION.addRequireVisible()); 397 CmsResourceUtil resutil = new CmsResourceUtil(cms, resource); 398 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(entry.getCustomTitle())) { 399 title = entry.getCustomTitle(); 400 } else { 401 switch (entry.getType()) { 402 case explorerFolder: 403 default: 404 title = CmsStringUtil.isEmpty(resutil.getTitle()) 405 ? CmsResource.getName(resource.getRootPath()) 406 : resutil.getTitle(); 407 break; 408 case page: 409 title = resutil.getTitle(); 410 break; 411 } 412 } 413 subtitle = resource.getRootPath(); 414 CmsResourceIcon icon = result.getResourceIcon(); 415 icon.initContent(resutil, CmsResource.STATE_UNCHANGED, false, false); 416 } catch (CmsException e) { 417 LOG.warn(e.getLocalizedMessage(), e); 418 } 419 result.setResource(resource); 420 result.getTopLine().setValue(title); 421 result.getBottomLine().setValue(subtitle); 422 result.getProjectLabel().setValue(project); 423 result.getSiteLabel().setValue(site); 424 425 return result; 426 427 } 428 429 /** 430 * Gets the favorite entry for a given row. 431 * 432 * @param row the widget used to display the favorite 433 * @return the favorite entry for the widget 434 */ 435 private CmsFavoriteEntry getEntry(Component row) { 436 437 if (row instanceof CmsFavInfo) { 438 439 return ((CmsFavInfo)row).getEntry(); 440 441 } 442 return null; 443 444 } 445 446 /** 447 * Gets the project name for a favorite entry. 448 * 449 * @param cms the CMS context 450 * @param entry the favorite entry 451 * @return the project name for the favorite entry 452 * @throws CmsException if something goes wrong 453 */ 454 private String getProject(CmsObject cms, CmsFavoriteEntry entry) throws CmsException { 455 456 String result = m_projectLabels.get(entry.getProjectId()); 457 if (result == null) { 458 result = cms.readProject(entry.getProjectId()).getName(); 459 m_projectLabels.put(entry.getProjectId(), result); 460 } 461 return result; 462 } 463 464 /** 465 * Gets the site label for the entry. 466 * 467 * @param cms the current CMS context 468 * @param entry the entry 469 * @return the site label for the entry 470 */ 471 private String getSite(CmsObject cms, CmsFavoriteEntry entry) { 472 473 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(entry.getSiteRoot()); 474 if (m_siteLabels.containsKey(entry.getSiteRoot())) { 475 return m_siteLabels.get(entry.getSiteRoot()); 476 } 477 String result = entry.getSiteRoot(); 478 if (site != null) { 479 if (!CmsStringUtil.isEmpty(site.getTitle())) { 480 result = site.getTitle(); 481 } 482 } 483 return result; 484 } 485 486 /** 487 * The click handler for the add button. 488 */ 489 private void onClickAdd() { 490 491 if (m_currentLocation.isPresent()) { 492 CmsFavoriteEntry entry = m_currentLocation.get(); 493 List<CmsFavoriteEntry> entries = getEntries(); 494 entries.add(entry); 495 try { 496 m_favDao.saveFavorites(entries); 497 } catch (Exception e) { 498 CmsErrorDialog.showErrorDialog(e); 499 } 500 m_context.close(); 501 } 502 } 503 504}