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

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

Add docstrings & comments, and rename old bsl_loader to otoko_loader

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