source: sasview/src/sas/sasgui/perspectives/file_converter/converter_panel.py @ 0b1a677

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 0b1a677 was 0b1a677, checked in by lewis, 5 years ago

Only perform writing on separate thread

Also move reader/writer classes to sascalc

  • Property mode set to 100644
File size: 26.0 KB
Line 
1"""
2This module provides a GUI for the file converter
3"""
4
5import wx
6import sys
7import os
8import numpy as np
9from wx.lib.scrolledpanel import ScrolledPanel
10from sas.sasgui.guiframe.panel_base import PanelBase
11from sas.sasgui.perspectives.calculator import calculator_widgets as widget
12from sas.sasgui.perspectives.file_converter.converter_widgets import FileInput
13from sas.sasgui.perspectives.file_converter.meta_panels import MetadataWindow
14from sas.sasgui.perspectives.file_converter.meta_panels import DetectorPanel
15from sas.sasgui.perspectives.file_converter.meta_panels import SamplePanel
16from sas.sasgui.perspectives.file_converter.meta_panels import SourcePanel
17from sas.sasgui.perspectives.file_converter.frame_select_dialog import FrameSelectDialog
18from sas.sasgui.guiframe.events import StatusEvent
19from sas.sasgui.guiframe.documentation_window import DocumentationWindow
20from sas.sasgui.guiframe.dataFitting import Data1D
21from sas.sasgui.guiframe.utils import check_float
22from sas.sascalc.file_converter.cansas_writer import CansasWriter
23from sas.sascalc.file_converter.otoko_loader import OTOKOLoader
24from sas.sascalc.file_converter.bsl_loader import BSLLoader
25from sas.sascalc.file_converter.convert_bsl_thread import ConvertBSLThread
26from sas.sascalc.dataloader.data_info import Detector
27from sas.sascalc.dataloader.data_info import Sample
28from sas.sascalc.dataloader.data_info import Source
29from sas.sascalc.dataloader.data_info import Vector
30
31# Panel size
32if sys.platform.count("win32") > 0:
33    PANEL_TOP = 0
34    _STATICBOX_WIDTH = 410
35    _BOX_WIDTH = 200
36    PANEL_SIZE = 480
37    FONT_VARIANT = 0
38else:
39    PANEL_TOP = 60
40    _STATICBOX_WIDTH = 430
41    _BOX_WIDTH = 200
42    PANEL_SIZE = 500
43    FONT_VARIANT = 1
44
45class ConverterPanel(ScrolledPanel, PanelBase):
46    """
47    This class provides the File Converter GUI
48    """
49
50    def __init__(self, parent, base=None, *args, **kwargs):
51        ScrolledPanel.__init__(self, parent, *args, **kwargs)
52        PanelBase.__init__(self)
53        self.SetupScrolling()
54        self.SetWindowVariant(variant=FONT_VARIANT)
55
56        self.base = base
57        self.parent = parent
58        self.meta_frames = []
59        self.to_convert = {}
60        self.bsl_thread = None
61
62        # GUI inputs
63        self.q_input = None
64        self.iq_input = None
65        self.output = None
66        self.radiation_input = None
67        self.convert_btn = None
68        self.metadata_section = None
69
70        self.data_type = "ascii"
71
72        # Metadata values
73        self.title = ''
74        self.run = ''
75        self.run_name = ''
76        self.instrument = ''
77        self.detector = Detector()
78        self.sample = Sample()
79        self.source = Source()
80        self.properties = ['title', 'run', 'run_name', 'instrument']
81
82        self.detector.name = ''
83        self.source.radiation = 'neutron'
84
85        self._do_layout()
86        self.SetAutoLayout(True)
87        self.Layout()
88
89    def convert_to_cansas(self, frame_data, filepath, single_file):
90        """
91        Saves an array of Data1D objects to a single CanSAS file with multiple
92        <SasData> elements, or to multiple CanSAS files, each with one
93        <SasData> element.
94
95        :param frame_data: If single_file is true, an array of Data1D objects.
96        If single_file is false, a dictionary of the form frame_number: Data1D.
97        :param filepath: Where to save the CanSAS file
98        :param single_file: If true, array is saved as a single file, if false,
99        each item in the array is saved to it's own file
100        """
101        writer = CansasWriter()
102        entry_attrs = None
103        if self.run_name != '':
104            entry_attrs = { 'name': self.run_name }
105
106        if single_file:
107            writer.write(filepath, frame_data,
108                sasentry_attrs=entry_attrs)
109        else:
110            # Folder and base filename
111            [group_path, group_name] = os.path.split(filepath)
112            ext = "." + group_name.split('.')[-1] # File extension
113            for frame_number, frame_data in frame_data.iteritems():
114                # Append frame number to base filename
115                filename = group_name.replace(ext, str(frame_number)+ext)
116                destination = os.path.join(group_path, filename)
117                writer.write(destination, [frame_data],
118                    sasentry_attrs=entry_attrs)
119
120    def extract_ascii_data(self, filename):
121        """
122        Extracts data from a single-column ASCII file
123
124        :param filename: The file to load data from
125        :return: A numpy array containing the extracted data
126        """
127        try:
128            data = np.loadtxt(filename, dtype=str)
129        except:
130            is_bsl = False
131            # Check if file is a BSL or OTOKO header file
132            f = open(filename, 'r')
133            f.readline()
134            f.readline()
135            bsl_metadata = f.readline().strip().split()
136            f.close()
137            if len(bsl_metadata) == 10:
138                msg = ("Error parsing ASII data. {} looks like a BSL or OTOKO "
139                    "header file.")
140                raise Exception(msg.format(os.path.split(filename)[-1]))
141
142        if len(data.shape) != 1:
143            msg = "Error reading {}: Only one column of data is allowed"
144            raise Exception(msg.format(filename.split('\\')[-1]))
145
146        is_float = True
147        try:
148            float(data[0])
149        except:
150            is_float = False
151
152        if not is_float:
153            end_char = data[0][-1]
154            # If lines end with comma or semi-colon, trim the last character
155            if end_char == ',' or end_char == ';':
156                data = map(lambda s: s[0:-1], data)
157            else:
158                msg = ("Error reading {}: Lines must end with a digit, comma "
159                    "or semi-colon").format(filename.split('\\')[-1])
160                raise Exception(msg)
161
162        return np.array(data, dtype=np.float32)
163
164    def extract_otoko_data(self, filename):
165        """
166        Extracts data from a 1D OTOKO file
167
168        :param filename: The OTOKO file to load the data from
169        :return: A numpy array containing the extracted data
170        """
171        loader = OTOKOLoader(self.q_input.GetPath(),
172            self.iq_input.GetPath())
173        otoko_data = loader.load_otoko_data()
174        qdata = otoko_data.q_axis.data
175        iqdata = otoko_data.data_axis.data
176        if len(qdata) > 1:
177            msg = ("Q-Axis file has multiple frames. Only 1 frame is "
178                "allowed for the Q-Axis")
179            wx.PostEvent(self.parent.manager.parent,
180                StatusEvent(status=msg, info="error"))
181            return
182        else:
183            qdata = qdata[0]
184
185        return qdata, iqdata
186
187    def extract_bsl_data(self, filename):
188        """
189        Extracts data from a 2D BSL file
190
191        :param filename: The header file to extract the data from
192        :return x_data: A 1D array containing all the x coordinates of the data
193        :return y_data: A 1D array containing all the y coordinates of the data
194        :return frame_data: A dictionary of the form frame_number: data, where
195        data is a 2D numpy array containing the intensity data
196        """
197        loader = BSLLoader(filename)
198        frames = [0]
199        should_continue = True
200
201        if loader.n_frames > 1:
202            params = self.ask_frame_range(loader.n_frames)
203            frames = params['frames']
204            if len(frames) == 0:
205                should_continue = False
206        elif loader.n_rasters == 1 and loader.n_frames == 1:
207            message = ("The selected file is an OTOKO file. Please select the "
208            "'OTOKO 1D' option if you wish to convert it.")
209            dlg = wx.MessageDialog(self,
210            message,
211            'Error!',
212            wx.OK | wx.ICON_WARNING)
213            dlg.ShowModal()
214            should_continue = False
215            dlg.Destroy()
216        else:
217            message = ("The selected data file only has 1 frame, it might be"
218                " a multi-frame OTOKO file.\nContinue conversion?")
219            dlg = wx.MessageDialog(self,
220            message,
221            'Warning!',
222            wx.YES_NO | wx.ICON_WARNING)
223            should_continue = (dlg.ShowModal() == wx.ID_YES)
224            dlg.Destroy()
225
226        if not should_continue:
227            return None, None, None
228
229        frame_data = {}
230
231        for frame in frames:
232            loader.frame = frame
233            frame_data[frame] = loader.load_data()
234
235        # TODO: Tidy this up
236        # Prepare axes values (arbitrary scale)
237        x_data = []
238        y_data = range(loader.n_pixels) * loader.n_rasters
239        for i in range(loader.n_rasters):
240            x_data += [i] * loader.n_pixels
241
242        return x_data, y_data, frame_data
243
244    def ask_frame_range(self, n_frames):
245        """
246        Display a dialog asking the user to input the range of frames they
247        would like to export
248
249        :param n_frames: How many frames the loaded data file has
250        :return: A dictionary containing the parameters input by the user
251        """
252        valid_input = False
253        is_bsl = (self.data_type == 'bsl')
254        dlg = FrameSelectDialog(n_frames, is_bsl)
255        frames = None
256        increment = None
257        single_file = True
258        while not valid_input:
259            if dlg.ShowModal() == wx.ID_OK:
260                msg = ""
261                try:
262                    first_frame = int(dlg.first_input.GetValue())
263                    last_frame = int(dlg.last_input.GetValue())
264                    increment = int(dlg.increment_input.GetValue())
265                    if not is_bsl:
266                        single_file = dlg.single_btn.GetValue()
267
268                    if last_frame < 0 or first_frame < 0:
269                        msg = "Frame values must be positive"
270                    elif increment < 1:
271                        msg = "Increment must be greater than or equal to 1"
272                    elif first_frame > last_frame:
273                        msg = "First frame must be less than last frame"
274                    elif last_frame >= n_frames:
275                        msg = "Last frame must be less than {}".format(n_frames)
276                    else:
277                        valid_input = True
278                except:
279                    valid_input = False
280                    msg = "Please enter valid integer values"
281
282                if not valid_input:
283                    wx.PostEvent(self.parent.manager.parent,
284                        StatusEvent(status=msg))
285            else:
286                return { 'frames': [], 'inc': None, 'file': single_file }
287        frames = range(first_frame, last_frame + increment,
288            increment)
289        return { 'frames': frames, 'inc': increment, 'file': single_file }
290
291    def on_convert(self, event):
292        """Called when the Convert button is clicked"""
293        if not self.validate_inputs():
294            return
295
296        if self.bsl_thread is not None and self.bsl_thread.isrunning():
297            self.to_convert = {}
298            self.bsl_thread.stop()
299            return
300
301        self.sample.ID = self.title
302
303        try:
304            if self.data_type == 'ascii':
305                qdata = self.extract_ascii_data(self.q_input.GetPath())
306                iqdata = np.array([self.extract_ascii_data(self.iq_input.GetPath())])
307            elif self.data_type == 'otoko':
308                qdata, iqdata = self.extract_otoko_data(self.q_input.GetPath())
309            else: # self.data_type == 'bsl'
310
311                x, y, frame_data = self.extract_bsl_data(self.iq_input.GetPath())
312                if x == None and y == None and frame_data == None:
313                    # Cancelled by user
314                    return
315
316                self.to_convert = frame_data
317
318                frame_number, data = self.to_convert.popitem()
319                self.bsl_thread = ConvertBSLThread((x, y), data,
320                    self.output.GetPath(), frame_number=frame_number,
321                    updatefn=self.conversion_update,
322                    completefn=self.conversion_complete)
323                self.bsl_thread.queue()
324
325                self.convert_btn.SetLabel("Stop Conversion")
326                return
327
328        except Exception as ex:
329            msg = str(ex)
330            wx.PostEvent(self.parent.manager.parent,
331                StatusEvent(status=msg, info='error'))
332            return
333
334        frames = []
335        increment = 1
336        single_file = True
337        n_frames = iqdata.shape[0]
338        # Standard file has 3 frames: SAS, calibration and WAS
339        if n_frames > 3:
340            # File has multiple frames - ask the user which ones they want to
341            # export
342            params = self.ask_frame_range(n_frames)
343            frames = params['frames']
344            increment = params['inc']
345            single_file = params['file']
346            if frames == []: return
347        else: # Only interested in SAS data
348            frames = [0]
349
350        output_path = self.output.GetPath()
351
352        # Prepare the metadata for writing to a file
353        if self.run is None:
354            self.run = []
355        elif not isinstance(self.run, list):
356            self.run = [self.run]
357
358        if self.title is None:
359            self.title = ''
360
361        metadata = {
362            'title': self.title,
363            'run': self.run,
364            'instrument': self.instrument,
365            'detector': [self.detector],
366            'sample': self.sample,
367            'source': self.source
368        }
369
370        frame_data = {}
371        for i in frames:
372            data = Data1D(x=qdata, y=iqdata[i])
373            frame_data[i] = data
374        if single_file:
375            # Only need to set metadata on first Data1D object
376            frame_data = frame_data.values() # Don't need to know frame numbers
377            frame_data[0].filename = output_path.split('\\')[-1]
378            for key, value in metadata.iteritems():
379                setattr(frame_data[0], key, value)
380        else:
381            # Need to set metadata for all Data1D objects
382            for datainfo in frame_data.values():
383                datainfo.filename = output_path.split('\\')[-1]
384                for key, value in metadata.iteritems():
385                    setattr(datainfo, key, value)
386
387
388        self.convert_to_cansas(frame_data, output_path, single_file)
389        wx.PostEvent(self.parent.manager.parent,
390            StatusEvent(status="Conversion completed."))
391
392    def conversion_update(self, msg="", exception=None):
393        if exception is not None:
394            msg = str(exception)
395            wx.PostEvent(self.parent.manager.parent,
396                StatusEvent(status=msg, info='error'))
397        else:
398            wx.PostEvent(self.parent.manager.parent,
399                StatusEvent(status=msg))
400
401    def conversion_complete(self, success=True):
402        msg = "Conversion of {} ".format(self.bsl_thread.frame_filename)
403
404        if success:
405            msg += "completed"
406        else:
407            msg += "failed"
408        wx.PostEvent(self.parent.manager.parent,
409            StatusEvent(status=msg))
410
411        if len(self.to_convert) == 0:
412            self.convert_btn.SetLabel("Convert")
413            self.bsl_thread = None
414            wx.PostEvent(self.parent.manager.parent,
415                StatusEvent(status="Conversion finished"))
416        else:
417            n, data = self.to_convert.popitem()
418            self.bsl_thread.frame_data = data
419            self.bsl_thread.frame_number = n
420            self.bsl_thread.queue()
421
422
423    def on_help(self, event):
424        """
425        Show the File Converter documentation
426        """
427        tree_location = ("user/sasgui/perspectives/file_converter/"
428            "file_converter_help.html")
429        doc_viewer = DocumentationWindow(self, -1, tree_location,
430            "", "File Converter Help")
431
432    def validate_inputs(self):
433        msg = "You must select a"
434        if self.q_input.GetPath() == '' and self.data_type != 'bsl':
435            msg += " Q Axis input file."
436        elif self.iq_input.GetPath() == '':
437            msg += "n Intensity input file."
438        elif self.output.GetPath() == '':
439            msg += " destination for the converted file."
440        if msg != "You must select a":
441            wx.PostEvent(self.parent.manager.parent,
442                StatusEvent(status=msg, info='error'))
443            return
444
445        return True
446
447    def show_detector_window(self, event):
448        """
449        Show the window for inputting Detector metadata
450        """
451        if self.meta_frames != []:
452            for frame in self.meta_frames:
453                frame.panel.on_close()
454        detector_frame = MetadataWindow(DetectorPanel,
455            parent=self.parent.manager.parent, manager=self,
456            metadata=self.detector, title='Detector Metadata')
457        self.meta_frames.append(detector_frame)
458        self.parent.manager.put_icon(detector_frame)
459        detector_frame.Show(True)
460
461    def show_sample_window(self, event):
462        """
463        Show the window for inputting Sample metadata
464        """
465        if self.meta_frames != []:
466            for frame in self.meta_frames:
467                frame.panel.on_close()
468        sample_frame = MetadataWindow(SamplePanel,
469            parent=self.parent.manager.parent, manager=self,
470            metadata=self.sample, title='Sample Metadata')
471        self.meta_frames.append(sample_frame)
472        self.parent.manager.put_icon(sample_frame)
473        sample_frame.Show(True)
474
475    def show_source_window(self, event):
476        """
477        Show the window for inputting Source metadata
478        """
479        if self.meta_frames != []:
480            for frame in self.meta_frames:
481                frame.panel.on_close()
482        source_frame = MetadataWindow(SourcePanel,
483            parent=self.parent.manager.parent, manager=self,
484            metadata=self.source, title="Source Metadata")
485        self.meta_frames.append(source_frame)
486        self.parent.manager.put_icon(source_frame)
487        source_frame.Show(True)
488
489    def on_collapsible_pane(self, event):
490        """
491        Resize the scrollable area to fit the metadata pane when it's
492        collapsed or expanded
493        """
494        self.Freeze()
495        self.SetupScrolling()
496        self.parent.Layout()
497        self.Thaw()
498
499    def datatype_changed(self, event):
500        """
501        Update the UI and self.data_type when a data type radio button is
502        pressed
503        """
504        event.Skip()
505        dtype = event.GetEventObject().GetName()
506        self.data_type = dtype
507        if dtype == 'bsl':
508            self.q_input.SetPath("")
509            self.q_input.Disable()
510            self.radiation_input.Disable()
511            self.metadata_section.Collapse()
512            self.on_collapsible_pane(None)
513            self.metadata_section.Disable()
514            self.output.SetWildcard("IGOR/DAT 2D file in Q_map (*.dat)|*.DAT")
515        else:
516            self.q_input.Enable()
517            self.radiation_input.Enable()
518            self.metadata_section.Enable()
519            self.output.SetWildcard("CanSAS 1D (*.xml)|*.xml")
520
521    def radiationtype_changed(self, event):
522        event.Skip()
523        rtype = event.GetEventObject().GetValue().lower()
524        self.source.radiation = rtype
525
526    def metadata_changed(self, event):
527        event.Skip()
528        textbox = event.GetEventObject()
529        attr = textbox.GetName()
530        value = textbox.GetValue().strip()
531
532        setattr(self, attr, value)
533
534
535    def _do_layout(self):
536        vbox = wx.BoxSizer(wx.VERTICAL)
537
538        instructions = ("Select either single column ASCII files or 1D OTOKO "
539        "files containing the Q-Axis and Intensity-Axis data, or a 2D BSL file"
540        ", then chose where to save the converted file, and click Convert.\n"
541        "ASCII and OTOKO files will be converted to CanSAS XML, and OTKO files"
542        " to IGOR/DAT 2D Q_map files.\nCanSAS metadata can also be optionally "
543        "input below.")
544
545        instruction_label = wx.StaticText(self, -1, instructions,
546            size=(_STATICBOX_WIDTH+40, -1))
547        instruction_label.Wrap(_STATICBOX_WIDTH+40)
548        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
549
550        section = wx.StaticBox(self, -1)
551        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
552        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
553
554        input_grid = wx.GridBagSizer(5, 5)
555
556        y = 0
557
558        data_type_label = wx.StaticText(self, -1, "Input Format: ")
559        input_grid.Add(data_type_label, (y,0), (1,1),
560            wx.ALIGN_CENTER_VERTICAL, 5)
561        radio_sizer = wx.BoxSizer(wx.HORIZONTAL)
562        ascii_btn = wx.RadioButton(self, -1, "ASCII", name="ascii",
563            style=wx.RB_GROUP)
564        ascii_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
565        radio_sizer.Add(ascii_btn)
566        otoko_btn = wx.RadioButton(self, -1, "OTOKO 1D", name="otoko")
567        otoko_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
568        radio_sizer.Add(otoko_btn)
569        input_grid.Add(radio_sizer, (y,1), (1,1), wx.ALL, 5)
570        bsl_btn = wx.RadioButton(self, -1, "BSL 2D", name="bsl")
571        bsl_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
572        radio_sizer.Add(bsl_btn)
573        y += 1
574
575        q_label = wx.StaticText(self, -1, "Q-Axis Data: ")
576        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
577
578        self.q_input = wx.FilePickerCtrl(self, -1,
579            size=(_STATICBOX_WIDTH-80, -1),
580            message="Chose the Q-Axis data file.",
581            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
582        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
583        y += 1
584
585        iq_label = wx.StaticText(self, -1, "Intensity-Axis Data: ")
586        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
587
588        self.iq_input = wx.FilePickerCtrl(self, -1,
589            size=(_STATICBOX_WIDTH-80, -1),
590            message="Chose the Intensity-Axis data file.",
591            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
592        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
593        y += 1
594
595        radiation_label = wx.StaticText(self, -1, "Radiation Type: ")
596        input_grid.Add(radiation_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
597        self.radiation_input = wx.ComboBox(self, -1,
598            choices=["Neutron", "X-Ray", "Muon", "Electron"],
599            name="radiation", style=wx.CB_READONLY, value="Neutron")
600        self.radiation_input.Bind(wx.EVT_COMBOBOX, self.radiationtype_changed)
601        input_grid.Add(self.radiation_input, (y,1), (1,1), wx.ALL, 5)
602        y += 1
603
604        output_label = wx.StaticText(self, -1, "Output File: ")
605        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
606
607        self.output = FileInput(self,
608            wildcard="CanSAS 1D (*.xml)|*.xml")
609        input_grid.Add(self.output.GetCtrl(), (y,1), (1,1), wx.EXPAND | wx.ALL, 5)
610        y += 1
611
612        self.convert_btn = wx.Button(self, wx.ID_OK, "Stop Converstion")
613        self.convert_btn.SetLabel("Convert")
614        input_grid.Add(self.convert_btn, (y,0), (1,1), wx.ALL, 5)
615        self.convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
616
617        help_btn = wx.Button(self, -1, "HELP")
618        input_grid.Add(help_btn, (y,1), (1,1), wx.ALL, 5)
619        help_btn.Bind(wx.EVT_BUTTON, self.on_help)
620
621        section_sizer.Add(input_grid)
622
623        vbox.Add(section_sizer, flag=wx.ALL, border=5)
624
625        self.metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
626            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
627        metadata_pane = self.metadata_section.GetPane()
628        metadata_grid = wx.GridBagSizer(5, 5)
629
630        self.metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
631            self.on_collapsible_pane)
632
633        y = 0
634        for item in self.properties:
635            # Capitalise each word
636            label_txt = " ".join(
637                [s.capitalize() for s in item.replace('_', ' ').split(' ')])
638            if item == 'run':
639                label_txt = "Run Number"
640            label = wx.StaticText(metadata_pane, -1, label_txt,
641                style=wx.ALIGN_CENTER_VERTICAL)
642            input_box = wx.TextCtrl(metadata_pane, name=item,
643                size=(_STATICBOX_WIDTH-80, -1))
644            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
645            metadata_grid.Add(label, (y,0), (1,1),
646                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
647            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
648            y += 1
649
650        detector_label = wx.StaticText(metadata_pane, -1,
651            "Detector:")
652        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
653        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
654        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
655        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
656        y += 1
657
658        sample_label = wx.StaticText(metadata_pane, -1, "Sample: ")
659        metadata_grid.Add(sample_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
660        sample_btn = wx.Button(metadata_pane, -1, "Enter Sample Metadata")
661        metadata_grid.Add(sample_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
662        sample_btn.Bind(wx.EVT_BUTTON, self.show_sample_window)
663        y += 1
664
665        source_label = wx.StaticText(metadata_pane, -1, "Source: ")
666        metadata_grid.Add(source_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
667        source_btn = wx.Button(metadata_pane, -1, "Enter Source Metadata")
668        source_btn.Bind(wx.EVT_BUTTON, self.show_source_window)
669        metadata_grid.Add(source_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
670        y += 1
671
672        metadata_pane.SetSizer(metadata_grid)
673
674        vbox.Add(self.metadata_section, proportion=0, flag=wx.ALL, border=5)
675
676        vbox.Fit(self)
677        self.SetSizer(vbox)
678
679class ConverterWindow(widget.CHILD_FRAME):
680    """Displays ConverterPanel"""
681
682    def __init__(self, parent=None, title='File Converter', base=None,
683        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.1),
684        *args, **kwargs):
685        kwargs['title'] = title
686        kwargs['size'] = size
687        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
688
689        self.manager = manager
690        self.panel = ConverterPanel(self, base=None)
691        self.Bind(wx.EVT_CLOSE, self.on_close)
692        self.SetPosition((wx.LEFT, PANEL_TOP))
693        self.Show(True)
694
695    def on_close(self, event):
696        if self.panel.bsl_thread.isrunning():
697            self.panel.bsl_thread.stop()
698        if self.manager is not None:
699            self.manager.converter_frame = None
700        self.Destroy()
Note: See TracBrowser for help on using the repository browser.