/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2551 - (show annotations)
Thu Jan 27 14:19:41 2005 UTC (20 years, 1 month ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 53657 byte(s)
Add a new dialog box for raster layers. The dialog box allows
the user to toggle a mask that is generated by ProjectRasterFile
and is used to only draw the real parts of the projected image.

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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26