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

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

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

revision 1786 by bh, Wed Oct 8 10:39:11 2003 UTC revision 2051 by frank, Wed Jan 21 17:09:15 2004 UTC
# Line 21  from Thuban import _ Line 21  from Thuban import _
21  from Thuban.Model.proj import Projection, ProjFile  from Thuban.Model.proj import Projection, ProjFile
22    
23  from Thuban.Model.resource import get_user_proj_file, get_system_proj_file, \  from Thuban.Model.resource import get_user_proj_file, get_system_proj_file, \
24                                    read_proj_file, write_proj_file                                    read_proj_file, write_proj_file, \
25                                      DEFAULT_PROJ_FILE, EPSG_PROJ_FILE, \
26                                      EPSG_DEPRECATED_PROJ_FILE
27  from Thuban.UI.dialogs import NonModalNonParentDialog  from Thuban.UI.dialogs import NonModalNonParentDialog
28    
29    from common import ThubanBeginBusyCursor, ThubanEndBusyCursor
30    from sizers import NotebookLikeSizer
31    from projlist import PROJ_SELECTION_CHANGED, ProjectionList
32    from common import ThubanBeginBusyCursor, ThubanEndBusyCursor
33    
34    
35    
 ID_PROJ_ADVANCED  = 4001  
36  ID_PROJ_PROJCHOICE = 4002  ID_PROJ_PROJCHOICE = 4002
37  ID_PROJ_ADDTOLIST    = 4003  ID_PROJ_ADDTOLIST    = 4003
38  ID_PROJ_NEW       = 4004  ID_PROJ_NEW       = 4004
# Line 49  class ProjFrame(NonModalNonParentDialog) Line 56  class ProjFrame(NonModalNonParentDialog)
56                          SetProjection(projection)                          SetProjection(projection)
57                          GetProjection()                          GetProjection()
58          """          """
59                    NonModalNonParentDialog.__init__(self, parent, name, title)
60    
61            self.projection_panel_defs = [
62                ("tmerc", _("Transverse Mercator"), TMPanel),
63                ("utm", _("Universal Transverse Mercator"), UTMPanel),
64                ("lcc", _("Lambert Conic Conformal"), LCCPanel),
65                ("latlong", _("Geographic"), GeoPanel),
66                ("longlat", _("Geographic"), GeoPanel)]#longlat is an alias of proj
67          self.receiver = receiver          self.receiver = receiver
68          self.haveTried = False          self.haveTried = False
69          self.curProjPanel = None          self.curProjPanel = None
70            self.__usrProjFile = None
71            self._sys_proj_files = {}
72    
73          self.projPanels = []          self.build_dialog()
74          self.projPanels.append(          self.Layout()
             ("tmerc", _("Transverse Mercator"), TMPanel))  
         self.projPanels.append(  
             ("utm", _("Universal Transverse Mercator"), UTMPanel))  
         self.projPanels.append(  
             ("lcc", _("Lambert Conic Conformal"), LCCPanel))  
         self.projPanels.append(  
             ("latlong", _("Geographic"), GeoPanel))  
75    
76          NonModalNonParentDialog.__init__(self, parent, name, title)          self.originalProjection = self.receiver.GetProjection()
         # originally generate by wxGlade  
         self.panel_1 = wxPanel(self, -1)  
         self.panel_edit = wxPanel(self, -1)  
         self.label_5 = wxStaticText(self.panel_1, -1,  
                         _("Available Projections:"))  
77    
78          # Projection List specific actions (Import/Export/Remove)          self.projection_list.SelectProjection(self.originalProjection)
79          self.button_import = wxButton(self.panel_1, ID_PROJ_IMPORT,          self.projection_list.SetFocus()
                                       _("Import..."))  
         self.button_export = wxButton(self.panel_1, ID_PROJ_EXPORT,  
                                       _("Export..."))  
         self.button_remove = wxButton(self.panel_1, ID_PROJ_REMOVE,  
                                       _("Remove"))  
   
         # The Projection List  
         self.availprojs = wxListBox(self.panel_1, ID_PROJ_AVAIL,  
                                     style=wxLB_EXTENDED|wxLB_SORT)  
         self.projfilepath = wxStaticText(self.panel_1, -1, "")  
