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