View Javadoc
1   /**
2    * This file Copyright (c) 2012-2016 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.editor.model.focus;
35  
36  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlArea;
37  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlComponent;
38  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlElement;
39  import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlPage;
40  import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStopMoveEvent;
41  import info.magnolia.ui.vaadin.gwt.client.editor.event.SelectElementEvent;
42  import info.magnolia.ui.vaadin.gwt.client.editor.jsni.scroll.ElementScrollPositionPreserver;
43  import info.magnolia.ui.vaadin.gwt.client.editor.model.Model;
44  import info.magnolia.ui.vaadin.gwt.client.shared.AreaElement;
45  import info.magnolia.ui.vaadin.gwt.client.shared.ComponentElement;
46  
47  import com.google.web.bindery.event.shared.EventBus;
48  
49  /**
50   * Helper class to keep track of selected items. Welcome to the MindTwister.
51   */
52  public class FocusModelImpl implements FocusModel {
53  
54      private final Model model;
55  
56      private final EventBus eventBus;
57  
58      private boolean rootSelected = false;
59  
60      private AreaElement selectedAreaElement = null;
61  
62      private ComponentElement selectedComponentElement = null;
63  
64      public FocusModelImpl(EventBus eventBus, Model model) {
65          super();
66          this.eventBus = eventBus;
67          this.model = model;
68      }
69  
70      @Override
71      public void selectElement(MgnlElement mgnlElement) {
72          if (mgnlElement == null) {
73              mgnlElement = model.getRootPage();
74          }
75  
76          final MgnlComponent currentlySelected = getSelectedComponent();
77          final MgnlComponent componentToPreserve = mgnlElement.isComponent() ? (MgnlComponent) mgnlElement : currentlySelected;
78  
79          ElementScrollPositionPreserver scrollPositionPreserver = null;
80          if (componentToPreserve != null) {
81              scrollPositionPreserver = new ElementScrollPositionPreserver(componentToPreserve);
82          }
83  
84          doSelectMgnlElement(mgnlElement);
85  
86          if (scrollPositionPreserver != null) {
87              final ElementScrollPositionPreserver preserver = scrollPositionPreserver;
88              preserver.restorePosition();
89          }
90      }
91  
92      private void doSelectMgnlElement(MgnlElement mgnlElement) {
93          if (model.isMoving()) {
94              // cancel move if click was outside the moving components relatives or null
95              if (!mgnlElement.isRelated(getSelectedComponent())) {
96                  eventBus.fireEvent(new ComponentStopMoveEvent(null, false));
97              }
98          } else {
99              MgnlPage page = null;
100             MgnlArea area = null;
101             MgnlComponent component = null;
102 
103             // if there is no mapping, we select the page
104             if (mgnlElement == null) {
105                 page = model.getRootPage();
106             } else {
107                 if (mgnlElement.isComponent()) {
108                     component = (MgnlComponent) mgnlElement;
109                     area = mgnlElement.getParentArea();
110                 } else if (mgnlElement.isArea()) {
111                     area = (MgnlArea) mgnlElement;
112                 }
113             }
114             // first set the component, then set the area. the selected component is used for setting
115             // the current area class.
116             setComponentSelection(component);
117             setAreaSelection(area);
118             setPageSelection(page);
119 
120             dispatchElementSelection(mgnlElement);
121         }
122     }
123 
124     @Override
125     public void init() {
126         for (MgnlArea root : model.getRootAreas()) {
127             root.setVisible(true);
128 
129             if (root.getComponents().isEmpty()) {
130                 root.setPlaceHolderVisible(true);
131             }
132         }
133 
134         tryRestoreSelection();
135     }
136 
137     private void tryRestoreSelection() {
138         MgnlElement selectedElement = getSelectedComponent();
139         if (selectedElement == null) {
140             selectedElement = getSelectedArea();
141         }
142 
143         if (selectedElement == null) {
144             selectedElement = model.getRootPage();
145         }
146 
147         selectedComponentElement = null;
148         selectedAreaElement = null;
149 
150         selectElement(selectedElement);
151     }
152 
153     @Override
154     public MgnlArea getSelectedArea() {
155         return (MgnlArea) model.getMgnlElement(selectedAreaElement);
156     }
157 
158     @Override
159     public MgnlComponent getSelectedComponent() {
160         return (MgnlComponent) model.getMgnlElement(selectedComponentElement);
161     }
162 
163     @Override
164     public void clearSelection() {
165         this.selectedAreaElement = null;
166         this.selectedComponentElement = null;
167         doSelectMgnlElement(model.getRootPage());
168     }
169 
170     /**
171      * Takes care of the selection of components. keeps track of last selected element and toggles
172      * the focus. If a null-value is passed it will reset the currently selected component.
173      *
174      * @param component the MgnlElement component, can be null.
175      */
176     private void setComponentSelection(MgnlComponent component) {
177         MgnlComponent currentComponent = getSelectedComponent();
178         if (currentComponent == component) {
179             return;
180         }
181         if (currentComponent != null) {
182             currentComponent.removeFocus();
183         }
184         if (component != null) {
185             component.setFocus();
186         }
187         this.selectedComponentElement = component == null ? null : component.getTypedElement();
188     }
189 
190     /**
191      * This method takes care of selecting and deselecting areas.
192      *
193      * @param area selected area, can be null.
194      */
195     private void setAreaSelection(MgnlArea area) {
196         MgnlArea selectedArea = getSelectedArea();
197         MgnlComponent currentComponent = getSelectedComponent();
198 
199         if (selectedArea != null) {
200 
201             selectedArea.removeFocus();
202 
203             // always reset current area selection unless area and current area are related
204             if (!selectedArea.isRelated(area)) {
205 
206                 toggleChildComponentVisibility(selectedArea, false);
207                 toggleAreaVisibility(selectedArea, false);
208             }
209 
210             // hide child components if area is an ascendant of current area or selectedArea is not
211             // a descendant
212             else if (selectedArea.getAscendants().contains(area) || (area != null && !area.getDescendants().contains(selectedArea))) {
213                 toggleChildComponentVisibility(selectedArea, false);
214             }
215         }
216 
217         // set focus on new selected area
218         if (area != null) {
219             toggleAreaVisibility(area, true);
220             toggleChildComponentVisibility(area, true);
221 
222             area.setFocus((currentComponent != null));
223         }
224         this.selectedAreaElement = area == null ? null : area.getTypedElement();
225     }
226 
227     private void toggleAreaVisibility(MgnlArea area, boolean visible) {
228 
229         MgnlArea parentArea = area.getParentArea();
230         if (parentArea != null) {
231             toggleAreaVisibility(parentArea, visible);
232             toggleChildComponentVisibility(parentArea, visible);
233 
234         }
235         // root areas are always visible
236         if (!model.getRootAreas().contains(area)) {
237             area.setVisible(visible);
238         }
239 
240         toggleNestedAreasVisibility(area, visible);
241     }
242 
243     private void toggleChildComponentVisibility(MgnlArea area, boolean visible) {
244 
245         // do not hide empty root areas placeholder
246         if (!model.getRootAreas().contains(area) || !area.getComponents().isEmpty()) {
247             area.setPlaceHolderVisible(visible);
248         }
249 
250         // hide
251         if (!visible && !area.getComponents().isEmpty()) {
252             area.setPlaceHolderVisible(visible);
253         }
254 
255 
256         for (MgnlComponent component : area.getComponents()) {
257 
258             // toggle all child-components editbar visibility - does this case occur?
259             component.setVisible(visible);
260 
261 
262             // toggle all child-components-area visibility
263             for (MgnlArea childArea : component.getAreas()) {
264                 childArea.setVisible(visible);
265                 toggleNestedAreasVisibility(childArea, visible);
266             }
267         }
268     }
269 
270     private void toggleNestedAreasVisibility(MgnlArea area, boolean visible) {
271         for (MgnlArea childArea : area.getAreas()) {
272             childArea.setVisible(visible);
273             toggleNestedAreasVisibility(childArea, visible);
274         }
275     }
276 
277     private void setPageSelection(MgnlPage page) {
278         boolean visible = true;
279         if (page == null) {
280             visible = false;
281         }
282 
283         this.rootSelected = !this.rootSelected;
284         for (MgnlArea root : model.getRootAreas()) {
285             root.toggleInitFocus(visible);
286         }
287     }
288 
289     private void dispatchElementSelection(MgnlElement mgnlElement) {
290         mgnlElement = (mgnlElement != null) ? mgnlElement : model.getRootPage();
291         if (mgnlElement != null) {
292             eventBus.fireEvent(new SelectElementEvent(mgnlElement.getTypedElement()));
293         }
294     }
295 
296 
297 }