View Javadoc

1   /**
2    * This file Copyright (c) 2013-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.workbench;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.ui.vaadin.icon.Icon;
38  import info.magnolia.ui.workbench.definition.ContentPresenterDefinition;
39  import info.magnolia.ui.workbench.list.ListPresenterDefinition;
40  import info.magnolia.ui.workbench.search.SearchPresenterDefinition;
41  import info.magnolia.ui.workbench.tree.TreePresenterDefinition;
42  import info.magnolia.ui.workbench.tree.TreeView;
43  
44  import java.io.Serializable;
45  import java.util.HashMap;
46  import java.util.Map;
47  
48  import javax.inject.Inject;
49  
50  import org.apache.commons.lang.StringUtils;
51  
52  import com.vaadin.data.Property;
53  import com.vaadin.event.FieldEvents;
54  import com.vaadin.event.ShortcutAction;
55  import com.vaadin.event.ShortcutListener;
56  import com.vaadin.shared.ui.MarginInfo;
57  import com.vaadin.ui.Button;
58  import com.vaadin.ui.Button.ClickEvent;
59  import com.vaadin.ui.Component;
60  import com.vaadin.ui.CssLayout;
61  import com.vaadin.ui.NativeButton;
62  import com.vaadin.ui.Panel;
63  import com.vaadin.ui.TextField;
64  import com.vaadin.ui.VerticalLayout;
65  import com.vaadin.ui.themes.BaseTheme;
66  
67  /**
68   * Implementation of the workbench view.
69   */
70  public class WorkbenchViewImpl extends VerticalLayout implements WorkbenchView, Serializable {
71  
72      private final CssLayout toolBar = new CssLayout();
73  
74      private final CssLayout viewModes = new CssLayout();
75  
76      private final CssLayout searchBox = new CssLayout();
77  
78      protected final Panel keyboardEventPanel;
79  
80      private TextField searchField;
81  
82      private Button clearSearchBoxButton;
83  
84      private Icon searchIcon;
85  
86      private Icon searchArrow;
87  
88      private StatusBarView statusBar;
89  
90      private Map<String, ContentView> contentViews = new HashMap<String, ContentView>();
91  
92      private Map<String, Button> contentViewsButton = new HashMap<String, Button>();
93  
94      private String currentViewType = TreePresenterDefinition.VIEW_TYPE;
95  
96      /**
97       * for going back from search view if search expression is empty.
98       */
99      private String previousViewType = currentViewType;
100 
101     private final Property.ValueChangeListener searchFieldListener = new Property.ValueChangeListener() {
102 
103         @Override
104         public void valueChange(Property.ValueChangeEvent event) {
105             listener.onSearch(searchField.getValue().toString());
106 
107             boolean hasSearchContent = !searchField.getValue().isEmpty();
108             if (hasSearchContent) {
109                 searchBox.addStyleName("has-content");
110             } else {
111                 searchBox.removeStyleName("has-content");
112             }
113             searchField.focus();
114         }
115     };
116 
117     private WorkbenchView.Listener listener;
118     private final SimpleTranslator i18n;
119 
120     @Inject
121     public WorkbenchViewImpl(SimpleTranslator i18n) {
122         this.i18n = i18n;
123 
124         setSizeFull();
125         setMargin(new MarginInfo(true, false, false, true));
126         addStyleName("workbench");
127 
128         viewModes.setStyleName("view-modes");
129 
130         clearSearchBoxButton = new Button();
131         clearSearchBoxButton.setStyleName("m-closebutton");
132         clearSearchBoxButton.addStyleName("icon-delete-search");
133         clearSearchBoxButton.addStyleName("searchbox-clearbutton");
134         clearSearchBoxButton.addClickListener(new Button.ClickListener() {
135 
136             @Override
137             public void buttonClick(ClickEvent event) {
138                 searchField.setValue("");
139             }
140         });
141 
142         searchIcon = new Icon("search");
143         searchIcon.addStyleName("searchbox-icon");
144 
145         searchArrow = new Icon("arrow2_s");
146         searchArrow.addStyleName("searchbox-arrow");
147 
148         searchField = buildSearchField();
149 
150         searchBox.setVisible(false);
151         searchBox.addComponent(searchField);
152         searchBox.addComponent(clearSearchBoxButton);
153         searchBox.addComponent(searchIcon);
154         searchBox.addComponent(searchArrow);
155         searchBox.setStyleName("searchbox");
156 
157         toolBar.addStyleName("toolbar");
158         toolBar.setWidth(100, Unit.PERCENTAGE);
159         toolBar.addComponent(viewModes);
160         toolBar.addComponent(searchBox);
161 
162         addComponent(toolBar);
163         setExpandRatio(toolBar, 0);
164 
165         keyboardEventPanel = new Panel();
166         keyboardEventPanel.setSizeFull();
167         keyboardEventPanel.addStyleName("keyboard-panel");
168         addComponent(keyboardEventPanel, 1); // between tool bar and status bar
169         setExpandRatio(keyboardEventPanel, 1);
170 
171         bindKeyboardHandlers();
172     }
173 
174     public void bindKeyboardHandlers() {
175 
176         final ShortcutListener enterShortcut = new ShortcutListener("Enter shortcut", ShortcutAction.KeyCode.ENTER, null) {
177             @Override
178             public void handleAction(Object sender, Object target) {
179                 getSelectedView().onShortcutKey(ShortcutAction.KeyCode.ENTER, null);
180             }
181         };
182         keyboardEventPanel.addShortcutListener(enterShortcut);
183 
184         final ShortcutListener deleteShortcut = new ShortcutListener("Delete shortcut", ShortcutAction.KeyCode.DELETE, null) {
185             @Override
186             public void handleAction(Object sender, Object target) {
187                 getSelectedView().onShortcutKey(ShortcutAction.KeyCode.DELETE, null);
188             }
189         };
190         // MGNLUI-2106 disable the delete shortcut until we apply it without disrupting inplace-editing
191         // keyboardEventPanel.addShortcutListener(deleteShortcut);
192 
193     }
194 
195     @Override
196     public void setSearchQuery(String query) {
197         if (searchField == null) {
198             return;
199         }
200         // turn off value change listener, so that presenter does not think there was user input and searches again
201         searchField.removeValueChangeListener(searchFieldListener);
202         if (StringUtils.isNotBlank(query)) {
203             searchField.setValue(query);
204             searchField.focus();
205         } else {
206             searchField.setValue("");
207             searchBox.removeStyleName("has-content");
208         }
209         searchField.addValueChangeListener(searchFieldListener);
210 
211     }
212 
213     @Override
214     public void addContentView(String viewType, ContentView view, ContentPresenterDefinition contentViewDefintion) {
215         contentViews.put(viewType, view);
216 
217         if (view instanceof TreeView) {
218             ((TreeView) view).setActionManager(keyboardEventPanel);
219         }
220 
221         // display search-box only if both list and search content presenters are configured
222         if (contentViews.containsKey(ListPresenterDefinition.VIEW_TYPE) && contentViews.containsKey(SearchPresenterDefinition.VIEW_TYPE)) {
223             searchBox.setVisible(true);
224         }
225 
226         if (contentViewDefintion instanceof SearchPresenterDefinition) {
227             // do not add a view-type button for search
228             return;
229         }
230 
231         // set button
232         Button button = buildButton(viewType, contentViewDefintion.getIcon(), contentViewDefintion.isActive());
233         contentViewsButton.put(viewType, button);
234         viewModes.addComponent(button);
235         // set active
236         if (contentViewDefintion.isActive()) {
237             currentViewType = previousViewType = viewType;
238         }
239     }
240 
241     @Override
242     public void setViewType(String type) {
243         // removeComponent(getSelectedView().asVaadinComponent());
244         final Component c = contentViews.get(type).asVaadinComponent();
245         // addComponent(c, 1); // between tool bar and status bar
246         // setExpandRatio(c, 1);
247         keyboardEventPanel.setContent(c);
248 
249         if (type != SearchPresenterDefinition.VIEW_TYPE) {
250             previousViewType = type;
251             setSearchQuery(null);
252         }
253         setViewTypeStyling(type);
254 
255         currentViewType = type;
256     }
257 
258     private void fireViewTypeChangedEvent(String viewType) {
259         this.listener.onViewTypeChanged(viewType);
260     }
261 
262     @Override
263     public void setStatusBarView(StatusBarView statusBar) {
264         Component c = statusBar.asVaadinComponent();
265         if (this.statusBar == null) {
266             addComponent(c, getComponentCount()); // add last
267         } else {
268             replaceComponent(this.statusBar.asVaadinComponent(), c);
269         }
270         setExpandRatio(c, 0);
271         this.statusBar = statusBar;
272     }
273 
274     @Override
275     public ContentView getSelectedView() {
276         return contentViews.get(currentViewType);
277     }
278 
279     @Override
280     public Component asVaadinComponent() {
281         return this;
282     }
283 
284     @Override
285     public void setListener(WorkbenchView.Listener listener) {
286         this.listener = listener;
287     }
288 
289     private Button buildButton(final String viewType, final String icon, final boolean active) {
290         NativeButton button = new NativeButton(null, new Button.ClickListener() {
291             @Override
292             public void buttonClick(Button.ClickEvent event) {
293                 fireViewTypeChangedEvent(viewType);
294             }
295         });
296         button.setStyleName(BaseTheme.BUTTON_LINK);
297 
298         button.setHtmlContentAllowed(true);
299         button.setCaption("<span class=\"" + icon + "\"></span><span class=\"view-type-arrow view-type-arrow-" + viewType + " icon-arrow2_n\"></span>");
300 
301         if (active) {
302             button.addStyleName("active");
303         }
304         return button;
305     }
306 
307     private void setViewTypeStyling(final String viewType) {
308         for (Map.Entry<String, Button> entry : contentViewsButton.entrySet()) {
309             entry.getValue().removeStyleName("active");
310             if (entry.getKey().equals(viewType)) {
311                 entry.getValue().addStyleName("active");
312             }
313         }
314         // search is a list view
315         if (viewType.equals(SearchPresenterDefinition.VIEW_TYPE) && contentViews.containsKey(ListPresenterDefinition.VIEW_TYPE)) {
316             contentViewsButton.get(ListPresenterDefinition.VIEW_TYPE).addStyleName("active");
317         }
318     }
319 
320     private TextField buildSearchField() {
321         final TextField field = new TextField();
322         final String inputPrompt = i18n.translate("toolbar.search.prompt");
323 
324         field.setInputPrompt(inputPrompt);
325         field.setSizeUndefined();
326         field.addStyleName("searchfield");
327 
328         // TextField has to be immediate to fire value changes when pressing Enter, avoiding ShortcutListener overkill.
329         field.setImmediate(true);
330         field.addValueChangeListener(searchFieldListener);
331 
332         field.addFocusListener(new FieldEvents.FocusListener() {
333             @Override
334             public void focus(FieldEvents.FocusEvent event) {
335                 // put the cursor at the end of the field
336                 TextField tf = (TextField) event.getSource();
337                 tf.setCursorPosition(tf.getValue().length());
338             }
339         });
340 
341         // No blur handler.
342 
343         return field;
344     }
345 
346     @Override
347     public void setMultiselect(boolean multiselect) {
348         for (String type : contentViews.keySet()) {
349             contentViews.get(type).setMultiselect(multiselect);
350         }
351     }
352 }