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.ui.favorites;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.i18n.CmsEncoder;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.OpenCms;
037import org.opencms.site.CmsSite;
038import org.opencms.ui.A_CmsUI;
039import org.opencms.ui.CmsVaadinUtils;
040import org.opencms.ui.Messages;
041import org.opencms.ui.components.CmsBasicDialog;
042import org.opencms.ui.components.CmsErrorDialog;
043import org.opencms.ui.components.CmsResourceIcon;
044import org.opencms.ui.components.OpenCmsTheme;
045import org.opencms.ui.components.editablegroup.CmsDefaultActionHandler;
046import org.opencms.ui.components.editablegroup.CmsEditableGroup;
047import org.opencms.ui.components.editablegroup.CmsEditableGroupButtons;
048import org.opencms.ui.components.editablegroup.I_CmsEditableGroupRow;
049import org.opencms.util.CmsStringUtil;
050import org.opencms.util.CmsUUID;
051import org.opencms.workplace.explorer.CmsResourceUtil;
052
053import java.util.ArrayList;
054import java.util.HashMap;
055import java.util.List;
056import java.util.Map;
057import java.util.Optional;
058
059import org.apache.commons.logging.Log;
060
061import com.vaadin.shared.ui.ContentMode;
062import com.vaadin.ui.Button;
063import com.vaadin.ui.Component;
064import com.vaadin.ui.Label;
065import com.vaadin.ui.VerticalLayout;
066import com.vaadin.v7.data.Item;
067import com.vaadin.v7.data.util.IndexedContainer;
068
069/**
070 * Dialog which shows the list of favorites for the current user and allows them to jump to individual favorites,
071 * edit the list, or add the current location to the favorite list.
072 */
073public class CmsFavoriteDialog extends CmsBasicDialog implements CmsEditableGroup.I_RowBuilder {
074
075    /**
076     * Action handler that saves favorites after every change.
077     */
078    public class SaveAfterChangeActionHandler extends CmsDefaultActionHandler {
079
080        /**
081         * Creates a new instance.
082         *
083         * @param row the row
084         */
085        @SuppressWarnings("synthetic-access")
086        public SaveAfterChangeActionHandler(I_CmsEditableGroupRow row) {
087
088            super(m_group, row);
089        }
090
091        /**
092         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onAdd()
093         */
094        @SuppressWarnings("synthetic-access")
095        @Override
096        public void onAdd() {
097
098            super.onAdd();
099            doSave();
100
101        }
102
103        /**
104         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onDelete()
105         */
106        @Override
107        public void onDelete() {
108
109            super.onDelete();
110            doSave();
111
112        }
113
114        /**
115         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onDown()
116         */
117        @SuppressWarnings("synthetic-access")
118        @Override
119        public void onDown() {
120
121            super.onDown();
122            doSave();
123
124        }
125
126        /**
127         * @see org.opencms.ui.components.editablegroup.CmsDefaultActionHandler#onUp()
128         */
129        @Override
130        public void onUp() {
131
132            super.onUp();
133            doSave();
134        }
135
136    }
137
138    /**
139     * Handles changes in empty/not empty state by hiding or displaying a message.
140     */
141    class EmptyHandler implements CmsEditableGroup.I_EmptyHandler {
142
143        /** The group. */
144        private CmsEditableGroup m_groupForHandler;
145
146        /** The placeholder. */
147        private Label m_placeholder;
148
149        /**
150         * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_EmptyHandler#init(org.opencms.ui.components.editablegroup.CmsEditableGroup)
151         */
152        public void init(CmsEditableGroup group) {
153
154            String message = CmsVaadinUtils.getMessageText(Messages.GUI_FAVORITES_EMPTY_LIST_PLACEHOLDER_0);
155            m_groupForHandler = group;
156            m_placeholder = new Label();
157            m_placeholder.setContentMode(ContentMode.HTML);
158            String spacer = "<div></div>";
159            String content = "<div>" + CmsEncoder.escapeXml(message) + "</div>";
160            m_placeholder.setValue(spacer + content + spacer);
161            m_placeholder.addStyleName(OpenCmsTheme.BOOKMARKS_PLACEHOLDER);
162            m_placeholder.setHeight("100%");
163
164        }
165
166        /**
167         * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_EmptyHandler#setEmpty(boolean)
168         */
169        public void setEmpty(boolean empty) {
170
171            if (!m_placeholder.isAttached()) {
172                m_groupForHandler.getContainer().addComponent(m_placeholder);
173                m_groupForHandler.getContainer().setExpandRatio(m_placeholder, 1.0f);
174            }
175            m_groupForHandler.getContainer().setHeight(empty ? "100%" : null);
176            m_placeholder.setVisible(empty);
177        }
178
179    }
180
181    /** Logger instance for this class. */
182    private static final Log LOG = CmsLog.getLog(CmsFavoriteDialog.class);
183
184    /** Serial version id. */
185    private static final long serialVersionUID = 1L;
186
187    /** The Add button. */
188    private Button m_addButton;
189
190    /** The Cancel button. */
191    private Button m_cancelButton;
192
193    /** The favorite context. */
194    private I_CmsFavoriteContext m_context;
195
196    /** Current favorite location. */
197    private Optional<CmsFavoriteEntry> m_currentLocation;
198
199    /** The container layout for the favorite widgets. */
200    private VerticalLayout m_favContainer;
201
202    /** Load/save handler for favorites. */
203    private CmsFavoriteDAO m_favDao;
204
205    /** The group for the favorite widgets. */
206    private CmsEditableGroup m_group;
207
208    /** Map of project labels. */
209    private Map<CmsUUID, String> m_projectLabels = new HashMap<>();
210
211    /** Container for the sites, used for their labels. */
212    private IndexedContainer m_sitesContainer;
213
214    /**
215     * Creates a new dialog instance.
216     *
217     * @param context the favorite context
218     * @param favDao the favorite load/save handler
219     */
220    public CmsFavoriteDialog(I_CmsFavoriteContext context, CmsFavoriteDAO favDao)
221    throws CmsException {
222
223        super();
224        m_favDao = favDao;
225        m_context = context;
226        context.setDialog(this);
227        m_sitesContainer = CmsVaadinUtils.getAvailableSitesContainer(A_CmsUI.getCmsObject(), "caption");
228        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
229        List<CmsFavoriteEntry> entries = m_favDao.loadFavorites();
230        m_cancelButton.addClickListener(evt -> m_context.close());
231        m_favContainer.addLayoutClickListener(evt -> {
232            CmsFavoriteEntry entry = getEntry(evt.getChildComponent());
233            if (entry != null) { // may be null when user double clicks on delete icon
234                m_context.openFavorite(entry);
235            }
236        });
237        m_currentLocation = context.getFavoriteForCurrentLocation();
238        m_addButton.setEnabled(m_currentLocation.isPresent());
239        m_addButton.setCaption(CmsVaadinUtils.getMessageText(Messages.GUI_FAVORITES_ADD_BUTTON_0));
240        m_addButton.addClickListener(evt -> onClickAdd());
241        m_favContainer.removeAllComponents();
242        m_group = new CmsEditableGroup(m_favContainer, null, new EmptyHandler());
243        m_group.setAddButtonVisible(false);
244        m_group.setRowBuilder(this);
245        m_group.init();
246
247        for (CmsFavoriteEntry favEntry : entries) {
248            Component favInfo;
249            try {
250                favInfo = createFavInfo(favEntry);
251                m_group.addRow(favInfo);
252            } catch (CmsException e) {
253                LOG.warn(e.getLocalizedMessage(), e);
254            }
255
256        }
257    }
258
259    /**
260     * @see org.opencms.ui.components.editablegroup.CmsEditableGroup.I_RowBuilder#buildRow(org.opencms.ui.components.editablegroup.CmsEditableGroup, com.vaadin.ui.Component)
261     */
262    public CmsFavInfo buildRow(CmsEditableGroup group, Component component) {
263
264        CmsFavInfo info = (CmsFavInfo)component;
265        CmsEditableGroupButtons buttons = new CmsEditableGroupButtons(new SaveAfterChangeActionHandler(info));
266        info.setButtons(buttons);
267        return info;
268    }
269
270    /**
271     * Saves the list of currently displayed favorites.
272     */
273    protected void doSave() {
274
275        List<CmsFavoriteEntry> entries = getEntries();
276        try {
277            m_favDao.saveFavorites(entries);
278        } catch (Exception e) {
279            CmsErrorDialog.showErrorDialog(e);
280        }
281    }
282
283    /**
284     * @see org.opencms.ui.components.CmsBasicDialog#enableMaxHeight()
285     */
286    @Override
287    protected void enableMaxHeight() {
288        // do nothing here, we want to disable the max height mechanism
289    }
290
291    /**
292     * Gets the favorite entries corresponding to the currently displayed favorite widgets.
293     *
294     * @return the list of favorite entries
295     */
296    List<CmsFavoriteEntry> getEntries() {
297
298        List<CmsFavoriteEntry> result = new ArrayList<>();
299        for (I_CmsEditableGroupRow row : m_group.getRows()) {
300            CmsFavoriteEntry entry = ((CmsFavInfo)row).getEntry();
301            result.add(entry);
302        }
303        return result;
304    }
305
306    /**
307     * Creates a favorite widget for a favorite entry.
308     *
309     * @param entry the favorite entry
310     * @return the favorite widget
311     *
312     * @throws CmsException if something goes wrong
313     */
314    private CmsFavInfo createFavInfo(CmsFavoriteEntry entry) throws CmsException {
315
316        String title = "";
317        String subtitle = "";
318        CmsFavInfo result = new CmsFavInfo(entry);
319        CmsObject cms = A_CmsUI.getCmsObject();
320        String project = getProject(cms, entry);
321        String site = getSite(cms, entry);
322        try {
323            CmsUUID idToLoad = entry.getDetailId() != null ? entry.getDetailId() : entry.getStructureId();
324            CmsResource resource = cms.readResource(idToLoad, CmsResourceFilter.IGNORE_EXPIRATION.addRequireVisible());
325            CmsResourceUtil resutil = new CmsResourceUtil(cms, resource);
326            switch (entry.getType()) {
327                case explorerFolder:
328                    title = CmsStringUtil.isEmpty(resutil.getTitle())
329                    ? CmsResource.getName(resource.getRootPath())
330                    : resutil.getTitle();
331                    break;
332                case page:
333                    title = resutil.getTitle();
334                    break;
335            }
336            subtitle = resource.getRootPath();
337            CmsResourceIcon icon = result.getResourceIcon();
338            icon.initContent(resutil, CmsResource.STATE_UNCHANGED, false, false);
339        } catch (CmsException e) {
340            LOG.warn(e.getLocalizedMessage(), e);
341        }
342        result.getTopLine().setValue(title);
343        result.getBottomLine().setValue(subtitle);
344        result.getProjectLabel().setValue(project);
345        result.getSiteLabel().setValue(site);
346
347        return result;
348
349    }
350
351    /**
352     * Gets the favorite entry for a given row.
353     *
354     * @param row the widget used to display the favorite
355     * @return the favorite entry for the widget
356     */
357    private CmsFavoriteEntry getEntry(Component row) {
358
359        if (row instanceof CmsFavInfo) {
360
361            return ((CmsFavInfo)row).getEntry();
362
363        }
364        return null;
365
366    }
367
368    /**
369     * Gets the project name for a favorite entry.
370     *
371     * @param cms the CMS context
372     * @param entry the favorite entry
373     * @return the project name for the favorite entry
374     * @throws CmsException if something goes wrong
375     */
376    private String getProject(CmsObject cms, CmsFavoriteEntry entry) throws CmsException {
377
378        String result = m_projectLabels.get(entry.getProjectId());
379        if (result == null) {
380            result = cms.readProject(entry.getProjectId()).getName();
381            m_projectLabels.put(entry.getProjectId(), result);
382        }
383        return result;
384    }
385
386    /**
387     * Gets the site label for the entry.
388     *
389     * @param cms the current CMS context
390     * @param entry the entry
391     * @return the site label for the entry
392     */
393    private String getSite(CmsObject cms, CmsFavoriteEntry entry) {
394
395        CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(entry.getSiteRoot());
396        Item item = m_sitesContainer.getItem(entry.getSiteRoot());
397        if (item != null) {
398            return (String)(item.getItemProperty("caption").getValue());
399        }
400        String result = entry.getSiteRoot();
401        if (site != null) {
402            if (!CmsStringUtil.isEmpty(site.getTitle())) {
403                result = site.getTitle();
404            }
405        }
406        return result;
407    }
408
409    /**
410     * The click handler for the add button.
411     */
412    private void onClickAdd() {
413
414        if (m_currentLocation.isPresent()) {
415            CmsFavoriteEntry entry = m_currentLocation.get();
416            List<CmsFavoriteEntry> entries = getEntries();
417            entries.add(entry);
418            try {
419                m_favDao.saveFavorites(entries);
420            } catch (Exception e) {
421                CmsErrorDialog.showErrorDialog(e);
422            }
423            m_context.close();
424        }
425    }
426
427}