View Javadoc
1   package siouxsie.desktop.commands.impl;
2   
3   import java.net.MalformedURLException;
4   import java.net.URL;
5   import java.util.ArrayList;
6   import java.util.Collection;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.LinkedHashMap;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import javax.swing.Icon;
15  import javax.swing.ImageIcon;
16  import javax.swing.JMenuBar;
17  import javax.swing.JToolBar;
18  import javax.swing.KeyStroke;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.pietschy.command.ActionCommand;
23  import org.pietschy.command.Command;
24  import org.pietschy.command.CommandGroup;
25  import org.pietschy.command.CommandManager;
26  
27  import siouxsie.desktop.commands.CommandDescription;
28  import siouxsie.desktop.commands.Face;
29  import siouxsie.desktop.commands.Group;
30  import siouxsie.desktop.commands.ICommand;
31  import siouxsie.desktop.commands.ICommandInitializer;
32  import siouxsie.desktop.commands.IExceptionHandler;
33  import siouxsie.desktop.commands.IToggleCommand;
34  
35  /**
36   * Commands handler. Creates menu bar and tool bar for
37   * applications.
38   * @author Arnaud Cogoluegnes
39   * @version $Id$
40   * @todo reimplement this basic implementation
41   * @todo handle exclusive toggle command (not very important)
42   * @todo handle sub menu (done, not good without ordering and presentation)
43   */
44  public class CommandsHandler {
45  	
46  	/** name for default menu and tool bar */
47  	private static final String DEFAULT_APP = "_siouxsie_default_app";
48  
49  	/** faces configuration */
50  	private List<Face> faces;
51  	/** groups configuration */
52  	private List<Group> groups;
53  	/** commands configuration */
54  	private List<CommandDescription> commandDescriptions;
55  	
56  	/** gui command manager listener */
57  	private List<CommandManagerListenerContribution> commandManagerListeners = new ArrayList<CommandManagerListenerContribution>();
58  	
59  	private Map<String, ActionCommand> idToCommand = new LinkedHashMap<String, ActionCommand>();
60  	
61  	/** the apps (from the app command) */
62  	private Set<String> apps = new HashSet<String>();
63  	
64  	private IExceptionHandler exceptionHandler;
65  	
66  	private CommandManager commandManager;
67  	
68  	private Map<String, JMenuBar> appsMenuBars = new HashMap<String, JMenuBar>();
69  	
70  	private Map<String, JToolBar> appsToolBars = new HashMap<String, JToolBar>();
71  	
72  	private Map<String, ICommand> siouxsieCommands = new HashMap<String, ICommand>();
73  	
74  	private static final Log LOG = LogFactory.getLog(CommandsHandler.class);
75  	
76  	private ICommandInitializer commandInitializerService;
77  	
78  	private static final String FACE_SEP_BEFORE_KEY = CommandsHandler.class.getName()+"_face_sep_before";
79  	private static final String FACE_SEP_AFTER_KEY = CommandsHandler.class.getName()+"_face_sep_after";
80  	
81  	public ICommandInitializer getCommandInitializerService() {
82  		return commandInitializerService;
83  	}
84  
85  	public void setCommandInitializerService(
86  			ICommandInitializer commandInitializerService) {
87  		this.commandInitializerService = commandInitializerService;
88  	}
89  
90  	public List<Face> getFaces() {
91  		return faces;
92  	}
93  
94  	public void setFaces(List<Face> faces) {
95  		this.faces = faces;
96  	}
97  
98  	public void init() {
99  		
100 		// gui command manager init
101 		commandManager = new CommandManager();		
102 		// add the listeners
103 		for(CommandManagerListenerContribution contrib : commandManagerListeners) {
104 			if(contrib.getHoverListener() != null) {
105 				commandManager.addHoverListener(contrib.getHoverListener());
106 			}
107 			if(contrib.getManagerListener() != null) {
108 				commandManager.addCommandManagerListener(contrib.getManagerListener());
109 			}
110 		}
111 		
112 		CommandFactory commandFactory = new CommandFactory();
113 		for(CommandDescription desc : commandDescriptions) {
114 			// command initialisation and wrapping
115 			ISiouxsieCommand command = commandFactory.createCommand(desc);
116 			
117 			// the exception handler for the wrapper
118 			command.setExceptionHandler(getExceptionHandler());					
119 			// initializes the command
120 			getCommandInitializerService().initialize(command.getCommand());			
121 			
122 			if(desc.getAppId() != null) {
123 				apps.add(desc.getAppId());
124 			}			
125 			siouxsieCommands.put(desc.getId(),command.getCommand());			
126 		}		
127 		
128 		Map<String, String> commandIdToGroup = new HashMap<String, String>();
129 		
130 		for(Face face : faces) {
131 			
132 			// retrieve the command
133 			ActionCommand command = getCommand(face.getCommandId());
134 			if(command == null) {
135 				LOG.warn("Cannot retrieve command "+face.getCommandId()+" from one of its faces.");
136 			}
137 			org.pietschy.command.Face faceImpl = null;
138 			if(face.getName() != null) {
139 				faceImpl = command.getFace(face.getName(), true);
140 			} else {
141 				faceImpl = command.getDefaultFace(true);
142 			}
143 			
144 			faceImpl.setText(face.getText());
145 			faceImpl.setDescription(face.getDescription());
146 			faceImpl.setLongDescription(face.getLongDescription());
147 			if(face.getIconPath() != null) {
148 				faceImpl.setIcon(createIcon(face.getIconType(),face.getIconPath()));
149 			}	
150 			
151 			if(face.getGroupId() != null) {
152 				commandIdToGroup.put(face.getCommandId(), face.getGroupId());
153 			}		
154 			if(face.getAccelerator() != null) {
155 				faceImpl.setAccelerator(KeyStroke.getKeyStroke(face.getAccelerator()));
156 			}
157 			// handling separators
158 			if(face.getSeparator() != null) {
159 				if(face.isSeparatorOk()) {
160 					if(face.separatorBefore()) {
161 						faceImpl.putClientProperty(FACE_SEP_BEFORE_KEY, Boolean.TRUE.toString());
162 					}
163 					if(face.separatorAfter()) {
164 						faceImpl.putClientProperty(FACE_SEP_AFTER_KEY, Boolean.TRUE.toString());
165 					}
166 				} else {
167 					LOG.warn("incorrect separator syntax '"+face.getSeparator()+"' for face "
168 							+face.getName()+" of action "+face.getCommandId()+
169 							". Separator ignored.");
170 					
171 				}
172 			}
173 			
174 		}		
175 		
176 		Map<String,ApplicationMenuBarContainer> appsMenuBar = new LinkedHashMap<String, ApplicationMenuBarContainer>();
177 		Map<String, CommandGroup> appIdToToolBar = new LinkedHashMap<String,CommandGroup>();
178 		// creates the group for each application
179 		// add the default application
180 		apps.add(DEFAULT_APP);
181 		for(String app : apps) {
182 			// menus for this application
183 			ApplicationMenuBarContainer menuBarContainer = new ApplicationMenuBarContainer();
184 			//appIdToMenuBar.put(app, menuBarContainer);
185 			appsMenuBar.put(app, menuBarContainer);
186 			
187 			// 2 iterations, one for gathering all groups and another one for subgroups
188 			// once the parents are initialized for sure			
189 			for(Group group : groups) {					
190 				LOG.info("processing group "+group.getId()+" for app "+app);
191 				if(menuBarContainer.isMainMenu(group)) {	
192 					LOG.info("group is a main group");	
193 					menuBarContainer.addMainMenu(group, app+"."+group.getId());
194 				} else {
195 					menuBarContainer.addMenu(group, app+"."+group.getId());
196 				}
197 			}
198 			
199 			for(Group group : groups) {
200 				boolean isMainMenu = menuBarContainer.isMainMenu(group.getId());
201 				LOG.info("is "+group.getId()+" main menu? "+isMainMenu);
202 				if(!isMainMenu) {
203 					// not a root/main group
204 					CommandGroup parent = menuBarContainer.getMenu(group.getGroupId());
205 					if(parent == null) {
206 						LOG.warn("cannot retrieve parent group "+group.getGroupId()+" for group "+group.getId());
207 						LOG.warn("subgroup ignored");
208 					} else {
209 						parent.add(menuBarContainer.getMenu(group.getId()));
210 					}
211 				}				
212 			}	
213 			// creates app  toolbar
214 			CommandGroup groupToolBar = new CommandGroup(commandManager,app+"."+org.pietschy.command.Face.TOOLBAR);
215 			// GUI commands bug? indeed, need to create the "toolbar" face
216 			groupToolBar.getFace(org.pietschy.command.Face.TOOLBAR,true);
217 			appIdToToolBar.put(app, groupToolBar);			
218 		}		
219 		
220 		// adding the command to each app menu bar and toolbar
221 		for(CommandDescription desc : commandDescriptions) {			
222 			
223 			//Collection<Map<String,CommandGroup>> commandGroupList;
224 			Collection<CommandGroup> toolBarList;
225 			
226 			Map<String,ApplicationMenuBarContainer> commandGroupList;
227 			
228 			// get the menu bars and tool bars we need the command to be added to
229 			if(desc.getAppId() == null) {
230 				// command for all applications
231 				commandGroupList = appsMenuBar;
232 				toolBarList = appIdToToolBar.values();
233 			} else {
234 				// application specific command
235 				// menu bar
236 				commandGroupList = new HashMap<String, ApplicationMenuBarContainer>();
237 				commandGroupList.put(desc.getAppId(), appsMenuBar.get(desc.getAppId()));
238 				// toolbar list
239 				CommandGroup appToolBar = appIdToToolBar.get(desc.getAppId());
240 				toolBarList = new ArrayList<CommandGroup>();
241 				toolBarList.add(appToolBar);
242 			}
243 			
244 			Command command = idToCommand.get(desc.getId());
245 			
246 			// menu bar
247 			for(ApplicationMenuBarContainer container : commandGroupList.values()) {
248 				String groupId = commandIdToGroup.get(desc.getId());
249 				if(groupId != null) {
250 					CommandGroup group = container.getMenu(groupId);
251 					if(command.getDefaultFace().getClientProperty(FACE_SEP_BEFORE_KEY) != null) {
252 						group.addSeparator();
253 					}
254 					group.add(command);
255 					if(command.getDefaultFace().getClientProperty(FACE_SEP_AFTER_KEY) != null) {
256 						group.addSeparator();
257 					}
258 				}
259 			}
260 			
261 			// in the toolbar?			
262 			if(command.faceExists(org.pietschy.command.Face.TOOLBAR)) {
263 				for(CommandGroup group : toolBarList) {		
264 					if(command.getFace(org.pietschy.command.Face.TOOLBAR).getClientProperty(FACE_SEP_BEFORE_KEY) != null) {
265 						group.addSeparator();
266 					}
267 					group.add(command);
268 					if(command.getFace(org.pietschy.command.Face.TOOLBAR).getClientProperty(FACE_SEP_AFTER_KEY) != null) {
269 						group.addSeparator();
270 					}
271 				}
272 			}		
273 		}
274 		
275 		// construct the menu bar
276 		for(String appId : appsMenuBar.keySet()) {
277 			ApplicationMenuBarContainer container = appsMenuBar.get(appId);
278 			CommandGroup menuBarGroup = new CommandGroup(commandManager,appId+".menubar");
279 			for(CommandGroup group : container.mainMenus.values()) {
280 				if(group.getMemberCount() > 0) {
281 					menuBarGroup.add(group);
282 				}
283 			}
284 			// sub group can be in parent but without action in it, must check that
285 			// and remove the sub menu
286 			for(Group group : container.groups.values()) {
287 				if(!container.isMainMenu(group)) {
288 					CommandGroup gImpl = container.getMenu(group.getId());
289 					if(gImpl.getMemberCount() == 0) {
290 						// retrieve the parent
291 						CommandGroup parent = container.getMenu(group.getGroupId());
292 						parent.remove(gImpl);
293 					}
294 				}
295 			}
296 			appsMenuBars.put(appId, menuBarGroup.createMenuBar());
297 		}	
298 		
299 		for(String appId : appIdToToolBar.keySet()) {			
300 			CommandGroup toolBarGroup = appIdToToolBar.get(appId);
301 			appsToolBars.put(appId, toolBarGroup.createToolBar());			
302 		}
303 		
304 		
305 	}
306 
307 	public List<CommandDescription> getCommandDescriptions() {
308 		return commandDescriptions;
309 	}
310 
311 	public void setCommandDescriptions(List<CommandDescription> commandDescriptions) {
312 		this.commandDescriptions = commandDescriptions;
313 	}
314 
315 	public List<Group> getGroups() {
316 		return groups;
317 	}
318 
319 	public void setGroups(List<Group> groups) {
320 		this.groups = groups;
321 	}
322 	
323 	private Icon createIcon(String type,String path) {
324 		Icon icon = null;
325 		if("url".equals(type)) {
326 			try {
327 				icon = new ImageIcon(new URL(path));
328 			} catch (MalformedURLException e) {
329 				LOG.error("Could not load icon.",e);
330 			}
331 		} else if("classpath".equals(type)) {
332 			icon = new ImageIcon(commandManager.getClassLoader().getResource(path));
333 		} else if("file".equals(type)) {
334 			icon = new ImageIcon(path);
335 		}
336 		return icon;
337 	}
338 	
339 	/**
340 	 * Return a command from its id.
341 	 * @param commandId
342 	 * @return
343 	 */
344 	public ActionCommand getCommand(String commandId) {
345 		return idToCommand.get(commandId);
346 	}
347 	
348 	public JMenuBar getAppMenuBar(String appId) {
349 		JMenuBar bar = appsMenuBars.get(appId);
350 		if(bar == null) {
351 			bar = appsMenuBars.get(DEFAULT_APP);
352 		}
353 		return bar;
354 	}
355 	
356 	public JToolBar getAppToolBar(String appId) {
357 		JToolBar bar = appsToolBars.get(appId);
358 		if(bar == null) {
359 			bar = appsToolBars.get(DEFAULT_APP);
360 		}
361 		return bar;
362 	}
363 	
364 	public ICommand getSiouxsieCommand(String id) {
365 		return siouxsieCommands.get(id);
366 	}
367 
368 	public List<CommandManagerListenerContribution> getCommandManagerListeners() {
369 		return commandManagerListeners;
370 	}
371 
372 	public void setCommandManagerListeners(
373 			List<CommandManagerListenerContribution> commandManagerListeners) {
374 		this.commandManagerListeners = commandManagerListeners;
375 	}
376 
377 	public IExceptionHandler getExceptionHandler() {
378 		return exceptionHandler;
379 	}
380 
381 	public void setExceptionHandler(IExceptionHandler exceptionHandler) {
382 		this.exceptionHandler = exceptionHandler;
383 	}
384 	
385 	/**
386 	 * Helper class for dealing with groups and sub groups.
387 	 * @author Arnaud Cogoluegnes
388 	 * @version $Id$
389 	 */
390 	class ApplicationMenuBarContainer {
391 		
392 		private Map<String,CommandGroup> mainMenus = new LinkedHashMap<String, CommandGroup>();
393 		
394 		private Map<String,CommandGroup> allMenus = new LinkedHashMap<String, CommandGroup>();
395 		
396 		private Map<String, Group> groups = new LinkedHashMap<String, Group>();
397 		
398 		public void addMainMenu(Group group, String guiCommandId) {
399 			CommandGroup gImpl = constructCommandGroup(group, guiCommandId);			
400 			mainMenus.put(group.getId(), gImpl);
401 			groups.put(group.getId(), group);
402 			allMenus.put(group.getId(), gImpl);
403 		}
404 		
405 		public void addMenu(Group group, String guiCommandId) {
406 			CommandGroup gImpl = constructCommandGroup(group, guiCommandId);
407 			groups.put(group.getId(), group);
408 			allMenus.put(group.getId(), gImpl);
409 		}
410 		
411 		private CommandGroup constructCommandGroup(Group group, String guiCommandId ) {
412 			CommandGroup groupImpl = new CommandGroup(commandManager,guiCommandId);			
413 			org.pietschy.command.Face face = groupImpl.getDefaultFace(true);			
414 			face.setText(group.getText());
415 			face.setDescription(group.getDescription());
416 			face.setLongDescription(group.getLongDescription());
417 			if(group.getIconPath() != null) {
418 				face.setIcon(createIcon(group.getIconType(),group.getIconPath()));
419 			}			
420 			return groupImpl;
421 		}
422 		
423 		public CommandGroup getMenu(String id) {
424 			return allMenus.get(id);
425 		}
426 		
427 		public boolean isMainMenu(String id) {
428 			Group group = groups.get(id);
429 			return group != null && isMainMenu(group);
430 		}
431 		
432 		public boolean isMainMenu(Group group) {
433 			return group.getGroupId() == null || group.getGroupId().trim().length() == 0;
434 		}
435 		
436 	}
437 	
438 	class CommandFactory {
439 		
440 		public ISiouxsieCommand createCommand(CommandDescription desc) {
441 			// the command
442 			ICommand command = null;
443 			if(desc.getCommand() != null) {
444 				command = desc.getCommand();
445 			} else {
446 				try {
447 					command = desc.getCommandClass().newInstance();
448 				} catch (InstantiationException e) {
449 					LOG.error("Could not create command "+desc.getId(),e);
450 				} catch (IllegalAccessException e) {
451 					LOG.error("Could not create command "+desc.getId(),e);
452 				}
453 			}				
454 			// wrap the command around
455 			
456 			// action or toggle command?
457 			ISiouxsieCommand res = null;
458 			if(command instanceof IToggleCommand) {
459 				// toggle 
460 				res = new SiouxsieToggleCommand(commandManager,desc.getId());
461 				// stock the command
462 				idToCommand.put(desc.getId(), (SiouxsieToggleCommand) res);	
463 				((SiouxsieToggleCommand)res).setCommand((IToggleCommand)command);
464 				if(desc.getAccelerator() != null) {
465 					((SiouxsieToggleCommand)res).getDefaultFace(true).setAccelerator(
466 							KeyStroke.getKeyStroke(desc.getAccelerator())
467 					);
468 				}
469 			} else {
470 				// action
471 				res = new SiouxsieActionCommand(commandManager,desc.getId());	
472 				// stock the command
473 				idToCommand.put(desc.getId(), (SiouxsieActionCommand) res);	
474 				((SiouxsieActionCommand)res).setCommand(command);	
475 				if(desc.getAccelerator() != null) {
476 					((SiouxsieActionCommand)res).getDefaultFace(true).setAccelerator(
477 							KeyStroke.getKeyStroke(desc.getAccelerator())
478 					);
479 				}
480 			}				
481 			return res;
482 		}
483 		
484 	}
485 	
486 }