View Javadoc

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