80    
81          # Projection Specific Entries (Name/Projection)      def build_dialog(self):
82          self.label_2 = wxStaticText(self.panel_edit, -1, _("Name:"))          """Build the dialog's widgets and set the event handlers"""
83          self.projname = wxTextCtrl(self.panel_edit, ID_PROJ_PROJNAME, "")          self.topBox = top_box = wxBoxSizer(wxVERTICAL)
84          self.label_3 = wxStaticText(self.panel_edit, -1, _("Projection:"))  
85          self.projchoice = wxChoice(self.panel_edit, ID_PROJ_PROJCHOICE)          main_box = wxBoxSizer(wxHORIZONTAL)
86            top_box.Add(main_box, 1, wxALL|wxEXPAND)
87    
88            #
89            #    The projection list and associated controls
90            #
91            vbox = wxBoxSizer(wxVERTICAL)
92            main_box.Add(vbox, 4, wxALL|wxEXPAND)
93    
94            #label = wxStaticText(self, -1, _("Available Projections:"))
95            #vbox.Add(label, 0, wxLEFT|wxRIGHT|wxTOP, 4)
96    
97            hbox = wxBoxSizer(wxHORIZONTAL)
98            vbox.Add(hbox, 1, wxALL|wxEXPAND)
99            proj_files = [self.load_user_proj(),
100                          self.load_system_proj(DEFAULT_PROJ_FILE)]
101            self.projection_list = ProjectionList(self, proj_files,
102                                                  self.receiver.GetProjection())
103            hbox.Add(self.projection_list, 1, wxALL|wxEXPAND|wxADJUST_MINSIZE, 4)
104            self.projection_list.Subscribe(PROJ_SELECTION_CHANGED,
105                                           self.proj_selection_changed)
106    
107          # Projection Specific actions (New/Save/Add)          # Projection List specific actions (Import/Export/Remove)
108          self.button_new = wxButton(self.panel_edit, ID_PROJ_NEW, _("New"))          buttons = wxBoxSizer(wxVERTICAL)
109          self.button_add = wxButton(self.panel_edit, ID_PROJ_ADDTOLIST,          hbox.Add(buttons, 0, wxALL)
110                                        _("Add to List"))          self.button_import = wxButton(self, ID_PROJ_IMPORT, _("Import..."))
111          self.button_save = wxButton(self.panel_edit, ID_PROJ_SAVE,_("Update"))          EVT_BUTTON(self, ID_PROJ_IMPORT, self._OnImport)
112            buttons.Add(self.button_import, 1, wxALL|wxEXPAND, 4)
113            self.button_export = wxButton(self, ID_PROJ_EXPORT, _("Export..."))
114            EVT_BUTTON(self, ID_PROJ_EXPORT, self._OnExport)
115            buttons.Add(self.button_export, 1, wxALL|wxEXPAND, 4)
116            buttons.Add(20, 20, 0, wxEXPAND, 0)
117            self.button_remove = wxButton(self, ID_PROJ_REMOVE, _("Remove"))
118            EVT_BUTTON(self, ID_PROJ_REMOVE, self._OnRemove)
119            buttons.Add(self.button_remove, 1, wxALL|wxEXPAND, 4)
120    
121          # Main Action buttons (Try/Revert/OK/Close)          buttons.Add(20, 20, 0, wxEXPAND, 0)
122          self.button_try = wxButton(self, wxID_APPLY, _("Try"))          label = wxStaticText(self, -1, _("Show EPSG:"))
123          self.button_revert = wxButton(self, ID_PROJ_REVERT,          buttons.Add(label, 0, wxLEFT|wxRIGHT|wxTOP, 4)
124                                        _("Revert"))          self.check_epsg = wxCheckBox(self, -1, _("Normal"))
125          self.button_ok = wxButton(self, wxID_OK, _("OK"))          EVT_CHECKBOX(self, self.check_epsg.GetId(), self._OnShowEPSG)
126          self.button_ok.SetDefault()          buttons.Add(self.check_epsg, 1, wxALL|wxEXPAND, 4)
127          self.button_close = wxButton(self, wxID_CANCEL,          self.check_epsg_depr = wxCheckBox(self, -1, _("Deprecated"))
128                                       _("Close"))          EVT_CHECKBOX(self, self.check_epsg_depr.GetId(), self._OnShowEPSG)
129            buttons.Add(self.check_epsg_depr, 1, wxALL|wxEXPAND, 4)
130    
131            # The file path
132            self.projfilepath = wxStaticText(self, -1, "")
133            vbox.Add(self.projfilepath, 0, wxALL|wxEXPAND)
134    
135            #
136            #   The projection editor part
137            #
138            self.edit_box = wxStaticBox(self, -1, _("Edit"))
139            sizer_edit = wxStaticBoxSizer(self.edit_box, wxHORIZONTAL)
140            main_box.Add(sizer_edit, 5, wxALL|wxEXPAND)
141    
142          self.__set_properties()          # Projection Specific Entries (Name/Projection)
143          self.__do_layout()          self.sizer_projctrls = wxBoxSizer(wxVERTICAL)
144            sizer_edit.Add(self.sizer_projctrls, 1, wxALL|wxEXPAND)
145    
146          # wxGlade          hbox = wxBoxSizer(wxHORIZONTAL)
147            self.sizer_projctrls.Add(hbox, 0, wxALL|wxEXPAND)
148            label = wxStaticText(self, -1, _("Name:"))
149            hbox.Add(label, 0, wxALL|wxALIGN_CENTER_VERTICAL, 4)
150            self.projname = wxTextCtrl(self, ID_PROJ_PROJNAME, "")
151            EVT_TEXT(self, ID_PROJ_PROJNAME, self._OnProjName)
152            hbox.Add(self.projname, 1, wxALL|wxEXPAND, 4)
153    
154            hbox = wxBoxSizer(wxHORIZONTAL)
155            self.sizer_projctrls.Add(hbox, 0, wxALL|wxEXPAND)
156            label = wxStaticText(self, -1, _("Projection:"))
157            hbox.Add(label, 0, wxALL|wxALIGN_CENTER_VERTICAL, 4)
158            self.projchoice = wxChoice(self, ID_PROJ_PROJCHOICE)
159            self.projchoice.SetSelection(0)
160            EVT_CHOICE(self, ID_PROJ_PROJCHOICE, self._OnProjChoice)
161            hbox.Add(self.projchoice, 1, wxALL|wxEXPAND, 4)
162          # Fill the projection choice list.          # Fill the projection choice list.
163          for proj, name, clazz in self.projPanels:          self.nbsizer = NotebookLikeSizer()
164              self.projchoice.Append(name, [clazz, None])          self.sizer_projctrls.Add(self.nbsizer, 1,
165                                     wxALL|wxEXPAND|wxADJUST_MINSIZE, 3)
166            self.projection_panels = []
167            self.projchoice.Append(_("<Unknown>"), "")
168            for proj_type, name, cls in self.projection_panel_defs:
169                self.projchoice.Append(name, proj_type)
170                panel = cls(self, self.receiver)
171                panel.Hide()
172                panel.projection_index = len(self.projection_panels)
173                panel.projection_type = proj_type
174                self.projection_panels.append(panel)
175                self.nbsizer.Add(panel)
176            self.unknown_projection_panel = UnknownProjPanel(self, self.receiver)
177            self.unknown_projection_panel.Hide()
178            self.nbsizer.Add(self.unknown_projection_panel)
179    
180          self.originalProjection = self.receiver.GetProjection()          # Projection Specific actions (New/Save/Add)
181            buttons = wxBoxSizer(wxVERTICAL)
182            sizer_edit.Add(buttons, 0, wxALL)
183            self.button_new = wxButton(self, ID_PROJ_NEW, _("New"))
184            EVT_BUTTON(self, ID_PROJ_NEW, self._OnNew)
185            buttons.Add(self.button_new, 0, wxEXPAND|wxALL, 4)
186            self.button_add = wxButton(self, ID_PROJ_ADDTOLIST, _("Add to List"))
187            EVT_BUTTON(self, ID_PROJ_ADDTOLIST, self._OnAddToList)
188            buttons.Add(self.button_add, 0, wxEXPAND|wxALL, 4)
189            buttons.Add(20, 20, 0, wxEXPAND, 0)
190            self.button_save = wxButton(self, ID_PROJ_SAVE,_("Update"))
191            EVT_BUTTON(self, ID_PROJ_SAVE, self._OnSave)
192            buttons.Add(self.button_save, 0, wxEXPAND|wxALL|wxALIGN_BOTTOM, 4)
193    
194          self.__DoOnProjAvail()          #
195          self.button_ok.SetFocus()          # Main Action buttons (Try/Revert/OK/Close)
196          self.availprojs.SetFocus()          #
197                    buttons = wxBoxSizer(wxHORIZONTAL)
198            top_box.Add(buttons, 0, wxALL|wxALIGN_RIGHT, 10)
199            self.button_try = wxButton(self, wxID_APPLY, _("Try"))
200          EVT_BUTTON(self, wxID_APPLY, self.OnApply)          EVT_BUTTON(self, wxID_APPLY, self.OnApply)
201            buttons.Add(self.button_try, 0, wxRIGHT, 10)
202            self.button_revert = wxButton(self, ID_PROJ_REVERT, _("Revert"))
203          EVT_BUTTON(self, ID_PROJ_REVERT, self._OnRevert)          EVT_BUTTON(self, ID_PROJ_REVERT, self._OnRevert)
204            buttons.Add(self.button_revert, 0, wxRIGHT, 10)
205            self.button_ok = wxButton(self, wxID_OK, _("OK"))
206          EVT_BUTTON(self, wxID_OK, self.OnOK)          EVT_BUTTON(self, wxID_OK, self.OnOK)
207            self.button_ok.SetDefault()
208            buttons.Add(self.button_ok, 0, wxRIGHT, 10)
209            self.button_close = wxButton(self, wxID_CANCEL, _("Close"))
210          EVT_BUTTON(self, wxID_CANCEL, self.OnCancel)          EVT_BUTTON(self, wxID_CANCEL, self.OnCancel)
211          EVT_CHOICE(self, ID_PROJ_PROJCHOICE, self._OnProjChoice)          buttons.Add(self.button_close, 0, wxRIGHT, 10)
         EVT_LISTBOX(self, ID_PROJ_AVAIL, self._OnProjAvail)  
         EVT_BUTTON(self, ID_PROJ_IMPORT, self._OnImport)  
         EVT_BUTTON(self, ID_PROJ_EXPORT, self._OnExport)  
         EVT_BUTTON(self, ID_PROJ_REMOVE, self._OnRemove)  
