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

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 ed3298b was 3d2d7f60, checked in by Gervaise Alina <gervyh@…>, 14 years ago

remove guicomm

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