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.setHTML(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        itemWidget.setUnselectable();
244        return itemWidget;
245    }
246
247    /**
248     * Fills a slot for a button in a publish list item widget.<p>
249     *
250     * @param listItemWidget the list item widget
251     * @param index the slot index
252     * @param widget the widget which should be displayed in the slot
253     * @param slotMapping array mapping logical slot ids to button indexes
254     */
255    private static void fillButtonSlot(CmsListItemWidget listItemWidget, int index, Widget widget, int[] slotMapping) {
256
257        int realIndex = slotMapping[index];
258        if (realIndex >= 0) {
259            SimplePanel panel = (SimplePanel)listItemWidget.getButton(slotMapping[index]);
260            panel.clear();
261            panel.add(widget);
262        }
263    }
264
265    /**
266     * Adds the list item for the next publish resource and returns  true on success, while
267     * also incrementing the internal item index.<p>
268     *
269     * @return true if an item was added
270     */
271    public boolean addNextItem() {
272
273        if (m_itemIndex >= m_publishResources.size()) {
274            return false;
275        }
276        CmsPublishResource res = m_publishResources.get(m_itemIndex);
277        m_itemIndex += 1;
278        if (m_showProblemsOnly && (!CmsPublishDataModel.hasProblems(res))) {
279            return false;
280        } else {
281            addItem(res);
282            return true;
283        }
284    }
285
286    /**
287     * Returns true if there are more potential items to add.<p>
288     *
289     * @return true if there are possibly more items
290     */
291    public boolean hasMoreItems() {
292
293        return m_itemIndex < m_publishResources.size();
294    }
295
296    /**
297     * Hides the tri-state select box for the group.<p>
298     */
299    public void hideGroupSelectCheckBox() {
300
301        m_selectGroup.getElement().getStyle().setVisibility(Visibility.HIDDEN);
302    }
303
304    /**
305     * Updates the check box state for this group.<p>
306     *
307     * @param value the state to use for updating the check box
308     */
309    public void updateCheckboxState(CmsPublishItemStateSummary value) {
310
311        CheckBoxUpdate update = CmsPublishSelectPanel.updateCheckbox(value);
312        m_selectGroup.setTitle(update.getAction());
313        m_selectGroup.setState(update.getState(), false);
314    }
315
316    /**
317     * Returns true if the corresponding group has no  resources with problems.<p>
318     *
319     * @return true if the group for this panel has no resources with problems
320     */
321    protected boolean hasNoProblemResources() {
322
323        return 0 == m_model.countResourcesInGroup(
324            new CmsPublishDataModel.HasProblems(),
325            m_model.getGroups().get(m_groupIndex).getResources());
326    }
327
328    /**
329     * Returns true if the corresponding group has only resources with problems.<p>
330     *
331     * @return true if the group for this panel has only resources with problems.
332     */
333    protected boolean hasOnlyProblemResources() {
334
335        return m_model.getGroups().get(m_groupIndex).getResources().size() == m_model.countResourcesInGroup(
336            new CmsPublishDataModel.HasProblems(),
337            m_model.getGroups().get(m_groupIndex).getResources());
338    }
339
340    /**
341     * Adds a resource bean to this group.<p>
342     *
343     * @param resourceBean the resource bean which should be added
344     */
345    private void addItem(CmsPublishResource resourceBean) {
346
347        CmsTreeItem row = buildItem(resourceBean, m_model.getStatus(resourceBean.getId()), false);
348        m_panel.add(row);
349
350        for (CmsPublishResource related : resourceBean.getRelated()) {
351            row.addChild(buildItem(related, m_model.getStatus(related.getId()), true));
352        }
353    }
354
355    /**
356     * Creates a widget from resource bean data.<p>
357     *
358     * @param resourceBean the resource bean for which a widget should be constructed
359     * @param status the publish item status
360     * @param isSubItem true if this is not a top-level publish item
361     *
362     * @return a widget representing the resource bean
363     */
364    private CmsTreeItem buildItem(
365        final CmsPublishResource resourceBean,
366        CmsPublishItemStatus status,
367        boolean isSubItem) {
368
369        CmsListItemWidget itemWidget = createListItemWidget(resourceBean, DEFAULT_SLOT_MAPPING);
370        if ((m_editorHandler != null) && resourceBean.getPermissionInfo().hasWritePermission()) {
371            CmsPushButton editButton = new CmsPushButton();
372            editButton.setImageClass(I_CmsButton.PEN_SMALL);
373            editButton.setButtonStyle(ButtonStyle.FONT_ICON, null);
374            editButton.setTitle(
375                org.opencms.gwt.client.Messages.get().key(org.opencms.gwt.client.Messages.GUI_BUTTON_ELEMENT_EDIT_0));
376            editButton.addClickHandler(new ClickHandler() {
377
378                public void onClick(ClickEvent event) {
379
380                    CmsPushButton button = (CmsPushButton)event.getSource();
381                    button.clearHoverState();
382                    CmsEditableData editableData = new CmsEditableData();
383                    editableData.setStructureId(resourceBean.getId());
384                    CmsContentEditorDialog.get().openEditDialog(
385                        editableData,
386                        false,
387                        null,
388                        new DialogOptions(),
389                        m_editorHandler);
390                }
391            });
392            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(resourceBean.getPermissionInfo().getNoEditReason())) {
393                editButton.disable(resourceBean.getPermissionInfo().getNoEditReason());
394            }
395            fillButtonSlot(itemWidget, SLOT_EDIT, editButton, DEFAULT_SLOT_MAPPING);
396        }
397        if (resourceBean.getPermissionInfo().hasViewPermission()) {
398            CmsContextMenuButton button = new CmsContextMenuButton(
399                resourceBean.getId(),
400                m_contextMenuHandler,
401                AdeContext.publish);
402            fillButtonSlot(itemWidget, SLOT_MENU, button, DEFAULT_SLOT_MAPPING);
403        }
404        resourceBean.getId();
405        final CmsStyleVariable styleVar = new CmsStyleVariable(itemWidget);
406        styleVar.setValue(CSS.itemToKeep());
407
408        final CmsCheckBox checkbox = new CmsCheckBox();
409        CmsTreeItem row;
410        row = new CmsTreeItem(true, checkbox, itemWidget);
411        if (isSubItem) {
412            checkbox.getElement().getStyle().setVisibility(Visibility.HIDDEN);
413        }
414
415        row.setOpen(false);
416        row.addStyleName(CSS.publishRow());
417
418        // we do not need most of the interactive elements for the sub-items
419        if (!isSubItem) {
420            ClickHandler checkboxHandler = new ClickHandler() {
421
422                /**
423                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
424                 */
425                public void onClick(ClickEvent event) {
426
427                    boolean checked = checkbox.isChecked();
428                    m_model.signal(checked ? Signal.publish : Signal.unpublish, resourceBean.getId());
429                }
430            };
431            checkbox.addClickHandler(checkboxHandler);
432
433            final boolean hasProblem = CmsPublishDataModel.hasProblems(resourceBean);
434            if (hasProblem) {
435                // can't select resource with problems
436                checkbox.setChecked(false);
437                checkbox.setEnabled(false);
438            }
439
440            final CmsCheckBox remover = new CmsCheckBox();
441            final CmsPublishItemSelectionController controller = new CmsPublishItemSelectionController(
442                resourceBean.getId(),
443                checkbox,
444                remover,
445                styleVar,
446                hasProblem);
447            m_controllersById.put(resourceBean.getId(), controller);
448
449            remover.setTitle(Messages.get().key(Messages.GUI_PUBLISH_REMOVE_BUTTON_0));
450            remover.addClickHandler(new ClickHandler() {
451
452                /**
453                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
454                 */
455                public void onClick(ClickEvent e) {
456
457                    boolean remove = remover.isChecked();
458                    m_model.signal(remove ? Signal.remove : Signal.unremove, resourceBean.getId());
459                }
460            });
461            if (resourceBean.isRemovable()) {
462                fillButtonSlot(itemWidget, SLOT_REMOVE, remover, DEFAULT_SLOT_MAPPING);
463            }
464            controller.update(status);
465        }
466        return row;
467    }
468
469    /**
470     * Initializes the "select all/none" buttons, adds them to the group header and
471     * attaches event handlers to them.<p>
472     */
473    private void initSelectButtons() {
474
475        m_selectGroup = new CmsTriStateCheckBox("");
476        m_selectGroup.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().inlineBlock());
477        m_selectGroup.setNextStateAfterIntermediateState(State.on);
478        m_selectGroup.addValueChangeHandler(new ValueChangeHandler<CmsTriStateCheckBox.State>() {
479
480            public void onValueChange(ValueChangeEvent<State> event) {
481
482                State state = event.getValue();
483                if (state == State.on) {
484                    m_model.signalGroup(Signal.publish, m_groupIndex);
485                } else if (state == State.off) {
486                    m_model.signalGroup(Signal.unpublish, m_groupIndex);
487                }
488            }
489
490        });
491        m_header.add(m_selectGroup);
492    }
493}