/[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 1695 - (hide annotations)
Mon Sep 1 12:45:07 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 45352 byte(s)
* Thuban/UI/dbdialog.py (ChooseDBTableDialog.__init__): Change the
parameter list to just parent and session
(ChooseDBTableDialog.__set_properties): Removed. Setting the
selection of empty list boxes is not allowed (and produces C++
assertion errors) and the rest of the setup is better done in
__init__ anyway.
(ChooseDBTableDialog.OnCancel, ChooseDBTableDialog.OnOK)
(ChooseDBTableDialog.OnLBDClick, DBDialog.OnOK): Use the Python
builtins True/False for booleans to avoid warnings from wxPython

* Thuban/UI/mainwindow.py (MainWindow.AddDBLayer): Adapt to new
ChooseDBTableDialog constructor parameters.

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