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

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 35488b2 was 35488b2, checked in by lewis, 5 years ago

Perform BSL file conversion on a separate thread

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