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