/[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 1782 - (show annotations)
Mon Oct 6 17:31:54 2003 UTC (21 years, 5 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 46247 byte(s)
(MainWindow.__init__): Subscribe to the
canvas' TITLE_CHANGED messages
(MainWindow.update_title): New. Update the main window's title
(MainWindow.__SetTitle): Removed. Use update_title instead.
(MainWindow.SetMap): Use update_title instead of __SetTitle
(MainWindow.RenameMap): Do change the window title explicitly
here. That's handled in a proper MVC way now.
(MainWindow.title_changed): New. Subscriber for the TITLE_CHANGED
messages

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26