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 m_siteBox.setValue(new SiteSelectorOption(cms.getRequestContext().getSiteRoot(), null, null)); 279 m_siteBox.addValueChangeListener(evt -> { 280 if (!evt.getOldValue().equals(evt.getValue())) { 281 m_context.changeSite(evt.getValue()); 282 } 283 }); 284 m_siteBox.setPageLength(CmsExtendedSiteSelector.LONG_PAGE_LENGTH); 285 if (OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_USER)) { 286 LinkedHashMap<CmsUUID, String> projects = CmsVaadinUtils.getProjectsMap(cms); 287 m_projectBox.setDataProvider(new ListDataProvider<>(projects.keySet())); 288 m_projectBox.setItemCaptionGenerator(project -> projects.get(project)); 289 m_projectBox.setEmptySelectionAllowed(false); 290 m_projectBox.setValue(cms.getRequestContext().getCurrentProject().getId()); 291 m_projectBox.addValueChangeListener(evt -> { 292 if (!evt.getOldValue().equals(evt.getValue())) { 293 m_context.changeProject(evt.getValue()); 294 } 295 }); 296 } else { 297 m_projectBox.setVisible(false); 298 m_projectBoxLabel.setVisible(false); 299 } 300 } 301 302 /** 303 * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_RowBuilder#buildRow(org.opencms.ui.components.editablegroup.CmsEditableGroup, com.vaadin.ui.Component) 304 */ 305 public CmsFavInfo buildRow(CmsEditableGroup group, Component component) { 306 307 CmsFavInfo info = (CmsFavInfo)component; 308 CmsEditableGroupButtons buttons = new CmsEditableGroupButtons(new SaveAfterChangeActionHandler(info)); 309 info.setButtons(buttons); 310 return info; 311 } 312 313 /** 314 * Saves the list of currently displayed favorites. 315 */ 316 protected void doSave() { 317 318 List<CmsFavoriteEntry> entries = getEntries(); 319 try { 320 m_favDao.saveFavorites(entries); 321 } catch (Exception e) { 322 CmsErrorDialog.showErrorDialog(e); 323 } 324 } 325 326 /** 327 * @see org.opencms.ui.components.CmsBasicDialog#enableMaxHeight() 328 */ 329 @Override 330 protected void enableMaxHeight() { 331 332 // do nothing here, we want to disable the max height mechanism 333 } 334 335 /** 336 * Initializes the bookmark widgets. 337 * 338 * @param entries the list of bookmark entries 339 */ 340 protected void initEntries(List<CmsFavoriteEntry> entries) { 341 342 m_favContainer.removeAllComponents(); 343 m_group = new CmsEditableGroup(m_favContainer, null, new EmptyHandler()); 344 m_group.setEditEnabled(true); 345 m_group.setAddButtonVisible(false); 346 m_group.setRowBuilder(this); 347 m_group.init(); 348 349 for (CmsFavoriteEntry favEntry : entries) { 350 Component favInfo; 351 try { 352 favInfo = createFavInfo(favEntry); 353 m_group.addRow(favInfo); 354 } catch (CmsException e) { 355 LOG.warn(e.getLocalizedMessage(), e); 356 } 357 358 } 359 } 360 361 /** 362 * Gets the favorite entries corresponding to the currently displayed favorite widgets. 363 * 364 * @return the list of favorite entries 365 */ 366 List<CmsFavoriteEntry> getEntries() { 367 368 List<CmsFavoriteEntry> result = new ArrayList<>(); 369 for (I_CmsEditableGroupRow row : m_group.getRows()) { 370 CmsFavoriteEntry entry = ((CmsFavInfo)row).getEntry(); 371 result.add(entry); 372 } 373 return result; 374 } 375 376 /** 377 * Creates a favorite widget for a favorite entry. 378 * 379 * @param entry the favorite entry 380 * @return the favorite widget 381 * 382 * @throws CmsException if something goes wrong 383 */ 384 private CmsFavInfo createFavInfo(CmsFavoriteEntry entry) throws CmsException { 385 386 String title = ""; 387 String subtitle = ""; 388 CmsFavInfo result = new CmsFavInfo(entry); 389 CmsObject cms = A_CmsUI.getCmsObject(); 390 String project = getProject(cms, entry); 391 String site = getSite(cms, entry); 392 CmsResource resource = null; 393 try { 394 CmsUUID idToLoad = entry.getDetailId() != null ? entry.getDetailId() : entry.getStructureId(); 395 resource = cms.readResource(idToLoad, CmsResourceFilter.IGNORE_EXPIRATION.addRequireVisible()); 396 CmsResourceUtil resutil = new CmsResourceUtil(cms, resource); 397 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(entry.getCustomTitle())) { 398 title = entry.getCustomTitle(); 399 } else { 400 switch (entry.getType()) { 401 case explorerFolder: 402 default: 403 title = CmsStringUtil.isEmpty(resutil.getTitle()) 404 ? CmsResource.getName(resource.getRootPath()) 405 : resutil.getTitle(); 406 break; 407 case page: 408 title = resutil.getTitle(); 409 break; 410 } 411 } 412 subtitle = resource.getRootPath(); 413 CmsResourceIcon icon = result.getResourceIcon(); 414 icon.initContent(resutil, CmsResource.STATE_UNCHANGED, false, false); 415 } catch (CmsException e) { 416 LOG.warn(e.getLocalizedMessage(), e); 417 } 418 result.setResource(resource); 419 result.getTopLine().setValue(title); 420 result.getBottomLine().setValue(subtitle); 421 result.getProjectLabel().setValue(project); 422 result.getSiteLabel().setValue(site); 423 424 return result; 425 426 } 427 428 /** 429 * Gets the favorite entry for a given row. 430 * 431 * @param row the widget used to display the favorite 432 * @return the favorite entry for the widget 433 */ 434 private CmsFavoriteEntry getEntry(Component row) { 435 436 if (row instanceof CmsFavInfo) { 437 438 return ((CmsFavInfo)row).getEntry(); 439 440 } 441 return null; 442 443 } 444 445 /** 446 * Gets the project name for a favorite entry. 447 * 448 * @param cms the CMS context 449 * @param entry the favorite entry 450 * @return the project name for the favorite entry 451 * @throws CmsException if something goes wrong 452 */ 453 private String getProject(CmsObject cms, CmsFavoriteEntry entry) throws CmsException { 454 455 String result = m_projectLabels.get(entry.getProjectId()); 456 if (result == null) { 457 result = cms.readProject(entry.getProjectId()).getName(); 458 m_projectLabels.put(entry.getProjectId(), result); 459 } 460 return result; 461 } 462 463 /** 464 * Gets the site label for the entry. 465 * 466 * @param cms the current CMS context 467 * @param entry the entry 468 * @return the site label for the entry 469 */ 470 private String getSite(CmsObject cms, CmsFavoriteEntry entry) { 471 472 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(entry.getSiteRoot()); 473 if (m_siteLabels.containsKey(entry.getSiteRoot())) { 474 return m_siteLabels.get(entry.getSiteRoot()); 475 } 476 String result = entry.getSiteRoot(); 477 if (site != null) { 478 if (!CmsStringUtil.isEmpty(site.getTitle())) { 479 result = site.getTitle(); 480 } 481 } 482 return result; 483 } 484 485 /** 486 * The click handler for the add button. 487 */ 488 private void onClickAdd() { 489 490 if (m_currentLocation.isPresent()) { 491 CmsFavoriteEntry entry = m_currentLocation.get(); 492 List<CmsFavoriteEntry> entries = getEntries(); 493 entries.add(entry); 494 try { 495 m_favDao.saveFavorites(entries); 496 } catch (Exception e) { 497 CmsErrorDialog.showErrorDialog(e); 498 } 499 m_context.close(); 500 } 501 } 502 503}