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.gwt.client.ui;
029
030import org.opencms.db.CmsResourceState;
031import org.opencms.gwt.client.CmsCoreProvider;
032import org.opencms.gwt.client.Messages;
033import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
034import org.opencms.gwt.client.ui.I_CmsButton.Size;
035import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle;
036import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
037import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.I_CmsListItemWidgetCss;
038import org.opencms.gwt.client.ui.input.CmsLabel;
039import org.opencms.gwt.client.util.CmsDomUtil;
040import org.opencms.gwt.client.util.CmsResourceStateUtil;
041import org.opencms.gwt.client.util.CmsStyleVariable;
042import org.opencms.gwt.client.util.DOMParser;
043import org.opencms.gwt.shared.CmsAdditionalInfoBean;
044import org.opencms.gwt.shared.CmsGwtLog;
045import org.opencms.gwt.shared.CmsListInfoBean;
046import org.opencms.gwt.shared.CmsListInfoBean.LockIcon;
047import org.opencms.gwt.shared.CmsListInfoBean.StateIcon;
048import org.opencms.util.CmsStringUtil;
049
050import java.util.ArrayList;
051import java.util.Iterator;
052import java.util.List;
053
054import com.google.gwt.core.client.GWT;
055import com.google.gwt.dom.client.Style;
056import com.google.gwt.dom.client.Style.Cursor;
057import com.google.gwt.dom.client.Style.Unit;
058import com.google.gwt.event.dom.client.BlurEvent;
059import com.google.gwt.event.dom.client.BlurHandler;
060import com.google.gwt.event.dom.client.ClickEvent;
061import com.google.gwt.event.dom.client.ClickHandler;
062import com.google.gwt.event.dom.client.DoubleClickEvent;
063import com.google.gwt.event.dom.client.DoubleClickHandler;
064import com.google.gwt.event.dom.client.HasClickHandlers;
065import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
066import com.google.gwt.event.dom.client.HasMouseOutHandlers;
067import com.google.gwt.event.dom.client.HasMouseOverHandlers;
068import com.google.gwt.event.dom.client.KeyPressEvent;
069import com.google.gwt.event.dom.client.KeyPressHandler;
070import com.google.gwt.event.dom.client.MouseOutEvent;
071import com.google.gwt.event.dom.client.MouseOutHandler;
072import com.google.gwt.event.dom.client.MouseOverEvent;
073import com.google.gwt.event.dom.client.MouseOverHandler;
074import com.google.gwt.event.logical.shared.CloseEvent;
075import com.google.gwt.event.logical.shared.CloseHandler;
076import com.google.gwt.event.logical.shared.HasCloseHandlers;
077import com.google.gwt.event.logical.shared.HasOpenHandlers;
078import com.google.gwt.event.logical.shared.OpenEvent;
079import com.google.gwt.event.logical.shared.OpenHandler;
080import com.google.gwt.event.shared.HandlerRegistration;
081import com.google.gwt.uibinder.client.UiBinder;
082import com.google.gwt.uibinder.client.UiField;
083import com.google.gwt.user.client.ui.Composite;
084import com.google.gwt.user.client.ui.FlowPanel;
085import com.google.gwt.user.client.ui.HTML;
086import com.google.gwt.user.client.ui.Image;
087import com.google.gwt.user.client.ui.InlineLabel;
088import com.google.gwt.user.client.ui.SimplePanel;
089import com.google.gwt.user.client.ui.TextBox;
090import com.google.gwt.user.client.ui.Widget;
091
092import elemental2.dom.HTMLDocument;
093import jsinterop.base.Js;
094
095/**
096 * Provides a UI list item.<p>
097 *
098 * @since 8.0.0
099 */
100public class CmsListItemWidget extends Composite
101implements HasOpenHandlers<CmsListItemWidget>, HasCloseHandlers<CmsListItemWidget>, HasMouseOutHandlers,
102HasClickHandlers, HasDoubleClickHandlers, HasMouseOverHandlers, I_CmsTruncable {
103
104    /** Additional info item HTML. */
105    public static class AdditionalInfoItem extends Composite implements I_CmsTruncable {
106
107        /** The title element. */
108        private CmsLabel m_titleLabel;
109
110        /** The value element. */
111        private CmsLabel m_valueLabel;
112
113        /**
114         * Constructor.<p>
115         *
116         * @param additionalInfo the info to display
117         */
118        public AdditionalInfoItem(CmsAdditionalInfoBean additionalInfo) {
119
120            this(additionalInfo.getName(), additionalInfo.getValue(), additionalInfo.getStyle());
121        }
122
123        /**
124         * Constructor.<p>
125         *
126         * @param title info title
127         * @param value info value
128         * @param additionalStyle an additional class name
129         */
130        public AdditionalInfoItem(String title, String value, String additionalStyle) {
131
132            super();
133            FlowPanel panel = new FlowPanel();
134            initWidget(panel);
135
136            I_CmsListItemWidgetCss style = I_CmsLayoutBundle.INSTANCE.listItemWidgetCss();
137            panel.addStyleName(style.itemInfoRow());
138            // create title
139            m_titleLabel = new CmsLabel(CmsStringUtil.isEmptyOrWhitespaceOnly(title) ? "" : title + ":");
140            m_titleLabel.addStyleName(style.itemAdditionalTitle());
141            panel.add(m_titleLabel);
142            // create value
143            m_valueLabel = new CmsLabel();
144            if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
145                m_valueLabel.setHTML(CmsDomUtil.Entity.nbsp.html());
146            } else {
147                m_valueLabel.setText(value);
148            }
149            m_valueLabel.addStyleName(style.itemAdditionalValue());
150            if (additionalStyle != null) {
151                m_valueLabel.addStyleName(additionalStyle);
152            }
153            panel.add(m_valueLabel);
154        }
155
156        /**
157         * Returns the title element.<p>
158         *
159         * @return the title element
160         */
161        public CmsLabel getTitleLabel() {
162
163            return m_titleLabel;
164        }
165
166        /**
167         * Returns the value element.<p>
168         *
169         * @return the value element
170         */
171        public CmsLabel getValueLabel() {
172
173            return m_valueLabel;
174        }
175
176        /**
177         * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int)
178         */
179        public void truncate(String textMetricsPrefix, int widgetWidth) {
180
181            int titleWidth = widgetWidth / 4;
182            m_titleLabel.setWidth(titleWidth + "px");
183        }
184    }
185
186    /** Background color values. */
187    public enum Background {
188        /** Color blue. */
189        BLUE,
190        /** Default color. */
191        DEFAULT,
192        /** Color red. */
193        RED,
194        /** Color yellow. */
195        YELLOW
196    }
197
198    /**
199     * The interface for handling edits of the title field.<p>
200     */
201    public interface I_CmsTitleEditHandler {
202
203        /**
204         * This method is called when the user has finished editing the title field.<p>
205         *
206         * @param title the label containing the title
207         * @param box the
208         */
209        void handleEdit(CmsLabel title, TextBox box);
210    }
211
212    /**
213     * @see com.google.gwt.uibinder.client.UiBinder
214     */
215    protected interface I_CmsListItemWidgetUiBinder extends UiBinder<CmsHoverPanel, CmsListItemWidget> {
216        // GWT interface, nothing to do here
217    }
218
219    /** The CSS class to set the additional info open. */
220    protected static final String OPENCLASS = I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().open();
221
222    /** Text metrics key. */
223    private static final String TM_SUBTITLE = "Subtitle";
224
225    /** The ui-binder instance for this class. */
226    private static I_CmsListItemWidgetUiBinder uiBinder = GWT.create(I_CmsListItemWidgetUiBinder.class);
227
228    /** DIV for additional item info. */
229    @UiField
230    protected FlowPanel m_additionalInfo;
231
232    /** Panel to hold buttons.*/
233    @UiField
234    protected FlowPanel m_buttonPanel;
235
236    /** Panel to hold the content.*/
237    @UiField
238    protected FlowPanel m_contentPanel;
239
240    /** A list of click handlers for the main icon. */
241    protected List<ClickHandler> m_iconClickHandlers = new ArrayList<ClickHandler>();
242
243    /** The DIV showing the list icon. */
244    @UiField
245    protected SimplePanel m_iconPanel;
246
247    /** The open-close button for the additional info. */
248    protected CmsPushButton m_openClose;
249
250    /** A label which is optionally displayed after the subtitle. */
251    protected InlineLabel m_shortExtraInfoLabel;
252
253    /** Sub title label. */
254    @UiField
255    protected CmsLabel m_subtitle;
256
257    /** Title label. */
258    @UiField
259    protected CmsLabel m_title;
260
261    /** Container for the title. */
262    @UiField
263    protected FlowPanel m_titleBox;
264
265    /** The title row, holding the title and the open-close button for the additional info. */
266    @UiField
267    protected FlowPanel m_titleRow;
268
269    /** Variable for the background style. */
270    private CmsStyleVariable m_backgroundStyle;
271
272    /** The child width in px for truncation. */
273    private int m_childWidth;
274
275    /** The fixed icon classes which will always be added if the icon classes are set. */
276    private String m_fixedIconClasses = "";
277
278    /** The event handler registrations. */
279    private List<HandlerRegistration> m_handlerRegistrations;
280
281    /** A click handler which triggers all icon click handlers. */
282    private ClickHandler m_iconSuperClickHandler = new ClickHandler() {
283
284        public void onClick(ClickEvent event) {
285
286            for (ClickHandler iconClickHandler : m_iconClickHandlers) {
287                iconClickHandler.onClick(event);
288            }
289        }
290    };
291
292    /** The main icon title. */
293    private String m_iconTitle = "";
294
295    /** The lock icon. */
296    private HTML m_lockIcon;
297
298    /** The state icon. */
299    private HTML m_stateIcon;
300
301    /** The handler registration for the click handler on the title field. */
302    private HandlerRegistration m_titleClickHandlerRegistration;
303
304    /** A handler object for handling editing of the title field. */
305    private I_CmsTitleEditHandler m_titleEditHandler;
306
307    /** The text metrics prefix. */
308    private String m_tmPrefix;
309
310    /** Widget for the overlay icon in the top-right corner. */
311    private HTML m_topRightIcon;
312
313    private CmsListInfoBean m_infoBean;
314
315    /**
316     * Constructor. Using a 'li'-tag as default root element.<p>
317     *
318     * @param infoBean bean holding the item information
319     */
320    public CmsListItemWidget(CmsListInfoBean infoBean) {
321
322        initWidget(uiBinder.createAndBindUi(this));
323        m_handlerRegistrations = new ArrayList<HandlerRegistration>();
324        m_backgroundStyle = new CmsStyleVariable(this);
325        m_shortExtraInfoLabel = new InlineLabel();
326        init(infoBean);
327    }
328
329
330    /**
331     * Adds an additional info item to the list.<p>
332     *
333     * @param additionalInfo the additional info to display
334     */
335    public void addAdditionalInfo(CmsAdditionalInfoBean additionalInfo) {
336
337        m_additionalInfo.add(new AdditionalInfoItem(additionalInfo));
338        ensureOpenCloseAdditionalInfo();
339    }
340
341    /**
342     * Adds a widget to the button panel.<p>
343     *
344     * @param w the widget to add
345     */
346    public void addButton(Widget w) {
347
348        m_buttonPanel.add(w);
349        if (CmsCoreProvider.get().isIe7()) {
350            m_buttonPanel.getElement().getStyle().setWidth(m_buttonPanel.getWidgetCount() * 22, Unit.PX);
351        }
352    }
353
354    /**
355     * Adds a widget to the front of the button panel.<p>
356     *
357     * @param w the widget to add
358     */
359    public void addButtonToFront(Widget w) {
360
361        m_buttonPanel.insert(w, 0);
362        if (CmsCoreProvider.get().isIe7()) {
363            m_buttonPanel.getElement().getStyle().setWidth(m_buttonPanel.getWidgetCount() * 22, Unit.PX);
364        }
365    }
366
367    /**
368     * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(ClickHandler)
369     */
370    public HandlerRegistration addClickHandler(ClickHandler handler) {
371
372        return addDomHandler(handler, ClickEvent.getType());
373    }
374
375    /**
376     * @see com.google.gwt.event.logical.shared.HasCloseHandlers#addCloseHandler(com.google.gwt.event.logical.shared.CloseHandler)
377     */
378    public HandlerRegistration addCloseHandler(CloseHandler<CmsListItemWidget> handler) {
379
380        return addHandler(handler, CloseEvent.getType());
381    }
382
383    /**
384     * @see com.google.gwt.event.dom.client.HasDoubleClickHandlers#addDoubleClickHandler(com.google.gwt.event.dom.client.DoubleClickHandler)
385     */
386    public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
387
388        return addDomHandler(handler, DoubleClickEvent.getType());
389    }
390
391    /**
392     * Adds a mouse click handler to the icon panel.<p>
393     *
394     * @param handler the click handler
395     *
396     * @return the handler registration
397     */
398    public HandlerRegistration addIconClickHandler(final ClickHandler handler) {
399
400        final HandlerRegistration internalHandlerRegistration = m_iconPanel.addDomHandler(
401            handler,
402            ClickEvent.getType());
403        m_iconClickHandlers.add(handler);
404        HandlerRegistration result = new HandlerRegistration() {
405
406            public void removeHandler() {
407
408                internalHandlerRegistration.removeHandler();
409                m_iconClickHandlers.remove(handler);
410            }
411        };
412        return result;
413    }
414
415    /**
416     * @see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler)
417     */
418    public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
419
420        HandlerRegistration req = addDomHandler(handler, MouseOutEvent.getType());
421        m_handlerRegistrations.add(req);
422        return req;
423
424    }
425
426    /**
427     * @see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler)
428     */
429    public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
430
431        HandlerRegistration req = addDomHandler(handler, MouseOverEvent.getType());
432        m_handlerRegistrations.add(req);
433        return req;
434    }
435
436    /**
437     * @see com.google.gwt.event.logical.shared.HasOpenHandlers#addOpenHandler(com.google.gwt.event.logical.shared.OpenHandler)
438     */
439    public HandlerRegistration addOpenHandler(OpenHandler<CmsListItemWidget> handler) {
440
441        return addHandler(handler, OpenEvent.getType());
442    }
443
444    /**
445     * Adds a style name to the subtitle label.<p>
446     *
447     * @param styleName the style name to add
448     */
449    public void addSubtitleStyleName(String styleName) {
450
451        m_subtitle.addStyleName(styleName);
452    }
453
454    /**
455     * Adds a style name to the title label.<p>
456     *
457     * @param styleName the style name to add
458     */
459    public void addTitleStyleName(String styleName) {
460
461        m_title.addStyleName(styleName);
462    }
463
464    /**
465     * Hides the icon of the list item widget.<p>
466     */
467    public void clearIcon() {
468
469        m_iconPanel.setVisible(false);
470    }
471
472    /**
473     * Forces mouse out on self and contained buttons.<p>
474     */
475    public void forceMouseOut() {
476
477        for (Widget w : m_buttonPanel) {
478            if (w instanceof CmsPushButton) {
479                ((CmsPushButton)w).clearHoverState();
480            }
481        }
482        CmsDomUtil.ensureMouseOut(this);
483        removeStyleName(I_CmsLayoutBundle.INSTANCE.stateCss().cmsHovering());
484    }
485
486    /**
487     * Returns the button at the given position.<p>
488     *
489     * @param index the button index
490     *
491     * @return the button at the given position
492     */
493    public Widget getButton(int index) {
494
495        return m_buttonPanel.getWidget(index);
496    }
497
498    /**
499     * Returns the number of buttons.<p>
500     *
501     * @return the number of buttons
502     */
503    public int getButtonCount() {
504
505        return m_buttonPanel.getWidgetCount();
506    }
507
508    /**
509     * Returns the button panel.<p>
510     *
511     * @return the button panel
512     */
513    public FlowPanel getButtonPanel() {
514
515        return m_buttonPanel;
516    }
517
518    /**
519     * Returns the content panel.<p>
520     *
521     * @return the content panel
522     */
523    public FlowPanel getContentPanel() {
524
525        return m_contentPanel;
526    }
527
528    public CmsListInfoBean getInfoBean() {
529
530        return m_infoBean;
531    }
532
533    public CmsPushButton getOpenClose() {
534        return m_openClose;
535    }
536
537    /**
538     * Returns the label after the subtitle.<p>
539     *
540     * @return the label after the subtitle
541     */
542    public InlineLabel getShortExtraInfoLabel() {
543
544        return m_shortExtraInfoLabel;
545    }
546
547    /**
548     * Returns the subtitle label.<p>
549     *
550     * @return the subtitle label
551     */
552    public String getSubtitleLabel() {
553
554        return m_subtitle.getText();
555    }
556
557    /**
558     * Returns the title label text.<p>
559     *
560     * @return the title label text
561     */
562    public String getTitleLabel() {
563
564        return m_title.getText();
565    }
566
567    /**
568     * Gets the title widget.<p>
569     *
570     * @return the title widget
571     */
572    public CmsLabel getTitleWidget() {
573
574        return m_title;
575    }
576
577    /**
578     * Returns if additional info items are present.<p>
579     *
580     * @return <code>true</code> if additional info items are present
581     */
582    public boolean hasAdditionalInfo() {
583
584        return m_additionalInfo.getWidgetCount() > 0;
585    }
586
587    /**
588     * Re-initializes the additional infos.<p>
589     *
590     * @param infoBean the info bean
591     */
592    public void reInitAdditionalInfo(CmsListInfoBean infoBean) {
593
594        m_additionalInfo.clear();
595        boolean hadOpenClose = false;
596        boolean openCloseDown = false;
597        if (m_openClose != null) {
598            hadOpenClose = true;
599            openCloseDown = m_openClose.isDown();
600            m_openClose.removeFromParent();
601            m_openClose = null;
602        }
603        initAdditionalInfo(infoBean);
604        if (hadOpenClose) {
605            m_openClose.setDown(openCloseDown);
606        }
607    }
608
609    /**
610     * Removes a widget from the button panel.<p>
611     *
612     * @param w the widget to remove
613     */
614    public void removeButton(Widget w) {
615
616        m_buttonPanel.remove(w);
617        if (CmsCoreProvider.get().isIe7()) {
618            m_buttonPanel.getElement().getStyle().setWidth(m_buttonPanel.getWidgetCount() * 22, Unit.PX);
619        }
620    }
621
622    /**
623     * Removes all registered mouse event handlers including the context menu handler.<p>
624     */
625    public void removeMouseHandlers() {
626
627        Iterator<HandlerRegistration> it = m_handlerRegistrations.iterator();
628        while (it.hasNext()) {
629            it.next().removeHandler();
630        }
631        m_handlerRegistrations.clear();
632    }
633
634    /**
635     * Removes a style name from the subtitle label.<p>
636     *
637     * @param styleName the style name to add
638     */
639    public void removeSubtitleStyleName(String styleName) {
640
641        m_subtitle.removeStyleName(styleName);
642    }
643
644    /**
645     * Removes a style name from the title label.<p>
646     *
647     * @param styleName the style name to add
648     */
649    public void removeTitleStyleName(String styleName) {
650
651        m_title.removeStyleName(styleName);
652    }
653
654    /**
655     * Sets the additional info value label at the given position.<p>
656     *
657     * @param index the additional info index
658     * @param label the new value to set
659     */
660    public void setAdditionalInfoValue(int index, String label) {
661
662        ((AdditionalInfoItem)m_additionalInfo.getWidget(index)).getValueLabel().setText(label);
663    }
664
665    /**
666     * Sets the additional info visible if present.<p>
667     *
668     * @param visible <code>true</code> to show, <code>false</code> to hide
669     */
670    public void setAdditionalInfoVisible(boolean visible) {
671
672        if (m_openClose == null) {
673            return;
674        }
675        if (visible) {
676            addStyleName(CmsListItemWidget.OPENCLASS);
677            m_openClose.setDown(true);
678            OpenEvent.fire(this, this);
679        } else {
680            removeStyleName(CmsListItemWidget.OPENCLASS);
681            m_openClose.setDown(false);
682            CloseEvent.fire(this, this);
683        }
684        CmsDomUtil.resizeAncestor(getParent());
685    }
686
687    /**
688     * Sets the background color.<p>
689     *
690     * @param background the color
691     */
692    public void setBackground(Background background) {
693
694        switch (background) {
695            case BLUE:
696                m_backgroundStyle.setValue(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemBlue());
697                break;
698            case RED:
699                m_backgroundStyle.setValue(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemRed());
700                break;
701            case YELLOW:
702                m_backgroundStyle.setValue(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemYellow());
703                break;
704            case DEFAULT:
705            default:
706                m_backgroundStyle.setValue(null);
707        }
708    }
709
710    /**
711     * Sets the extra info text, and hides or displays the extra info label depending on whether
712     * the text is null or not null.<p>
713     *
714     * @param text the text to put into the subtitle suffix
715     */
716    public void setExtraInfo(String text) {
717
718        if (text == null) {
719            if (m_shortExtraInfoLabel.getParent() != null) {
720                m_shortExtraInfoLabel.removeFromParent();
721            }
722        } else {
723            if (m_shortExtraInfoLabel.getParent() == null) {
724                m_titleBox.add(m_shortExtraInfoLabel);
725            }
726            m_shortExtraInfoLabel.setText(text);
727        }
728        updateTruncation();
729    }
730
731    /**
732     * Sets the icon of this item.<p>
733     *
734     * @param image the image to use as icon
735     */
736    public void setIcon(Image image) {
737
738        m_iconPanel.setVisible(true);
739        if (image == null) {
740            return;
741        }
742        m_iconPanel.setWidget(image);
743    }
744
745    /**
746     * Sets the icon for this item using the given CSS classes.<p>
747     *
748     * @param iconClasses the CSS classes
749     */
750    public void setIcon(String iconClasses) {
751
752        setIcon(iconClasses, null);
753    }
754
755    /**
756     * Sets the icon for this item using the given CSS classes.<p>
757     *
758     * @param iconClasses the CSS classes
759     * @param detailIconClasses the detail type icon classes if available
760     */
761    public void setIcon(String iconClasses, String detailIconClasses) {
762
763        m_iconPanel.setVisible(true);
764        HTML iconWidget = new HTML();
765        m_iconPanel.setWidget(iconWidget);
766        iconWidget.setStyleName(iconClasses + " " + m_fixedIconClasses);
767        // render the detail icon as an overlay above the main icon, if required
768        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(detailIconClasses)) {
769            iconWidget.setHTML(
770                "<span class=\""
771                    + detailIconClasses
772                    + " "
773                    + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().pageDetailType()
774                    + "\"></span>");
775        }
776    }
777
778    /**
779     * Sets the cursor for the icon.<p>
780     *
781     * @param cursor the cursor for the icon
782     */
783    public void setIconCursor(Cursor cursor) {
784
785        m_iconPanel.getElement().getStyle().setCursor(cursor);
786
787    }
788
789    /**
790     * Sets the icon title.<p>
791     *
792     * @param title the new icon title
793     */
794    public void setIconTitle(String title) {
795
796        m_iconTitle = title;
797        m_iconPanel.setTitle(title);
798    }
799
800    /**
801     * Sets the lock icon.<p>
802     *
803     * @param icon the icon to use
804     * @param iconTitle the icon title
805     */
806    public void setLockIcon(LockIcon icon, String iconTitle) {
807
808        if (m_lockIcon == null) {
809            m_lockIcon = new HTML();
810            m_lockIcon.addClickHandler(m_iconSuperClickHandler);
811            m_contentPanel.add(m_lockIcon);
812        }
813        switch (icon) {
814            case CLOSED:
815                m_lockIcon.setStyleName(
816                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
817                        + " "
818                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockClosed());
819                break;
820            case OPEN:
821                m_lockIcon.setStyleName(
822                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
823                        + " "
824                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockOpen());
825                break;
826            case SHARED_CLOSED:
827                m_lockIcon.setStyleName(
828                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
829                        + " "
830                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockSharedClosed());
831                break;
832            case SHARED_OPEN:
833                m_lockIcon.setStyleName(
834                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
835                        + " "
836                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockSharedOpen());
837                break;
838            case NONE:
839            default:
840                m_lockIcon.setStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon());
841        }
842
843        m_lockIcon.setTitle(concatIconTitles(m_iconTitle, iconTitle));
844        m_lockIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER);
845    }
846
847    /**
848     * Sets the state icon.<p>
849     *
850     * The state icon indicates if a resource is exported, secure, etc.<p>
851     *
852     * @param icon the state icon
853     */
854    public void setStateIcon(StateIcon icon) {
855
856        if (m_stateIcon == null) {
857            m_stateIcon = new HTML();
858            m_stateIcon.addClickHandler(m_iconSuperClickHandler);
859            m_contentPanel.add(m_stateIcon);
860
861        }
862        String iconTitle = null;
863        I_CmsListItemWidgetCss listItemWidgetCss = I_CmsLayoutBundle.INSTANCE.listItemWidgetCss();
864        String styleStateIcon = I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().stateIcon();
865        switch (icon) {
866            case export:
867                m_stateIcon.setStyleName(styleStateIcon + " " + listItemWidgetCss.export());
868                iconTitle = Messages.get().key(Messages.GUI_ICON_TITLE_EXPORT_0);
869                break;
870            case secure:
871                m_stateIcon.setStyleName(styleStateIcon + " " + listItemWidgetCss.secure());
872                iconTitle = Messages.get().key(Messages.GUI_ICON_TITLE_SECURE_0);
873                break;
874            case copy:
875                m_stateIcon.setStyleName(styleStateIcon + " " + listItemWidgetCss.copyModel());
876                break;
877            case standard:
878            default:
879                m_stateIcon.setStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().stateIcon());
880                break;
881        }
882        m_stateIcon.setTitle(concatIconTitles(m_iconTitle, iconTitle));
883        m_stateIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER);
884    }
885
886    /**
887     * Sets the subtitle label text.<p>
888     *
889     * @param label the new subtitle to set
890     */
891    public void setSubtitleLabel(String label) {
892
893        if (label == null) {
894            label = "";
895        }
896        DOMParser parser = new DOMParser();
897        HTMLDocument doc = Js.cast(parser.parseFromString(label, "text/html"));
898        String stripped = doc.body.textContent;
899        if (CmsStringUtil.isEmptyOrWhitespaceOnly(stripped) && !CmsStringUtil.isEmptyOrWhitespaceOnly(label)) {
900            CmsGwtLog.log("Empty HTML stripping output for input: " + label);
901        }
902        m_subtitle.setText(stripped);
903    }
904
905    /**
906     * Enables or disabled editing of the title field.<p>
907     *
908     * @param editable if true, makes the title field editable
909     */
910    public void setTitleEditable(boolean editable) {
911
912        boolean alreadyEditable = m_titleClickHandlerRegistration != null;
913        if (alreadyEditable == editable) {
914            return;
915        }
916        if (!editable) {
917            m_titleClickHandlerRegistration.removeHandler();
918            m_titleClickHandlerRegistration = null;
919            m_title.removeStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().inlineEditable());
920        } else {
921            m_title.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().inlineEditable());
922            m_titleClickHandlerRegistration = m_title.addClickHandler(new ClickHandler() {
923
924                /**
925                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
926                 */
927                public void onClick(ClickEvent event) {
928
929                    editTitle();
930                }
931            });
932        }
933
934    }
935
936    /**
937     * Sets the handler for editing the list item widget's title.
938     *
939     * @param handler the new title editing handler
940     */
941    public void setTitleEditHandler(I_CmsTitleEditHandler handler) {
942
943        m_titleEditHandler = handler;
944    }
945
946    /**
947     * Sets the title label text.<p>
948     *
949     * @param label the new title to set
950     */
951    public void setTitleLabel(String label) {
952
953        m_title.setText(label);
954    }
955
956    /**
957     * Sets the icon in the top right corner and its title.<p>
958     *
959     * @param iconClass the CSS class for the icon
960     * @param title the value for the title attribute of the icon
961     */
962    public void setTopRightIcon(String iconClass, String title) {
963
964        if (m_topRightIcon == null) {
965            m_topRightIcon = new HTML();
966            m_contentPanel.add(m_topRightIcon);
967        }
968        m_topRightIcon.setStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().topRightIcon() + " " + iconClass);
969        if (title != null) {
970            m_topRightIcon.setTitle(title);
971        }
972    }
973
974    /**
975     * Makes the content of the list info box unselectable.<p>
976     */
977    public void setUnselectable() {
978
979        m_contentPanel.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().unselectable());
980    }
981
982    /**
983     * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int)
984     */
985    public void truncate(final String textMetricsPrefix, final int widgetWidth) {
986
987        m_childWidth = widgetWidth;
988        m_tmPrefix = textMetricsPrefix;
989        int width = widgetWidth - 4; // just to be on the save side
990        if (m_openClose != null) {
991            width -= 16;
992        }
993        if (m_iconPanel.isVisible()) {
994            width -= 32;
995        }
996        if (width < 0) {
997            // IE fails with a JS error if the width is negative
998            width = 0;
999        }
1000        m_titleBox.setWidth(Math.max(0, width - 30) + "px");
1001        m_subtitle.truncate(textMetricsPrefix + TM_SUBTITLE, width);
1002        truncateAdditionalInfo(textMetricsPrefix, widgetWidth);
1003    }
1004
1005    /**
1006     * Truncates the additional info items.<p>
1007     *
1008     * @param textMetricsPrefix the text metrics prefix
1009     * @param widgetWidth the width to truncate to
1010     */
1011    public void truncateAdditionalInfo(final String textMetricsPrefix, final int widgetWidth) {
1012
1013        for (Widget addInfo : m_additionalInfo) {
1014            ((AdditionalInfoItem)addInfo).truncate(textMetricsPrefix, widgetWidth - 10);
1015        }
1016    }
1017
1018    /**
1019     * Updates the truncation of labels if needed.<p>
1020     *
1021     * Use after changing any text on the widget.<p>
1022     */
1023    public void updateTruncation() {
1024
1025        truncate(m_tmPrefix, m_childWidth);
1026    }
1027
1028    /**
1029     * Internal method which is called when the user clicks on an editable title field.<p>
1030     */
1031    protected void editTitle() {
1032
1033        m_title.setVisible(false);
1034        final TextBox box = new TextBox();
1035        box.setText(m_title.getText());
1036        box.getElement().setAttribute("size", "45");
1037        box.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().labelInput());
1038        box.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().titleInput());
1039        final String originalTitle = m_title.getText();
1040        // wrap the boolean flag in an array so we can change it from the event handlers
1041        final boolean[] checked = new boolean[] {false};
1042        final boolean restoreUnselectable = CmsDomUtil.hasClass(
1043            I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().unselectable(),
1044            m_contentPanel.getElement());
1045        m_contentPanel.removeStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().unselectable());
1046        box.addBlurHandler(new BlurHandler() {
1047
1048            /**
1049             * @see com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event.dom.client.BlurEvent)
1050             */
1051            public void onBlur(BlurEvent event) {
1052
1053                if (restoreUnselectable) {
1054                    setUnselectable();
1055                }
1056                if (checked[0]) {
1057                    return;
1058                }
1059
1060                onEditTitleTextBox(box);
1061                checked[0] = true;
1062            }
1063        });
1064
1065        box.addKeyPressHandler(new KeyPressHandler() {
1066
1067            /**
1068             * @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent)
1069             */
1070            public void onKeyPress(KeyPressEvent event) {
1071
1072                if (checked[0]) {
1073                    return;
1074                }
1075
1076                int keycode = event.getNativeEvent().getKeyCode();
1077
1078                if ((keycode == 10) || (keycode == 13)) {
1079                    onEditTitleTextBox(box);
1080                    checked[0] = true;
1081                }
1082                if (keycode == 27) {
1083                    box.setText(originalTitle);
1084                    onEditTitleTextBox(box);
1085                    checked[0] = true;
1086
1087                }
1088            }
1089        });
1090        m_titleBox.insert(box, 2);
1091        box.setFocus(true);
1092    }
1093
1094    /**
1095     * Ensures the open close button for the additional info list is present.<p>
1096     */
1097    protected void ensureOpenCloseAdditionalInfo() {
1098
1099        if (m_openClose == null) {
1100            m_openClose = new CmsPushButton(I_CmsButton.TRIANGLE_RIGHT, I_CmsButton.TRIANGLE_DOWN);
1101            m_openClose.setButtonStyle(ButtonStyle.FONT_ICON, null);
1102            m_openClose.setSize(Size.small);
1103            m_titleBox.insert(m_openClose, 0);
1104            m_openClose.addClickHandler(new ClickHandler() {
1105
1106                /**
1107                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
1108                 */
1109                public void onClick(ClickEvent event) {
1110
1111                    setAdditionalInfoVisible(!getElement().getClassName().contains(CmsListItemWidget.OPENCLASS));
1112                    CmsDomUtil.resizeAncestor(CmsListItemWidget.this);
1113                }
1114            });
1115        }
1116    }
1117
1118    /**
1119     * Constructor.<p>
1120     *
1121     * @param infoBean bean holding the item information
1122     */
1123    protected void init(CmsListInfoBean infoBean) {
1124
1125        m_infoBean = infoBean;
1126        m_iconPanel.setVisible(false);
1127        m_title.setText(infoBean.getTitle());
1128        setSubtitleLabel(infoBean.getSubTitle());
1129
1130        // set the resource type icon if present
1131        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(infoBean.getBigIconClasses())) {
1132            setIcon(infoBean.getBigIconClasses(), infoBean.getSmallIconClasses());
1133        }
1134
1135        if (infoBean.getStateIcon() != null) {
1136            setStateIcon(infoBean.getStateIcon());
1137        }
1138        if (infoBean.getLockIcon() != null) {
1139            setLockIcon(infoBean.getLockIcon(), infoBean.getLockIconTitle());
1140        }
1141
1142        CmsResourceState resourceState = infoBean.getResourceState();
1143
1144        if ((resourceState != null) && !resourceState.isUnchanged() && infoBean.isMarkChangedState()) {
1145            String title = Messages.get().key(Messages.GUI_UNPUBLISHED_CHANGES_TITLE_0);
1146            if (resourceState.isNew()) {
1147                title = Messages.get().key(Messages.GUI_UNPUBLISHED_CHANGES_NEW_TITLE_0);
1148            }
1149            setTopRightIcon(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().changed(), title);
1150        }
1151
1152        if ((resourceState != null) && resourceState.isDeleted()) {
1153            m_title.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().titleDeleted());
1154        }
1155
1156        initAdditionalInfo(infoBean);
1157    }
1158
1159    /**
1160     * Initializes the additional info.<p>
1161     *
1162     * @param infoBean the info bean
1163     */
1164    protected void initAdditionalInfo(CmsListInfoBean infoBean) {
1165
1166        // create the state info
1167        CmsResourceState state = infoBean.getResourceState();
1168        if (state != null) {
1169            String stateKey = Messages.get().key(Messages.GUI_RESOURCE_STATE_0);
1170            String stateValue = CmsResourceStateUtil.getStateName(state);
1171            String stateStyle = CmsResourceStateUtil.getStateStyle(state);
1172            m_additionalInfo.add(new AdditionalInfoItem(new CmsAdditionalInfoBean(stateKey, stateValue, stateStyle)));
1173            ensureOpenCloseAdditionalInfo();
1174        }
1175
1176        // set the additional info
1177        if (infoBean.hasAdditionalInfo()) {
1178            ensureOpenCloseAdditionalInfo();
1179            for (CmsAdditionalInfoBean additionalInfo : infoBean.getAdditionalInfo()) {
1180                m_additionalInfo.add(new AdditionalInfoItem(additionalInfo));
1181            }
1182        }
1183    }
1184
1185    /**
1186     * Internal method which is called when the user has finished editing the title.
1187     *
1188     * @param box the text box which has been edited
1189     */
1190    protected void onEditTitleTextBox(TextBox box) {
1191
1192        if (m_titleEditHandler != null) {
1193            m_titleEditHandler.handleEdit(m_title, box);
1194            return;
1195        }
1196
1197        String text = box.getText();
1198        box.removeFromParent();
1199        m_title.setText(text);
1200        m_title.setVisible(true);
1201
1202    }
1203
1204    /**
1205     * Combines the main icon title with the title for a status icon overlayed over the main icon.<p>
1206     *
1207     * @param main the main icon title
1208     * @param secondary the secondary icon title
1209     *
1210     * @return the combined icon title for the secondary icon
1211     */
1212    String concatIconTitles(String main, String secondary) {
1213
1214        if (main == null) {
1215            main = "";
1216        }
1217        if (secondary == null) {
1218            secondary = "";
1219        }
1220
1221        if (secondary.length() == 0) {
1222            return main;
1223        }
1224        return main + " [" + secondary + "]";
1225
1226    }
1227
1228}