Example #1
0
class inline_script(libopensesame.inline_script.inline_script, qtitem.qtitem):

	"""The inline_script GUI controls"""

	def __init__(self, name, experiment, string=None):

		"""
		Constructor.

		Arguments:
		name 		--	The item name.
		experiment	--	The experiment object.

		Keywords arguments:
		string		--	A definition string. (default=None)
		"""

		libopensesame.inline_script.inline_script.__init__(self, name, \
			experiment, string)
		qtitem.qtitem.__init__(self)
		self.lock = False
		self._var_info = None

	def apply_edit_changes(self, **args):

		"""
		Applies the controls.

		Keywords arguments:
		args	--	A dictionary to accept unused keyword arguments.
		"""

		qtitem.qtitem.apply_edit_changes(self, False)
		sp = self.qprogedit.text(index=0)
		sr = self.qprogedit.text(index=1)
		self.set(u'_prepare', sp)
		self.set(u'_run', sr)
		self.lock = True
		self._var_info = None
		self.experiment.main_window.refresh(self.name)
		self.lock = False

	def init_edit_widget(self):

		"""Constructs the GUI controls."""
		
		from QProgEdit import QTabManager
		qtitem.qtitem.init_edit_widget(self, False)
		self.qprogedit = QTabManager(handler=self.apply_edit_changes, \
			defaultLang=u'Python', cfg=cfg)
		self.qprogedit.addTab(u'Prepare')
		self.qprogedit.addTab(u'Run')
		# Switch to the run phase, unless there is only content for the prepare
		# phase.
		if self._run == u'' and self._prepare != u'':
			self.qprogedit.setCurrentIndex(0)
		else:
			self.qprogedit.setCurrentIndex(1)
		self.edit_vbox.addWidget(self.qprogedit)

	def edit_widget(self):

		"""
		Updates the GUI controls.

		Returns:
		The control QWidget.
		"""

		qtitem.qtitem.edit_widget(self, False)
		if not self.lock:
			self.qprogedit.setText(self._prepare, index=0)
			self.qprogedit.setText(self._run, index=1)
		return self._edit_widget

	def get_ready(self):

		"""Applies pending script changes."""

		if self.qprogedit.isModified():
			debug.msg(u'applying pending script changes')
			self.apply_edit_changes(catch=False)
			return True
		return qtitem.qtitem.get_ready(self)
