/[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 1620 - (show annotations)
Wed Aug 20 13:14:22 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 44829 byte(s)
Add dialogs and commands to open database connections and add
database layers.

* Thuban/UI/mainwindow.py (MainWindow.DatabaseManagement): New
method to open the database connection management dialog
(MainWindow.AddDBLayer): New method to add a layer from a database
(_has_dbconnections): New helper function to use for sensitivity
(database_management command, layer_add_db command): New commands
that call the above new methods.
(main_menu): Add the new commands to the menu.

* Thuban/Model/postgisdb.py (PostGISConnection.__init__)
(PostGISConnection.connect): Establish the actual connection in a
separate method and call it in __init__. This makes it easier to
override the behavior in test cases
(PostGISConnection.BriefDescription): New method to return a brief
description for use in dialogs.

* test/test_postgis_db.py (NonConnection): DB connection that
doesn't actually connect
(TestBriefDescription): New class with tests for the new
BriefDescription method

1 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Jan-Oliver Wagner <[email protected]>
4 # Bernhard Herzog <[email protected]>
5 # Frank Koormann <[email protected]>
6 #
7 # This program is free software under the GPL (>=v2)
8 # Read the file COPYING coming with Thuban for details.
9
10 """
11 The main window
12 """
13
14 __version__ = "$Revision$"
15
16 __ThubanVersion__ = "0.8" #"$THUBAN_0_2$"
17 #__BuildDate__ = "$Date$"
18
19 import os
20 import copy
21
22 from wxPython.wx import *
23 from wxPython.wx import __version__ as wxPython_version
24
25 import Thuban
26 import Thuban.version
27
28 from Thuban import _
29 from Thuban.Model.session import create_empty_session
30 from Thuban.Model.layer import Layer, RasterLayer
31 from Thuban.Model.postgisdb import PostGISShapeStore
32 # XXX: replace this by
33 # from wxPython.lib.dialogs import wxMultipleChoiceDialog
34 # when Thuban does not support wxPython 2.4.0 any more.
35 from Thuban.UI.multiplechoicedialog import wxMultipleChoiceDialog
36
37 import view
38 import tree
39 import tableview, identifyview
40 from Thuban.UI.classifier import Classifier
41 import legend
42 from menu import Menu
43
44 from context import Context
45 from command import registry, Command, ToolCommand
46 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
47 MAP_REPLACED
48 from about import About
49
50 from Thuban.UI.dock import DockFrame
51 from Thuban.UI.join import JoinDialog
52 from Thuban.UI.dbdialog import DBFrame, ChooseDBTableDialog
53 import resource
54 import Thuban.Model.resource
55
56 import projdialog
57
58
59 class MainWindow(DockFrame):
60
61 # Some messages that can be subscribed/unsubscribed directly through
62 # the MapCanvas come in fact from other objects. This is a map to
63 # map those messages to the names of the instance variables they
64 # actually come from. This delegation is implemented in the
65 # Subscribe and unsubscribed methods
66 delegated_messages = {LAYER_SELECTED: "canvas",
67 SHAPES_SELECTED: "canvas",
68 MAP_REPLACED: "canvas"}
69
70 # Methods delegated to some instance variables. The delegation is
71 # implemented in the __getattr__ method.
72 delegated_methods = {"SelectLayer": "canvas",
73 "SelectShapes": "canvas",
74 "SelectedLayer": "canvas",
75 "SelectedShapes": "canvas",
76 }
77
78 def __init__(self, parent, ID, title, application, interactor,
79 initial_message = None, size = wxSize(-1, -1)):
80 DockFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
81 #wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
82
83 self.application = application
84
85 self.CreateStatusBar()
86 if initial_message:
87 self.SetStatusText(initial_message)
88
89 self.identify_view = None
90
91 self.init_ids()
92
93 # creat the menubar from the main_menu description
94 self.SetMenuBar(self.build_menu_bar(main_menu))
95
96 # Similarly, create the toolbar from main_toolbar
97 toolbar = self.build_toolbar(main_toolbar)
98 # call Realize to make sure that the tools appear.
99 toolbar.Realize()
100
101
102 # Create the map canvas
103 canvas = view.MapCanvas(self, -1)
104 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
105 canvas.Subscribe(SHAPES_SELECTED, self.identify_view_on_demand)
106 self.canvas = canvas
107
108 self.SetMainWindow(self.canvas)
109
110 self.SetAutoLayout(True)
111
112 self.init_dialogs()
113
114 self.ShowLegend()
115
116 EVT_CLOSE(self, self.OnClose)
117
118 def Subscribe(self, channel, *args):
119 """Subscribe a function to a message channel.
120
121 If channel is one of the delegated messages call the appropriate
122 object's Subscribe method. Otherwise do nothing.
123 """
124 if channel in self.delegated_messages:
125 object = getattr(self, self.delegated_messages[channel])
126 object.Subscribe(channel, *args)
127 else:
128 print "Trying to subscribe to unsupported channel %s" % channel
129
130 def Unsubscribe(self, channel, *args):
131 """Unsubscribe a function from a message channel.
132
133 If channel is one of the delegated messages call the appropriate
134 object's Unsubscribe method. Otherwise do nothing.
135 """
136 if channel in self.delegated_messages:
137 object = getattr(self, self.delegated_messages[channel])
138 object.Unsubscribe(channel, *args)
139
140 def __getattr__(self, attr):
141 """If attr is one of the delegated methods return that method
142
143 Otherwise raise AttributeError.
144 """
145 if attr in self.delegated_methods:
146 return getattr(getattr(self, self.delegated_methods[attr]), attr)
147 raise AttributeError(attr)
148
149 def init_ids(self):
150 """Initialize the ids"""
151 self.current_id = 6000
152 self.id_to_name = {}
153 self.name_to_id = {}
154 self.events_bound = {}
155
156 def get_id(self, name):
157 """Return the wxWindows id for the command named name.
158
159 Create a new one if there isn't one yet"""
160 ID = self.name_to_id.get(name)
161 if ID is None:
162 ID = self.current_id
163 self.current_id = self.current_id + 1
164 self.name_to_id[name] = ID
165 self.id_to_name[ID] = name
166 return ID
167
168 def bind_command_events(self, command, ID):
169 """Bind the necessary events for the given command and ID"""
170 if not self.events_bound.has_key(ID):
171 # the events haven't been bound yet
172 EVT_MENU(self, ID, self.invoke_command)
173 if command.IsDynamic():
174 EVT_UPDATE_UI(self, ID, self.update_command_ui)
175
176 def build_menu_bar(self, menudesc):
177 """Build and return the menu bar from the menu description"""
178 menu_bar = wxMenuBar()
179
180 for item in menudesc.items:
181 # here the items must all be Menu instances themselves
182 menu_bar.Append(self.build_menu(item), item.title)
183
184 return menu_bar
185
186 def build_menu(self, menudesc):
187 """Return a wxMenu built from the menu description menudesc"""
188 wxmenu = wxMenu()
189 last = None
190 for item in menudesc.items:
191 if item is None:
192 # a separator. Only add one if the last item was not a
193 # separator
194 if last is not None:
195 wxmenu.AppendSeparator()
196 elif isinstance(item, Menu):
197 # a submenu
198 wxmenu.AppendMenu(wxNewId(), item.title, self.build_menu(item))
199 else:
200 # must the name the name of a command
201 self.add_menu_command(wxmenu, item)
202 last = item
203 return wxmenu
204
205 def build_toolbar(self, toolbardesc):
206 """Build and return the main toolbar window from a toolbar description
207
208 The parameter should be an instance of the Menu class but it
209 should not contain submenus.
210 """
211 toolbar = self.CreateToolBar(wxTB_3DBUTTONS)
212
213 # set the size of the tools' bitmaps. Not needed on wxGTK, but
214 # on Windows, although it doesn't work very well there. It seems
215 # that only 16x16 icons are really supported on windows.
216 # We probably shouldn't hardwire the bitmap size here.
217 toolbar.SetToolBitmapSize(wxSize(24, 24))
218
219 for item in toolbardesc.items:
220 if item is None:
221 toolbar.AddSeparator()
222 else:
223 # assume it's a string.
224 self.add_toolbar_command(toolbar, item)
225
226 return toolbar
227
228 def add_menu_command(self, menu, name):
229 """Add the command with name name to the menu menu.
230
231 If name is None, add a separator.
232 """
233 if name is None:
234 menu.AppendSeparator()
235 else:
236 command = registry.Command(name)
237 if command is not None:
238 ID = self.get_id(name)
239 menu.Append(ID, command.Title(), command.HelpText(),
240 command.IsCheckCommand())
241 self.bind_command_events(command, ID)
242 else:
243 print _("Unknown command %s") % name
244
245 def add_toolbar_command(self, toolbar, name):
246 """Add the command with name name to the toolbar toolbar.
247
248 If name is None, add a separator.
249 """
250 # Assume that all toolbar commands are also menu commmands so
251 # that we don't have to add the event handlers here
252 if name is None:
253 toolbar.AddSeparator()
254 else:
255 command = registry.Command(name)
256 if command is not None:
257 ID = self.get_id(name)
258 bitmap = resource.GetBitmapResource(command.Icon(),
259 wxBITMAP_TYPE_XPM)
260 toolbar.AddTool(ID, bitmap,
261 shortHelpString = command.HelpText(),
262 isToggle = command.IsCheckCommand())
263 self.bind_command_events(command, ID)
264 else:
265 print _("Unknown command %s") % name
266
267 def Context(self):
268 """Return the context object for a command invoked from this window
269 """
270 return Context(self.application, self.application.Session(), self)
271
272 def invoke_command(self, event):
273 name = self.id_to_name.get(event.GetId())
274 if name is not None:
275 command = registry.Command(name)
276 command.Execute(self.Context())
277 else:
278 print _("Unknown command ID %d") % event.GetId()
279
280 def update_command_ui(self, event):
281 #print "update_command_ui", self.id_to_name[event.GetId()]
282 context = self.Context()
283 command = registry.Command(self.id_to_name[event.GetId()])
284 if command is not None:
285 sensitive = command.Sensitive(context)
286 event.Enable(sensitive)
287 if command.IsTool() and not sensitive and command.Checked(context):
288 # When a checked tool command is disabled deselect all
289 # tools. Otherwise the tool would remain active but it
290 # might lead to errors if the tools stays active. This
291 # problem occurred in GREAT-ER and this fixes it, but
292 # it's not clear to me whether this is really the best
293 # way to do it (BH, 20021206).
294 self.canvas.SelectTool(None)
295 event.SetText(command.DynText(context))
296 if command.IsCheckCommand():
297 event.Check(command.Checked(context))
298
299 def RunMessageBox(self, title, text, flags = wxOK | wxICON_INFORMATION):
300 """Run a modal message box with the given text, title and flags
301 and return the result"""
302 dlg = wxMessageDialog(self, text, title, flags)
303 dlg.CenterOnParent()
304 result = dlg.ShowModal()
305 dlg.Destroy()
306 return result
307
308 def init_dialogs(self):
309 """Initialize the dialog handling"""
310 # The mainwindow maintains a dict mapping names to open
311 # non-modal dialogs. The dialogs are put into this dict when
312 # they're created and removed when they're closed
313 self.dialogs = {}
314
315 def add_dialog(self, name, dialog):
316 if self.dialogs.has_key(name):
317 raise RuntimeError(_("The Dialog named %s is already open") % name)
318 self.dialogs[name] = dialog
319
320 def dialog_open(self, name):
321 return self.dialogs.has_key(name)
322
323 def remove_dialog(self, name):
324 del self.dialogs[name]
325
326 def get_open_dialog(self, name):
327 return self.dialogs.get(name)
328
329 def view_position_changed(self):
330 pos = self.canvas.CurrentPosition()
331 if pos is not None:
332 text = "(%10.10g, %10.10g)" % pos
333 else:
334 text = ""
335 self.set_position_text(text)
336
337 def set_position_text(self, text):
338 """Set the statusbar text showing the current position.
339
340 By default the text is shown in field 0 of the status bar.
341 Override this method in derived classes to put it into a
342 different field of the statusbar.
343 """
344 self.SetStatusText(text)
345
346 def save_modified_session(self, can_veto = 1):
347 """If the current session has been modified, ask the user
348 whether to save it and do so if requested. Return the outcome of
349 the dialog (either wxID_OK, wxID_CANCEL or wxID_NO). If the
350 dialog wasn't run return wxID_NO.
351
352 If the can_veto parameter is true (default) the dialog includes
353 a cancel button, otherwise not.
354 """
355 if self.application.session.WasModified():
356 flags = wxYES_NO | wxICON_QUESTION
357 if can_veto:
358 flags = flags | wxCANCEL
359 result = self.RunMessageBox(_("Exit"),
360 _("The session has been modified."
361 " Do you want to save it?"),
362 flags)
363 if result == wxID_YES:
364 self.SaveSession()
365 else:
366 result = wxID_NO
367 return result
368
369 def NewSession(self):
370 if self.save_modified_session() != wxID_CANCEL:
371 self.application.SetSession(create_empty_session())
372
373 def OpenSession(self):
374 if self.save_modified_session() != wxID_CANCEL:
375 dlg = wxFileDialog(self, _("Open Session"), ".", "",
376 "Thuban Session File (*.thuban)|*.thuban",
377 wxOPEN)
378 if dlg.ShowModal() == wxID_OK:
379 self.application.OpenSession(dlg.GetPath())
380 dlg.Destroy()
381
382 def SaveSession(self):
383 if self.application.session.filename == None:
384 self.SaveSessionAs()
385 else:
386 self.application.SaveSession()
387
388 def SaveSessionAs(self):
389 dlg = wxFileDialog(self, _("Save Session As"), ".", "",
390 "Thuban Session File (*.thuban)|*.thuban",
391 wxSAVE|wxOVERWRITE_PROMPT)
392 if dlg.ShowModal() == wxID_OK:
393 self.application.session.SetFilename(dlg.GetPath())
394 self.application.SaveSession()
395 dlg.Destroy()
396
397 def Exit(self):
398 self.Close(False)
399
400 def OnClose(self, event):
401 result = self.save_modified_session(can_veto = event.CanVeto())
402 if result == wxID_CANCEL:
403 event.Veto()
404 else:
405 # FIXME: it would be better to tie the unsubscription to
406 # wx's destroy event, but that isn't implemented for wxGTK
407 # yet.
408 self.canvas.Unsubscribe(VIEW_POSITION, self.view_position_changed)
409 DockFrame.OnClose(self, event)
410 for dlg in self.dialogs.values():
411 dlg.Destroy()
412 self.canvas.Destroy()
413 self.Destroy()
414
415 def SetMap(self, map):
416 self.canvas.SetMap(map)
417 self.__SetTitle(map.Title())
418
419 dialog = self.FindRegisteredDock("legend")
420 if dialog is not None:
421 dialog.GetPanel().SetMap(self.Map())
422
423 def Map(self):
424 """Return the map displayed by this mainwindow"""
425
426 return self.canvas.Map()
427
428 def ToggleSessionTree(self):
429 """If the session tree is shown close it otherwise create a new tree"""
430 name = "session_tree"
431 dialog = self.get_open_dialog(name)
432 if dialog is None:
433 dialog = tree.SessionTreeView(self, self.application, name)
434 self.add_dialog(name, dialog)
435 dialog.Show(True)
436 else:
437 dialog.Close()
438
439 def SessionTreeShown(self):
440 """Return true iff the session tree is currently shown"""
441 return self.get_open_dialog("session_tree") is not None
442
443 def About(self):
444 dlg = About(self)
445 dlg.ShowModal()
446 dlg.Destroy()
447
448 def DatabaseManagement(self):
449 name = "dbmanagement"
450 dialog = self.get_open_dialog(name)
451 if dialog is None:
452 map = self.canvas.Map()
453 dialog = DBFrame(self, name, self.application.Session())
454 self.add_dialog(name, dialog)
455 dialog.Show()
456 dialog.Raise()
457
458 def AddLayer(self):
459 dlg = wxFileDialog(self, _("Select a data file"), ".", "", "*.*",
460 wxOPEN)
461 if dlg.ShowModal() == wxID_OK:
462 filename = dlg.GetPath()
463 title = os.path.splitext(os.path.basename(filename))[0]
464 map = self.canvas.Map()
465 has_layers = map.HasLayers()
466 try:
467 store = self.application.Session().OpenShapefile(filename)
468 except IOError:
469 # the layer couldn't be opened
470 self.RunMessageBox(_("Add Layer"),
471 _("Can't open the file '%s'.") % filename)
472 else:
473 layer = Layer(title, store)
474 map.AddLayer(layer)
475 if not has_layers:
476 # if we're adding a layer to an empty map, fit the
477 # new map to the window
478 self.canvas.FitMapToWindow()
479 dlg.Destroy()
480
481 def AddRasterLayer(self):
482 dlg = wxFileDialog(self, _("Select an image file"), ".", "", "*.*",
483 wxOPEN)
484 if dlg.ShowModal() == wxID_OK:
485 filename = dlg.GetPath()
486 title = os.path.splitext(os.path.basename(filename))[0]
487 map = self.canvas.Map()
488 has_layers = map.HasLayers()
489 try:
490 layer = RasterLayer(title, filename)
491 except IOError:
492 # the layer couldn't be opened
493 self.RunMessageBox(_("Add Image Layer"),
494 _("Can't open the file '%s'.") % filename)
495 else:
496 map.AddLayer(layer)
497 if not has_layers:
498 # if we're adding a layer to an empty map, fit the
499 # new map to the window
500 self.canvas.FitMapToWindow()
501 dlg.Destroy()
502
503 def AddDBLayer(self):
504 """Add a layer read from a database"""
505 session = self.application.Session()
506 dlg = ChooseDBTableDialog(self.application.Session(), self,-1, "")
507
508 if dlg.ShowModal() == wxID_OK:
509 dbconn, dbtable = dlg.GetTable()
510 try:
511 title = str(dbtable)
512
513 # Chose the correct Interface for the database type
514 store = PostGISShapeStore(dbconn, dbtable)
515 session.AddShapeStore(store)
516 layer = Layer(title, store)
517 except:
518 # Some error occured while initializing the layer
519 self.RunMessageBox(_("Add Layer from database"),
520 _("Can't open the database table '%s'")
521 % dbtable)
522
523 map = self.canvas.Map()
524
525 has_layers = map.HasLayers()
526 map.AddLayer(layer)
527 if not has_layers:
528 self.canvas.FitMapToWindow()
529
530 dlg.Destroy()
531
532 def RemoveLayer(self):
533 layer = self.current_layer()
534 if layer is not None:
535 self.canvas.Map().RemoveLayer(layer)
536
537 def CanRemoveLayer(self):
538 """Return true if the currently selected layer can be deleted.
539
540 If no layer is selected return False.
541
542 The return value of this method determines whether the remove
543 layer command is sensitive in menu.
544 """
545 layer = self.current_layer()
546 if layer is not None:
547 return self.canvas.Map().CanRemoveLayer(layer)
548 return False
549
550 def RaiseLayer(self):
551 layer = self.current_layer()
552 if layer is not None:
553 self.canvas.Map().RaiseLayer(layer)
554
555 def LowerLayer(self):
556 layer = self.current_layer()
557 if layer is not None:
558 self.canvas.Map().LowerLayer(layer)
559
560 def current_layer(self):
561 """Return the currently selected layer.
562
563 If no layer is selected, return None
564 """
565 return self.canvas.SelectedLayer()
566
567 def has_selected_layer(self):
568 """Return true if a layer is currently selected"""
569 return self.canvas.HasSelectedLayer()
570
571 def has_selected_shapes(self):
572 """Return true if a shape is currently selected"""
573 return self.canvas.HasSelectedShapes()
574
575 def HideLayer(self):
576 layer = self.current_layer()
577 if layer is not None:
578 layer.SetVisible(0)
579
580 def ShowLayer(self):
581 layer = self.current_layer()
582 if layer is not None:
583 layer.SetVisible(1)
584
585 def DuplicateLayer(self):
586 """Ceate a new layer above the selected layer with the same shapestore
587 """
588 layer = self.current_layer()
589 if layer is not None and hasattr(layer, "ShapeStore"):
590 new_layer = Layer(_("Copy of `%s'") % layer.Title(),
591 layer.ShapeStore(),
592 projection = layer.GetProjection())
593 new_classification = copy.deepcopy(layer.GetClassification())
594 new_layer.SetClassification(new_classification)
595 self.Map().AddLayer(new_layer)
596
597 def CanDuplicateLayer(self):
598 """Return whether the DuplicateLayer method can create a duplicate"""
599 layer = self.current_layer()
600 return layer is not None and hasattr(layer, "ShapeStore")
601
602 def LayerShowTable(self):
603 layer = self.current_layer()
604 if layer is not None:
605 table = layer.ShapeStore().Table()
606 name = "table_view" + str(id(table))
607 dialog = self.get_open_dialog(name)
608 if dialog is None:
609 dialog = tableview.LayerTableFrame(self, name,
610 _("Layer Table: %s") % layer.Title(),
611 layer, table)
612 self.add_dialog(name, dialog)
613 dialog.Show(True)
614 else:
615 # FIXME: bring dialog to front here
616 pass
617
618 def MapProjection(self):
619
620 name = "map_projection"
621 dialog = self.get_open_dialog(name)
622
623 if dialog is None:
624 map = self.canvas.Map()
625 dialog = projdialog.ProjFrame(self, name,
626 _("Map Projection: %s") % map.Title(), map)
627 self.add_dialog(name, dialog)
628 dialog.Show()
629 dialog.Raise()
630
631 def LayerProjection(self):
632
633 layer = self.current_layer()
634
635 name = "layer_projection" + str(id(layer))
636 dialog = self.get_open_dialog(name)
637
638 if dialog is None:
639 map = self.canvas.Map()
640 dialog = projdialog.ProjFrame(self, name,
641 _("Layer Projection: %s") % layer.Title(), layer)
642 self.add_dialog(name, dialog)
643 dialog.Show()
644 dialog.Raise()
645
646 def LayerEditProperties(self):
647
648 #
649 # the menu option for this should only be available if there
650 # is a current layer, so we don't need to check if the
651 # current layer is None
652 #
653
654 layer = self.current_layer()
655 self.OpenLayerProperties(layer)
656
657 def OpenLayerProperties(self, layer, group = None):
658 name = "layer_properties" + str(id(layer))
659 dialog = self.get_open_dialog(name)
660
661 if dialog is None:
662 dialog = Classifier(self, name, self.Map(), layer, group)
663 self.add_dialog(name, dialog)
664 dialog.Show()
665 dialog.Raise()
666
667 def LayerJoinTable(self):
668 layer = self.canvas.SelectedLayer()
669 if layer is not None:
670 dlg = JoinDialog(self, _("Join Layer with Table"),
671 self.application.session,
672 layer = layer)
673 dlg.ShowModal()
674
675 def LayerUnjoinTable(self):
676 layer = self.canvas.SelectedLayer()
677 if layer is not None:
678 orig_store = layer.ShapeStore().OrigShapeStore()
679 if orig_store:
680 layer.SetShapeStore(orig_store)
681
682 def ShowLegend(self):
683 if not self.LegendShown():
684 self.ToggleLegend()
685
686 def ToggleLegend(self):
687 """Show the legend if it's not shown otherwise hide it again"""
688 name = "legend"
689 dialog = self.FindRegisteredDock(name)
690
691 if dialog is None:
692 dialog = self.CreateDock(name, -1, _("Legend"), wxLAYOUT_LEFT)
693 legend.LegendPanel(dialog, None, self)
694 dialog.Dock()
695 dialog.GetPanel().SetMap(self.Map())
696 dialog.Show()
697 else:
698 dialog.Show(not dialog.IsShown())
699
700 def LegendShown(self):
701 """Return true iff the legend is currently open"""
702 dialog = self.FindRegisteredDock("legend")
703 return dialog is not None and dialog.IsShown()
704
705 def TableOpen(self):
706 dlg = wxFileDialog(self, _("Open Table"), ".", "",
707 _("DBF Files (*.dbf)") + "|*.dbf|" +
708 #_("CSV Files (*.csv)") + "|*.csv|" +
709 _("All Files (*.*)") + "|*.*",
710 wxOPEN)
711 if dlg.ShowModal() == wxID_OK:
712 filename = dlg.GetPath()
713 dlg.Destroy()
714 try:
715 table = self.application.session.OpenTableFile(filename)
716 except IOError:
717 # the layer couldn't be opened
718 self.RunMessageBox(_("Open Table"),
719 _("Can't open the file '%s'.") % filename)
720 else:
721 self.ShowTableView(table)
722
723 def TableClose(self):
724 tables = self.application.session.UnreferencedTables()
725
726 lst = [(t.Title(), t) for t in tables]
727 lst.sort()
728 titles = [i[0] for i in lst]
729 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
730 _("Close Table"), titles,
731 size = (400, 300),
732 style = wxDEFAULT_DIALOG_STYLE |
733 wxRESIZE_BORDER)
734 if dlg.ShowModal() == wxID_OK:
735 for i in dlg.GetValue():
736 self.application.session.RemoveTable(lst[i][1])
737
738
739 def TableShow(self):
740 """Offer a multi-selection dialog for tables to be displayed
741
742 The windows for the selected tables are opened or brought to
743 the front.
744 """
745 tables = self.application.session.Tables()
746
747 lst = [(t.Title(), t) for t in tables]
748 lst.sort()
749 titles = [i[0] for i in lst]
750 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
751 _("Show Table"), titles,
752 size = (400,300),
753 style = wxDEFAULT_DIALOG_STYLE |
754 wxRESIZE_BORDER)
755 if (dlg.ShowModal() == wxID_OK):
756 for i in dlg.GetValue():
757 # XXX: if the table belongs to a layer, open a
758 # LayerTableFrame instead of QueryTableFrame
759 self.ShowTableView(lst[i][1])
760
761 def TableJoin(self):
762 dlg = JoinDialog(self, _("Join Tables"), self.application.session)
763 dlg.ShowModal()
764
765 def ShowTableView(self, table):
766 """Open a table view for the table and optionally"""
767 name = "table_view%d" % id(table)
768 dialog = self.get_open_dialog(name)
769 if dialog is None:
770 dialog = tableview.QueryTableFrame(self, name,
771 _("Table: %s") % table.Title(),
772 table)
773 self.add_dialog(name, dialog)
774 dialog.Show(True)
775 dialog.Raise()
776
777 def TableRename(self):
778 """Let the user rename a table"""
779
780 # First, let the user select a table
781 tables = self.application.session.Tables()
782 lst = [(t.Title(), t) for t in tables]
783 lst.sort()
784 titles = [i[0] for i in lst]
785 dlg = wxMultipleChoiceDialog(self, _("Pick the table to rename:"),
786 _("Rename Table"), titles,
787 size = (400,300),
788 style = wxDEFAULT_DIALOG_STYLE |
789 wxRESIZE_BORDER)
790 if (dlg.ShowModal() == wxID_OK):
791 to_rename = [lst[i][1] for i in dlg.GetValue()]
792 dlg.Destroy()
793 else:
794 to_rename = []
795
796 # Second, let the user rename the layers
797 for table in to_rename:
798 dlg = wxTextEntryDialog(self, "Table Title: ", "Rename Table",
799 table.Title())
800 try:
801 if dlg.ShowModal() == wxID_OK:
802 title = dlg.GetValue()
803 if title != "":
804 table.SetTitle(title)
805
806 # Make sure the session is marked as modified.
807 # FIXME: This should be handled automatically,
808 # but that requires more changes to the tables
809 # than I have time for currently.
810 self.application.session.changed()
811 finally:
812 dlg.Destroy()
813
814
815 def ZoomInTool(self):
816 self.canvas.ZoomInTool()
817
818 def ZoomOutTool(self):
819 self.canvas.ZoomOutTool()
820
821 def PanTool(self):
822 self.canvas.PanTool()
823
824 def IdentifyTool(self):
825 self.canvas.IdentifyTool()
826 self.identify_view_on_demand(None, None)
827
828 def LabelTool(self):
829 self.canvas.LabelTool()
830
831 def FullExtent(self):
832 self.canvas.FitMapToWindow()
833
834 def FullLayerExtent(self):
835 self.canvas.FitLayerToWindow(self.current_layer())
836
837 def FullSelectionExtent(self):
838 self.canvas.FitSelectedToWindow()
839
840 def ExportMap(self):
841 self.canvas.Export()
842
843 def PrintMap(self):
844 self.canvas.Print()
845
846 def RenameMap(self):
847 dlg = wxTextEntryDialog(self, "Map Title: ", "Rename Map",
848 self.Map().Title())
849 if dlg.ShowModal() == wxID_OK:
850 title = dlg.GetValue()
851 if title != "":
852 self.Map().SetTitle(title)
853 self.__SetTitle(title)
854
855 dlg.Destroy()
856
857 def RenameLayer(self):
858 """Let the user rename the currently selected layer"""
859 layer = self.current_layer()
860 if layer is not None:
861 dlg = wxTextEntryDialog(self, "Layer Title: ", "Rename Layer",
862 layer.Title())
863 try:
864 if dlg.ShowModal() == wxID_OK:
865 title = dlg.GetValue()
866 if title != "":
867 layer.SetTitle(title)
868 finally:
869 dlg.Destroy()
870
871 def identify_view_on_demand(self, layer, shapes):
872 """Subscribed to the canvas' SHAPES_SELECTED message
873
874 If the current tool is the identify tool, at least one shape is
875 selected and the identify dialog is not shown, show the dialog.
876 """
877 # If the selection has become empty we don't need to do
878 # anything. Otherwise it could happen that the dialog was popped
879 # up when the selection became empty, e.g. when a new selection
880 # is opened while the identify tool is active and dialog had
881 # been closed
882 if not shapes:
883 return
884
885 name = "identify_view"
886 if self.canvas.CurrentTool() == "IdentifyTool":
887 if not self.dialog_open(name):
888 dialog = identifyview.IdentifyView(self, name)
889 self.add_dialog(name, dialog)
890 dialog.Show(True)
891 else:
892 # FIXME: bring dialog to front?
893 pass
894
895 def __SetTitle(self, title):
896 self.SetTitle("Thuban - " + title)
897
898 #
899 # Define all the commands available in the main window
900 #
901
902
903 # Helper functions to define common command implementations
904 def call_method(context, methodname, *args):
905 """Call the mainwindow's method methodname with args *args"""
906 apply(getattr(context.mainwindow, methodname), args)
907
908 def _method_command(name, title, method, helptext = "",
909 icon = "", sensitive = None, checked = None):
910 """Add a command implemented by a method of the mainwindow object"""
911 registry.Add(Command(name, title, call_method, args=(method,),
912 helptext = helptext, icon = icon,
913 sensitive = sensitive, checked = checked))
914
915 def make_check_current_tool(toolname):
916 """Return a function that tests if the currently active tool is toolname
917
918 The returned function can be called with the context and returns
919 true iff the currently active tool's name is toolname. It's directly
920 usable as the 'checked' callback of a command.
921 """
922 def check_current_tool(context, name=toolname):
923 return context.mainwindow.canvas.CurrentTool() == name
924 return check_current_tool
925
926 def _tool_command(name, title, method, toolname, helptext = "",
927 icon = "", sensitive = None):
928 """Add a tool command"""
929 registry.Add(ToolCommand(name, title, call_method, args=(method,),
930 helptext = helptext, icon = icon,
931 checked = make_check_current_tool(toolname),
932 sensitive = sensitive))
933
934 def _has_selected_layer(context):
935 """Return true if a layer is selected in the context"""
936 return context.mainwindow.has_selected_layer()
937
938 def _has_selected_shapes(context):
939 """Return true if a layer is selected in the context"""
940 return context.mainwindow.has_selected_shapes()
941
942 def _can_remove_layer(context):
943 return context.mainwindow.CanRemoveLayer()
944
945 def _has_tree_window_shown(context):
946 """Return true if the tree window is shown"""
947 return context.mainwindow.SessionTreeShown()
948
949 def _has_visible_map(context):
950 """Return true iff theres a visible map in the mainwindow.
951
952 A visible map is a map with at least one visible layer."""
953 map = context.mainwindow.Map()
954 if map is not None:
955 for layer in map.Layers():
956 if layer.Visible():
957 return 1
958 return 0
959
960 def _has_legend_shown(context):
961 """Return true if the legend window is shown"""
962 return context.mainwindow.LegendShown()
963
964 def _has_gdal_support(context):
965 """Return True if the GDAL is available"""
966 return Thuban.Model.resource.has_gdal_support()
967
968 def _has_dbconnections(context):
969 """Return whether the the session has database connections"""
970 return context.session.HasDBConnections()
971
972 # File menu
973 _method_command("new_session", _("&New Session"), "NewSession",
974 helptext = _("Start a new session"))
975 _method_command("open_session", _("&Open Session..."), "OpenSession",
976 helptext = _("Open a session file"))
977 _method_command("save_session", _("&Save Session"), "SaveSession",
978 helptext =_("Save this session to the file it was opened from"))
979 _method_command("save_session_as", _("Save Session &As..."), "SaveSessionAs",
980 helptext = _("Save this session to a new file"))
981 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
982 checked = _has_tree_window_shown,
983 helptext = _("Toggle on/off the session tree analysis window"))
984 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
985 checked = _has_legend_shown,
986 helptext = _("Toggle Legend on/off"))
987 _method_command("database_management", _("&Database Connections..."),
988 "DatabaseManagement")
989 _method_command("exit", _("E&xit"), "Exit",
990 helptext = _("Finish working with Thuban"))
991
992 # Help menu
993 _method_command("help_about", _("&About..."), "About",
994 helptext = _("Info about Thuban authors, version and modules"))
995
996
997 # Map menu
998 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
999 helptext = _("Set or change the map projection"))
1000
1001 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
1002 helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
1003 sensitive = _has_visible_map)
1004 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
1005 helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
1006 sensitive = _has_visible_map)
1007 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
1008 helptext = _("Switch to map-mode 'pan'"), icon = "pan",
1009 sensitive = _has_visible_map)
1010 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
1011 "IdentifyTool",
1012 helptext = _("Switch to map-mode 'identify'"), icon = "identify",
1013 sensitive = _has_visible_map)
1014 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
1015 helptext = _("Add/Remove labels"), icon = "label",
1016 sensitive = _has_visible_map)
1017 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
1018 helptext = _("Zoom to the full map extent"), icon = "fullextent",
1019 sensitive = _has_visible_map)
1020 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
1021 helptext = _("Zoom to the full layer extent"),
1022 icon = "fulllayerextent", sensitive = _has_selected_layer)
1023 _method_command("selected_full_extent", _("&Full selection extent"),
1024 "FullSelectionExtent",
1025 helptext = _("Zoom to the full selection extent"),
1026 icon = "fullselextent", sensitive = _has_selected_shapes)
1027 _method_command("map_export", _("E&xport"), "ExportMap",
1028 helptext = _("Export the map to file"))
1029 _method_command("map_print", _("Prin&t"), "PrintMap",
1030 helptext = _("Print the map"))
1031 _method_command("map_rename", _("&Rename..."), "RenameMap",
1032 helptext = _("Rename the map"))
1033 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
1034 helptext = _("Add a new layer to the map"))
1035 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
1036 helptext = _("Add a new image layer to the map"),
1037 sensitive = _has_gdal_support)
1038 _method_command("layer_add_db", _("Add &Database Layer..."), "AddDBLayer",
1039 helptext = _("Add a new database layer to active map"),
1040 sensitive = _has_dbconnections)
1041 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1042 helptext = _("Remove selected layer"),
1043 sensitive = _can_remove_layer)
1044
1045 # Layer menu
1046 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1047 sensitive = _has_selected_layer,
1048 helptext = _("Specify projection for selected layer"))
1049 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1050 helptext = _("Duplicate selected layer"),
1051 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1052 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1053 helptext = _("Rename selected layer"),
1054 sensitive = _has_selected_layer)
1055 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1056 helptext = _("Raise selected layer"),
1057 sensitive = _has_selected_layer)
1058 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1059 helptext = _("Lower selected layer"),
1060 sensitive = _has_selected_layer)
1061 _method_command("layer_show", _("&Show"), "ShowLayer",
1062 helptext = _("Make selected layer visible"),
1063 sensitive = _has_selected_layer)
1064 _method_command("layer_hide", _("&Hide"), "HideLayer",
1065 helptext = _("Make selected layer unvisible"),
1066 sensitive = _has_selected_layer)
1067 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1068 helptext = _("Show the selected layer's table"),
1069 sensitive = _has_selected_layer)
1070 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1071 sensitive = _has_selected_layer,
1072 helptext = _("Edit the properties of the selected layer"))
1073 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1074 sensitive = _has_selected_layer,
1075 helptext = _("Join and attach a table to the selected layer"))
1076
1077 def _can_unjoin(context):
1078 """Return whether the Layer/Unjoin command can be executed.
1079
1080 This is the case if a layer is selected and that layer has a
1081 shapestore that has an original shapestore.
1082 """
1083 layer = context.mainwindow.SelectedLayer()
1084 if layer is None:
1085 return 0
1086 getstore = getattr(layer, "ShapeStore", None)
1087 if getstore is not None:
1088 return getstore().OrigShapeStore() is not None
1089 else:
1090 return 0
1091 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1092 sensitive = _can_unjoin,
1093 helptext = _("Undo the last join operation"))
1094
1095
1096 def _has_tables(context):
1097 return bool(context.session.Tables())
1098
1099 # Table menu
1100 _method_command("table_open", _("&Open..."), "TableOpen",
1101 helptext = _("Open a DBF-table from a file"))
1102 _method_command("table_close", _("&Close..."), "TableClose",
1103 sensitive = lambda context: bool(context.session.UnreferencedTables()),
1104 helptext = _("Close one or more tables from a list"))
1105 _method_command("table_rename", _("&Rename..."), "TableRename",
1106 sensitive = _has_tables,
1107 helptext = _("Rename one or more tables"))
1108 _method_command("table_show", _("&Show..."), "TableShow",
1109 sensitive = _has_tables,
1110 helptext = _("Show one or more tables in a dialog"))
1111 _method_command("table_join", _("&Join..."), "TableJoin",
1112 sensitive = _has_tables,
1113 helptext = _("Join two tables creating a new one"))
1114
1115 # Export only under Windows ...
1116 map_menu = ["layer_add", "layer_add_db", "rasterlayer_add", "layer_remove",
1117 None,
1118 "map_rename",
1119 "map_projection",
1120 None,
1121 "map_zoom_in_tool", "map_zoom_out_tool",
1122 "map_pan_tool",
1123 "map_full_extent",
1124 "layer_full_extent",
1125 "selected_full_extent",
1126 None,
1127 "map_identify_tool", "map_label_tool",
1128 None,
1129 "toggle_legend",
1130 None]
1131 if wxPlatform == '__WXMSW__':
1132 map_menu.append("map_export")
1133 map_menu.append("map_print")
1134
1135 # the menu structure
1136 main_menu = Menu("<main>", "<main>",
1137 [Menu("file", _("&File"),
1138 ["new_session", "open_session", None,
1139 "save_session", "save_session_as", None,
1140 "database_management", None,
1141 "toggle_session_tree", None,
1142 "exit"]),
1143 Menu("map", _("&Map"), map_menu),
1144 Menu("layer", _("&Layer"),
1145 ["layer_rename", "layer_duplicate",
1146 None,
1147 "layer_raise", "layer_lower",
1148 None,
1149 "layer_show", "layer_hide",
1150 None,
1151 "layer_projection",
1152 None,
1153 "layer_show_table",
1154 "layer_jointable",
1155 "layer_unjointable",
1156 None,
1157 "layer_properties"]),
1158 Menu("table", _("&Table"),
1159 ["table_open", "table_close", "table_rename",
1160 None,
1161 "table_show",
1162 None,
1163 "table_join"]),
1164 Menu("help", _("&Help"),
1165 ["help_about"])])
1166
1167 # the main toolbar
1168
1169 main_toolbar = Menu("<toolbar>", "<toolbar>",
1170 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1171 "map_full_extent",
1172 "layer_full_extent",
1173 "selected_full_extent",
1174 None,
1175 "map_identify_tool", "map_label_tool"])
1176

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26