/[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 1644 - (show annotations)
Mon Aug 25 12:44:55 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: 45164 byte(s)
(__ThubanVersion__): Remove this and
replace it and the comment with __BuildDate__ by the Source: and
Id: cvs keywords as used in the other files.

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 from wxPython.wx import __version__ as wxPython_version
23
24 import Thuban
25 import Thuban.version
26
27 from Thuban import _
28 from Thuban.Model.session import create_empty_session
29 from Thuban.Model.layer import Layer, RasterLayer
30 from Thuban.Model.postgisdb import PostGISShapeStore, has_postgis_support
31 # XXX: replace this by
32 # from wxPython.lib.dialogs import wxMultipleChoiceDialog
33 # when Thuban does not support wxPython 2.4.0 any more.
34 from Thuban.UI.multiplechoicedialog import wxMultipleChoiceDialog
35
36 import view
37 import tree
38 import tableview, identifyview
39 from Thuban.UI.classifier import Classifier
40 import legend
41 from menu import Menu
42
43 from context import Context
44 from command import registry, Command, ToolCommand
45 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
46 MAP_REPLACED
47 from about import About
48
49 from Thuban.UI.dock import DockFrame
50 from Thuban.UI.join import JoinDialog
51 from Thuban.UI.dbdialog import DBFrame, ChooseDBTableDialog
52 import resource
53 import Thuban.Model.resource
54
55 import projdialog
56
57
58 class MainWindow(DockFrame):
59
60 # Some messages that can be subscribed/unsubscribed directly through
61 # the MapCanvas come in fact from other objects. This is a map to
62 # map those messages to the names of the instance variables they
63 # actually come from. This delegation is implemented in the
64 # Subscribe and unsubscribed methods
65 delegated_messages = {LAYER_SELECTED: "canvas",
66 SHAPES_SELECTED: "canvas",
67 MAP_REPLACED: "canvas"}
68
69 # Methods delegated to some instance variables. The delegation is
70 # implemented in the __getattr__ method.
71 delegated_methods = {"SelectLayer": "canvas",
72 "SelectShapes": "canvas",
73 "SelectedLayer": "canvas",
74 "SelectedShapes": "canvas",
75 }
76
77 def __init__(self, parent, ID, title, application, interactor,
78 initial_message = None, size = wxSize(-1, -1)):
79 DockFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
80 #wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
81
82 self.application = application
83
84 self.CreateStatusBar()
85 if initial_message:
86 self.SetStatusText(initial_message)
87
88 self.identify_view = None
89
90 self.init_ids()
91
92 # creat the menubar from the main_menu description
93 self.SetMenuBar(self.build_menu_bar(main_menu))
94
95 # Similarly, create the toolbar from main_toolbar
96 toolbar = self.build_toolbar(main_toolbar)
97 # call Realize to make sure that the tools appear.
98 toolbar.Realize()
99
100
101 # Create the map canvas
102 canvas = view.MapCanvas(self, -1)
103 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
104 canvas.Subscribe(SHAPES_SELECTED, self.identify_view_on_demand)
105 self.canvas = canvas
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 dlg.Destroy()
380
381 def SaveSession(self):
382 if self.application.session.filename == None:
383 self.SaveSessionAs()
384 else:
385 self.application.SaveSession()
386
387 def SaveSessionAs(self):
388 dlg = wxFileDialog(self, _("Save Session As"), ".", "",
389 "Thuban Session File (*.thuban)|*.thuban",
390 wxSAVE|wxOVERWRITE_PROMPT)
391 if dlg.ShowModal() == wxID_OK:
392 self.application.session.SetFilename(dlg.GetPath())
393 self.application.SaveSession()
394 dlg.Destroy()
395
396 def Exit(self):
397 self.Close(False)
398
399 def OnClose(self, event):
400 result = self.save_modified_session(can_veto = event.CanVeto())
401 if result == wxID_CANCEL:
402 event.Veto()
403 else:
404 # FIXME: it would be better to tie the unsubscription to
405 # wx's destroy event, but that isn't implemented for wxGTK
406 # yet.
407 self.canvas.Unsubscribe(VIEW_POSITION, self.view_position_changed)
408 DockFrame.OnClose(self, event)
409 for dlg in self.dialogs.values():
410 dlg.Destroy()
411 self.canvas.Destroy()
412 self.Destroy()
413
414 def SetMap(self, map):
415 self.canvas.SetMap(map)
416 self.__SetTitle(map.Title())
417
418 dialog = self.FindRegisteredDock("legend")
419 if dialog is not None:
420 dialog.GetPanel().SetMap(self.Map())
421
422 def Map(self):
423 """Return the map displayed by this mainwindow"""
424
425 return self.canvas.Map()
426
427 def ToggleSessionTree(self):
428 """If the session tree is shown close it otherwise create a new tree"""
429 name = "session_tree"
430 dialog = self.get_open_dialog(name)
431 if dialog is None:
432 dialog = tree.SessionTreeView(self, self.application, name)
433 self.add_dialog(name, dialog)
434 dialog.Show(True)
435 else:
436 dialog.Close()
437
438 def SessionTreeShown(self):
439 """Return true iff the session tree is currently shown"""
440 return self.get_open_dialog("session_tree") is not None
441
442 def About(self):
443 dlg = About(self)
444 dlg.ShowModal()
445 dlg.Destroy()
446
447 def DatabaseManagement(self):
448 name = "dbmanagement"
449 dialog = self.get_open_dialog(name)
450 if dialog is None:
451 map = self.canvas.Map()
452 dialog = DBFrame(self, name, self.application.Session())
453 self.add_dialog(name, dialog)
454 dialog.Show()
455 dialog.Raise()
456
457 def AddLayer(self):
458 dlg = wxFileDialog(self, _("Select one or more data files"), ".", "",
459 _("Shapefiles (*.shp)") + "|*.shp|" +
460 _("All Files (*.*)") + "|*.*",
461 wxOPEN | wxMULTIPLE)
462 if dlg.ShowModal() == wxID_OK:
463 filenames = dlg.GetPaths()
464 for filename in filenames:
465 title = os.path.splitext(os.path.basename(filename))[0]
466 map = self.canvas.Map()
467 has_layers = map.HasLayers()
468 try:
469 store = self.application.Session().OpenShapefile(filename)
470 except IOError:
471 # the layer couldn't be opened
472 self.RunMessageBox(_("Add Layer"),
473 _("Can't open the file '%s'.")%filename)
474 else:
475 layer = Layer(title, store)
476 map.AddLayer(layer)
477 if not has_layers:
478 # if we're adding a layer to an empty map, fit the
479 # new map to the window
480 self.canvas.FitMapToWindow()
481 dlg.Destroy()
482
483 def AddRasterLayer(self):
484 dlg = wxFileDialog(self, _("Select an image file"), ".", "", "*.*",
485 wxOPEN)
486 if dlg.ShowModal() == wxID_OK:
487 filename = dlg.GetPath()
488 title = os.path.splitext(os.path.basename(filename))[0]
489 map = self.canvas.Map()
490 has_layers = map.HasLayers()
491 try:
492 layer = RasterLayer(title, filename)
493 except IOError:
494 # the layer couldn't be opened
495 self.RunMessageBox(_("Add Image Layer"),
496 _("Can't open the file '%s'.") % filename)
497 else:
498 map.AddLayer(layer)
499 if not has_layers:
500 # if we're adding a layer to an empty map, fit the
501 # new map to the window
502 self.canvas.FitMapToWindow()
503 dlg.Destroy()
504
505 def AddDBLayer(self):
506 """Add a layer read from a database"""
507 session = self.application.Session()
508 dlg = ChooseDBTableDialog(self.application.Session(), self,-1, "")
509
510 if dlg.ShowModal() == wxID_OK:
511 dbconn, dbtable = dlg.GetTable()
512 try:
513 title = str(dbtable)
514
515 # Chose the correct Interface for the database type
516 store = PostGISShapeStore(dbconn, dbtable)
517 session.AddShapeStore(store)
518 layer = Layer(title, store)
519 except:
520 # Some error occured while initializing the layer
521 self.RunMessageBox(_("Add Layer from database"),
522 _("Can't open the database table '%s'")
523 % dbtable)
524
525 map = self.canvas.Map()
526
527 has_layers = map.HasLayers()
528 map.AddLayer(layer)
529 if not has_layers:
530 self.canvas.FitMapToWindow()
531
532 dlg.Destroy()
533
534 def RemoveLayer(self):
535 layer = self.current_layer()
536 if layer is not None:
537 self.canvas.Map().RemoveLayer(layer)
538
539 def CanRemoveLayer(self):
540 """Return true if the currently selected layer can be deleted.
541
542 If no layer is selected return False.
543
544 The return value of this method determines whether the remove
545 layer command is sensitive in menu.
546 """
547 layer = self.current_layer()
548 if layer is not None:
549 return self.canvas.Map().CanRemoveLayer(layer)
550 return False
551
552 def RaiseLayer(self):
553 layer = self.current_layer()
554 if layer is not None:
555 self.canvas.Map().RaiseLayer(layer)
556
557 def LowerLayer(self):
558 layer = self.current_layer()
559 if layer is not None:
560 self.canvas.Map().LowerLayer(layer)
561
562 def current_layer(self):
563 """Return the currently selected layer.
564
565 If no layer is selected, return None
566 """
567 return self.canvas.SelectedLayer()
568
569 def has_selected_layer(self):
570 """Return true if a layer is currently selected"""
571 return self.canvas.HasSelectedLayer()
572
573 def has_selected_shapes(self):
574 """Return true if a shape is currently selected"""
575 return self.canvas.HasSelectedShapes()
576
577 def HideLayer(self):
578 layer = self.current_layer()
579 if layer is not None:
580 layer.SetVisible(0)
581
582 def ShowLayer(self):
583 layer = self.current_layer()
584 if layer is not None:
585 layer.SetVisible(1)
586
587 def DuplicateLayer(self):
588 """Ceate a new layer above the selected layer with the same shapestore
589 """
590 layer = self.current_layer()
591 if layer is not None and hasattr(layer, "ShapeStore"):
592 new_layer = Layer(_("Copy of `%s'") % layer.Title(),
593 layer.ShapeStore(),
594 projection = layer.GetProjection())
595 new_classification = copy.deepcopy(layer.GetClassification())
596 new_layer.SetClassification(new_classification)
597 self.Map().AddLayer(new_layer)
598
599 def CanDuplicateLayer(self):
600 """Return whether the DuplicateLayer method can create a duplicate"""
601 layer = self.current_layer()
602 return layer is not None and hasattr(layer, "ShapeStore")
603
604 def LayerShowTable(self):
605 layer = self.current_layer()
606 if layer is not None:
607 table = layer.ShapeStore().Table()
608 name = "table_view" + str(id(table))
609 dialog = self.get_open_dialog(name)
610 if dialog is None:
611 dialog = tableview.LayerTableFrame(self, name,
612 _("Layer Table: %s") % layer.Title(),
613 layer, table)
614 self.add_dialog(name, dialog)
615 dialog.Show(True)
616 else:
617 # FIXME: bring dialog to front here
618 pass
619
620 def MapProjection(self):
621
622 name = "map_projection"
623 dialog = self.get_open_dialog(name)
624
625 if dialog is None:
626 map = self.canvas.Map()
627 dialog = projdialog.ProjFrame(self, name,
628 _("Map Projection: %s") % map.Title(), map)
629 self.add_dialog(name, dialog)
630 dialog.Show()
631 dialog.Raise()
632
633 def LayerProjection(self):
634
635 layer = self.current_layer()
636
637 name = "layer_projection" + str(id(layer))
638 dialog = self.get_open_dialog(name)
639
640 if dialog is None:
641 map = self.canvas.Map()
642 dialog = projdialog.ProjFrame(self, name,
643 _("Layer Projection: %s") % layer.Title(), layer)
644 self.add_dialog(name, dialog)
645 dialog.Show()
646 dialog.Raise()
647
648 def LayerEditProperties(self):
649
650 #
651 # the menu option for this should only be available if there
652 # is a current layer, so we don't need to check if the
653 # current layer is None
654 #
655
656 layer = self.current_layer()
657 self.OpenLayerProperties(layer)
658
659 def OpenLayerProperties(self, layer, group = None):
660 name = "layer_properties" + str(id(layer))
661 dialog = self.get_open_dialog(name)
662
663 if dialog is None:
664 dialog = Classifier(self, name, self.Map(), layer, group)
665 self.add_dialog(name, dialog)
666 dialog.Show()
667 dialog.Raise()
668
669 def LayerJoinTable(self):
670 layer = self.canvas.SelectedLayer()
671 if layer is not None:
672 dlg = JoinDialog(self, _("Join Layer with Table"),
673 self.application.session,
674 layer = layer)
675 dlg.ShowModal()
676
677 def LayerUnjoinTable(self):
678 layer = self.canvas.SelectedLayer()
679 if layer is not None:
680 orig_store = layer.ShapeStore().OrigShapeStore()
681 if orig_store:
682 layer.SetShapeStore(orig_store)
683
684 def ShowLegend(self):
685 if not self.LegendShown():
686 self.ToggleLegend()
687
688 def ToggleLegend(self):
689 """Show the legend if it's not shown otherwise hide it again"""
690 name = "legend"
691 dialog = self.FindRegisteredDock(name)
692
693 if dialog is None:
694 dialog = self.CreateDock(name, -1, _("Legend"), wxLAYOUT_LEFT)
695 legend.LegendPanel(dialog, None, self)
696 dialog.Dock()
697 dialog.GetPanel().SetMap(self.Map())
698 dialog.Show()
699 else:
700 dialog.Show(not dialog.IsShown())
701
702 def LegendShown(self):
703 """Return true iff the legend is currently open"""
704 dialog = self.FindRegisteredDock("legend")
705 return dialog is not None and dialog.IsShown()
706
707 def TableOpen(self):
708 dlg = wxFileDialog(self, _("Open Table"), ".", "",
709 _("DBF Files (*.dbf)") + "|*.dbf|" +
710 #_("CSV Files (*.csv)") + "|*.csv|" +
711 _("All Files (*.*)") + "|*.*",
712 wxOPEN)
713 if dlg.ShowModal() == wxID_OK:
714 filename = dlg.GetPath()
715 dlg.Destroy()
716 try:
717 table = self.application.session.OpenTableFile(filename)
718 except IOError:
719 # the layer couldn't be opened
720 self.RunMessageBox(_("Open Table"),
721 _("Can't open the file '%s'.") % filename)
722 else:
723 self.ShowTableView(table)
724
725 def TableClose(self):
726 tables = self.application.session.UnreferencedTables()
727
728 lst = [(t.Title(), t) for t in tables]
729 lst.sort()
730 titles = [i[0] for i in lst]
731 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
732 _("Close Table"), titles,
733 size = (400, 300),
734 style = wxDEFAULT_DIALOG_STYLE |
735 wxRESIZE_BORDER)
736 if dlg.ShowModal() == wxID_OK:
737 for i in dlg.GetValue():
738 self.application.session.RemoveTable(lst[i][1])
739
740
741 def TableShow(self):
742 """Offer a multi-selection dialog for tables to be displayed
743
744 The windows for the selected tables are opened or brought to
745 the front.
746 """
747 tables = self.application.session.Tables()
748
749 lst = [(t.Title(), t) for t in tables]
750 lst.sort()
751 titles = [i[0] for i in lst]
752 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
753 _("Show Table"), titles,
754 size = (400,300),
755 style = wxDEFAULT_DIALOG_STYLE |
756 wxRESIZE_BORDER)
757 if (dlg.ShowModal() == wxID_OK):
758 for i in dlg.GetValue():
759 # XXX: if the table belongs to a layer, open a
760 # LayerTableFrame instead of QueryTableFrame
761 self.ShowTableView(lst[i][1])
762
763 def TableJoin(self):
764 dlg = JoinDialog(self, _("Join Tables"), self.application.session)
765 dlg.ShowModal()
766
767 def ShowTableView(self, table):
768 """Open a table view for the table and optionally"""
769 name = "table_view%d" % id(table)
770 dialog = self.get_open_dialog(name)
771 if dialog is None:
772 dialog = tableview.QueryTableFrame(self, name,
773 _("Table: %s") % table.Title(),
774 table)
775 self.add_dialog(name, dialog)
776 dialog.Show(True)
777 dialog.Raise()
778
779 def TableRename(self):
780 """Let the user rename a table"""
781
782 # First, let the user select a table
783 tables = self.application.session.Tables()
784 lst = [(t.Title(), t) for t in tables]
785 lst.sort()
786 titles = [i[0] for i in lst]
787 dlg = wxMultipleChoiceDialog(self, _("Pick the table to rename:"),
788 _("Rename Table"), titles,
789 size = (400,300),
790 style = wxDEFAULT_DIALOG_STYLE |
791 wxRESIZE_BORDER)
792 if (dlg.ShowModal() == wxID_OK):
793 to_rename = [lst[i][1] for i in dlg.GetValue()]
794 dlg.Destroy()
795 else:
796 to_rename = []
797
798 # Second, let the user rename the layers
799 for table in to_rename:
800 dlg = wxTextEntryDialog(self, "Table Title: ", "Rename Table",
801 table.Title())
802 try:
803 if dlg.ShowModal() == wxID_OK:
804 title = dlg.GetValue()
805 if title != "":
806 table.SetTitle(title)
807
808 # Make sure the session is marked as modified.
809 # FIXME: This should be handled automatically,
810 # but that requires more changes to the tables
811 # than I have time for currently.
812 self.application.session.changed()
813 finally:
814 dlg.Destroy()
815
816
817 def ZoomInTool(self):
818 self.canvas.ZoomInTool()
819
820 def ZoomOutTool(self):
821 self.canvas.ZoomOutTool()
822
823 def PanTool(self):
824 self.canvas.PanTool()
825
826 def IdentifyTool(self):
827 self.canvas.IdentifyTool()
828 self.identify_view_on_demand(None, None)
829
830 def LabelTool(self):
831 self.canvas.LabelTool()
832
833 def FullExtent(self):
834 self.canvas.FitMapToWindow()
835
836 def FullLayerExtent(self):
837 self.canvas.FitLayerToWindow(self.current_layer())
838
839 def FullSelectionExtent(self):
840 self.canvas.FitSelectedToWindow()
841
842 def ExportMap(self):
843 self.canvas.Export()
844
845 def PrintMap(self):
846 self.canvas.Print()
847
848 def RenameMap(self):
849 dlg = wxTextEntryDialog(self, "Map Title: ", "Rename Map",
850 self.Map().Title())
851 if dlg.ShowModal() == wxID_OK:
852 title = dlg.GetValue()
853 if title != "":
854 self.Map().SetTitle(title)
855 self.__SetTitle(title)
856
857 dlg.Destroy()
858
859 def RenameLayer(self):
860 """Let the user rename the currently selected layer"""
861 layer = self.current_layer()
862 if layer is not None:
863 dlg = wxTextEntryDialog(self, "Layer Title: ", "Rename Layer",
864 layer.Title())
865 try:
866 if dlg.ShowModal() == wxID_OK:
867 title = dlg.GetValue()
868 if title != "":
869 layer.SetTitle(title)
870 finally:
871 dlg.Destroy()
872
873 def identify_view_on_demand(self, layer, shapes):
874 """Subscribed to the canvas' SHAPES_SELECTED message
875
876 If the current tool is the identify tool, at least one shape is
877 selected and the identify dialog is not shown, show the dialog.
878 """
879 # If the selection has become empty we don't need to do
880 # anything. Otherwise it could happen that the dialog was popped
881 # up when the selection became empty, e.g. when a new selection
882 # is opened while the identify tool is active and dialog had
883 # been closed
884 if not shapes:
885 return
886
887 name = "identify_view"
888 if self.canvas.CurrentTool() == "IdentifyTool":
889 if not self.dialog_open(name):
890 dialog = identifyview.IdentifyView(self, name)
891 self.add_dialog(name, dialog)
892 dialog.Show(True)
893 else:
894 # FIXME: bring dialog to front?
895 pass
896
897 def __SetTitle(self, title):
898 self.SetTitle("Thuban - " + title)
899
900 #
901 # Define all the commands available in the main window
902 #
903
904
905 # Helper functions to define common command implementations
906 def call_method(context, methodname, *args):
907 """Call the mainwindow's method methodname with args *args"""
908 apply(getattr(context.mainwindow, methodname), args)
909
910 def _method_command(name, title, method, helptext = "",
911 icon = "", sensitive = None, checked = None):
912 """Add a command implemented by a method of the mainwindow object"""
913 registry.Add(Command(name, title, call_method, args=(method,),
914 helptext = helptext, icon = icon,
915 sensitive = sensitive, checked = checked))
916
917 def make_check_current_tool(toolname):
918 """Return a function that tests if the currently active tool is toolname
919
920 The returned function can be called with the context and returns
921 true iff the currently active tool's name is toolname. It's directly
922 usable as the 'checked' callback of a command.
923 """
924 def check_current_tool(context, name=toolname):
925 return context.mainwindow.canvas.CurrentTool() == name
926 return check_current_tool
927
928 def _tool_command(name, title, method, toolname, helptext = "",
929 icon = "", sensitive = None):
930 """Add a tool command"""
931 registry.Add(ToolCommand(name, title, call_method, args=(method,),
932 helptext = helptext, icon = icon,
933 checked = make_check_current_tool(toolname),
934 sensitive = sensitive))
935
936 def _has_selected_layer(context):
937 """Return true if a layer is selected in the context"""
938 return context.mainwindow.has_selected_layer()
939
940 def _has_selected_shapes(context):
941 """Return true if a layer is selected in the context"""
942 return context.mainwindow.has_selected_shapes()
943
944 def _can_remove_layer(context):
945 return context.mainwindow.CanRemoveLayer()
946
947 def _has_tree_window_shown(context):
948 """Return true if the tree window is shown"""
949 return context.mainwindow.SessionTreeShown()
950
951 def _has_visible_map(context):
952 """Return true iff theres a visible map in the mainwindow.
953
954 A visible map is a map with at least one visible layer."""
955 map = context.mainwindow.Map()
956 if map is not None:
957 for layer in map.Layers():
958 if layer.Visible():
959 return 1
960 return 0
961
962 def _has_legend_shown(context):
963 """Return true if the legend window is shown"""
964 return context.mainwindow.LegendShown()
965
966 def _has_gdal_support(context):
967 """Return True if the GDAL is available"""
968 return Thuban.Model.resource.has_gdal_support()
969
970 def _has_dbconnections(context):
971 """Return whether the the session has database connections"""
972 return context.session.HasDBConnections()
973
974 def _has_postgis_support(context):
975 return has_postgis_support()
976
977
978 # File menu
979 _method_command("new_session", _("&New Session"), "NewSession",
980 helptext = _("Start a new session"))
981 _method_command("open_session", _("&Open Session..."), "OpenSession",
982 helptext = _("Open a session file"))
983 _method_command("save_session", _("&Save Session"), "SaveSession",
984 helptext =_("Save this session to the file it was opened from"))
985 _method_command("save_session_as", _("Save Session &As..."), "SaveSessionAs",
986 helptext = _("Save this session to a new file"))
987 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
988 checked = _has_tree_window_shown,
989 helptext = _("Toggle on/off the session tree analysis window"))
990 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
991 checked = _has_legend_shown,
992 helptext = _("Toggle Legend on/off"))
993 _method_command("database_management", _("&Database Connections..."),
994 "DatabaseManagement",
995 sensitive = _has_postgis_support)
996 _method_command("exit", _("E&xit"), "Exit",
997 helptext = _("Finish working with Thuban"))
998
999 # Help menu
1000 _method_command("help_about", _("&About..."), "About",
1001 helptext = _("Info about Thuban authors, version and modules"))
1002
1003
1004 # Map menu
1005 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
1006 helptext = _("Set or change the map projection"))
1007
1008 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
1009 helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
1010 sensitive = _has_visible_map)
1011 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
1012 helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
1013 sensitive = _has_visible_map)
1014 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
1015 helptext = _("Switch to map-mode 'pan'"), icon = "pan",
1016 sensitive = _has_visible_map)
1017 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
1018 "IdentifyTool",
1019 helptext = _("Switch to map-mode 'identify'"), icon = "identify",
1020 sensitive = _has_visible_map)
1021 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
1022 helptext = _("Add/Remove labels"), icon = "label",
1023 sensitive = _has_visible_map)
1024 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
1025 helptext = _("Zoom to the full map extent"), icon = "fullextent",
1026 sensitive = _has_visible_map)
1027 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
1028 helptext = _("Zoom to the full layer extent"),
1029 icon = "fulllayerextent", sensitive = _has_selected_layer)
1030 _method_command("selected_full_extent", _("&Full selection extent"),
1031 "FullSelectionExtent",
1032 helptext = _("Zoom to the full selection extent"),
1033 icon = "fullselextent", sensitive = _has_selected_shapes)
1034 _method_command("map_export", _("E&xport"), "ExportMap",
1035 helptext = _("Export the map to file"))
1036 _method_command("map_print", _("Prin&t"), "PrintMap",
1037 helptext = _("Print the map"))
1038 _method_command("map_rename", _("&Rename..."), "RenameMap",
1039 helptext = _("Rename the map"))
1040 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
1041 helptext = _("Add a new layer to the map"))
1042 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
1043 helptext = _("Add a new image layer to the map"),
1044 sensitive = _has_gdal_support)
1045 _method_command("layer_add_db", _("Add &Database Layer..."), "AddDBLayer",
1046 helptext = _("Add a new database layer to active map"),
1047 sensitive = _has_dbconnections)
1048 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1049 helptext = _("Remove selected layer"),
1050 sensitive = _can_remove_layer)
1051
1052 # Layer menu
1053 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1054 sensitive = _has_selected_layer,
1055 helptext = _("Specify projection for selected layer"))
1056 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1057 helptext = _("Duplicate selected layer"),
1058 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1059 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1060 helptext = _("Rename selected layer"),
1061 sensitive = _has_selected_layer)
1062 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1063 helptext = _("Raise selected layer"),
1064 sensitive = _has_selected_layer)
1065 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1066 helptext = _("Lower selected layer"),
1067 sensitive = _has_selected_layer)
1068 _method_command("layer_show", _("&Show"), "ShowLayer",
1069 helptext = _("Make selected layer visible"),
1070 sensitive = _has_selected_layer)
1071 _method_command("layer_hide", _("&Hide"), "HideLayer",
1072 helptext = _("Make selected layer unvisible"),
1073 sensitive = _has_selected_layer)
1074 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1075 helptext = _("Show the selected layer's table"),
1076 sensitive = _has_selected_layer)
1077 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1078 sensitive = _has_selected_layer,
1079 helptext = _("Edit the properties of the selected layer"))
1080 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1081 sensitive = _has_selected_layer,
1082 helptext = _("Join and attach a table to the selected layer"))
1083
1084 def _can_unjoin(context):
1085 """Return whether the Layer/Unjoin command can be executed.
1086
1087 This is the case if a layer is selected and that layer has a
1088 shapestore that has an original shapestore.
1089 """
1090 layer = context.mainwindow.SelectedLayer()
1091 if layer is None:
1092 return 0
1093 getstore = getattr(layer, "ShapeStore", None)
1094 if getstore is not None:
1095 return getstore().OrigShapeStore() is not None
1096 else:
1097 return 0
1098 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1099 sensitive = _can_unjoin,
1100 helptext = _("Undo the last join operation"))
1101
1102
1103 def _has_tables(context):
1104 return bool(context.session.Tables())
1105
1106 # Table menu
1107 _method_command("table_open", _("&Open..."), "TableOpen",
1108 helptext = _("Open a DBF-table from a file"))
1109 _method_command("table_close", _("&Close..."), "TableClose",
1110 sensitive = lambda context: bool(context.session.UnreferencedTables()),
1111 helptext = _("Close one or more tables from a list"))
1112 _method_command("table_rename", _("&Rename..."), "TableRename",
1113 sensitive = _has_tables,
1114 helptext = _("Rename one or more tables"))
1115 _method_command("table_show", _("&Show..."), "TableShow",
1116 sensitive = _has_tables,
1117 helptext = _("Show one or more tables in a dialog"))
1118 _method_command("table_join", _("&Join..."), "TableJoin",
1119 sensitive = _has_tables,
1120 helptext = _("Join two tables creating a new one"))
1121
1122 # Export only under Windows ...
1123 map_menu = ["layer_add", "layer_add_db", "rasterlayer_add", "layer_remove",
1124 None,
1125 "map_rename",
1126 "map_projection",
1127 None,
1128 "map_zoom_in_tool", "map_zoom_out_tool",
1129 "map_pan_tool",
1130 "map_full_extent",
1131 "layer_full_extent",
1132 "selected_full_extent",
1133 None,
1134 "map_identify_tool", "map_label_tool",
1135 None,
1136 "toggle_legend",
1137 None]
1138 if wxPlatform == '__WXMSW__':
1139 map_menu.append("map_export")
1140 map_menu.append("map_print")
1141
1142 # the menu structure
1143 main_menu = Menu("<main>", "<main>",
1144 [Menu("file", _("&File"),
1145 ["new_session", "open_session", None,
1146 "save_session", "save_session_as", None,
1147 "database_management", None,
1148 "toggle_session_tree", None,
1149 "exit"]),
1150 Menu("map", _("&Map"), map_menu),
1151 Menu("layer", _("&Layer"),
1152 ["layer_rename", "layer_duplicate",
1153 None,
1154 "layer_raise", "layer_lower",
1155 None,
1156 "layer_show", "layer_hide",
1157 None,
1158 "layer_projection",
1159 None,
1160 "layer_show_table",
1161 "layer_jointable",
1162 "layer_unjointable",
1163 None,
1164 "layer_properties"]),
1165 Menu("table", _("&Table"),
1166 ["table_open", "table_close", "table_rename",
1167 None,
1168 "table_show",
1169 None,
1170 "table_join"]),
1171 Menu("help", _("&Help"),
1172 ["help_about"])])
1173
1174 # the main toolbar
1175
1176 main_toolbar = Menu("<toolbar>", "<toolbar>",
1177 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1178 "map_full_extent",
1179 "layer_full_extent",
1180 "selected_full_extent",
1181 None,
1182 "map_identify_tool", "map_label_tool"])
1183

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26