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

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 ca6659f was 18544f8, checked in by lewis, 8 years ago

Fix frame range input

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