Index: src/sas/sasgui/perspectives/file_converter/converter_panel.py
===================================================================
--- src/sas/sasgui/perspectives/file_converter/converter_panel.py	(revision 36f4debbc5028e91bd58ddcdd40ef90cd31c53b2)
+++ src/sas/sasgui/perspectives/file_converter/converter_panel.py	(revision fdbea3ce018551a044127bc34f8ecac9b274b219)
@@ -11,6 +11,8 @@
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sasgui.guiframe.dataFitting import Data1D
+from sas.sasgui.guiframe.utils import check_float
 from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
 from sas.sascalc.dataloader.data_info import Detector
+from sas.sascalc.dataloader.data_info import Vector
 
 # Panel size
@@ -42,4 +44,5 @@
         self.iq_input = None
         self.output = None
+        self.to_validate = []
 
         self.metadata = {
@@ -48,6 +51,9 @@
             'run_name': None,
             'instrument': None,
-            'detector': None,
+            'detector': [Detector()]
         }
+        self.vectors = ['offset', 'orientation', 'pixel_size']
+        for vector_name in self.vectors:
+            setattr(self.metadata['detector'][0], vector_name, Vector())
 
         self._do_layout()
@@ -85,4 +91,7 @@
 
     def on_convert(self, event):
+        if not self.validate_inputs():
+            return
+
         try:
             qdata = self.extract_data(self.q_input.GetPath())
@@ -104,16 +113,56 @@
             if run_name is not None:
                 self.metadata['run_name'] = { run: run_name }
-        if self.metadata['detector'] is not None:
-            name = self.metadata['detector']
-            detector = Detector()
-            detector.name = name
-            self.metadata['detector'] = [detector]
+            else:
+                self.metadata['run_name'] = {}
+        else:
+            self.metadata['run'] = []
+            self.metadata['run_name'] = {}
+        detector = self.metadata['detector'][0]
+        if detector.name is None:
+            self.metadata['detector'][0].name = ''
+
+        # Convert vectors from strings to float
+        for vector_name in self.vectors:
+            # Vector of strings or Nones
+            vector = getattr(self.metadata['detector'][0], vector_name)
+            for direction in ['x', 'y', 'z']:
+                value = getattr(vector, direction)
+                if value is not None:
+                    value = float(value)
+                    setattr(vector, direction, value)
+            setattr(self.metadata['detector'][0], vector_name, vector)
 
         for attr, value in self.metadata.iteritems():
-            setattr(data, attr, value)
+            if value is not None:
+                setattr(data, attr, value)
 
         self.convert_to_cansas(data, output_path)
         wx.PostEvent(self.parent.manager.parent,
             StatusEvent(status="Conversion completed."))
+
+    def validate_inputs(self):
+        msg = "You must select a"
+        if self.q_input.GetPath() == '':
+            msg += " Q Axis input file."
+        elif self.iq_input.GetPath() == '':
+            msg += "n Intensity input file."
+        elif self.output.GetPath() == '':
+            msg += "destination for the converted file."
+        if msg != "You must select a":
+            wx.PostEvent(self.parent.manager.parent,
+                StatusEvent(status=msg, info='error'))
+            return
+
+        for ctrl in self.to_validate:
+            if ctrl.GetValue() == '': continue
+            ctrl_valid = check_float(ctrl)
+            if not ctrl_valid:
+                msg = "{} must be a valid float".format(
+                    ctrl.GetName().replace('_', ' '))
+                wx.PostEvent(self.parent.manager.parent,
+                    StatusEvent(status=msg, info='error'))
+                return False
+        return True
+
 
     def metadata_changed(self, event):
@@ -122,4 +171,20 @@
         attr = textbox.GetName()
         value = textbox.GetValue().strip()
+
+        if attr.startswith('detector_'):
+            attr = attr[9:] # Strip detector_
+            is_vector = False
+            for vector_name in self.vectors:
+                if attr.startswith(vector_name): is_vector = True
+            if is_vector:
+                if value == '': value = None
+                direction = attr[-1]
+                attr = attr[:-2]
+                vector = getattr(self.metadata['detector'][0], attr)
+                setattr(vector, direction, value)
+                value = vector
+            setattr(self.metadata['detector'][0], attr, value)
+            return
+
         if value == '':
             self.metadata[attr] = None
