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

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

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

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26