source: sasview/src/sas/sasgui/perspectives/simulation/ShapeParameters.py @ 7677b4d

Last change on this file since 7677b4d was d85c194, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 9 years ago

Remaining modules refactored

  • Property mode set to 100644
File size: 20.9 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2009, University of Tennessee
9"""
10import wx
11import sys
12import wx.lib.newevent
13from copy import deepcopy
14import SimCanvas
15
16(CreateShapeEvent, EVT_ADD_SHAPE) = wx.lib.newevent.NewEvent()
17(EditShapeEvent, EVT_EDIT_SHAPE)  = wx.lib.newevent.NewEvent()
18(DelShapeEvent, EVT_DEL_SHAPE)    = wx.lib.newevent.NewEvent()
19(QRangeEvent, EVT_Q_RANGE)        = wx.lib.newevent.NewEvent() 
20(PtDensityEvent, EVT_PT_DENSITY)  = wx.lib.newevent.NewEvent() 
21
22class ShapeParameterPanel(wx.Panel):
23    #TODO: show units
24    #TODO: order parameters properly
25    CENTER_PANE = True
26   
27    def __init__(self, parent, q_min=0.001, q_max=0.5, q_npts=10, pt_density=0.1, *args, **kwargs):
28        wx.Panel.__init__(self, parent, *args, **kwargs)
29       
30        self.window_name = "ShapeParams"
31        self.window_caption = "Shape parameter"
32       
33     
34        self.params = {}
35        self.parent = parent
36        self.type = None
37        self.listeners = []
38        self.parameters = []
39        self.bck = wx.GridBagSizer(5,5)
40        self.SetSizer(self.bck)
41       
42        # position and orientation ctrl
43        self.xctrl = None
44        self.yctrl = None
45        self.zctrl = None
46        self.actrl = None
47        self.bctrl = None
48        self.cctrl = None
49               
50        # Sizer to hold the shape parameters
51        self.shape_sizer = wx.GridBagSizer(5,5)
52       
53        ny = 0       
54        title = wx.StaticText(self, -1, "[Temporary form]", style=wx.ALIGN_LEFT)
55        self.bck.Add(title, (ny,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
56       
57        # Shape list
58        shape_text = wx.StaticText(self, -1, "Geometric shape", style=wx.ALIGN_LEFT)
59        self.shape_list = SimCanvas.getShapes()
60        value_list = []
61        for item in self.shape_list:
62            value_list.append(item['name'])
63           
64        self.model_combo = wx.ComboBox(self, -1, value=value_list[0], choices=value_list, style=wx.CB_READONLY)
65        self.model_combo.SetToolTip(wx.ToolTip("Select a geometric shape from the drop-down list"))
66       
67        ny+=1
68        self.bck.Add(shape_text, (ny,0), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
69        self.bck.Add(self.model_combo, (ny,1), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
70        wx.EVT_COMBOBOX(self.model_combo,-1, self._on_select_shape)
71   
72        # Placeholder for parameter form
73        ny+=1
74        self.bck.Add(self.shape_sizer, (ny,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=0)   
75       
76        # Point density control
77        point_density_text = wx.StaticText(self, -1, "Point density", style=wx.ALIGN_LEFT)
78        ny+=1
79        self.bck.Add(point_density_text, (ny,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
80        self.point_density_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
81        self.point_density_ctrl.SetValue(str(pt_density))
82        self.point_density_ctrl.SetToolTip(wx.ToolTip("Enter the number of real-space points per Angstrom cube"))
83        self.point_density_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_density_changed)
84        self.point_density_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_density_changed)
85        self.bck.Add(self.point_density_ctrl, (ny,1), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
86       
87        # Q range
88        q_min_text = wx.StaticText(self, -1, "Q min", style=wx.ALIGN_LEFT)
89        ny+=1
90        self.bck.Add(q_min_text, (ny,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
91        self.q_min_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
92        self.q_min_ctrl.SetValue(str(q_min))
93        self.q_min_ctrl.SetToolTip(wx.ToolTip("Enter the minimum Q value to be simulated"))
94        self.q_min_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_q_range_changed)
95        self.q_min_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_q_range_changed)
96        self.bck.Add(self.q_min_ctrl, (ny,1), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
97       
98        q_max_text = wx.StaticText(self, -1, "Q max", style=wx.ALIGN_LEFT)
99        self.bck.Add(q_max_text, (ny,2), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
100        self.q_max_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
101        self.q_max_ctrl.SetValue(str(q_max))
102        self.q_min_ctrl.SetToolTip(wx.ToolTip("Enter the maximum Q value to be simulated"))
103        self.q_max_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_q_range_changed)
104        self.q_max_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_q_range_changed)
105        self.bck.Add(self.q_max_ctrl, (ny,3), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
106       
107        q_npts_text = wx.StaticText(self, -1, "No. of Q pts", style=wx.ALIGN_LEFT)
108        self.bck.Add(q_npts_text, (ny,4), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
109        self.q_npts_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
110        self.q_npts_ctrl.SetValue(str(q_npts))
111        self.q_min_ctrl.SetToolTip(wx.ToolTip("Enter the number of Q points to be simulated"))
112        self.q_npts_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_q_range_changed)
113        self.q_npts_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_q_range_changed)
114        self.bck.Add(self.q_npts_ctrl, (ny,5), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
115       
116        # Shape List
117        # Internal counter of shapes in the listbox
118        self.counter = 0
119        # Buffer filled flag
120        self.buffer_filled = False
121        # Save current flag
122        self.current_saved = False       
123         
124        id = wx.NewId()
125        shape_listbox_text = wx.StaticText(self, -1, "List of shapes on 3D canvas", style=wx.ALIGN_LEFT)
126        self.shape_listbox = wx.ListBox(self, id, wx.DefaultPosition, (295, 200), 
127                                   [], wx.LB_SINGLE | wx.LB_HSCROLL)
128       
129        # Listen to history events
130        self.parent.Bind(EVT_ADD_SHAPE, self._on_add_shape_to_listbox)
131        self.shape_listbox.Bind(wx.EVT_LISTBOX, self._on_select_from_listbox, id=id)
132        self.shape_listbox.Bind(wx.EVT_CONTEXT_MENU, self._on_listbox_context_menu, id=id)
133       
134        ny+=1
135        self.bck.Add(shape_listbox_text, (ny,0), (1,2), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
136        ny+=1
137        self.bck.Add(self.shape_listbox, (ny,0), (1,5), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
138       
139        self.current_shape = None
140       
141        # Default shape
142        if type(self.shape_list)==list and len(self.shape_list)>0:
143            shape = SimCanvas.getShapeClassByName(self.shape_list[0]['name'])()
144            self.editShape(shape)                       
145
146    def _on_select_shape(self, event):
147        shape = SimCanvas.getShapeClassByName(self.shape_list[event.GetEventObject().GetSelection()]['name'])()
148        self.editShape(shape)   
149
150    def _on_density_changed(self, event):
151        """
152            Process event that might mean a change of the simulation point density
153        """
154        npts  = self._readCtrlFloat(self.point_density_ctrl)
155        if npts is not None:
156            event = PtDensityEvent(npts=npts)
157            wx.PostEvent(self.parent, event)
158           
159    def _on_q_range_changed(self, event):
160        """
161            Process event that might mean a change in Q range
162            @param event: EVT_Q_RANGE event
163        """
164        q_min = self._readCtrlFloat(self.q_min_ctrl)
165        q_max = self._readCtrlFloat(self.q_max_ctrl)
166        npts  = self._readCtrlInt(self.q_npts_ctrl)
167        if q_min is not None or q_max is not None or npts is not None:
168            event = QRangeEvent(q_min=q_min, q_max=q_max, npts=npts)
169            wx.PostEvent(self.parent, event)
170           
171    def _onEditShape(self, evt):
172        """
173            Process an EVT_EDIT_SHAPE event
174            @param evt: EVT_EDIT_SHAPE object
175        """
176        evt.Skip()
177        self.editShape(evt.shape, False)
178       
179    def editShape(self, shape=None, new=True):
180        """
181            Rebuild the panel
182        """
183        self.current_shape = shape
184        self.shape_sizer.Clear(True) 
185        #self.type = type 
186       
187        if shape==None:
188            title = wx.StaticText(self, -1, "Use menu to add a shapes", style=wx.ALIGN_LEFT)
189            self.shape_sizer.Add(title, (0,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
190
191        else:
192            if new==False:
193                title = wx.StaticText(self, -1, "Edit shape parameters", style=wx.ALIGN_LEFT)
194            else:
195                title = wx.StaticText(self, -1, "Create shape from parameters", style=wx.ALIGN_LEFT)
196               
197            self.shape_sizer.Add(title, (0,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
198           
199            n = 1
200            self.parameters = []
201            keys = shape.params.keys()
202            keys.sort()
203           
204            for item in keys:
205                if item in ['contrast', 'order']:
206                    continue
207                n += 1
208                text = wx.StaticText(self, -1, item, style=wx.ALIGN_LEFT)
209                self.shape_sizer.Add(text, (n-1,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
210                ctl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
211               
212               
213                ctl.SetValue(str(shape.params[item]))
214                self.Bind(wx.EVT_TEXT_ENTER, self.onTextEnter)
215                ctl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
216                self.parameters.append([item, ctl])
217                self.shape_sizer.Add(ctl, (n-1,1), flag=wx.TOP|wx.BOTTOM, border = 0)
218               
219                # Units
220                units = wx.StaticText(self, -1, shape.details[item], style=wx.ALIGN_LEFT)
221                self.shape_sizer.Add(units, (n-1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
222
223            for item in ['contrast', 'order']:
224                n += 1
225                text = wx.StaticText(self, -1, item, style=wx.ALIGN_LEFT)
226                self.shape_sizer.Add(text, (n-1,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
227                ctl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
228               
229               
230                ctl.SetValue(str(shape.params[item]))
231                self.Bind(wx.EVT_TEXT_ENTER, self.onTextEnter)
232                ctl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
233                self.parameters.append([item, ctl])
234                self.shape_sizer.Add(ctl, (n-1,1), flag=wx.TOP|wx.BOTTOM, border = 0)
235               
236                # Units
237                units = wx.StaticText(self, -1, shape.details[item], style=wx.ALIGN_LEFT)
238                self.shape_sizer.Add(units, (n-1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
239               
240               
241            # Add position
242            n += 1
243           
244            pos_sizer = wx.GridBagSizer(0,0)
245           
246            text = wx.StaticText(self, -1, 'x', style=wx.ALIGN_LEFT)
247            pos_sizer.Add(text, (0,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
248            text = wx.StaticText(self, -1, 'y', style=wx.ALIGN_LEFT)
249            pos_sizer.Add(text, (0,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
250            text = wx.StaticText(self, -1, 'z', style=wx.ALIGN_LEFT)
251            pos_sizer.Add(text, (0,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
252           
253            self.xctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
254            self.xctrl.SetValue(str(shape.x))
255            self.xctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
256            pos_sizer.Add(self.xctrl, (1,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
257            self.yctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
258            self.yctrl.SetValue(str(shape.y))
259            self.yctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
260            pos_sizer.Add(self.yctrl, (1,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
261            self.zctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
262            self.zctrl.SetValue(str(shape.z))
263            self.zctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
264            pos_sizer.Add(self.zctrl, (1,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
265           
266            self.shape_sizer.Add(pos_sizer, (n-1, 0), (1,3), flag=wx.LEFT|wx.ALIGN_CENTER)
267           
268            # Add orientation
269            n += 1
270           
271            pos_sizer = wx.GridBagSizer(0,0)
272           
273            text = wx.StaticText(self, -1, 'theta_x', style=wx.ALIGN_LEFT)
274            pos_sizer.Add(text, (0,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
275            text = wx.StaticText(self, -1, 'theta_y', style=wx.ALIGN_LEFT)
276            pos_sizer.Add(text, (0,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
277            text = wx.StaticText(self, -1, 'theta_z', style=wx.ALIGN_LEFT)
278            pos_sizer.Add(text, (0,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
279           
280            self.actrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
281            self.actrl.SetValue(str(shape.theta_x))
282            self.actrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
283            pos_sizer.Add(self.actrl, (1,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
284            self.bctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
285            self.bctrl.SetValue(str(shape.theta_y))
286            self.bctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
287            pos_sizer.Add(self.bctrl, (1,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
288            self.cctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
289            self.cctrl.SetValue(str(shape.theta_z))
290            self.cctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
291            pos_sizer.Add(self.cctrl, (1,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
292           
293            self.shape_sizer.Add(pos_sizer, (n-1, 0), (1,3), flag=wx.LEFT|wx.ALIGN_CENTER)
294           
295           
296            # Add a button to create the new shape on the canvas
297            n += 1
298            if new==True:
299                create = wx.Button(self, 1,"Create")
300                self.shape_sizer.Add(create, (n-1,1), (1,2), flag=wx.ALIGN_RIGHT|wx.ALL, border = 15)
301                self.Bind(wx.EVT_BUTTON, self._onCreate, id = 1)       
302            else:
303                create = wx.Button(self, 1,"Apply")
304                self.shape_sizer.Add(create, (n-1,1), (1,2), flag=wx.ALIGN_RIGHT|wx.ALL, border = 15)
305                self.Bind(wx.EVT_BUTTON, self._onEdited, id = 1)       
306               
307
308
309        self.shape_sizer.Layout()
310        self.shape_sizer.Fit(self)
311        try:
312            self.parent.GetSizer().Layout()
313        except:
314            print "TODO: move the Layout call of editShape up to the caller"
315
316    def _readCtrlFloat(self, ctrl):
317        """
318            Parses a TextCtrl for a float value.
319            Returns None is the value hasn't changed or the value is not a float.
320            The control background turns pink if the value is not a float.
321           
322            @param ctrl: TextCtrl object
323        """
324        if ctrl.IsModified():
325            ctrl.SetModified(False)
326            str_value = ctrl.GetValue().lstrip().rstrip()
327            try:
328                flt_value = float(str_value)
329                ctrl.SetBackgroundColour(
330                        wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
331                ctrl.Refresh()
332                return flt_value
333            except:
334                ctrl.SetBackgroundColour("pink")
335                ctrl.Refresh()
336        return None
337
338    def _readCtrlInt(self, ctrl):
339        """
340            Parses a TextCtrl for a float value.
341            Returns None is the value hasn't changed or the value is not a float.
342            The control background turns pink if the value is not a float.
343           
344            @param ctrl: TextCtrl object
345        """
346        if ctrl.IsModified():
347            ctrl.SetModified(False)
348            str_value = ctrl.GetValue().lstrip().rstrip()
349            try:
350                int_value = int(str_value)
351                ctrl.SetBackgroundColour(
352                        wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
353                ctrl.Refresh()
354                return int_value
355            except:
356                ctrl.SetBackgroundColour("pink")
357                ctrl.Refresh()
358        return None
359
360    def _parseCtrl(self):
361        try:
362            # Position
363            tmp = self._readCtrlFloat(self.xctrl)
364            if not tmp==None:
365                self.current_shape.x = tmp
366               
367            tmp = self._readCtrlFloat(self.yctrl)
368            if not tmp==None:
369                self.current_shape.y = tmp
370               
371            tmp = self._readCtrlFloat(self.zctrl)
372            if not tmp==None:
373                self.current_shape.z = tmp
374               
375            # Orientation
376            tmp = self._readCtrlFloat(self.actrl)
377            if not tmp==None:
378                self.current_shape.theta_x = tmp
379               
380            tmp = self._readCtrlFloat(self.bctrl)
381            if not tmp==None:
382                self.current_shape.theta_y = tmp
383               
384            tmp = self._readCtrlFloat(self.cctrl)
385            if not tmp==None:
386                self.current_shape.theta_z = tmp
387               
388            # Parameters
389            for item in self.parameters:
390                tmp = self._readCtrlFloat(item[1])
391                if not tmp==None:
392                    self.current_shape.params[item[0]] = tmp
393        except:
394            print "Could not create"
395            print sys.exc_value
396               
397    def _onCreate(self, evt):
398        """
399            Create a new shape with the parameters given by the user
400        """
401        self._parseCtrl()
402        event = CreateShapeEvent(shape=self.current_shape, new=True)
403        wx.PostEvent(self.parent, event)
404        self.editShape(self.current_shape, False)
405           
406       
407    def _onEdited(self, evt):
408        self._parseCtrl()
409        event = CreateShapeEvent(shape=self.current_shape, new=False)
410        wx.PostEvent(self.parent, event)
411        self.editShape(self.current_shape, False)
412
413    def onTextEnter(self, evt): 
414        """
415            Read text field to check values
416        """ 
417        self._readCtrlFloat(self.xctrl)
418        self._readCtrlFloat(self.yctrl)
419        self._readCtrlFloat(self.zctrl)
420        self._readCtrlFloat(self.actrl)
421        self._readCtrlFloat(self.bctrl)
422        self._readCtrlFloat(self.cctrl)
423        for item in self.parameters:
424            self._readCtrlFloat(item[1])
425 
426    #-- Methods to support list of shapes --
427    def _on_add_shape_to_listbox(self, event):
428        """
429            Process a new shape           
430            @param event: EVT_ADD_SHAPE event
431        """
432        event.Skip()
433        if event.new:
434            self.counter += 1
435            self.shape_listbox.Insert("%d: %s" % (self.counter, event.shape.name), 
436                                 self.shape_listbox.GetCount(), event.shape)
437
438    def _on_select_from_listbox(self, event):
439        """
440            Process item selection events
441        """
442        index = event.GetSelection()
443        view_name = self.shape_listbox.GetString(index)
444        self.editShape(shape=event.GetClientData(), new=False)
445        # The following event is bound to the SimPanel to highlight the shape in blue
446        #TODO: how come it doesn't work?
447        wx.PostEvent(self.parent, EditShapeEvent(shape=event.GetClientData()))
448        #TODO: select the shape from the drop down
449       
450    def _on_listbox_context_menu(self, event):
451        """
452            Popup context menu event
453        """
454        # Create context menu
455        popupmenu = wx.Menu()
456       
457        # Create an item to delete the selected shape from the canvas
458        id = wx.NewId()
459        popupmenu.Append(id, "&Remove Selected")
460        wx.EVT_MENU(self, id, self._on_remove_shape_from_listbox)
461       
462        # Create an item to rename a shape to a more user friendly name
463        #id = wx.NewId()
464        #popupmenu.Append(102, "&Rename Selected")
465        #wx.EVT_MENU(self, 102, self.onRename)
466
467        pos = event.GetPosition()
468        pos = self.ScreenToClient(pos)
469        self.PopupMenu(popupmenu, pos)       
470       
471    def _on_remove_shape_from_listbox(self, ev):
472        """
473            Remove an item
474        """
475        indices = self.shape_listbox.GetSelections()
476        if len(indices)>0:
477            name =  self.shape_listbox.GetClientData(indices[0]).name
478            self.shape_listbox.Delete(indices[0])
479            wx.PostEvent(self.parent, DelShapeEvent(id = name))
480       
481    def _on_rename_shape_from_listbox(self, ev):
482        """
483            Rename an item
484        """
485        indices = self.shape_listbox.GetSelections()
486        if len(indices)>0:
487            print "NOT YET IMPLMENTED"
488            print "renaming", self.shape_listbox.GetString(indices[0])
489               
Note: See TracBrowser for help on using the repository browser.