View Javadoc

1   /**********************************************
2    * Copyright (C) 2011 Lukas Laag
3    * This file is part of svgreal.
4    * 
5    * svgreal is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * svgreal is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   * 
15   * You should have received a copy of the GNU General Public License
16   * along with svgreal.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.svg.edit.client.inspector;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.vectomatic.dom.svg.OMSVGPathSeg;
27  import org.vectomatic.dom.svg.utils.SVGConstants;
28  import org.vectomatic.svg.edit.client.AppBundle;
29  import org.vectomatic.svg.edit.client.event.SelectionChangedProcessor;
30  import org.vectomatic.svg.edit.client.event.SelectionChangedProxy;
31  import org.vectomatic.svg.edit.client.model.MetaModel;
32  import org.vectomatic.svg.edit.client.model.ModelCategory;
33  import org.vectomatic.svg.edit.client.model.ModelConstants;
34  import org.vectomatic.svg.edit.client.model.svg.SVGPathElementModel;
35  import org.vectomatic.svg.edit.client.model.svg.SVGPathSegType;
36  import org.vectomatic.svg.edit.client.model.svg.path.SVGCloseSegModel;
37  import org.vectomatic.svg.edit.client.model.svg.path.SVGSegModel;
38  import org.vectomatic.svg.edit.client.model.svg.path.SVGSegStore;
39  
40  import com.extjs.gxt.ui.client.Style.LayoutRegion;
41  import com.extjs.gxt.ui.client.Style.Scroll;
42  import com.extjs.gxt.ui.client.data.ModelData;
43  import com.extjs.gxt.ui.client.event.ButtonEvent;
44  import com.extjs.gxt.ui.client.event.Events;
45  import com.extjs.gxt.ui.client.event.GridEvent;
46  import com.extjs.gxt.ui.client.event.Listener;
47  import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
48  import com.extjs.gxt.ui.client.event.SelectionListener;
49  import com.extjs.gxt.ui.client.store.ListStore;
50  import com.extjs.gxt.ui.client.util.Format;
51  import com.extjs.gxt.ui.client.util.Margins;
52  import com.extjs.gxt.ui.client.widget.Component;
53  import com.extjs.gxt.ui.client.widget.ContentPanel;
54  import com.extjs.gxt.ui.client.widget.LayoutContainer;
55  import com.extjs.gxt.ui.client.widget.button.Button;
56  import com.extjs.gxt.ui.client.widget.form.FormPanel;
57  import com.extjs.gxt.ui.client.widget.form.NumberField;
58  import com.extjs.gxt.ui.client.widget.form.SimpleComboBox;
59  import com.extjs.gxt.ui.client.widget.grid.CellEditor;
60  import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
61  import com.extjs.gxt.ui.client.widget.grid.ColumnData;
62  import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
63  import com.extjs.gxt.ui.client.widget.grid.EditorGrid;
64  import com.extjs.gxt.ui.client.widget.grid.EditorGrid.ClicksToEdit;
65  import com.extjs.gxt.ui.client.widget.grid.Grid;
66  import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
67  import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel;
68  import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
69  import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
70  import com.extjs.gxt.ui.client.widget.layout.CardLayout;
71  import com.extjs.gxt.ui.client.widget.layout.ColumnLayout;
72  import com.extjs.gxt.ui.client.widget.layout.FitLayout;
73  import com.extjs.gxt.ui.client.widget.layout.VBoxLayout;
74  import com.extjs.gxt.ui.client.widget.layout.VBoxLayout.VBoxLayoutAlign;
75  import com.extjs.gxt.ui.client.widget.layout.VBoxLayoutData;
76  import com.google.gwt.core.client.GWT;
77  import com.google.gwt.user.client.ui.AbstractImagePrototype;
78  
79  /**
80   * Inspector section dedicated to SVG path geometry
81   * @author laaglu
82   */
83  public class PathInspectorSection implements IInspectorSection<SVGPathElementModel> {
84  	private class PathPanel extends ContentPanel implements SelectionChangedProcessor<SVGSegModel> {
85  		protected ColumnModel cm;
86  		protected EditorGrid<SVGSegModel> grid;
87  		
88  		protected Button addSegmentButton;
89  		protected Button insertSegmentButton;
90  		protected Button removeSegmentsButton;
91  		
92  		protected SelectionChangedProxy<SVGSegModel> selChangeProxy = new SelectionChangedProxy<SVGSegModel>(this);
93  		
94  		private CardLayout cardLayout;
95  		private LayoutContainer container;
96  		private LayoutContainer noSelection;
97  		private LayoutContainer multipleSelection;
98  		private IInspectorSection<SVGSegModel> currentSection;
99  
100 		public PathPanel() {
101 			setHeading(Format.capitalize(category.getDescription()));
102 			ModelConstants constants = ModelConstants.INSTANCE;
103 			List<ColumnConfig> configs = new ArrayList<ColumnConfig>();
104 			
105 			final SimpleComboBox<String> combo = (SimpleComboBox<String>) SVGPathSegType.INSTANCE.createField(SVGSegModel.TYPE);
106 			CellEditor editor = new CellEditor(combo) {
107 				@Override
108 				public Object preProcessValue(Object value) {
109 					if (value == null) {
110 						return value;
111 					}
112 					return combo.findModel(value.toString());
113 				}
114 
115 				@Override
116 				public Object postProcessValue(Object value) {
117 					if (value == null) {
118 						return value;
119 					}
120 					return ((ModelData) value).get("value");
121 				}
122 			};
123 		    
124 			ColumnConfig typeColumn = new ColumnConfig();
125 			typeColumn.setSortable(false);
126 			typeColumn.setId(SVGSegModel.TYPE_ID);
127 			typeColumn.setHeader(constants.segTypeDesc());
128 			typeColumn.setWidth(220);
129 			typeColumn.setEditor(editor);
130 			configs.add(typeColumn);
131 			
132 			GridCellRenderer<ModelData> coordinateRenderer = new GridCellRenderer<ModelData>() {
133 				@Override
134 				public Object render(ModelData model, String property,
135 						ColumnData config, int rowIndex, int colIndex,
136 						ListStore<ModelData> store, Grid<ModelData> grid) {
137 					if (model instanceof SVGCloseSegModel) {
138 						return "";
139 					}
140 					return null;
141 				}
142 			};
143 			ColumnConfig xColumn = new ColumnConfig();
144 			xColumn.setSortable(false);
145 			xColumn.setId(SVGConstants.SVG_X_ATTRIBUTE);
146 			xColumn.setHeader(constants.x());
147 			xColumn.setWidth(100);
148 			NumberField xField = new NumberField();
149 			xField.setPropertyEditorType(Float.class);
150 			xField.setAllowBlank(false);
151 			xColumn.setEditor(new CellEditor(xField));
152 			xColumn.setRenderer(coordinateRenderer);
153 			configs.add(xColumn);
154 
155 			ColumnConfig yColumn = new ColumnConfig();
156 			yColumn.setSortable(false);
157 			yColumn.setId(SVGConstants.SVG_Y_ATTRIBUTE);
158 			yColumn.setHeader(constants.y());
159 			yColumn.setWidth(100);
160 			NumberField yField = new NumberField();
161 			yField.setPropertyEditorType(Float.class);
162 			yField.setAllowBlank(false);
163 			yColumn.setEditor(new CellEditor(yField));
164 			yColumn.setRenderer(coordinateRenderer);
165 			configs.add(yColumn);
166 
167 			cm = new ColumnModel(configs);
168 			grid = new EditorGrid<SVGSegModel>(new SVGSegStore(), cm);  
169 		    grid.setAutoExpandColumn(SVGSegModel.TYPE_ID);  
170 		    grid.setBorders(true);
171 			grid.setClicksToEdit(ClicksToEdit.TWO);
172 			grid.addListener(Events.BeforeEdit, new Listener<GridEvent<SVGSegModel>>() {
173 				@Override
174 				public void handleEvent(GridEvent<SVGSegModel> ge) {
175 					String property = ge.getProperty();
176 					// Do not allow changing the type of the first moveto segment
177 					if (SVGSegModel.TYPE_ID.equals(property) && ge.getRowIndex() == 0) {
178 						ge.setCancelled(true);
179 					}
180 					// Do not allow changing the coordinates of a close path
181 					if (ge.getModel() instanceof SVGCloseSegModel
182 					 && (SVGConstants.SVG_X_ATTRIBUTE.equals(property)
183 					  || SVGConstants.SVG_Y_ATTRIBUTE.equals(property))) {
184 						ge.setCancelled(true);
185 					}
186 				}			
187 			});
188 			
189 			
190 			addSegmentButton = new Button();
191 			addSegmentButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
192 				@Override
193 				public void componentSelected(ButtonEvent ce) {
194 					GWT.log("Add segment");
195 					getStore().appendSegment();
196 				}
197 			});
198 			addSegmentButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.addPoint()));
199 			addSegmentButton.setToolTip(Format.capitalize(constants.addSegmentButton()));
200 
201 			insertSegmentButton = new Button();
202 			insertSegmentButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
203 				@Override
204 				public void componentSelected(ButtonEvent ce) {
205 					GWT.log("Insert segment");
206 					getStore().insertSegment();
207 				}
208 			});
209 			insertSegmentButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.insertPoint()));
210 			insertSegmentButton.setToolTip(Format.capitalize(constants.insertSegmentButton()));
211 			
212 			removeSegmentsButton = new Button();
213 			removeSegmentsButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
214 				@Override
215 				public void componentSelected(ButtonEvent ce) {
216 					GWT.log("Remove segment");
217 					getStore().removeSelectedSegments();
218 				}
219 			});
220 			removeSegmentsButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.removePoints()));
221 			removeSegmentsButton.setToolTip(Format.capitalize(constants.removeSegmentsButton()));
222 			
223 			LayoutContainer c = new LayoutContainer(new ColumnLayout());  
224 			c.add(addSegmentButton);
225 			c.add(insertSegmentButton);
226 			c.add(removeSegmentsButton);
227 			
228 			container = new LayoutContainer();
229 			cardLayout = new CardLayout();
230 			container.setLayout(cardLayout);
231 
232 			noSelection = new LayoutContainer();
233 			noSelection.addText("No selection");
234 			noSelection.setLayout(new FitLayout());
235 			container.add(noSelection);
236 
237 			multipleSelection = new LayoutContainer();  
238 			multipleSelection.addText("Multiple selection");  
239 			multipleSelection.setLayout(new FitLayout());
240 			container.add(multipleSelection);
241 
242 			LayoutContainer detailPanel = new LayoutContainer();
243 			detailPanel.setLayout(new FitLayout());
244 			detailPanel.add(container);
245 
246 			LayoutContainer top = new LayoutContainer();
247 			top.setLayout(new VBoxLayout(VBoxLayoutAlign.STRETCH));
248 			VBoxLayoutData fl1 = new VBoxLayoutData(new Margins(10, 10, 0, 10));
249 			fl1.setFlex(0);
250 			top.add(c, fl1);
251 			VBoxLayoutData fl2 = new VBoxLayoutData(new Margins(10));
252 			fl2.setFlex(1);
253 			top.add(grid, fl2);
254 			
255 			BorderLayout layout = new BorderLayout();  
256 		    layout.setEnableState(true);  
257 		    setLayout(layout);
258 		    
259 			BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER);  
260 		    centerData.setMargins(new Margins(0)); 
261 		    add(top, centerData);
262 			
263 		    BorderLayoutData southData = new BorderLayoutData(LayoutRegion.SOUTH, 100); 
264 		    southData.setMargins(new Margins(0,10,10,10)); 
265 		    southData.setSplit(true); 
266 		    southData.setCollapsible(true); 
267 		    add(detailPanel, southData);
268 		    setScrollMode(Scroll.AUTOY);
269 			
270 		    processSelectionChanged(null);
271 		}
272 		
273 		public void bind(SVGSegStore store) {
274 			grid.reconfigure(store, cm);
275 			final GridSelectionModel<SVGSegModel> selectionModel = store.getSelectionModel();
276 			selectionModel.addSelectionChangedListener(selChangeProxy);
277 			grid.setSelectionModel(selectionModel);
278 			selectionModel.refresh();
279 		}
280 		
281 		public void unbind() {
282 			GridSelectionModel<SVGSegModel> selectionModel = grid.getSelectionModel();
283 			if (selectionModel != null) {
284 				selectionModel.removeSelectionListener(selChangeProxy);
285 			}
286 			grid.setSelectionModel(null);
287 		}
288 
289 		@Override
290 		public boolean processSelectionChanged(SelectionChangedEvent<SVGSegModel> se) {
291 			List<SVGSegModel> models = se != null ? se.getSelection() : Collections.<SVGSegModel>emptyList();
292 			GWT.log("PathPanel.selectionChanged: " + models);
293 			SVGSegStore store = getStore();
294 			insertSegmentButton.setEnabled(store.canInsertSegment());
295 			removeSegmentsButton.setEnabled(store.canRemoveSelectedSegments());
296 			
297 			Component panel = noSelection;
298 			SVGSegModel model = null;
299 			if (currentSection != null) {
300 				currentSection.unbind();
301 				currentSection = null;
302 			}
303 			if (models != null) {
304 				if (models.size() > 1) {
305 					panel = multipleSelection;
306 				} else if (models.size() == 1) {
307 					model = models.get(0);
308 					currentSection = getSection(model);
309 					currentSection.bind(model);
310 					panel = currentSection.getPanel();
311 				}
312 			}
313 			cardLayout.setActiveItem(panel);
314 			return false;
315 		}
316 
317 		private SVGSegStore getStore() {
318 			return (SVGSegStore) grid.getStore();
319 		}
320 
321 		private IInspectorSection<SVGSegModel> getSection(SVGSegModel model) {
322 			if (metaModelToSection == null) {
323 				metaModelToSection = new HashMap<MetaModel<OMSVGPathSeg>, IInspectorSection<SVGSegModel>>();
324 			}
325 			MetaModel<OMSVGPathSeg> metaModel = model.getMetaModel();
326 			IInspectorSection<SVGSegModel> section = metaModelToSection.get(metaModel);
327 			if (section == null) {
328 				ModelCategory<?> geometry = model.getMetaModel().getCategory(ModelCategory.GEOMETRY);
329 				section = geometry.getInspectorSection();
330 				metaModelToSection.put(metaModel, section);
331 				FormPanel form = (FormPanel)section.getPanel();
332 				form.setHeaderVisible(false);
333 				container.add(form);
334 			}
335 			return section;
336 		}
337 
338 	}
339 
340 	protected static Map<MetaModel<OMSVGPathSeg>, IInspectorSection<SVGSegModel>> metaModelToSection;
341 	private PathPanel panel;
342 	private ModelCategory<?> category;
343 
344 	public PathInspectorSection(ModelCategory<?> category) {
345 		this.category = category;
346 		panel = new PathPanel();
347 	}
348 	@Override
349 	public Component getPanel() {
350 		return panel ;
351 	}
352 	@Override
353 	public String toString() {
354 		StringBuilder builder = new StringBuilder("PathInspectorSection(");
355 		builder.append(category);
356 		builder.append(")");
357 		return builder.toString();
358 	}
359 
360 	@Override
361 	public void bind(SVGPathElementModel model) {
362 		GWT.log("PathInspectorSection.bind(" + model + ")");
363 		panel.bind(model.getSegStore());
364 	}
365 
366 	@Override
367 	public void unbind() {
368 		panel.unbind();
369 	}
370 
371 }
372