212    
         EVT_BUTTON(self, ID_PROJ_NEW, self._OnNew)  
         EVT_BUTTON(self, ID_PROJ_SAVE, self._OnSave)  
         EVT_BUTTON(self, ID_PROJ_ADDTOLIST, self._OnAddToList)  
213    
214          EVT_TEXT(self, ID_PROJ_PROJNAME, self._OnProjName)          #
215            # Automatic Layout
216            #
217            self.SetAutoLayout(1)
218            self.SetSizer(top_box)
219            top_box.Fit(self)
220            top_box.SetSizeHints(self)
221    
222        def OnClose(self, event):
223            self.projection_list.Unsubscribe(PROJ_SELECTION_CHANGED,
224                                             self.proj_selection_changed)
225            # Destroy the projection list explicitly so that it properly
226            # unsubscribes everything. It would be cleaner if the projection
227            # could do this by itself but wx doesn't always send destroy
228            # events for non-top-level widgets
229            self.projection_list.Destroy()
230            NonModalNonParentDialog.OnClose(self, event)
231    
232      def OnApply(self, event):      def OnApply(self, event):
233          self.__SetProjection()          self.__SetProjection()
# Line 157  class ProjFrame(NonModalNonParentDialog) Line 250  class ProjFrame(NonModalNonParentDialog)
250    
251      def _OnNew(self, event):      def _OnNew(self, event):
252    
253          # clear all selections          self.projection_list.ClearSelection()
         sel = self.availprojs.GetSelections()  
         for index in sel:  
             self.availprojs.SetSelection(index, False)  
   
254          self.projname.Clear()          self.projname.Clear()
255    
256          # supply a projection panel if there wasn't one          # supply a projection panel if there wasn't one
# Line 169  class ProjFrame(NonModalNonParentDialog) Line 258  class ProjFrame(NonModalNonParentDialog)
258              self.projchoice.SetSelection(0)              self.projchoice.SetSelection(0)
259              self.__DoOnProjChoice()              self.__DoOnProjChoice()
260    
261          self.curProjPanel.Clear()          if self.curProjPanel is not None:
262                self.curProjPanel.Clear()
263    
264      def _OnSave(self, event):      def _OnSave(self, event):
265    
266          sel = self.availprojs.GetSelections()          sel = self.projection_list.selected_projections()
267          assert len(sel) == 1,  "button shouldn't be enabled"          assert len(sel) == 1,  "button shouldn't be enabled"
268    
269          proj, projfile = self.availprojs.GetClientData(sel[0])          proj, projfile = sel[0]
270    
271          assert proj is not None and projfile is not None          assert proj is not None and projfile is not None
272    
273          newproj = self.__GetProjection()          newproj = self.__GetProjection()
274    
275          if newproj is not None:          if newproj is not None:
276                # FIXME: we should only allow this for the user proj file.
277              projfile.Replace(proj, newproj)              projfile.Replace(proj, newproj)
278              try:              self.write_proj_file(projfile)
279                  write_proj_file(projfile)              self.projection_list.SelectProjection(newproj)
             except IOError, (errno, errstr):  
                 self.__ShowError(projfile.GetFilename(), errstr)  
             self.availprojs.SetClientData(sel[0], [newproj, projfile])  
   
             self.__FillAvailList( selectProj = newproj.GetName())  
280    
281      def _OnAddToList(self, event):      def _OnAddToList(self, event):
282    
283          proj = self.__GetProjection()          proj = self.__GetProjection()
284          if proj is not None:          if proj is not None:
285              self.__usrProjFile.Add(proj)              self.__usrProjFile.Add(proj)
286              try:              self.write_proj_file(self.__usrProjFile)
287                  write_proj_file(self.__usrProjFile)              self.projection_list.SelectProjection(proj)
             except IOError, (errno, errstr):  
                 self.__ShowError(self.__userProjFile.GetFilename(), errstr)  
   
             self.__FillAvailList( selectProj = proj.GetName())  
   
     def _OnProjAvail(self, event):  
         self.__DoOnProjAvail()  
