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.event.logical.shared.ResizeEvent;
46  import com.google.gwt.event.logical.shared.ResizeHandler;
47  import com.google.gwt.event.shared.HandlerRegistration;
48  import com.google.gwt.media.client.Audio;
49  import com.google.gwt.media.client.Video;
50  import com.google.gwt.user.client.DOM;
51  import com.google.gwt.user.client.Element;
52  import com.google.gwt.user.client.Event;
53  import com.google.gwt.user.client.Window;
54  import com.google.gwt.user.client.ui.ComplexPanel;
55  import com.google.gwt.user.client.ui.FlowPanel;
56  import com.google.gwt.user.client.ui.Image;
57  import com.google.gwt.user.client.ui.Widget;
58  import com.google.web.bindery.event.shared.EventBus;
59  import com.googlecode.mgwt.dom.client.event.touch.TouchStartEvent;
60  import com.googlecode.mgwt.dom.client.event.touch.TouchStartHandler;
61  import com.googlecode.mgwt.ui.client.widget.touch.TouchDelegate;
62  
63  /**
64   * The Class VActionbarViewImpl, GWT implementation for the VActionbarView interface.
65   */
66  public class ActionbarWidgetViewImpl extends ComplexPanel implements ActionbarWidgetView, ActionTriggerEvent.Handler, ResizeHandler {
67  
68      private static final Logger log = Logger.getLogger(ActionbarWidgetViewImpl.class.getName());
69  
70      private static final int BREAK_POINT_1 = 1200;
71      private static final int BREAK_POINT_2 = 1600;
72  
73      public static final String CLASSNAME = "v-actionbar";
74  
75      public static final String CLASSNAME_TOGGLE = "v-actionbar-toggle";
76  
77      private final Element root = DOM.createElement("section");
78  
79      private final FlowPanel toggleButton = new FlowPanel(); // Must be a widget so that it can capture events
80  
81      private final Element toggleButtonIcon = DOM.createElement("span");
82  
83      private final EventBus eventBus;
84  
85      private final HandlerRegistration registration;
86  
87      private Presenter presenter;
88  
89      private int tabletRow = -1; // Used to assign rows and columns to each action item
90  
91      private int tabletColumn = 0;
92  
93      private boolean isToggledOpen = false;
94  
95      private final TouchDelegateient/widget/touch/TouchDelegate.html#TouchDelegate">TouchDelegate delegate = new TouchDelegate(toggleButton);
96  
97      private final Map<String, ActionbarSectionWidget> sections = new LinkedHashMap<String, ActionbarSectionWidget>();
98  
99      public ActionbarWidgetViewImpl(final EventBus eventBus, Presenter presenter) {
100         setElement(root);
101         addStyleName(CLASSNAME);
102 
103         this.presenter = presenter;
104         this.eventBus = eventBus;
105         this.eventBus.addHandler(ActionTriggerEvent.TYPE, this);
106 
107         createToggleControl();
108 
109         isToggledOpen = !presenter.isDeviceTablet();
110         actualizeToggleState(isToggledOpen);
111 
112         this.registration = Window.addResizeHandler(this);
113     }
114 
115 
116     private void createToggleControl() {
117 
118         toggleButton.addStyleName(CLASSNAME_TOGGLE);
119         add(toggleButton, root);
120 
121         toggleButtonIcon.addClassName("v-actionbar-toggle-icon");
122         toggleButton.getElement().appendChild(toggleButtonIcon);
123 
124         DOM.sinkEvents(toggleButton.getElement(), Event.TOUCHEVENTS);
125         delegate.addTouchStartHandler(new TouchStartHandler() {
126             @Override
127             public void onTouchStart(TouchStartEvent event) {
128                 isToggledOpen = !isToggledOpen;
129                 presenter.setOpened(isToggledOpen);
130             }
131         });
132     }
133 
134     @Override
135     protected void onDetach() {
136         super.onDetach();
137         this.registration.removeHandler();
138     }
139 
140     /*
141      * Actions positions in tablet mode are set via row_x and col_x classes.
142      * These need to be updated anytime a section is hidden or shown.
143      */
144     @Override
145     public void refreshActionsPositionsTablet() {
146 
147         if (!presenter.isDeviceTablet()) {
148             return;
149         }
150 
151         tabletRow = -1; // Used to assign rows and columns to each action item
152         tabletColumn = 0;
153 
154         for (final ActionbarSectionWidget section : sections.values()) {
155 
156             // if section is visible - then update rows & cols
157             if (section.isVisible()) {
158 
159                 for (final VActionbarGroup group : section.getGroups().values()) {
160 
161                     tabletColumn = 0;
162                     tabletRow++;
163 
164                     for (ActionbarItemWidget action : group.getActions()) {
165                         // Add a flyout indicator if this is the first action and there are other
166                         // actions
167                         if (group.getNumActions() > 1) {
168                             if (tabletColumn == 0) {
169                                 action.addStyleName("flyout");
170                             }
171                         } else {
172                             action.removeStyleName("flyout");
173                         }
174                         ((VActionbarItemTablet) action).setRow(tabletRow);
175                         ((VActionbarItemTablet) action).setColumn(tabletColumn);
176                         tabletColumn++;
177                     }
178                 }
179             }
180         }
181         setToggleButtonHeights(tabletRow);
182     }
183 
184     /**
185      * Actualize the state of the actionbar 'openness' by setting classes on html elements.
186      */
187     private void actualizeToggleState(boolean isOpen) {
188         if (isOpen) {
189             toggleButtonIcon.addClassName("open");
190         } else {
191             toggleButtonIcon.removeClassName("open");
192         }
193         if (presenter.isDeviceTablet()) {
194             for (final ActionbarSectionWidget section : sections.values()) {
195                 for (final VActionbarGroup group : section.getGroups().values()) {
196                     group.setOpenHorizontally(isOpen);
197                 }
198             }
199         }
200     }
201 
202     @Override
203     public Map<String, ActionbarSectionWidget> getSections() {
204         return sections;
205     }
206 
207     @Override
208     public void setPresenter(final Presenter presenter) {
209         this.presenter = presenter;
210     }
211 
212     public void addSection(ActionbarSection sectionParams) {
213         ActionbarSectionWidgett/actionbar/widget/ActionbarSectionWidget.html#ActionbarSectionWidget">ActionbarSectionWidget section = new ActionbarSectionWidget(sectionParams);
214         sections.put(sectionParams.getName(), section);
215         add(section, root);
216     }
217 
218     public void addAction(ActionbarItem actionParams, String sectionName) {
219         ActionbarSectionWidget section = sections.get(sectionName);
220         if (section != null) {
221             VActionbarGroup group = section.getGroups().get(actionParams.getGroupName());
222             if (group == null) {
223                 tabletColumn = 0;
224                 tabletRow++;
225                 group = new VActionbarGroup(actionParams.getGroupName());
226                 section.addGroup(group);
227                 setToggleButtonHeights(tabletRow);
228             }
229 
230             ActionbarItemWidget action;
231             if (presenter.isDeviceTablet()) {
232                 action = new VActionbarItemTablet(actionParams, group, eventBus);
233                 ((VActionbarItemTablet) action).setRow(tabletRow);
234                 ((VActionbarItemTablet) action).setColumn(tabletColumn);
235                 tabletColumn++;
236             } else {
237                 action = new ActionbarItemWidget(actionParams, group, eventBus);
238             }
239             group.addAction(action);
240         }
241     }
242 
243     /**
244      * For tablet mode, position these buttons at the bottom of the button stack.
245      */
246     private void setToggleButtonHeights(int tabletRow) {
247         // Position toggleButton at bottom of stack.
248         toggleButton.setStyleName(CLASSNAME_TOGGLE + " row-" + (tabletRow + 1));
249     }
250 
251     @Override
252     public void onActionTriggered(ActionTriggerEvent event) {
253         ActionbarItemWidget action = event.getSource();
254         ActionbarSectionWidget/../../info/magnolia/ui/vaadin/gwt/client/actionbar/widget/ActionbarSectionWidget.html#ActionbarSectionWidget">ActionbarSectionWidget section = (ActionbarSectionWidget) action.getParent().getParent();
255         presenter.triggerAction(section.getName() + ":" + action.getName());
256     }
257 
258     @Override
259     public void setSections(Collection<ActionbarSection> newSections) {
260         for (final ActionbarSectionWidget section : this.sections.values()) {
261             remove(section);
262         }
263         sections.clear();
264         for (final ActionbarSection section : newSections) {
265             addSection(section);
266             for (final String actionName : section.getActionOrder()) {
267                 ActionbarItem action = section.getActions().get(actionName);
268                 if (action.getIconFontId() == null) {
269                     action.setResourceUrl(presenter.getIconResourceURL(action.getName()));
270                 }
271                 addAction(action, section.getName());
272             }
273         }
274         updateLayout();
275         refreshActionsPositionsTablet();
276     }
277 
278     @Override
279     public void setVisibleSections(Collection<ActionbarSection> visibleSections) {
280         for (final ActionbarSectionWidget section : sections.values()) {
281             section.setVisible(visibleSections.contains(section.getData()));
282         }
283         updateLayout();
284         refreshActionsPositionsTablet();
285     }
286 
287     @Override
288     public void setDisabledActions(Collection<ActionbarItem> disabledActions) {
289         for (final ActionbarSectionWidget section : sections.values()) {
290             for (final VActionbarGroup group : section.getGroups().values()) {
291                 for (final ActionbarItemWidget action : group.getActions()) {
292                     action.setEnabled(!disabledActions.contains(action.getData()));
293                 }
294             }
295         }
296     }
297 
298     @Override
299     public boolean isOpen() {
300         return isToggledOpen;
301     }
302 
303     @Override
304     public void setOpen(boolean isOpen) {
305         actualizeToggleState(isToggledOpen);
306         if (isToggledOpen != isOpen) {
307             presenter.forceLayout();
308             updateLayout();
309         }
310     }
311 
312     @Override
313     public void setSectionPreview(String sectionName, String previewUrl) {
314         ActionbarSectionWidget sectionWidget = sections.get(sectionName);
315         if (sectionWidget != null) {
316             sectionWidget.addStyleName("preview");
317 
318             if (previewUrl != null) {
319                 Widget widget;
320                 Video video = Video.createIfSupported();
321                 if (video != null && !"".equals(video.canPlayType("video/" + previewUrl.substring(previewUrl.lastIndexOf(".") + 1)))) {
322                     video.addSource(previewUrl);
323                     video.setControls(true);
324                     widget = video;
325                 } else {
326                     Audio audio = Audio.createIfSupported();
327                     if (audio != null && !"".equals(audio.canPlayType("audio/" + previewUrl.substring(previewUrl.lastIndexOf(".") + 1)))) {
328                         audio.addSource(previewUrl);
329                         audio.setControls(true);
330                         widget = audio;
331                     } else {
332                         widget = new Image(previewUrl);
333                     }
334                 }
335                 sectionWidget.setPreview(widget);
336             }
337         }
338     }
339 
340     @Override
341     public void updateLayout() {
342         int windowWidth = Window.getClientWidth();
343 
344         // sections margin
345         final int MARGIN_TOP = windowWidth < BREAK_POINT_1 ? 55 : (windowWidth >= BREAK_POINT_2 ? 125 : 100) ;
346         final int MARGIN_BOTTOM = 20;
347 
348         int availableHeight = getOffsetHeight();
349         if (sections.containsKey("preview") && sections.get("preview").isVisible() && isToggledOpen) {
350             availableHeight -= 215 - MARGIN_BOTTOM;
351         }
352 
353         int actualHeight = 0;
354         for (ActionbarSectionWidget section : sections.values()) {
355             if (!section.getName().equals("preview") && section.isVisible()) {
356                 section.getElement().getStyle().clearProperty("maxHeight");
357                 actualHeight += section.getOffsetHeight() + MARGIN_TOP + MARGIN_BOTTOM; // add margin
358             }
359         }
360         log.fine("actualHeight/availableHeight: " + actualHeight + "/" + availableHeight);
361 
362         // rewind from bottom-up to shrink sections
363         int i = getWidgetCount() - 1;
364         int minimumHeight = 75;
365         while (actualHeight > availableHeight && i >= 0) {
366             log.fine("rewinding widget list, current height diff: " + (actualHeight - availableHeight) + " (i=" + i + ")");
367             Widget widget = getWidget(i);
368             if (widget instanceof ActionbarSectionWidget && widget.isVisible()) {
369                 String sectionName = ((ActionbarSectionWidget) widget).getName();
370                 if (!sectionName.equals("preview")) {
371                     int currentHeight = widget.getOffsetHeight() + MARGIN_TOP;
372                     if (currentHeight > minimumHeight) {
373                         int newHeight = (actualHeight - availableHeight > currentHeight - minimumHeight) ? minimumHeight : currentHeight + availableHeight - actualHeight;
374                         log.fine(sectionName + " currentHeight => newHeight: " + currentHeight + "=>" + (newHeight - MARGIN_TOP));
375                         widget.getElement().getStyle().setPropertyPx("maxHeight", newHeight - MARGIN_TOP); // account for padding or margin;
376                         actualHeight -= (currentHeight - newHeight);
377                     }
378                 }
379             }
380             i--;
381         }
382     }
383 
384     @Override
385     public void onResize(ResizeEvent event) {
386         updateLayout();
387     }
388 }