/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * For further information about Alkacon Software, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.opencms.ade.publish.client; import org.opencms.ade.publish.client.CmsPublishItemStatus.Signal; import org.opencms.ade.publish.shared.CmsProjectBean; import org.opencms.ade.publish.shared.CmsPublishGroup; import org.opencms.ade.publish.shared.CmsPublishOptions; import org.opencms.ade.publish.shared.CmsPublishResource; import org.opencms.file.CmsResource; import org.opencms.gwt.client.ui.CmsAlertDialog; import org.opencms.gwt.client.ui.CmsPushButton; import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle; import org.opencms.gwt.client.ui.input.CmsCheckBox; import org.opencms.gwt.client.ui.input.CmsSelectBox; import org.opencms.gwt.client.util.CmsMessages; import org.opencms.gwt.client.util.CmsScrollToBottomHandler; import org.opencms.util.CmsPair; import org.opencms.util.CmsUUID; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.Maps; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.InlineHTML; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; /** * This is the main widget of the publish dialog.<p> * * It allows the user to choose which resources from the publish list should be published * and/or removed from the publish list. * * @since 8.0.0 */ public class CmsPublishSelectPanel extends Composite implements I_CmsPublishSelectionChangeHandler, I_CmsPublishItemStatusUpdateHandler { /** The UiBinder interface for this widget. */ protected interface I_CmsPublishSelectPanelUiBinder extends UiBinder<Widget, CmsPublishSelectPanel> { // empty } /** * Command for adding more list items to the list of publish items.<p> */ protected class MoreItemsCommand implements RepeatingCommand { /** The number of items left to add. */ private int m_numItems; /** * Creates a new instance.<p> * * @param numItems the maximal number of items to add */ public MoreItemsCommand(int numItems) { m_numItems = numItems; } /** * @see com.google.gwt.core.client.Scheduler.RepeatingCommand#execute() */ public boolean execute() { if (m_numItems == 0) { finishLoading(); return false; } boolean hasMore = CmsPublishSelectPanel.this.addNextItem(); if (!hasMore) { finishLoading(); return false; } m_numItems -= 1; return true; } } /** The CSS bundle used for this widget. */ private static final I_CmsPublishCss CSS = I_CmsPublishLayoutBundle.INSTANCE.publishCss(); /** * When dynamically adding groups on scrolling, the number of groups should be calculated such that the total sum of resources * in the groups is the smallest number greater or equal to this constant.<p> */ private static final int MIN_BATCH_SIZE = 20; /** The scroll threshold for the list of problem resources. */ private static final int SCROLL_THRESHOLD = 100; /** Text metrics key. */ private static final String TM_PUBLISH = "Publish"; /** The UiBinder instance used for this widget. */ private static final I_CmsPublishSelectPanelUiBinder UI_BINDER = GWT.create(I_CmsPublishSelectPanelUiBinder.class); /** The button for escaping from the publish dialog. */ @UiField protected CmsPushButton m_cancelButton; /** The checkbox for the "show problems only" mode. */ @UiField protected CmsCheckBox m_checkboxProblems; /** The checkbox for including related resources. */ @UiField protected CmsCheckBox m_checkboxRelated; /** The checkbox for including sibling resources. */ @UiField protected CmsCheckBox m_checkboxSiblings; /** The panel containing the publish groups. */ @UiField protected Panel m_groupPanelContainer; /** The label which is displayed when there are no resources to publish. */ @UiField protected Label m_noResources; /** The panel which shows a message telling the user the number of problems. */ @UiField protected Panel m_problemsPanel; /** The project select box. */ @UiField protected CmsSelectBox m_projectSelector; /** The button for publishing. */ @UiField protected CmsPushButton m_publishButton; /** The publish dialog which contains this panel. */ protected CmsPublishDialog m_publishDialog; /** The data model for the publish dialog. */ protected CmsPublishDataModel m_model; /** The global map of selection controllers for all groups. */ protected Map<CmsUUID, CmsPublishItemSelectionController> m_selectionControllers = Maps.newHashMap(); /** The current publish list options. */ protected CmsPublishOptions m_publishOptions; /** The scroll panel containing the group panel. */ @UiField protected ScrollPanel m_scrollPanel; /** The button for selecting all resources for publishing. */ @UiField protected CmsPushButton m_selectAll; /** The label in front of the "select all/none" buttons. */ @UiField protected InlineLabel m_selectLabel; /** The button for de-selecting all resources for publishing. */ @UiField protected CmsPushButton m_selectNone; /** The label shown in front of the project selector. */ @UiField protected InlineLabel m_selectorLabel; /** The panel containing the project selector. */ @UiField protected FlowPanel m_selectorPanel; /** The top button bar. */ @UiField protected Panel m_topBar; /** The list of group panels for each publish list group. */ private List<CmsPublishGroupPanel> m_groupPanels = new ArrayList<CmsPublishGroupPanel>(); /** The current group panel. */ private CmsPublishGroupPanel m_currentGroupPanel; /** The current group index used for scrolling. */ private int m_currentGroupIndex; /** The label displaying the resource count. */ @UiField protected InlineHTML m_resourceCountLabel; /** Flag which indicates whether only resources with problems should be shown. */ private boolean m_showProblemsOnly; /** Flag to indicate whether new items are currently being added to the list. */ protected boolean m_loading; /** * Creates a new instance.<p> * * @param publishDialog the publish dialog to which this panel should belong * @param projects a map of projects, where the keys are the project ids and the values are the names of the projects * @param publishOptions the initial publish options */ public CmsPublishSelectPanel( CmsPublishDialog publishDialog, List<CmsProjectBean> projects, CmsPublishOptions publishOptions) { m_publishOptions = publishOptions; initWidget(UI_BINDER.createAndBindUi(this)); m_checkboxProblems.setVisible(false); List<CmsPair<String, String>> items = new ArrayList<CmsPair<String, String>>(); CmsMessages messages = Messages.get(); items.add(new CmsPair<String, String>( CmsUUID.getNullUUID().toString(), messages.key(Messages.GUI_PUBLISH_DIALOG_MY_CHANGES_0))); boolean foundOldProject = false; for (CmsProjectBean project : projects) { items.add(new CmsPair<String, String>(project.getId().toString(), project.getName())); // look if the project id from the last publish list is among the available projects. // (this might not be the case if the project has been deleted in the meantime.) if (project.getId().equals(publishOptions.getProjectId())) { foundOldProject = true; } } m_projectSelector.setItems(items); m_projectSelector.addStyleName(CSS.selector()); m_publishDialog = publishDialog; m_checkboxRelated.setChecked(publishOptions.isIncludeRelated()); m_checkboxSiblings.setChecked(publishOptions.isIncludeSiblings()); if (foundOldProject) { m_projectSelector.selectValue(publishOptions.getProjectId().toString()); } m_projectSelector.addValueChangeHandler(new ValueChangeHandler<String>() { /** * @see ValueChangeHandler#onValueChange(ValueChangeEvent) */ public void onValueChange(ValueChangeEvent<String> event) { m_publishOptions.setProjectId(new CmsUUID(event.getValue())); m_publishDialog.onChangeOptions(); } }); m_projectSelector.truncate(TM_PUBLISH, 200); m_checkboxRelated.addClickHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent e) { m_publishOptions.setIncludeRelated(m_checkboxRelated.isChecked()); m_publishDialog.onChangeOptions(); } }); m_checkboxSiblings.addClickHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent e) { m_publishOptions.setIncludeSiblings(m_checkboxSiblings.isChecked()); m_publishDialog.onChangeOptions(); } }); m_checkboxProblems.addClickHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent e) { setProblemMode(m_checkboxProblems.isChecked()); } }); m_publishButton.setText(messages.key(Messages.GUI_PUBLISH_DIALOG_PUBLISH_0)); m_publishButton.setUseMinWidth(true); m_cancelButton.setText(messages.key(Messages.GUI_PUBLISH_DIALOG_CANCEL_BUTTON_0)); m_cancelButton.setUseMinWidth(true); m_selectAll.setText(messages.key(Messages.GUI_PUBLISH_TOP_PANEL_ALL_BUTTON_0)); m_selectAll.setImageClass(I_CmsInputLayoutBundle.INSTANCE.inputCss().checkBoxImageChecked()); m_selectAll.setUseMinWidth(true); m_selectNone.setText(messages.key(Messages.GUI_PUBLISH_TOP_PANEL_NONE_BUTTON_0)); m_selectNone.setImageClass(I_CmsInputLayoutBundle.INSTANCE.inputCss().checkBoxImageUnchecked()); m_selectNone.setUseMinWidth(true); m_noResources.setText(messages.key(Messages.GUI_PUBLISH_DIALOG_NO_RES_0)); m_selectAll.addClickHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent e) { m_model.signalAll(Signal.publish); CmsPublishSelectPanel.this.onChangePublishSelection(); } }); m_selectNone.addClickHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent e) { m_model.signalAll(Signal.unpublish); CmsPublishSelectPanel.this.onChangePublishSelection(); } }); m_checkboxSiblings.setText(messages.key(Messages.GUI_PUBLISH_CHECKBOXES_SIBLINGS_0)); m_checkboxRelated.setText(messages.key(Messages.GUI_PUBLISH_CHECKBOXES_REL_RES_0)); m_checkboxProblems.setText(messages.key(Messages.GUI_PUBLISH_CHECKBOXES_PROBLEMS_0)); m_selectLabel.setText(messages.key(Messages.GUI_PUBLISH_TOP_PANEL_LEFT_LABEL_0)); m_selectorLabel.setText(messages.key(Messages.GUI_PUBLISH_TOP_PANEL_RIGHT_LABEL_0)); addScrollHandler(); } /** * Formats a number of publish resources in a more user-friendly form.<p> * * @param resourceCount a number of resources * * @return the formatted number of resources */ public static String formatResourceCount(int resourceCount) { return Messages.get().key(Messages.GUI_RESOURCE_COUNT_1, "" + resourceCount); } /** * Check for problems with new/deleted folders in the publish selection.<p> * * @param resourceIds the ids of the resources selected for publishing * @return true if there are problems with nested */ public boolean checkForProblems(Set<CmsUUID> resourceIds) { List<CmsPublishResource> pubResources = new ArrayList<CmsPublishResource>(); Set<CmsUUID> publishIds = getResourcesToPublish(); for (CmsUUID publishId : publishIds) { pubResources.add(m_model.getPublishResources().get(publishId)); } for (CmsPublishResource pubResource : pubResources) { String parentPath = CmsResource.getParentFolder(pubResource.getName()); CmsPublishResource parent = m_model.getPublishResourcesByPath().get(parentPath); if (parent != null) { boolean parentIsNew = parent.getState().isNew(); boolean parentIsDeleted = parent.getState().isDeleted(); if (parentIsNew || parentIsDeleted) { if (!resourceIds.contains(parent.getId())) { String title = Messages.get().key(Messages.ERR_CANT_PUBLISH_RESOURCE_TITLE_0); String message = null; if (parentIsNew) { message = Messages.get().key( Messages.ERR_PUBLISH_CANT_PUBLISH_NEW_RESOURCE_2, pubResource.getName(), parent.getName()); } if (parentIsDeleted) { message = Messages.get().key( Messages.ERR_PUBLISH_CANT_PUBLISH_DELETED_RESOURCE_2, pubResource.getName(), parent.getName()); } CmsAlertDialog alert = new CmsAlertDialog(title, message); alert.center(); return true; } } } } return false; } /** * Returns the buttons of this panel which should be shown as the buttons of the publish dialog.<p> * * @return a list of buttons */ public List<CmsPushButton> getButtons() { List<CmsPushButton> result = new ArrayList<CmsPushButton>(); result.add(m_cancelButton); result.add(m_publishButton); return result; } /** * Returns the current publish options.<p> * * @return a publish options bean */ public CmsPublishOptions getPublishOptions() { return m_publishOptions; } /** * Returns the ids of the resources which should be published.<p> * * @return a set of id strings */ public Set<CmsUUID> getResourcesToPublish() { return new HashSet<CmsUUID>(m_model.getPublishIds()); } /** * Returns the set of ids of resources which have been selected for removal.<p> * * @return a set of id strings */ public Set<CmsUUID> getResourcesToRemove() { return new HashSet<CmsUUID>(m_model.getRemoveIds()); } /** * Gets the global map of selection controllers.<p> * * @return the map of selection controller */ public Map<CmsUUID, CmsPublishItemSelectionController> getSelectionControllers() { return m_selectionControllers; } /** * @see org.opencms.ade.publish.client.I_CmsPublishSelectionChangeHandler#onChangePublishSelection() */ public void onChangePublishSelection() { m_publishButton.setEnabled(shouldEnablePublishButton()); } /** * Sets the publish groups used by this widget.<p> * * @param groups the new publish groups * @param newData true if the groups are new data which has been loaded */ public void setGroups(List<CmsPublishGroup> groups, boolean newData) { m_model = new CmsPublishDataModel(groups, this); m_resourceCountLabel.setHTML(formatResourceCount(m_model.getPublishResources().size())); m_currentGroupIndex = 0; m_currentGroupPanel = null; m_problemsPanel.clear(); if (newData) { m_showProblemsOnly = false; m_checkboxProblems.setChecked(false); m_checkboxProblems.setVisible(false); m_problemsPanel.setVisible(false); } m_groupPanels.clear(); m_groupPanelContainer.clear(); m_publishButton.setEnabled(false); int numGroups = groups.size(); setResourcesVisible(numGroups > 0); if (numGroups == 0) { return; } m_publishButton.setEnabled(true); addMoreListItems(); showProblemCount(m_model.countProblems()); onChangePublishSelection(); } /** * Returns true if the publish button should be enabled.<p> * * @return true if the publish button should be enabled */ public boolean shouldEnablePublishButton() { boolean enablePublishButton = (getResourcesToRemove().size() != 0) || (getResourcesToPublish().size() != 0); return enablePublishButton; } /** * @see org.opencms.ade.publish.client.I_CmsPublishItemStatusUpdateHandler#update(org.opencms.util.CmsUUID, org.opencms.ade.publish.client.CmsPublishItemStatus) */ public void update(CmsUUID id, CmsPublishItemStatus status) { CmsPublishItemSelectionController selectionController = m_selectionControllers.get(id); if (selectionController != null) { selectionController.update(status); } } /** * Adds more groups if there are still undisplayed groups left.<p> */ protected void addMoreGroups() { //TODO: adding more groups } /** * Adds more publish list items to the panel.<p> */ protected void addMoreListItems() { MoreItemsCommand cmd = new MoreItemsCommand(MIN_BATCH_SIZE); // we use a repeating command instead of a loop because a loop locks up the browser for too long in IE7. Scheduler.get().scheduleFixedDelay(cmd, 0); } /** * Tries to add a new publish list item to the panel, and returns false if there aren't any items left.<p> * * @return true if an item could be added, false if no items are left */ protected boolean addNextItem() { // this method is so complicated because to add the next item, // you may need to skip to another group and create the corresponding widget if (m_model.isEmpty()) { return false; } // now we know there is at least one group if (m_currentGroupPanel == null) { // this case happens if the method is called for the first time m_currentGroupPanel = addGroupPanel(m_model.getGroups().get(0), 0); } while (true) { if (m_currentGroupPanel.hasMoreItems()) { // found next item in the current group boolean found = m_currentGroupPanel.addNextItem(); if (found) { return true; } } else if (m_currentGroupIndex < m_model.getGroups().size() - 1) { // didn't find item in the current group, so skip to next group if available // and create the group widget m_currentGroupIndex += 1; m_currentGroupPanel = addGroupPanel(m_model.getGroups().get(m_currentGroupIndex), m_currentGroupIndex); } else { // all groups exhausted return false; } } } /** * The method to call when the items have finished being loaded into the list.<p> */ protected void finishLoading() { m_loading = false; } /** * The event handler for the Cancel button.<p> * * @param e the event */ @UiHandler("m_cancelButton") protected void onClickCancel(ClickEvent e) { m_publishDialog.onCancel(); } /** * The event handler for the publish button.<p> * * @param e the event */ @UiHandler("m_publishButton") protected void onClickPublish(ClickEvent e) { if (!checkForProblems(getResourcesToPublish())) { m_publishDialog.onRequestPublish(); } } /** * Enables or disables the "only show resources with problems" mode.<p> * * @param enabled if true, enable the mode, else disable it */ protected void setProblemMode(boolean enabled) { m_showProblemsOnly = enabled; setGroups(m_model.getGroups(), false); } /** * The method which should be called when new items start being inserted into the list.<p> */ protected void startLoading() { m_loading = true; } /** * Adds a new group panel.<p> * * @param group the publish group for which a panel should be added * @param currentIndex the index of the publish group * * @return the publish group panel which has been added */ private CmsPublishGroupPanel addGroupPanel(CmsPublishGroup group, int currentIndex) { String header = group.getName(); CmsPublishGroupPanel groupPanel = new CmsPublishGroupPanel( header, currentIndex, this, m_model, m_selectionControllers, m_showProblemsOnly); m_groupPanels.add(groupPanel); m_groupPanelContainer.add(groupPanel); return groupPanel; } /** * Adds the scroll handler to the scroll panel which makes more groups visible when the user * scrolls to the bottom.<p> */ private void addScrollHandler() { m_scrollPanel.addScrollHandler(new CmsScrollToBottomHandler(new Runnable() { /** * @see java.lang.Runnable#run() */ public void run() { if (!m_loading) { startLoading(); addMoreListItems(); } } }, SCROLL_THRESHOLD)); } /** * Shows either the scroll panel or the "no resources" label and hides the other one.<p> * * @param visible if true, set the scroll panel to visible, otherwise the "no resources" label */ private void setResourcesVisible(boolean visible) { m_noResources.setVisible(!visible); m_scrollPanel.setVisible(visible); m_topBar.getElement().getStyle().setVisibility(visible ? Visibility.VISIBLE : Visibility.HIDDEN); m_checkboxSiblings.setVisible(visible); m_checkboxRelated.setVisible(visible); } /** * Shows the problem count in the panel.<p> * * @param numProblems the number of resources with publish problems */ private void showProblemCount(int numProblems) { m_problemsPanel.clear(); if (numProblems > 0) { HorizontalPanel errorBox = new HorizontalPanel(); Label warnIcon = new Label(); warnIcon.addStyleName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.gwtImages().style().warningIcon()); String message = Messages.get().key(Messages.GUI_PUBLISH_DIALOG_PROBLEM_1, "" + numProblems); errorBox.add(warnIcon); errorBox.add(new Label(message)); m_problemsPanel.add(errorBox); m_problemsPanel.setVisible(true); } m_checkboxProblems.setVisible(numProblems > 0); } }