288    
289      def show_warnings(self, title, filename, warnings):      def show_warnings(self, title, filename, warnings):
290          """Show the warnings (a list of strings) in a dialog          """Show the warnings (a list of strings) in a dialog
# Line 218  class ProjFrame(NonModalNonParentDialog) Line 297  class ProjFrame(NonModalNonParentDialog)
297              self.parent.RunMessageBox(title, text)              self.parent.RunMessageBox(title, text)
298    
299      def _OnImport(self, event):      def _OnImport(self, event):
300            """Handler for the 'Import' button
301    
302          dlg = wxFileDialog(self, _("Import"), style = wxOPEN)          Ask the user for a filename, read the projections from that file
303            add them to the user ProjFile object and write the user file
304            back to disk.
305            """
306            dlg = wxFileDialog(self, _("Import"),
307                    self.parent.application.Path("projection"), style = wxOPEN)
308    
309          if dlg.ShowModal() == wxID_OK:          if dlg.ShowModal() == wxID_OK:
310              path = dlg.GetPath()              path = dlg.GetPath()
311    
312                ThubanBeginBusyCursor()
313              try:              try:
314                  projFile, warnings = read_proj_file(path)                  try:
315                  self.show_warnings(_("Warnings"), path, warnings)                      projFile, warnings = read_proj_file(path)
316                  for proj in projFile.GetProjections():                  except IOError, (errno, errstr):
317                      self.__usrProjFile.Add(proj)                      self.__ShowError(dlg.GetPath(), errstr)
318                  write_proj_file(self.__usrProjFile)                  else:
319              except IOError, (errno, errstr):                      self.show_warnings(_("Warnings"), path, warnings)
320                  self.__ShowError(dlg.GetPath(), errstr)                      for proj in projFile.GetProjections():
321                            self.__usrProjFile.Add(proj)
322              self.__FillAvailList()                      self.write_proj_file(self.__usrProjFile)
323                        self.parent.application.SetPath("projection", path)
324                finally:
325                    ThubanEndBusyCursor()
326          dlg.Destroy()          dlg.Destroy()
327    
328      def _OnExport(self, event):      def _OnExport(self, event):
329            """Handler for the 'Export' button.
330    
331          sel = self.availprojs.GetSelections()          Ask the user for a filename and write the selected projections
332            to that file.
333            """
334            sel = self.projection_list.selected_projections()
335          assert len(sel) != 0, "button should be disabled"          assert len(sel) != 0, "button should be disabled"
336    
337          dlg = wxFileDialog(self, _("Export"),          dlg = wxFileDialog(self, _("Export"),
338                          style = wxSAVE|wxOVERWRITE_PROMPT)                  self.parent.application.Path("projection"),
339                    style=wxSAVE|wxOVERWRITE_PROMPT)
340    
341          if dlg.ShowModal() == wxID_OK:          if dlg.ShowModal() == wxID_OK:
342              path = dlg.GetPath()              proj_file = ProjFile(dlg.GetPath())
343                for proj, pf in sel:
             projFile = ProjFile(path)  
   
             for i in sel:  
                 proj = self.availprojs.GetClientData(i)[CLIENT_PROJ]  
344                  if proj is not None:                  if proj is not None:
345                      projFile.Add(proj)                      proj_file.Add(proj)
346                self.write_proj_file(proj_file)
347              try:              self.parent.application.SetPath("projection", dlg.GetPath())
                 write_proj_file(projFile)  
             except IOError, (errno, errstr):  
                 self.__ShowError(dlg.GetPath(), errstr)  
348    
349          dlg.Destroy()          dlg.Destroy()
350    
351      def _OnRemove(self, event):      def _OnRemove(self, event):
352            """Handler for the 'Remove' button
353    
354          sel = self.availprojs.GetSelections()          Remove any selected projection that came from the user's
355            ProjFile. If the user ProjFile was modified write it back to
356            disk.
357            """
358            sel = self.projection_list.selected_projections()
359          assert len(sel) != 0, "button should be disabled!"          assert len(sel) != 0, "button should be disabled!"
360    
361          #          modified = False
362          # remove the items backwards so the indices don't change          for proj, pf in sel:
363          #              if proj is not None and pf is self.__usrProjFile:
364          sel = list(sel)                  pf.Remove(proj)
365          sel.sort()                  modified = True
         sel.reverse()  
   
         newselection = -1  
         if len(sel) == 1:  
             newselection = sel[0] - 1  
             if newselection < 0:  
                 newselection = 0  
   
         for i in sel:  
             proj, projfile = self.availprojs.GetClientData(i)  
   
             #  
             # this could be the case if they selected <None> or  
             # the currently used projection  
             #  
             if proj is not None and projfile is not None:  
                 projfile.Remove(proj)  
366    
367          try:          if modified:
368              write_proj_file(projfile)              self.write_proj_file(self.__usrProjFile)
         except IOError, (errno, errstr):  
             self.__ShowError(projfile.GetFilename(), errstr)  
369    
370          self.__FillAvailList()      def _OnShowEPSG(self, event):
371            """Handler for the EVT_CHECKBOX events from the EPSG check button
372    
373          #          If the button is checked add the EPSG_PROJ_FILE to the list of
374          # this *could* produce incorrect results if the .proj files          projfiles shown by the projection list. Otherwise remove it
375          # change between the last list update and this selection          """
376          # because the list has been repopulated.          proj_files = [self.load_user_proj(),
377          #                        self.load_system_proj(DEFAULT_PROJ_FILE)]
378          if newselection != -1 and self.availprojs.GetCount() > 0:          if self.check_epsg.IsChecked():
379              self.availprojs.SetSelection(newselection)              proj_files.append(self.load_system_proj(EPSG_PROJ_FILE))
380            if self.check_epsg_depr.IsChecked():
381          self.__VerifyButtons()              proj_files.append(self.load_system_proj(EPSG_DEPRECATED_PROJ_FILE))
382            self.projection_list.SetProjFiles(proj_files)
383    
384      def _OnProjName(self, event):      def _OnProjName(self, event):
385          self.__VerifyButtons()          self.__VerifyButtons()
386    
387      def __ShowError(self, filename, errstr):      def __ShowError(self, filename, errstr):
388          wxMessageDialog(self,          wxMessageDialog(self,
389              _("The following error occured:\n") +              _("The following error occured:\n") +
390              filename + "\n" + errstr,              filename + "\n" + errstr,
391              _("Error"), wxOK | wxICON_ERROR).ShowModal()              _("Error"), wxOK | wxICON_ERROR).ShowModal()
392    
393      def __VerifyButtons(self):      def __VerifyButtons(self):
394          """Enable or disable the appropriate buttons based on the          """Update button sensitivity"""
         current state of the dialog.  
         """  
