/[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 1782 - (hide annotations)
Mon Oct 6 17:31:54 2003 UTC (21 years, 5 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 46247 byte(s)
(MainWindow.__init__): Subscribe to the
canvas' TITLE_CHANGED messages
(MainWindow.update_title): New. Update the main window's title
(MainWindow.__SetTitle): Removed. Use update_title instead.
(MainWindow.SetMap): Use update_title instead of __SetTitle
(MainWindow.RenameMap): Do change the window title explicitly
here. That's handled in a proper MVC way now.
(MainWindow.title_changed): New. Subscriber for the TITLE_CHANGED
messages

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