/[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 1622 - (hide annotations)
Thu Aug 21 11:56:06 2003 UTC (21 years, 6 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 45074 byte(s)
(MainWindow.AddLayer): Changed dialog settings to allow multiple files
to load into the map.  Also reduced selection to *.shp as a default.

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