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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ed03b99 was ed9f872, checked in by smk78, 8 years ago

Tweaked instructions and button labels on File Converter panel.

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