View Javadoc

1   /**
2    * This file Copyright (c) 2011-2014 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.magnoliashell.shell;
35  
36  import info.magnolia.ui.vaadin.gwt.client.jquerywrapper.JQueryCallback;
37  import info.magnolia.ui.vaadin.gwt.client.jquerywrapper.JQueryWrapper;
38  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.ShellState;
39  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.shellmessage.ShellMessageWidget;
40  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.shellmessage.ShellMessageWidget.MessageType;
41  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.shellmessage.VInfoMessage;
42  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.shellmessage.VShellErrorMessage;
43  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.shellmessage.VWarningMessage;
44  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.animation.JQueryAnimation;
45  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.widget.AppsViewportWidget;
46  import info.magnolia.ui.vaadin.gwt.client.magnoliashell.viewport.widget.ViewportWidget;
47  import info.magnolia.ui.vaadin.gwt.client.shared.magnoliashell.Fragment;
48  import info.magnolia.ui.vaadin.gwt.client.shared.magnoliashell.ShellAppType;
49  import info.magnolia.ui.vaadin.gwt.client.shared.magnoliashell.ViewportType;
50  
51  import java.util.EnumMap;
52  import java.util.Map;
53  
54  import com.google.gwt.core.client.Scheduler;
55  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
56  import com.google.gwt.event.dom.client.KeyCodes;
57  import com.google.gwt.event.dom.client.KeyPressEvent;
58  import com.google.gwt.event.dom.client.KeyPressHandler;
59  import com.google.gwt.user.client.DOM;
60  import com.google.gwt.user.client.Element;
61  import com.google.gwt.user.client.ui.FocusPanel;
62  import com.google.gwt.user.client.ui.RootPanel;
63  import com.google.gwt.user.client.ui.Widget;
64  import com.googlecode.mgwt.ui.client.widget.touch.TouchPanel;
65  
66  /**
67   * GWT implementation of MagnoliaShell client side (the view part basically).
68   */
69  public class MagnoliaShellViewImpl extends TouchPanel implements MagnoliaShellView {
70  
71      public static final String CLASS_NAME = "v-magnolia-shell";
72  
73      public static final String VIEWPORT_SLOT_CLASS_NAME = "v-shell-viewport-slot";
74  
75      private final Map<ViewportType, ViewportWidget> viewports = new EnumMap<ViewportType, ViewportWidget>(ViewportType.class);
76  
77      private final ShellAppLauncher mainAppLauncher;
78  
79      private ShellMessageWidget lowPriorityMessage;
80  
81      private ShellMessageWidget hiPriorityMessage;
82  
83      private Presenter presenter;
84  
85      private JQueryAnimation viewportShifter = new JQueryAnimation();
86  
87      private final Element viewportSlot = DOM.createDiv();
88  
89      // To remove focus (blur) all input elements, one just needs to focus this element.
90      private final FocusPanel blurHelper = new FocusPanel();
91  
92      public MagnoliaShellViewImpl() {
93          super();
94          this.mainAppLauncher = new ShellAppLauncher();
95          getElement().setClassName(CLASS_NAME);
96          viewportSlot.setClassName(VIEWPORT_SLOT_CLASS_NAME);
97          add(mainAppLauncher, getElement());
98  
99  
100         getElement().appendChild(viewportSlot);
101         viewportShifter.addCallback(new JQueryCallback() {
102             @Override
103             public void execute(JQueryWrapper query) {
104                 presenter.updateViewportLayout(appViewport());
105             }
106         });
107 
108         initKeyboardShortcutSupport();
109     }
110 
111 
112     /**
113      * Bind keyboard handlers.
114      * These commands are only processed if an input area does not have focus.
115      */
116     protected void initKeyboardShortcutSupport() {
117 
118         add(blurHelper, getElement());
119 
120         KeyPressHandler keyboardShortcutHandler = new KeyPressHandler() {
121 
122             @Override
123             public void onKeyPress(KeyPressEvent event) {
124 
125                 // Only process keyboard shortcuts if user is not in an input field.
126                 if (isFocusedElementAnInputField()) {
127                     return;
128                 }
129 
130                 // Only process if no modifier keys are held down to avoid collision with OS or Browser hotkeys.
131                 if (event.isAnyModifierKeyDown()) {
132                     return;
133                 }
134 
135                 char c = event.getCharCode();
136 
137                 switch (c) {
138 
139                 // Shell Apps
140                 case '1':
141                     mainAppLauncher.toggleShellApp(ShellAppType.APPLAUNCHER);
142                     break;
143                 case '2':
144                     mainAppLauncher.toggleShellApp(ShellAppType.PULSE);
145                     break;
146                 case '3':
147                     mainAppLauncher.toggleShellApp(ShellAppType.FAVORITE);
148                     break;
149 
150                 // App Stack Navigation.
151                 case '9':
152                     // We have more than one app open
153                     if (appViewport().readyForAppSwipeOrShortcutNavigation()) {
154                         ShellState.get().setAppStarting();
155                         appViewport().goToPreviousApp();
156                     }
157                     break;
158                 case '0':
159                     // We have more than one app open
160                     if (appViewport().readyForAppSwipeOrShortcutNavigation()) {
161                         ShellState.get().setAppStarting();
162                         appViewport().goToNextApp();
163                     }
164                     break;
165 
166                 default:
167                     // Nothing
168                 }
169             }
170         };
171         RootPanel.get().addDomHandler(keyboardShortcutHandler, KeyPressEvent.getType());
172 
173 
174         /**
175          * Pressing the escape key causes all elements to loose focus.
176          * This is a handy way to be able to start using the single-key keyboard shortcuts.
177          */
178         KeyPressHandler escapeKeyPressHandler = new KeyPressHandler() {
179             @Override
180             public void onKeyPress(KeyPressEvent event) {
181                 int code = event.getNativeEvent().getKeyCode();
182                 if (code == KeyCodes.KEY_ESCAPE) {
183                     blurHelper.getElement().focus();
184                 }
185             }
186         };
187         RootPanel.get().addDomHandler(escapeKeyPressHandler, KeyPressEvent.getType());
188     }
189 
190     /**
191      * Returns whether the currently focused element is one that accepts keyboard input.
192      * 
193      * @return
194      */
195     protected boolean isFocusedElementAnInputField() {
196         Element focused = elementInFocus(RootPanel.get().getElement());
197         String tagName = focused.getTagName();
198         if ("input".equalsIgnoreCase(tagName) || "select".equalsIgnoreCase(tagName) || "textarea".equalsIgnoreCase(tagName)) {
199             return true;
200         }
201         return false;
202     }
203 
204     protected native Element elementInFocus(Element element) /*-{
205         return element.ownerDocument.activeElement;
206     }-*/;
207 
208 
209     protected AppsViewportWidget appViewport() {
210         return (AppsViewportWidget) viewports.get(ViewportType.APP);
211     }
212 
213     protected void replaceWidget(final Widget oldWidget, final Widget newWidget) {
214         if (oldWidget != newWidget) {
215             if (oldWidget != null) {
216                 remove(oldWidget);
217             }
218         }
219         if (getWidgetIndex(newWidget) < 0) {
220             add(newWidget, viewportSlot);
221         }
222     }
223 
224     @Override
225     public void setPresenter(Presenter presenter) {
226         this.presenter = presenter;
227         mainAppLauncher.setListener(presenter);
228     }
229 
230     @Override
231     public void showMessage(MessageType type, String topic, String message, String id) {
232         final ShellMessageWidget msg;
233         switch (type) {
234         case WARNING:
235             msg = new VWarningMessage(this, topic, message, id);
236             if (lowPriorityMessage != null && getWidgetIndex(lowPriorityMessage) != -1) {
237                 lowPriorityMessage.hide();
238             }
239             lowPriorityMessage = msg;
240             break;
241         case INFO:
242             msg = new VInfoMessage(this, topic, message, id);
243             if (lowPriorityMessage != null && getWidgetIndex(lowPriorityMessage) != -1) {
244                 lowPriorityMessage.hide();
245             }
246             lowPriorityMessage = msg;
247             break;
248         case ERROR:
249             msg = new VShellErrorMessage(this, topic, message, id);
250             if (hiPriorityMessage != null && getWidgetIndex(hiPriorityMessage) != -1) {
251                 hiPriorityMessage.hide();
252             }
253             hiPriorityMessage = msg;
254             break;
255         default:
256             msg = null;
257         }
258         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
259 
260             @Override
261             public void execute() {
262                 if (msg != null) {
263                     add(msg, getElement());
264                 }
265             }
266         });
267     }
268 
269     @Override
270     public void hideAllMessages() {
271         if (hiPriorityMessage != null && getWidgetIndex(hiPriorityMessage) != -1) {
272             hiPriorityMessage.hideWithoutTransition();
273         }
274         if (lowPriorityMessage != null && getWidgetIndex(lowPriorityMessage) != -1) {
275             lowPriorityMessage.hideWithoutTransition();
276         }
277 
278         hiPriorityMessage = null;
279         lowPriorityMessage = null;
280     }
281 
282     @Override
283     public void updateViewport(ViewportWidget viewport, ViewportType type) {
284         ViewportWidget oldViewport = viewports.get(type);
285         if (oldViewport != viewport) {
286             replaceWidget(oldViewport, viewport);
287             viewports.put(type, viewport);
288         }
289     }
290 
291     @Override
292     public void shiftViewportsVertically(int shiftPx, boolean animated) {
293         viewportShifter.setProperty("top", mainAppLauncher.getOffsetHeight() + shiftPx);
294         if (shiftPx == 0 || shiftPx == 60) {
295             viewportShifter.clearTopAfterThisAnimation();
296         }
297         viewportShifter.run(animated ? 300 : 0, viewportSlot);
298     }
299 
300     @Override
301     public void setShellAppIndication(ShellAppType type, int indication) {
302         mainAppLauncher.setIndication(type, indication);
303     }
304 
305     @Override
306     public void closeMessageEager(final String id) {
307         presenter.removeMessage(id);
308     }
309 
310     @Override
311     public void navigateToMessageDetails(String id) {
312         presenter.activateShellApp(Fragment.fromString("shell:pulse:messages/" + id));
313     }
314 
315     @Override
316     public void updateShellDivet() {
317         mainAppLauncher.updateDivet();
318     }
319 
320     @Override
321     public void openOverlayOnWidget(Widget overlayWidget, Widget overlayParent) {
322         add(overlayWidget, overlayParent.getElement());
323     }
324 
325     @Override
326     public void onShellAppStarting(ShellAppType type) {
327         mainAppLauncher.activateControl(type);
328     }
329 
330     @Override
331     public void onAppStarting() {
332         mainAppLauncher.deactivateControls();
333     }
334 
335     @Override
336     public void setUserMenu(Widget widget) {
337         mainAppLauncher.setUserMenu(widget);
338     }
339 
340     @Override
341     public void onLoad() {
342         super.onLoad();
343         presenter.initHistory();
344     }
345 
346     @Override
347     public boolean hasOverlay(Widget widget) {
348         return getWidgetIndex(widget) != -1;
349     }
350 
351 
352 }