/[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 351 - (show annotations)
Wed Nov 6 14:46:33 2002 UTC (22 years, 4 months ago) by frank
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 25262 byte(s)
Altered the order of tools in the toolbar:
First now are all navigation tools (Zoom In/Out, Pan, Full Extent).

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