source: sasview/src/sas/sasgui/perspectives/file_converter/converter_panel.py @ 2120a43

Last change on this file since 2120a43 was a26f67f, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

fixup api doc errors

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