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