Example #2
0
class qtitem(QtCore.QObject):
    """Base class for the GUI controls of other items"""
    def __init__(self):
        """Constructor"""

        QtCore.QObject.__init__(self)

        # The auto-widgets are stored in name -> (var, widget) dictionaries
        self.auto_line_edit = {}
        self.auto_combobox = {}
        self.auto_spinbox = {}
        self.auto_slider = {}
        self.auto_editor = {}
        self.auto_checkbox = {}
        self.sanity_criteria = {}

        self.init_edit_widget()
        self.init_script_widget()
        self.script_tab = None
        self.lock = False
        self.edit_mode = u'edit'

        debug.msg(u'created %s' % self.name)

    def open_help_tab(self, page=None):
        """Opens a help tab."""

        self.experiment.main_window.ui.tabwidget.open_help(self.item_type)

    def open_tab(self):
        """Opens the correct tab based on the current edit mode"""

        if self.edit_mode == u'edit':
            self.open_edit_tab()
        else:
            self.open_script_tab()

    def init_edit_widget(self, stretch=True):
        """Build the GUI controls"""

        # Header widget
        self.header = header_widget.header_widget(self)
        self.user_hint_widget = user_hint_widget.user_hint_widget( \
         self.experiment.main_window, self)
        self.header_hbox = QtGui.QHBoxLayout()
        self.header_hbox.addWidget(self.experiment.label_image(self.item_type))
        self.header_hbox.addWidget(self.header)
        self.header_hbox.addStretch()
        self.header_hbox.setContentsMargins(0, 5, 0, 10)

        # Edit script button
        button = QtGui.QPushButton(self.experiment.icon(u"script"), u"")
        button.setToolTip(_(u"Edit script"))
        button.setIconSize(QtCore.QSize(16, 16))
        QtCore.QObject.connect(button, QtCore.SIGNAL(u"clicked()"), \
         self.open_script_tab)
        self.header_hbox.addWidget(button)

        # Help button
        button = QtGui.QPushButton(self.experiment.icon(u"help"), u"")
        button.setToolTip(
            _(u"Tell me more about the %s item") % self.item_type)
        button.setIconSize(QtCore.QSize(16, 16))
        QtCore.QObject.connect(button, QtCore.SIGNAL(u"clicked()"), \
         self.open_help_tab)
        self.header_hbox.addWidget(button)

        self.header_widget = QtGui.QWidget()
        self.header_widget.setLayout(self.header_hbox)

        # The edit_grid is the layout that contains the actual controls for the
        # items.
        self.edit_grid = QtGui.QGridLayout()
        self.edit_grid.setColumnStretch(2, 2)
        self.edit_grid_widget = QtGui.QWidget()
        self.edit_grid.setMargin(0)
        self.edit_grid_widget.setLayout(self.edit_grid)

        # The edit_vbox contains the edit_grid and the header widget
        self.edit_vbox = QtGui.QVBoxLayout()
        self.edit_vbox.setMargin(5)
        self.edit_vbox.addWidget(self.header_widget)
        self.edit_vbox.addWidget(self.user_hint_widget)
        self.edit_vbox.addWidget(self.edit_grid_widget)
        if stretch:
            self.edit_vbox.addStretch()
        self._edit_widget = QtGui.QWidget()
        self._edit_widget.setLayout(self.edit_vbox)
        self._edit_widget.__edit_item__ = self.name

        return self._edit_widget

    def edit_widget(self, stretch=True):
        """
		A dummy edit widget, to be overridden.

		Keywords arguments:
		stretch		--	DEPRECATED (default=True)
		"""

        if not stretch:
            debug.msg(u"passing the stretch argument is deprecated", \
             reason=u"deprecation")
        self.user_hint_widget.clear()
        self.header.restore_name(False)
        self.header.refresh()
        self._edit_widget.__edit_item__ = self.name
        if not self.sanity_check():
            self.open_script_tab()
            return
        self.auto_edit_widget()
        self.user_hint_widget.refresh()
        return self._edit_widget

    def apply_name_change(self, rebuild=True):
        """
		Apply an item name change

		Keywords arguments:
		rebuild -- a deprecated argument (default=True)
		"""

        debug.msg()

        # Sanitize the name, check if it is new and valid, and if so, rename
        new_name = self.experiment.sanitize(self.header.edit_name.text(), \
         strict=True, allow_vars=False)
        if new_name.lower() != self.name.lower():
            valid = self.experiment.check_name(new_name)
            if valid != True:
                self.experiment.notify(valid)
                self.header.edit_name.setText(self.name)
                return
        old_name = self.name
        self.name = new_name
        self._edit_widget.__edit_item__ = new_name
        self.experiment.main_window.dispatch.event_name_change.emit(old_name, \
         new_name)

    def apply_edit_changes(self, rebuild=True):
        """
		Applies the GUI controls.

		Keywords arguments:
		rebuild	--	Specifies whether the overview area (item list) should be
					rebuild. (default=True)
		"""

        debug.msg(self.name)
        if self.experiment.main_window.lock_refresh:
            debug.msg(u"skipping, because refresh in progress")
            return False
        self.auto_apply_edit_changes()
        self.set(u"description", \
         self.experiment.sanitize(unicode( \
          self.header.edit_desc.text()).strip()))
        if self.description == u"":
            self.description = u"No description"
        self.header.label_desc.setText(self.description)
        self.experiment.main_window.dispatch.event_simple_change.emit(
            self.name)
        return True

    def close_edit_tab(self, index=None):
        """
		Closes the edit tab (does nothing by default).

		Keywords arguments:
		index	--	The index of the tab in the tab area. (default=None)
		"""

        pass

    def open_edit_tab(self, index=None, focus=True):
        """
		Opens the GUI control tab, or switches to the tab if it was already
		open.

		Keywords arguments:
		index	--	The index of the tab (if open). (default=None)
		focus	--	Indicates whether the tab should receive focus.
					(default=True)
		"""

        debug.msg(u"%s (#%s)" % (self.name, hash(self)))

        # Switch to edit mode and close the script tab if it was open
        self.edit_mode = u"edit"
        for i in range(self.experiment.ui.tabwidget.count()):
            w = self.experiment.ui.tabwidget.widget(i)
            if hasattr(w, u"__script_item__") and w.__script_item__ == \
             self.name:
                self.experiment.ui.tabwidget.removeTab(i)
                if index == None:
                    index = i
                break

        # Focus the edit tab, instead of reopening, if it was already open
        for i in range(self.experiment.ui.tabwidget.count()):
            w = self.experiment.ui.tabwidget.widget(i)
            if hasattr(w, u"__edit_item__") and w.__edit_item__ == self.name:
                index = i

        # Refresh the controls on the tab. In debug mode don't catch any errors
        if debug.enabled:
            widget = self.edit_widget()
        else:
            try:
                widget = self.edit_widget()
            except Exception as e:
                self.experiment.notify(_(u"%s (Edit the script to fix this)") \
                 % e)
                self.open_script_tab()
                return

        # Open the tab or focus the tab if it was already open
        if index == None:
            self.edit_tab_index = self.experiment.ui.tabwidget.addTab(widget, \
             self.experiment.icon(self.item_type), u"%s" % self.name)
        else:
            self.experiment.ui.tabwidget.insertTab(index, widget, \
             self.experiment.icon(self.item_type), u"%s" % self.name)
            self.edit_tab_index = index
        if focus:
            self.experiment.ui.tabwidget.setCurrentIndex(self.edit_tab_index)

    def apply_script_and_close(self):
        """Applies script changes and opens the edit tab"""

        self.apply_script_changes(mode=u'edit')
        self.experiment.main_window.select_item(self.name)

    def apply_script_changes(self, rebuild=True, catch=True, mode=u'script'):
        """
		Applies changes to the script, by regenerating the item from the script.

		Keywords arguments:
		rebuild	--	Specifies whether the overview area (item list) should be
					rebuild. (default=True)
		catch	--	Indicates whether exceptions should be caught and shown in a
					notification dialog (True) or not be caught (False).
					(default=True)
		mode	--	Indicates whether the item should re-open in edit or script
					mode. (default=u'script')
		"""

        debug.msg(self.name)
        script = self.script_qprogedit.text()
        # Create a new item and make it a clone of the current item
        item = self.experiment.main_window.add_item(self.item_type, False, \
         name=self.name, interactive=False)
        if catch:
            try:
                self.experiment.items[item].from_string(script)
            except Exception as e:
                self.experiment.notify(unicode(e))
                return
        else:
            self.experiment.items[item].from_string(script)
        self.experiment.items[item].name = self.name
        # Replace the current item
        self.experiment.items[self.name] = self.experiment.items[item]
        del self.experiment.items[item]
        self.experiment.items[self.name].init_script_widget()
        self.experiment.items[self.name].edit_mode = mode
        self.experiment.main_window.dispatch.event_script_change.emit(
            self.name)
        # The logic here is pretty complex, and is more-or-less a hack until the
        # event handling code has been improved. Basically, if we want to apply
        # the script and stay in script mode, we have to re-open the script tab,
        # because the entire item is re-generated. This new tab has to be
        # inserted in place of (i.e. with the same index as) the old tab, which
        # has to be removed. We always refocus the tab, but if the tab doesn't
        # actually have focus, we refocus the original tab. This is necessary
        # to avoid repainting artifacts.
        #
        # See also this issue:
        # - <https://github.com/smathot/OpenSesame/issues/219>
        if mode == u'script':
            currentIndex = self.experiment.ui.tabwidget.currentIndex()
            for i in range(self.experiment.ui.tabwidget.count()):
                w = self.experiment.ui.tabwidget.widget(i)
                if hasattr(w, u'__script_item__') and w.__script_item__ == \
                 self.name:
                    if i == currentIndex:
                        focus = True
                    else:
                        focus = False
                    self.experiment.items[self.name].open_script_tab(index=i, \
                     focus=True)
                    self.experiment.ui.tabwidget.removeTab(i + 1)
                    if not focus:
                        self.experiment.ui.tabwidget.setCurrentIndex( \
                         currentIndex)
                    break

    def strip_script_line(self, s):
        """
		Strips unwanted characters from a line of script

		Arguments:
		s -- a line of script

		Returns:
		A stripped line of script
		"""

        if len(s) > 0 and s[0] == u"\t":
            return s[1:] + u"\n"
        return s + u"\n"

    def init_script_widget(self):
        """Build the script tab"""

        from QProgEdit import QTabManager
        self.script_qprogedit = QTabManager(handler= \
         self.apply_script_and_close, defaultLang=u'OpenSesame', \
         handlerButtonText=_(u'Apply and close script editor'), \
         focusOutHandler=self.apply_script_changes, cfg=cfg)
        self.script_qprogedit.addTab(u'Script')

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.experiment.label_image(self.item_type))
        self.script_header = QtGui.QLabel()
        hbox.addWidget(self.script_header)
        hbox.addStretch()
        hbox.setContentsMargins(0, 0, 0, 0)
        hwidget = QtGui.QWidget()
        hwidget.setLayout(hbox)

        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(hwidget)
        vbox.addWidget(self.script_qprogedit)
        self._script_widget = QtGui.QWidget()
        self._script_widget.setLayout(vbox)
        self._script_widget.__script_item__ = self.name

    def script_widget(self):
        """
		Update the script tab

		Returns:
		The QWidget containing the script tab
		"""

        self.script_header.setText( \
         _(u"Editing script for <b>%s</b> - %s") % (self.name, \
         self.item_type))
        script = u""
        for s in self.to_string().split(u"\n")[1:]:
            script += self.strip_script_line(s)
        self.script_qprogedit.setText(script)
        self._script_widget.__script_item__ = self.name
        return self._script_widget

    def open_script_tab(self, index=None, focus=True):
        """
		Open/ show the script tab

		Keywords arguments:
		index -- the index of the tab (if it is already open) (default=None)
		focus -- indicates whether the tab should receive focus (default=True)
		"""

        debug.msg(u"%s (#%s)" % (self.name, hash(self)))
        self.edit_mode = u"script"

        # Close the edit tab
        for i in range(self.experiment.ui.tabwidget.count()):
            w = self.experiment.ui.tabwidget.widget(i)
            if hasattr(w, u"__edit_item__") and w.__edit_item__ == self.name:
                self.experiment.ui.tabwidget.removeTab(i)
                if index == None:
                    index = i
                break

        for i in range(self.experiment.ui.tabwidget.count()):
            w = self.experiment.ui.tabwidget.widget(i)
            if hasattr(w,
                       u"__script_item__") and w.__script_item__ == self.name:
                index = i
        if index == None:
            self.script_tab_index = self.experiment.ui.tabwidget.addTab( \
             self.script_widget(), self.experiment.icon(u"script"), u"%s" \
             % self.name)
        else:
            self.script_tab_index = index
            self.experiment.ui.tabwidget.insertTab(index, \
             self.script_widget(), self.experiment.icon(u"script"), u"%s" \
             % self.name)
        if focus:
            self.experiment.ui.tabwidget.setCurrentIndex(self.script_tab_index)

    def close_script_tab(self, index=None):
        """
		Close the script tab (does nothing by defaut)

		Keywords arguments:
		index -- the index of the tab in the tab area (default=None)
		"""

        pass

    def rename(self, from_name, to_name):
        """
		Handle the renaming of an item (not necesarrily the currnet item)

		Arguments:
		from_name -- the old item name
		to_name -- the new item name
		"""

        if self.name == from_name:
            self.name = to_name

    def delete(self, item_name, item_parent=None, index=None):
        """
		Delete an item (not necessarily the current one)

		Arguments:
		item_name -- the name of the item to be deleted

		Keywords arguments:
		item_parent -- the parent item (default=None)
		index -- the index of the item in the parent (default=None)
		"""

        pass

    def rename_var(self, item, from_name, to_name):
        """
		A notification that a variable has been renamed

		Arguments:
		item -- the item doing the renaming
		from_name -- the old variable name
		to_name -- the new variable name
		"""

        pass

    def item_tree_widget(self, toplevel, icon=None, name=None, tooltip=None):
        """
		Create a single item tree widget

		Arguments:
		toplevel -- the toplevel item

		Keyword arguments:
		icon -- an icon name or None for default (default=None)
		name -- the name of the item or None for default (default=None)
		tooltip -- the tooltip or None for default (default=None)

		Returns:
		A QTreeWidgetItem
		"""

        if name == None:
            name = self.name
        if icon == None:
            icon = self.item_type
        if tooltip == None:
            tooltip = _(u"Type: %s\nDescription: %s") % (self.item_type, \
             self.description)
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setItalic(True)
        widget = QtGui.QTreeWidgetItem(toplevel)
        widget.setText(0, name)
        widget.setIcon(0, self.experiment.icon(icon))
        widget.name = name
        widget.setToolTip(0, tooltip)
        return widget

    def build_item_tree(self, toplevel=None, items=[]):
        """
		Construct an item tree

		Keyword arguments:
		toplevel -- the toplevel widget (default = None)
		items -- a list of items that have been added, to prevent recursion
				 (default=[])
		"""

        toplevel.addChild(self.item_tree_widget(toplevel))

    def is_offspring(self, item):
        """
		Checks if the item is offspring of the current item, in the sense that
		the current item is contained by the item

		Arguments:
		item -- the potential offspring

		Returns:
		True if the current item is offspring of the item, False otherwise
		"""

        return False

    def parents(self):
        """
		Creates a list of all the items	that the current sequences is connected
		to upstream

		Returns:
		A list of item names
		"""

        l = [self.name]
        for item in self.experiment.items:
            if self.experiment.items[item].is_offspring(self.name):
                l.append(item)
        return l

    def variable_vars(self, exclude=[]):
        """
		Determines if one of the variables of the current item is defined in
		terms of another variable

		Keywords arguments:
		exclude -- a list of variables that should not be checked

		Returns:
		True if there are variably defined variables, False otherwise
		"""

        for var in self.variables:
            if var not in exclude:
                val = self.variables[var]
                if isinstance(val, basestring) and u'[' in val:
                    return True
        return False

    def get_ready(self):
        """
		This function should be overridden to do any last-minute stuff that
		and item should do before an experiment is actually run, such as
		applying pending script changes.

		Returns:
		True if some action has been taken, False if nothing was done
		"""

        if self.script_qprogedit.isModified():
            debug.msg(u'applying pending script changes')
            self.apply_script_changes(catch=False)
            return True
        return False

    def auto_edit_widget(self):
        """Update the GUI controls based on the auto-widgets"""

        debug.msg()
        for var, edit in self.auto_line_edit.iteritems():
            edit.editingFinished.disconnect()
            if self.has(var):
                try:
                    edit.setText(self.unistr(self.get(var, _eval=False)))
                except Exception as e:
                    self.experiment.notify(_(u"Failed to set control '%s': %s") \
                     % (var, e))
            else:
                edit.setText(u"")
            edit.editingFinished.connect(self.apply_edit_changes)

        for var, combobox in self.auto_combobox.iteritems():
            combobox.currentIndexChanged.disconnect()
            if self.has(var):
                try:
                    combobox.setCurrentIndex(combobox.findText( \
                     self.unistr(self.get(var, _eval=False))))
                except Exception as e:
                    self.experiment.notify(_(u"Failed to set control '%s': %s") \
                     % (var, e))
            combobox.currentIndexChanged.connect(self.apply_edit_changes)

        for var, spinbox in self.auto_spinbox.iteritems():
            spinbox.editingFinished.disconnect()
            if self.has(var):
                val = self.get(var, _eval=False)
                if type(val) in (float, int):
                    try:
                        spinbox.setValue(val)
                    except Exception as e:
                        self.experiment.notify(_( \
                         u"Failed to set control '%s': %s") % (var, e))
                else:
                    spinbox.setDisabled(True)
                    self.user_hint_widget.add_user_hint(_( \
                     u'"%s" is defined using variables and can only be edited through the script.' \
                     % var))
            spinbox.editingFinished.connect(self.apply_edit_changes)

        for var, slider in self.auto_slider.iteritems():
            slider.valueChanged.disconnect()
            if self.has(var):
                val = self.get(var, _eval=False)
                if type(val) in (float, int):
                    try:
                        slider.setValue(val)
                    except Exception as e:
                        self.experiment.notify(_( \
                         u"Failed to set control '%s': %s") % (var, e))
                else:
                    slider.setDisabled(True)
                    self.user_hint_widget.add_user_hint(_( \
                     u'"%s" is defined using variables and can only be edited through the script.' \
                     % var))
            slider.valueChanged.connect(self.apply_edit_changes)

        for var, checkbox in self.auto_checkbox.iteritems():
            checkbox.toggled.disconnect()
            if self.has(var):
                try:
                    checkbox.setChecked(self.get(var, _eval=False) == u"yes")
                except Exception as e:
                    self.experiment.notify(_(u"Failed to set control '%s': %s") \
                     % (var, e))
            checkbox.toggled.connect(self.apply_edit_changes)

        for var, qprogedit in self.auto_editor.iteritems():
            if self.has(var):
                try:
                    qprogedit.setText(self.unistr(self.get(var, _eval=False)))
                except Exception as e:
                    self.experiment.notify(_(u"Failed to set control '%s': %s") \
                     % (var, e))

    def sanitize_check(self, s, strict=False, allow_vars=True, notify=True):
        """
		Checks whether a string is sane (i.e. unchanged by sanitize()) and
		optionally presents a warning.

		Arguments:
		s			--	The string to check.

		Keyword arguments:
		strict		--	See sanitize().
		allow_vars	--	See sanitize().
		notify		--	Indicates whether a notification should be presented if
						the string is not sane.

		Returns:
		True if s is sane, False otherwise.
		"""

        sane = s == self.sanitize(s, strict=strict, allow_vars=allow_vars)
        if not sane and notify:
            if strict:
                self.experiment.notify(
                    _(u'All non-alphanumeric characters except underscores have been stripped'
                      ))
            else:
                self.experiment.notify(
                    _(u'The following characters are not allowed and have been stripped: double-quote ("), backslash (\), and newline'
                      ))
        return sane

    def sanity_check(self):
        """
		Checks whether all variables match prespecified criteria and fall back
		to the script editor otherwise. This is usefull to check that certain
		variables are numeric, etc.
		"""

        debug.msg()
        errors = []
        for var_name, criteria in self.sanity_criteria.items():
            msg = _(u"Invalid or missing value for variable '%s' (edit script to fix this)") \
             % var_name
            if u'msg' in criteria:
                msg += u': ' + criteria[u'msg']
            if not self.has(var_name) and u'required' in criteria and \
             criteria[u'required']:
                self.experiment.notify(msg)
                return False
            else:
                var = self.get(var_name, _eval=False)
                if u'type' in criteria:
                    _type = criteria[u'type']
                    if type(_type) != list:
                        _type = [_type]
                    if type(var) not in _type:
                        self.experiment.notify(msg)
                        return False
                if u'func' in criteria:
                    if not criteria[u'func'](var):
                        self.experiment.notify(msg)
                        return False
        return True

    def auto_apply_edit_changes(self, rebuild=True):
        """
		Apply the auto-widget controls

		Keyword arguments:
		rebuild -- deprecated (does nothing) (default=True)
		"""

        debug.msg()
        for var, edit in self.auto_line_edit.iteritems():
            if edit.isEnabled() and isinstance(var, basestring):
                val = unicode(edit.text()).strip()
                if val != u"":
                    self.set(var, val)

                # If the variable has no value, we assign a default value if it
                # has been specified, and unset it otherwise.
                elif hasattr(edit, u"default"):
                    self.set(var, edit.default)
                else:
                    self.unset(var)

        for var, combobox in self.auto_combobox.iteritems():
            if combobox.isEnabled() and isinstance(var, basestring):
                self.set(var, unicode(combobox.currentText()))

        for var, spinbox in self.auto_spinbox.iteritems():
            if spinbox.isEnabled() and isinstance(var, basestring):
                self.set(var, spinbox.value())

        for var, slider in self.auto_slider.iteritems():
            if slider.isEnabled() and isinstance(var, basestring):
                self.set(var, slider.value())

        for var, checkbox in self.auto_checkbox.iteritems():
            if checkbox.isEnabled() and isinstance(var, basestring):
                if checkbox.isChecked():
                    val = u"yes"
                else:
                    val = u"no"
                self.set(var, val)

        for var, qprogedit in self.auto_editor.iteritems():
            if isinstance(var, basestring):
                self.set(var, qprogedit.text())

        return True

    def auto_add_widget(self, widget, var=None):
        """
		Add a widget to the list of auto-widgets

		Arguments:
		widget -- a QWidget

		Keyword arguments:
		var -- the variable to be linked to the widget (default=None)
		"""

        # Use the object id as a fallback name
        if var == None:
            var = id(widget)
        debug.msg(var)

        if isinstance(widget, QtGui.QSpinBox) or isinstance(widget, \
         QtGui.QDoubleSpinBox):
            widget.editingFinished.connect(self.apply_edit_changes)
            self.auto_spinbox[var] = widget

        elif isinstance(widget, QtGui.QComboBox):
            widget.currentIndexChanged.connect(self.apply_edit_changes)
            self.auto_combobox[var] = widget

        elif isinstance(widget, QtGui.QSlider):
            widget.editingFinished.connect(self.apply_edit_changes)
            self.auto_slider[var] = widget

        elif isinstance(widget, QtGui.QLineEdit):
            widget.editingFinished.connect(self.apply_edit_changes)
            self.auto_line_edit[var] = widget

        elif isinstance(widget, QtGui.QCheckBox):
            widget.toggled.connect(self.apply_edit_changes)
            self.auto_checkbox[var] = widget

        else:
            raise Exception(u"Cannot auto-add widget of type %s" % widget)

    def clean_cond(self, cond, default=u'always'):
        """
		Cleans a conditional statement. May raise a dialog box if problems are
		encountered.

		Arguments:
		cond	--	A (potentially filthy) conditional statement.

		Keyword arguments:
		default	--	A default value to use for empty

		Returns:
		cond	--	A clean conditional statement conditional statements.
					(default=u'always')
		"""

        cond = self.unistr(cond)
        if not self.sanitize_check(cond):
            cond = self.sanitize(cond)
        if cond.strip() == u'':
            cond = default
        try:
            self.compile_cond(cond)
        except osexception as e:
            self.experiment.notify( \
             u'Failed to compile conditional statement "%s": %s' % (cond, e))
        return cond
