/[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 357 - (show annotations)
Mon Dec 9 10:32:27 2002 UTC (22 years, 3 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 25855 byte(s)
(MainWindow.update_command_ui): If an
active tool's command turns insensitive, disable the tool.
(_tool_command): Use the new ToolCommand class

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26