source: sasview/calculatorview/perspectives/calculator/data_editor.py @ 9ec4406

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 9ec4406 was c5dca87, checked in by Gervaise Alina <gervyh@…>, 15 years ago

working on data editor

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