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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26