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.gxt.panels;
19  
20  import java.util.List;
21  
22  import org.vectomatic.dom.svg.OMSVGPaint;
23  import org.vectomatic.dom.svg.utils.SVGConstants;
24  import org.vectomatic.svg.edit.client.AppBundle;
25  import org.vectomatic.svg.edit.client.AppConstants;
26  import org.vectomatic.svg.edit.client.SvgrealApp;
27  import org.vectomatic.svg.edit.client.command.CommandFactorySelector;
28  import org.vectomatic.svg.edit.client.command.CommandStore;
29  import org.vectomatic.svg.edit.client.command.ICommand;
30  import org.vectomatic.svg.edit.client.command.ICommandFactory;
31  import org.vectomatic.svg.edit.client.command.IFactoryInstantiator;
32  import org.vectomatic.svg.edit.client.engine.Grid;
33  import org.vectomatic.svg.edit.client.engine.SVGModel;
34  import org.vectomatic.svg.edit.client.event.ActivateWindowEvent;
35  import org.vectomatic.svg.edit.client.event.ActivateWindowHandler;
36  import org.vectomatic.svg.edit.client.event.CommandFactorySelectorChangeEvent;
37  import org.vectomatic.svg.edit.client.event.CommandFactorySelectorChangeHandler;
38  import org.vectomatic.svg.edit.client.event.DeactivateWindowEvent;
39  import org.vectomatic.svg.edit.client.event.DeactivateWindowHandler;
40  import org.vectomatic.svg.edit.client.event.SelectionChangedProcessor;
41  import org.vectomatic.svg.edit.client.event.SelectionChangedProxy;
42  import org.vectomatic.svg.edit.client.gxt.widget.BlurComboBox;
43  import org.vectomatic.svg.edit.client.gxt.widget.PaintCell;
44  import org.vectomatic.svg.edit.client.gxt.widget.RedoCommandMenuItem;
45  import org.vectomatic.svg.edit.client.gxt.widget.UndoCommandMenuItem;
46  import org.vectomatic.svg.edit.client.model.svg.CssContextModel;
47  
48  import com.extjs.gxt.ui.client.data.BeanModel;
49  import com.extjs.gxt.ui.client.data.ChangeEvent;
50  import com.extjs.gxt.ui.client.data.ChangeListener;
51  import com.extjs.gxt.ui.client.event.ButtonEvent;
52  import com.extjs.gxt.ui.client.event.MenuEvent;
53  import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
54  import com.extjs.gxt.ui.client.event.SelectionListener;
55  import com.extjs.gxt.ui.client.store.ListStore;
56  import com.extjs.gxt.ui.client.store.StoreEvent;
57  import com.extjs.gxt.ui.client.store.StoreListener;
58  import com.extjs.gxt.ui.client.util.Format;
59  import com.extjs.gxt.ui.client.widget.Label;
60  import com.extjs.gxt.ui.client.widget.Status;
61  import com.extjs.gxt.ui.client.widget.WidgetComponent;
62  import com.extjs.gxt.ui.client.widget.button.Button;
63  import com.extjs.gxt.ui.client.widget.button.SplitButton;
64  import com.extjs.gxt.ui.client.widget.form.ComboBox.TriggerAction;
65  import com.extjs.gxt.ui.client.widget.menu.CheckMenuItem;
66  import com.extjs.gxt.ui.client.widget.menu.Menu;
67  import com.extjs.gxt.ui.client.widget.toolbar.SeparatorToolItem;
68  import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
69  import com.google.gwt.core.client.GWT;
70  import com.google.gwt.dom.client.Style.Position;
71  import com.google.gwt.dom.client.Style.Unit;
72  import com.google.gwt.event.dom.client.ClickEvent;
73  import com.google.gwt.event.dom.client.ClickHandler;
74  import com.google.gwt.user.client.ui.AbstractImagePrototype;
75  
76  /**
77   * Class to manage the command toolbar displayed at the bottom of the screen.
78   * @author laaglu
79   */
80  public class CommandFactoryToolBar extends ToolBar implements CommandFactorySelectorChangeHandler, ActivateWindowHandler, DeactivateWindowHandler, SelectionChangedProcessor<IFactoryInstantiator<?>> {
81  	/**
82  	 * The command factory selector (global to the app)
83  	 */
84  	private CommandFactorySelector factorySelector;
85  	/**
86  	 * The command list combo (global to the app)
87  	 */
88  	private BlurComboBox<IFactoryInstantiator<?>> factoryCombo;
89  	/**
90  	 * Button to cancel the current command
91  	 */
92  	private Button cancelButton;
93  	/**
94  	 * The undo button
95  	 */
96  	private SplitButton undoButton;
97  	/**
98  	 * The redo button
99  	 */
100 	private SplitButton redoButton;
101 	/**
102 	 * The undo button menu
103 	 */
104 	private Menu undoMenu;
105 	/**
106 	 * The redo button menu
107 	 */
108 	private Menu redoMenu;
109 	/**
110 	 * A status line to display command instructions
111 	 */
112 	private Status status;
113 	/**
114 	 * The command store of the currently selected model
115 	 */
116 	private CommandStore currentCommandStore;
117 	/**
118 	 * True if events are disabled
119 	 */
120 	private boolean silent;
121 	/**
122 	 * Displays the current stroke paint
123 	 */
124 	private PaintCell strokeCell;
125 	/**
126 	 * Displays the current fill paint
127 	 */
128 	private PaintCell fillCell;
129 	/**
130 	 * Grid menu
131 	 */
132 	private SplitButton gridButton;
133 	private CheckMenuItem showGridItem; 
134 	private CheckMenuItem showGuidesItem; 
135 	private CheckMenuItem snapToGridItem; 
136 	/**
137 	 * The x coordinate label 
138 	 */
139 	private Label xLabel;
140 	/**
141 	 * The y coordinate label 
142 	 */
143 	private Label yLabel;
144 
145 	private StoreListener<BeanModel> commandStoreListener = new StoreListener<BeanModel>() {
146 		public void storeAdd(StoreEvent<BeanModel> se) {
147 			updateUndoRedo();
148 		}
149 
150 		public void storeFilter(StoreEvent<BeanModel> se) {
151 			updateUndoRedo();
152 		}
153 	};
154 
155 	public CommandFactoryToolBar(ListStore<IFactoryInstantiator<?>> factoryStore, CommandFactorySelector factoryStack) {
156 		setSpacing(2);
157 		this.factorySelector = factoryStack;
158 		factoryStack.addCommandFactoryChangeHandler(this);
159 		AppConstants constants = AppConstants.INSTANCE;
160 		factoryCombo = new BlurComboBox<IFactoryInstantiator<?>>();
161 		factoryCombo.setEmptyText(constants.selectCommand());  
162 		factoryCombo.setStore(factoryStore);
163 		factoryCombo.setDisplayField(IFactoryInstantiator.NAME);
164 		factoryCombo.setItemSelector("div.command-factory");  
165 		factoryCombo.setTemplate(getTemplate());  
166 	    factoryCombo.setWidth(200);
167 	    factoryCombo.setMinListWidth(500);
168 	    factoryCombo.setPageSize(5);
169 		factoryCombo.setTypeAhead(true);
170 		factoryCombo.setTriggerAction(TriggerAction.ALL);
171 		factoryCombo.addSelectionChangedListener(new SelectionChangedProxy<IFactoryInstantiator<?>>(this));
172 		cancelButton = new Button(constants.cancelButton());
173 		cancelButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
174 			@Override
175 			public void componentSelected(ButtonEvent ce) {
176 				cancelCommandFactory();
177 			}
178 		});
179 		cancelButton.setEnabled(false);
180 		undoButton = new SplitButton();
181 		undoButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.undo()));
182 		undoButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
183 			@Override
184 			public void componentSelected(ButtonEvent ce) {
185 				undoCommand();
186 			}
187 		});
188 		undoMenu = new Menu();
189 		undoButton.setMenu(undoMenu);
190 		redoButton = new SplitButton();
191 		redoButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.redo()));
192 		redoButton.addSelectionListener(new SelectionListener<ButtonEvent>() {
193 			@Override
194 			public void componentSelected(ButtonEvent ce) {
195 				redoCommand();
196 			}
197 		});
198 		redoMenu = new Menu();
199 		redoButton.setMenu(redoMenu);
200 		status = new Status();
201 		Label strokeLabel = new Label(constants.stroke() + ":");
202 		ClickHandler clickHandler = new ClickHandler() {
203 			@Override
204 			public void onClick(ClickEvent event) {
205 				SVGModel model = SvgrealApp.getApp().getActiveModel();
206 				if (model != null) {
207 					model.getSelectionModel().deselectAll();
208 				}
209 				SvgrealApp.getApp().inspector();
210 			}
211 		};
212 		strokeCell = new PaintCell();
213 		strokeCell.getStyle().setWidth(15, Unit.PX);
214 		strokeCell.getStyle().setHeight(15, Unit.PX);
215 		strokeCell.addClickHandler(clickHandler);
216 		strokeCell.getElement().getStyle().setPosition(Position.RELATIVE);
217 		strokeCell.getElement().getStyle().setTop(2, Unit.PX);
218 		Label fillLabel = new Label(constants.fill() + ":");
219 		fillCell = new PaintCell();
220 		fillCell.getStyle().setWidth(15, Unit.PX);
221 		fillCell.getStyle().setHeight(15, Unit.PX);
222 		fillCell.addClickHandler(clickHandler);
223 		fillCell.getElement().getStyle().setPosition(Position.RELATIVE);
224 		fillCell.getElement().getStyle().setTop(2, Unit.PX);
225 		updatePaint();
226 		SvgrealApp.getApp().getCssContext().addChangeListener(new ChangeListener() {
227 			@Override
228 			public void modelChanged(ChangeEvent event) {
229 				updatePaint();
230 			}
231 		});
232 		showGridItem = new CheckMenuItem(AppConstants.INSTANCE.showGrid());
233 		showGridItem.addSelectionListener(new SelectionListener<MenuEvent>() {
234 			@Override
235 			public void componentSelected(MenuEvent ce) {
236 				Grid grid = SvgrealApp.getApp().getActiveModel().getGrid();
237 				grid.setShowsGrid(((CheckMenuItem)ce.getItem()).isChecked());
238 			}
239 		});
240 		showGuidesItem = new CheckMenuItem(AppConstants.INSTANCE.showGuides());
241 		showGuidesItem.addSelectionListener(new SelectionListener<MenuEvent>() {
242 			@Override
243 			public void componentSelected(MenuEvent ce) {
244 				Grid grid = SvgrealApp.getApp().getActiveModel().getGrid();
245 				grid.setShowsGuides(((CheckMenuItem)ce.getItem()).isChecked());
246 			}
247 		});
248 		snapToGridItem = new CheckMenuItem(AppConstants.INSTANCE.snapToGrid());
249 		snapToGridItem.addSelectionListener(new SelectionListener<MenuEvent>() {
250 			@Override
251 			public void componentSelected(MenuEvent ce) {
252 				Grid grid = SvgrealApp.getApp().getActiveModel().getGrid();
253 				grid.setSnapsToGrid(((CheckMenuItem)ce.getItem()).isChecked());
254 			}
255 		});
256 		Menu gridMenu = new Menu();
257 		gridMenu.add(showGridItem);
258 		gridMenu.add(showGuidesItem);
259 		gridMenu.add(snapToGridItem);
260 		gridButton = new SplitButton(AppConstants.INSTANCE.grid());
261 		gridButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.grid()));
262 		gridButton.setMenu(gridMenu);
263 		
264 		add(strokeLabel);
265 		add(new WidgetComponent(strokeCell));
266 		add(fillLabel);
267 		add(new WidgetComponent(fillCell));
268 		add(new SeparatorToolItem());
269 		add(gridButton);
270 		add(new SeparatorToolItem());
271 		add(factoryCombo);
272 		add(new SeparatorToolItem());
273 		add(undoButton);
274 		add(redoButton);
275 		add(new SeparatorToolItem());
276 		add(cancelButton);
277 		add(new SeparatorToolItem());
278 		add(status);
279 		SvgrealApp.getApp().addActivateWindowHandler(this);
280 		SvgrealApp.getApp().addDeactivateWindowHandler(this);
281 	}
282 	
283 	private void updatePaint() {
284 		CssContextModel context = SvgrealApp.getApp().getCssContext();
285 		strokeCell.setPaint(context.<OMSVGPaint>get(SVGConstants.CSS_STROKE_PROPERTY));
286 		fillCell.setPaint(context.<OMSVGPaint>get(SVGConstants.CSS_FILL_PROPERTY));
287 	}
288 	
289 	@Override
290 	public boolean processSelectionChanged(SelectionChangedEvent<IFactoryInstantiator<?>> se) {
291 		List<IFactoryInstantiator<?>> selection = se.getSelection();
292 		GWT.log("CommandFactoryToolBar.selectionChanged(" + selection + ")");
293 		if (!silent) {
294 			if (selection.size() == 1) {
295 				// Terminate current factory if needed.
296 				ICommandFactory factory = factorySelector.getActiveFactory();
297 				if (factory != null) {
298 					factory.stop();
299 				}
300 				selection.get(0).create().start(CommandFactoryToolBar.this);
301 			}
302 		}
303 		factoryCombo.triggerBlur();
304 		return true;
305 	}
306 
307 	private native String getTemplate() /*-{ 
308 		return [ 
309 		'<tpl for="."><div class="command-factory">', 
310 		'<h3><span>{name}</h3>', 
311 		'{description}', 
312 		'</div></tpl>' 
313 		].join(""); 
314 	}-*/;
315 
316 	@Override
317 	public void onChange(CommandFactorySelectorChangeEvent event) {
318 		silent = true;
319 		ICommandFactory factory = event.getCommandFactory();
320 		GWT.log("CommandFactoryToolBar.onChange(" + factory + ")");
321 		if (factory != null) {
322 			factoryCombo.disableEvents(true);
323 			factoryCombo.setValue(factory.getInstantiator());
324 			factoryCombo.disableEvents(false);
325 			status.setText(factory.getStatus());
326 		} else {
327 			factoryCombo.setValue(null);
328 			factoryCombo.clearSelections();
329 			status.setText("");
330 		}
331 		cancelButton.setEnabled(factorySelector.getActiveFactory() != null);
332 		silent = false;
333 	}
334 
335 	private void updateUndoRedo() {
336 		GWT.log("CommandFactoryToolBar.updateUndoRedo()");
337 		undoButton.setEnabled(currentCommandStore != null && currentCommandStore.canUndo());
338 		redoButton.setEnabled(currentCommandStore != null && currentCommandStore.canRedo());
339 		if (currentCommandStore != null) {
340 			String undoTooltip = null;
341 			if (currentCommandStore.canUndo()) {
342 				undoMenu.removeAll();
343 				for (BeanModel command : currentCommandStore.getUndoCommands()) {
344 					undoMenu.add(new UndoCommandMenuItem(currentCommandStore, command));
345 				}
346 				undoButton.setMenu(undoMenu);
347 				undoTooltip = Format.capitalize(
348 					AppConstants.INSTANCE.undoButton() 
349 					+ " " 
350 					+ ((ICommand)currentCommandStore.getUndoCommand().getBean()).getDescription());
351 			}
352 			undoButton.setToolTip(undoTooltip);
353 			String redoTooltip = null;
354 			if (currentCommandStore.canRedo()) {
355 				redoMenu.removeAll();
356 				for (BeanModel command : currentCommandStore.getRedoCommands()) {
357 					redoMenu.add(new RedoCommandMenuItem(currentCommandStore, command));
358 				}
359 				redoButton.setMenu(redoMenu);
360 				redoTooltip = Format.capitalize(
361 					AppConstants.INSTANCE.redoButton() 
362 					+ " " 
363 					+ ((ICommand)currentCommandStore.getRedoCommand().getBean()).getDescription());
364 			}
365 			redoButton.setToolTip(redoTooltip);
366 		}
367 	}
368 
369 	public void updateStatus() {
370 		ICommandFactory currentFactory = factorySelector.getActiveFactory();
371 		status.setText(currentFactory != null ? currentFactory.getStatus() : "");
372 	}
373 	
374 	private void cancelCommandFactory() {
375 		GWT.log("CommandFactoryToolBar.cancelCommandFactory()");
376 		factorySelector.getActiveFactory().stop();
377 	}
378 	
379 	private void undoCommand() {
380 		GWT.log("CommandFactoryToolBar.undoCommand()");
381 		currentCommandStore.undo();
382 	}
383 
384 	private void redoCommand() {
385 		GWT.log("CommandFactoryToolBar.redoCommand()");
386 		currentCommandStore.redo();
387 	}
388 
389 	@Override
390 	public void onDeactivate(DeactivateWindowEvent event) {
391 		GWT.log("CommandFactoryToolBar.onDeactivate");
392 		event.getWindow().getSvgModel().getCommandStore().removeStoreListener(commandStoreListener);
393 
394 		// Udpate undo / redo menus
395 		currentCommandStore = null;
396 		updateUndoRedo();
397 		
398 		// Update the grid menu
399 		gridButton.setEnabled(false);
400 	}
401 
402 	@Override
403 	public void onActivate(ActivateWindowEvent event) {
404 		GWT.log("CommandFactoryToolBar.onActivate");
405 		SVGModel model = event.getWindow().getSvgModel();
406 		
407 		// Udpate undo / redo menus
408 		currentCommandStore = model.getCommandStore();
409 		currentCommandStore.addStoreListener(commandStoreListener);
410 		updateUndoRedo();
411 		
412 		// Update the grid menu
413 		Grid grid = model.getGrid();
414 		showGridItem.setChecked(grid.showsGrid(), true);
415 		showGuidesItem.setChecked(grid.showsGuides(), true);
416 		snapToGridItem.setChecked(grid.snapsToGrid(), true);
417 		gridButton.setEnabled(true);
418 	}
419 }