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

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 785fa53 was 785fa53, checked in by lewis, 8 years ago

Make default output file picker directory same as input files

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