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

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

Check for BSL header if loading ASCII file fails

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