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.shared.CmsProjectBean;
032import org.opencms.ade.publish.shared.CmsPublishGroup;
033import org.opencms.ade.publish.shared.CmsPublishGroupList;
034import org.opencms.ade.publish.shared.CmsPublishOptions;
035import org.opencms.ade.publish.shared.CmsPublishResource;
036import org.opencms.ade.publish.shared.CmsWorkflow;
037import org.opencms.ade.publish.shared.CmsWorkflowAction;
038import org.opencms.file.CmsResource;
039import org.opencms.gwt.client.ui.CmsAlertDialog;
040import org.opencms.gwt.client.ui.CmsPushButton;
041import org.opencms.gwt.client.ui.CmsScrollPanel;
042import org.opencms.gwt.client.ui.FontOpenCms;
043import org.opencms.gwt.client.ui.contenteditor.I_CmsContentEditorHandler;
044import org.opencms.gwt.client.ui.contextmenu.CmsContextMenuHandler;
045import org.opencms.gwt.client.ui.css.I_CmsConstantsBundle;
046import org.opencms.gwt.client.ui.input.CmsCheckBox;
047import org.opencms.gwt.client.ui.input.CmsSelectBox;
048import org.opencms.gwt.client.ui.input.CmsTriStateCheckBox;
049import org.opencms.gwt.client.ui.input.CmsTriStateCheckBox.State;
050import org.opencms.gwt.client.util.CmsDomUtil;
051import org.opencms.gwt.client.util.CmsMessages;
052import org.opencms.gwt.client.util.CmsScrollToBottomHandler;
053import org.opencms.util.CmsUUID;
054
055import java.util.ArrayList;
056import java.util.Collections;
057import java.util.HashSet;
058import java.util.LinkedHashMap;
059import java.util.List;
060import java.util.Map;
061import java.util.Set;
062
063import com.google.common.collect.Maps;
064import com.google.gwt.core.client.GWT;
065import com.google.gwt.core.client.Scheduler;
066import com.google.gwt.core.client.Scheduler.RepeatingCommand;
067import com.google.gwt.dom.client.Style;
068import com.google.gwt.dom.client.Style.Visibility;
069import com.google.gwt.event.dom.client.ClickEvent;
070import com.google.gwt.event.dom.client.ClickHandler;
071import com.google.gwt.event.logical.shared.ValueChangeEvent;
072import com.google.gwt.event.logical.shared.ValueChangeHandler;
073import com.google.gwt.uibinder.client.UiBinder;
074import com.google.gwt.uibinder.client.UiField;
075import com.google.gwt.uibinder.client.UiHandler;
076import com.google.gwt.user.client.ui.Composite;
077import com.google.gwt.user.client.ui.FlowPanel;
078import com.google.gwt.user.client.ui.HorizontalPanel;
079import com.google.gwt.user.client.ui.InlineLabel;
080import com.google.gwt.user.client.ui.Label;
081import com.google.gwt.user.client.ui.Panel;
082import com.google.gwt.user.client.ui.Widget;
083
084/**
085 * This is the main widget of the publish dialog.<p>
086 *
087 * It allows the user to choose which resources from the publish list should be published
088 * and/or removed from the publish list.
089 *
090 * @since 8.0.0
091 */
092public class CmsPublishSelectPanel extends Composite
093implements I_CmsPublishSelectionChangeHandler, I_CmsPublishItemStatusUpdateHandler {
094
095    /**
096     * Data with which to update a check box.
097     */
098    public static class CheckBoxUpdate {
099
100        /** The action (used for the tooltip). */
101        private String m_action;
102
103        /** The new state. */
104        private CmsTriStateCheckBox.State m_state;
105
106        /** The new text. */
107        private String m_text;
108
109        /**
110         * Gets the action text.<P>
111         *
112         * @return the action text
113         */
114        public String getAction() {
115
116            return m_action;
117        }
118
119        /**
120         * Gets the new state.<p>
121         *
122         * @return the new state
123         */
124        public CmsTriStateCheckBox.State getState() {
125
126            return m_state;
127        }
128
129        /**
130         * Gets the new text.<p>
131         *
132         * @return the new text
133         */
134        public String getText() {
135
136            return m_text;
137        }
138
139        /**
140         * Sets the action text.<p>
141         *
142         * @param action the action text
143         */
144        public void setAction(String action) {
145
146            m_action = action;
147        }
148
149        /**
150         * Sets the new state.<p>
151         *
152         * @param state the new state
153         */
154        public void setState(CmsTriStateCheckBox.State state) {
155
156            m_state = state;
157        }
158
159        /**
160         * Sets the new text.<p>
161         *
162         * @param text the new text
163         */
164        public void setText(String text) {
165
166            m_text = text;
167        }
168
169    }
170
171    /** The UiBinder interface for this widget. */
172    protected interface I_CmsPublishSelectPanelUiBinder extends UiBinder<Widget, CmsPublishSelectPanel> {
173        // empty
174    }
175
176    /**
177     * Command for adding more list items to the list of publish items.<p>
178     */
179    protected class MoreItemsCommand implements RepeatingCommand {
180
181        /** The number of items left to add. */
182        private int m_numItems;
183
184        /**
185         * Creates a new instance.<p>
186         *
187         * @param numItems the maximal number of items to add
188         */
189        public MoreItemsCommand(int numItems) {
190
191            m_numItems = numItems;
192        }
193
194        /**
195         * @see com.google.gwt.core.client.Scheduler.RepeatingCommand#execute()
196         */
197        public boolean execute() {
198
199            if (m_numItems == 0) {
200                finishLoading();
201                return false;
202            }
203            boolean hasMore = addNextItem();
204            if (!hasMore) {
205                finishLoading();
206                return false;
207            }
208            m_numItems -= 1;
209            return true;
210        }
211
212    }
213
214    /** The CSS bundle used for this widget. */
215    private static final I_CmsPublishCss CSS = I_CmsPublishLayoutBundle.INSTANCE.publishCss();
216
217    /**
218     * When dynamically adding groups on scrolling, the number of groups should be calculated such that the total sum of resources
219     * in the groups is the smallest number greater or equal to this constant.<p>
220     */
221    private static final int MIN_BATCH_SIZE = 20;
222
223    /** The scroll threshold for the list of problem resources. */
224    private static final int SCROLL_THRESHOLD = 200;
225
226    /** The UiBinder instance used for this widget. */
227    private static final I_CmsPublishSelectPanelUiBinder UI_BINDER = GWT.create(I_CmsPublishSelectPanelUiBinder.class);
228
229    /** The button for escaping from the publish dialog. */
230    @UiField
231    protected CmsPushButton m_cancelButton;
232
233    /** Checkbox for including the contents of folders in the direct publish case. */
234    @UiField
235    protected CmsCheckBox m_checkboxAddContents;
236
237    /** The checkbox for the "show problems only" mode. */
238    @UiField
239    protected CmsCheckBox m_checkboxProblems;
240
241    /** The checkbox for including related resources. */
242    @UiField
243    protected CmsCheckBox m_checkboxRelated;
244
245    /** The checkbox for including sibling resources. */
246    @UiField
247    protected CmsCheckBox m_checkboxSiblings;
248
249    /** The panel containing the publish groups. */
250    @UiField
251    protected Panel m_groupPanelContainer;
252
253    /** Flag to indicate whether new items are currently being added to the list. */
254    protected boolean m_loading;
255
256    /** The data model for the publish dialog. */
257    protected CmsPublishDataModel m_model;
258
259    /** The label which is displayed when there are no resources to publish. */
260    @UiField
261    protected Label m_noResources;
262
263    /** The panel which shows a message telling the user the number of problems. */
264    @UiField
265    protected Panel m_problemsPanel;
266
267    /** The project select box. */
268    @UiField
269    protected CmsSelectBox m_projectSelector;
270
271    /** The publish dialog which contains this panel. */
272    protected CmsPublishDialog m_publishDialog;
273
274    /** The scroll panel containing the group panel. */
275    @UiField
276    protected CmsScrollPanel m_scrollPanel;
277
278    /** The global map of selection controllers for all groups. */
279    protected Map<CmsUUID, CmsPublishItemSelectionController> m_selectionControllers = Maps.newHashMap();
280
281    /** The label shown in front of the project selector. */
282    @UiField
283    protected InlineLabel m_selectorLabel;
284
285    /** The panel containing the project selector. */
286    @UiField
287    protected FlowPanel m_selectorPanel;
288
289    /** Label which is shown instead of the resource list when the resource list is too long. */
290    @UiField
291    protected Label m_tooManyResources;
292
293    /** The top button bar. */
294    @UiField
295    protected Panel m_topBar;
296
297    /** The workflow selector. */
298    @UiField
299    protected CmsSelectBox m_workflowSelector;
300
301    /** The workflow selector label. */
302    @UiField
303    protected InlineLabel m_workflowsLabel;
304
305    /** The action buttons. */
306    private List<CmsPushButton> m_actionButtons;
307
308    /** The available actions. */
309    private List<CmsWorkflowAction> m_actions;
310
311    /** The current group index used for scrolling. */
312    private int m_currentGroupIndex;
313
314    /** The current group panel. */
315    private CmsPublishGroupPanel m_currentGroupPanel;
316    /** The id of the virtual project used for 'direct publish'. */
317    private CmsUUID m_directPublishId;
318
319    /** Indicates whether a previously selected project has been found. */
320    private boolean m_foundOldProject;
321
322    /** The list of group panels for each publish list group. */
323    private List<CmsPublishGroupPanel> m_groupPanels = new ArrayList<CmsPublishGroupPanel>();
324
325    /** Flag indicating that the panel has been initialized. */
326    private boolean m_initialized;
327
328    /** Checkbox for selecting/deselecting all items. */
329    private CmsTriStateCheckBox m_selectAll;
330
331    /** Flag which indicates whether only resources with problems should be shown. */
332    private boolean m_showProblemsOnly;
333
334    /** Indicates whether the resources are being shown (which is not done if the resource list is too long). */
335    private boolean m_showResources = true;
336
337    /**
338     * Creates a new instance.<p>
339     *
340     * @param publishDialog the publish dialog to which this panel should belong
341     * @param projects a map of projects, where the keys are the project ids and the values are the names of the projects
342     * @param publishOptions the initial publish options
343     * @param workflows the available workflows
344     * @param selectedWorkflowId the selected workflow id
345     * @param scrollPanelHeight the available scroll panel height
346     */
347    public CmsPublishSelectPanel(
348        CmsPublishDialog publishDialog,
349        List<CmsProjectBean> projects,
350        CmsPublishOptions publishOptions,
351        Map<String, CmsWorkflow> workflows,
352        String selectedWorkflowId,
353        int scrollPanelHeight) {
354
355        projects = new ArrayList<CmsProjectBean>(projects);
356        m_publishDialog = publishDialog;
357        m_actions = workflows.get(selectedWorkflowId).getActions();
358        m_actionButtons = new ArrayList<CmsPushButton>();
359        initWidget(UI_BINDER.createAndBindUi(this));
360        String enableAddContentsStr = null;
361        boolean enableAddContents = false;
362        boolean addContent = false;
363        try {
364            enableAddContentsStr = publishOptions.getParameters().get(CmsPublishOptions.PARAM_ENABLE_INCLUDE_CONTENTS);
365            enableAddContents = (null != enableAddContentsStr)
366                && !(CmsPublishOptions.INCLUDE_CONTENTS_CHECKBOX_DISABLE.equals(enableAddContentsStr));
367            addContent = Boolean.parseBoolean(
368                publishOptions.getParameters().get(CmsPublishOptions.PARAM_INCLUDE_CONTENTS));
369        } catch (Exception e) {
370            // ignore; enableAddContents remains the default value
371        }
372        m_checkboxAddContents.setVisible(enableAddContents);
373        m_checkboxAddContents.setChecked(addContent);
374        if (enableAddContents) {
375            m_directPublishId = publishOptions.getProjectId();
376        }
377        String addContentsText = Messages.get().key(Messages.GUI_CHECKBOX_ADD_CONTENT_0);
378        if (CmsPublishOptions.INCLUDE_CONTENTS_CHECKBOX_SINGULAR.equals(enableAddContentsStr)) {
379            addContentsText = Messages.get().key(Messages.GUI_CHECKBOX_ADD_CONTENT_SINGULAR_0);
380        }
381        m_checkboxAddContents.setText(addContentsText);
382        m_selectAll = new CmsTriStateCheckBox("");
383        m_selectAll.getElement().getStyle().setMarginLeft(4, Style.Unit.PX);
384        m_selectAll.setNextStateAfterIntermediateState(State.on);
385        m_selectAll.addValueChangeHandler(new ValueChangeHandler<CmsTriStateCheckBox.State>() {
386
387            public void onValueChange(ValueChangeEvent<State> event) {
388
389                State state = event.getValue();
390                if (state == State.on) {
391                    m_model.signalAll(Signal.publish);
392                } else if (state == State.off) {
393                    m_model.signalAll(Signal.unpublish);
394                }
395            }
396        });
397        m_topBar.add(m_selectAll);
398
399        m_scrollPanel.getElement().getStyle().setPropertyPx(CmsDomUtil.Style.maxHeight.toString(), scrollPanelHeight);
400        m_checkboxProblems.setVisible(false);
401        CmsMessages messages = Messages.get();
402        LinkedHashMap<String, String> workflowSelectorItems = new LinkedHashMap<String, String>();
403        for (CmsWorkflow workflow : workflows.values()) {
404            workflowSelectorItems.put(workflow.getId(), workflow.getNiceName());
405        }
406        LinkedHashMap<String, String> projectSelectItems = new LinkedHashMap<String, String>();
407        Collections.<CmsProjectBean> sort(projects);
408
409        m_foundOldProject = false;
410        boolean selectedWorkflowProject = false;
411        for (CmsProjectBean project : projects) {
412            if (project.isWorkflowProject()) {
413                workflowSelectorItems.put(project.getId().toString(), project.getName());
414                if (project.getId().equals(publishOptions.getProjectId())) {
415                    selectedWorkflowProject = true;
416                }
417            } else {
418                projectSelectItems.put(project.getId().toString(), project.getName());
419                // look if the project id from the last publish list is among the available projects.
420                // (this might not be the case if the project has been deleted in the meantime.)
421                if (project.getId().equals(publishOptions.getProjectId())) {
422                    m_foundOldProject = true;
423                }
424            }
425        }
426
427        m_workflowSelector.setItems(workflowSelectorItems);
428
429        m_workflowSelector.addStyleName(CSS.selector());
430        if (!(workflows.size() > 1)) {
431            m_workflowSelector.setEnabled(false);
432        }
433
434        m_workflowsLabel.setText(messages.key(Messages.GUI_PUBLISH_WORKFLOW_SELECT_0));
435
436        m_projectSelector.setItems(projectSelectItems);
437        m_projectSelector.addStyleName(CSS.selector());
438        if (!(null == publishOptions.getProjectId()) && m_foundOldProject) {
439            m_projectSelector.setFormValueAsString(publishOptions.getProjectId().toString());
440        }
441
442        m_checkboxRelated.setChecked(publishOptions.isIncludeRelated());
443        m_checkboxSiblings.setChecked(publishOptions.isIncludeSiblings());
444        if (selectedWorkflowProject) {
445            m_projectSelector.setEnabled(false);
446            m_workflowSelector.setFormValueAsString(publishOptions.getProjectId().toString());
447        } else {
448            m_workflowSelector.setFormValueAsString(selectedWorkflowId);
449        }
450        m_cancelButton.setText(messages.key(Messages.GUI_PUBLISH_DIALOG_CANCEL_BUTTON_0));
451        m_cancelButton.setUseMinWidth(true);
452        m_noResources.setText(messages.key(Messages.GUI_PUBLISH_DIALOG_NO_RES_0));
453        m_checkboxSiblings.setText(messages.key(Messages.GUI_PUBLISH_CHECKBOXES_SIBLINGS_0));
454        m_checkboxRelated.setText(messages.key(Messages.GUI_PUBLISH_CHECKBOXES_REL_RES_0));
455        m_checkboxProblems.setText(messages.key(Messages.GUI_PUBLISH_CHECKBOXES_PROBLEMS_0));
456        m_selectorLabel.setText(messages.key(Messages.GUI_PUBLISH_TOP_PANEL_RIGHT_LABEL_0));
457        addScrollHandler();
458        m_initialized = true;
459    }
460
461    /**
462     * Formats a number of publish resources in a more user-friendly form.<p>
463     *
464     * @param resourceCount a number of resources
465     *
466     * @return the formatted number of resources
467     */
468    public static String formatResourceCount(int resourceCount) {
469
470        return Messages.get().key(Messages.GUI_RESOURCE_COUNT_1, "" + resourceCount);
471    }
472
473    /**
474     * Updates the state of a check box used for selecting/deselecting items.<p>
475     *
476     * @param states the selection states of the items for the check box
477     *
478     * @return the data needed to update the check box
479     */
480    public static CheckBoxUpdate updateCheckbox(CmsPublishItemStateSummary states) {
481
482        CheckBoxUpdate result = new CheckBoxUpdate();
483        boolean hasPublish = states.getPublishCount() > 0;
484        boolean hasNormal = states.getNormalCount() > 0;
485        String actionSelectAll = Messages.get().key(Messages.GUI_CHECKBOX_SELECT_ALL_0);
486        String textDeselectAll = Messages.get().key(Messages.GUI_CHECKBOX_DESELECT_ALL_0);
487        String some = Messages.get().key(
488            Messages.GUI_CHECKBOX_SOME_2,
489            "" + states.getPublishCount(),
490            "" + (states.getPublishCount() + states.getNormalCount()));
491        String all = Messages.get().key(Messages.GUI_CHECKBOX_ALL_0);
492        String none = Messages.get().key(Messages.GUI_CHECKBOX_NONE_0);
493
494        if (hasNormal && hasPublish) {
495            result.setAction(actionSelectAll);
496            result.setText(some);
497            result.setState(CmsTriStateCheckBox.State.middle);
498        } else if (hasNormal) {
499            result.setAction(actionSelectAll);
500            result.setText(none);
501            result.setState(CmsTriStateCheckBox.State.off);
502        } else if (hasPublish) {
503            result.setAction(textDeselectAll);
504            result.setText(all);
505            result.setState(CmsTriStateCheckBox.State.on);
506        } else {
507            result.setText(none);
508            result.setAction(actionSelectAll);
509            result.setState(CmsTriStateCheckBox.State.off);
510        }
511        return result;
512    }
513
514    /**
515     * Check for problems with new/deleted folders in the publish selection.<p>
516     *
517     * @param resourceIds the ids of the resources selected for publishing
518     * @return true if there are problems with nested
519     */
520    public boolean checkForProblems(Set<CmsUUID> resourceIds) {
521
522        List<CmsPublishResource> pubResources = new ArrayList<CmsPublishResource>();
523        Set<CmsUUID> publishIds = getResourcesToPublish();
524        for (CmsUUID publishId : publishIds) {
525            pubResources.add(m_model.getPublishResources().get(publishId));
526        }
527        for (CmsPublishResource pubResource : pubResources) {
528            String parentPath = CmsResource.getParentFolder(pubResource.getName());
529            CmsPublishResource parent = m_model.getPublishResourcesByPath().get(parentPath);
530            if (parent != null) {
531                boolean parentIsNew = parent.getResourceState().isNew();
532                boolean parentIsDeleted = parent.getResourceState().isDeleted();
533                if (parentIsNew || parentIsDeleted) {
534                    if (!resourceIds.contains(parent.getId())) {
535                        String title = Messages.get().key(Messages.ERR_CANT_PUBLISH_RESOURCE_TITLE_0);
536                        String message = null;
537                        if (parentIsNew) {
538                            message = Messages.get().key(
539                                Messages.ERR_PUBLISH_CANT_PUBLISH_NEW_RESOURCE_2,
540                                pubResource.getName(),
541                                parent.getName());
542                        }
543                        if (parentIsDeleted) {
544                            message = Messages.get().key(
545                                Messages.ERR_PUBLISH_CANT_PUBLISH_DELETED_RESOURCE_2,
546                                pubResource.getName(),
547                                parent.getName());
548                        }
549                        CmsAlertDialog alert = new CmsAlertDialog(title, message);
550                        alert.center();
551                        return true;
552                    }
553                }
554            }
555        }
556        return false;
557    }
558
559    /**
560     * Returns the buttons of this panel which should be shown as the buttons of the publish dialog.<p>
561     *
562     * @return a list of buttons
563     */
564    public List<CmsPushButton> getButtons() {
565
566        List<CmsPushButton> result = new ArrayList<CmsPushButton>();
567        result.add(m_cancelButton);
568        m_actionButtons.clear();
569        boolean enable = shouldEnablePublishButton();
570        if (m_actions != null) {
571            for (final CmsWorkflowAction action : m_actions) {
572                CmsPushButton actionButton = new CmsPushButton();
573                actionButton.setText(action.getLabel());
574                actionButton.setUseMinWidth(true);
575                actionButton.addClickHandler(new ClickHandler() {
576
577                    public void onClick(ClickEvent event) {
578
579                        executeAction(action);
580                    }
581                });
582                actionButton.setEnabled(enable);
583                m_actionButtons.add(actionButton);
584            }
585        }
586        result.addAll(m_actionButtons);
587        return result;
588    }
589
590    /**
591     * Returns the ids of the resources which should be published.<p>
592     *
593     * @return a set of id strings
594     */
595    public Set<CmsUUID> getResourcesToPublish() {
596
597        return new HashSet<CmsUUID>(m_model.getPublishIds());
598    }
599
600    /**
601     * Returns the set of ids of resources which have been selected for removal.<p>
602     *
603     * @return a set of id strings
604     */
605    public Set<CmsUUID> getResourcesToRemove() {
606
607        Set<CmsUUID> result = new HashSet<CmsUUID>(m_model.getRemoveIds());
608        result.addAll(m_model.getIdsOfAlreadyPublishedResources());
609        return result;
610    }
611
612    /**
613     * Gets the global map of selection controllers.<p>
614     *
615     * @return the map of selection controller
616     */
617    public Map<CmsUUID, CmsPublishItemSelectionController> getSelectionControllers() {
618
619        return m_selectionControllers;
620    }
621
622    /**
623     * Returns if the resource list is being shown.<p>
624     *
625     * The resource list is now shown if it is too long.<p>
626     *
627     * @return true if the resource list is being shown
628     */
629    public boolean isShowResources() {
630
631        return m_showResources;
632    }
633
634    /**
635     * @see org.opencms.ade.publish.client.I_CmsPublishSelectionChangeHandler#onChangePublishSelection()
636     */
637    public void onChangePublishSelection() {
638
639        enableActions(shouldEnablePublishButton());
640        Map<Integer, CmsPublishItemStateSummary> states = m_model.computeGroupSelectionStates();
641        for (Map.Entry<Integer, CmsPublishItemStateSummary> entry : states.entrySet()) {
642            int key = entry.getKey().intValue();
643            if (key == -1) {
644                updateCheckboxState(entry.getValue());
645            } else {
646                if (key < m_groupPanels.size()) {
647                    m_groupPanels.get(key).updateCheckboxState(entry.getValue());
648                }
649            }
650        }
651    }
652
653    /**
654     * Sets the publish groups used by this widget.<p>
655     *
656     * @param groups the new publish groups
657     * @param newData true if the groups are new data which has been loaded
658     * @param defaultWorkflow if not null, selects the given workflow
659     */
660    public void setGroupList(CmsPublishGroupList groups, boolean newData, String defaultWorkflow) {
661
662        if (groups.getToken() == null) {
663            setGroups(groups.getGroups(), newData, defaultWorkflow);
664            setShowResources(true, "");
665        } else {
666            setShowResources(false, groups.getTooManyResourcesMessage());
667        }
668        boolean isDirectPublish = m_publishDialog.getPublishOptions().getProjectId().equals(m_directPublishId);
669        m_checkboxAddContents.setVisible(isDirectPublish);
670    }
671
672    /**
673     * Sets the mode to either show resources, or only show a "too many resources" message.<p>
674     * In the latter case, the check boxes for the siblings/related resources will be deactivated.<p>
675     *
676     * @param showResources true if the resource list should be shown, false if only the given message should be shown
677     * @param tooManyResourcesMessage the message to show if there are too many resources to display
678     */
679    public void setShowResources(boolean showResources, String tooManyResourcesMessage) {
680
681        m_showResources = showResources;
682        m_checkboxRelated.setEnabled(showResources);
683        m_checkboxRelated.setChecked(showResources && m_publishDialog.getPublishOptions().isIncludeRelated());
684        m_checkboxSiblings.setEnabled(showResources);
685        m_checkboxSiblings.setChecked(showResources && m_publishDialog.getPublishOptions().isIncludeSiblings());
686        m_groupPanelContainer.setVisible(showResources);
687        m_tooManyResources.setVisible(!showResources);
688        m_tooManyResources.setText(tooManyResourcesMessage);
689        m_selectAll.setVisible(showResources);
690        enableActions(true);
691        if (!showResources) {
692            m_checkboxProblems.setVisible(false);
693            m_noResources.setVisible(false);
694            m_scrollPanel.setVisible(false);
695        } else {
696            addMoreListItems();
697            showProblemCount(m_model.countProblems());
698            onChangePublishSelection();
699        }
700    }
701
702    /**
703     * Returns true if the publish button should be enabled.<p>
704     *
705     * @return true if the publish button should be enabled
706     */
707    public boolean shouldEnablePublishButton() {
708
709        boolean enablePublishButton = !m_showResources
710            || (getResourcesToRemove().size() != 0)
711            || (getResourcesToPublish().size() != 0);
712        return enablePublishButton;
713
714    }
715
716    /**
717     * @see org.opencms.ade.publish.client.I_CmsPublishItemStatusUpdateHandler#update(org.opencms.util.CmsUUID, org.opencms.ade.publish.client.CmsPublishItemStatus)
718     */
719    public void update(CmsUUID id, CmsPublishItemStatus status) {
720
721        CmsPublishItemSelectionController selectionController = m_selectionControllers.get(id);
722        if (selectionController != null) {
723            selectionController.update(status);
724        }
725    }
726
727    /**
728     * Updates the dialog title.<p>
729     **/
730    public void updateDialogTitle() {
731
732        String title;
733        if ((m_model != null) && (m_model.getGroups().size() > 1)) {
734            title = Messages.get().key(
735                Messages.GUI_PUBLISH_DIALOG_TITLE_3,
736                m_publishDialog.getSelectedWorkflow().getNiceName(),
737                String.valueOf(m_model.getGroups().size()),
738                String.valueOf(m_model.getPublishResources().size()));
739        } else {
740            title = m_publishDialog.getSelectedWorkflow().getNiceName();
741        }
742        m_publishDialog.setCaption(title);
743    }
744
745    /**
746     * Adds more publish list items to the panel.<p>
747     */
748    protected void addMoreListItems() {
749
750        MoreItemsCommand cmd = new MoreItemsCommand(MIN_BATCH_SIZE);
751        // we use a repeating command instead of a loop because a loop locks up the browser for too long in IE7.
752        Scheduler.get().scheduleFixedDelay(cmd, 0);
753    }
754
755    /**
756     * Tries to add a new publish list item to the panel, and returns false if there aren't any items left.<p>
757     *
758     * @return true if an item could be added, false if no items are left
759     */
760    protected boolean addNextItem() {
761
762        // this method is so complicated because to add the next item,
763        // you may need to skip to another group and create the corresponding widget
764
765        if (m_model.isEmpty()) {
766            return false;
767        }
768        // now we know there is at least one group
769        if (m_currentGroupPanel == null) {
770            // this case happens if the method is called for the first time
771            m_currentGroupPanel = addGroupPanel(m_model.getGroups().get(0), 0);
772        }
773        while (true) {
774            if (m_currentGroupPanel.hasMoreItems()) {
775                // found next item in the current group
776                boolean found = m_currentGroupPanel.addNextItem();
777                if (found) {
778                    m_scrollPanel.onResizeDescendant();
779                    return true;
780                }
781            } else if (m_currentGroupIndex < (m_model.getGroups().size() - 1)) {
782                // didn't find item in the current group, so skip to next group if available
783                // and create the group widget
784                m_currentGroupIndex += 1;
785                m_currentGroupPanel = addGroupPanel(m_model.getGroups().get(m_currentGroupIndex), m_currentGroupIndex);
786            } else {
787                // all groups exhausted
788                m_scrollPanel.onResizeDescendant();
789                return false;
790            }
791        }
792    }
793
794    /**
795     * Executes the given action.<p>
796     *
797     * @param action the action to execute on the selected resources
798     */
799    protected void executeAction(CmsWorkflowAction action) {
800
801        m_publishDialog.executeAction(action);
802    }
803
804    /**
805     * The method to call when the items have finished being loaded into the list.<p>
806     */
807    protected void finishLoading() {
808
809        m_loading = false;
810        onChangePublishSelection();
811    }
812
813    /**
814     * Event handler for the 'add contents' check box.<p>
815     *
816     * @param event the click event
817     */
818    @UiHandler("m_checkboxAddContents")
819    protected void onAddContentsClick(ClickEvent event) {
820
821        setAddContents(m_checkboxAddContents.isChecked());
822        m_publishDialog.updateResourceList();
823    }
824
825    /**
826     * The event handler for the Cancel button.<p>
827     *
828     * @param e the event
829     */
830    @UiHandler("m_cancelButton")
831    protected void onClickCancel(ClickEvent e) {
832
833        m_publishDialog.onCancel();
834    }
835
836    /**
837     * Handles the click event for problem resources check box.<p>
838     *
839     * @param event the click event
840     *
841     * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
842     */
843    @UiHandler("m_checkboxProblems")
844    protected void onProblemClick(ClickEvent event) {
845
846        setProblemMode(m_checkboxProblems.isChecked());
847    }
848
849    /**
850     * Handling the value change event for the project selector.<p>
851     *
852     * @param event the change event
853     *
854     * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(ValueChangeEvent)
855     */
856    @UiHandler("m_projectSelector")
857    protected void onProjectChange(ValueChangeEvent<String> event) {
858
859        if (m_initialized) {
860            m_publishDialog.setProjectId(new CmsUUID(event.getValue()));
861            m_publishDialog.setProjectChanged();
862            m_publishDialog.updateResourceList();
863        }
864    }
865
866    /**
867     * Handles the click event for related resources check box.<p>
868     *
869     * @param event the click event
870     *
871     * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
872     */
873    @UiHandler("m_checkboxRelated")
874    protected void onRelatedClick(ClickEvent event) {
875
876        if (!m_showResources) {
877            return;
878        }
879        m_publishDialog.setIncludeRelated(m_checkboxRelated.isChecked());
880        m_publishDialog.updateResourceList();
881    }
882
883    /**
884     * Handles the click event for sibling resources check box.<p>
885     *
886     * @param event the click event
887     *
888     * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
889     */
890    @UiHandler("m_checkboxSiblings")
891    protected void onSiblingClick(ClickEvent event) {
892
893        if (!m_showResources) {
894            return;
895        }
896        m_publishDialog.setIncludeSiblings(m_checkboxSiblings.isChecked());
897        m_publishDialog.updateResourceList();
898    }
899
900    /**
901     * Handling the value change event for the project selector.<p>
902     *
903     * @param event the change event
904     *
905     * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(ValueChangeEvent)
906     */
907    @UiHandler("m_workflowSelector")
908    protected void onWorkflowChange(ValueChangeEvent<String> event) {
909
910        if (m_initialized) {
911            m_publishDialog.setWorkflowId(event.getValue());
912            // check for workflow project
913            if (!m_publishDialog.getSelectedWorkflow().getId().equals(event.getValue())) {
914                m_publishDialog.setProjectId(new CmsUUID(event.getValue()));
915                m_projectSelector.setEnabled(false);
916            } else {
917                m_projectSelector.setEnabled(true);
918                m_publishDialog.setProjectId(new CmsUUID(m_projectSelector.getFormValueAsString()));
919            }
920            m_actions = m_publishDialog.getSelectedWorkflow().getActions();
921            m_publishDialog.updateResourceList();
922        }
923    }
924
925    /**
926     * Sets the publish groups.<p>
927     *
928     * @param groups the list of publish groups
929     * @param newData true if the data is new
930     */
931    protected void setGroups(List<CmsPublishGroup> groups, boolean newData) {
932
933        m_model = new CmsPublishDataModel(groups, this);
934        m_model.setSelectionChangeAction(new Runnable() {
935
936            public void run() {
937
938                onChangePublishSelection();
939            }
940        });
941        m_currentGroupIndex = 0;
942        m_currentGroupPanel = null;
943        m_problemsPanel.clear();
944        if (newData) {
945            m_showProblemsOnly = false;
946            m_checkboxProblems.setChecked(false);
947            m_checkboxProblems.setVisible(false);
948            m_problemsPanel.setVisible(false);
949        }
950        m_groupPanels.clear();
951        m_groupPanelContainer.clear();
952        m_scrollPanel.onResizeDescendant();
953        enableActions(false);
954
955        int numGroups = groups.size();
956        setResourcesVisible(numGroups > 0);
957
958        if (numGroups == 0) {
959            return;
960        }
961        enableActions(true);
962        addMoreListItems();
963        showProblemCount(m_model.countProblems());
964    }
965
966    /**
967     * Sets the publish groups.<p>
968     *
969     * @param groups the list of publish groups
970     * @param newData true if the data is new
971     * @param defaultWorkflow the default workflow id
972     */
973    protected void setGroups(List<CmsPublishGroup> groups, boolean newData, String defaultWorkflow) {
974
975        m_model = new CmsPublishDataModel(groups, this);
976        m_model.setSelectionChangeAction(new Runnable() {
977
978            public void run() {
979
980                onChangePublishSelection();
981            }
982        });
983        m_currentGroupIndex = 0;
984        m_currentGroupPanel = null;
985        m_problemsPanel.clear();
986        if (newData) {
987            m_showProblemsOnly = false;
988            m_checkboxProblems.setChecked(false);
989            m_checkboxProblems.setVisible(false);
990            m_problemsPanel.setVisible(false);
991        }
992        m_groupPanels.clear();
993        m_groupPanelContainer.clear();
994        m_scrollPanel.onResizeDescendant();
995        enableActions(false);
996
997        int numGroups = groups.size();
998        setResourcesVisible(numGroups > 0);
999
1000        if (numGroups == 0) {
1001            return;
1002        }
1003        enableActions(true);
1004        addMoreListItems();
1005        showProblemCount(m_model.countProblems());
1006
1007        if (defaultWorkflow != null) {
1008            m_workflowSelector.setFormValue(defaultWorkflow, false);
1009            m_publishDialog.setWorkflowId(defaultWorkflow);
1010            m_actions = m_publishDialog.getSelectedWorkflow().getActions();
1011            m_publishDialog.setPanel(CmsPublishDialog.PANEL_SELECT);
1012        }
1013    }
1014
1015    /**
1016     * Enables or disables the "only show resources with problems" mode.<p>
1017     *
1018     * @param enabled if true, enable the mode, else disable it
1019     */
1020    protected void setProblemMode(boolean enabled) {
1021
1022        m_showProblemsOnly = enabled;
1023        setGroups(m_model.getGroups(), false, null);
1024    }
1025
1026    /**
1027     * The method which should be called when new items start being inserted into the list.<p>
1028     */
1029    protected void startLoading() {
1030
1031        m_loading = true;
1032    }
1033
1034    /**
1035     * Gets the context menu handler for the publish items' context menus.<p>
1036     *
1037     * @return the context menu handler
1038     */
1039    CmsContextMenuHandler getContextMenuHandler() {
1040
1041        return m_publishDialog.getContextMenuHandler();
1042
1043    }
1044
1045    /**
1046     * Returns the content editor handler.<p>
1047     *
1048     * @return the content editor handler
1049     */
1050    I_CmsContentEditorHandler getEditorHandler() {
1051
1052        return m_publishDialog.getEditorHandler();
1053    }
1054
1055    /**
1056     * Adds a new group panel.<p>
1057     *
1058     * @param group the publish group for which a panel should be added
1059     * @param currentIndex the index of the publish group
1060     *
1061     * @return the publish group panel which has been added
1062     */
1063    private CmsPublishGroupPanel addGroupPanel(CmsPublishGroup group, int currentIndex) {
1064
1065        String header = group.getName();
1066        CmsPublishGroupPanel groupPanel = new CmsPublishGroupPanel(
1067            group,
1068            header,
1069            currentIndex,
1070            this,
1071            m_model,
1072            m_selectionControllers,
1073            getContextMenuHandler(),
1074            getEditorHandler(),
1075            m_showProblemsOnly);
1076        if (m_model.hasSingleGroup()) {
1077            groupPanel.hideGroupSelectCheckBox();
1078        }
1079        m_groupPanels.add(groupPanel);
1080        m_groupPanelContainer.add(groupPanel);
1081        return groupPanel;
1082    }
1083
1084    /**
1085     * Adds the scroll handler to the scroll panel which makes more groups visible when the user
1086     * scrolls to the bottom.<p>
1087     */
1088    private void addScrollHandler() {
1089
1090        m_scrollPanel.addScrollHandler(new CmsScrollToBottomHandler(new Runnable() {
1091
1092            /**
1093             * @see java.lang.Runnable#run()
1094             */
1095            public void run() {
1096
1097                if (!m_loading) {
1098                    startLoading();
1099                    addMoreListItems();
1100                }
1101            }
1102        }, SCROLL_THRESHOLD));
1103
1104    }
1105
1106    /**
1107     * Enables action buttons.<p>
1108     *
1109     * @param enable <code>true</code> to enable the action buttons
1110     */
1111    private void enableActions(boolean enable) {
1112
1113        for (CmsPushButton button : m_actionButtons) {
1114            button.setEnabled(enable);
1115        }
1116    }
1117
1118    /**
1119     * Enables / disables the 'add contents' option in the publish options object.<p>
1120     *
1121     * @param addContents true if folder contents should be added
1122     */
1123    private void setAddContents(boolean addContents) {
1124
1125        m_publishDialog.getPublishOptions().getParameters().put(
1126            CmsPublishOptions.PARAM_INCLUDE_CONTENTS,
1127            "" + addContents);
1128    }
1129
1130    /**
1131     * Shows either the scroll panel or the "no resources" label and hides the other one.<p>
1132     *
1133     * @param visible if true, set the scroll panel to visible, otherwise the "no resources" label
1134     */
1135    private void setResourcesVisible(boolean visible) {
1136
1137        m_noResources.setVisible(!visible);
1138        m_scrollPanel.setVisible(visible);
1139        m_topBar.getElement().getStyle().setVisibility(visible ? Visibility.VISIBLE : Visibility.HIDDEN);
1140        m_checkboxSiblings.setVisible(true);
1141        m_checkboxRelated.setVisible(true);
1142    }
1143
1144    /**
1145     * Shows the problem count in the panel.<p>
1146     *
1147     * @param numProblems the number of resources with publish problems
1148     */
1149    private void showProblemCount(int numProblems) {
1150
1151        m_problemsPanel.clear();
1152        if (numProblems > 0) {
1153            HorizontalPanel errorBox = new HorizontalPanel();
1154            Widget warnIcon = FontOpenCms.WARNING.getWidget(20, I_CmsConstantsBundle.INSTANCE.css().colorWarning());
1155            String message = Messages.get().key(Messages.GUI_PUBLISH_DIALOG_PROBLEM_1, "" + numProblems);
1156            errorBox.add(warnIcon);
1157            errorBox.add(new Label(message));
1158            m_problemsPanel.add(errorBox);
1159            m_problemsPanel.setVisible(true);
1160        }
1161        m_checkboxProblems.setVisible(numProblems > 0);
1162    }
1163
1164    /**
1165     * Updates the state of the check box for all items.<p>
1166     *
1167     * @param value the state to use to update the check box
1168     */
1169    private void updateCheckboxState(CmsPublishItemStateSummary value) {
1170
1171        CheckBoxUpdate update = updateCheckbox(value);
1172        m_selectAll.setText(update.getText());
1173        m_selectAll.setTitle(update.getAction());
1174        m_selectAll.setState(update.getState(), false);
1175
1176    }
1177}