/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1644 - (hide 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 bh 535 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4     # Bernhard Herzog <[email protected]>
5 frank 911 # Frank Koormann <[email protected]>
6 bh 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 bh 1644 # $Source$
16     # $Id$
17 bh 6
18 bh 188 import os
19 bh 1094 import copy
20 bh 6
21     from wxPython.wx import *
22 frank 923 from wxPython.wx import __version__ as wxPython_version
23 bh 6
24     import Thuban
25 frank 923 import Thuban.version
26    
27 jan 374 from Thuban import _
28 bh 188 from Thuban.Model.session import create_empty_session
29 jonathan 937 from Thuban.Model.layer import Layer, RasterLayer
30 bh 1625 from Thuban.Model.postgisdb import PostGISShapeStore, has_postgis_support
31 jan 1155 # 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 bh 6 import view
37     import tree
38     import tableview, identifyview
39 jonathan 633 from Thuban.UI.classifier import Classifier
40 jonathan 550 import legend
41 bh 188 from menu import Menu
42 bh 6
43 bh 222 from context import Context
44 bh 357 from command import registry, Command, ToolCommand
45 bh 1464 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
46     MAP_REPLACED
47 jonathan 1309 from about import About
48 bh 6
49 bh 704 from Thuban.UI.dock import DockFrame
50 jonathan 879 from Thuban.UI.join import JoinDialog
51 bh 1620 from Thuban.UI.dbdialog import DBFrame, ChooseDBTableDialog
52 jonathan 653 import resource
53 jonathan 1164 import Thuban.Model.resource
54 jonathan 563
55 jonathan 713 import projdialog
56 jonathan 653
57 jonathan 1309
58 jonathan 573 class MainWindow(DockFrame):
59 bh 6
60 bh 535 # 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 bh 1464 SHAPES_SELECTED: "canvas",
67     MAP_REPLACED: "canvas"}
68 bh 535
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 bh 1074 "SelectedLayer": "canvas",
74 jonathan 879 "SelectedShapes": "canvas",
75 bh 535 }
76    
77 bh 235 def __init__(self, parent, ID, title, application, interactor,
78 bh 238 initial_message = None, size = wxSize(-1, -1)):
79 jonathan 573 DockFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
80     #wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
81 bh 6
82 bh 227 self.application = application
83 bh 37
84 bh 6 self.CreateStatusBar()
85 bh 235 if initial_message:
86     self.SetStatusText(initial_message)
87 bh 6
88     self.identify_view = None
89    
90     self.init_ids()
91    
92 bh 191 # creat the menubar from the main_menu description
93 bh 188 self.SetMenuBar(self.build_menu_bar(main_menu))
94 bh 6
95 bh 191 # Similarly, create the toolbar from main_toolbar
96     toolbar = self.build_toolbar(main_toolbar)
97 bh 13 # call Realize to make sure that the tools appear.
98     toolbar.Realize()
99 bh 6
100 jonathan 563
101 bh 6 # Create the map canvas
102 bh 535 canvas = view.MapCanvas(self, -1)
103 bh 123 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
104 bh 535 canvas.Subscribe(SHAPES_SELECTED, self.identify_view_on_demand)
105 bh 6 self.canvas = canvas
106    
107 jonathan 573 self.SetMainWindow(self.canvas)
108    
109 jonathan 563 self.SetAutoLayout(True)
110    
111 bh 31 self.init_dialogs()
112    
113 jonathan 1233 self.ShowLegend()
114    
115 frank 951 EVT_CLOSE(self, self.OnClose)
116 bh 6
117 bh 535 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 bh 6 def init_ids(self):
149     """Initialize the ids"""
150     self.current_id = 6000
151     self.id_to_name = {}
152     self.name_to_id = {}
153 bh 193 self.events_bound = {}
154 bh 6
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 bh 188
167 bh 193 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 bh 188 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 bh 314 """Return a wxMenu built from the menu description menudesc"""
187 bh 188 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 bh 191 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 bh 6 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 bh 193 self.bind_command_events(command, ID)
241 bh 6 else:
242 jan 374 print _("Unknown command %s") % name
243 bh 6
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 jonathan 653 bitmap = resource.GetBitmapResource(command.Icon(),
258     wxBITMAP_TYPE_XPM)
259 bh 6 toolbar.AddTool(ID, bitmap,
260     shortHelpString = command.HelpText(),
261     isToggle = command.IsCheckCommand())
262 bh 193 self.bind_command_events(command, ID)
263 bh 6 else:
264 jan 374 print _("Unknown command %s") % name
265 bh 6
266 bh 281 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 bh 6 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 bh 281 command.Execute(self.Context())
276 bh 6 else:
277 jan 374 print _("Unknown command ID %d") % event.GetId()
278 bh 6
279     def update_command_ui(self, event):
280     #print "update_command_ui", self.id_to_name[event.GetId()]
281 bh 281 context = self.Context()
282 bh 6 command = registry.Command(self.id_to_name[event.GetId()])
283     if command is not None:
284 bh 357 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 bh 222 event.SetText(command.DynText(context))
295 bh 13 if command.IsCheckCommand():
296 bh 357 event.Check(command.Checked(context))
297 bh 6
298 bh 20 def RunMessageBox(self, title, text, flags = wxOK | wxICON_INFORMATION):
299 bh 181 """Run a modal message box with the given text, title and flags
300 bh 20 and return the result"""
301     dlg = wxMessageDialog(self, text, title, flags)
302 bh 316 dlg.CenterOnParent()
303 bh 20 result = dlg.ShowModal()
304     dlg.Destroy()
305     return result
306    
307 bh 31 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 jan 374 raise RuntimeError(_("The Dialog named %s is already open") % name)
317 bh 31 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 bh 123 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 bh 321 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 bh 123 self.SetStatusText(text)
344    
345 bh 58 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 bh 227 if self.application.session.WasModified():
355 bh 58 flags = wxYES_NO | wxICON_QUESTION
356     if can_veto:
357     flags = flags | wxCANCEL
358 jan 374 result = self.RunMessageBox(_("Exit"),
359     _("The session has been modified."
360 bh 58 " 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 bh 6 def NewSession(self):
369 jonathan 937 if self.save_modified_session() != wxID_CANCEL:
370     self.application.SetSession(create_empty_session())
371 bh 6
372     def OpenSession(self):
373 jonathan 937 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 bh 6
381     def SaveSession(self):
382 bh 227 if self.application.session.filename == None:
383 jan 102 self.SaveSessionAs()
384 jonathan 487 else:
385     self.application.SaveSession()
386 bh 6
387     def SaveSessionAs(self):
388 jonathan 431 dlg = wxFileDialog(self, _("Save Session As"), ".", "",
389 jonathan 879 "Thuban Session File (*.thuban)|*.thuban",
390     wxSAVE|wxOVERWRITE_PROMPT)
391 bh 6 if dlg.ShowModal() == wxID_OK:
392 bh 227 self.application.session.SetFilename(dlg.GetPath())
393     self.application.SaveSession()
394 bh 6 dlg.Destroy()
395    
396     def Exit(self):
397 jonathan 621 self.Close(False)
398 bh 6
399 frank 951 def OnClose(self, event):
400 bh 58 result = self.save_modified_session(can_veto = event.CanVeto())
401     if result == wxID_CANCEL:
402 bh 6 event.Veto()
403     else:
404 bh 307 # 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 jonathan 974 DockFrame.OnClose(self, event)
409 frank 1056 for dlg in self.dialogs.values():
410     dlg.Destroy()
411     self.canvas.Destroy()
412 bh 6 self.Destroy()
413    
414     def SetMap(self, map):
415     self.canvas.SetMap(map)
416 jonathan 653 self.__SetTitle(map.Title())
417 bh 6
418 jonathan 768 dialog = self.FindRegisteredDock("legend")
419     if dialog is not None:
420     dialog.GetPanel().SetMap(self.Map())
421    
422 bh 310 def Map(self):
423     """Return the map displayed by this mainwindow"""
424 jonathan 563
425 bh 310 return self.canvas.Map()
426    
427 bh 622 def ToggleSessionTree(self):
428     """If the session tree is shown close it otherwise create a new tree"""
429 bh 37 name = "session_tree"
430     dialog = self.get_open_dialog(name)
431     if dialog is None:
432 bh 227 dialog = tree.SessionTreeView(self, self.application, name)
433 bh 37 self.add_dialog(name, dialog)
434 jonathan 512 dialog.Show(True)
435 bh 37 else:
436 bh 622 dialog.Close()
437 bh 37
438 bh 622 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 jonathan 517
442 bh 6 def About(self):
443 jonathan 1309 dlg = About(self)
444     dlg.ShowModal()
445     dlg.Destroy()
446 bh 6
447 bh 1620 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 bh 6 def AddLayer(self):
458 jan 1622 dlg = wxFileDialog(self, _("Select one or more data files"), ".", "",
459     _("Shapefiles (*.shp)") + "|*.shp|" +
460     _("All Files (*.*)") + "|*.*",
461     wxOPEN | wxMULTIPLE)
462 bh 6 if dlg.ShowModal() == wxID_OK:
463 jan 1622 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 bh 6 dlg.Destroy()
482    
483 jonathan 937 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 jonathan 963 layer = RasterLayer(title, filename)
493 jonathan 937 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 jonathan 963 map.AddLayer(layer)
499 jonathan 937 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 bh 1620 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 bh 6 def RemoveLayer(self):
535     layer = self.current_layer()
536     if layer is not None:
537     self.canvas.Map().RemoveLayer(layer)
538    
539 bh 299 def CanRemoveLayer(self):
540     """Return true if the currently selected layer can be deleted.
541    
542 jonathan 621 If no layer is selected return False.
543 bh 299
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 jonathan 621 return False
551 bh 299
552 bh 6 def RaiseLayer(self):
553     layer = self.current_layer()
554     if layer is not None:
555     self.canvas.Map().RaiseLayer(layer)
556 bh 222
557 bh 6 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 bh 535 return self.canvas.SelectedLayer()
568 bh 6
569     def has_selected_layer(self):
570     """Return true if a layer is currently selected"""
571 bh 535 return self.canvas.HasSelectedLayer()
572 bh 6
573 jonathan 829 def has_selected_shapes(self):
574     """Return true if a shape is currently selected"""
575     return self.canvas.HasSelectedShapes()
576    
577 bh 6 def HideLayer(self):
578     layer = self.current_layer()
579     if layer is not None:
580     layer.SetVisible(0)
581 bh 1094
582 bh 6 def ShowLayer(self):
583     layer = self.current_layer()
584     if layer is not None:
585     layer.SetVisible(1)
586    
587 bh 1094 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 bh 6 def LayerShowTable(self):
605     layer = self.current_layer()
606     if layer is not None:
607 bh 1219 table = layer.ShapeStore().Table()
608 bh 31 name = "table_view" + str(id(table))
609     dialog = self.get_open_dialog(name)
610     if dialog is None:
611 bh 535 dialog = tableview.LayerTableFrame(self, name,
612 jan 1023 _("Layer Table: %s") % layer.Title(),
613     layer, table)
614 bh 31 self.add_dialog(name, dialog)
615 jan 1035 dialog.Show(True)
616 bh 31 else:
617     # FIXME: bring dialog to front here
618     pass
619 bh 6
620 jonathan 729 def MapProjection(self):
621 bh 6
622 jonathan 729 name = "map_projection"
623 jonathan 713 dialog = self.get_open_dialog(name)
624    
625     if dialog is None:
626     map = self.canvas.Map()
627 jonathan 750 dialog = projdialog.ProjFrame(self, name,
628     _("Map Projection: %s") % map.Title(), map)
629 jonathan 713 self.add_dialog(name, dialog)
630     dialog.Show()
631     dialog.Raise()
632    
633 jonathan 729 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 jonathan 750 dialog = projdialog.ProjFrame(self, name,
643     _("Layer Projection: %s") % layer.Title(), layer)
644 jonathan 729 self.add_dialog(name, dialog)
645     dialog.Show()
646     dialog.Raise()
647    
648 jonathan 640 def LayerEditProperties(self):
649 jonathan 363
650 jonathan 487 #
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 jonathan 640 self.OpenLayerProperties(layer)
658 jonathan 550
659 jonathan 640 def OpenLayerProperties(self, layer, group = None):
660     name = "layer_properties" + str(id(layer))
661 jonathan 487 dialog = self.get_open_dialog(name)
662    
663     if dialog is None:
664 bh 1142 dialog = Classifier(self, name, self.Map(), layer, group)
665 jonathan 487 self.add_dialog(name, dialog)
666     dialog.Show()
667 jonathan 573 dialog.Raise()
668 jonathan 487
669 jonathan 879 def LayerJoinTable(self):
670 bh 1072 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 jonathan 550
677 jonathan 879 def LayerUnjoinTable(self):
678 bh 1074 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 jonathan 879
684 jonathan 621 def ShowLegend(self):
685 bh 622 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 jonathan 550 name = "legend"
691 jonathan 573 dialog = self.FindRegisteredDock(name)
692 jonathan 550
693     if dialog is None:
694 jonathan 640 dialog = self.CreateDock(name, -1, _("Legend"), wxLAYOUT_LEFT)
695 jonathan 573 legend.LegendPanel(dialog, None, self)
696 jonathan 580 dialog.Dock()
697 bh 622 dialog.GetPanel().SetMap(self.Map())
698     dialog.Show()
699     else:
700     dialog.Show(not dialog.IsShown())
701 jonathan 563
702 bh 622 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 jonathan 563
707 jonathan 879 def TableOpen(self):
708     dlg = wxFileDialog(self, _("Open Table"), ".", "",
709 bh 1037 _("DBF Files (*.dbf)") + "|*.dbf|" +
710     #_("CSV Files (*.csv)") + "|*.csv|" +
711     _("All Files (*.*)") + "|*.*",
712 jonathan 879 wxOPEN)
713     if dlg.ShowModal() == wxID_OK:
714 bh 1054 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 jonathan 879
725     def TableClose(self):
726 bh 1068 tables = self.application.session.UnreferencedTables()
727 jonathan 879
728 jan 1084 lst = [(t.Title(), t) for t in tables]
729     lst.sort()
730     titles = [i[0] for i in lst]
731 bh 1068 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
732 jan 1084 _("Close Table"), titles,
733     size = (400, 300),
734     style = wxDEFAULT_DIALOG_STYLE |
735     wxRESIZE_BORDER)
736 bh 1068 if dlg.ShowModal() == wxID_OK:
737     for i in dlg.GetValue():
738 jan 1084 self.application.session.RemoveTable(lst[i][1])
739 bh 1068
740    
741 jonathan 879 def TableShow(self):
742 jan 1014 """Offer a multi-selection dialog for tables to be displayed
743 bh 1054
744 jan 1014 The windows for the selected tables are opened or brought to
745     the front.
746     """
747     tables = self.application.session.Tables()
748 jonathan 879
749 jan 1084 lst = [(t.Title(), t) for t in tables]
750     lst.sort()
751     titles = [i[0] for i in lst]
752 jan 1014 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
753 jan 1084 _("Show Table"), titles,
754 frank 1076 size = (400,300),
755     style = wxDEFAULT_DIALOG_STYLE |
756     wxRESIZE_BORDER)
757 jan 1014 if (dlg.ShowModal() == wxID_OK):
758     for i in dlg.GetValue():
759 jan 1023 # XXX: if the table belongs to a layer, open a
760     # LayerTableFrame instead of QueryTableFrame
761 jan 1084 self.ShowTableView(lst[i][1])
762 jan 1014
763 jonathan 879 def TableJoin(self):
764     dlg = JoinDialog(self, _("Join Tables"), self.application.session)
765 frank 1001 dlg.ShowModal()
766 jonathan 879
767 bh 1054 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 jonathan 1393 dialog.Raise()
778 bh 1054
779 bh 1126 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 bh 6 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 bh 49 self.identify_view_on_demand(None, None)
829 bh 6
830     def LabelTool(self):
831     self.canvas.LabelTool()
832    
833     def FullExtent(self):
834     self.canvas.FitMapToWindow()
835    
836 jonathan 821 def FullLayerExtent(self):
837     self.canvas.FitLayerToWindow(self.current_layer())
838    
839 jonathan 829 def FullSelectionExtent(self):
840     self.canvas.FitSelectedToWindow()
841    
842 frank 911 def ExportMap(self):
843     self.canvas.Export()
844    
845 bh 6 def PrintMap(self):
846     self.canvas.Print()
847    
848 jonathan 653 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 bh 1126 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 bh 535 def identify_view_on_demand(self, layer, shapes):
874 bh 787 """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 bh 31 name = "identify_view"
888     if self.canvas.CurrentTool() == "IdentifyTool":
889     if not self.dialog_open(name):
890 bh 535 dialog = identifyview.IdentifyView(self, name)
891 bh 31 self.add_dialog(name, dialog)
892 jonathan 563 dialog.Show(True)
893 bh 31 else:
894 bh 33 # FIXME: bring dialog to front?
895 bh 31 pass
896 bh 6
897 jonathan 653 def __SetTitle(self, title):
898     self.SetTitle("Thuban - " + title)
899    
900 bh 6 #
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 bh 222 """Call the mainwindow's method methodname with args *args"""
908     apply(getattr(context.mainwindow, methodname), args)
909 bh 6
910 jan 110 def _method_command(name, title, method, helptext = "",
911 bh 622 icon = "", sensitive = None, checked = None):
912 bh 222 """Add a command implemented by a method of the mainwindow object"""
913 bh 6 registry.Add(Command(name, title, call_method, args=(method,),
914 jan 110 helptext = helptext, icon = icon,
915 bh 622 sensitive = sensitive, checked = checked))
916 jan 110
917 bh 270 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 bh 6 def _tool_command(name, title, method, toolname, helptext = "",
929 bh 310 icon = "", sensitive = None):
930 bh 6 """Add a tool command"""
931 bh 357 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 bh 6
936     def _has_selected_layer(context):
937     """Return true if a layer is selected in the context"""
938 bh 222 return context.mainwindow.has_selected_layer()
939 bh 6
940 jonathan 829 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 bh 299 def _can_remove_layer(context):
945     return context.mainwindow.CanRemoveLayer()
946    
947 jan 264 def _has_tree_window_shown(context):
948     """Return true if the tree window is shown"""
949 bh 622 return context.mainwindow.SessionTreeShown()
950 jan 264
951 bh 310 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 jonathan 550 def _has_legend_shown(context):
963     """Return true if the legend window is shown"""
964 bh 622 return context.mainwindow.LegendShown()
965 bh 310
966 jonathan 1164 def _has_gdal_support(context):
967     """Return True if the GDAL is available"""
968     return Thuban.Model.resource.has_gdal_support()
969 jonathan 550
970 bh 1620 def _has_dbconnections(context):
971     """Return whether the the session has database connections"""
972     return context.session.HasDBConnections()
973    
974 bh 1625 def _has_postgis_support(context):
975     return has_postgis_support()
976    
977    
978 bh 6 # File menu
979 jan 1140 _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 bh 622 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
988 jan 1140 checked = _has_tree_window_shown,
989     helptext = _("Toggle on/off the session tree analysis window"))
990 bh 622 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
991 jan 1140 checked = _has_legend_shown,
992     helptext = _("Toggle Legend on/off"))
993 bh 1620 _method_command("database_management", _("&Database Connections..."),
994 bh 1625 "DatabaseManagement",
995     sensitive = _has_postgis_support)
996 jan 1140 _method_command("exit", _("E&xit"), "Exit",
997     helptext = _("Finish working with Thuban"))
998 bh 6
999     # Help menu
1000 jan 1140 _method_command("help_about", _("&About..."), "About",
1001     helptext = _("Info about Thuban authors, version and modules"))
1002 bh 6
1003    
1004     # Map menu
1005 jan 1140 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
1006     helptext = _("Set or change the map projection"))
1007 bh 6
1008 jan 374 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
1009     helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
1010 bh 310 sensitive = _has_visible_map)
1011 jan 374 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
1012     helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
1013 bh 310 sensitive = _has_visible_map)
1014 jan 374 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
1015     helptext = _("Switch to map-mode 'pan'"), icon = "pan",
1016 bh 310 sensitive = _has_visible_map)
1017 jan 374 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
1018     "IdentifyTool",
1019     helptext = _("Switch to map-mode 'identify'"), icon = "identify",
1020 bh 310 sensitive = _has_visible_map)
1021 jan 374 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
1022     helptext = _("Add/Remove labels"), icon = "label",
1023 bh 310 sensitive = _has_visible_map)
1024 jan 374 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
1025 jan 1140 helptext = _("Zoom to the full map extent"), icon = "fullextent",
1026 bh 310 sensitive = _has_visible_map)
1027 jonathan 821 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
1028 jan 1140 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 frank 911 _method_command("map_export", _("E&xport"), "ExportMap",
1035 jan 1140 helptext = _("Export the map to file"))
1036 jan 374 _method_command("map_print", _("Prin&t"), "PrintMap",
1037     helptext = _("Print the map"))
1038 jonathan 815 _method_command("map_rename", _("&Rename..."), "RenameMap",
1039 jonathan 653 helptext = _("Rename the map"))
1040 jonathan 815 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
1041 jan 1140 helptext = _("Add a new layer to the map"))
1042 jonathan 937 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
1043 jonathan 1164 helptext = _("Add a new image layer to the map"),
1044     sensitive = _has_gdal_support)
1045 bh 1620 _method_command("layer_add_db", _("Add &Database Layer..."), "AddDBLayer",
1046     helptext = _("Add a new database layer to active map"),
1047     sensitive = _has_dbconnections)
1048 jan 374 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1049 jan 1140 helptext = _("Remove selected layer"),
1050 bh 299 sensitive = _can_remove_layer)
1051 jonathan 729
1052     # Layer menu
1053 jonathan 815 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1054 jan 1140 sensitive = _has_selected_layer,
1055     helptext = _("Specify projection for selected layer"))
1056 bh 1094 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1057 jan 1140 helptext = _("Duplicate selected layer"),
1058 bh 1094 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1059 bh 1126 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1060     helptext = _("Rename selected layer"),
1061     sensitive = _has_selected_layer)
1062 jan 374 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1063 jan 1140 helptext = _("Raise selected layer"),
1064 bh 6 sensitive = _has_selected_layer)
1065 jan 374 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1066 jan 1140 helptext = _("Lower selected layer"),
1067 bh 6 sensitive = _has_selected_layer)
1068 jan 374 _method_command("layer_show", _("&Show"), "ShowLayer",
1069 jan 1140 helptext = _("Make selected layer visible"),
1070 bh 6 sensitive = _has_selected_layer)
1071 jan 374 _method_command("layer_hide", _("&Hide"), "HideLayer",
1072 jan 1140 helptext = _("Make selected layer unvisible"),
1073 bh 6 sensitive = _has_selected_layer)
1074 jan 374 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1075     helptext = _("Show the selected layer's table"),
1076 bh 6 sensitive = _has_selected_layer)
1077 jonathan 815 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1078 jan 1140 sensitive = _has_selected_layer,
1079     helptext = _("Edit the properties of the selected layer"))
1080 jonathan 879 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1081 jan 1140 sensitive = _has_selected_layer,
1082     helptext = _("Join and attach a table to the selected layer"))
1083 bh 1074
1084     def _can_unjoin(context):
1085 bh 1080 """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 bh 1074 layer = context.mainwindow.SelectedLayer()
1091 bh 1080 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 jonathan 879 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1099 jan 1140 sensitive = _can_unjoin,
1100     helptext = _("Undo the last join operation"))
1101 bh 188
1102 bh 1126
1103     def _has_tables(context):
1104     return bool(context.session.Tables())
1105    
1106 jonathan 879 # Table menu
1107 jan 1140 _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 bh 1126 _method_command("table_rename", _("&Rename..."), "TableRename",
1113 jan 1140 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 bh 1126 _method_command("table_join", _("&Join..."), "TableJoin",
1119 jan 1140 sensitive = _has_tables,
1120     helptext = _("Join two tables creating a new one"))
1121 jonathan 879
1122 frank 911 # Export only under Windows ...
1123 bh 1620 map_menu = ["layer_add", "layer_add_db", "rasterlayer_add", "layer_remove",
1124 bh 188 None,
1125 jonathan 1164 "map_rename",
1126 bh 188 "map_projection",
1127     None,
1128     "map_zoom_in_tool", "map_zoom_out_tool",
1129 bh 1620 "map_pan_tool",
1130     "map_full_extent",
1131 jonathan 829 "layer_full_extent",
1132     "selected_full_extent",
1133 bh 188 None,
1134 jonathan 815 "map_identify_tool", "map_label_tool",
1135 bh 188 None,
1136 bh 624 "toggle_legend",
1137 frank 911 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 bh 1620 "database_management", None,
1148 frank 911 "toggle_session_tree", None,
1149     "exit"]),
1150     Menu("map", _("&Map"), map_menu),
1151 jan 374 Menu("layer", _("&Layer"),
1152 bh 1126 ["layer_rename", "layer_duplicate",
1153 bh 188 None,
1154 bh 1126 "layer_raise", "layer_lower",
1155     None,
1156 bh 188 "layer_show", "layer_hide",
1157     None,
1158 jonathan 879 "layer_projection",
1159     None,
1160 jonathan 363 "layer_show_table",
1161 jonathan 879 "layer_jointable",
1162     "layer_unjointable",
1163 jonathan 363 None,
1164 jonathan 815 "layer_properties"]),
1165 jonathan 879 Menu("table", _("&Table"),
1166 bh 1126 ["table_open", "table_close", "table_rename",
1167 jonathan 879 None,
1168 bh 1052 "table_show",
1169 jonathan 879 None,
1170     "table_join"]),
1171 jan 374 Menu("help", _("&Help"),
1172 bh 188 ["help_about"])])
1173 bh 191
1174     # the main toolbar
1175    
1176     main_toolbar = Menu("<toolbar>", "<toolbar>",
1177 frank 351 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1178 jonathan 829 "map_full_extent",
1179     "layer_full_extent",
1180     "selected_full_extent",
1181     None,
1182 frank 351 "map_identify_tool", "map_label_tool"])
1183 jonathan 1293

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26