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}