Example #3
0
class qtitem(QtCore.QObject):

	"""Base class for the GUI controls of other items"""

	def __init__(self):

		"""Constructor"""

		QtCore.QObject.__init__(self)

		# The auto-widgets are stored in name -> (var, widget) dictionaries
		self.auto_line_edit = {}
		self.auto_combobox = {}
		self.auto_spinbox = {}
		self.auto_slider = {}
		self.auto_editor = {}
		self.auto_checkbox = {}
		self.sanity_criteria = {}

		self.init_edit_widget()
		self.init_script_widget()
		self.script_tab = None
		self.lock = False
		self.edit_mode = u'edit'

		debug.msg(u'created %s' % self.name)

	def open_help_tab(self, page=None):

		"""Opens a help tab."""
		
		self.experiment.main_window.ui.tabwidget.open_help(self.item_type)

	def open_tab(self):

		"""Opens the correct tab based on the current edit mode"""

		if self.edit_mode == u'edit':
			self.open_edit_tab()
		else:
			self.open_script_tab()

	def init_edit_widget(self, stretch=True):

		"""Build the GUI controls"""

		# Header widget
		self.header = header_widget.header_widget(self)
		self.user_hint_widget = user_hint_widget.user_hint_widget( \
			self.experiment.main_window, self)
		self.header_hbox = QtGui.QHBoxLayout()
		self.header_hbox.addWidget(self.experiment.label_image(self.item_type))
		self.header_hbox.addWidget(self.header)
		self.header_hbox.addStretch()
		self.header_hbox.setContentsMargins(0, 0, 0, 16)

		# Edit script button
		button = QtGui.QPushButton(self.experiment.icon(u"script"), u"")
		button.setToolTip(_(u"Edit script"))
		button.setIconSize(QtCore.QSize(16, 16))
		QtCore.QObject.connect(button, QtCore.SIGNAL(u"clicked()"), \
			self.open_script_tab)
		self.header_hbox.addWidget(button)

		# Help button
		button = QtGui.QPushButton(self.experiment.icon(u"help"), u"")
		button.setToolTip(_(u"Tell me more about the %s item") % self.item_type)
		button.setIconSize(QtCore.QSize(16, 16))
		QtCore.QObject.connect(button, QtCore.SIGNAL(u"clicked()"), \
			self.open_help_tab)
		self.header_hbox.addWidget(button)

		self.header_widget = QtGui.QWidget()
		self.header_widget.setLayout(self.header_hbox)

		# The edit_grid is the layout that contains the actual controls for the
		# items.
		self.edit_grid = QtGui.QGridLayout()
		self.edit_grid.setColumnStretch(2, 2)
		self.edit_grid_widget = QtGui.QWidget()
		self.edit_grid.setMargin(0)
		self.edit_grid_widget.setLayout(self.edit_grid)

		# The edit_vbox contains the edit_grid and the header widget
		self.edit_vbox = QtGui.QVBoxLayout()
		self.edit_vbox.setMargin(16)
		self.edit_vbox.addWidget(self.header_widget)
		self.edit_vbox.addWidget(self.user_hint_widget)
		self.edit_vbox.addWidget(self.edit_grid_widget)		
		if stretch:
			self.edit_vbox.addStretch()
		self._edit_widget = QtGui.QWidget()
		self._edit_widget.setLayout(self.edit_vbox)
		self._edit_widget.__edit_item__ = self.name

		return self._edit_widget

	def edit_widget(self, stretch=True):

		"""
		A dummy edit widget, to be overridden.

		Keywords arguments:
		stretch		--	DEPRECATED (default=True)
		"""

		if not stretch:
			debug.msg(u"passing the stretch argument is deprecated", \
				reason=u"deprecation")
		self.user_hint_widget.clear()
		self.header.restore_name(False)
		self.header.refresh()
		self._edit_widget.__edit_item__ = self.name
		if not self.sanity_check():
			self.open_script_tab()
			return
		self.auto_edit_widget()
		self.user_hint_widget.refresh()
		return self._edit_widget

	def apply_name_change(self, rebuild=True):

		"""
		Apply an item name change

		Keywords arguments:
		rebuild -- a deprecated argument (default=True)
		"""

		debug.msg()

		# Sanitize the name, check if it is new and valid, and if so, rename
		new_name = self.experiment.sanitize(self.header.edit_name.text(), \
			strict=True, allow_vars=False)
		if new_name.lower() != self.name.lower():
			valid = self.experiment.check_name(new_name)
			if valid != True:
				self.experiment.notify(valid)
				self.header.edit_name.setText(self.name)
				return
		old_name = self.name
		self.name = new_name
		self._edit_widget.__edit_item__	= new_name
		self.experiment.main_window.dispatch.event_name_change.emit(old_name, \
			new_name)

	def apply_edit_changes(self, rebuild=True):

		"""
		Applies the GUI controls.

		Keywords arguments:
		rebuild	--	Specifies whether the overview area (item list) should be
					rebuild. (default=True)
		"""

		debug.msg(self.name)
		if self.experiment.main_window.lock_refresh:
			debug.msg(u"skipping, because refresh in progress")
			return False
		self.auto_apply_edit_changes()
		self.set(u"description", \
			self.experiment.sanitize(unicode( \
				self.header.edit_desc.text()).strip()))
		if self.description == u"":
			self.description = u"No description"
		self.header.label_desc.setText(self.description)
		self.experiment.main_window.dispatch.event_simple_change.emit(self.name)
		return True

	def close_edit_tab(self, index=None):

		"""
		Closes the edit tab (does nothing by default).

		Keywords arguments:
		index	--	The index of the tab in the tab area. (default=None)
		"""

		pass

	def open_edit_tab(self, index=None, focus=True):

		"""
		Opens the GUI control tab, or switches to the tab if it was already
		open.

		Keywords arguments:
		index	--	The index of the tab (if open). (default=None)
		focus	--	Indicates whether the tab should receive focus.
					(default=True)
		"""

		debug.msg(u"%s (#%s)" % (self.name, hash(self)))

		# Switch to edit mode and close the script tab if it was open
		self.edit_mode = u"edit"
		for i in range(self.experiment.ui.tabwidget.count()):
			w = self.experiment.ui.tabwidget.widget(i)
			if hasattr(w, u"__script_item__") and w.__script_item__ == \
				self.name:
				self.experiment.ui.tabwidget.removeTab(i)
				if index == None:
					index = i
				break

		# Focus the edit tab, instead of reopening, if it was already open
		for i in range(self.experiment.ui.tabwidget.count()):
			w = self.experiment.ui.tabwidget.widget(i)
			if hasattr(w, u"__edit_item__") and w.__edit_item__ == self.name:
				index = i

		# Refresh the controls on the tab. In debug mode don't catch any errors
		if debug.enabled:
			widget = self.edit_widget()
		else:
			try:
				widget = self.edit_widget()
			except Exception as e:
				self.experiment.notify(_(u"%s (Edit the script to fix this)") \
					% e)
				self.open_script_tab()
				return

		# Open the tab or focus the tab if it was already open
		if index == None:
			self.edit_tab_index = self.experiment.ui.tabwidget.addTab(widget, \
				self.experiment.icon(self.item_type), u"%s" % self.name)
		else:
			self.experiment.ui.tabwidget.insertTab(index, widget, \
				self.experiment.icon(self.item_type), u"%s" % self.name)
			self.edit_tab_index = index
		if focus:
			self.experiment.ui.tabwidget.setCurrentIndex(self.edit_tab_index)

	def apply_script_and_close(self):

		"""Applies script changes and opens the edit tab"""

		self.apply_script_changes()
		self.experiment.main_window.select_item(self.name)

	def apply_script_changes(self, rebuild=True, catch=True):

		"""
		Apply changes to the script, by regenerating the item from the script

		Keywords arguments:
		rebuild -- specifies whether the overview area (item list) should be
				   rebuild (default=True)
		catch -- indicates if exceptions should be caught and shown in a
				 notification dialog (True) or not be caught (False)
				 (default=True)
		"""

		debug.msg(self.name)
		script = self.script_qprogedit.text()
		# Create a new item and make it a clone of the current item
		item = self.experiment.main_window.add_item(self.item_type, False, \
			name=self.name, interactive=False)
		if catch:
			try:
				self.experiment.items[item].from_string(script)
			except Exception as e:
				self.experiment.notify(unicode(e))
				return
		else:
			self.experiment.items[item].from_string(script)
		self.experiment.items[item].name = self.name
		# Replace the current item
		self.experiment.items[self.name] = self.experiment.items[item]
		del self.experiment.items[item]
		self.experiment.items[self.name].init_script_widget()
		self.experiment.main_window.dispatch.event_script_change.emit(self.name)

	def strip_script_line(self, s):

		"""
		Strips unwanted characters from a line of script

		Arguments:
		s -- a line of script

		Returns:
		A stripped line of script
		"""

		if len(s) > 0 and s[0] == u"\t":
			return s[1:] + u"\n"
		return s + u"\n"

	def init_script_widget(self):

		"""Build the script tab"""

		from QProgEdit import QTabManager
		self.script_qprogedit = QTabManager(handler=self.apply_script_and_close, \
			defaultLang=u'OpenSesame', handlerButtonText= \
			_(u'Apply and close script editor'), callHandlerOnFocusOut=False,
			cfg=cfg)
		self.script_qprogedit.addTab(u'Script')
		
		hbox = QtGui.QHBoxLayout()
		hbox.addWidget(self.experiment.label_image(u"%s" % self.item_type))
		self.script_header = QtGui.QLabel()			
		hbox.addWidget(self.script_header)
		hbox.addStretch()
		hbox.setContentsMargins(0,0,0,0)
		hwidget = QtGui.QWidget()
		hwidget.setLayout(hbox)

		vbox = QtGui.QVBoxLayout()
		vbox.addWidget(hwidget)
		vbox.addWidget(self.script_qprogedit)
		self._script_widget = QtGui.QWidget()
		self._script_widget.setLayout(vbox)
		self._script_widget.__script_item__ = self.name

	def script_widget(self):

		"""
		Update the script tab

		Returns:
		The QWidget containing the script tab
		"""

		self.script_header.setText( \
			_(u"Editing script for <b>%s</b> - %s") % (self.name, \
			self.item_type))
		script = u""
		for s in self.to_string().split(u"\n")[1:]:
			script += self.strip_script_line(s)
		self.script_qprogedit.setText(script)
		self._script_widget.__script_item__ = self.name
		return self._script_widget

	def open_script_tab(self, index=None, focus=True):

		"""
		Open/ show the script tab

		Keywords arguments:
		index -- the index of the tab (if it is already open) (default=None)
		focus -- indicates whether the tab should receive focus (default=True)
		"""

		debug.msg(u"%s (#%s)" % (self.name, hash(self)))
		self.edit_mode = u"script"

		# Close the edit tab
		for i in range(self.experiment.ui.tabwidget.count()):
			w = self.experiment.ui.tabwidget.widget(i)
			if hasattr(w, u"__edit_item__") and w.__edit_item__ == self.name:
				self.experiment.ui.tabwidget.removeTab(i)
				if index == None:
					index = i
				break

		for i in range(self.experiment.ui.tabwidget.count()):
			w = self.experiment.ui.tabwidget.widget(i)
			if hasattr(w, u"__script_item__") and w.__script_item__ == self.name:
				index = i
		if index == None:
			self.script_tab_index = self.experiment.ui.tabwidget.addTab( \
				self.script_widget(), self.experiment.icon(u"script"), u"%s" \
				% self.name)
		else:
			self.script_tab_index = index
			self.experiment.ui.tabwidget.insertTab(index, \
				self.script_widget(), self.experiment.icon(u"script"), u"%s" \
				% self.name)
		if focus:
			self.experiment.ui.tabwidget.setCurrentIndex(self.script_tab_index)

	def close_script_tab(self, index=None):

		"""
		Close the script tab (does nothing by defaut)

		Keywords arguments:
		index -- the index of the tab in the tab area (default=None)
		"""

		pass

	def rename(self, from_name, to_name):

		"""
		Handle the renaming of an item (not necesarrily the currnet item)

		Arguments:
		from_name -- the old item name
		to_name -- the new item name
		"""

		if self.name == from_name:
			self.name = to_name

	def delete(self, item_name, item_parent=None, index=None):

		"""
		Delete an item (not necessarily the current one)

		Arguments:
		item_name -- the name of the item to be deleted

		Keywords arguments:
		item_parent -- the parent item (default=None)
		index -- the index of the item in the parent (default=None)
		"""

		pass

	def rename_var(self, item, from_name, to_name):

		"""
		A notification that a variable has been renamed

		Arguments:
		item -- the item doing the renaming
		from_name -- the old variable name
		to_name -- the new variable name
		"""

		pass

	def item_tree_widget(self, toplevel, icon=None, name=None, tooltip=None):

		"""
		Create a single item tree widget

		Arguments:
		toplevel -- the toplevel item

		Keyword arguments:
		icon -- an icon name or None for default (default=None)
		name -- the name of the item or None for default (default=None)
		tooltip -- the tooltip or None for default (default=None)

		Returns:
		A QTreeWidgetItem
		"""

		if name == None:
			name = self.name
		if icon == None:
			icon = self.item_type
		if tooltip == None:
			tooltip = _(u"Type: %s\nDescription: %s") % (self.item_type, \
				self.description)
		font = QtGui.QFont()
		font.setPointSize(8)
		font.setItalic(True)
		widget = QtGui.QTreeWidgetItem(toplevel)
		widget.setText(0, name)
		widget.setIcon(0, self.experiment.icon(icon))
		widget.name = name
		widget.setToolTip(0, tooltip)
		return widget

	def build_item_tree(self, toplevel=None, items=[]):

		"""
		Construct an item tree

		Keyword arguments:
		toplevel -- the toplevel widget (default = None)
		items -- a list of items that have been added, to prevent recursion
				 (default=[])
		"""

		toplevel.addChild(self.item_tree_widget(toplevel))

	def is_offspring(self, item):

		"""
		Checks if the item is offspring of the current item, in the sense that
		the current item is contained by the item

		Arguments:
		item -- the potential offspring

		Returns:
		True if the current item is offspring of the item, False otherwise
		"""

		return False

	def parents(self):

		"""
		Creates a list of all the items	that the current sequences is connected
		to upstream

		Returns:
		A list of item names
		"""

		l = [self.name]
		for item in self.experiment.items:
			if self.experiment.items[item].is_offspring(self.name):
				l.append(item)
		return l

	def variable_vars(self, exclude=[]):

		"""
		Determines if one of the variables of the current item is defined in
		terms of another variable

		Keywords arguments:
		exclude -- a list of variables that should not be checked

		Returns:
		True if there are variably defined variables, False otherwise
		"""

		for var in self.variables:
			if var not in exclude:
				val = self.variables[var]
				if isinstance(val, basestring) and u'[' in val:
					return True
		return False

	def get_ready(self):

		"""
		This function should be overridden to do any last-minute stuff that
		and item should do before an experiment is actually run, such as
		applying pending script changes.

		Returns:
		True if some action has been taken, False if nothing was done
		"""

		if self.script_qprogedit.isModified():
			debug.msg(u'applying pending script changes')
			self.apply_script_changes(catch=False)
			return True
		return False		
		
	def auto_edit_widget(self):

		"""Update the GUI controls based on the auto-widgets"""

		debug.msg()
		for var, edit in self.auto_line_edit.iteritems():
			edit.editingFinished.disconnect()
			if self.has(var):
				try:
					edit.setText(self.unistr(self.get(var, _eval=False)))
				except Exception as e:
					self.experiment.notify(_(u"Failed to set control '%s': %s") \
						% (var, e))
			else:
				edit.setText(u"")
			edit.editingFinished.connect(self.apply_edit_changes)

		for var, combobox in self.auto_combobox.iteritems():
			combobox.currentIndexChanged.disconnect()
			if self.has(var):
				try:
					combobox.setCurrentIndex(combobox.findText( \
						self.unistr(self.get(var, _eval=False))))
				except Exception as e:
					self.experiment.notify(_(u"Failed to set control '%s': %s") \
						% (var, e))
			combobox.currentIndexChanged.connect(self.apply_edit_changes)

		for var, spinbox in self.auto_spinbox.iteritems():
			spinbox.editingFinished.disconnect()
			if self.has(var):
				val = self.get(var, _eval=False)
				if type(val) in (float, int):						
					try:
						spinbox.setValue(val)
					except Exception as e:
						self.experiment.notify(_( \
							u"Failed to set control '%s': %s") % (var, e))
				else:
					spinbox.setDisabled(True)
					self.user_hint_widget.add_user_hint(_( \
						u'"%s" is defined using variables and can only be edited through the script.' \
						% var))
			spinbox.editingFinished.connect(self.apply_edit_changes)			

		for var, slider in self.auto_slider.iteritems():
			slider.valueChanged.disconnect()
			if self.has(var):
				val = self.get(var, _eval=False)
				if type(val) in (float, int):	
					try:
						slider.setValue(val)
					except Exception as e:
						self.experiment.notify(_( \
							u"Failed to set control '%s': %s") % (var, e))						
				else:
					slider.setDisabled(True)
					self.user_hint_widget.add_user_hint(_( \
						u'"%s" is defined using variables and can only be edited through the script.' \
						% var))
			slider.valueChanged.connect(self.apply_edit_changes)

		for var, checkbox in self.auto_checkbox.iteritems():
			checkbox.toggled.disconnect()
			if self.has(var):
				try:
					checkbox.setChecked(self.get(var, _eval=False) == u"yes")
				except Exception as e:
					self.experiment.notify(_(u"Failed to set control '%s': %s") \
						% (var, e))
			checkbox.toggled.connect(self.apply_edit_changes)

		for var, qprogedit in self.auto_editor.iteritems():
			if self.has(var):
				try:
					qprogedit.setText(self.unistr(self.get(var, _eval=False)))
				except Exception as e:
					self.experiment.notify(_(u"Failed to set control '%s': %s") \
						% (var, e))

	def sanitize_check(self, s, strict=False, allow_vars=True, notify=True):

		"""
		Checks whether a string is sane (i.e. unchanged by sanitize()) and
		optionally presents a warning.

		Arguments:
		s			--	The string to check.

		Keyword arguments:
		strict		--	See sanitize().
		allow_vars	--	See sanitize().
		notify		--	Indicates whether a notification should be presented if
						the string is not sane.

		Returns:
		True if s is sane, False otherwise.
		"""

		sane = s == self.sanitize(s, strict=strict, allow_vars=allow_vars)
		if not sane and notify:
			if strict:
				self.experiment.notify(
					_(u'All non-alphanumeric characters except underscores have been stripped'))
			else:
				self.experiment.notify(
					_(u'The following characters are not allowed and have been stripped: double-quote ("), backslash (\), and newline'))
		return sane

	def sanity_check(self):

		"""
		Checks whether all variables match prespecified criteria and fall back
		to the script editor otherwise. This is usefull to check that certain
		variables are numeric, etc.
		"""

		debug.msg()
		errors = []
		for var_name, criteria in self.sanity_criteria.items():
			msg = _(u"Invalid or missing value for variable '%s' (edit script to fix this)") \
				% var_name
			if u'msg' in criteria:
				msg += u': ' + criteria[u'msg']
			if not self.has(var_name) and u'required' in criteria and \
				criteria[u'required']:
				self.experiment.notify(msg)
				return False
			else:
				var = self.get(var_name, _eval=False)
				if u'type' in criteria:
					_type = criteria[u'type']
					if type(_type) != list:
						_type = [_type]
				 	if type(var) not in _type:
						self.experiment.notify(msg)
						return False
				if u'func' in criteria:
					if not criteria[u'func'](var):
						self.experiment.notify(msg)
						return False
		return True

	def auto_apply_edit_changes(self, rebuild=True):

		"""
		Apply the auto-widget controls

		Keyword arguments:
		rebuild -- deprecated (does nothing) (default=True)
		"""

		debug.msg()
		for var, edit in self.auto_line_edit.iteritems():
			if edit.isEnabled() and isinstance(var, basestring):
				val = unicode(edit.text()).strip()
				if val != u"":
					self.set(var, val)

				# If the variable has no value, we assign a default value if it
				# has been specified, and unset it otherwise.
				elif hasattr(edit, u"default"):
					self.set(var, edit.default)
				else:
					self.unset(var)

		for var, combobox in self.auto_combobox.iteritems():
			if combobox.isEnabled() and isinstance(var, basestring):
				self.set(var, unicode(combobox.currentText()))

		for var, spinbox in self.auto_spinbox.iteritems():
			if spinbox.isEnabled() and isinstance(var, basestring):
				self.set(var, spinbox.value())

		for var, slider in self.auto_slider.iteritems():
			if slider.isEnabled() and isinstance(var, basestring):
				self.set(var, slider.value())

		for var, checkbox in self.auto_checkbox.iteritems():
			if checkbox.isEnabled() and isinstance(var, basestring):
				if checkbox.isChecked():
					val = u"yes"
				else:
					val = u"no"
				self.set(var, val)
				
		for var, qprogedit in self.auto_editor.iteritems():
			if isinstance(var, basestring):
				self.set(var, qprogedit.text())

		return True

	def auto_add_widget(self, widget, var=None):

		"""
		Add a widget to the list of auto-widgets

		Arguments:
		widget -- a QWidget

		Keyword arguments:
		var -- the variable to be linked to the widget (default=None)
		"""

		# Use the object id as a fallback name
		if var == None:
			var = id(widget)
		debug.msg(var)

		if isinstance(widget, QtGui.QSpinBox) or isinstance(widget, \
			QtGui.QDoubleSpinBox):
			widget.editingFinished.connect(self.apply_edit_changes)
			self.auto_spinbox[var] = widget

		elif isinstance(widget, QtGui.QComboBox):
			widget.currentIndexChanged.connect(self.apply_edit_changes)
			self.auto_combobox[var] = widget

		elif isinstance(widget, QtGui.QSlider):
			widget.editingFinished.connect(self.apply_edit_changes)
			self.auto_slider[var] = widget

		elif isinstance(widget, QtGui.QLineEdit):
			widget.editingFinished.connect(self.apply_edit_changes)
			self.auto_line_edit[var] = widget

		elif isinstance(widget, QtGui.QCheckBox):
			widget.toggled.connect(self.apply_edit_changes)
			self.auto_checkbox[var] = widget

		else:
			raise Exception(u"Cannot auto-add widget of type %s" % widget)
