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    /**
314     * Constructor. Using a 'li'-tag as default root element.<p>
315     *
316     * @param infoBean bean holding the item information
317     */
318    public CmsListItemWidget(CmsListInfoBean infoBean) {
319
320        initWidget(uiBinder.createAndBindUi(this));
321        m_handlerRegistrations = new ArrayList<HandlerRegistration>();
322        m_backgroundStyle = new CmsStyleVariable(this);
323        m_shortExtraInfoLabel = new InlineLabel();
324        init(infoBean);
325    }
326
327    /**
328     * Adds an additional info item to the list.<p>
329     *
330     * @param additionalInfo the additional info to display
331     */
332    public void addAdditionalInfo(CmsAdditionalInfoBean additionalInfo) {
333
334        m_additionalInfo.add(new AdditionalInfoItem(additionalInfo));
335        ensureOpenCloseAdditionalInfo();
336    }
337
338    /**
339     * Adds a widget to the button panel.<p>
340     *
341     * @param w the widget to add
342     */
343    public void addButton(Widget w) {
344
345        m_buttonPanel.add(w);
346        if (CmsCoreProvider.get().isIe7()) {
347            m_buttonPanel.getElement().getStyle().setWidth(m_buttonPanel.getWidgetCount() * 22, Unit.PX);
348        }
349    }
350
351    /**
352     * Adds a widget to the front of the button panel.<p>
353     *
354     * @param w the widget to add
355     */
356    public void addButtonToFront(Widget w) {
357
358        m_buttonPanel.insert(w, 0);
359        if (CmsCoreProvider.get().isIe7()) {
360            m_buttonPanel.getElement().getStyle().setWidth(m_buttonPanel.getWidgetCount() * 22, Unit.PX);
361        }
362    }
363
364    /**
365     * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(ClickHandler)
366     */
367    public HandlerRegistration addClickHandler(ClickHandler handler) {
368
369        return addDomHandler(handler, ClickEvent.getType());
370    }
371
372    /**
373     * @see com.google.gwt.event.logical.shared.HasCloseHandlers#addCloseHandler(com.google.gwt.event.logical.shared.CloseHandler)
374     */
375    public HandlerRegistration addCloseHandler(CloseHandler<CmsListItemWidget> handler) {
376
377        return addHandler(handler, CloseEvent.getType());
378    }
379
380    /**
381     * @see com.google.gwt.event.dom.client.HasDoubleClickHandlers#addDoubleClickHandler(com.google.gwt.event.dom.client.DoubleClickHandler)
382     */
383    public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
384
385        return addDomHandler(handler, DoubleClickEvent.getType());
386    }
387
388    /**
389     * Adds a mouse click handler to the icon panel.<p>
390     *
391     * @param handler the click handler
392     *
393     * @return the handler registration
394     */
395    public HandlerRegistration addIconClickHandler(final ClickHandler handler) {
396
397        final HandlerRegistration internalHandlerRegistration = m_iconPanel.addDomHandler(
398            handler,
399            ClickEvent.getType());
400        m_iconClickHandlers.add(handler);
401        HandlerRegistration result = new HandlerRegistration() {
402
403            public void removeHandler() {
404
405                internalHandlerRegistration.removeHandler();
406                m_iconClickHandlers.remove(handler);
407            }
408        };
409        return result;
410    }
411
412    /**
413     * @see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler)
414     */
415    public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
416
417        HandlerRegistration req = addDomHandler(handler, MouseOutEvent.getType());
418        m_handlerRegistrations.add(req);
419        return req;
420
421    }
422
423    /**
424     * @see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler)
425     */
426    public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
427
428        HandlerRegistration req = addDomHandler(handler, MouseOverEvent.getType());
429        m_handlerRegistrations.add(req);
430        return req;
431    }
432
433    /**
434     * @see com.google.gwt.event.logical.shared.HasOpenHandlers#addOpenHandler(com.google.gwt.event.logical.shared.OpenHandler)
435     */
436    public HandlerRegistration addOpenHandler(OpenHandler<CmsListItemWidget> handler) {
437
438        return addHandler(handler, OpenEvent.getType());
439    }
440
441    /**
442     * Adds a style name to the subtitle label.<p>
443     *
444     * @param styleName the style name to add
445     */
446    public void addSubtitleStyleName(String styleName) {
447
448        m_subtitle.addStyleName(styleName);
449    }
450
451    /**
452     * Adds a style name to the title label.<p>
453     *
454     * @param styleName the style name to add
455     */
456    public void addTitleStyleName(String styleName) {
457
458        m_title.addStyleName(styleName);
459    }
460
461    /**
462     * Hides the icon of the list item widget.<p>
463     */
464    public void clearIcon() {
465
466        m_iconPanel.setVisible(false);
467    }
468
469    /**
470     * Forces mouse out on self and contained buttons.<p>
471     */
472    public void forceMouseOut() {
473
474        for (Widget w : m_buttonPanel) {
475            if (w instanceof CmsPushButton) {
476                ((CmsPushButton)w).clearHoverState();
477            }
478        }
479        CmsDomUtil.ensureMouseOut(this);
480        removeStyleName(I_CmsLayoutBundle.INSTANCE.stateCss().cmsHovering());
481    }
482
483    /**
484     * Returns the button at the given position.<p>
485     *
486     * @param index the button index
487     *
488     * @return the button at the given position
489     */
490    public Widget getButton(int index) {
491
492        return m_buttonPanel.getWidget(index);
493    }
494
495    /**
496     * Returns the number of buttons.<p>
497     *
498     * @return the number of buttons
499     */
500    public int getButtonCount() {
501
502        return m_buttonPanel.getWidgetCount();
503    }
504
505    /**
506     * Returns the button panel.<p>
507     *
508     * @return the button panel
509     */
510    public FlowPanel getButtonPanel() {
511
512        return m_buttonPanel;
513    }
514
515    /**
516     * Returns the content panel.<p>
517     *
518     * @return the content panel
519     */
520    public FlowPanel getContentPanel() {
521
522        return m_contentPanel;
523    }
524
525    /**
526     * Returns the label after the subtitle.<p>
527     *
528     * @return the label after the subtitle
529     */
530    public InlineLabel getShortExtraInfoLabel() {
531
532        return m_shortExtraInfoLabel;
533    }
534
535    /**
536     * Returns the subtitle label.<p>
537     *
538     * @return the subtitle label
539     */
540    public String getSubtitleLabel() {
541
542        return m_subtitle.getText();
543    }
544
545    /**
546     * Returns the title label text.<p>
547     *
548     * @return the title label text
549     */
550    public String getTitleLabel() {
551
552        return m_title.getText();
553    }
554
555    /**
556     * Gets the title widget.<p>
557     *
558     * @return the title widget
559     */
560    public CmsLabel getTitleWidget() {
561
562        return m_title;
563    }
564
565    /**
566     * Returns if additional info items are present.<p>
567     *
568     * @return <code>true</code> if additional info items are present
569     */
570    public boolean hasAdditionalInfo() {
571
572        return m_additionalInfo.getWidgetCount() > 0;
573    }
574
575    /**
576     * Re-initializes the additional infos.<p>
577     *
578     * @param infoBean the info bean
579     */
580    public void reInitAdditionalInfo(CmsListInfoBean infoBean) {
581
582        m_additionalInfo.clear();
583        boolean hadOpenClose = false;
584        boolean openCloseDown = false;
585        if (m_openClose != null) {
586            hadOpenClose = true;
587            openCloseDown = m_openClose.isDown();
588            m_openClose.removeFromParent();
589            m_openClose = null;
590        }
591        initAdditionalInfo(infoBean);
592        if (hadOpenClose) {
593            m_openClose.setDown(openCloseDown);
594        }
595    }
596
597    /**
598     * Removes a widget from the button panel.<p>
599     *
600     * @param w the widget to remove
601     */
602    public void removeButton(Widget w) {
603
604        m_buttonPanel.remove(w);
605        if (CmsCoreProvider.get().isIe7()) {
606            m_buttonPanel.getElement().getStyle().setWidth(m_buttonPanel.getWidgetCount() * 22, Unit.PX);
607        }
608    }
609
610    /**
611     * Removes all registered mouse event handlers including the context menu handler.<p>
612     */
613    public void removeMouseHandlers() {
614
615        Iterator<HandlerRegistration> it = m_handlerRegistrations.iterator();
616        while (it.hasNext()) {
617            it.next().removeHandler();
618        }
619        m_handlerRegistrations.clear();
620    }
621
622    /**
623     * Removes a style name from the subtitle label.<p>
624     *
625     * @param styleName the style name to add
626     */
627    public void removeSubtitleStyleName(String styleName) {
628
629        m_subtitle.removeStyleName(styleName);
630    }
631
632    /**
633     * Removes a style name from the title label.<p>
634     *
635     * @param styleName the style name to add
636     */
637    public void removeTitleStyleName(String styleName) {
638
639        m_title.removeStyleName(styleName);
640    }
641
642    /**
643     * Sets the additional info value label at the given position.<p>
644     *
645     * @param index the additional info index
646     * @param label the new value to set
647     */
648    public void setAdditionalInfoValue(int index, String label) {
649
650        ((AdditionalInfoItem)m_additionalInfo.getWidget(index)).getValueLabel().setText(label);
651    }
652
653    /**
654     * Sets the additional info visible if present.<p>
655     *
656     * @param visible <code>true</code> to show, <code>false</code> to hide
657     */
658    public void setAdditionalInfoVisible(boolean visible) {
659
660        if (m_openClose == null) {
661            return;
662        }
663        if (visible) {
664            addStyleName(CmsListItemWidget.OPENCLASS);
665            m_openClose.setDown(true);
666            OpenEvent.fire(this, this);
667        } else {
668            removeStyleName(CmsListItemWidget.OPENCLASS);
669            m_openClose.setDown(false);
670            CloseEvent.fire(this, this);
671        }
672        CmsDomUtil.resizeAncestor(getParent());
673    }
674
675    /**
676     * Sets the background color.<p>
677     *
678     * @param background the color
679     */
680    public void setBackground(Background background) {
681
682        switch (background) {
683            case BLUE:
684                m_backgroundStyle.setValue(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemBlue());
685                break;
686            case RED:
687                m_backgroundStyle.setValue(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemRed());
688                break;
689            case YELLOW:
690                m_backgroundStyle.setValue(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemYellow());
691                break;
692            case DEFAULT:
693            default:
694                m_backgroundStyle.setValue(null);
695        }
696    }
697
698    /**
699     * Sets the extra info text, and hides or displays the extra info label depending on whether
700     * the text is null or not null.<p>
701     *
702     * @param text the text to put into the subtitle suffix
703     */
704    public void setExtraInfo(String text) {
705
706        if (text == null) {
707            if (m_shortExtraInfoLabel.getParent() != null) {
708                m_shortExtraInfoLabel.removeFromParent();
709            }
710        } else {
711            if (m_shortExtraInfoLabel.getParent() == null) {
712                m_titleBox.add(m_shortExtraInfoLabel);
713            }
714            m_shortExtraInfoLabel.setText(text);
715        }
716        updateTruncation();
717    }
718
719    /**
720     * Sets the icon of this item.<p>
721     *
722     * @param image the image to use as icon
723     */
724    public void setIcon(Image image) {
725
726        m_iconPanel.setVisible(true);
727        if (image == null) {
728            return;
729        }
730        m_iconPanel.setWidget(image);
731    }
732
733    /**
734     * Sets the icon for this item using the given CSS classes.<p>
735     *
736     * @param iconClasses the CSS classes
737     */
738    public void setIcon(String iconClasses) {
739
740        setIcon(iconClasses, null);
741    }
742
743    /**
744     * Sets the icon for this item using the given CSS classes.<p>
745     *
746     * @param iconClasses the CSS classes
747     * @param detailIconClasses the detail type icon classes if available
748     */
749    public void setIcon(String iconClasses, String detailIconClasses) {
750
751        m_iconPanel.setVisible(true);
752        HTML iconWidget = new HTML();
753        m_iconPanel.setWidget(iconWidget);
754        iconWidget.setStyleName(iconClasses + " " + m_fixedIconClasses);
755        // render the detail icon as an overlay above the main icon, if required
756        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(detailIconClasses)) {
757            iconWidget.setHTML(
758                "<span class=\""
759                    + detailIconClasses
760                    + " "
761                    + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().pageDetailType()
762                    + "\"></span>");
763        }
764    }
765
766    /**
767     * Sets the cursor for the icon.<p>
768     *
769     * @param cursor the cursor for the icon
770     */
771    public void setIconCursor(Cursor cursor) {
772
773        m_iconPanel.getElement().getStyle().setCursor(cursor);
774
775    }
776
777    /**
778     * Sets the icon title.<p>
779     *
780     * @param title the new icon title
781     */
782    public void setIconTitle(String title) {
783
784        m_iconTitle = title;
785        m_iconPanel.setTitle(title);
786    }
787
788    /**
789     * Sets the lock icon.<p>
790     *
791     * @param icon the icon to use
792     * @param iconTitle the icon title
793     */
794    public void setLockIcon(LockIcon icon, String iconTitle) {
795
796        if (m_lockIcon == null) {
797            m_lockIcon = new HTML();
798            m_lockIcon.addClickHandler(m_iconSuperClickHandler);
799            m_contentPanel.add(m_lockIcon);
800        }
801        switch (icon) {
802            case CLOSED:
803                m_lockIcon.setStyleName(
804                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
805                        + " "
806                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockClosed());
807                break;
808            case OPEN:
809                m_lockIcon.setStyleName(
810                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
811                        + " "
812                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockOpen());
813                break;
814            case SHARED_CLOSED:
815                m_lockIcon.setStyleName(
816                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
817                        + " "
818                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockSharedClosed());
819                break;
820            case SHARED_OPEN:
821                m_lockIcon.setStyleName(
822                    I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon()
823                        + " "
824                        + I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockSharedOpen());
825                break;
826            case NONE:
827            default:
828                m_lockIcon.setStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().lockIcon());
829        }
830
831        m_lockIcon.setTitle(concatIconTitles(m_iconTitle, iconTitle));
832        m_lockIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER);
833    }
834
835    /**
836     * Sets the state icon.<p>
837     *
838     * The state icon indicates if a resource is exported, secure, etc.<p>
839     *
840     * @param icon the state icon
841     */
842    public void setStateIcon(StateIcon icon) {
843
844        if (m_stateIcon == null) {
845            m_stateIcon = new HTML();
846            m_stateIcon.addClickHandler(m_iconSuperClickHandler);
847            m_contentPanel.add(m_stateIcon);
848
849        }
850        String iconTitle = null;
851        I_CmsListItemWidgetCss listItemWidgetCss = I_CmsLayoutBundle.INSTANCE.listItemWidgetCss();
852        String styleStateIcon = I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().stateIcon();
853        switch (icon) {
854            case export:
855                m_stateIcon.setStyleName(styleStateIcon + " " + listItemWidgetCss.export());
856                iconTitle = Messages.get().key(Messages.GUI_ICON_TITLE_EXPORT_0);
857                break;
858            case secure:
859                m_stateIcon.setStyleName(styleStateIcon + " " + listItemWidgetCss.secure());
860                iconTitle = Messages.get().key(Messages.GUI_ICON_TITLE_SECURE_0);
861                break;
862            case copy:
863                m_stateIcon.setStyleName(styleStateIcon + " " + listItemWidgetCss.copyModel());
864                break;
865            case standard:
866            default:
867                m_stateIcon.setStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().stateIcon());
868                break;
869        }
870        m_stateIcon.setTitle(concatIconTitles(m_iconTitle, iconTitle));
871        m_stateIcon.getElement().getStyle().setCursor(Style.Cursor.POINTER);
872    }
873
874    /**
875     * Sets the subtitle label text.<p>
876     *
877     * @param label the new subtitle to set
878     */
879    public void setSubtitleLabel(String label) {
880
881        if (label == null) {
882            label = "";
883        }
884        DOMParser parser = new DOMParser();
885        HTMLDocument doc = Js.cast(parser.parseFromString(label, "text/html"));
886        String stripped = doc.body.textContent;
887        if (CmsStringUtil.isEmptyOrWhitespaceOnly(stripped) && !CmsStringUtil.isEmptyOrWhitespaceOnly(label)) {
888            CmsGwtLog.log("Empty HTML stripping output for input: " + label);
889        }
890        m_subtitle.setText(stripped);
891    }
892
893    /**
894     * Enables or disabled editing of the title field.<p>
895     *
896     * @param editable if true, makes the title field editable
897     */
898    public void setTitleEditable(boolean editable) {
899
900        boolean alreadyEditable = m_titleClickHandlerRegistration != null;
901        if (alreadyEditable == editable) {
902            return;
903        }
904        if (!editable) {
905            m_titleClickHandlerRegistration.removeHandler();
906            m_titleClickHandlerRegistration = null;
907            m_title.removeStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().inlineEditable());
908        } else {
909            m_title.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().inlineEditable());
910            m_titleClickHandlerRegistration = m_title.addClickHandler(new ClickHandler() {
911
912                /**
913                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
914                 */
915                public void onClick(ClickEvent event) {
916
917                    editTitle();
918                }
919            });
920        }
921
922    }
923
924    /**
925     * Sets the handler for editing the list item widget's title.
926     *
927     * @param handler the new title editing handler
928     */
929    public void setTitleEditHandler(I_CmsTitleEditHandler handler) {
930
931        m_titleEditHandler = handler;
932    }
933
934    /**
935     * Sets the title label text.<p>
936     *
937     * @param label the new title to set
938     */
939    public void setTitleLabel(String label) {
940
941        m_title.setText(label);
942    }
943
944    /**
945     * Sets the icon in the top right corner and its title.<p>
946     *
947     * @param iconClass the CSS class for the icon
948     * @param title the value for the title attribute of the icon
949     */
950    public void setTopRightIcon(String iconClass, String title) {
951
952        if (m_topRightIcon == null) {
953            m_topRightIcon = new HTML();
954            m_contentPanel.add(m_topRightIcon);
955        }
956        m_topRightIcon.setStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().topRightIcon() + " " + iconClass);
957        if (title != null) {
958            m_topRightIcon.setTitle(title);
959        }
960    }
961
962    /**
963     * Makes the content of the list info box unselectable.<p>
964     */
965    public void setUnselectable() {
966
967        m_contentPanel.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().unselectable());
968    }
969
970    /**
971     * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int)
972     */
973    public void truncate(final String textMetricsPrefix, final int widgetWidth) {
974
975        m_childWidth = widgetWidth;
976        m_tmPrefix = textMetricsPrefix;
977        int width = widgetWidth - 4; // just to be on the save side
978        if (m_openClose != null) {
979            width -= 16;
980        }
981        if (m_iconPanel.isVisible()) {
982            width -= 32;
983        }
984        if (width < 0) {
985            // IE fails with a JS error if the width is negative
986            width = 0;
987        }
988        m_titleBox.setWidth(Math.max(0, width - 30) + "px");
989        m_subtitle.truncate(textMetricsPrefix + TM_SUBTITLE, width);
990        truncateAdditionalInfo(textMetricsPrefix, widgetWidth);
991    }
992
993    /**
994     * Truncates the additional info items.<p>
995     *
996     * @param textMetricsPrefix the text metrics prefix
997     * @param widgetWidth the width to truncate to
998     */
999    public void truncateAdditionalInfo(final String textMetricsPrefix, final int widgetWidth) {
1000
1001        for (Widget addInfo : m_additionalInfo) {
1002            ((AdditionalInfoItem)addInfo).truncate(textMetricsPrefix, widgetWidth - 10);
1003        }
1004    }
1005
1006    /**
1007     * Updates the truncation of labels if needed.<p>
1008     *
1009     * Use after changing any text on the widget.<p>
1010     */
1011    public void updateTruncation() {
1012
1013        truncate(m_tmPrefix, m_childWidth);
1014    }
1015
1016    /**
1017     * Internal method which is called when the user clicks on an editable title field.<p>
1018     */
1019    protected void editTitle() {
1020
1021        m_title.setVisible(false);
1022        final TextBox box = new TextBox();
1023        box.setText(m_title.getText());
1024        box.getElement().setAttribute("size", "45");
1025        box.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().labelInput());
1026        box.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().titleInput());
1027        final String originalTitle = m_title.getText();
1028        // wrap the boolean flag in an array so we can change it from the event handlers
1029        final boolean[] checked = new boolean[] {false};
1030        final boolean restoreUnselectable = CmsDomUtil.hasClass(
1031            I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().unselectable(),
1032            m_contentPanel.getElement());
1033        m_contentPanel.removeStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().unselectable());
1034        box.addBlurHandler(new BlurHandler() {
1035
1036            /**
1037             * @see com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event.dom.client.BlurEvent)
1038             */
1039            public void onBlur(BlurEvent event) {
1040
1041                if (restoreUnselectable) {
1042                    setUnselectable();
1043                }
1044                if (checked[0]) {
1045                    return;
1046                }
1047
1048                onEditTitleTextBox(box);
1049                checked[0] = true;
1050            }
1051        });
1052
1053        box.addKeyPressHandler(new KeyPressHandler() {
1054
1055            /**
1056             * @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent)
1057             */
1058            public void onKeyPress(KeyPressEvent event) {
1059
1060                if (checked[0]) {
1061                    return;
1062                }
1063
1064                int keycode = event.getNativeEvent().getKeyCode();
1065
1066                if ((keycode == 10) || (keycode == 13)) {
1067                    onEditTitleTextBox(box);
1068                    checked[0] = true;
1069                }
1070                if (keycode == 27) {
1071                    box.setText(originalTitle);
1072                    onEditTitleTextBox(box);
1073                    checked[0] = true;
1074
1075                }
1076            }
1077        });
1078        m_titleBox.insert(box, 2);
1079        box.setFocus(true);
1080    }
1081
1082    /**
1083     * Ensures the open close button for the additional info list is present.<p>
1084     */
1085    protected void ensureOpenCloseAdditionalInfo() {
1086
1087        if (m_openClose == null) {
1088            m_openClose = new CmsPushButton(I_CmsButton.TRIANGLE_RIGHT, I_CmsButton.TRIANGLE_DOWN);
1089            m_openClose.setButtonStyle(ButtonStyle.FONT_ICON, null);
1090            m_openClose.setSize(Size.small);
1091            m_titleBox.insert(m_openClose, 0);
1092            m_openClose.addClickHandler(new ClickHandler() {
1093
1094                /**
1095                 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
1096                 */
1097                public void onClick(ClickEvent event) {
1098
1099                    setAdditionalInfoVisible(!getElement().getClassName().contains(CmsListItemWidget.OPENCLASS));
1100                    CmsDomUtil.resizeAncestor(CmsListItemWidget.this);
1101                }
1102            });
1103        }
1104    }
1105
1106    /**
1107     * Constructor.<p>
1108     *
1109     * @param infoBean bean holding the item information
1110     */
1111    protected void init(CmsListInfoBean infoBean) {
1112
1113        m_iconPanel.setVisible(false);
1114        m_title.setText(infoBean.getTitle());
1115        setSubtitleLabel(infoBean.getSubTitle());
1116
1117        // set the resource type icon if present
1118        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(infoBean.getBigIconClasses())) {
1119            setIcon(infoBean.getBigIconClasses(), infoBean.getSmallIconClasses());
1120        }
1121
1122        if (infoBean.getStateIcon() != null) {
1123            setStateIcon(infoBean.getStateIcon());
1124        }
1125        if (infoBean.getLockIcon() != null) {
1126            setLockIcon(infoBean.getLockIcon(), infoBean.getLockIconTitle());
1127        }
1128
1129        CmsResourceState resourceState = infoBean.getResourceState();
1130
1131        if ((resourceState != null) && !resourceState.isUnchanged() && infoBean.isMarkChangedState()) {
1132            String title = Messages.get().key(Messages.GUI_UNPUBLISHED_CHANGES_TITLE_0);
1133            if (resourceState.isNew()) {
1134                title = Messages.get().key(Messages.GUI_UNPUBLISHED_CHANGES_NEW_TITLE_0);
1135            }
1136            setTopRightIcon(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().changed(), title);
1137        }
1138
1139        if ((resourceState != null) && resourceState.isDeleted()) {
1140            m_title.addStyleName(I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().titleDeleted());
1141        }
1142
1143        initAdditionalInfo(infoBean);
1144    }
1145
1146    /**
1147     * Initializes the additional info.<p>
1148     *
1149     * @param infoBean the info bean
1150     */
1151    protected void initAdditionalInfo(CmsListInfoBean infoBean) {
1152
1153        // create the state info
1154        CmsResourceState state = infoBean.getResourceState();
1155        if (state != null) {
1156            String stateKey = Messages.get().key(Messages.GUI_RESOURCE_STATE_0);
1157            String stateValue = CmsResourceStateUtil.getStateName(state);
1158            String stateStyle = CmsResourceStateUtil.getStateStyle(state);
1159            m_additionalInfo.add(new AdditionalInfoItem(new CmsAdditionalInfoBean(stateKey, stateValue, stateStyle)));
1160            ensureOpenCloseAdditionalInfo();
1161        }
1162
1163        // set the additional info
1164        if (infoBean.hasAdditionalInfo()) {
1165            ensureOpenCloseAdditionalInfo();
1166            for (CmsAdditionalInfoBean additionalInfo : infoBean.getAdditionalInfo()) {
1167                m_additionalInfo.add(new AdditionalInfoItem(additionalInfo));
1168            }
1169        }
1170    }
1171
1172    /**
1173     * Internal method which is called when the user has finished editing the title.
1174     *
1175     * @param box the text box which has been edited
1176     */
1177    protected void onEditTitleTextBox(TextBox box) {
1178
1179        if (m_titleEditHandler != null) {
1180            m_titleEditHandler.handleEdit(m_title, box);
1181            return;
1182        }
1183
1184        String text = box.getText();
1185        box.removeFromParent();
1186        m_title.setText(text);
1187        m_title.setVisible(true);
1188
1189    }
1190
1191    /**
1192     * Combines the main icon title with the title for a status icon overlayed over the main icon.<p>
1193     *
1194     * @param main the main icon title
1195     * @param secondary the secondary icon title
1196     *
1197     * @return the combined icon title for the secondary icon
1198     */
1199    String concatIconTitles(String main, String secondary) {
1200
1201        if (main == null) {
1202            main = "";
1203        }
1204        if (secondary == null) {
1205            secondary = "";
1206        }
1207
1208        if (secondary.length() == 0) {
1209            return main;
1210        }
1211        return main + " [" + secondary + "]";
1212
1213    }
1214
1215}