View Javadoc
1   /**
2    * This file Copyright (c) 2010-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.vaadin.gwt.client.actionbar.widget;
35  
36  import info.magnolia.ui.vaadin.gwt.client.actionbar.event.ActionTriggerEvent;
37  import info.magnolia.ui.vaadin.gwt.client.actionbar.shared.ActionbarItem;
38  import info.magnolia.ui.vaadin.gwt.client.actionbar.shared.ActionbarSection;
39  
40  import java.util.Collection;
41  import java.util.LinkedHashMap;
42  import java.util.Map;
43  import java.util.logging.Logger;
44  
45  import com.google.gwt.user.client.DOM;
46  import com.google.gwt.user.client.Element;
47  import com.google.gwt.user.client.Event;
48  import com.google.gwt.user.client.ui.ComplexPanel;
49  import com.google.gwt.user.client.ui.FlowPanel;
50  import com.google.gwt.user.client.ui.Image;
51  import com.google.gwt.user.client.ui.Widget;
52  import com.google.web.bindery.event.shared.EventBus;
53  import com.googlecode.mgwt.dom.client.event.touch.TouchStartEvent;
54  import com.googlecode.mgwt.dom.client.event.touch.TouchStartHandler;
55  import com.googlecode.mgwt.ui.client.widget.touch.TouchDelegate;
56  
57  /**
58   * The Class VActionbarViewImpl, GWT implementation for the VActionbarView interface.
59   */
60  public class ActionbarWidgetViewImpl extends ComplexPanel implements ActionbarWidgetView, ActionTriggerEvent.Handler {
61  
62      private static final Logger log = Logger.getLogger(ActionbarWidgetViewImpl.class.getName());
63  
64      public static final String CLASSNAME = "v-actionbar";
65  
66      public static final String CLASSNAME_TOGGLE = "v-actionbar-toggle";
67  
68      private final Element root = DOM.createElement("section");
69  
70      private final FlowPanel toggleButton = new FlowPanel(); // Must be a widget so that it can capture events
71  
72      private final Element toggleButtonIcon = DOM.createElement("span");
73  
74      private final EventBus eventBus;
75  
76      private Presenter presenter;
77  
78      private int tabletRow = -1; // Used to assign rows and columns to each action item
79  
80      private int tabletColumn = 0;
81  
82      private boolean isToggledOpen = false;
83  
84      private final TouchDelegate delegate = new TouchDelegate(toggleButton);
85  
86      private final Map<String, ActionbarSectionWidget> sections = new LinkedHashMap<String, ActionbarSectionWidget>();
87  
88      public ActionbarWidgetViewImpl(final EventBus eventBus, Presenter presenter) {
89          setElement(root);
90          addStyleName(CLASSNAME);
91  
92          this.presenter = presenter;
93          this.eventBus = eventBus;
94          this.eventBus.addHandler(ActionTriggerEvent.TYPE, this);
95  
96          createToggleControl();
97  
98          isToggledOpen = !presenter.isDeviceTablet();
99          actualizeToggleState(isToggledOpen);
100     }
101 
102 
103     private void createToggleControl() {
104 
105         toggleButton.addStyleName(CLASSNAME_TOGGLE);
106         add(toggleButton, root);
107 
108         toggleButtonIcon.addClassName("v-actionbar-toggle-icon");
109         toggleButton.getElement().appendChild(toggleButtonIcon);
110 
111         DOM.sinkEvents(toggleButton.getElement(), Event.TOUCHEVENTS);
112         delegate.addTouchStartHandler(new TouchStartHandler() {
113             @Override
114             public void onTouchStart(TouchStartEvent event) {
115                 isToggledOpen = !isToggledOpen;
116                 presenter.setOpened(isToggledOpen);
117             }
118         });
119     }
120 
121     /*
122      * Actions positions in tablet mode are set via row_x and col_x classes.
123      * These need to be updated anytime a section is hidden or shown.
124      */
125     @Override
126     public void refreshActionsPositionsTablet() {
127 
128         if (!presenter.isDeviceTablet()) {
129             return;
130         }
131 
132         tabletRow = -1; // Used to assign rows and columns to each action item
133         tabletColumn = 0;
134 
135         for (final ActionbarSectionWidget section : sections.values()) {
136 
137             // if section is visible - then update rows & cols
138             if (section.isVisible()) {
139 
140                 for (final VActionbarGroup group : section.getGroups().values()) {
141 
142                     tabletColumn = 0;
143                     tabletRow++;
144 
145                     for (ActionbarItemWidget action : group.getActions()) {
146                         // Add a flyout indicator if this is the first action and there are other
147                         // actions
148                         if (group.getNumActions() > 1) {
149                             if (tabletColumn == 0) {
150                                 action.addStyleName("flyout");
151                             }
152                         } else {
153                             action.removeStyleName("flyout");
154                         }
155                         ((VActionbarItemTablet) action).setRow(tabletRow);
156                         ((VActionbarItemTablet) action).setColumn(tabletColumn);
157                         tabletColumn++;
158                     }
159                 }
160             }
161         }
162         setToggleButtonHeights(tabletRow);
163     }
164 
165     /**
166      * Actualize the state of the actionbar 'openness' by setting classes on html elements.
167      */
168     private void actualizeToggleState(boolean isOpen) {
169         if (isOpen) {
170             toggleButtonIcon.addClassName("open");
171         } else {
172             toggleButtonIcon.removeClassName("open");
173         }
174         if (presenter.isDeviceTablet()) {
175             for (final ActionbarSectionWidget section : sections.values()) {
176                 for (final VActionbarGroup group : section.getGroups().values()) {
177                     group.setOpenHorizontally(isOpen);
178                 }
179             }
180         }
181     }
182 
183     @Override
184     public Map<String, ActionbarSectionWidget> getSections() {
185         return sections;
186     }
187 
188     @Override
189     public void setPresenter(final Presenter presenter) {
190         this.presenter = presenter;
191     }
192 
193     public void addSection(ActionbarSection sectionParams) {
194         ActionbarSectionWidget section = new ActionbarSectionWidget(sectionParams);
195         sections.put(sectionParams.getName(), section);
196         add(section, root);
197     }
198 
199     public void addAction(ActionbarItem actionParams, String sectionName) {
200         ActionbarSectionWidget section = sections.get(sectionName);
201         if (section != null) {
202             VActionbarGroup group = section.getGroups().get(actionParams.getGroupName());
203             if (group == null) {
204                 tabletColumn = 0;
205                 tabletRow++;
206                 group = new VActionbarGroup(actionParams.getGroupName());
207                 section.addGroup(group);
208                 setToggleButtonHeights(tabletRow);
209             }
210 
211             ActionbarItemWidget action;
212             if (presenter.isDeviceTablet()) {
213                 action = new VActionbarItemTablet(actionParams, group, eventBus);
214                 ((VActionbarItemTablet) action).setRow(tabletRow);
215                 ((VActionbarItemTablet) action).setColumn(tabletColumn);
216                 tabletColumn++;
217             } else {
218                 action = new ActionbarItemWidget(actionParams, group, eventBus);
219             }
220             group.addAction(action);
221         }
222     }
223 
224     /**
225      * For tablet mode, position these buttons at the bottom of the button stack.
226      */
227     private void setToggleButtonHeights(int tabletRow) {
228         // Position toggleButton at bottom of stack.
229         toggleButton.setStyleName(CLASSNAME_TOGGLE + " row-" + (tabletRow + 1));
230     }
231 
232     @Override
233     public void onActionTriggered(ActionTriggerEvent event) {
234         ActionbarItemWidget action = event.getSource();
235         ActionbarSectionWidget section = (ActionbarSectionWidget) action.getParent().getParent();
236         presenter.triggerAction(section.getName() + ":" + action.getName());
237     }
238 
239     @Override
240     public void setSections(Collection<ActionbarSection> newSections) {
241         for (final ActionbarSectionWidget section : this.sections.values()) {
242             remove(section);
243         }
244         sections.clear();
245         for (final ActionbarSection section : newSections) {
246             addSection(section);
247             for (final String actionName : section.getActionOrder()) {
248                 ActionbarItem action = section.getActions().get(actionName);
249                 if (action.getIconFontId() == null) {
250                     action.setResourceUrl(presenter.getIconResourceURL(action.getName()));
251                 }
252                 addAction(action, section.getName());
253             }
254         }
255         updateLayout();
256         refreshActionsPositionsTablet();
257     }
258 
259     @Override
260     public void setVisibleSections(Collection<ActionbarSection> visibleSections) {
261         for (final ActionbarSectionWidget section : sections.values()) {
262             section.setVisible(visibleSections.contains(section.getData()));
263         }
264         updateLayout();
265         refreshActionsPositionsTablet();
266     }
267 
268     @Override
269     public void setDisabledActions(Collection<ActionbarItem> disabledActions) {
270         for (final ActionbarSectionWidget section : sections.values()) {
271             for (final VActionbarGroup group : section.getGroups().values()) {
272                 for (final ActionbarItemWidget action : group.getActions()) {
273                     action.setEnabled(!disabledActions.contains(action.getData()));
274                 }
275             }
276         }
277     }
278 
279     @Override
280     public boolean isOpen() {
281         return isToggledOpen;
282     }
283 
284     @Override
285     public void setOpen(boolean isOpen) {
286         actualizeToggleState(isToggledOpen);
287         if (isToggledOpen != isOpen) {
288             presenter.forceLayout();
289             updateLayout();
290         }
291     }
292 
293     @Override
294     public void setSectionPreview(String sectionName, String previewUrl) {
295         ActionbarSectionWidget sectionWidget = sections.get(sectionName);
296         if (sectionWidget != null) {
297             sectionWidget.addStyleName("preview");
298             sectionWidget.setPreview(new Image(previewUrl));
299         }
300     }
301 
302     @Override
303     public void updateLayout() {
304         // sections margin
305         final int MARGIN_TOP = 35;
306 
307         int availableHeight = getOffsetHeight();
308         if (sections.containsKey("preview") && sections.get("preview").isVisible() && isToggledOpen) {
309             availableHeight -= 200;
310         }
311 
312         int actualHeight = 0;
313         for (ActionbarSectionWidget section : sections.values()) {
314             if (!section.getName().equals("preview") && section.isVisible()) {
315                 section.getElement().getStyle().clearProperty("maxHeight");
316                 actualHeight += section.getOffsetHeight() + MARGIN_TOP; // add margin
317             }
318         }
319         log.fine("actualHeight/availableHeight: " + actualHeight + "/" + availableHeight);
320 
321         // rewind from bottom-up to shrink sections
322         int i = getWidgetCount() - 1;
323         int minimumHeight = 75;
324         while (actualHeight > availableHeight && i >= 0) {
325             log.fine("rewinding widget list, current height diff: " + (actualHeight - availableHeight) + " (i=" + i + ")");
326             Widget widget = getWidget(i);
327             if (widget instanceof ActionbarSectionWidget && widget.isVisible()) {
328                 String sectionName = ((ActionbarSectionWidget) widget).getName();
329                 if (!sectionName.equals("preview")) {
330                     int currentHeight = widget.getOffsetHeight() + MARGIN_TOP;
331                     if (currentHeight > minimumHeight) {
332                         int newHeight = (actualHeight - availableHeight > currentHeight - minimumHeight) ? minimumHeight : currentHeight + availableHeight - actualHeight;
333                         log.fine(sectionName + " currentHeight => newHeight: " + currentHeight + "=>" + (newHeight - MARGIN_TOP));
334                         widget.getElement().getStyle().setPropertyPx("maxHeight", newHeight - MARGIN_TOP); // account for padding or margin;
335                         actualHeight -= (currentHeight - newHeight);
336                     }
337                 }
338             }
339             i--;
340         }
341     }
342 
343 }