/[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 314 - (show annotations)
Wed Sep 11 15:18:09 2002 UTC (22 years, 5 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 24870 byte(s)
(MainWindow.build_menu): remove an incorrect comment.

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