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

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 346d56e was 346d56e, checked in by lewis, 6 years ago

Allow adding metadata to 2d files and ensure it's written correctly

  • Property mode set to 100644
File size: 24.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.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        is_bsl = (self.data_type == 'bsl')
242        dlg = FrameSelectDialog(n_frames, is_bsl)
243        frames = None
244        increment = None
245        single_file = True
246        while not valid_input:
247            if dlg.ShowModal() == wx.ID_OK:
248                msg = ""
249                try:
250                    first_frame = int(dlg.first_input.GetValue())
251                    last_frame = int(dlg.last_input.GetValue())
252                    increment = int(dlg.increment_input.GetValue())
253                    if not is_bsl:
254                        single_file = dlg.single_btn.GetValue()
255
256                    if last_frame < 0 or first_frame < 0:
257                        msg = "Frame values must be positive"
258                    elif increment < 1:
259                        msg = "Increment must be greater than or equal to 1"
260                    elif first_frame > last_frame:
261                        msg = "First frame must be less than last frame"
262                    elif last_frame >= n_frames:
263                        msg = "Last frame must be less than {}".format(n_frames)
264                    else:
265                        valid_input = True
266                except:
267                    valid_input = False
268                    msg = "Please enter valid integer values"
269
270                if not valid_input:
271                    wx.PostEvent(self.parent.manager.parent,
272                        StatusEvent(status=msg))
273            else:
274                return { 'frames': [], 'inc': None, 'file': single_file }
275        frames = range(first_frame, last_frame + 1, increment)
276        return { 'frames': frames, 'inc': increment, 'file': single_file }
277
278    def get_metadata(self):
279        # Prepare the metadata for writing to a file
280        if self.run is None:
281            self.run = []
282        elif not isinstance(self.run, list):
283            self.run = [self.run]
284
285        if self.title is None:
286            self.title = ''
287
288        metadata = {
289            'title': self.title,
290            'run': self.run,
291            'instrument': self.instrument,
292            'detector': [self.detector],
293            'sample': self.sample,
294            'source': self.source
295        }
296
297        return metadata
298
299    def convert_1d_data(self, qdata, iqdata):
300        """
301        Formats a 1D array of q_axis data and a 2D array of I axis data (where
302        each row of iqdata is a separate row), into an array of Data1D objects
303        """
304        frames = []
305        increment = 1
306        single_file = True
307        n_frames = iqdata.shape[0]
308        # Standard file has 3 frames: SAS, calibration and WAS
309        if n_frames > 3:
310            # File has multiple frames - ask the user which ones they want to
311            # export
312            params = self.ask_frame_range(n_frames)
313            frames = params['frames']
314            increment = params['inc']
315            single_file = params['file']
316            if frames == []: return
317        else: # Only interested in SAS data
318            frames = [0]
319
320        output_path = self.output.GetPath()
321        metadata = self.get_metadata()
322
323        frame_data = {}
324        for i in frames:
325            data = Data1D(x=qdata, y=iqdata[i])
326            frame_data[i] = data
327        if single_file:
328            # Only need to set metadata on first Data1D object
329            frame_data = frame_data.values() # Don't need to know frame numbers
330            frame_data[0].filename = output_path.split('\\')[-1]
331            for key, value in metadata.iteritems():
332                setattr(frame_data[0], key, value)
333        else:
334            # Need to set metadata for all Data1D objects
335            for datainfo in frame_data.values():
336                datainfo.filename = output_path.split('\\')[-1]
337                for key, value in metadata.iteritems():
338                    setattr(datainfo, key, value)
339
340        self.convert_to_cansas(frame_data, output_path, single_file)
341
342    def on_convert(self, event):
343        """Called when the Convert button is clicked"""
344        if not self.validate_inputs():
345            return
346
347        self.sample.ID = self.title
348
349        try:
350            if self.data_type == 'ascii':
351                qdata = self.extract_ascii_data(self.q_input.GetPath())
352                iqdata = np.array([self.extract_ascii_data(self.iq_input.GetPath())])
353                self.convert_1d_data(qdata, iqdata)
354            elif self.data_type == 'otoko':
355                qdata, iqdata = self.extract_otoko_data(self.q_input.GetPath())
356                self.convert_1d_data(qdata, iqdata)
357            else: # self.data_type == 'bsl'
358                dataset = self.extract_bsl_data(self.iq_input.GetPath())
359                if dataset is None:
360                    # Cancelled by user
361                    return
362
363                metadata = self.get_metadata()
364                for key, value in metadata.iteritems():
365                    setattr(dataset[0], key, value)
366                if self.run != []:
367                    run_number = self.run[0]
368                    dataset[0].run_name[run_number] = self.run_name
369
370                w = NXcanSASWriter()
371                w.write(dataset, self.output.GetPath())
372        except Exception as ex:
373            msg = str(ex)
374            wx.PostEvent(self.parent.manager.parent,
375                StatusEvent(status=msg, info='error'))
376            return
377
378        wx.PostEvent(self.parent.manager.parent,
379            StatusEvent(status="Conversion completed."))
380
381    def on_help(self, event):
382        """
383        Show the File Converter documentation
384        """
385        tree_location = ("user/sasgui/perspectives/file_converter/"
386            "file_converter_help.html")
387        doc_viewer = DocumentationWindow(self, -1, tree_location,
388            "", "File Converter Help")
389
390    def validate_inputs(self):
391        msg = "You must select a"
392        if self.q_input.GetPath() == '' and self.data_type != 'bsl':
393            msg += " Q Axis input file."
394        elif self.iq_input.GetPath() == '':
395            msg += "n Intensity input file."
396        elif self.output.GetPath() == '':
397            msg += " destination for the converted file."
398        if msg != "You must select a":
399            wx.PostEvent(self.parent.manager.parent,
400                StatusEvent(status=msg, info='error'))
401            return
402
403        return True
404
405    def show_detector_window(self, event):
406        """
407        Show the window for inputting Detector metadata
408        """
409        if self.meta_frames != []:
410            for frame in self.meta_frames:
411                frame.panel.on_close()
412        detector_frame = MetadataWindow(DetectorPanel,
413            parent=self.parent.manager.parent, manager=self,
414            metadata=self.detector, title='Detector Metadata')
415        self.meta_frames.append(detector_frame)
416        self.parent.manager.put_icon(detector_frame)
417        detector_frame.Show(True)
418
419    def show_sample_window(self, event):
420        """
421        Show the window for inputting Sample metadata
422        """
423        if self.meta_frames != []:
424            for frame in self.meta_frames:
425                frame.panel.on_close()
426        sample_frame = MetadataWindow(SamplePanel,
427            parent=self.parent.manager.parent, manager=self,
428            metadata=self.sample, title='Sample Metadata')
429        self.meta_frames.append(sample_frame)
430        self.parent.manager.put_icon(sample_frame)
431        sample_frame.Show(True)
432
433    def show_source_window(self, event):
434        """
435        Show the window for inputting Source metadata
436        """
437        if self.meta_frames != []:
438            for frame in self.meta_frames:
439                frame.panel.on_close()
440        source_frame = MetadataWindow(SourcePanel,
441            parent=self.parent.manager.parent, manager=self,
442            metadata=self.source, title="Source Metadata")
443        self.meta_frames.append(source_frame)
444        self.parent.manager.put_icon(source_frame)
445        source_frame.Show(True)
446
447    def on_collapsible_pane(self, event):
448        """
449        Resize the scrollable area to fit the metadata pane when it's
450        collapsed or expanded
451        """
452        self.Freeze()
453        self.SetupScrolling()
454        self.parent.Layout()
455        self.Thaw()
456
457    def datatype_changed(self, event):
458        """
459        Update the UI and self.data_type when a data type radio button is
460        pressed
461        """
462        event.Skip()
463        dtype = event.GetEventObject().GetName()
464        self.data_type = dtype
465        if dtype == 'bsl':
466            self.q_input.SetPath("")
467            self.q_input.Disable()
468            self.output.SetWildcard("NXcanSAS HDF5 File (*.h5)|*.h5")
469        else:
470            self.q_input.Enable()
471            self.radiation_input.Enable()
472            self.metadata_section.Enable()
473            self.output.SetWildcard("CanSAS 1D (*.xml)|*.xml")
474
475    def radiationtype_changed(self, event):
476        event.Skip()
477        rtype = event.GetEventObject().GetValue().lower()
478        self.source.radiation = rtype
479
480    def metadata_changed(self, event):
481        event.Skip()
482        textbox = event.GetEventObject()
483        attr = textbox.GetName()
484        value = textbox.GetValue().strip()
485
486        setattr(self, attr, value)
487
488
489    def _do_layout(self):
490        vbox = wx.BoxSizer(wx.VERTICAL)
491
492        instructions = ("Select either single column ASCII files or 1D OTOKO "
493        "files containing the Q-Axis and Intensity-Axis data, or a 2D BSL file"
494        ", then chose where to save the converted file, and click Convert.\n"
495        "ASCII and OTOKO files will be converted to CanSAS XML, and OTKO files"
496        " to IGOR/DAT 2D Q_map files.\nCanSAS metadata can also be optionally "
497        "input below.")
498
499        instruction_label = wx.StaticText(self, -1, instructions,
500            size=(_STATICBOX_WIDTH+40, -1))
501        instruction_label.Wrap(_STATICBOX_WIDTH+40)
502        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
503
504        section = wx.StaticBox(self, -1)
505        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
506        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
507
508        input_grid = wx.GridBagSizer(5, 5)
509
510        y = 0
511
512        data_type_label = wx.StaticText(self, -1, "Input Format: ")
513        input_grid.Add(data_type_label, (y,0), (1,1),
514            wx.ALIGN_CENTER_VERTICAL, 5)
515        radio_sizer = wx.BoxSizer(wx.HORIZONTAL)
516        ascii_btn = wx.RadioButton(self, -1, "ASCII", name="ascii",
517            style=wx.RB_GROUP)
518        ascii_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
519        radio_sizer.Add(ascii_btn)
520        otoko_btn = wx.RadioButton(self, -1, "OTOKO 1D", name="otoko")
521        otoko_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
522        radio_sizer.Add(otoko_btn)
523        input_grid.Add(radio_sizer, (y,1), (1,1), wx.ALL, 5)
524        bsl_btn = wx.RadioButton(self, -1, "BSL 2D", name="bsl")
525        bsl_btn.Bind(wx.EVT_RADIOBUTTON, self.datatype_changed)
526        radio_sizer.Add(bsl_btn)
527        y += 1
528
529        q_label = wx.StaticText(self, -1, "Q-Axis Data: ")
530        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
531
532        self.q_input = wx.FilePickerCtrl(self, -1,
533            size=(_STATICBOX_WIDTH-80, -1),
534            message="Chose the Q-Axis data file.",
535            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
536        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
537        y += 1
538
539        iq_label = wx.StaticText(self, -1, "Intensity-Axis Data: ")
540        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
541
542        self.iq_input = wx.FilePickerCtrl(self, -1,
543            size=(_STATICBOX_WIDTH-80, -1),
544            message="Chose the Intensity-Axis data file.",
545            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
546        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
547        y += 1
548
549        radiation_label = wx.StaticText(self, -1, "Radiation Type: ")
550        input_grid.Add(radiation_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
551        self.radiation_input = wx.ComboBox(self, -1,
552            choices=["Neutron", "X-Ray", "Muon", "Electron"],
553            name="radiation", style=wx.CB_READONLY, value="Neutron")
554        self.radiation_input.Bind(wx.EVT_COMBOBOX, self.radiationtype_changed)
555        input_grid.Add(self.radiation_input, (y,1), (1,1), wx.ALL, 5)
556        y += 1
557
558        output_label = wx.StaticText(self, -1, "Output File: ")
559        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
560
561        self.output = FileInput(self,
562            wildcard="CanSAS 1D (*.xml)|*.xml")
563        input_grid.Add(self.output.GetCtrl(), (y,1), (1,1), wx.EXPAND | wx.ALL, 5)
564        y += 1
565
566        self.convert_btn = wx.Button(self, wx.ID_OK, "Stop Converstion")
567        self.convert_btn.SetLabel("Convert")
568        input_grid.Add(self.convert_btn, (y,0), (1,1), wx.ALL, 5)
569        self.convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
570
571        help_btn = wx.Button(self, -1, "HELP")
572        input_grid.Add(help_btn, (y,1), (1,1), wx.ALL, 5)
573        help_btn.Bind(wx.EVT_BUTTON, self.on_help)
574
575        section_sizer.Add(input_grid)
576
577        vbox.Add(section_sizer, flag=wx.ALL, border=5)
578
579        self.metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
580            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
581        metadata_pane = self.metadata_section.GetPane()
582        metadata_grid = wx.GridBagSizer(5, 5)
583
584        self.metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
585            self.on_collapsible_pane)
586
587        y = 0
588        for item in self.properties:
589            # Capitalise each word
590            label_txt = " ".join(
591                [s.capitalize() for s in item.replace('_', ' ').split(' ')])
592            if item == 'run':
593                label_txt = "Run Number"
594            label = wx.StaticText(metadata_pane, -1, label_txt,
595                style=wx.ALIGN_CENTER_VERTICAL)
596            input_box = wx.TextCtrl(metadata_pane, name=item,
597                size=(_STATICBOX_WIDTH-80, -1))
598            input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
599            metadata_grid.Add(label, (y,0), (1,1),
600                wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
601            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
602            y += 1
603
604        detector_label = wx.StaticText(metadata_pane, -1,
605            "Detector:")
606        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
607        detector_btn = wx.Button(metadata_pane, -1, "Enter Detector Metadata")
608        metadata_grid.Add(detector_btn, (y, 1), (1,1), wx.ALL | wx.EXPAND, 5)
609        detector_btn.Bind(wx.EVT_BUTTON, self.show_detector_window)
610        y += 1
611
612        sample_label = wx.StaticText(metadata_pane, -1, "Sample: ")
613        metadata_grid.Add(sample_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
614        sample_btn = wx.Button(metadata_pane, -1, "Enter Sample Metadata")
615        metadata_grid.Add(sample_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
616        sample_btn.Bind(wx.EVT_BUTTON, self.show_sample_window)
617        y += 1
618
619        source_label = wx.StaticText(metadata_pane, -1, "Source: ")
620        metadata_grid.Add(source_label, (y,0), (1,1), wx.ALL | wx.EXPAND, 5)
621        source_btn = wx.Button(metadata_pane, -1, "Enter Source Metadata")
622        source_btn.Bind(wx.EVT_BUTTON, self.show_source_window)
623        metadata_grid.Add(source_btn, (y,1), (1,1), wx.ALL | wx.EXPAND, 5)
624        y += 1
625
626        metadata_pane.SetSizer(metadata_grid)
627
628        vbox.Add(self.metadata_section, proportion=0, flag=wx.ALL, border=5)
629
630        vbox.Fit(self)
631        self.SetSizer(vbox)
632
633class ConverterWindow(widget.CHILD_FRAME):
634    """Displays ConverterPanel"""
635
636    def __init__(self, parent=None, title='File Converter', base=None,
637        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.1),
638        *args, **kwargs):
639        kwargs['title'] = title
640        kwargs['size'] = size
641        widget.CHILD_FRAME.__init__(self, parent, *args, **kwargs)
642
643        self.manager = manager
644        self.panel = ConverterPanel(self, base=None)
645        self.Bind(wx.EVT_CLOSE, self.on_close)
646        self.SetPosition((wx.LEFT, PANEL_TOP))
647        self.Show(True)
648
649    def on_close(self, event):
650        if self.manager is not None:
651            self.manager.converter_frame = None
652        self.Destroy()
Note: See TracBrowser for help on using the repository browser.