source: sasview/calculatorview/perspectives/calculator/data_editor.py @ ad6f597

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 ad6f597 was 0c0d458, checked in by Gervaise Alina <gervyh@…>, 15 years ago

working on data editor

  • Property mode set to 100644
File size: 22.6 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
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('') 
[051e22f]127        self.data_run_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1), style=wx.TE_MULTILINE)
[91f151a]128        hint_run = "Data's run."
129        self.data_run_tcl.SetToolTipString(hint_run)
130        self.run_sizer.AddMany([(data_run_txt, 0, wx.LEFT, 15),
131                                       (self.data_run_tcl, 0, wx.LEFT, 10)])
132       
133    def _layout_instrument(self):
134        """
135            Do the layout for instrument related widgets
136        """
137        instrument_txt = wx.StaticText(self, -1, 'Instrument : ') 
138        hint_instrument_txt = ''
139        instrument_txt.SetToolTipString(hint_instrument_txt) 
140        self.instrument_tcl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH*5, 20)) 
141        hint_instrument = "Instrument."
142        self.instrument_tcl.SetToolTipString(hint_instrument)
143        self.instrument_sizer.AddMany([(instrument_txt, 0, wx.LEFT, 15),
144                                (self.instrument_tcl, 0, wx.LEFT, 10)])
145       
146    def _layout_editor(self):
147        """
148            Do the layout for sample related widgets
149        """
150        self.detector_rb = wx.RadioButton(self, -1, "Detector",
151                                           style=wx.RB_GROUP)
152        self.sample_rb = wx.RadioButton(self, -1, "Sample")
153        self.source_rb = wx.RadioButton(self, -1, "Source")
154        self.collimation_rb = wx.RadioButton(self, -1, "Collimation")
155       
156        self.bt_edit = wx.Button(self, -1, "Edit")
157        self.bt_edit.SetToolTipString("Edit data.")
158        self.bt_edit.Bind(wx.EVT_BUTTON, self.on_edit)
159        self.edit_sizer.AddMany([(self.detector_rb, 0, wx.ALL, 10),
160                        (self.sample_rb, 0, wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
161                        (self.source_rb, 0, wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
162                    (self.collimation_rb, 0, wx.RIGHT|wx.BOTTOM|wx.TOP, 10),
163                    (self.bt_edit, 0,
164                                  wx.RIGHT|wx.BOTTOM|wx.TOP, 10)])
165        self.reset_radiobox()
166   
167   
168    def _layout_source(self):
169        """
170            Do the layout for source related widgets
171        """
172        source_txt = wx.StaticText(self, -1, 'Source ') 
173        hint_source_txt = ''
174        source_txt.SetToolTipString(hint_source_txt) 
175        self.bt_edit_source = wx.Button(self, -1, "Edit")
176        self.bt_edit_source.SetToolTipString("Edit data's sample.")
177        self.bt_edit_source.Bind(wx.EVT_BUTTON, self.edit_source)
178        self.source_sizer.AddMany([(source_txt, 0, wx.ALL, 10),
179                                (self.bt_edit_source, 0,
180                                  wx.RIGHT|wx.BOTTOM|wx.TOP, 10)])
181
182    def _layout_summary(self):
183        """
184            Layout widgets related to data's summary
185        """
186        self.data_summary = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE|wx.HSCROLL,
187                                        size=(-1, 200))
188        summary = 'No data info available...'
189        self.data_summary.SetValue(summary)
190        self.summary_sizer.Add(self.data_summary, 1, wx.EXPAND|wx.ALL, 10) 
191                   
192    def _layout_button(self): 
193        """
194            Do the layout for the button widgets
195        """ 
196        self.bt_summary = wx.Button(self, -1, "View", size=(_BOX_WIDTH*2/3,-1))
197        self.bt_summary.SetToolTipString("View final changes on data.")
198        self.bt_summary.Bind(wx.EVT_BUTTON, self.on_click_view)
199       
200        self.bt_save = wx.Button(self, -1, "Save As", size=(_BOX_WIDTH*2/3,-1))
201        self.bt_save.SetToolTipString("Save changes in a file.")
202        self.bt_save.Bind(wx.EVT_BUTTON, self.on_click_save)
203       
204        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH*2/3,-1))
205        self.bt_apply.SetToolTipString("Save changes into the imported data.")
206        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
207     
208        self.bt_reset = wx.Button(self, -1,'Reset', size=(_BOX_WIDTH*2/3,-1))
209        self.bt_reset.Bind(wx.EVT_BUTTON, self.on_click_reset)
210        self.bt_reset.SetToolTipString("Reset data to its initial state.")
211       
212        self.bt_close = wx.Button(self, -1,'Close', size=(_BOX_WIDTH*2/3,-1))
213        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
214        self.bt_close.SetToolTipString("Close this panel.")
215       
216        self.button_sizer.AddMany([(self.bt_save, 0, wx.LEFT, 120),
217                                   (self.bt_apply, 0, wx.LEFT, 10),
218                                   (self.bt_reset, 0, wx.LEFT|wx.RIGHT, 10),
219                                   (self.bt_summary, 0, wx.RIGHT, 10),
220                                   (self.bt_close, 0, wx.RIGHT, 10)])
221       
222    def _do_layout(self, data=None):
223        """
224            Draw the current panel
225        """
226        self._define_structure()
227        self._layout_name()
228        self._layout_title()
229        self._layout_run()
230        self._layout_editor()
231        self._layout_button()
232        self.main_sizer.AddMany([(self.name_sizer, 0, wx.EXPAND|wx.ALL, 10),
233                                (self.title_sizer, 0,
234                                         wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
235                                (self.run_sizer, 0,
236                                         wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
237                                (self.instrument_sizer, 0,
238                                         wx.EXPAND|wx.TOP|wx.BOTTOM, 5),
239                                (self.edit_sizer, 0,
240                                        wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP,10),
241                                (self.button_sizer, 0,
242                                          wx.EXPAND|wx.TOP|wx.BOTTOM, 5)])
243        self.SetSizer(self.main_sizer)
244        self.SetScrollbars(20,20,25,65)
245        self.SetAutoLayout(True)
246       
247    def fill_data_combox(self):
248        """
249            fill the current combobox with the available data
250        """
251        if not self._data:
252            return
253        self.data_cbox.Clear()
254        for data in self._data:
[051e22f]255            name = data.title
256            if data.run:
257                name = data.run[0]
258            if name.lstrip().rstrip() =="":
259                name = data.filename
260            pos = self.data_cbox.Append(str(name))
[91f151a]261            self.data_cbox.SetClientData(pos, data)
262            self.data_cbox.SetSelection(pos)
[051e22f]263            self.data_cbox.SetStringSelection(str(name)) 
264       
[91f151a]265    def reset_panel(self):
266        """
267        """
268        self.enable_data_cbox()
269        self.data_title_tcl.SetValue("")
[051e22f]270        self.data_run_tcl.SetValue("")
[91f151a]271       
272    def on_select_data(self, event=None):
273        """
274        """
275        data, data_name, position = self.get_current_data()
276        self.reset_panel()
277        if data is None:
278            return
279        self.data_title_tcl.SetValue(str(data.title))
[051e22f]280        text = ""
281        if data.run:
282            for item in data.run:
283                text += item+"\n"
284        self.data_run_tcl.SetValue(str(text))
[91f151a]285           
286    def get_current_data(self):
287        """
288        """
289        position = self.data_cbox.GetSelection() 
290        if position == wx.NOT_FOUND:
291            return None, None, None
292        data_name = self.data_cbox.GetStringSelection() 
293        data = self.data_cbox.GetClientData(position)
294        return data, data_name, position
295   
296    def enable_data_cbox(self):
297        """
298        """
299        if self._data:
300            self.data_cbox.Enable()
301            self.bt_summary.Enable()
302            self.bt_reset.Enable()
303            self.bt_save.Enable()
304            self.bt_edit.Enable()
305        else:
306            self.data_cbox.Disable()
307            self.bt_summary.Disable()
308            self.bt_reset.Disable()
309            self.bt_save.Disable()
310            self.bt_edit.Disable()
311           
312    def reset_radiobox(self):
313        """
314        """
315        self.detector_rb.SetValue(True)
316        self.source_rb.SetValue(False)
317        self.sample_rb.SetValue(False)
318        self.collimation_rb.SetValue(False)
319   
320    def set_sample(self, sample, notes=None):
321        """
322            set sample for data
323        """
324        data, data_name, position = self.get_current_data()
325        if data is None:
326            return 
327        data.sample = sample
328        if notes is not None:
329            data.process.append(notes)
330   
331    def set_source(self, source, notes=None):
332        """
333            set source for data
334        """
335        data, data_name, position = self.get_current_data()
336        if data is None:
337            return 
338        data.source = source
339        if notes is not None:
340            data.process.append(notes)
341       
342    def set_detector(self, detector, notes=None):
343        """
344            set detector for data
345        """
346        data, data_name, position = self.get_current_data()
347        if data is None:
348            return 
349        data.detector = detector
350        if notes is not None:
351            data.process.append(notes)
352       
353    def set_collimation(self, collimation, notes=None):
354        """
355            set collimation for data
356        """
357        data, data_name, position = self.get_current_data()
358        if data is None:
359            return 
360        data.collimation = collimation
361        if notes is not None:
362            data.process.append(notes)
363               
364    def edit_collimation(self):
365        """
366            Edit the selected collimation
367        """
368        data, data_name, position = self.get_current_data()
369        if data is None:
370            return 
371        dlg = CollimationDialog(collimation=data.collimation)
372        dlg.set_manager(self)
373        dlg.ShowModal()
374           
375    def edit_detector(self):
376        """
377            Edit the selected detector
378        """
379        data, data_name, position = self.get_current_data()
380        if data is None:
381            return 
382        dlg = DetectorDialog(detector=data.detector)
383        dlg.set_manager(self)
384        dlg.ShowModal()
385
386    def edit_sample(self):
387        """
388            Open the dialog to edit the sample of the current data
389        """
390        data, data_name, position = self.get_current_data()
391        if data is None:
392            return
393        from sample_editor import SampleDialog
394        dlg = SampleDialog(parent=self, sample=data.sample)
395        dlg.set_manager(self)
396        dlg.ShowModal()
397       
398    def edit_source(self):
399        """
400            Open the dialog to edit the saource of the current data
401        """
402        data, data_name, position = self.get_current_data()
403        if data is None:
404            return
405        from source_editor import SourceDialog
406        dlg = SourceDialog(parent=self, source=data.source)
407        dlg.set_manager(self)
408        dlg.ShowModal()
409       
[051e22f]410    def choose_data_file(self, location=None):
[91f151a]411        """
412            Open a file dialog to allow loading a file
413        """
414        path = None
415        if location == None:
416            location = os.getcwd()
417       
418        l = Loader()
419        cards = l.get_wildcards()
420        wlist = '|'.join(cards)
421       
[051e22f]422        dlg = wx.FileDialog(self, "Choose a file", location, "", wlist, wx.OPEN)
[91f151a]423        if dlg.ShowModal() == wx.ID_OK:
424            path = dlg.GetPath()
425            mypath = os.path.basename(path)
426        dlg.Destroy()
427        return path
428   
[051e22f]429   
[91f151a]430    def complete_loading(self, data=None, filename=''):
431        """
432            Complete the loading and compute the slit size
433        """
[051e22f]434        self.done = True
[91f151a]435        self._data = []
436        if data is None:
437            msg = "Couldn't load data"
[051e22f]438            wx.PostEvent(self.parent.parent, StatusEvent(status=msg, info="warning",type='stop'))
[91f151a]439            return 
440        if not  data.__class__.__name__ == "list":
441            self._data.append(data)
442            self._reset_data.append(deepcopy(data))
443        else:
[051e22f]444            self._data = deepcopy(data)
[91f151a]445            self._reset_data = deepcopy(data)
446        self.set_values()
447        if self.parent.parent is None:
448            return 
449        msg = "Load Complete"
[051e22f]450        wx.PostEvent(self.parent.parent, StatusEvent(status=msg,info="info",type='stop'))
[91f151a]451 
452    def set_values(self):
453        """
454            take the aperture values of the current data and display them
455            through the panel
456        """
[051e22f]457        if self._data:
458            self.fill_data_combox()
459            self.on_select_data(event=None)
[91f151a]460       
461    def get_data(self):
462        """
463            return the current data
464        """
465        return self._data
466   
467    def get_notes(self):
468        """
469            return notes
470        """
471        return self._notes
472   
473    def on_change_run(self, event=None):
474        """
475            Change run
476        """
[051e22f]477        run = []
[91f151a]478        data, data_name, position = self.get_current_data()
[051e22f]479        for i in range(self.data_run_tcl.GetNumberOfLines()):
[0c0d458]480            text = self.data_run_tcl.GetLineText(i).lstrip().rstrip()
481            if text != "":
482                run.append(text)
[91f151a]483        if data.run != run:
484            self._notes += "Change data 's "
485            self._notes += "run from %s to %s \n"%(data.run, str(run))
[051e22f]486            data.run = run
[91f151a]487           
488    def on_change_title(self, event=None):
489        """
490            Change title
491        """
492        data, data_name, position = self.get_current_data()
493        #Change data's name
494        title = self.data_title_tcl.GetValue().lstrip().rstrip()
495       
496        if data.title != title:
497            self._notes += "Change data 's "
498            self._notes += "title from %s to %s \n"%(data.title, str(title))
499            data.title = title
500           
501    def on_click_browse(self, event):
502        """
503            Open a file dialog to allow the user to select a given file.
504            Display the loaded data if available.
505        """
506        path = self.choose_data_file(location=self._default_save_location)
507        if path is None:
508            return 
[051e22f]509        if self.parent.parent is not None:
510            wx.PostEvent(self.parent.parent, StatusEvent(status="Loading...",info="info",
511                                type="progress"))
512       
513        self.done = False
[91f151a]514        self._default_save_location = path
515        try:
516            #Load data
517            from load_thread import DataReader
518            ## If a thread is already started, stop it
519            if self.reader is not None and self.reader.isrunning():
520                self.reader.stop()
521            self.reader = DataReader(path=path,
522                                    completefn=self.complete_loading,
523                                    updatefn=None)
524            self.reader.queue()
525        except:
526            msg = "Data Editor: %s"%(sys.exc_value)
527            load_error(msg)
528            return 
529         
530    def on_edit(self, event):
531        """
532        """
533        if self.detector_rb.GetValue():
534            self.edit_detector()
535        if self.sample_rb.GetValue():
536            self.edit_sample()
537        if self.source_rb.GetValue():
538            self.edit_source()
539        if self.collimation_rb.GetValue():
540            self.edit_collimation()
541           
542    def on_click_apply(self, event):
543        """   
544            changes are saved in data object imported to edit
545        """
546        data, data_name, position = self.get_current_data()
547        if data is None:
548            return
549        self.on_change_run(event=None)
550        self.on_change_title(event=None)
551        #must post event here
552       
553    def on_click_save(self, event):
554        """
555            Save change into a file
556        """
557        data, data_name, position = self.get_current_data()
558        if data is None:
559            return
560        self.on_change_run(event=None)
561        self.on_change_title(event=None)
562       
563        if issubclass(data.__class__, Data2D):
564            msg = "No conventional writing format for \n\n"
565            msg += "Data2D at this time.\n"
566            dlg = wx.MessageDialog(None, msg, 'Error Loading File',
567                                         wx.OK | wx.ICON_EXCLAMATION)
568            dlg.ShowModal()   
569            return 
570           
571        path = None
572        wildcard = "CanSAS 1D files(*.xml)|*.xml" 
573        dlg = wx.FileDialog(self, "Choose a file",
574                            self._default_save_location, "",wildcard , wx.SAVE)
575       
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()
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.