/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 193 - (show annotations)
Wed May 29 10:33:41 2002 UTC (22 years, 9 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 22468 byte(s)
	* Thuban/UI/mainwindow.py (MainWindow.bind_command_events): Bind
	the events for a comnmand.
	(MainWindow.add_toolbar_command, MainWindow.add_menu_command):
	Call bind_command_events to bind the events. add_toolbar_command
	can now bind events too so that it's possible to have commands
	that are only available through the toolbar.
	(MainWindow.init_ids): New instance variable events_bound to keep
	track of for which commands events have been bound.

1 # Copyright (C) 2001, 2002 by Intevation GmbH
2 # Authors:
3 # Jan-Oliver Wagner <[email protected]>
4 # Bernhard Herzog <[email protected]>
5 #
6 # This program is free software under the GPL (>=v2)
7 # Read the file COPYING coming with Thuban for details.
8
9 """
10 The main window
11 """
12
13 __version__ = "$Revision$"
14
15 import os
16
17 from wxPython.wx import *
18
19 import Thuban
20 from Thuban.Model.session import create_empty_session
21 from Thuban.Model.layer import Layer
22 from Thuban.Model.color import Color
23 from Thuban.Model.proj import Projection
24
25 import view
26 import tree
27 import proj4dialog
28 import tableview, identifyview
29 from menu import Menu
30
31 import main
32 from command import registry, Command
33 from messages import SELECTED_SHAPE, VIEW_POSITION
34
35
36 # the directory where the toolbar icons are stored
37 bitmapdir = os.path.join(Thuban.__path__[0], os.pardir, "Resources", "Bitmaps")
38 bitmapext = ".xpm"
39
40
41 class MainWindow(wxFrame):
42
43 def __init__(self, parent, ID, interactor):
44 wxFrame.__init__(self, parent, ID, 'Thuban',
45 wxDefaultPosition, wxSize(400, 300))
46
47 self.interactor = interactor
48
49 self.CreateStatusBar()
50 self.SetStatusText("This is the wxPython-based "
51 "Graphical User Interface for exploring geographic data")
52
53 self.identify_view = None
54
55 self.init_ids()
56
57 # creat the menubar from the main_menu description
58 self.SetMenuBar(self.build_menu_bar(main_menu))
59
60 # Similarly, create the toolbar from main_toolbar
61 toolbar = self.build_toolbar(main_toolbar)
62 # call Realize to make sure that the tools appear.
63 toolbar.Realize()
64
65 # Create the map canvas
66 canvas = view.MapCanvas(self, -1, interactor)
67 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
68 self.canvas = canvas
69
70 self.init_dialogs()
71
72 interactor.Subscribe(SELECTED_SHAPE, self.identify_view_on_demand)
73
74 EVT_CLOSE(self, self.OnClose)
75
76 def init_ids(self):
77 """Initialize the ids"""
78 self.current_id = 6000
79 self.id_to_name = {}
80 self.name_to_id = {}
81 self.events_bound = {}
82
83 def get_id(self, name):
84 """Return the wxWindows id for the command named name.
85
86 Create a new one if there isn't one yet"""
87 ID = self.name_to_id.get(name)
88 if ID is None:
89 ID = self.current_id
90 self.current_id = self.current_id + 1
91 self.name_to_id[name] = ID
92 self.id_to_name[ID] = name
93 return ID
94
95 def bind_command_events(self, command, ID):
96 """Bind the necessary events for the given command and ID"""
97 if not self.events_bound.has_key(ID):
98 # the events haven't been bound yet
99 EVT_MENU(self, ID, self.invoke_command)
100 if command.IsDynamic():
101 EVT_UPDATE_UI(self, ID, self.update_command_ui)
102
103 def build_menu_bar(self, menudesc):
104 """Build and return the menu bar from the menu description"""
105 menu_bar = wxMenuBar()
106
107 for item in menudesc.items:
108 # here the items must all be Menu instances themselves
109 menu_bar.Append(self.build_menu(item), item.title)
110
111 return menu_bar
112
113 def build_menu(self, menudesc):
114 """Build and return a wxMenu from a menudescription"""
115 wxmenu = wxMenu()
116 last = None
117 for item in menudesc.items:
118 # here the items must all be Menu instances themselves
119 if item is None:
120 # a separator. Only add one if the last item was not a
121 # separator
122 if last is not None:
123 wxmenu.AppendSeparator()
124 elif isinstance(item, Menu):
125 # a submenu
126 wxmenu.AppendMenu(wxNewId(), item.title, self.build_menu(item))
127 else:
128 # must the name the name of a command
129 self.add_menu_command(wxmenu, item)
130 last = item
131 return wxmenu
132
133 def build_toolbar(self, toolbardesc):
134 """Build and return the main toolbar window from a toolbar description
135
136 The parameter should be an instance of the Menu class but it
137 should not contain submenus.
138 """
139 toolbar = self.CreateToolBar(wxTB_3DBUTTONS)
140
141 # set the size of the tools' bitmaps. Not needed on wxGTK, but
142 # on Windows, although it doesn't work very well there. It seems
143 # that only 16x16 icons are really supported on windows.
144 # We probably shouldn't hardwire the bitmap size here.
145 toolbar.SetToolBitmapSize(wxSize(24, 24))
146
147 for item in toolbardesc.items:
148 if item is None:
149 toolbar.AddSeparator()
150 else:
151 # assume it's a string.
152 self.add_toolbar_command(toolbar, item)
153
154 return toolbar
155
156 def add_menu_command(self, menu, name):
157 """Add the command with name name to the menu menu.
158
159 If name is None, add a separator.
160 """
161 if name is None:
162 menu.AppendSeparator()
163 else:
164 command = registry.Command(name)
165 if command is not None:
166 ID = self.get_id(name)
167 menu.Append(ID, command.Title(), command.HelpText(),
168 command.IsCheckCommand())
169 self.bind_command_events(command, ID)
170 else:
171 print "Unknown command %s" % name
172
173 def add_toolbar_command(self, toolbar, name):
174 """Add the command with name name to the toolbar toolbar.
175
176 If name is None, add a separator.
177 """
178 # Assume that all toolbar commands are also menu commmands so
179 # that we don't have to add the event handlers here
180 if name is None:
181 toolbar.AddSeparator()
182 else:
183 command = registry.Command(name)
184 if command is not None:
185 ID = self.get_id(name)
186 filename = os.path.join(bitmapdir, command.Icon()) + bitmapext
187 bitmap = wxBitmap(filename, wxBITMAP_TYPE_XPM)
188 toolbar.AddTool(ID, bitmap,
189 shortHelpString = command.HelpText(),
190 isToggle = command.IsCheckCommand())
191 self.bind_command_events(command, ID)
192 else:
193 print "Unknown command %s" % name
194
195 def invoke_command(self, event):
196 name = self.id_to_name.get(event.GetId())
197 if name is not None:
198 command = registry.Command(name)
199 command.Execute(self)
200 else:
201 print "Unknown command ID %d" % event.GetId()
202
203 def update_command_ui(self, event):
204 #print "update_command_ui", self.id_to_name[event.GetId()]
205 command = registry.Command(self.id_to_name[event.GetId()])
206 if command is not None:
207 event.Enable(command.Sensitive(self))
208 event.SetText(command.DynText(self))
209 if command.IsCheckCommand():
210 event.Check(command.Checked(self))
211
212 def RunMessageBox(self, title, text, flags = wxOK | wxICON_INFORMATION):
213 """Run a modal message box with the given text, title and flags
214 and return the result"""
215 dlg = wxMessageDialog(self, text, title, flags)
216 result = dlg.ShowModal()
217 dlg.Destroy()
218 return result
219
220 def init_dialogs(self):
221 """Initialize the dialog handling"""
222 # The mainwindow maintains a dict mapping names to open
223 # non-modal dialogs. The dialogs are put into this dict when
224 # they're created and removed when they're closed
225 self.dialogs = {}
226
227 def add_dialog(self, name, dialog):
228 if self.dialogs.has_key(name):
229 raise RuntimeError("The Dialog named %s is already open" % name)
230 self.dialogs[name] = dialog
231
232 def dialog_open(self, name):
233 return self.dialogs.has_key(name)
234
235 def remove_dialog(self, name):
236 del self.dialogs[name]
237
238 def get_open_dialog(self, name):
239 return self.dialogs.get(name)
240
241 def view_position_changed(self):
242 pos = self.canvas.CurrentPosition()
243 if pos is not None:
244 text = "(%10.10g, %10.10g)" % pos
245 else:
246 text = ""
247 self.SetStatusText(text)
248
249 def save_modified_session(self, can_veto = 1):
250 """If the current session has been modified, ask the user
251 whether to save it and do so if requested. Return the outcome of
252 the dialog (either wxID_OK, wxID_CANCEL or wxID_NO). If the
253 dialog wasn't run return wxID_NO.
254
255 If the can_veto parameter is true (default) the dialog includes
256 a cancel button, otherwise not.
257 """
258 if main.app.session.WasModified():
259 flags = wxYES_NO | wxICON_QUESTION
260 if can_veto:
261 flags = flags | wxCANCEL
262 result = self.RunMessageBox("Exit",
263 ("The session has been modified."
264 " Do you want to save it?"),
265 flags)
266 if result == wxID_YES:
267 self.SaveSession()
268 else:
269 result = wxID_NO
270 return result
271
272 def NewSession(self):
273 self.save_modified_session()
274 main.app.SetSession(create_empty_session())
275
276 def OpenSession(self):
277 self.save_modified_session()
278 dlg = wxFileDialog(self, "Select a session file", ".", "",
279 "*.thuban", wxOPEN)
280 if dlg.ShowModal() == wxID_OK:
281 main.app.OpenSession(dlg.GetPath())
282 dlg.Destroy()
283
284 def SaveSession(self):
285 if main.app.session.filename == None:
286 self.SaveSessionAs()
287 main.app.SaveSession()
288
289 def SaveSessionAs(self):
290 dlg = wxFileDialog(self, "Enter a filename for session", ".", "",
291 "*.thuban", wxOPEN)
292 if dlg.ShowModal() == wxID_OK:
293 main.app.session.SetFilename(dlg.GetPath())
294 main.app.SaveSession()
295 dlg.Destroy()
296
297 def Exit(self):
298 self.Close(false)
299
300 def OnClose(self, event):
301 result = self.save_modified_session(can_veto = event.CanVeto())
302 if result == wxID_CANCEL:
303 event.Veto()
304 else:
305 self.Destroy()
306
307 def SetMap(self, map):
308 self.canvas.SetMap(map)
309
310 def ShowSessionTree(self):
311 name = "session_tree"
312 dialog = self.get_open_dialog(name)
313 if dialog is None:
314 dialog = tree.SessionTreeView(self, main.app, name)
315 self.add_dialog(name, dialog)
316 dialog.Show(true)
317 else:
318 # FIXME: bring dialog to front here
319 pass
320
321 def About(self):
322 self.RunMessageBox("About",
323 ("Thuban is a program for\n"
324 "exploring geographic data.\n"
325 "Copyright (C) 2001 Intevation GmbH.\n"
326 "Thuban is licensed under the GPL"),
327 wxOK | wxICON_INFORMATION)
328
329 def AddLayer(self):
330 dlg = wxFileDialog(self, "Select a data file", ".", "", "*.*",
331 wxOPEN)
332 if dlg.ShowModal() == wxID_OK:
333 filename = dlg.GetPath()
334 title = os.path.splitext(os.path.basename(filename))[0]
335 layer = Layer(title, filename)
336 map = self.canvas.Map()
337 has_layers = map.HasLayers()
338 try:
339 map.AddLayer(layer)
340 except IOError:
341 # the layer couldn't be opened
342 self.RunMessageBox("Add Layer",
343 "Can't open the file '%s'." % filename)
344 else:
345 if not has_layers:
346 # if we're adding a layer to an empty map, for the
347 # new map to the window
348 self.canvas.FitMapToWindow()
349 dlg.Destroy()
350
351 def RemoveLayer(self):
352 layer = self.current_layer()
353 if layer is not None:
354 self.canvas.Map().RemoveLayer(layer)
355
356 def RaiseLayer(self):
357 layer = self.current_layer()
358 if layer is not None:
359 self.canvas.Map().RaiseLayer(layer)
360
361 def LowerLayer(self):
362 layer = self.current_layer()
363 if layer is not None:
364 self.canvas.Map().LowerLayer(layer)
365
366 def current_layer(self):
367 """Return the currently selected layer.
368
369 If no layer is selected, return None
370 """
371 return self.interactor.SelectedLayer()
372
373 def has_selected_layer(self):
374 """Return true if a layer is currently selected"""
375 return self.interactor.HasSelectedLayer()
376
377 def choose_color(self):
378 """Run the color selection dialog and return the selected color.
379
380 If the user cancels, return None.
381 """
382 dlg = wxColourDialog(self)
383 color = None
384 if dlg.ShowModal() == wxID_OK:
385 data = dlg.GetColourData()
386 wxc = data.GetColour()
387 color = Color(wxc.Red() / 255.0,
388 wxc.Green() / 255.0,
389 wxc.Blue() / 255.0)
390 dlg.Destroy()
391 return color
392
393 def LayerFillColor(self):
394 layer = self.current_layer()
395 if layer is not None:
396 color = self.choose_color()
397 if color is not None:
398 layer.SetFill(color)
399
400 def LayerTransparentFill(self):
401 layer = self.current_layer()
402 if layer is not None:
403 layer.SetFill(None)
404
405 def LayerOutlineColor(self):
406 layer = self.current_layer()
407 if layer is not None:
408 color = self.choose_color()
409 if color is not None:
410 layer.SetStroke(color)
411
412 def LayerNoOutline(self):
413 layer = self.current_layer()
414 if layer is not None:
415 layer.SetStroke(None)
416
417 def HideLayer(self):
418 layer = self.current_layer()
419 if layer is not None:
420 layer.SetVisible(0)
421
422 def ShowLayer(self):
423 layer = self.current_layer()
424 if layer is not None:
425 layer.SetVisible(1)
426
427 def LayerShowTable(self):
428 layer = self.current_layer()
429 if layer is not None:
430 table = layer.table
431 name = "table_view" + str(id(table))
432 dialog = self.get_open_dialog(name)
433 if dialog is None:
434 dialog = tableview.TableFrame(self, self.interactor, name,
435 "Table: %s" % layer.Title(),
436 layer, table)
437 self.add_dialog(name, dialog)
438 dialog.Show(true)
439 else:
440 # FIXME: bring dialog to front here
441 pass
442
443 def Projection(self):
444 map = self.canvas.Map()
445 proj = map.projection
446 if proj is None:
447 proj4Dlg = proj4dialog.Proj4Dialog(NULL, None, map.BoundingBox())
448 else:
449 proj4Dlg = proj4dialog.Proj4Dialog(NULL, map.projection.params,
450 map.BoundingBox())
451 if proj4Dlg.ShowModal() == wxID_OK:
452 params = proj4Dlg.GetParams()
453 if params is not None:
454 proj = Projection(params)
455 else:
456 proj = None
457 map.SetProjection(proj)
458 proj4Dlg.Destroy()
459
460 def ZoomInTool(self):
461 self.canvas.ZoomInTool()
462
463 def ZoomOutTool(self):
464 self.canvas.ZoomOutTool()
465
466 def PanTool(self):
467 self.canvas.PanTool()
468
469 def IdentifyTool(self):
470 self.canvas.IdentifyTool()
471 self.identify_view_on_demand(None, None)
472
473 def LabelTool(self):
474 self.canvas.LabelTool()
475
476 def FullExtent(self):
477 self.canvas.FitMapToWindow()
478
479 def PrintMap(self):
480 self.canvas.Print()
481
482 def identify_view_on_demand(self, layer, shape):
483 name = "identify_view"
484 if self.canvas.CurrentTool() == "IdentifyTool":
485 if not self.dialog_open(name):
486 dialog = identifyview.IdentifyView(self, self.interactor, name)
487 self.add_dialog(name, dialog)
488 dialog.Show(true)
489 else:
490 # FIXME: bring dialog to front?
491 pass
492
493 #
494 # Define all the commands available in the main window
495 #
496
497
498 # Helper functions to define common command implementations
499 def call_method(context, methodname, *args):
500 """Call the context's method methodname with args *args"""
501 apply(getattr(context, methodname), args)
502
503 def _method_command(name, title, method, helptext = "",
504 icon = "", sensitive = None):
505 """Add a command implemented by a method of the context object"""
506 registry.Add(Command(name, title, call_method, args=(method,),
507 helptext = helptext, icon = icon,
508 sensitive = sensitive))
509
510 def _tool_command(name, title, method, toolname, helptext = "",
511 icon = ""):
512 """Add a tool command"""
513 def check_current_tool(context, name=toolname):
514 return context.canvas.CurrentTool() == name
515 registry.Add(Command(name, title, call_method, args=(method,),
516 helptext = helptext, icon = icon,
517 checked = check_current_tool))
518
519 def _has_selected_layer(context):
520 """Return true if a layer is selected in the context"""
521 return context.has_selected_layer()
522
523 # File menu
524 _method_command("new_session", "&New Session", "NewSession")
525 _method_command("open_session", "&Open Session", "OpenSession")
526 _method_command("save_session", "&Save Session", "SaveSession")
527 _method_command("save_session_as", "Save Session &As", "SaveSessionAs")
528 _method_command("show_session_tree", "Show Session &Tree", "ShowSessionTree")
529 _method_command("exit", "&Exit", "Exit")
530
531 # Help menu
532 _method_command("help_about", "&About", "About")
533
534
535 # Map menu
536 _method_command("map_projection", "Pro&jection", "Projection")
537
538 _tool_command("map_zoom_in_tool", "&Zoom in", "ZoomInTool", "ZoomInTool",
539 helptext = "Switch to map-mode 'zoom-in'", icon = "zoom_in")
540 _tool_command("map_zoom_out_tool", "Zoom &out", "ZoomOutTool", "ZoomOutTool",
541 helptext = "Switch to map-mode 'zoom-out'", icon = "zoom_out")
542 _tool_command("map_pan_tool", "&Pan", "PanTool", "PanTool",
543 helptext = "Switch to map-mode 'pan'", icon = "pan")
544 _tool_command("map_identify_tool", "&Identify", "IdentifyTool", "IdentifyTool",
545 helptext = "Switch to map-mode 'identify'", icon = "identify")
546 _tool_command("map_label_tool", "&Label", "LabelTool", "LabelTool",
547 helptext = "Add/Remove labels", icon = "label")
548 _method_command("map_full_extent", "&Full extent", "FullExtent",
549 helptext = "Full Extent", icon = "fullextent")
550 _method_command("map_print", "Prin&t", "PrintMap", helptext = "Print the map")
551
552 # Layer menu
553 _method_command("layer_add", "&Add Layer", "AddLayer",
554 helptext = "Add a new layer to active map")
555 _method_command("layer_remove", "&Remove Layer", "RemoveLayer",
556 helptext = "Remove selected layer(s)",
557 sensitive = _has_selected_layer)
558 _method_command("layer_fill_color", "&Fill Color", "LayerFillColor",
559 helptext = "Set the fill color of selected layer(s)",
560 sensitive = _has_selected_layer)
561 _method_command("layer_transparent_fill", "&Transparent Fill",
562 "LayerTransparentFill",
563 helptext = "Do not fill the selected layer(s)",
564 sensitive = _has_selected_layer)
565 _method_command("layer_outline_color", "&Outline Color", "LayerOutlineColor",
566 helptext = "Set the outline color of selected layer(s)",
567 sensitive = _has_selected_layer)
568 _method_command("layer_no_outline", "&No Outline", "LayerNoOutline",
569 helptext = "Do not draw the outline of the selected layer(s)",
570 sensitive = _has_selected_layer)
571 _method_command("layer_raise", "&Raise", "RaiseLayer",
572 helptext = "Raise selected layer(s)",
573 sensitive = _has_selected_layer)
574 _method_command("layer_lower", "&Lower", "LowerLayer",
575 helptext = "Lower selected layer(s)",
576 sensitive = _has_selected_layer)
577 _method_command("layer_show", "&Show", "ShowLayer",
578 helptext = "Make selected layer(s) visible",
579 sensitive = _has_selected_layer)
580 _method_command("layer_hide", "&Hide", "HideLayer",
581 helptext = "Make selected layer(s) unvisible",
582 sensitive = _has_selected_layer)
583 _method_command("layer_show_table", "Show Ta&ble", "LayerShowTable",
584 helptext = "Show the selected layer's table",
585 sensitive = _has_selected_layer)
586
587
588 # the menu structure
589 main_menu = Menu("<main>", "<main>",
590 [Menu("file", "&File",
591 ["new_session", "open_session", None,
592 "save_session", "save_session_as", None,
593 "show_session_tree", None,
594 "exit"]),
595 Menu("map", "&Map",
596 ["layer_add", "layer_remove",
597 None,
598 "map_projection",
599 None,
600 "map_zoom_in_tool", "map_zoom_out_tool",
601 "map_pan_tool", "map_identify_tool", "map_label_tool",
602 None,
603 "map_full_extent",
604 None,
605 "map_print"]),
606 Menu("layer", "&Layer",
607 ["layer_fill_color", "layer_transparent_fill",
608 "layer_outline_color", "layer_no_outline",
609 None,
610 "layer_raise", "layer_lower",
611 None,
612 "layer_show", "layer_hide",
613 None,
614 "layer_show_table"]),
615 Menu("help", "&Help",
616 ["help_about"])])
617
618 # the main toolbar
619
620 main_toolbar = Menu("<toolbar>", "<toolbar>",
621 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
622 "map_identify_tool", "map_label_tool", "map_full_extent"])

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26