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, 8 years ago

Perform BSL file conversion on a separate thread

  • Property mode set to 100644
File size: 23.1 KB
RevLine 
[77d92cd]1"""
2This module provides a GUI for the file converter
3"""
4
5import wx
6import sys
[05595c4]7import os
[a58706d]8import numpy as np
[77d92cd]9from wx.lib.scrolledpanel import ScrolledPanel
10from sas.sasgui.guiframe.panel_base import PanelBase
11from sas.sasgui.perspectives.calculator import calculator_widgets as widget
[0e11ec7]12from sas.sasgui.perspectives.file_converter.converter_widgets import FileInput
[de0df2c]13from sas.sasgui.perspectives.file_converter.meta_panels import MetadataWindow
14from sas.sasgui.perspectives.file_converter.meta_panels import DetectorPanel
[2a7722b]15from sas.sasgui.perspectives.file_converter.meta_panels import SamplePanel
[55bc56bc]16from sas.sasgui.perspectives.file_converter.meta_panels import SourcePanel
[eb8da5f]17from sas.sasgui.perspectives.file_converter.frame_select_dialog import FrameSelectDialog
[a58706d]18from sas.sasgui.guiframe.events import StatusEvent
[11794f2]19from sas.sasgui.guiframe.documentation_window import DocumentationWindow
[a58706d]20from sas.sasgui.guiframe.dataFitting import Data1D
[fdbea3c]21from sas.sasgui.guiframe.utils import check_float
[8976865]22from sas.sasgui.perspectives.file_converter.cansas_writer import CansasWriter
[35488b2]23from sas.sascalc.file_converter.convert_bsl_thread import ConvertBSLThread
[ba65aff]24from sas.sasgui.perspectives.file_converter.otoko_loader import OTOKOLoader
[f2b3f28]25from sas.sascalc.dataloader.data_info import Detector
[2a7722b]26from sas.sascalc.dataloader.data_info import Sample
[55bc56bc]27from sas.sascalc.dataloader.data_info import Source
[fdbea3c]28from sas.sascalc.dataloader.data_info import Vector
[77d92cd]29
30# Panel size
31if sys.platform.count("win32") > 0:
32    PANEL_TOP = 0
33    _STATICBOX_WIDTH = 410
34    _BOX_WIDTH = 200
[36f4debb]35    PANEL_SIZE = 480
[77d92cd]36    FONT_VARIANT = 0
37else:
38    PANEL_TOP = 60
39    _STATICBOX_WIDTH = 430
40    _BOX_WIDTH = 200
[36f4debb]41    PANEL_SIZE = 500
[77d92cd]42    FONT_VARIANT = 1
43
44class ConverterPanel(ScrolledPanel, PanelBase):
[ba65aff]45    """
46    This class provides the File Converter GUI
47    """
[77d92cd]48
49    def __init__(self, parent, base=None, *args, **kwargs):
50        ScrolledPanel.__init__(self, parent, *args, **kwargs)
51        PanelBase.__init__(self)
[a58706d]52        self.SetupScrolling()
[77d92cd]53        self.SetWindowVariant(variant=FONT_VARIANT)
54
55        self.base = base
56        self.parent = parent
[de0df2c]57        self.meta_frames = []
[35488b2]58        self.bsl_thread = None
[77d92cd]59
[ba65aff]60        # GUI inputs
[a58706d]61        self.q_input = None
62        self.iq_input = None
63        self.output = None
[d6bf064]64        self.radiation_input = None
[35488b2]65        self.convert_btn = None
[d6bf064]66        self.metadata_section = None
[ba65aff]67
[4b862c4]68        self.data_type = "ascii"
[a58706d]69
[ba65aff]70        # Metadata values
[94667e27]71        self.title = ''
72        self.run = ''
73        self.run_name = ''
74        self.instrument = ''
[503cc34]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'
[4b862c4]82
[a58706d]83        self._do_layout()
[77d92cd]84        self.SetAutoLayout(True)
85        self.Layout()
86
[9e9f848]87    def convert_to_cansas(self, frame_data, filepath, single_file):
[ba65aff]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
[9e9f848]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
[ba65aff]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()
[ea2a7348]100        entry_attrs = None
[94667e27]101        if self.run_name != '':
[ea2a7348]102            entry_attrs = { 'name': self.run_name }
[5451a78]103
[94f4518]104        if single_file:
[9e9f848]105            writer.write(filepath, frame_data,
[94f4518]106                sasentry_attrs=entry_attrs)
107        else:
[9e9f848]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],
[94f4518]116                    sasentry_attrs=entry_attrs)
[a58706d]117
[535e181]118    def extract_ascii_data(self, filename):
[ba65aff]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        """
[514a00e]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]))
[a58706d]139
[ff790b3]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
[a58706d]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
[535e181]162    def extract_otoko_data(self, filename):
[ba65aff]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        """
[535e181]169        loader = OTOKOLoader(self.q_input.GetPath(),
170            self.iq_input.GetPath())
[9c500ab]171        otoko_data = loader.load_otoko_data()
172        qdata = otoko_data.q_axis.data
173        iqdata = otoko_data.data_axis.data
[535e181]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
[eb8da5f]185    def ask_frame_range(self, n_frames):
[ba65aff]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        """
[eb8da5f]193        valid_input = False
[05595c4]194        is_bsl = (self.data_type == 'bsl')
195        dlg = FrameSelectDialog(n_frames, is_bsl)
[eb8da5f]196        frames = None
197        increment = None
[94f4518]198        single_file = True
[eb8da5f]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())
[05595c4]206                    if not is_bsl:
207                        single_file = dlg.single_btn.GetValue()
208
[eb8da5f]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"
[05595c4]215                    elif last_frame >= n_frames:
[eb8da5f]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:
[94f4518]227                return { 'frames': [], 'inc': None, 'file': single_file }
[eb8da5f]228        frames = range(first_frame, last_frame + increment,
229            increment)
[94f4518]230        return { 'frames': frames, 'inc': increment, 'file': single_file }
[eb8da5f]231
[a58706d]232    def on_convert(self, event):
[ba65aff]233        """Called when the Convert button is clicked"""
[fdbea3c]234        if not self.validate_inputs():
235            return
236
[35488b2]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
[503cc34]242        self.sample.ID = self.title
[3ea9371]243
[a58706d]244        try:
[4b862c4]245            if self.data_type == 'ascii':
[535e181]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())
[4b862c4]250            else: # self.data_type == 'bsl'
[35488b2]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")
[535e181]256                return
257
[a58706d]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
[535e181]264        frames = []
265        increment = 1
266        single_file = True
[05595c4]267        n_frames = iqdata.shape[0]
[535e181]268        # Standard file has 3 frames: SAS, calibration and WAS
[05595c4]269        if n_frames > 3:
[ba65aff]270            # File has multiple frames - ask the user which ones they want to
271            # export
[05595c4]272            params = self.ask_frame_range(n_frames)
[535e181]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
[f2b3f28]280        output_path = self.output.GetPath()
281
[ba65aff]282        # Prepare the metadata for writing to a file
[d112c30]283        if self.run is None:
[503cc34]284            self.run = []
[d112c30]285        elif not isinstance(self.run, list):
286            self.run = [self.run]
[503cc34]287
[ea2a7348]288        if self.title is None:
289            self.title = ''
290
[503cc34]291        metadata = {
292            'title': self.title,
293            'run': self.run,
[5451a78]294            'instrument': self.instrument,
[503cc34]295            'detector': [self.detector],
296            'sample': self.sample,
297            'source': self.source
298        }
[f2b3f28]299
[9e9f848]300        frame_data = {}
[eb8da5f]301        for i in frames:
302            data = Data1D(x=qdata, y=iqdata[i])
[9e9f848]303            frame_data[i] = data
[94f4518]304        if single_file:
305            # Only need to set metadata on first Data1D object
[9e9f848]306            frame_data = frame_data.values() # Don't need to know frame numbers
[94f4518]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
[9e9f848]312            for datainfo in frame_data.values():
[94f4518]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)
[a58706d]319        wx.PostEvent(self.parent.manager.parent,
320            StatusEvent(status="Conversion completed."))
321
[35488b2]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
[11794f2]342    def on_help(self, event):
[ba65aff]343        """
344        Show the File Converter documentation
345        """
[11794f2]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
[fdbea3c]351    def validate_inputs(self):
352        msg = "You must select a"
[535e181]353        if self.q_input.GetPath() == '' and self.data_type != 'bsl':
[fdbea3c]354            msg += " Q Axis input file."
355        elif self.iq_input.GetPath() == '':
356            msg += "n Intensity input file."
357        elif self.output.GetPath() == '':
[35488b2]358            msg += " destination for the converted file."
[fdbea3c]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
[af84162]366    def show_detector_window(self, event):
[ba65aff]367        """
[b7c21a7]368        Show the window for inputting Detector metadata
[ba65aff]369        """
[de0df2c]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,
[503cc34]375            metadata=self.detector, title='Detector Metadata')
[de0df2c]376        self.meta_frames.append(detector_frame)
377        self.parent.manager.put_icon(detector_frame)
378        detector_frame.Show(True)
[fdbea3c]379
[2a7722b]380    def show_sample_window(self, event):
[ba65aff]381        """
[b7c21a7]382        Show the window for inputting Sample metadata
[ba65aff]383        """
[2a7722b]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,
[503cc34]389            metadata=self.sample, title='Sample Metadata')
[2a7722b]390        self.meta_frames.append(sample_frame)
391        self.parent.manager.put_icon(sample_frame)
392        sample_frame.Show(True)
393
[55bc56bc]394    def show_source_window(self, event):
[ba65aff]395        """
[b7c21a7]396        Show the window for inputting Source metadata
[ba65aff]397        """
[55bc56bc]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,
[503cc34]403            metadata=self.source, title="Source Metadata")
[55bc56bc]404        self.meta_frames.append(source_frame)
405        self.parent.manager.put_icon(source_frame)
406        source_frame.Show(True)
407
[a027549]408    def on_collapsible_pane(self, event):
[ba65aff]409        """
410        Resize the scrollable area to fit the metadata pane when it's
411        collapsed or expanded
412        """
[a027549]413        self.Freeze()
414        self.SetupScrolling()
415        self.parent.Layout()
416        self.Thaw()
417
[4b862c4]418    def datatype_changed(self, event):
[ba65aff]419        """
420        Update the UI and self.data_type when a data type radio button is
421        pressed
422        """
[4b862c4]423        event.Skip()
424        dtype = event.GetEventObject().GetName()
425        self.data_type = dtype
[535e181]426        if dtype == 'bsl':
[05595c4]427            self.q_input.SetPath("")
[535e181]428            self.q_input.Disable()
[d6bf064]429            self.radiation_input.Disable()
430            self.metadata_section.Collapse()
431            self.on_collapsible_pane(None)
432            self.metadata_section.Disable()
[f5c52b0]433            self.output.SetWildcard("IGOR/DAT 2D file in Q_map (*.dat)|*.DAT")
[535e181]434        else:
435            self.q_input.Enable()
[d6bf064]436            self.radiation_input.Enable()
437            self.metadata_section.Enable()
[0e11ec7]438            self.output.SetWildcard("CanSAS 1D (*.xml)|*.xml")
[4b862c4]439
[55775e8]440    def radiationtype_changed(self, event):
441        event.Skip()
442        rtype = event.GetEventObject().GetValue().lower()
[503cc34]443        self.source.radiation = rtype
[55775e8]444
[f2b3f28]445    def metadata_changed(self, event):
446        event.Skip()
447        textbox = event.GetEventObject()
448        attr = textbox.GetName()
449        value = textbox.GetValue().strip()
[fdbea3c]450
[503cc34]451        setattr(self, attr, value)
[f2b3f28]452
453
[77d92cd]454    def _do_layout(self):
[f2b3f28]455        vbox = wx.BoxSizer(wx.VERTICAL)
[a58706d]456
[f5c52b0]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
[5afe77f]464        instruction_label = wx.StaticText(self, -1, instructions,
465            size=(_STATICBOX_WIDTH+40, -1))
466        instruction_label.Wrap(_STATICBOX_WIDTH+40)
[f2b3f28]467        vbox.Add(instruction_label, flag=wx.TOP | wx.LEFT | wx.RIGHT, border=5)
[77d92cd]468
[a58706d]469        section = wx.StaticBox(self, -1)
470        section_sizer = wx.StaticBoxSizer(section, wx.VERTICAL)
471        section_sizer.SetMinSize((_STATICBOX_WIDTH, -1))
[77d92cd]472
473        input_grid = wx.GridBagSizer(5, 5)
474
[4b862c4]475        y = 0
476
[489bb46]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
[13b9a63]494        q_label = wx.StaticText(self, -1, "Q-Axis Data: ")
[4b862c4]495        input_grid.Add(q_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]496
497        self.q_input = wx.FilePickerCtrl(self, -1,
498            size=(_STATICBOX_WIDTH-80, -1),
[785fa53]499            message="Chose the Q-Axis data file.",
500            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
[4b862c4]501        input_grid.Add(self.q_input, (y,1), (1,1), wx.ALL, 5)
502        y += 1
[a58706d]503
[13b9a63]504        iq_label = wx.StaticText(self, -1, "Intensity-Axis Data: ")
[4b862c4]505        input_grid.Add(iq_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]506
507        self.iq_input = wx.FilePickerCtrl(self, -1,
508            size=(_STATICBOX_WIDTH-80, -1),
[785fa53]509            message="Chose the Intensity-Axis data file.",
510            style=wx.FLP_USE_TEXTCTRL | wx.FLP_CHANGE_DIR)
[4b862c4]511        input_grid.Add(self.iq_input, (y,1), (1,1), wx.ALL, 5)
512        y += 1
513
[55775e8]514        radiation_label = wx.StaticText(self, -1, "Radiation Type: ")
[489bb46]515        input_grid.Add(radiation_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[d6bf064]516        self.radiation_input = wx.ComboBox(self, -1,
[55775e8]517            choices=["Neutron", "X-Ray", "Muon", "Electron"],
518            name="radiation", style=wx.CB_READONLY, value="Neutron")
[d6bf064]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)
[55775e8]521        y += 1
522
[a58706d]523        output_label = wx.StaticText(self, -1, "Output File: ")
[4b862c4]524        input_grid.Add(output_label, (y,0), (1,1), wx.ALIGN_CENTER_VERTICAL, 5)
[a58706d]525
[0e11ec7]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)
[4b862c4]529        y += 1
[a58706d]530
[35488b2]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)
[77d92cd]535
[11794f2]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
[a58706d]540        section_sizer.Add(input_grid)
[77d92cd]541
[f2b3f28]542        vbox.Add(section_sizer, flag=wx.ALL, border=5)
543
[d6bf064]544        self.metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
[fdbea3c]545            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
[d6bf064]546        metadata_pane = self.metadata_section.GetPane()
[f2b3f28]547        metadata_grid = wx.GridBagSizer(5, 5)
548
[d6bf064]549        self.metadata_section.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED,
[a027549]550            self.on_collapsible_pane)
551
[f2b3f28]552        y = 0
[503cc34]553        for item in self.properties:
[7c8ddb83]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"
[fdbea3c]559            label = wx.StaticText(metadata_pane, -1, label_txt,
[f2b3f28]560                style=wx.ALIGN_CENTER_VERTICAL)
[fdbea3c]561            input_box = wx.TextCtrl(metadata_pane, name=item,
[f2b3f28]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)
[fdbea3c]566            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
[f2b3f28]567            y += 1
568
[fdbea3c]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)
[af84162]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)
[fdbea3c]575        y += 1
576
[2a7722b]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
[55bc56bc]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
[fdbea3c]591        metadata_pane.SetSizer(metadata_grid)
[f2b3f28]592
[d6bf064]593        vbox.Add(self.metadata_section, proportion=0, flag=wx.ALL, border=5)
[77d92cd]594
595        vbox.Fit(self)
596        self.SetSizer(vbox)
597
598class ConverterWindow(widget.CHILD_FRAME):
[ba65aff]599    """Displays ConverterPanel"""
[77d92cd]600
601    def __init__(self, parent=None, title='File Converter', base=None,
[f5c52b0]602        manager=None, size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.1),
[77d92cd]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.