395    
396          sel = self.availprojs.GetSelections()          num_sel = self.projection_list.GetSelectedItemCount()
397    
398          self.button_import.Enable(True)          self.button_import.Enable(True)
399          self.button_export.Enable(True)          self.button_export.Enable(True)
400          self.button_save.Enable(True)          self.button_save.Enable(True)
401          self.button_remove.Enable(True)          self.button_remove.Enable(True)
402    
403          self.panel_edit.Enable(True)          self.edit_box.Enable(True)
404    
405          for ctrl in [self.button_import,          for ctrl in [self.button_import,
406                       self.button_export,                       self.button_export,
# Line 337  class ProjFrame(NonModalNonParentDialog) Line 409  class ProjFrame(NonModalNonParentDialog)
409                       self.button_add,                       self.button_add,
410                       self.projchoice,                       self.projchoice,
411                       self.projname,                       self.projname,
412                       self.panel_edit]:                       self.edit_box]:
413              ctrl.Enable(True)              ctrl.Enable(True)
414    
415          if self.curProjPanel is not None:          if self.curProjPanel is not None:
416              self.curProjPanel.Enable(True)              self.curProjPanel.Enable(True)
417    
418          if len(sel) == 0:          if num_sel == 0:
419              self.button_import.Enable(True)              self.button_import.Enable(True)
420              self.button_export.Enable(False)              self.button_export.Enable(False)
421              self.button_remove.Enable(False)              self.button_remove.Enable(False)
422              self.button_save.Enable(False)              self.button_save.Enable(False)
423    
424          elif len(sel) == 1:          elif num_sel == 1:
425    
426              proj, projFile = self.availprojs.GetClientData(sel[0])              selection = self.projection_list.selected_projections()
427                proj, projFile = selection[0]
428    
429              self.button_save.Enable(len(self.projname.GetValue()) > 0)              self.button_save.Enable(len(self.projname.GetValue()) > 0)
430              self.button_add.Enable(len(self.projname.GetValue()) > 0)              self.button_add.Enable(len(self.projname.GetValue()) > 0)
# Line 376  class ProjFrame(NonModalNonParentDialog) Line 449  class ProjFrame(NonModalNonParentDialog)
449                  self.button_save.Enable(False)                  self.button_save.Enable(False)
450    
451          else:          else:
452              self.panel_edit.Enable(False)              self.edit_box.Enable(False)
   
     def __DoOnProjAvail(self):  
   
         sel = self.availprojs.GetSelections()  
         if len(sel) == 1:  
453    
454              proj = self.availprojs.GetClientData(sel[0])[CLIENT_PROJ]      def proj_selection_changed(self, projs):
455              projfile = self.availprojs.GetClientData(sel[0])[CLIENT_PROJFILE]          """Subscribed to the projection_list's PROJ_SELECTION_CHANGED message
456    
457            Update the dialog to reflect the new selection.
458            """
459            if len(projs) == 0:
460                self.projfilepath.SetLabel(_("No Projections selected"))
461            elif len(projs) == 1:
462                proj, projfile = projs[0]
463              if proj is None:              if proj is None:
464                  # user selected <None>                  # user selected <None>
465                  self.projname.Clear()                  self.projname.Clear()
466                  self.projfilepath.SetLabel(_("Projection File: "))                  self.projfilepath.SetLabel("")
467              else:              else:
               
468                  if projfile is not None:                  if projfile is not None:
469                      self.projfilepath.SetLabel(_("Projection File: ") +                      filename = os.path.basename(projfile.GetFilename())
470                          os.path.basename(projfile.GetFilename()))                      self.projfilepath.SetLabel(_("Source of Projection: %s")
471                                                   % filename)
472                  else:                  else:
473                      # only None if the currently used projection is selected                      # only None if the currently used projection is selected
474                      self.projfilepath.SetLabel(_("Projection File: "))                      self.projfilepath.SetLabel("")
475    
476                  self.projname.SetValue(proj.GetName())                  self.projname.SetValue(proj.Label())
477    
478                  myProjType = proj.GetParameter("proj")                  myProjType = proj.GetParameter("proj")
479                  i = 0                  i = 0
480                  for projType, name, clazz in self.projPanels:                  for projType, name, cls in self.projection_panel_defs:
481                      if myProjType == projType:                      if myProjType == projType:
482                          self.projchoice.SetSelection(i)                          self.projchoice.Enable(True)
483                            self.projchoice.SetSelection(i + 1)
484                          self.__DoOnProjChoice()                          self.__DoOnProjChoice()
485    
486                          #                          #
# Line 415  class ProjFrame(NonModalNonParentDialog) Line 490  class ProjFrame(NonModalNonParentDialog)
490                          assert self.curProjPanel is not None                          assert self.curProjPanel is not None
491    
492                          self.curProjPanel.SetProjection(proj)                          self.curProjPanel.SetProjection(proj)
493                            break
494                      i += 1                      i += 1
495                    else:
496                        self.projchoice.Select(0)
497                        self.projchoice.Disable()
498                        self._show_proj_panel(UnknownProjPanel)
499                        assert self.curProjPanel is not None
500                        self.curProjPanel.SetProjection(proj)
501            else:
502                self.projfilepath.SetLabel(_("Multiple Projections selected"))
503    
504          self.__VerifyButtons()          self.__VerifyButtons()
505    
# Line 432  class ProjFrame(NonModalNonParentDialog) Line 516  class ProjFrame(NonModalNonParentDialog)
516          At the end of this method self.curProjPanel will not be None          At the end of this method self.curProjPanel will not be None
517          if there was a item selected.          if there was a item selected.
518          """          """
   
