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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 281 - (hide annotations)
Mon Aug 26 18:16:36 2002 UTC (22 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 23429 byte(s)
* Thuban/UI/mainwindow.py: Refactor the context creation:
(MainWindow.Context): New method to return a context
(MainWindow.invoke_command, MainWindow.update_command_ui): Use the
new method

1 bh 123 # Copyright (C) 2001, 2002 by Intevation GmbH
2 bh 6 # 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 bh 188 import os
16 bh 6
17     from wxPython.wx import *
18    
19     import Thuban
20 bh 188 from Thuban.Model.session import create_empty_session
21 bh 6 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 bh 188 from menu import Menu
30 bh 6
31 bh 222 from context import Context
32 bh 6 from command import registry, Command
33 bh 123 from messages import SELECTED_SHAPE, VIEW_POSITION
34 bh 6
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 bh 235 def __init__(self, parent, ID, title, application, interactor,
44 bh 238 initial_message = None, size = wxSize(-1, -1)):
45     wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
46 bh 6
47 bh 227 self.application = application
48 bh 37 self.interactor = interactor
49    
50 bh 6 self.CreateStatusBar()
51 bh 235 if initial_message:
52     self.SetStatusText(initial_message)
53 bh 6
54     self.identify_view = None
55    
56     self.init_ids()
57    
58 bh 191 # creat the menubar from the main_menu description
59 bh 188 self.SetMenuBar(self.build_menu_bar(main_menu))
60 bh 6
61 bh 191 # Similarly, create the toolbar from main_toolbar
62     toolbar = self.build_toolbar(main_toolbar)
63 bh 13 # call Realize to make sure that the tools appear.
64     toolbar.Realize()
65 bh 6
66     # Create the map canvas
67 bh 24 canvas = view.MapCanvas(self, -1, interactor)
68 bh 123 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
69 bh 6 self.canvas = canvas
70    
71 bh 31 self.init_dialogs()
72    
73     interactor.Subscribe(SELECTED_SHAPE, self.identify_view_on_demand)
74    
75 bh 6 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 bh 193 self.events_bound = {}
83 bh 6
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 bh 188
96 bh 193 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 bh 188 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 bh 191 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 bh 6 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 bh 193 self.bind_command_events(command, ID)
171 bh 6 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 bh 193 self.bind_command_events(command, ID)
193 bh 6 else:
194     print "Unknown command %s" % name
195    
196 bh 281 def Context(self):
197     """Return the context object for a command invoked from this window
198     """
199     return Context(self.application, self.application.Session(), self)
200    
201 bh 6 def invoke_command(self, event):
202     name = self.id_to_name.get(event.GetId())
203     if name is not None:
204     command = registry.Command(name)
205 bh 281 command.Execute(self.Context())
206 bh 6 else:
207     print "Unknown command ID %d" % event.GetId()
208    
209     def update_command_ui(self, event):
210     #print "update_command_ui", self.id_to_name[event.GetId()]
211 bh 281 context = self.Context()
212 bh 6 command = registry.Command(self.id_to_name[event.GetId()])
213     if command is not None:
214 bh 222 event.Enable(command.Sensitive(context))
215     event.SetText(command.DynText(context))
216 bh 13 if command.IsCheckCommand():
217 bh 222 event.Check(command.Checked(context))
218 bh 6
219 bh 20 def RunMessageBox(self, title, text, flags = wxOK | wxICON_INFORMATION):
220 bh 181 """Run a modal message box with the given text, title and flags
221 bh 20 and return the result"""
222     dlg = wxMessageDialog(self, text, title, flags)
223     result = dlg.ShowModal()
224     dlg.Destroy()
225     return result
226    
227 bh 31 def init_dialogs(self):
228     """Initialize the dialog handling"""
229     # The mainwindow maintains a dict mapping names to open
230     # non-modal dialogs. The dialogs are put into this dict when
231     # they're created and removed when they're closed
232     self.dialogs = {}
233    
234     def add_dialog(self, name, dialog):
235     if self.dialogs.has_key(name):
236     raise RuntimeError("The Dialog named %s is already open" % name)
237     self.dialogs[name] = dialog
238    
239     def dialog_open(self, name):
240     return self.dialogs.has_key(name)
241    
242     def remove_dialog(self, name):
243     del self.dialogs[name]
244    
245     def get_open_dialog(self, name):
246     return self.dialogs.get(name)
247    
248 bh 123 def view_position_changed(self):
249     pos = self.canvas.CurrentPosition()
250     if pos is not None:
251     text = "(%10.10g, %10.10g)" % pos
252     else:
253     text = ""
254     self.SetStatusText(text)
255    
256 bh 58 def save_modified_session(self, can_veto = 1):
257     """If the current session has been modified, ask the user
258     whether to save it and do so if requested. Return the outcome of
259     the dialog (either wxID_OK, wxID_CANCEL or wxID_NO). If the
260     dialog wasn't run return wxID_NO.
261    
262     If the can_veto parameter is true (default) the dialog includes
263     a cancel button, otherwise not.
264     """
265 bh 227 if self.application.session.WasModified():
266 bh 58 flags = wxYES_NO | wxICON_QUESTION
267     if can_veto:
268     flags = flags | wxCANCEL
269     result = self.RunMessageBox("Exit",
270     ("The session has been modified."
271     " Do you want to save it?"),
272     flags)
273     if result == wxID_YES:
274     self.SaveSession()
275     else:
276     result = wxID_NO
277     return result
278    
279 bh 6 def NewSession(self):
280 bh 58 self.save_modified_session()
281 bh 227 self.application.SetSession(create_empty_session())
282 bh 6
283     def OpenSession(self):
284 bh 58 self.save_modified_session()
285 bh 6 dlg = wxFileDialog(self, "Select a session file", ".", "",
286 bh 130 "*.thuban", wxOPEN)
287 bh 6 if dlg.ShowModal() == wxID_OK:
288 bh 227 self.application.OpenSession(dlg.GetPath())
289 bh 6 dlg.Destroy()
290    
291     def SaveSession(self):
292 bh 227 if self.application.session.filename == None:
293 jan 102 self.SaveSessionAs()
294 bh 227 self.application.SaveSession()
295 bh 6
296     def SaveSessionAs(self):
297     dlg = wxFileDialog(self, "Enter a filename for session", ".", "",
298 bh 130 "*.thuban", wxOPEN)
299 bh 6 if dlg.ShowModal() == wxID_OK:
300 bh 227 self.application.session.SetFilename(dlg.GetPath())
301     self.application.SaveSession()
302 bh 6 dlg.Destroy()
303    
304     def Exit(self):
305     self.Close(false)
306    
307     def OnClose(self, event):
308 bh 58 result = self.save_modified_session(can_veto = event.CanVeto())
309     if result == wxID_CANCEL:
310 bh 6 event.Veto()
311     else:
312     self.Destroy()
313    
314     def SetMap(self, map):
315     self.canvas.SetMap(map)
316    
317 bh 37 def ShowSessionTree(self):
318     name = "session_tree"
319     dialog = self.get_open_dialog(name)
320     if dialog is None:
321 bh 227 dialog = tree.SessionTreeView(self, self.application, name)
322 bh 37 self.add_dialog(name, dialog)
323     dialog.Show(true)
324     else:
325     # FIXME: bring dialog to front here
326     pass
327    
328 bh 6 def About(self):
329 bh 20 self.RunMessageBox("About",
330     ("Thuban is a program for\n"
331     "exploring geographic data.\n"
332     "Copyright (C) 2001 Intevation GmbH.\n"
333     "Thuban is licensed under the GPL"),
334     wxOK | wxICON_INFORMATION)
335 bh 6
336     def AddLayer(self):
337 frank 119 dlg = wxFileDialog(self, "Select a data file", ".", "", "*.*",
338 bh 6 wxOPEN)
339     if dlg.ShowModal() == wxID_OK:
340     filename = dlg.GetPath()
341     title = os.path.splitext(os.path.basename(filename))[0]
342     layer = Layer(title, filename)
343 bh 18 map = self.canvas.Map()
344     has_layers = map.HasLayers()
345 bh 20 try:
346     map.AddLayer(layer)
347     except IOError:
348     # the layer couldn't be opened
349     self.RunMessageBox("Add Layer",
350     "Can't open the file '%s'." % filename)
351     else:
352     if not has_layers:
353     # if we're adding a layer to an empty map, for the
354     # new map to the window
355     self.canvas.FitMapToWindow()
356 bh 6 dlg.Destroy()
357    
358     def RemoveLayer(self):
359     layer = self.current_layer()
360     if layer is not None:
361     self.canvas.Map().RemoveLayer(layer)
362    
363     def RaiseLayer(self):
364     layer = self.current_layer()
365     if layer is not None:
366     self.canvas.Map().RaiseLayer(layer)
367 bh 222
368 bh 6 def LowerLayer(self):
369     layer = self.current_layer()
370     if layer is not None:
371     self.canvas.Map().LowerLayer(layer)
372    
373     def current_layer(self):
374     """Return the currently selected layer.
375    
376     If no layer is selected, return None
377     """
378 bh 37 return self.interactor.SelectedLayer()
379 bh 6
380     def has_selected_layer(self):
381     """Return true if a layer is currently selected"""
382 bh 37 return self.interactor.HasSelectedLayer()
383 bh 6
384     def choose_color(self):
385     """Run the color selection dialog and return the selected color.
386    
387     If the user cancels, return None.
388     """
389     dlg = wxColourDialog(self)
390     color = None
391     if dlg.ShowModal() == wxID_OK:
392     data = dlg.GetColourData()
393     wxc = data.GetColour()
394     color = Color(wxc.Red() / 255.0,
395     wxc.Green() / 255.0,
396     wxc.Blue() / 255.0)
397     dlg.Destroy()
398     return color
399    
400     def LayerFillColor(self):
401     layer = self.current_layer()
402     if layer is not None:
403     color = self.choose_color()
404     if color is not None:
405     layer.SetFill(color)
406    
407     def LayerTransparentFill(self):
408     layer = self.current_layer()
409     if layer is not None:
410     layer.SetFill(None)
411    
412     def LayerOutlineColor(self):
413     layer = self.current_layer()
414     if layer is not None:
415     color = self.choose_color()
416     if color is not None:
417     layer.SetStroke(color)
418    
419     def LayerNoOutline(self):
420     layer = self.current_layer()
421     if layer is not None:
422     layer.SetStroke(None)
423    
424     def HideLayer(self):
425     layer = self.current_layer()
426     if layer is not None:
427     layer.SetVisible(0)
428    
429     def ShowLayer(self):
430     layer = self.current_layer()
431     if layer is not None:
432     layer.SetVisible(1)
433    
434     def LayerShowTable(self):
435     layer = self.current_layer()
436     if layer is not None:
437 bh 31 table = layer.table
438     name = "table_view" + str(id(table))
439     dialog = self.get_open_dialog(name)
440     if dialog is None:
441 bh 278 dialog = tableview.LayerTableFrame(self, self.interactor, name,
442     "Table: %s" % layer.Title(),
443     layer, table)
444 bh 31 self.add_dialog(name, dialog)
445     dialog.Show(true)
446     else:
447     # FIXME: bring dialog to front here
448     pass
449 bh 6
450     def Projection(self):
451     map = self.canvas.Map()
452     proj = map.projection
453     if proj is None:
454 jan 110 proj4Dlg = proj4dialog.Proj4Dialog(NULL, None, map.BoundingBox())
455 bh 6 else:
456 jan 110 proj4Dlg = proj4dialog.Proj4Dialog(NULL, map.projection.params,
457     map.BoundingBox())
458 bh 6 if proj4Dlg.ShowModal() == wxID_OK:
459     params = proj4Dlg.GetParams()
460     if params is not None:
461     proj = Projection(params)
462     else:
463     proj = None
464     map.SetProjection(proj)
465     proj4Dlg.Destroy()
466    
467     def ZoomInTool(self):
468     self.canvas.ZoomInTool()
469    
470     def ZoomOutTool(self):
471     self.canvas.ZoomOutTool()
472    
473     def PanTool(self):
474     self.canvas.PanTool()
475    
476     def IdentifyTool(self):
477     self.canvas.IdentifyTool()
478 bh 49 self.identify_view_on_demand(None, None)
479 bh 6
480     def LabelTool(self):
481     self.canvas.LabelTool()
482    
483     def FullExtent(self):
484     self.canvas.FitMapToWindow()
485    
486     def PrintMap(self):
487     self.canvas.Print()
488    
489 bh 31 def identify_view_on_demand(self, layer, shape):
490     name = "identify_view"
491     if self.canvas.CurrentTool() == "IdentifyTool":
492     if not self.dialog_open(name):
493 bh 37 dialog = identifyview.IdentifyView(self, self.interactor, name)
494 bh 31 self.add_dialog(name, dialog)
495     dialog.Show(true)
496     else:
497 bh 33 # FIXME: bring dialog to front?
498 bh 31 pass
499 bh 6
500     #
501     # Define all the commands available in the main window
502     #
503    
504    
505     # Helper functions to define common command implementations
506     def call_method(context, methodname, *args):
507 bh 222 """Call the mainwindow's method methodname with args *args"""
508     apply(getattr(context.mainwindow, methodname), args)
509 bh 6
510 jan 110 def _method_command(name, title, method, helptext = "",
511     icon = "", sensitive = None):
512 bh 222 """Add a command implemented by a method of the mainwindow object"""
513 bh 6 registry.Add(Command(name, title, call_method, args=(method,),
514 jan 110 helptext = helptext, icon = icon,
515     sensitive = sensitive))
516    
517 bh 270 def make_check_current_tool(toolname):
518     """Return a function that tests if the currently active tool is toolname
519    
520     The returned function can be called with the context and returns
521     true iff the currently active tool's name is toolname. It's directly
522     usable as the 'checked' callback of a command.
523     """
524     def check_current_tool(context, name=toolname):
525     return context.mainwindow.canvas.CurrentTool() == name
526     return check_current_tool
527    
528 bh 6 def _tool_command(name, title, method, toolname, helptext = "",
529     icon = ""):
530     """Add a tool command"""
531     registry.Add(Command(name, title, call_method, args=(method,),
532     helptext = helptext, icon = icon,
533 bh 270 checked = make_check_current_tool(toolname)))
534 bh 6
535     def _has_selected_layer(context):
536     """Return true if a layer is selected in the context"""
537 bh 222 return context.mainwindow.has_selected_layer()
538 bh 6
539 jan 264 def _has_tree_window_shown(context):
540     """Return true if the tree window is shown"""
541     return context.mainwindow.get_open_dialog("session_tree") is None
542    
543 bh 6 # File menu
544     _method_command("new_session", "&New Session", "NewSession")
545     _method_command("open_session", "&Open Session", "OpenSession")
546     _method_command("save_session", "&Save Session", "SaveSession")
547     _method_command("save_session_as", "Save Session &As", "SaveSessionAs")
548 jan 264 _method_command("show_session_tree", "Show Session &Tree", "ShowSessionTree",
549     sensitive = _has_tree_window_shown)
550 bh 6 _method_command("exit", "&Exit", "Exit")
551    
552     # Help menu
553     _method_command("help_about", "&About", "About")
554    
555    
556     # Map menu
557     _method_command("map_projection", "Pro&jection", "Projection")
558    
559     _tool_command("map_zoom_in_tool", "&Zoom in", "ZoomInTool", "ZoomInTool",
560     helptext = "Switch to map-mode 'zoom-in'", icon = "zoom_in")
561     _tool_command("map_zoom_out_tool", "Zoom &out", "ZoomOutTool", "ZoomOutTool",
562     helptext = "Switch to map-mode 'zoom-out'", icon = "zoom_out")
563     _tool_command("map_pan_tool", "&Pan", "PanTool", "PanTool",
564     helptext = "Switch to map-mode 'pan'", icon = "pan")
565     _tool_command("map_identify_tool", "&Identify", "IdentifyTool", "IdentifyTool",
566     helptext = "Switch to map-mode 'identify'", icon = "identify")
567     _tool_command("map_label_tool", "&Label", "LabelTool", "LabelTool",
568     helptext = "Add/Remove labels", icon = "label")
569 jan 110 _method_command("map_full_extent", "&Full extent", "FullExtent",
570     helptext = "Full Extent", icon = "fullextent")
571 bh 6 _method_command("map_print", "Prin&t", "PrintMap", helptext = "Print the map")
572    
573     # Layer menu
574 bh 84 _method_command("layer_add", "&Add Layer", "AddLayer",
575 bh 6 helptext = "Add a new layer to active map")
576 bh 84 _method_command("layer_remove", "&Remove Layer", "RemoveLayer",
577 bh 6 helptext = "Remove selected layer(s)",
578     sensitive = _has_selected_layer)
579     _method_command("layer_fill_color", "&Fill Color", "LayerFillColor",
580     helptext = "Set the fill color of selected layer(s)",
581     sensitive = _has_selected_layer)
582     _method_command("layer_transparent_fill", "&Transparent Fill",
583     "LayerTransparentFill",
584     helptext = "Do not fill the selected layer(s)",
585     sensitive = _has_selected_layer)
586 bh 185 _method_command("layer_outline_color", "&Outline Color", "LayerOutlineColor",
587 bh 6 helptext = "Set the outline color of selected layer(s)",
588     sensitive = _has_selected_layer)
589     _method_command("layer_no_outline", "&No Outline", "LayerNoOutline",
590     helptext = "Do not draw the outline of the selected layer(s)",
591     sensitive = _has_selected_layer)
592     _method_command("layer_raise", "&Raise", "RaiseLayer",
593     helptext = "Raise selected layer(s)",
594     sensitive = _has_selected_layer)
595     _method_command("layer_lower", "&Lower", "LowerLayer",
596     helptext = "Lower selected layer(s)",
597     sensitive = _has_selected_layer)
598     _method_command("layer_show", "&Show", "ShowLayer",
599     helptext = "Make selected layer(s) visible",
600     sensitive = _has_selected_layer)
601     _method_command("layer_hide", "&Hide", "HideLayer",
602     helptext = "Make selected layer(s) unvisible",
603     sensitive = _has_selected_layer)
604     _method_command("layer_show_table", "Show Ta&ble", "LayerShowTable",
605     helptext = "Show the selected layer's table",
606     sensitive = _has_selected_layer)
607 bh 188
608    
609     # the menu structure
610     main_menu = Menu("<main>", "<main>",
611     [Menu("file", "&File",
612     ["new_session", "open_session", None,
613     "save_session", "save_session_as", None,
614     "show_session_tree", None,
615     "exit"]),
616     Menu("map", "&Map",
617     ["layer_add", "layer_remove",
618     None,
619     "map_projection",
620     None,
621     "map_zoom_in_tool", "map_zoom_out_tool",
622     "map_pan_tool", "map_identify_tool", "map_label_tool",
623     None,
624     "map_full_extent",
625     None,
626     "map_print"]),
627     Menu("layer", "&Layer",
628     ["layer_fill_color", "layer_transparent_fill",
629     "layer_outline_color", "layer_no_outline",
630     None,
631     "layer_raise", "layer_lower",
632     None,
633     "layer_show", "layer_hide",
634     None,
635     "layer_show_table"]),
636     Menu("help", "&Help",
637     ["help_about"])])
638 bh 191
639     # the main toolbar
640    
641     main_toolbar = Menu("<toolbar>", "<toolbar>",
642     ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
643     "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