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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.6  
changed lines
  Added in v.2700

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26