519          choice = self.projchoice          choice = self.projchoice
520    
521          sel = choice.GetSelection()          sel = choice.GetSelection()
522          if sel != -1:          if sel != -1:
523                proj_type = choice.GetClientData(sel)
524              clazz, obj = choice.GetClientData(sel)              for t, name, cls in self.projection_panel_defs:
525                    if t == proj_type:
526              if obj is None:                      self._show_proj_panel(cls)
527                  obj = clazz(self.panel_edit, self.receiver)                      break
528                  choice.SetClientData(sel, [clazz, obj])          # FIXME: what to do if sel == -1?
529    
530              if self.curProjPanel is not None:      def _show_proj_panel(self, panel_class):
531                  self.curProjPanel.Hide()          """Show the panel as the projection panel"""
532                  self.sizer_projctrls.Remove(self.curProjPanel)          if panel_class is UnknownProjPanel:
533                self.edit_box.Disable()
534              self.curProjPanel = obj              self.nbsizer.Activate(self.unknown_projection_panel)
535              self.curProjPanel.Show()              self.curProjPanel = self.unknown_projection_panel
536            else:
537              self.sizer_projctrls.Add(self.curProjPanel, 1,              self.edit_box.Enable(True)
538                  wxALL|wxEXPAND|wxADJUST_MINSIZE, 3)              self.unknown_projection_panel.Hide()
539              self.sizer_projctrls.Layout()              for panel in self.projection_panels:
540              self.Layout()                  if panel.__class__ is panel_class:
541              self.topBox.SetSizeHints(self)                      self.nbsizer.Activate(panel)
542                        self.curProjPanel = panel
543    
544      def __SetProjection(self):      def __SetProjection(self):
545          """Set the receiver's projection."""          """Set the receiver's projection."""
# Line 473  class ProjFrame(NonModalNonParentDialog) Line 557  class ProjFrame(NonModalNonParentDialog)
557          Could be None.          Could be None.
558          """          """
559    
560          sel = self.availprojs.GetSelections()          assert self.projection_list.GetSelectedItemCount() < 2, \
561          assert len(sel) < 2, "button should be disabled"                 "button should be disabled"
   
562    
563            sel = self.projection_list.selected_projections()
564          if len(sel) == 1:          if len(sel) == 1:
565              proj = self.availprojs.GetClientData(sel[0])[CLIENT_PROJ]              if sel[0][0] is None:
             if proj is None:  
566                  # <None> is selected                  # <None> is selected
567                  return None                  return None
568    
569          #          # self.curProjPanel should always contain the most relevant data
570          # self.curProjPanel should always contain the most          # for a projection
         # relevant data for a projection  
         #  
