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.ade.publish.client; 029 030import org.opencms.ade.publish.client.CmsPublishItemStatus.Signal; 031import org.opencms.ade.publish.client.CmsPublishSelectPanel.CheckBoxUpdate; 032import org.opencms.ade.publish.shared.CmsPublishGroup; 033import org.opencms.ade.publish.shared.CmsPublishResource; 034import org.opencms.gwt.client.CmsCoreProvider; 035import org.opencms.gwt.client.CmsEditableData; 036import org.opencms.gwt.client.ui.CmsList; 037import org.opencms.gwt.client.ui.CmsListItemWidget; 038import org.opencms.gwt.client.ui.CmsPushButton; 039import org.opencms.gwt.client.ui.CmsSimpleListItem; 040import org.opencms.gwt.client.ui.FontOpenCms; 041import org.opencms.gwt.client.ui.I_CmsButton; 042import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; 043import org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog; 044import org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog.DialogOptions; 045import org.opencms.gwt.client.ui.contenteditor.I_CmsContentEditorHandler; 046import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuButton; 047import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuHandler; 048import org.opencms.gwt.client.ui.css.I_CmsConstantsBundle; 049import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle; 050import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 051import org.opencms.gwt.client.ui.input.CmsCheckBox; 052import org.opencms.gwt.client.ui.input.CmsTriStateCheckBox; 053import org.opencms.gwt.client.ui.input.CmsTriStateCheckBox.State; 054import org.opencms.gwt.client.ui.tree.CmsTreeItem; 055import org.opencms.gwt.client.util.CmsStyleVariable; 056import org.opencms.gwt.shared.CmsAdditionalInfoBean; 057import org.opencms.gwt.shared.CmsCoreData.AdeContext; 058import org.opencms.util.CmsStringUtil; 059import org.opencms.util.CmsUUID; 060 061import java.util.List; 062import java.util.Map; 063 064import com.google.gwt.dom.client.Style.Unit; 065import com.google.gwt.dom.client.Style.Visibility; 066import com.google.gwt.event.dom.client.ClickEvent; 067import com.google.gwt.event.dom.client.ClickHandler; 068import com.google.gwt.event.logical.shared.ValueChangeEvent; 069import com.google.gwt.event.logical.shared.ValueChangeHandler; 070import com.google.gwt.user.client.ui.Composite; 071import com.google.gwt.user.client.ui.FlowPanel; 072import com.google.gwt.user.client.ui.HTML; 073import com.google.gwt.user.client.ui.SimplePanel; 074import com.google.gwt.user.client.ui.Widget; 075 076/** 077 * A panel representing a single publish group.<p> 078 * 079 * @since 8.0.0 080 */ 081public class CmsPublishGroupPanel extends Composite { 082 083 /** Button slot mapping for publish list items. */ 084 public static int[] DEFAULT_SLOT_MAPPING = new int[] {0, 1, 2, 3}; 085 086 /** The slot for the preview button. */ 087 public static final int SLOT_EDIT = 1; 088 089 /** The slot for the context menu button. */ 090 public static final int SLOT_MENU = 0; 091 092 /** The slot for the 'remove' checkbox. */ 093 public static final int SLOT_REMOVE = 2; 094 095 /** The slot for the warning symbol. */ 096 public static final int SLOT_WARNING = 3; 097 098 /** The CSS bundle used for this widget. */ 099 protected static final I_CmsPublishCss CSS = I_CmsPublishLayoutBundle.INSTANCE.publishCss(); 100 101 /** The number of button slits. */ 102 private static final int NUM_BUTTON_SLOTS = 4; 103 104 /** Text metrics key. */ 105 private static final String TM_PUBLISH_LIST = "PublishList"; 106 107 /** The group index for this panel's corresponding group. */ 108 protected int m_groupIndex; 109 110 /** The data model for the publish dialog. */ 111 protected CmsPublishDataModel m_model; 112 113 /** The handler which is called when the publish item selection changes. */ 114 protected I_CmsPublishSelectionChangeHandler m_selectionChangeHandler; 115 116 /** The content editor handler. */ 117 I_CmsContentEditorHandler m_editorHandler; 118 119 /** The context menu handler. */ 120 private CmsContextMenuHandler m_contextMenuHandler; 121 122 /** The global map of selection controllers of *ALL* groups (to which this group's selection controllers are added). */ 123 private Map<CmsUUID, CmsPublishItemSelectionController> m_controllersById; 124 125 /** The group header (containing the label and add/remove buttons). */ 126 private CmsSimpleListItem m_header = new CmsSimpleListItem(); 127 128 /** The number of loaded publish item widgets for this group (used for scrolling).<p> */ 129 private int m_itemIndex; 130 131 /** The root panel of this widget. */ 132 private CmsList<CmsTreeItem> m_panel = new CmsList<CmsTreeItem>(); 133 134 /** The publish resources of the current group.<p>*/ 135 private List<CmsPublishResource> m_publishResources; 136 137 /** Checkbox for selecting/deselecting all group items. */ 138 private CmsTriStateCheckBox m_selectGroup; 139 140 /** A flag which indicates whether only resources with problems should be shown. */ 141 private boolean m_showProblemsOnly; 142 143 /** 144 * Constructs a new instance.<p> 145 * 146 * @param publishGroup the group for which to build the group panel 147 * @param title the title of the group 148 * @param groupIndex the index of the group which this panel should render 149 * @param selectionChangeHandler the handler for selection changes for publish resources 150 * @param model the data model for the publish resources 151 * @param controllersById the map of selection controllers to which this panel's selection controllers should be added 152 * @param menuHandler the context menu handler 153 * @param editorHandler the content editor handler 154 * @param showProblemsOnly if true, sets this panel into "show resources with problems only" mode 155 */ 156 public CmsPublishGroupPanel( 157 CmsPublishGroup publishGroup, 158 String title, 159 int groupIndex, 160 I_CmsPublishSelectionChangeHandler selectionChangeHandler, 161 CmsPublishDataModel model, 162 Map<CmsUUID, CmsPublishItemSelectionController> controllersById, 163 CmsContextMenuHandler menuHandler, 164 I_CmsContentEditorHandler editorHandler, 165 boolean showProblemsOnly) { 166 167 initWidget(m_panel); 168 m_panel.add(m_header); 169 m_model = model; 170 m_groupIndex = groupIndex; 171 m_contextMenuHandler = menuHandler; 172 m_editorHandler = editorHandler; 173 m_publishResources = model.getGroups().get(groupIndex).getResources(); 174 m_controllersById = controllersById; 175 m_panel.truncate(TM_PUBLISH_LIST, CmsPublishDialog.DIALOG_WIDTH); 176 initSelectButtons(); 177 if ((groupIndex == 0) && publishGroup.isAutoSelectable()) { 178 m_model.signalGroup(Signal.publish, 0); 179 } 180 m_showProblemsOnly = showProblemsOnly; 181 if (hasNoProblemResources() && showProblemsOnly) { 182 this.setVisible(false); 183 } 184 185 HTML label = new HTML(); 186 label.setText(title); 187 label.addStyleName(CSS.groupHeader()); 188 m_header.add(label); 189 190 FlowPanel clear = new FlowPanel(); 191 clear.setStyleName(CSS.clear()); 192 m_header.add(clear); 193 m_selectionChangeHandler = selectionChangeHandler; 194 } 195 196 /** 197 * Creates a basic list item widget for a given publish resource bean.<p> 198 * 199 * @param resourceBean the publish resource bean 200 * @param slotMapping maps button slot ids to actual button indexes 201 * 202 * @return the list item widget representing the publish resource bean 203 */ 204 public static CmsListItemWidget createListItemWidget(CmsPublishResource resourceBean, int[] slotMapping) { 205 206 CmsListItemWidget itemWidget = new CmsListItemWidget(resourceBean); 207 if (resourceBean.getUserLastModified() != null) { 208 String userLabel = org.opencms.ade.publish.client.Messages.get().key( 209 org.opencms.ade.publish.client.Messages.GUI_LABEL_USER_LAST_MODIFIED_0); 210 itemWidget.addAdditionalInfo( 211 new CmsAdditionalInfoBean(userLabel, resourceBean.getUserLastModified(), null)); 212 } 213 if (resourceBean.getDateLastModifiedString() != null) { 214 String dateLabel = org.opencms.ade.publish.client.Messages.get().key( 215 org.opencms.ade.publish.client.Messages.GUI_LABEL_DATE_LAST_MODIFIED_0); 216 itemWidget.addAdditionalInfo( 217 new CmsAdditionalInfoBean(dateLabel, resourceBean.getDateLastModifiedString(), null)); 218 } 219 220 String resourceUser = resourceBean.getUserLastModified(); 221 String currentUser = CmsCoreProvider.get().getUserInfo().getName(); 222 if (!currentUser.equals(resourceUser)) { 223 itemWidget.setTopRightIcon( 224 I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().changed(), 225 Messages.get().key(Messages.GUI_CHANGED_BY_USER_1, resourceBean.getUserLastModified())); 226 } 227 for (int i = 0; i < NUM_BUTTON_SLOTS; i++) { 228 SimplePanel panel = new SimplePanel(); 229 panel.getElement().getStyle().setWidth(20, Unit.PX); 230 panel.getElement().getStyle().setHeight(20, Unit.PX); 231 if (i == slotMapping[SLOT_WARNING]) { 232 panel.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().permaVisible()); 233 } 234 itemWidget.addButton(panel); 235 } 236 237 if (CmsPublishDataModel.hasProblems(resourceBean)) { 238 Widget warningImage = FontOpenCms.WARNING.getWidget(20, I_CmsConstantsBundle.INSTANCE.css().colorWarning()); 239 warningImage.setTitle(resourceBean.getInfo().getValue()); 240 warningImage.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().permaVisible()); 241 fillButtonSlot(itemWidget, SLOT_WARNING, warningImage, slotMapping); 242 } 243 return itemWidget; 244 } 245 246 /** 247 * Fills a slot for a button in a publish list item widget.<p> 248 * 249 * @param listItemWidget the list item widget 250 * @param index the slot index 251 * @param widget the widget which should be displayed in the slot 252 * @param slotMapping array mapping logical slot ids to button indexes 253 */ 254 public static void fillButtonSlot(CmsListItemWidget listItemWidget, int index, Widget widget, int[] slotMapping) { 255 256 int realIndex = slotMapping[index]; 257 if (realIndex >= 0) { 258 SimplePanel panel = (SimplePanel)listItemWidget.getButton(slotMapping[index]); 259 panel.clear(); 260 panel.add(widget); 261 } 262 } 263 264 /** 265 * Adds the list item for the next publish resource and returns true on success, while 266 * also incrementing the internal item index.<p> 267 * 268 * @return true if an item was added 269 */ 270 public boolean addNextItem() { 271 272 if (m_itemIndex >= m_publishResources.size()) { 273 return false; 274 } 275 CmsPublishResource res = m_publishResources.get(m_itemIndex); 276 m_itemIndex += 1; 277 if (m_showProblemsOnly && (!CmsPublishDataModel.hasProblems(res))) { 278 return false; 279 } else { 280 addItem(res); 281 return true; 282 } 283 } 284 285 /** 286 * Returns true if there are more potential items to add.<p> 287 * 288 * @return true if there are possibly more items 289 */ 290 public boolean hasMoreItems() { 291 292 return m_itemIndex < m_publishResources.size(); 293 } 294 295 /** 296 * Hides the tri-state select box for the group.<p> 297 */ 298 public void hideGroupSelectCheckBox() { 299 300 m_selectGroup.getElement().getStyle().setVisibility(Visibility.HIDDEN); 301 } 302 303 /** 304 * Updates the check box state for this group.<p> 305 * 306 * @param value the state to use for updating the check box 307 */ 308 public void updateCheckboxState(CmsPublishItemStateSummary value) { 309 310 CheckBoxUpdate update = CmsPublishSelectPanel.updateCheckbox(value); 311 m_selectGroup.setTitle(update.getAction()); 312 m_selectGroup.setState(update.getState(), false); 313 } 314 315 /** 316 * Returns true if the corresponding group has no resources with problems.<p> 317 * 318 * @return true if the group for this panel has no resources with problems 319 */ 320 protected boolean hasNoProblemResources() { 321 322 return 0 == m_model.countResourcesInGroup( 323 new CmsPublishDataModel.HasProblems(), 324 m_model.getGroups().get(m_groupIndex).getResources()); 325 } 326 327 /** 328 * Returns true if the corresponding group has only resources with problems.<p> 329 * 330 * @return true if the group for this panel has only resources with problems. 331 */ 332 protected boolean hasOnlyProblemResources() { 333 334 return m_model.getGroups().get(m_groupIndex).getResources().size() == m_model.countResourcesInGroup( 335 new CmsPublishDataModel.HasProblems(), 336 m_model.getGroups().get(m_groupIndex).getResources()); 337 } 338 339 /** 340 * Adds a resource bean to this group.<p> 341 * 342 * @param resourceBean the resource bean which should be added 343 */ 344 private void addItem(CmsPublishResource resourceBean) { 345 346 CmsTreeItem row = buildItem(resourceBean, m_model.getStatus(resourceBean.getId()), false); 347 m_panel.add(row); 348 349 for (CmsPublishResource related : resourceBean.getRelated()) { 350 row.addChild(buildItem(related, m_model.getStatus(related.getId()), true)); 351 } 352 } 353 354 /** 355 * Creates a widget from resource bean data.<p> 356 * 357 * @param resourceBean the resource bean for which a widget should be constructed 358 * @param status the publish item status 359 * @param isSubItem true if this is not a top-level publish item 360 * 361 * @return a widget representing the resource bean 362 */ 363 private CmsTreeItem buildItem( 364 final CmsPublishResource resourceBean, 365 CmsPublishItemStatus status, 366 boolean isSubItem) { 367 368 CmsListItemWidget itemWidget = createListItemWidget(resourceBean, DEFAULT_SLOT_MAPPING); 369 if ((m_editorHandler != null) && resourceBean.getPermissionInfo().hasWritePermission()) { 370 CmsPushButton editButton = new CmsPushButton(); 371 editButton.setImageClass(I_CmsButton.PEN_SMALL); 372 editButton.setButtonStyle(ButtonStyle.FONT_ICON, null); 373 editButton.setTitle( 374 org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_BUTTON_ELEMENT_EDIT_0)); 375 editButton.addClickHandler(new ClickHandler() { 376 377 public void onClick(ClickEvent event) { 378 379 CmsPushButton button = (CmsPushButton)event.getSource(); 380 button.clearHoverState(); 381 CmsEditableData editableData = new CmsEditableData(); 382 editableData.setStructureId(resourceBean.getId()); 383 CmsContentEditorDialog.get().openEditDialog( 384 editableData, 385 false, 386 null, 387 new DialogOptions(), 388 m_editorHandler); 389 } 390 }); 391 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(resourceBean.getPermissionInfo().getNoEditReason())) { 392 editButton.disable(resourceBean.getPermissionInfo().getNoEditReason()); 393 } 394 fillButtonSlot(itemWidget, SLOT_EDIT, editButton, DEFAULT_SLOT_MAPPING); 395 } 396 if (resourceBean.getPermissionInfo().hasViewPermission()) { 397 CmsContextMenuButton button = new CmsContextMenuButton( 398 resourceBean.getId(), 399 m_contextMenuHandler, 400 AdeContext.publish); 401 fillButtonSlot(itemWidget, SLOT_MENU, button, DEFAULT_SLOT_MAPPING); 402 } 403 resourceBean.getId(); 404 final CmsStyleVariable styleVar = new CmsStyleVariable(itemWidget); 405 styleVar.setValue(CSS.itemToKeep()); 406 407 final CmsCheckBox checkbox = new CmsCheckBox(); 408 CmsTreeItem row; 409 row = new CmsTreeItem(true, checkbox, itemWidget); 410 if (isSubItem) { 411 checkbox.getElement().getStyle().setVisibility(Visibility.HIDDEN); 412 } 413 414 row.setOpen(false); 415 row.addStyleName(CSS.publishRow()); 416 417 // we do not need most of the interactive elements for the sub-items 418 if (!isSubItem) { 419 ClickHandler checkboxHandler = new ClickHandler() { 420 421 /** 422 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) 423 */ 424 public void onClick(ClickEvent event) { 425 426 boolean checked = checkbox.isChecked(); 427 m_model.signal(checked ? Signal.publish : Signal.unpublish, resourceBean.getId()); 428 } 429 }; 430 checkbox.addClickHandler(checkboxHandler); 431 432 final boolean hasProblem = CmsPublishDataModel.hasProblems(resourceBean); 433 if (hasProblem) { 434 // can't select resource with problems 435 checkbox.setChecked(false); 436 checkbox.setEnabled(false); 437 } 438 439 final CmsCheckBox remover = new CmsCheckBox(); 440 final CmsPublishItemSelectionController controller = new CmsPublishItemSelectionController( 441 resourceBean.getId(), 442 checkbox, 443 remover, 444 styleVar, 445 hasProblem); 446 m_controllersById.put(resourceBean.getId(), controller); 447 448 remover.setTitle(Messages.get().key(Messages.GUI_PUBLISH_REMOVE_BUTTON_0)); 449 remover.addClickHandler(new ClickHandler() { 450 451 /** 452 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) 453 */ 454 public void onClick(ClickEvent e) { 455 456 boolean remove = remover.isChecked(); 457 m_model.signal(remove ? Signal.remove : Signal.unremove, resourceBean.getId()); 458 } 459 }); 460 if (resourceBean.isRemovable()) { 461 fillButtonSlot(itemWidget, SLOT_REMOVE, remover, DEFAULT_SLOT_MAPPING); 462 } 463 controller.update(status); 464 } 465 return row; 466 } 467 468 /** 469 * Initializes the "select all/none" buttons, adds them to the group header and 470 * attaches event handlers to them.<p> 471 */ 472 private void initSelectButtons() { 473 474 m_selectGroup = new CmsTriStateCheckBox(""); 475 m_selectGroup.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().inlineBlock()); 476 m_selectGroup.setNextStateAfterIntermediateState(State.on); 477 m_selectGroup.addValueChangeHandler(new ValueChangeHandler<CmsTriStateCheckBox.State>() { 478 479 public void onValueChange(ValueChangeEvent<State> event) { 480 481 State state = event.getValue(); 482 if (state == State.on) { 483 m_model.signalGroup(Signal.publish, m_groupIndex); 484 } else if (state == State.off) { 485 m_model.signalGroup(Signal.unpublish, m_groupIndex); 486 } 487 } 488 489 }); 490 m_header.add(m_selectGroup); 491 } 492}