source: sasview/calculatorview/perspectives/calculator/data_editor.py @ 91f151a

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 91f151a was 91f151a, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on data editor

  • Property mode set to 100644
File size: 22.5 KB
Line 
1
2import wx
3import sys
4import os 
5from copy import deepcopy
6
7from DataLoader.loader import Loader
8from DataLoader.data_info import DataInfo, Detector, Collimation, Data2D
9from detector_editor import DetectorDialog
10from collimation_editor import CollimationDialog
11from console import ConsoleDialog
12
13from sans.guiframe.utils import check_float
14from sans.guicomm.events import StatusEvent
15
16_BOX_WIDTH = 76
17_QMIN_DEFAULT = 0.001
18_QMAX_DEFAULT = 0.13
19_NPTS_DEFAULT = 50
20#Control panel width
21if sys.platform.count("darwin")==0:
22    PANEL_WIDTH = 500
23    PANEL_HEIGTH = 350
24    FONT_VARIANT = 0
25    ON_MAC = False
26else:
27    PANEL_WIDTH = 550
28    PANEL_HEIGTH = 400
29    FONT_VARIANT = 1
30    ON_MAC = True
31   
32def load_error(error=None):
33    """
34        Pop up an error message.
35       
36        @param error: details error message to be displayed
37    """
38    message = "You had to try this, didn't you?\n\n"
39    message += "The data file you selected could not be loaded.\n"
40    message += "Make sure the content of your file is properly formatted.\n\n"
41   
42    if error is not None:
43        message += "When contacting the DANSE team, mention the following:\n%s" % str(error)
44   
45    dial = wx.MessageDialog(None, message, 'Error Loading File', wx.OK | wx.ICON_EXCLAMATION)
46    dial.ShowModal() 
47   
48   
49class DataEditorPanel(wx.ScrolledWindow):
50    """
51        @param data: when not empty the class can same information into a dat object
52        and post event containing the changed data object to some other frame
53    """
54    def __init__(self, parent, data=[], *args, **kwds):
55         kwds['name'] = "Data Editor"
56         kwds["size"]= (PANEL_WIDTH, PANEL_HEIGTH)
57         wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
58         self.parent = parent
59         self._data = data
60         self._reset_data = deepcopy(data)
61         self.reader = None
62         self._notes = ""
63         self_description = "Edit Data"
64         self._default_save_location = os.getcwd()
65         self._do_layout()
66         self.reset_panel()
67         self.bt_apply.Disable()
68         if data:
69             self.complete_loading(data=data)
70             self.bt_apply.Enable()
71             
72    def _define_structure(self):
73        """
74            define initial sizer
75        """
76        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
77        name_box = wx.StaticBox(self, -1, "Load Data")
78        self.name_sizer = wx.StaticBoxSizer(name_box, wx.HORIZONTAL)
79       
80        self.title_sizer = wx.BoxSizer(wx.HORIZONTAL)
81        self.run_sizer = wx.BoxSizer(wx.HORIZONTAL)
82        self.instrument_sizer = wx.BoxSizer(wx.HORIZONTAL)
83       
84        edit_box = wx.StaticBox(self, -1, "Edit ")
85        self.edit_sizer = wx.StaticBoxSizer(edit_box, wx.HORIZONTAL)
86       
87        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
88     
89    def _layout_name(self):
90        """
91            Do the layout for data name related widgets
92        """
93        #data name [string]
94        data_name_txt = wx.StaticText(self, -1, 'Data : ') 
95        self.data_cbox = wx.ComboBox(self, -1, style=wx.CB_READONLY)
96        wx.EVT_COMBOBOX(self.data_cbox, -1, self.on_select_data) 
97        hint_data = "Loaded data."
98        self.data_cbox.SetToolTipString(hint_data)
99        id = wx.NewId()
100        self.browse_button = wx.Button(self, id, "Browse")
101        hint_on_browse = "Click on this button to import data in this panel."
102        self.browse_button.SetToolTipString(hint_on_browse)
103        self.Bind(wx.EVT_BUTTON, self.on_click_browse, id=id)
104        self.name_sizer.AddMany([(data_name_txt, 0, wx.LEFT, 15),
105                                       (self.data_cbox, 0, wx.LEFT, 10),
106                                       (self.browse_button, 0, wx.LEFT, 10)])
107       
108    def _layout_title(self):
109        """
110            Do the layout for data title related widgets
111        """
112        #title name [string]
113        data_title_txt = wx.StaticText(self, -1, 'Title : ') 
114        self.data_title_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
115        self.data_title_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_title)
116        hint_title = "Data's title."
117        self.data_title_tcl.SetToolTipString(hint_title)
118        self.title_sizer.AddMany([(data_title_txt, 0, wx.LEFT, 15),
119                                       (self.data_title_tcl, 0, wx.LEFT, 10)])
120       
121    def _layout_run(self):
122        """
123            Do the layout for data run related widgets
124        """
125        data_run_txt = wx.StaticText(self, -1, 'Run : ') 
126        data_run_txt.SetToolTipString('') 
127        self.data_run_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
128        self.data_run_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_run)
129        hint_run = "Data's run."
130        self.data_run_tcl.SetToolTipString(hint_run)
131        self.run_sizer.AddMany([(data_run_txt, 0, wx.LEFT, 15),
132                                       (self.data_run_tcl, 0, wx.LEFT, 10)])
133       
134    def _layout_instrument(self):
135        """
136            Do the layout for instrument related widgets
137        """
138        instrument_txt = wx.StaticText(self, -1, 'Instrument : ') 
139        hint_instrument_txt = ''
140        instrument_txt.SetToolTipString(hint_instrument_txt) 
141        self.instrument_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH*5, 20)) 
142        hint_instrument = "Instrument."
143        self.instrument_tcl.SetToolTipString(hint_instrument)
144        self.instrument_sizer.AddMany([(instrument_txt, 0, wx.LEFT, 15),
145                                (self.instrument_tcl, 0, wx.LEFT, 10)])
146       
147    def _layout_editor(self):
148        """
149            Do the layout for sample related widgets
150        """
151        self.detector_rb = wx.RadioButton(self, -1, "Detector",
152                                           style=wx.RB_GROUP)
153        self.sample_rb = wx.RadioButton(self, -1, "Sample")
154        self.source_rb = wx.RadioButton(self, -1, "Source")
155        self.collimation_rb = wx.RadioButton(self, -1, "Collimation")
156       
157        self.bt_edit = wx.Button(self, -1, "Edit")
158        self.bt_edit.SetToolTipString("Edit data.")
159        self.bt_edit.Bind(wx.EVT_BUTTON, self.on_edit)
160        self.edit_sizer.AddMany([(self.detector_rb, 0, wx.ALL, 10),
161                        (self.sample_rb, 0, wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
162                        (self.source_rb, 0, wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
163                    (self.collimation_rb, 0, wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
164                    (self.bt_edit, 0,
165                                  wx.RIGHT|wx.BOTTOM|wx.TOP, 10)])
166        self.reset_radiobox()
167   
168   
169    def _layout_source(self):
170        """
171            Do the layout for source related widgets
172        """
173        source_txt = wx.StaticText(self, -1, 'Source ') 
174        hint_source_txt = ''
175        source_txt.SetToolTipString(hint_source_txt) 
176        self.bt_edit_source = wx.Button(self, -1, "Edit")
177        self.bt_edit_source.SetToolTipString("Edit data's sample.")
178        self.bt_edit_source.Bind(wx.EVT_BUTTON, self.edit_source)
179        self.source_sizer.AddMany([(source_txt, 0, wx.ALL, 10),
180                                (self.bt_edit_source, 0,
181                                  wx.RIGHT|wx.BOTTOM|wx.TOP, 10)])
182
183    def _layout_summary(self):
184        """
185            Layout widgets related to data's summary
186        """
187        self.data_summary = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE|wx.HSCROLL,
188                                        size=(-1, 200))
189        summary = 'No data info available...'
190        self.data_summary.SetValue(summary)
191        self.summary_sizer.Add(self.data_summary, 1, wx.EXPAND|wx.ALL, 10) 
192                   
193    def _layout_button(self): 
194        """
195            Do the layout for the button widgets
196        """ 
197        self.bt_summary = wx.Button(self, -1, "View", size=(_BOX_WIDTH*2/3,-1))
198        self.bt_summary.SetToolTipString("View final changes on data.")
199        self.bt_summary.Bind(wx.EVT_BUTTON, self.on_click_view)
200       
201        self.bt_save = wx.Button(self, -1, "Save As", size=(_BOX_WIDTH*2/3,-1))
202        self.bt_save.SetToolTipString("Save changes in a file.")
203        self.bt_save.Bind(wx.EVT_BUTTON, self.on_click_save)
204       
205        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH*2/3,-1))
206        self.bt_apply.SetToolTipString("Save changes into the imported data.")
207        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
208     
209        self.bt_reset = wx.Button(self, -1,'Reset', size=(_BOX_WIDTH*2/3,-1))
210        self.bt_reset.Bind(wx.EVT_BUTTON, self.on_click_reset)
211        self.bt_reset.SetToolTipString("Reset data to its initial state.")
212       
213        self.bt_close = wx.Button(self, -1,'Close', size=(_BOX_WIDTH*2/3,-1))
214        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
215        self.bt_close.SetToolTipString("Close this panel.")
216       
217        self.button_sizer.AddMany([(self.bt_save, 0, wx.LEFT, 120),
218                                   (self.bt_apply, 0, wx.LEFT, 10),
219                                   (self.bt_reset, 0, wx.LEFT|wx.RIGHT, 10),
220                                   (self.bt_summary, 0, wx.RIGHT, 10),
221                                   (self.bt_close, 0, wx.RIGHT, 10)])
222       
223    def _do_layout(self, data=None):
224        """
225            Draw the current panel
226        """
227        self._define_structure()
228        self._layout_name()
229        self._layout_title()
230        self._layout_run()
231        self._layout_editor()
232        self._layout_button()
233        self.main_sizer.AddMany([(self.name_sizer, 0, wx.EXPAND|wx.ALL, 10),
234                                (self.title_sizer, 0,
235                                         wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
236                                (self.run_sizer, 0,
237                                         wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
238                                (self.instrument_sizer, 0,
239                                         wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
240                                (self.edit_sizer, 0,
241                                        wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP,10),
242                                (self.button_sizer, 0,
243                                          wx.EXPAND|wx.TOP|wx.BOTTOM, 5)])
244        self.SetSizer(self.main_sizer)
245        self.SetScrollbars(20,20,25,65)
246        self.SetAutoLayout(True)
247       
248    def fill_data_combox(self):
249        """
250            fill the current combobox with the available data
251        """
252        if not self._data:
253            return
254        self.data_cbox.Clear()
255        for data in self._data:
256            pos = self.data_cbox.Append(str(data.filename))
257            self.data_cbox.SetClientData(pos, data)
258            self.data_cbox.SetSelection(pos)
259            self.data_cbox.SetStringSelection(str(data.filename)) 
260           
261           
262    def reset_panel(self):
263        """
264        """
265        self.enable_data_cbox()
266        self.data_run_tcl.SetValue("")
267        self.data_title_tcl.SetValue("")
268       
269    def on_select_data(self, event=None):
270        """
271        """
272        data, data_name, position = self.get_current_data()
273        self.reset_panel()
274        if data is None:
275            return
276        self.data_title_tcl.SetValue(str(data.title))
277        for item in data.run:
278            self.data_run_tcl.AppendText(str(item))
279           
280    def get_current_data(self):
281        """
282        """
283        if not self.data_cbox.IsEnabled():
284            return None, None, None
285        position = self.data_cbox.GetSelection() 
286        if position == wx.NOT_FOUND:
287            return None, None, None
288        data_name = self.data_cbox.GetStringSelection() 
289        data = self.data_cbox.GetClientData(position)
290        return data, data_name, position
291   
292    def enable_data_cbox(self):
293        """
294        """
295        if self._data:
296            self.data_cbox.Enable()
297            self.bt_summary.Enable()
298            self.bt_reset.Enable()
299            self.bt_save.Enable()
300            self.bt_edit.Enable()
301        else:
302            self.data_cbox.Disable()
303            self.bt_summary.Disable()
304            self.bt_reset.Disable()
305            self.bt_save.Disable()
306            self.bt_edit.Disable()
307           
308    def reset_radiobox(self):
309        """
310        """
311        self.detector_rb.SetValue(True)
312        self.source_rb.SetValue(False)
313        self.sample_rb.SetValue(False)
314        self.collimation_rb.SetValue(False)
315   
316    def set_sample(self, sample, notes=None):
317        """
318            set sample for data
319        """
320        data, data_name, position = self.get_current_data()
321        if data is None:
322            return 
323        data.sample = sample
324        if notes is not None:
325            data.process.append(notes)
326   
327    def set_source(self, source, notes=None):
328        """
329            set source for data
330        """
331        data, data_name, position = self.get_current_data()
332        if data is None:
333            return 
334        data.source = source
335        if notes is not None:
336            data.process.append(notes)
337       
338    def set_detector(self, detector, notes=None):
339        """
340            set detector for data
341        """
342        data, data_name, position = self.get_current_data()
343        if data is None:
344            return 
345        data.detector = detector
346        if notes is not None:
347            data.process.append(notes)
348       
349    def set_collimation(self, collimation, notes=None):
350        """
351            set collimation for data
352        """
353        data, data_name, position = self.get_current_data()
354        if data is None:
355            return 
356        data.collimation = collimation
357        if notes is not None:
358            data.process.append(notes)
359               
360    def edit_collimation(self):
361        """
362            Edit the selected collimation
363        """
364        data, data_name, position = self.get_current_data()
365        if data is None:
366            return 
367        dlg = CollimationDialog(collimation=data.collimation)
368        dlg.set_manager(self)
369        dlg.ShowModal()
370           
371    def edit_detector(self):
372        """
373            Edit the selected detector
374        """
375        data, data_name, position = self.get_current_data()
376        if data is None:
377            return 
378        dlg = DetectorDialog(detector=data.detector)
379        dlg.set_manager(self)
380        dlg.ShowModal()
381
382    def edit_sample(self):
383        """
384            Open the dialog to edit the sample of the current data
385        """
386        data, data_name, position = self.get_current_data()
387        if data is None:
388            return
389        from sample_editor import SampleDialog
390        dlg = SampleDialog(parent=self, sample=data.sample)
391        dlg.set_manager(self)
392        dlg.ShowModal()
393       
394    def edit_source(self):
395        """
396            Open the dialog to edit the saource of the current data
397        """
398        data, data_name, position = self.get_current_data()
399        if data is None:
400            return
401        from source_editor import SourceDialog
402        dlg = SourceDialog(parent=self, source=data.source)
403        dlg.set_manager(self)
404        dlg.ShowModal()
405       
406    def choose_data_file(parent, location=None):
407        """
408            Open a file dialog to allow loading a file
409        """
410        path = None
411        if location == None:
412            location = os.getcwd()
413       
414        l = Loader()
415        cards = l.get_wildcards()
416        wlist = '|'.join(cards)
417       
418        dlg = wx.FileDialog(parent, "Choose a file", location, "", wlist, wx.OPEN)
419        if dlg.ShowModal() == wx.ID_OK:
420            path = dlg.GetPath()
421            mypath = os.path.basename(path)
422        dlg.Destroy()
423        return path
424   
425    def complete_loading(self, data=None, filename=''):
426        """
427            Complete the loading and compute the slit size
428        """
429        self._data = []
430        if data is None:
431            msg = "Couldn't load data"
432            wx.PostEvent(self.parent.parent, StatusEvent(status=msg, type='stop'))
433            return 
434        if not  data.__class__.__name__ == "list":
435            self._data.append(data)
436            self._reset_data.append(deepcopy(data))
437        else:
438            self._data = data
439            self._reset_data = deepcopy(data)
440        # set data field
441        self.reset_panel()
442        self.set_values()
443       
444        if self.parent.parent is None:
445            return 
446        msg = "Load Complete"
447        wx.PostEvent(self.parent.parent, StatusEvent(status=msg, type='stop'))
448 
449    def set_values(self):
450        """
451            take the aperture values of the current data and display them
452            through the panel
453        """
454        if self._data == []:
455            return 
456        self.fill_data_combox()
457        self.on_select_data(event=None)
458       
459    def get_data(self):
460        """
461            return the current data
462        """
463        return self._data
464   
465    def get_notes(self):
466        """
467            return notes
468        """
469        return self._notes
470   
471    def on_change_run(self, event=None):
472        """
473            Change run
474        """
475        data, data_name, position = self.get_current_data()
476        #Change data's name
477        run = self.data_run_tcl.GetValue().lstrip().rstrip()
478        if run == "":
479            run = []
480        if data.run != run:
481            self._notes += "Change data 's "
482            self._notes += "run from %s to %s \n"%(data.run, str(run))
483            data.run = [run]
484           
485    def on_change_title(self, event=None):
486        """
487            Change title
488        """
489        data, data_name, position = self.get_current_data()
490        #Change data's name
491        title = self.data_title_tcl.GetValue().lstrip().rstrip()
492       
493        if data.title != title:
494            self._notes += "Change data 's "
495            self._notes += "title from %s to %s \n"%(data.title, str(title))
496            data.title = title
497           
498    def on_click_browse(self, event):
499        """
500            Open a file dialog to allow the user to select a given file.
501            Display the loaded data if available.
502        """
503        path = self.choose_data_file(location=self._default_save_location)
504       
505        if path is None:
506            return 
507        self._default_save_location = path
508        try:
509            #Load data
510            from load_thread import DataReader
511            ## If a thread is already started, stop it
512            if self.reader is not None and self.reader.isrunning():
513                self.reader.stop()
514            if self.parent.parent is not None:
515                wx.PostEvent(self.parent.parent, StatusEvent(status="Loading...",
516                                type="progress"))
517            self.reader = DataReader(path=path,
518                                    completefn=self.complete_loading,
519                                    updatefn=None)
520            self.reader.queue()
521        except:
522            msg = "Data Editor: %s"%(sys.exc_value)
523            load_error(msg)
524            return 
525         
526    def on_edit(self, event):
527        """
528        """
529        if self.detector_rb.GetValue():
530            self.edit_detector()
531        if self.sample_rb.GetValue():
532            self.edit_sample()
533        if self.source_rb.GetValue():
534            self.edit_source()
535        if self.collimation_rb.GetValue():
536            self.edit_collimation()
537           
538    def on_click_apply(self, event):
539        """   
540            changes are saved in data object imported to edit
541        """
542        data, data_name, position = self.get_current_data()
543        if data is None:
544            return
545        self.on_change_run(event=None)
546        self.on_change_title(event=None)
547        #must post event here
548       
549    def on_click_save(self, event):
550        """
551            Save change into a file
552        """
553        data, data_name, position = self.get_current_data()
554        if data is None:
555            return
556        self.on_change_run(event=None)
557        self.on_change_title(event=None)
558       
559        if issubclass(data.__class__, Data2D):
560            msg = "No conventional writing format for \n\n"
561            msg += "Data2D at this time.\n"
562            dlg = wx.MessageDialog(None, msg, 'Error Loading File',
563                                         wx.OK | wx.ICON_EXCLAMATION)
564            dlg.ShowModal()   
565            return 
566           
567        path = None
568        wildcard = "CanSAS 1D files(*.xml)|*.xml" 
569        dlg = wx.FileDialog(self, "Choose a file",
570                            self._default_save_location, "",wildcard , wx.SAVE)
571       
572        if dlg.ShowModal() == wx.ID_OK:
573            path = dlg.GetPath()
574            mypath = os.path.basename(path)
575            loader = Loader() 
576            format = ".xml"
577            if os.path.splitext(mypath)[1].lower() ==format:
578                loader.save( path, data, format)
579            try:
580                self._default_save_location = os.path.dirname(path)
581            except:
582                pass   
583        dlg.Destroy()
584       
585    def on_click_view(self, event):
586        """
587            Display data info
588        """
589        data, data_name, position = self.get_current_data()
590        if data is None:
591            return
592        self.on_change_run(event=None)
593        self.on_change_title(event=None)
594        dlg = ConsoleDialog(data=data)
595        dlg.ShowModal()
596   
597    def on_click_reset(self, event):
598        """
599        """
600        data, data_name, position = self.get_current_data()
601        if data is None:
602            return
603        self._data[position]= deepcopy(self._reset_data[position])
604        self.set_values()
605       
606    def on_close(self, event):
607        """
608            leave data as it is and close
609        """
610        self.parent.Close()
611       
612class DataEditorWindow(wx.Frame):
613    def __init__(self, parent, data=None, *args, **kwds):
614        kwds["size"]= (PANEL_WIDTH, PANEL_HEIGTH)
615        wx.Frame.__init__(self, parent, *args, **kwds)
616        self.parent = parent
617        self.panel = DataEditorPanel(parent=self, data=data)
618        self.Show()
619       
620    def get_data(self):
621        """
622            return the current data
623        """
624        return self.panel.get_data()
625   
626if __name__ =="__main__":
627   
628    app  = wx.App()
629    window = DataEditorWindow(parent=None, data=[], title="Data Editor")
630    app.MainLoop()
631 
Note: See TracBrowser for help on using the repository browser.