@@ -132,6 +197,7 @@
 
         instructions = ("Select the 1 column ASCII files containing the Q Axis"
-            "and Intensity data, chose where to save the converted file, then "
-            "click Convert to convert them to CanSAS XML format")
+            " and Intensity data, chose where to save the converted file, then"
+            " click Convert to convert them to CanSAS XML format. If required,"
+            " metadata can also be input below.")
         instruction_label = wx.StaticText(self, -1, instructions,
             size=(_STATICBOX_WIDTH+40, -1))
@@ -171,5 +237,5 @@
         input_grid.Add(self.output, (2,1), (1,1), wx.ALL, 5)
 
-        convert_btn = wx.Button(self, -1, "Convert")
+        convert_btn = wx.Button(self, wx.ID_OK, "Convert")
         input_grid.Add(convert_btn, (3,0), (1,1), wx.ALL, 5)
         convert_btn.Bind(wx.EVT_BUTTON, self.on_convert)
@@ -180,22 +246,127 @@
 
         metadata_section = wx.CollapsiblePane(self, -1, "Metadata",
-            size=(_STATICBOX_WIDTH+40, -1))
-
+            size=(_STATICBOX_WIDTH+40, -1), style=wx.WS_EX_VALIDATE_RECURSIVELY)
+        metadata_pane = metadata_section.GetPane()
         metadata_grid = wx.GridBagSizer(5, 5)
 
         y = 0
         for item in self.metadata.keys():
+            if item == 'detector': continue
             label_txt = item.replace('_', ' ').capitalize()
