/[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 2544 - (hide annotations)
Mon Jan 24 11:19:53 2005 UTC (20 years, 1 month ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 53390 byte(s)
Rework the status bar updates a bit to make sure the message about
the projections is produced at the right times.

* Thuban/UI/mainwindow.py (MainWindow.update_status_bar_messages):
New class variable with messages that may require a status bar
update.
(MainWindow.view_position_changed)
(MainWindow.update_status_bar): Rename from view_position_changed
to update_status_bar.  It's meaning has changed now that it may
also generate messages about problems with projection settings.
(MainWindow.__init__): Use the new update_status_bar_messages
class variable to subscribe update_status_bar
(MainWindow.set_position_text): Update doc-string.  This method
has to be renamed at some point.  See doc-string and comments.
(MainWindow.OnClose): Unsubscribe update_status_bar from all
messages in update_status_bar_messages

* Thuban/UI/viewport.py (ViewPort.forwarded_map_messages): New
class attribute.  map messages to be forwarded by the viewport.
(ViewPort._subscribe_map, ViewPort._unsubscribe_map): (un)subscribe
the messages in forwarded_map_messages

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