View Javadoc

1   /**
2    * This file Copyright (c) 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.admincentral.shellapp.favorites;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.ui.admincentral.shellapp.favorites.EditingEvent.EditingListener;
38  import info.magnolia.ui.admincentral.shellapp.favorites.SelectedEvent.SelectedListener;
39  import info.magnolia.ui.api.overlay.ConfirmationCallback;
40  import info.magnolia.ui.api.shell.Shell;
41  import info.magnolia.ui.framework.AdmincentralNodeTypes;
42  import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
43  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
44  
45  import java.util.Iterator;
46  import java.util.Map;
47  
48  import org.apache.commons.lang.StringUtils;
49  
50  import com.vaadin.event.FieldEvents.BlurEvent;
51  import com.vaadin.event.FieldEvents.BlurListener;
52  import com.vaadin.event.FieldEvents.FocusEvent;
53  import com.vaadin.event.FieldEvents.FocusListener;
54  import com.vaadin.event.LayoutEvents.LayoutClickEvent;
55  import com.vaadin.event.LayoutEvents.LayoutClickListener;
56  import com.vaadin.event.ShortcutListener;
57  import com.vaadin.ui.Button.ClickEvent;
58  import com.vaadin.ui.Button.ClickListener;
59  import com.vaadin.ui.Component;
60  import com.vaadin.ui.CssLayout;
61  import com.vaadin.ui.DragAndDropWrapper.DragStartMode;
62  import com.vaadin.ui.NativeButton;
63  import com.vaadin.ui.TextField;
64  
65  /**
66   * Favorite group.
67   */
68  public final class FavoritesGroup extends CssLayout implements SelectedEvent.SelectedNotifier {
69  
70      private TextField titleField;
71      private NativeButton editButton;
72      private NativeButton removeButton;
73      private String title;
74      private String relPath;
75      private boolean editable;
76      private boolean selected;
77      private CssLayout wrapper;
78      private EnterKeyShortcutListener enterKeyShortcutListener;
79      private EscapeKeyShortcutListener escapeKeyShortcutListener;
80      private Shell shell;
81      private FavoritesView view;
82      private Component currentlySelectedFavEntry;
83      private final SimpleTranslator i18n;
84  
85      /**
86       * Creates an empty placeholder group.
87       */
88      public FavoritesGroup(SimpleTranslator i18n) {
89          this.i18n = i18n;
90          addStyleName("no-group");
91      }
92  
93      public FavoritesGroup(final AbstractJcrNodeAdapter favoritesGroup, final FavoritesView.Listener listener, final Shell shell, final FavoritesView view, final SimpleTranslator i18n) {
94          this.shell = shell;
95          this.view = view;
96          this.i18n = i18n;
97  
98          addStyleName("favorites-group");
99          construct(favoritesGroup, listener);
100 
101         final Map<String, AbstractJcrNodeAdapter> nodeAdapters = favoritesGroup.getChildren();
102 
103         for (String key : nodeAdapters.keySet()) {
104             final AbstractJcrNodeAdapter fav = nodeAdapters.get(key);
105             final FavoritesEntry favEntry = new FavoritesEntry(fav, listener, shell, i18n);
106             favEntry.addSelectedListener(new SelectedListener() {
107 
108                 @Override
109                 public void onSelected(SelectedEvent event) {
110                     currentlySelectedFavEntry = event.getComponent();
111                     view.updateSelection(event.getComponent());
112                 }
113             });
114             favEntry.setGroup(this.relPath);
115             final EntryDragAndDropWrapper wrapper = new EntryDragAndDropWrapper(favEntry, listener);
116             favEntry.addEditingListener(new EditingListener() {
117 
118                 @Override
119                 public void onEdit(EditingEvent event) {
120                     if (event.isEditing()) {
121                         wrapper.setDragStartMode(DragStartMode.NONE);
122                     } else {
123                         wrapper.setDragStartMode(DragStartMode.WRAPPER);
124                     }
125                 }
126             });
127             addComponent(wrapper);
128         }
129     }
130 
131     public String getRelPath() {
132         return this.relPath;
133     }
134 
135     /**
136      * Sets this group and all of its fav entries (if any) as unselected and non editable, that is at their initial state.
137      */
138     public void reset() {
139         // skip it if this group is a placeholder for no group fav entries, as it has no title
140         if (titleField != null) {
141             setEditable(false);
142             setSelected(false);
143         }
144         Iterator<Component> components = iterator();
145         while (components.hasNext()) {
146             Component component = components.next();
147             if (component instanceof EntryDragAndDropWrapper) {
148                 component = ((EntryDragAndDropWrapper) component).getWrappedComponent();
149             } else if (component instanceof FavoritesEntry) {
150                 if (component == currentlySelectedFavEntry) {
151                     continue;
152                 }
153                 FavoritesEntry fav = (FavoritesEntry) component;
154                 fav.reset();
155             }
156         }
157     }
158 
159     @Override
160     public void addSelectedListener(SelectedListener listener) {
161         addListener("onSelected", SelectedEvent.class, listener, SelectedEvent.SELECTED_METHOD);
162     }
163 
164     @Override
165     public void removeSelectedListener(SelectedListener listener) {
166         removeListener(SelectedEvent.class, listener, SelectedEvent.SELECTED_METHOD);
167     }
168 
169     private void setEditable(boolean editable) {
170         this.editable = editable;
171         String icon = "icon-tick";
172         if (editable) {
173             titleField.addStyleName("editable");
174             titleField.focus();
175             titleField.selectAll();
176         } else {
177             icon = "icon-edit";
178             // discard pending changes
179             titleField.setValue(title);
180             titleField.removeStyleName("editable");
181         }
182         titleField.setReadOnly(!editable);
183         editButton.setCaption("<span class=\"" + icon + "\"></span>");
184     }
185 
186     private void setSelected(boolean selected) {
187         this.selected = selected;
188         if (selected) {
189             wrapper.addStyleName("selected");
190             fireEvent(new SelectedEvent(this));
191         } else {
192             wrapper.removeStyleName("selected");
193         }
194         titleField.setReadOnly(true);
195         editButton.setVisible(selected);
196         editButton.setCaption("<span class=\"icon-edit\"></span>");
197         removeButton.setVisible(selected);
198     }
199 
200     private void construct(final AbstractJcrNodeAdapter favoritesGroup, final FavoritesView.Listener listener) {
201         wrapper = new CssLayout();
202         wrapper.addStyleName("favorites-group-title");
203 
204         this.enterKeyShortcutListener = new EnterKeyShortcutListener(listener);
205         this.escapeKeyShortcutListener = new EscapeKeyShortcutListener();
206 
207         this.relPath = favoritesGroup.getNodeName();
208         this.title = favoritesGroup.getItemProperty(AdmincentralNodeTypes.Favorite.TITLE).getValue().toString();
209 
210         titleField = new TextField();
211         titleField.setValue(title);
212         titleField.setReadOnly(true);
213         titleField.addFocusListener(new FocusListener() {
214 
215             @Override
216             public void focus(FocusEvent event) {
217                 titleField.addShortcutListener(enterKeyShortcutListener);
218                 titleField.addShortcutListener(escapeKeyShortcutListener);
219             }
220         });
221 
222         titleField.addBlurListener(new BlurListener() {
223 
224             @Override
225             public void blur(BlurEvent event) {
226                 titleField.removeShortcutListener(enterKeyShortcutListener);
227                 titleField.removeShortcutListener(escapeKeyShortcutListener);
228             }
229         });
230 
231         wrapper.addComponent(titleField);
232 
233         editButton = new NativeButton();
234         editButton.setHtmlContentAllowed(true);
235         editButton.setCaption("<span class=\"icon-edit\"></span>");
236         editButton.addStyleName("favorite-action");
237         editButton.addClickListener(new ClickListener() {
238 
239             @Override
240             public void buttonClick(ClickEvent event) {
241                 if (selected && !editable) {
242                     setEditable(true);
243                     return;
244                 }
245                 doEditTitle(listener);
246             }
247         });
248         editButton.setVisible(false);
249         wrapper.addComponent(editButton);
250 
251         removeButton = new NativeButton();
252         removeButton.setHtmlContentAllowed(true);
253         removeButton.setCaption("<span class=\"icon-trash\"></span>");
254         removeButton.addStyleName("favorite-action");
255         removeButton.addClickListener(new ClickListener() {
256 
257             @Override
258             public void buttonClick(ClickEvent event) {
259                 shell.openConfirmation(MessageStyleTypeEnum.WARNING, i18n.translate("favorites.group.confirmation.title"), i18n.translate("confirmation.cannot.undo"), i18n.translate("confirmation.delete.yes"), i18n.translate("confirmation.no"), true, new ConfirmationCallback() {
260 
261                     @Override
262                     public void onSuccess() {
263                         listener.removeGroup(relPath);
264                     }
265 
266                     @Override
267                     public void onCancel() {
268                         // no op
269                     }
270                 });
271             }
272         });
273         removeButton.setVisible(false);
274         wrapper.addComponent(removeButton);
275 
276         addLayoutClickListener(new LayoutClickListener() {
277 
278             @Override
279             public void layoutClick(LayoutClickEvent event) {
280 
281                 if (event.getClickedComponent() == titleField) {
282                     if (!editable) {
283                         setSelected(!selected);
284                     }
285                 }
286             }
287         });
288 
289         addComponent(new GroupDragAndDropWrapper(wrapper, listener, this));
290     }
291 
292     private void doEditTitle(final FavoritesView.Listener listener) {
293         if (StringUtils.isBlank(titleField.getValue())) {
294             shell.openNotification(MessageStyleTypeEnum.ERROR, true, i18n.translate("favorites.title.required"));
295             return;
296         }
297         boolean titleHasChanged = !title.equals(titleField.getValue());
298         if (editable && titleHasChanged) {
299             listener.editGroup(relPath, titleField.getValue());
300         }
301         setEditable(false);
302     }
303 
304     private class EnterKeyShortcutListener extends ShortcutListener {
305         private FavoritesView.Listener listener;
306 
307         public EnterKeyShortcutListener(final FavoritesView.Listener listener) {
308             super("", KeyCode.ENTER, null);
309             this.listener = listener;
310         }
311 
312         @Override
313         public void handleAction(Object sender, Object target) {
314             if (editable) {
315                 doEditTitle(listener);
316             } else {
317                 setEditable(true);
318             }
319         }
320     }
321 
322     private class EscapeKeyShortcutListener extends ShortcutListener {
323 
324         public EscapeKeyShortcutListener() {
325             super("", KeyCode.ESCAPE, null);
326         }
327 
328         @Override
329         public void handleAction(Object sender, Object target) {
330             reset();
331         }
332     }
333 }