-            label = wx.StaticText(metadata_section.GetPane(), -1, label_txt,
+            label = wx.StaticText(metadata_pane, -1, label_txt,
                 style=wx.ALIGN_CENTER_VERTICAL)
-            input_box = wx.TextCtrl(metadata_section.GetPane(), name=item,
+            input_box = wx.TextCtrl(metadata_pane, name=item,
                 size=(_STATICBOX_WIDTH-80, -1))
             input_box.Bind(wx.EVT_TEXT, self.metadata_changed)
             metadata_grid.Add(label, (y,0), (1,1),
                 wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
-            metadata_grid.Add(input_box, (y,1), (1,1), wx.EXPAND)
+            metadata_grid.Add(input_box, (y,1), (1,2), wx.EXPAND)
             y += 1
 
-        metadata_section.GetPane().SetSizer(metadata_grid)
+        detector_label = wx.StaticText(metadata_pane, -1,
+            "Detector:")
+        metadata_grid.Add(detector_label, (y, 0), (1,1), wx.ALL | wx.EXPAND, 5)
+        y += 1
+
+        name_label = wx.StaticText(metadata_pane, -1, "Name: ")
+        metadata_grid.Add(name_label, (y, 1), (1,1))
+
+        name_input = wx.TextCtrl(metadata_pane,
+            name="detector_name", size=(_STATICBOX_WIDTH-80-55, -1))
+        metadata_grid.Add(name_input, (y, 2), (1,1))
+        name_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        y += 1
+
+        distance_label = wx.StaticText(metadata_pane, -1,
+            "Distance (mm):")
+        metadata_grid.Add(distance_label, (y, 1), (1,1))
+
+        distance_input = wx.TextCtrl(metadata_pane, -1,
+            name="detector_distance", size=(50,-1))
+        metadata_grid.Add(distance_input, (y,2), (1,1))
+        self.to_validate.append(distance_input)
+        distance_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        y += 1
+
+        offset_label = wx.StaticText(metadata_pane, -1,
+            "Offset (m): ")
+        metadata_grid.Add(offset_label, (y,1), (1,1))
+
+        offset_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        x_label = wx.StaticText(metadata_pane, -1, "x: ",
+            style=wx.ALIGN_CENTER_VERTICAL)
+        offset_sizer.Add(x_label, wx.ALIGN_CENTER_VERTICAL)
+        offset_x_input = wx.TextCtrl(metadata_pane, -1,
+            name="detector_offset_x", size=(50, -1))
+        offset_sizer.Add(offset_x_input)
+        self.to_validate.append(offset_x_input)
+        offset_x_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        offset_sizer.AddSpacer((15, -1))
+        y_label = wx.StaticText(metadata_pane, -1, "y: ",
+            style=wx.ALIGN_CENTER_VERTICAL)
+        offset_sizer.Add(y_label, wx.ALIGN_CENTER_VERTICAL)
+        offset_y_input = wx.TextCtrl(metadata_pane, -1,
+            name="detector_offset_y", size=(50, -1))
+        offset_sizer.Add(offset_y_input)
+        self.to_validate.append(offset_y_input)
+        offset_y_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        metadata_grid.Add(offset_sizer, (y,2), (1,1), wx.BOTTOM, 5)
+        y += 1
+
+        orientation_label = wx.StaticText(metadata_pane, -1,
+            u"Orientation (\xb0): ")
+        metadata_grid.Add(orientation_label, (y, 1), (1, 1))
+
+        orientation_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        roll_label = wx.StaticText(metadata_pane, -1, "Roll: ")
+        orientation_sizer.Add(roll_label)
+        roll_input = wx.TextCtrl(metadata_pane, -1,
+            name="detector_orientation_x", size=(50, -1))
+        self.to_validate.append(roll_input)
+        roll_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        orientation_sizer.Add(roll_input)
+        orientation_sizer.AddSpacer((15, -1))
+        pitch_label = wx.StaticText(metadata_pane, -1, "Pitch: ")
+        orientation_sizer.Add(pitch_label)
+        pitch_input = wx.TextCtrl(metadata_pane, 1,
+            name="detector_orientation_y", size=(50,-1))
+        self.to_validate.append(pitch_input)
+        pitch_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        orientation_sizer.Add(pitch_input)
+        orientation_sizer.AddSpacer((15, -1))
+        yaw_label = wx.StaticText(metadata_pane, -1, "Yaw: ")
+        orientation_sizer.Add(yaw_label)
+        yaw_input = wx.TextCtrl(metadata_pane, 1,
+            name="detector_orientation_z", size=(50,-1))
+        yaw_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        self.to_validate.append(yaw_input)
+        orientation_sizer.Add(yaw_input)
+
+        metadata_grid.Add(orientation_sizer, (y,2), (1,1), wx.BOTTOM, 5)
+        y += 1
+
+        pixel_label = wx.StaticText(metadata_pane, -1, "Pixel Size (mm):")
+        metadata_grid.Add(pixel_label, (y,1), (1,1))
+
+        pixel_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        pixel_x_label = wx.StaticText(metadata_pane, -1, "x: ")
+        pixel_sizer.Add(pixel_x_label)
+        pixel_x_input = wx.TextCtrl(metadata_pane, -1,
+            name="detector_pixel_size_x", size=(50, -1))
+        self.to_validate.append(pixel_x_input)
+        pixel_x_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        pixel_sizer.Add(pixel_x_input)
+        pixel_sizer.AddSpacer((15, -1))
+        pixel_y_label = wx.StaticText(metadata_pane, -1, "y: ")
+        pixel_sizer.Add(pixel_y_label)
+        pixel_y_input = wx.TextCtrl(metadata_pane, 1,
+            name="detector_pixel_size_y", size=(50,-1))
+        self.to_validate.append(pixel_y_input)
+        pixel_y_input.Bind(wx.EVT_TEXT, self.metadata_changed)
+        pixel_sizer.Add(pixel_y_input)
+
+        metadata_grid.Add(pixel_sizer, (y,2), (1,1), wx.BOTTOM, 5)
+
+        metadata_pane.SetSizer(metadata_grid)
 
         vbox.Add(metadata_section, proportion=0, flag=wx.ALL, border=5)
