source: sasview/src/sas/sasgui/perspectives/calculator/data_editor.py

ESS_GUI
Last change on this file was fa81e94, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial commit of the P(r) inversion perspective.
Code merged from Jeff Krzywon's ESS_GUI_Pr branch.
Also, minor 2to3 mods to sascalc/sasgui to enble error free setup.

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