/[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 316 - (show annotations)
Thu Sep 12 18:46:01 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: 24899 byte(s)
(MainWindow.RunMessageBox): Center the
message box on the main window.

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