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