571          if self.curProjPanel is not None:          if self.curProjPanel is not None:
572              return Projection(self.curProjPanel.GetParameters(),              return Projection(self.curProjPanel.GetParameters(),
573                                self.projname.GetValue())                                self.projname.GetValue())
574    
575          return None          return None
576    
577      def __FillAvailList(self, selectCurrent = False, selectProj = None):      def load_user_proj(self):
578          """Populate the list of available projections.          """Return the user's ProjFile
           
         selectCurrent -- if True, select the projection used by  
                          the receiver, otherwise select nothing.  
         selectProj    -- if set, select the projection found under the  
                          specified name. This overwrites any other  
                          selection estimate.  
         """  
   
         self.availprojs.Clear()  
   
         #  
         # the list can never be empty. there will always be  
         # at least this one item  
         #  
         self.availprojs.Append("<None>", (None, None))  
   
         projfile, warnings = get_system_proj_file()  
         self.show_warnings(_("Warnings"), projfile.GetFilename(), warnings)  
         for proj in projfile.GetProjections():  
             self.availprojs.Append(proj.GetName(), [proj, projfile])  
         self.__sysProjFile = projfile  
   
         projfile, warnings = get_user_proj_file()  
         self.show_warnings(_("Warnings"), projfile.GetFilename(), warnings)  
         for proj in projfile.GetProjections():  
             self.availprojs.Append(proj.GetName(), [proj, projfile])  
         self.__usrProjFile = projfile  
579    
580          #          If the file has not yet been loaded by the dialog, load it first
581          # We add the current projection to the list at last.          with get_user_proj_file and cache it in self.__usrProjFile.
         # Since the list is resorted immediately a following FindString()  
         # determines the index correctly.  
         #  
         proj = self.receiver.GetProjection()  
         if proj is not None:  
             proj_item = _("%s (current)") % proj.GetName()  
             self.availprojs.Append(proj_item, [proj, None])  
             if selectCurrent:  
                 self.availprojs.SetSelection(  
                         self.availprojs.FindString(proj_item)  
                     )  
         else:  
             if selectCurrent:  
                 self.availprojs.SetSelection(  
                         self.availprojs.FindString("<None>")  
                     )  
         if selectProj:  
             self.availprojs.SetSelection(  
                         self.availprojs.FindString(selectProj)  
                     )  
   
                   
   
     def __set_properties(self):  
   
         #self.availprojs.SetSelection(0)  
         self.projchoice.SetSelection(0)  
   
         self.__FillAvailList(selectCurrent = True)  
582    
583          self.projname.SetMaxLength(32)          Show a busy cursor while loading the file.
584        
585      def __do_layout(self):          If the file is not available, leave a note to the console.
586          # originally generated by wxGlade          """
587            if self.__usrProjFile is None:
588          self.topBox = wxBoxSizer(wxVERTICAL)              ThubanBeginBusyCursor()
589          self.sizer_panel = wxBoxSizer(wxVERTICAL)              try:
590          sizer_6 = wxBoxSizer(wxHORIZONTAL)                  projfile, warnings = get_user_proj_file()
591          self.sizer_mainbttns = wxBoxSizer(wxHORIZONTAL)                  if warnings:
592          self.sizer_mainctrls = wxBoxSizer(wxHORIZONTAL)                      sys.stderr.write("".join(warnings))
593          self.sizer_edit = wxStaticBoxSizer(wxStaticBox(self.panel_edit, -1, _("Edit")), wxHORIZONTAL)                  self.__usrProjFile = projfile
594          sizer_11 = wxBoxSizer(wxVERTICAL)              finally:
595          self.sizer_projctrls = wxBoxSizer(wxVERTICAL)                  ThubanEndBusyCursor()
596          sizer_14 = wxBoxSizer(wxHORIZONTAL)          return self.__usrProjFile
597          sizer_13 = wxBoxSizer(wxHORIZONTAL)  
598          sizer_15 = wxBoxSizer(wxVERTICAL)      def load_system_proj(self, name):
599          sizer_15.Add(self.button_import, 0, wxALL, 4)          """Load the system ProjFile with the given name.
600          sizer_15.Add(self.button_export, 0, wxALL, 4)  
601          sizer_15.Add(20, 20, 0, wxEXPAND, 0)          If the file has not been loaded yet, load it first with
602          sizer_15.Add(self.button_remove, 0, wxALL|wxALIGN_BOTTOM, 4)          get_system_proj_file and put it into the cache. The name is
603            simply forwarded to get_system_proj_file.
         # list controls  
         grid_sizer_1 = wxFlexGridSizer(3, 2, 0, 0)  
         grid_sizer_1.Add(self.label_5, 0, wxLEFT|wxRIGHT|wxTOP, 4)  
         grid_sizer_1.Add(20, 20, 0, wxEXPAND, 0)  
         grid_sizer_1.Add(self.availprojs, 1, wxALL|wxEXPAND|wxADJUST_MINSIZE, 4)  
         grid_sizer_1.Add(sizer_15, 0, wxALL|wxEXPAND, 4)  
         grid_sizer_1.Add(self.projfilepath, 0, wxEXPAND|wxALL|wxADJUST_MINSIZE, 4)  
         grid_sizer_1.AddGrowableRow(1)  
         grid_sizer_1.AddGrowableCol(0)  
   
         # edit controls  
         sizer_13.Add(self.label_2, 0, wxALL|wxALIGN_CENTER_VERTICAL, 4)  
         sizer_13.Add(self.projname, 1, wxALL, 4)  
         self.sizer_projctrls.Add(sizer_13, 0, wxEXPAND, 0)  
         sizer_14.Add(self.label_3, 0, wxALL|wxALIGN_CENTER_VERTICAL, 4)  
         sizer_14.Add(self.projchoice, 1, wxALL|wxALIGN_CENTER_VERTICAL, 4)  
         self.sizer_projctrls.Add(sizer_14, 0, wxEXPAND, 0)  
         self.sizer_edit.Add(self.sizer_projctrls, 1, wxEXPAND, 0)  
         sizer_11.Add(self.button_new, 0, wxEXPAND|wxALL, 4)  
         sizer_11.Add(self.button_add, 0, wxEXPAND|wxALL, 4)  
         sizer_11.Add(20, 20, 0, wxEXPAND, 0)  
         sizer_11.Add(self.button_save, 0, wxEXPAND|wxALL|wxALIGN_BOTTOM, 4)  
         self.sizer_edit.Add(sizer_11, 0, wxALL|wxEXPAND, 4)  
   
         sizer_6.Add(self.button_try, 0, wxRIGHT| wxEXPAND, 10)  
         sizer_6.Add(self.button_revert, 0, wxRIGHT| wxEXPAND, 10)  
         sizer_6.Add(self.button_ok, 0, wxRIGHT| wxEXPAND, 10)  
         sizer_6.Add(self.button_close, 0, wxRIGHT| wxEXPAND, 10)  
   
         self.panel_1.SetAutoLayout(1)  
         self.panel_1.SetSizer(grid_sizer_1)  
         grid_sizer_1.Fit(self.panel_1)  
         grid_sizer_1.SetSizeHints(self.panel_1)  
   
         self.panel_edit.SetAutoLayout(1)  
         self.panel_edit.SetSizer(self.sizer_edit)  
         self.sizer_edit.Fit(self.panel_edit)  
         self.sizer_edit.SetSizeHints(self.panel_edit)  
   
         self.sizer_mainctrls.Add(self.panel_1, 0,  
             wxALL|wxEXPAND|wxADJUST_MINSIZE, 0)  
         self.sizer_mainctrls.Add(self.panel_edit, 1,  
             wxALL|wxEXPAND|wxADJUST_MINSIZE, 0)  
604    
605          self.sizer_mainbttns.Add(sizer_6, 0,          Show a busy cursor while loading the file.
606              wxALL|wxEXPAND|wxADJUST_MINSIZE, 10)          """
607            if name not in self._sys_proj_files:
608                ThubanBeginBusyCursor()
609                try:
610                    projfile, warnings = get_system_proj_file(name)
611                    self.show_warnings(_("Warnings"), projfile.GetFilename(),
612                                       warnings)
613                    self._sys_proj_files[name] = projfile
614                finally:
615                    ThubanEndBusyCursor()
616            return self._sys_proj_files[name]
617    
618          self.topBox.Add(self.sizer_mainctrls, 1, wxALL|wxEXPAND, 4)      def write_proj_file(self, proj_file):
619          self.topBox.Add(self.sizer_mainbttns, 0, wxALIGN_RIGHT|wxBOTTOM, 0)          """Write the ProjFile object proj_file back to its file
620    
621          self.SetAutoLayout(1)          Show a busy cursor while writing and if an error occurs show a
622          self.SetSizer(self.topBox)          dialog with the error message.
623          self.topBox.Fit(self)          """
624          self.topBox.SetSizeHints(self)          try:
625          self.Layout()              ThubanBeginBusyCursor()
626                try:
627                    write_proj_file(proj_file)
628                finally:
629                    ThubanEndBusyCursor()
630            except IOError, (errno, errstr):
631                self.__ShowError(proj_file.GetFilename(), errstr)
632    
 # end of class ProjFrame  
