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.property;
029
030import org.opencms.gwt.client.I_CmsDescendantResizeHandler;
031import org.opencms.gwt.client.Messages;
032import org.opencms.gwt.client.ui.CmsFieldSet;
033import org.opencms.gwt.client.ui.CmsListItemWidget;
034import org.opencms.gwt.client.ui.CmsScrollPanel;
035import org.opencms.gwt.client.ui.CmsTabbedPanel;
036import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle;
037import org.opencms.gwt.client.ui.input.CmsTextArea;
038import org.opencms.gwt.client.ui.input.CmsTextBox;
039import org.opencms.gwt.client.ui.input.I_CmsFormField;
040import org.opencms.gwt.client.ui.input.I_CmsFormWidget;
041import org.opencms.gwt.client.ui.input.form.A_CmsFormFieldPanel;
042import org.opencms.gwt.client.ui.input.form.CmsFormDialog;
043import org.opencms.gwt.client.ui.input.form.CmsInfoBoxFormFieldPanel;
044import org.opencms.gwt.client.util.CmsDomUtil;
045
046import org.opencms.gwt.shared.CmsListInfoBean;
047import org.opencms.util.CmsStringUtil;
048
049import java.util.ArrayList;
050import java.util.Collection;
051import java.util.Iterator;
052import java.util.LinkedHashMap;
053import java.util.List;
054import java.util.Map;
055import java.util.Set;
056
057import com.google.common.collect.ArrayListMultimap;
058import com.google.common.collect.Multimap;
059import com.google.common.collect.Sets;
060import com.google.gwt.core.client.GWT;
061import com.google.gwt.dom.client.Style.Unit;
062import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
063import com.google.gwt.event.logical.shared.SelectionEvent;
064import com.google.gwt.event.logical.shared.SelectionHandler;
065import com.google.gwt.user.client.Timer;
066import com.google.gwt.user.client.ui.FlowPanel;
067import com.google.gwt.user.client.ui.Panel;
068import com.google.gwt.user.client.ui.Widget;
069
070/**
071 * A tabbed form field container widget.<p>
072 *
073 * @since 8.0.0
074 */
075public class CmsPropertyPanel extends A_CmsFormFieldPanel {
076
077    /** Layout data key. */
078    public static final String LD_DISPLAY_VALUE = "displayValue";
079
080    /** Layout data key. */
081    public static final String LD_GROUP = "group";
082
083    /** Layout data key. */
084    public static final String LD_PROPERTY = "property";
085
086    /** Tab id for the "individual" tab. */
087    public static final String TAB_INDIVIDUAL = "individual";
088
089    /** Tab id for the "shared" tab. */
090    public static final String TAB_SHARED = "shared";
091
092    /** Tab id for the "simple" tab. */
093    public static final String TAB_SIMPLE = "simple";
094
095    /** The tab panel. */
096    protected CmsTabbedPanel<CmsScrollPanel> m_tabPanel = new CmsTabbedPanel<CmsScrollPanel>();
097
098    /** Multimap of fields by field group. */
099    private Multimap<String, I_CmsFormField> m_fieldsByGroup = ArrayListMultimap.create();
100
101    /** Map from group/tab names to corresponding panels. */
102    private Map<String, Panel> m_groups = new LinkedHashMap<String, Panel>();
103
104    /** Set of fields which should be displayed at the top of the "individual" tab. */
105    private Set<String> m_individualDisplay = Sets.newHashSet();
106
107    /** The "individual" tab. */
108    private FlowPanel m_individualTab = new FlowPanel();
109
110    /** The tab wrapper for the individual tab. */
111    private FlowPanel m_individualTabWrapper = new FlowPanel();
112
113    /** Name of property which should be focused, used while rendering the extended tabs. */
114    private String m_markedProperty;
115
116    /** Set of fields which should be displayed at the top of the "shared" tab. */
117    private Set<String> m_sharedDisplay = Sets.newHashSet();
118
119    /** The "shared" tab. */
120    private FlowPanel m_sharedTab = new FlowPanel();
121
122    /** The tab wrapper for the shared tab. */
123    private FlowPanel m_sharedTabWrapper = new FlowPanel();
124
125    /** True if the "shared" tab should be shown. */
126    private boolean m_showShared;
127
128    /** The "simple" tab. */
129    private FlowPanel m_simpleTab = new FlowPanel();
130
131    /** The tab wrapper for the simple tab. */
132    private FlowPanel m_simpleTabWrapper = new FlowPanel();
133
134    /**
135     * Creates a new instance.<p>
136     *
137     * @param showShared true if the "shared" tab should be shown
138     * @param info the bean to use for displaying the info item
139     */
140    public CmsPropertyPanel(boolean showShared, CmsListInfoBean info) {
141
142        m_infoWidget = createListItemWidget(info);
143        m_simpleTabWrapper.add(m_infoWidget);
144        m_simpleTabWrapper.add(m_simpleTab);
145        m_simpleTab.addStyleName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll());
146        m_simpleTab.addStyleName(
147            org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.propertiesCss().vfsModeSimplePropertiesBox());
148        m_simpleTab.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().formGradientBackground());
149        m_sharedTabWrapper.add(createListItemWidget(info));
150        m_sharedTabWrapper.add(m_sharedTab);
151
152        m_individualTabWrapper.add(createListItemWidget(info));
153        m_individualTabWrapper.add(m_individualTab);
154        m_groups.put(TAB_SIMPLE, m_simpleTab);
155        m_groups.put(TAB_SHARED, m_sharedTab);
156        m_groups.put(TAB_INDIVIDUAL, m_individualTab);
157        CmsScrollPanel scrollPanel = GWT.create(CmsScrollPanel.class);
158        scrollPanel.setWidget(m_simpleTabWrapper);
159        m_tabPanel.add(scrollPanel, Messages.get().key(Messages.GUI_PROPERTY_TAB_SIMPLE_0));
160        m_showShared = showShared;
161        if (m_showShared) {
162            scrollPanel = GWT.create(CmsScrollPanel.class);
163            scrollPanel.setWidget(m_individualTabWrapper);
164            m_tabPanel.add(scrollPanel, Messages.get().key(Messages.GUI_PROPERTY_TAB_STRUCTURE_0));
165            scrollPanel = GWT.create(CmsScrollPanel.class);
166            scrollPanel.setWidget(m_sharedTabWrapper);
167            m_tabPanel.add(scrollPanel, Messages.get().key(Messages.GUI_PROPERTY_TAB_RESOURCE_0));
168        } else {
169            scrollPanel = GWT.create(CmsScrollPanel.class);
170            scrollPanel.setWidget(m_individualTabWrapper);
171            m_tabPanel.add(scrollPanel, Messages.get().key(Messages.GUI_PROPERTY_TAB_COMPLETE_0));
172        }
173        initWidget(m_tabPanel);
174        addStyleName(org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.propertiesCss().propertyPanel());
175        m_tabPanel.addSelectionHandler(new SelectionHandler<Integer>() {
176
177            public void onSelection(SelectionEvent<Integer> event) {
178
179                Widget selectedTab = m_tabPanel.getWidget(event.getSelectedItem().intValue());
180                if (selectedTab instanceof I_CmsDescendantResizeHandler) {
181                    ((I_CmsDescendantResizeHandler)selectedTab).onResizeDescendant();
182                }
183            }
184        });
185    }
186
187    /**
188     * Adds the {@link BeforeSelectionHandler} for the tab panel.<p>
189     *
190     * @param handler the pre-selection handler
191     */
192    public void addBeforeSelectionHandler(BeforeSelectionHandler<Integer> handler) {
193
194        m_tabPanel.addBeforeSelectionHandler(handler);
195    }
196
197    /**
198     * Clears the tab with the given id.<p>
199     *
200     * @param tabId the id of the tab to clear
201     */
202    public void clearTab(String tabId) {
203
204        m_groups.get(tabId).clear();
205    }
206
207    /**
208     * @see org.opencms.gwt.client.ui.input.form.A_CmsFormFieldPanel#getDefaultGroup()
209     */
210    @Override
211    public String getDefaultGroup() {
212
213        return TAB_SIMPLE;
214    }
215
216    /**
217     * Renders a extended tab.<p>
218     *
219     * @param fields the fields to add
220     * @param tab the tab
221     */
222    public void renderExtendedTab(Collection<I_CmsFormField> fields, FlowPanel tab) {
223
224        List<CmsFieldSet> result = new ArrayList<CmsFieldSet>();
225
226        tab.clear();
227
228        String used = Messages.get().key(Messages.GUI_PROPERTY_BLOCK_USED_0);
229        CmsFieldSet usedFieldSet = new CmsFieldSet();
230        usedFieldSet.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().formGradientBackground());
231        usedFieldSet.setLegend(used);
232        usedFieldSet.setAnimationDuration(50);
233
234        String unused = Messages.get().key(Messages.GUI_PROPERTY_BLOCK_UNUSED_0);
235        CmsFieldSet unusedFieldSet = new CmsFieldSet();
236        unusedFieldSet.addStyleName(I_CmsInputLayoutBundle.INSTANCE.inputCss().formGradientBackground());
237        unusedFieldSet.setOpen(false);
238        unusedFieldSet.setLegend(unused);
239        unusedFieldSet.setAnimationDuration(50);
240        boolean reopen = false;
241        final String currentMarkedProperty = m_markedProperty;
242        m_markedProperty = null;
243        for (I_CmsFormField field : fields) {
244            if (isTop(field)) {
245                usedFieldSet.addContent(createRow(field));
246            } else {
247                if ((currentMarkedProperty != null)
248                    && field.getLayoutData().get(LD_PROPERTY).equals(currentMarkedProperty)) {
249                    reopen = true;
250                }
251                unusedFieldSet.addContent(createRow(field));
252            }
253        }
254        if (reopen) {
255            unusedFieldSet.setOpen(true);
256        }
257
258        if (usedFieldSet.getWidgetCount() > 0) {
259            result.add(usedFieldSet);
260        }
261        if (unusedFieldSet.getWidgetCount() > 0) {
262            result.add(unusedFieldSet);
263        }
264
265        Iterator<CmsFieldSet> iter = result.iterator();
266        while (iter.hasNext()) {
267            CmsFieldSet fieldSet = iter.next();
268            if (iter.hasNext()) {
269                fieldSet.getElement().getStyle().setMarginTop(9, Unit.PX);
270            } else {
271                fieldSet.getElement().getStyle().setMarginTop(15, Unit.PX);
272            }
273            tab.add(fieldSet);
274        }
275        CmsDomUtil.resizeAncestor(tab.getParent());
276    }
277
278    /**
279     * @see org.opencms.gwt.client.ui.input.form.A_CmsFormFieldPanel#renderFields(java.util.Collection)
280     */
281    @Override
282    public void renderFields(Collection<I_CmsFormField> fields) {
283
284        m_fieldsByGroup = getFieldsByGroup(fields);
285        Collection<I_CmsFormField> simpleTabFields = m_fieldsByGroup.get(TAB_SIMPLE);
286        Collection<I_CmsFormField> individualTabFields = m_fieldsByGroup.get(TAB_INDIVIDUAL);
287        Collection<I_CmsFormField> sharedTabfields = m_fieldsByGroup.get(TAB_SHARED);
288
289        // process simple tab
290        renderSimpleTab(simpleTabFields);
291
292        // process individual tab
293        m_individualDisplay = preprocessFields(individualTabFields);
294        // process shared tab
295        if (m_showShared) {
296            m_sharedDisplay = preprocessFields(sharedTabfields);
297        }
298    }
299
300    /**
301     * @see org.opencms.gwt.client.ui.input.form.A_CmsFormFieldPanel#rerenderFields(java.lang.String, java.util.Collection)
302     */
303    @Override
304    public void rerenderFields(String tab, Collection<I_CmsFormField> fields) {
305
306        m_fieldsByGroup.removeAll(tab);
307        m_fieldsByGroup.putAll(tab, fields);
308
309        m_individualDisplay = preprocessFields(m_fieldsByGroup.get(TAB_INDIVIDUAL));
310        m_sharedDisplay = preprocessFields(m_fieldsByGroup.get(TAB_SHARED));
311
312        if (tab.equals(TAB_SIMPLE)) {
313            m_simpleTab.clear();
314            renderSimpleTab(fields);
315        } else {
316            if (tab.equals(TAB_INDIVIDUAL)) {
317                renderExtendedTab(fields, m_individualTab);
318            } else if (tab.equals(TAB_SHARED)) {
319                renderExtendedTab(fields, m_sharedTab);
320            }
321        }
322    }
323
324    /**
325     * Tries to restore the active field data.<p>
326     *
327     * @param fieldDataToBeRestored the field data which should be restored (may be null, in which case this method does nothing)
328     */
329    public void tryToRestoreFieldData(CmsActiveFieldData fieldDataToBeRestored) {
330
331        if (fieldDataToBeRestored == null) {
332            return;
333        }
334        m_markedProperty = null;
335        final String propName = fieldDataToBeRestored.getProperty();
336        final String tabName = fieldDataToBeRestored.getTab();
337        m_markedProperty = propName;
338        final int tabIndex;
339        if (TAB_INDIVIDUAL.equals(tabName)) {
340            tabIndex = 1;
341        } else if (TAB_SHARED.equals(tabName)) {
342            tabIndex = 2;
343        } else {
344            tabIndex = 0;
345        }
346
347        Timer timer = new Timer() {
348
349            @Override
350            public void run() {
351
352                if ((tabIndex >= 0) && (tabIndex < m_tabPanel.getTabCount())) {
353                    I_CmsFormField markedField = null;
354                    m_tabPanel.selectTab(tabIndex);
355                    @SuppressWarnings("synthetic-access")
356                    Collection<I_CmsFormField> fieldsForTab = m_fieldsByGroup.get(tabName);
357                    if ((fieldsForTab != null) && !fieldsForTab.isEmpty()) {
358                        for (I_CmsFormField currentField : fieldsForTab) {
359                            if (currentField.getLayoutData().get(LD_PROPERTY).equals(propName)) {
360                                markedField = currentField;
361                                break;
362                            }
363                        }
364                    }
365                    if (markedField != null) {
366                        final I_CmsFormField constMarkedField = markedField;
367                        Timer timer2 = new Timer() {
368
369                            @Override
370                            @SuppressWarnings("synthetic-access")
371                            public void run() {
372
373                                focusField(constMarkedField);
374                            }
375
376                        };
377                        timer2.schedule(1);
378                    }
379                }
380
381            }
382
383            private void focusField(final I_CmsFormField constMarkedField) {
384
385                I_CmsFormWidget widget = constMarkedField.getWidget();
386                if (widget instanceof CmsTextBox) {
387                    CmsTextBox box = (CmsTextBox)widget;
388                    box.selectAll();
389                    box.setFocus(true);
390                } else if (widget instanceof CmsTextArea) {
391                    CmsTextArea textarea = ((CmsTextArea)widget);
392                    textarea.selectAll();
393                    textarea.setFocus(true);
394                }
395            }
396        };
397        timer.schedule(1);
398
399    }
400
401    /**
402     * Creates a list item widget from a list info bean.<p>
403     *
404     * @param info the list info bean
405     *
406     * @return the list item widget
407     */
408    protected CmsListItemWidget createListItemWidget(CmsListInfoBean info) {
409
410        CmsListItemWidget result = new CmsListItemWidget(info);
411
412        result.truncate(CmsInfoBoxFormFieldPanel.TM_INFOBOX, CmsFormDialog.STANDARD_DIALOG_WIDTH - 50);
413        return result;
414    }
415
416    /**
417     * Returns the tabbed panel.<p>
418     *
419     * @return the tabbed panel
420     */
421    protected CmsTabbedPanel<CmsScrollPanel> getTabPanel() {
422
423        return m_tabPanel;
424    }
425
426    /**
427     * Partitions a collection of fields by group.<p>
428     *
429     * @param fields the collection of fields
430     *
431     * @return a multimap from groups to form fields
432     */
433    private Multimap<String, I_CmsFormField> getFieldsByGroup(Collection<I_CmsFormField> fields) {
434
435        Multimap<String, I_CmsFormField> result = ArrayListMultimap.create();
436        for (I_CmsFormField field : fields) {
437            String group = field.getLayoutData().get(LD_GROUP);
438            result.put(group, field);
439        }
440        return result;
441    }
442
443    /**
444     * Returns true if the field should be displayed at the top of both the "individual" and "shared" tabs.<p>
445     *
446     * @param field the field to test
447     *
448     * @return true if the field should be displayed at the top
449     */
450    private boolean isTop(I_CmsFormField field) {
451
452        String propName = field.getLayoutData().get(LD_PROPERTY);
453        return m_individualDisplay.contains(propName) || m_sharedDisplay.contains(propName);
454    }
455
456    /**
457     * Preprocesses the fields to find out which fields need to displayed at the top/bottom later.<p>
458     *
459     * @param fields the fields
460     *
461     * @return the set of property names of the preprocessed fields
462     */
463    private Set<String> preprocessFields(Collection<I_CmsFormField> fields) {
464
465        Set<String> displaySet = Sets.newHashSet();
466        for (I_CmsFormField field : fields) {
467            boolean hasValue = !CmsStringUtil.isEmpty(field.getWidget().getApparentValue());
468            if (hasValue || Boolean.TRUE.toString().equals(field.getLayoutData().get(LD_DISPLAY_VALUE))) {
469                String propName = field.getLayoutData().get(LD_PROPERTY);
470                displaySet.add(propName);
471            }
472        }
473        return displaySet;
474    }
475
476    /**
477     * Renders the simple tab.<p>
478     *
479     * @param fields the fields to render
480     */
481    private void renderSimpleTab(Collection<I_CmsFormField> fields) {
482
483        for (I_CmsFormField field : fields) {
484            m_simpleTab.add(createRow(field));
485        }
486        CmsDomUtil.resizeAncestor(m_simpleTab.getParent());
487    }
488}