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

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

Implement ascii2d conversion

  • Property mode set to 100644
File size: 25.5 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 = 480
39    FONT_VARIANT = 0
40else:
41    PANEL_TOP = 60
42    _STATICBOX_WIDTH = 430
43    _BOX_WIDTH = 200
44    PANEL_SIZE = 500
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 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
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        "Select linked single column 1D ASCII files containing the Q-axis and "
513        "Intensity-axis data, or 1D BSL/OTOKO files, or a 2D BSL/OTOKO file, "
514        "then choose where to save the converted file, and click Convert.\n"
515        "1D ASCII and BSL/OTOKO files can be converted to CanSAS (XML) or "
516        "NXcanSAS (HDF5) formats. 2D BSL/OTOKO files can only be converted to "
517        "the NXcanSAS format.\n"
518        "Metadata can be optionally added for the CanSAS XML format."
519        )
520
521        instruction_label = wx.StaticText(self, -1, instructions,
522            size=(_STATICBOX_WIDTH+40, -1))
523        instruction_label.Wrap(_STATICBOX_WIDTH+40)
524        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
525
526        section = wx.StaticBox(self, -1)
527        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
528        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
529
530        input_grid = wx.GridBagSizer(5, 5)
531
532        y = 0
533
534        data_type_label = wx.StaticText(self, -1, "Input Format: ")
535        input_grid.Add(data_type_label, (y,0), (1,1),
536            wx.ALIGN_CENTER_VERTICAL, 5)
537        radio_sizer = wx.BoxSizer(wx.HORIZONTAL)
538        ascii_btn = wx.RadioButton(self, -1, "ASCII 1D", name="ascii",
539            style=wx.RB_GROUP)
540        ascii_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
541        radio_sizer.Add(ascii_btn)
542        ascii2d_btn = wx.RadioButton(self, -1, "ASCII 2D", name="ascii2d")
543        ascii2d_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
544        radio_sizer.Add(ascii2d_btn)
545        otoko_btn = wx.RadioButton(self, -1, "BSL 1D", name="otoko")
546        otoko_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
547        radio_sizer.Add(otoko_btn)
548        bsl_btn = wx.RadioButton(self, -1, "BSL 2D", name="bsl")
549        bsl_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
550        radio_sizer.Add(bsl_btn)
551        input_grid.Add(radio_sizer, (y,1), (1,1), wx.ALL, 5)
552        y += 1
553
554        q_label = wx.StaticText(self, -1, "Q-Axis Data: ")
555        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
556
557        self.q_input = wx.FilePickerCtrl(self, -1,
558            size=(_STATICBOX_WIDTH-80, -1),
559            message="Chose the Q-Axis data file.",
560            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
561        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
562        y += 1
563
564        iq_label = wx.StaticText(self, -1, "Intensity Data: ")
565        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
566
567        self.iq_input = wx.FilePickerCtrl(self, -1,
568            size=(_STATICBOX_WIDTH-80, -1),
569            message="Chose the Intensity-Axis data file.",
570            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
571        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
572        y += 1
573
574        radiation_label = wx.StaticText(self, -1, "Radiation Type: ")
575        input_grid.Add(radiation_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
576        self.radiation_input = wx.ComboBox(self, -1,
577            choices=["Neutron", "X-Ray", "Muon", "Electron"],
578            name="radiation", style=wx.CB_READONLY, value="Neutron")
579        self.radiation_input.Bind(wx.EVT_COMBOBOX, self.radiationtype_changed)
580        input_grid.Add(self.radiation_input, (y,1), (1,1), wx.ALL, 5)
581        y += 1
582
583        output_label = wx.StaticText(self, -1, "Output File: ")
584        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
585
586        self.output = FileInput(self,
587            wildcard="CanSAS 1D (*.xml)|*.xml|NXcanSAS HDF5 File (*.h5)|*.h5")
588        input_grid.Add(self.output.GetCtrl(), (y,1), (1,1), wx.EXPAND | wx.ALL, 5)
589        y += 1
590
591        self.convert_btn = wx.Button(self, wx.ID_OK, "Stop Converstion")
592        self.convert_btn.SetLabel("Convert")
593        input_grid.Add(self.convert_btn, (y,0), (1,1), wx.ALL, 5)
594        self.convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
595
596        help_btn = wx.Button(self, -1, "HELP")
597        input_grid.Add(help_btn, (y,1), (1,1), wx.ALL, 5)
598        help_btn.Bind(wx.EVT_BUTTON, self.on_help)
599
600        section_sizer.Add(input_grid)
601
602        vbox.Add(section_sizer, flag=wx.ALL, border=5)
603
604        self.metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
605            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
606        metadata_pane = self.metadata_section.GetPane()
607        metadata_grid = wx.GridBagSizer(5, 5)
608
609        self.metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
610            self.on_collapsible_pane)
611
612        y = 0
613        for item in self.properties:
614            # Capitalise each word
615            label_txt = " ".join(
616                [s.capitalize() for s in item.replace('_', ' ').split(' ')])
617            if item == 'run':
618                label_txt = "Run Number"
619            label = wx.StaticText(metadata_pane, -1, label_txt,
620                style=wx.ALIGN_CENTER_VERTICAL)
621            input_box = wx.TextCtrl(metadata_pane, name=item,
622                size=(_STATICBOX_WIDTH-80, -1))
623            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
624            metadata_grid.Add(label, (y,0), (1,1),
625                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
626            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
627            y += 1
628
629        detector_label = wx.StaticText(metadata_pane, -1,
630            "Detector:")
631        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
632        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
633        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
634        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
635        y += 1
636
637        sample_label = wx.StaticText(metadata_pane, -1, "Sample: ")
638        metadata_grid.Add(sample_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
639        sample_btn = wx.Button(metadata_pane, -1, "Enter Sample Metadata")
640        metadata_grid.Add(sample_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
641        sample_btn.Bind(wx.EVT_BUTTON, self.show_sample_window)
642        y += 1
643
644        source_label = wx.StaticText(metadata_pane, -1, "Source: ")
645        metadata_grid.Add(source_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
646        source_btn = wx.Button(metadata_pane, -1, "Enter Source Metadata")
647        source_btn.Bind(wx.EVT_BUTTON, self.show_source_window)
648        metadata_grid.Add(source_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
649        y += 1
650
651        metadata_pane.SetSizer(metadata_grid)
652
653        vbox.Add(self.metadata_section, proportion=0, flag=wx.ALL, border=5)
654
655        vbox.Fit(self)
656        self.SetSizer(vbox)
657
658class ConverterWindow(widget.CHILD_FRAME):
659    """Displays ConverterPanel"""
660
661    def __init__(self, parent=None, title='File Converter', base=None,
662        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.1),
663        *args, **kwargs):
664        kwargs['title'] = title
665        kwargs['size'] = size
666        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
667
668        self.manager = manager
669        self.panel = ConverterPanel(self, base=None)
670        self.Bind(wx.EVT_CLOSE, self.on_close)
671        self.SetPosition((wx.LEFT, PANEL_TOP))
672        self.Show(True)
673
674    def on_close(self, event):
675        if self.manager is not None:
676            self.manager.converter_frame = None
677        self.Destroy()
Note: See TracBrowser for help on using the repository browser.