633    
634    
635  class ProjPanel(wxPanel):  class ProjPanel(wxPanel):
# Line 642  class ProjPanel(wxPanel): Line 639  class ProjPanel(wxPanel):
639          wxPanel.__init__(self, parent, -1)          wxPanel.__init__(self, parent, -1)
640    
641          self.__ellps = wxChoice(self, -1)          self.__ellps = wxChoice(self, -1)
642          self.ellpsData = [("airy"  , _("Airy")),          self.ellpsData = [("", _("<Unknown>")),
643                              ("airy"  , _("Airy")),
644                            ("bessel", _("Bessel 1841")),                            ("bessel", _("Bessel 1841")),
645                            ("clrk66", _("Clarke 1866")),                            ("clrk66", _("Clarke 1866")),
646                            ("clrk80", _("Clarke 1880")),                            ("clrk80", _("Clarke 1880")),
647                            ("GRS80" , _("GRS 1980 (IUGG, 1980)")),                            ("GRS80" , _("GRS 1980 (IUGG, 1980)")),
648                            ("intl"  , _("International 1909 (Hayford)")),                            ("intl"  , _("International 1909 (Hayford)")),
# Line 690  class ProjPanel(wxPanel): Line 688  class ProjPanel(wxPanel):
688          self.__ellps.SetSelection(0)          self.__ellps.SetSelection(0)
689    
690      def GetParameters(self):      def GetParameters(self):
691          return ["ellps=" + self.__ellps.GetClientData(          ellps = self.__ellps.GetSelection()
692                                          self.__ellps.GetSelection())]          if ellps > 0:
693                return ["ellps=" + self.__ellps.GetClientData(ellps)]
694            return []
695    
696    
697  ID_TMPANEL_LAT = 4001  ID_TMPANEL_LAT = 4001
# Line 701  ID_TMPANEL_FALSE_NORTH = 4004 Line 701  ID_TMPANEL_FALSE_NORTH = 4004
701  ID_TMPANEL_SCALE = 4005  ID_TMPANEL_SCALE = 4005
702    
703  class UnknownProjPanel(ProjPanel):  class UnknownProjPanel(ProjPanel):
704    
705        """Panel for unknown projection types"""
706    
707      def __init__(self, parent, receiver):      def __init__(self, parent, receiver):
708          ProjPanel.__init__(self, parent)          ProjPanel.__init__(self, parent)
709    
710            self.__text = _("Thuban does not know the parameters\n"
711                            "for the current projection and cannot\n"
712                            "display a configuration panel.\n\n"
713                            "The unidentified set of parameters is:\n\n")
714    
715            self.__textbox = wxTextCtrl(self, -1, self.__text, size=(100,200),
716                                style=wxTE_READONLY|wxTE_MULTILINE|wxTE_LINEWRAP)
717          self._DoLayout()          self._DoLayout()
718    
719      def _DoLayout(self):      def _DoLayout(self):
720          sizer = wxBoxSizer(wxVERTICAL)          sizer = wxBoxSizer(wxVERTICAL)
721    
722          sizer.Add(wxStaticText(self, -1,          sizer.Add(self.__textbox, 0, wxALL|wxEXPAND, 4)
                                _("Thuban does not know the parameters for the"  
                                  " current projection and cannot display a"  
                                  " configuration panel.")))  
723    
724          ProjPanel._DoLayout(self, sizer)          ProjPanel._DoLayout(self, sizer)
725    
# Line 720  class UnknownProjPanel(ProjPanel): Line 727  class UnknownProjPanel(ProjPanel):
727          return "Unknown"          return "Unknown"
728    
729      def SetProjection(self, proj):      def SetProjection(self, proj):
730          pass          """Append the available parameters to the info text."""
731            text = self.__text
732            for param in proj.GetAllParameters():
733                text = text + '%s\n' % param
734            self.__textbox.SetValue(text)
735    
736      def GetParameters(self):      def GetParameters(self):
737          return None          return None
# Line 733  class TMPanel(ProjPanel): Line 744  class TMPanel(ProjPanel):
744    
745          self.__latitude = wxTextCtrl(self, ID_TMPANEL_LAT)          self.__latitude = wxTextCtrl(self, ID_TMPANEL_LAT)
746          self.__longitude = wxTextCtrl(self, ID_TMPANEL_LONG)          self.__longitude = wxTextCtrl(self, ID_TMPANEL_LONG)
         self.__scale = wxTextCtrl(self, ID_TMPANEL_SCALE)  
747          self.__falseEast = wxTextCtrl(self, ID_TMPANEL_FASLE_EAST)          self.__falseEast = wxTextCtrl(self, ID_TMPANEL_FASLE_EAST)
748          self.__falseNorth = wxTextCtrl(self, ID_TMPANEL_FALSE_NORTH)          self.__falseNorth = wxTextCtrl(self, ID_TMPANEL_FALSE_NORTH)
749            self.__scale = wxTextCtrl(self, ID_TMPANEL_SCALE)
750    
751          self._DoLayout()          self._DoLayout()
752    
753      def _DoLayout(self):      def _DoLayout(self):
754    
755          sizer = wxFlexGridSizer(4, 4, 0, 0)          sizer = wxFlexGridSizer(4, 2, 0, 0)
756          sizer.Add(wxStaticText(self, -1, _("Latitude:")), 0, wxALL, 4)          sizer.Add(wxStaticText(self, -1, _("Latitude:")), 0, wxALL, 4)
757          sizer.Add(self.__latitude, 0, wxALL, 4)          sizer.Add(self.__latitude, 0, wxALL, 4)
         sizer.Add(wxStaticText(self, -1, _("False Easting:")), 0, wxALL, 4)  
         sizer.Add(self.__falseEast, 0, wxALL, 4)  
758          sizer.Add(wxStaticText(self, -1, _("Longitude:")), 0, wxALL, 4)          sizer.Add(wxStaticText(self, -1, _("Longitude:")), 0, wxALL, 4)
759          sizer.Add(self.__longitude, 0, wxALL, 4)          sizer.Add(self.__longitude, 0, wxALL, 4)
760            sizer.Add(wxStaticText(self, -1, _("False Easting:")), 0, wxALL, 4)
761            sizer.Add(self.__falseEast, 0, wxALL, 4)
762          sizer.Add(wxStaticText(self, -1, _("False Northing:")), 0, wxALL, 4)          sizer.Add(wxStaticText(self, -1, _("False Northing:")), 0, wxALL, 4)
763          sizer.Add(self.__falseNorth, 0, wxALL, 4)          sizer.Add(self.__falseNorth, 0, wxALL, 4)
764          sizer.Add(wxStaticText(self, -1, _("Scale Factor:")), 0, wxALL, 4)          sizer.Add(wxStaticText(self, -1, _("Scale Factor:")), 0, wxALL, 4)
# Line 870  class LCCPanel(ProjPanel): Line 881  class LCCPanel(ProjPanel):
881                    
882          self.__fspLatitude = wxTextCtrl(self, -1)          self.__fspLatitude = wxTextCtrl(self, -1)
883          self.__sspLatitude = wxTextCtrl(self, -1)          self.__sspLatitude = wxTextCtrl(self, -1)
         self.__originLat   = wxTextCtrl(self, -1)  
884          self.__meridian    = wxTextCtrl(self, -1)          self.__meridian    = wxTextCtrl(self, -1)
885            self.__originLat   = wxTextCtrl(self, -1)
886          self.__falseEast   = wxTextCtrl(self, -1)          self.__falseEast   = wxTextCtrl(self, -1)
887          self.__falseNorth  = wxTextCtrl(self, -1)          self.__falseNorth  = wxTextCtrl(self, -1)
888    

Legend:
Removed from v.1786  
changed lines
  Added in v.2051

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26