/[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 222 - (show annotations)
Thu Jul 18 13:03:08 2002 UTC (22 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 22637 byte(s)
	* Thuban/UI/mainwindow.py (MainWindow.invoke_command,
	MainWindow.update_command_ui): Pass an instance of the context
	class to the command's methods
	(check_current_tool, call_method): Handle the new context
	implementation

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