Example #4
0
class inline_script(libopensesame.inline_script.inline_script, qtitem.qtitem):
    """The inline_script GUI controls"""
    def __init__(self, name, experiment, string=None):
        """
		Constructor.

		Arguments:
		name 		--	The item name.
		experiment	--	The experiment object.

		Keywords arguments:
		string		--	A definition string. (default=None)
		"""

        libopensesame.inline_script.inline_script.__init__(self, name, \
         experiment, string)
        qtitem.qtitem.__init__(self)
        self.lock = False
        self._var_info = None

    def apply_edit_changes(self, **args):
        """
		Applies the controls.

		Keywords arguments:
		args	--	A dictionary to accept unused keyword arguments.
		"""

        qtitem.qtitem.apply_edit_changes(self, False)
        sp = self.qprogedit.text(index=0)
        sr = self.qprogedit.text(index=1)
        self.set(u'_prepare', sp)
        self.set(u'_run', sr)
        self.lock = True
        self._var_info = None
        self.experiment.main_window.refresh(self.name)
        self.lock = False

    def init_edit_widget(self):
        """Constructs the GUI controls."""

        from QProgEdit import QTabManager
        qtitem.qtitem.init_edit_widget(self, False)
        self.qprogedit = QTabManager(handler=self.apply_edit_changes, \
         defaultLang=u'Python', cfg=cfg, focusOutHandler= \
         self.apply_edit_changes)
        self.qprogedit.addTab(u'Prepare')
        self.qprogedit.addTab(u'Run')
        # Switch to the run phase, unless there is only content for the prepare
        # phase.
        if self._run == u'' and self._prepare != u'':
            self.qprogedit.setCurrentIndex(0)
        else:
            self.qprogedit.setCurrentIndex(1)
        self.edit_vbox.addWidget(self.qprogedit)

    def edit_widget(self):
        """
		Updates the GUI controls.

		Returns:
		The control QWidget.
		"""

        qtitem.qtitem.edit_widget(self, False)
        if not self.lock:
            self.qprogedit.setText(self._prepare, index=0)
            self.qprogedit.setText(self._run, index=1)
        return self._edit_widget

    def get_ready(self):
        """Applies pending script changes."""

        if self.qprogedit.isModified():
            debug.msg(u'applying pending script changes')
            self.apply_edit_changes(catch=False)
            return True
        return qtitem.qtitem.get_ready(self)