def add_cyclevar(self): """Presents a dialog and add a variable,""" var_name, ok = QtGui.QInputDialog.getText(self.loop_table, _(u'New variable'), _(u'Enter a variable name, optionally followed by a default value (i.e., \"varname defaultvalue\")')) if not ok: return l = self.cyclevar_list() var_name = str(var_name) # Split by space, because a name may be followed by a default value _l = var_name.split() if len(_l) > 1: default = _l[1] var_name = _l[0] else: default = u"" if not self.name_valid_and_unique(var_name): return for i in range(self.var.cycles): if i not in self.matrix: self.matrix[i] = {} self.matrix[i][var_name] = default self.refresh_loop_table() self.apply_edit_changes()
def name_valid_and_unique(self, name): """ desc: Checks if a variable name is syntactically valid and unique. arguments: name: The name to check. returns: True if the name is valid and unique, False otherwise. """ if not self.syntax.valid_var_name(name): self.experiment.notify( _(u'"%s" is not a valid variable name. Valid names consist of ' u'letters, numbers, and underscores, and do not start with a ' u'number.') % name) return False var_list = self.cyclevar_list() if var_list is not None and name in var_list: self.experiment.notify( _(u"A variable with the name '%s' already exists") % \ name) return False return True
def purge_unused(self): """Remove all unused items from the items list""" # Ask confirmation resp = QtGui.QMessageBox.question( self.main_window.ui.centralwidget, _(u"Permanently delete items?"), _(u"Are you sure you want to permanently delete all unused items? This action cannot be undone."), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No, ) if resp == QtGui.QMessageBox.No: return # We need a loop, because items may become unused # by deletion of their unused parent items while len(self.main_window.experiment.unused_items) > 0: for item in self.main_window.experiment.unused_items: if item in self.main_window.experiment.items: del self.main_window.experiment.items[item] self.main_window.experiment.build_item_tree() # Notify dispatch self.main_window.dispatch.event_structure_change.emit(u"") self.main_window.ui.tabwidget.close_all() self.main_window.ui.tabwidget.open_general()
def _apply(self): """ desc: Confirms and applies the script changes. """ resp = QtGui.QMessageBox.question(self.main_window, _(u'Apply?'), _(u'Are you sure you want to apply the changes to the general script?'), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if resp == QtGui.QMessageBox.No: return try: exp = experiment(self.main_window, name=self.experiment.title, string=self.ui.qprogedit.text(), pool_folder=self.experiment.pool_folder, experiment_path=self.experiment.experiment_path, resources=self.experiment.resources) except osexception as e: self.notify(e.html()) self.main_window.print_debug_window(e) return self.main_window.experiment = exp self.main_window.tabwidget.close_all() self.main_window.tabwidget.open_general() self.experiment.build_item_tree() self.extension_manager.fire(u'regenerate')
def contextMenuEvent(self, event): """ desc: Presents a context menu. arguments: event: type: QMouseClickEvent """ item = self.ui.list_pool.itemAt(self.ui.list_pool.mapFromGlobal( event.globalPos())) if item is None: return path = item.path pm = popup_menu(self, [ (0, _(u'Open'), item.icon), (1, _(u'Remove from pool'), u'delete'), (2, _(u'Rename'), u'rename'), ]) action = pm.show() if action == 0: self.open_file(path) elif action == 1: self.delete_selected_files() elif action == 2: self.ui.list_pool.editItem(item)
def set_cycle_count(self, cycles, confirm=True): """ Sets the nr of cycles and truncates data if necessary. Arguments: cycles -- The number of cycles. Keyword arguments: confirm -- Indicates whether confirmation is required before data is removed from the table. (default=True) """ if self.lock_cycles: return self.lock_cycles = True debug.msg(u"cycles = %s" % cycles) # Ask for confirmation before reducing the number of cycles. if len(self.matrix) > cycles and confirm: resp = QtGui.QMessageBox.question(self.experiment.ui.centralwidget, _(u"Remove cycles?"), _(u"By reducing the number of cycles, data will be lost from the table. Do you wish to continue?"), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if resp == QtGui.QMessageBox.No: self.loop_widget.ui.spin_cycles.setValue(self.cycles) self.lock_cycles = False return for i in self.matrix.keys(): if i >= cycles: del self.matrix[i] self.lock_cycles = False self.set(u"cycles", cycles)
def closeEvent(self, e): """ desc: Process a request to close the application. arguments: e: type: QCloseEvent """ if debug.enabled or self.devmode: libopensesame.experiment.clean_up(debug.enabled) self.save_state() e.accept() return resp = QtGui.QMessageBox.question(self.ui.centralwidget, _(u"Quit?"), _(u"Are you sure you want to quit OpenSesame?"), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if resp == QtGui.QMessageBox.No: if not isinstance(e, bool): e.ignore() return if not self.save_unsaved_changes(): e.ignore() return self.extension_manager.fire(u'close') self.save_state() libopensesame.experiment.clean_up(debug.enabled) e.accept()
def __init__(self, main_window): """ desc: Constructor. arguments: main_window: The main-window object. """ self.setup(main_window) self.main_window.set_status(_(u'Loading extensions'), timeout=None, status=u'busy') QtGui.QApplication.processEvents() self._extensions = [] self.events = {} for ext_name in plugins.list_plugins(_type=u'extensions'): try: ext = plugins.load_extension(ext_name, self.main_window) except Exception as e: if not isinstance(e, osexception): e = osexception(msg=u'Extension error', exception=e) self.notify( u'Failed to load extension %s (see debug window for stack trace)' \ % ext_name) self.main_window.print_debug_window(e) self._extensions.append(ext) for event in ext.supported_events(): if event not in self.events: self.events[event] = [] self.events[event].append(ext) self.main_window.set_status(_(u'Done'), status=u'ready')
def permanently_delete(self): """ desc: Permanently deletes the item, if possible. """ if not self.is_deletable() and not self.is_unused(): return if QtGui.QMessageBox.question(self.treeWidget(), _(u'Permanently delete item'), _(u'Are you sure you want to permanently delete <b>%s</b>? All linked copies of <b>%s</b> will be deleted. You will not be able to undo this.') \ % (self.name, self.name), buttons=(QtGui.QMessageBox.Yes|QtGui.QMessageBox.No), defaultButton=QtGui.QMessageBox.No) \ != QtGui.QMessageBox.Yes: return del self.item_store[self.name] self.close_tab() try: # Sometimes the treeWidget has already been deleted, in which case # this gives an Exception. But it when it hasn't been deleted, a # structure_change has to be emitted, otherwise the GUI doesn't # update properly. self.treeWidget().structure_change.emit() except: pass
def __init__(self, main_window): super(backend_settings, self).__init__(main_window, ui=u'widgets.backend_settings') self.tab_name = u'__backend_settings__' for backend_type in backend._backend_types: try: _backend = backend.get_backend_class(self.experiment, backend_type) except: _backend = None layout = getattr(self.ui, u'layout_%s' % backend_type) label = getattr(self.ui, u'label_%s' % backend_type) # Horribly ugly way to clear the previous settings while layout.count() > 1: w = layout.itemAt(1) layout.removeItem(w) w.widget().hide() sip.delete(w) if _backend is None: label.setText(_(u"Failed to load backend")) elif not hasattr(_backend, u"settings") or _backend.settings == \ None: label.setText(_(u"No settings for %s") % _backend.__name__) else: label.setText(_(u"Settings for %s:") % _backend.__name__) layout.addWidget(settings_widget(self.main_window, _backend.settings))
def copy_to_pool(self, fname): """ Copy a file to the file pool Arguments: fname -- full path to file """ import shutil renamed = False _fname = os.path.basename(fname) while os.path.exists(os.path.join(self.experiment.pool_folder, _fname)): _fname = u"_" + _fname renamed = True if renamed: QtGui.QMessageBox.information(self.ui.centralwidget, \ _(u"File renamed"), \ _(u"The file has been renamed to '%s', because the file pool already contains a file named '%s'.") \ % (_fname, os.path.basename(fname))) shutil.copyfile(fname, os.path.join(self.experiment.pool_folder, _fname)) self.refresh_pool(True)
def select_from_pool(main_window): """ A static function that presents the select from pool dialog Arguments: main_window -- the GUI main window """ d = QtGui.QDialog(main_window.ui.centralwidget) widget = pool_widget(main_window) widget.refresh() bbox = QtGui.QDialogButtonBox(d) bbox.addButton(_("Cancel"), QtGui.QDialogButtonBox.RejectRole) bbox.addButton(_("Select"), QtGui.QDialogButtonBox.AcceptRole) bbox.accepted.connect(d.accept) bbox.rejected.connect(d.reject) vbox = QtGui.QVBoxLayout() vbox.addWidget(widget) vbox.addWidget(bbox) d.setLayout(vbox) d.setWindowTitle(_("Select file from pool")) res = d.exec_() main_window.refresh_pool() if res == QtGui.QDialog.Rejected: return "" selected = widget.ui.list_pool.currentItem() if selected == None: return "" return unicode(selected.text())
def __init__(self, tree_overview, target_treeitem=None): """ desc: Constructor. arguments: tree_overview: desc: The tree_overview with which this menu is associated. type: tree_overview keywords: target_treeitem: desc: The treewidget item which corresponds to the sequence to which the append operation should be applied. If `None`, the top-level item from the tree_overview is used. type: tree_item_item """ super(tree_append_menu, self).__init__(tree_overview) self.setup(tree_overview) self.target_treeitem = target_treeitem self.tree_overview = tree_overview self.action_new_items = QtGui.QAction(self.theme.qicon(u'list-add'), _(u'Append new item'), self) self.addAction(self.action_new_items) self.action_linked_copy = QtGui.QAction(self.theme.qicon(u'edit-copy'), _(u'Append existing item (linked)'), self) self.addAction(self.action_linked_copy) self.aboutToShow.connect(self.refresh) self.triggered.connect(self.append_item) self._new_items_menu = None
def __init__(self, item): """ desc: Constructor. arguments: item: desc: An item. type: qtitem. """ super(item_view_button, self).__init__(item.main_window) self.item = item self.setup(item) self.set_view_icon(u'controls') self.setIconSize(QtCore.QSize(16,16)) self.menu_view = QtGui.QMenu() self.menu_view.addAction(self.view_controls_icon(), _(u'View controls'), self.item.set_view_controls) self.menu_view.addAction(self.view_script_icon(), _(u'View script'), self.item.set_view_script) self.menu_view.addAction(self.view_split_icon(), _(u'Split view'), self.item.set_view_split) self.setMenu(self.menu_view) self.setToolTip(_(u'Select view'))
def rename_file(self, item): """ desc: Starts a rename action for an item. arguments: item: type: QListWidgetItem """ old_name = item.path new_name = item.text().strip() if new_name in self.pool: self.notify( _(u"There already is a file named '%s' in the file pool") \ % new_name) new_name = old_name elif new_name in (old_name, u''): new_name = old_name else: try: self.pool.rename(old_name, new_name) except: self.notify(_(u'Failed to rename "%s" to "%s".') \ % (old_name, new_name)) self.refresh() self.select(new_name)
def __init__(self, main_window): self.main_window = main_window QtGui.QWidget.__init__(self, main_window) self.ui = Ui_widget_backend_settings() self.ui.setupUi(self) self.main_window.theme.apply_theme(self) self.tab_name = '__backend_settings__' for backend_type in ["canvas", "keyboard", "mouse", "synth", \ "sampler"]: backend = self.main_window.experiment.get("%s_backend" \ % backend_type) backend_module = __import__(u'openexp._%s.%s' % (backend_type, \ backend), fromlist=[u'dummy']) _backend = getattr(backend_module, backend) group = getattr(self.ui, u'group_%s' % backend_type) layout = getattr(self.ui, u'layout_%s' % backend_type) label = getattr(self.ui, u'label_%s' % backend_type) # Horribly ugly way to clear the previous settings while layout.count() > 1: w = layout.itemAt(1) layout.removeItem(w) w.widget().hide() sip.delete(w) if not hasattr(_backend, "settings") or _backend.settings == \ None: label.setText(_("No settings for %s") % backend) else: label.setText(_("Settings for %s:") % backend) layout.addWidget(settings_widget( \ self.main_window.experiment, _backend.settings, self))
def show_context_menu(self, pos): """ desc: Shows a context menu for the element. arguments: pos: type: QPoint """ from libqtopensesame._input.popup_menu import popup_menu pm = popup_menu(self.main_window, [ (0, _(u'Edit script'), u'utilities-terminal'), (1, _(u'Raise to front'), u'go-top'), (2, _(u'Lower to bottom'), u'go-bottom'), (3, _(u'Delete'), u'edit-delete'), ]) resp = pm.show() if resp is None: return if resp == 0: self.show_script_edit_dialog() elif resp == 1: self.properties[u'z_index'] = self.sketchpad.min_z_index() - 1 elif resp == 2: self.properties[u'z_index'] = self.sketchpad.max_z_index() + 1 elif resp == 3: self.sketchpad.remove_elements([self]) self.sketchpad.draw()
def handle_exception(self, e): """ desc: Shows a summary when the experiment was aborted. arguments: e: desc: The Exception that caused the experiment to stop. type: Exception """ if not isinstance(e, osexception): e = osexception(msg=u'Unexpected error', exception=e) if e.user_triggered: icon = u'os-finished-user-interrupt' title = _(u'Aborted') md = _(u'# Aborted\n\n- ') + e.markdown() else: icon = u'os-finished-error' title = _(u'Stopped') md = _(u'# Stopped\n\nThe experiment did not finish normally for the ' u'following reason:\n\n- ') + e.markdown() self.console.write(e) self.tabwidget.open_markdown(md, icon, title)
def add(self, files, rename=False): """ desc: Adds a list of files to the pool. arguments: files: desc: A list of paths. type: list """ if not files: return for path in files: basename = os.path.basename(path) if self.pool.in_folder(basename): if rename: while self.pool.in_folder(basename): basename = u'_' + basename else: c = confirmation(self.main_window, _(u"A file named '%s' already exists in the pool. Do you want to overwrite this file?") \ % basename) if not c.show(): continue try: self.pool.add(path, new_name=basename) except IOError as e: self.notify(_(u'Failed to copy %s to file pool') % path) self.console.write(safe_decode(e, errors=u'ignore')) self.refresh() self.select(basename)
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 if it's notably 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( _('All non-alphanumeric characters except underscores have been stripped')) else: self.experiment.notify( _('The following characters are not allowed and have been stripped: double-quote ("), backslash (\), and newline')) return sane
def add_control(self, label, widget, tooltip=None, min_width=None): """ Adds a generic control QWidget. Arguments: label -- A text label. widget -- A control QWidget. Keyword arguments: tooltip -- A tooltip text. (default=None) min_width -- A minimum width for the widget. (default=None) """ if tooltip is not None: try: widget.setToolTip(_(tooltip, context=self.name)) except: pass if type(min_width) == int: widget.setMinimumWidth(min_width) row = self.edit_grid.rowCount() self.edit_grid.addWidget(QtGui.QLabel(_(label, context=self.name)), row, 0) self.edit_grid.addWidget(widget, row, 1) self.set_focus_widget(widget)
def on_success(self, quick=False): """ Is called when an experiment has successfully ended, and gives the user the opportunity to copy the logfile to the file pool. Keyword arguments: quick -- Indicates whether we are quickrunning the experiment. (default=False) """ if quick: return resp = QtGui.QMessageBox.question( self.main_window.ui.centralwidget, _(u"Finished!"), _( u"The experiment is finished and data has been logged to '%s'. Do you want to copy the logfile to the file pool?" ) % self.experiment.logfile, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No, ) if resp == QtGui.QMessageBox.Yes: self.main_window.copy_to_pool(self.experiment.logfile)
def add(self, files): """ Add a list of files to the pool. Arguments: files - A list of paths """ basename = "" for path in files: path = unicode(path) debug.msg(path) basename = os.path.basename(path) if not os.path.isfile(path): self.main_window.experiment.notify( \ "'%s' is not a regular file and could not be added to the file pool." \ % path) else: # If a similar file already exists in the pool, ask before overwriting if os.path.exists(os.path.join( \ self.main_window.experiment.pool_folder, basename)): resp = QtGui.QMessageBox.question(self, _("Overwrite"), \ _("A file named '%s' already exists in the pool. Do you want to overwrite this file?") \ % basename, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if resp == QtGui.QMessageBox.Yes: shutil.copyfile(path, os.path.join( \ self.main_window.experiment.pool_folder, basename)) else: shutil.copyfile(path, os.path.join( \ self.main_window.experiment.pool_folder, basename)) self.refresh() self.select(basename)
def __init__(self, main_window, overview_mode=True): """ desc: Constructor. arguments: main_window: desc: The main window object. type: qtopensesame keywords: overview_mode: desc: Indicates whether the tree should be overview-area style (True) or sequence style (False). type: int """ super(tree_overview, self).__init__(main_window) self.overview_mode = overview_mode self.setAcceptDrops(True) if self.overview_mode: self.setHeaderHidden(True) else: self.setHeaderHidden(False) self.setHeaderLabels([_(u'Item name'), _(u'Run if')]) self.setAlternatingRowColors(True) self.itemChanged.connect(self.text_edited) self.pending_drag_data = None self.drag_timer = None if not self.overview_mode: self.shortcut_edit_runif = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_edit_runif), self, self.start_edit_runif, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_rename = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_rename), self, self.start_rename, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_copy_item = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_copy_clipboard), self, self.copy_item, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_paste_item = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_paste_clipboard), self, self.paste_item, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_delete_item = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_delete), self, self.delete_item, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_delete_item = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_permanently_delete), self, self.permanently_delete_item, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_linked_copy = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_linked_copy), self, self.create_linked_copy, context=QtCore.Qt.WidgetWithChildrenShortcut) self.shortcut_unlinked_copy = QtGui.QShortcut( QtGui.QKeySequence(cfg.shortcut_unlinked_copy), self, self.create_unlinked_copy, context=QtCore.Qt.WidgetWithChildrenShortcut)
def drop_event_item_move(self, data, e): """ desc: Handles drop events for item moves. arguments: data: desc: A drop-data dictionary. type: dict: e: desc: A drop event. type: QDropEvent """ if not drag_and_drop.matches(data, [u"item-existing"]): e.ignore() return target_treeitem = self.itemAt(e.pos()) if not self.droppable(target_treeitem, data): debug.msg(u"Drop ignored: target not droppable") self.main_window.set_status(_(u"Drop cancelled: Target not droppable")) e.ignore() return # If the drop comes from this application, check for recursion etc. if data[u"application-id"] == self.main_window._id(): target_item_name, target_item_ancestry = target_treeitem.ancestry() item_name = data[u"item-name"] item = self.experiment.items[item_name] if target_item_name in item.children(): debug.msg(u"Drop ignored: recursion prevented") self.main_window.set_status(_(u"Drop cancelled: Recursion prevented")) e.ignore() return parent_item_name, index = self.parent_from_ancestry(data[u"ancestry"]) if parent_item_name == None: debug.msg(u"Drop ignored: no parent") e.ignore() return # The logic below is a bit complicated, but works as follows: # - If we're in a move action, remove the dragged item from its parent, # and set need_restore so that we know this happened. # - Try to drop the dragged item onto the target item # - If the drop action was unsuccesful, and if need_restore is set, # re-add the dragged item to its former parent. need_restore = False if not QtCore.Qt.ControlModifier & e.keyboardModifiers() and data[u"application-id"] == self.main_window._id(): if parent_item_name not in self.experiment.items: debug.msg(u"Don't know how to remove item from %s" % parent_item_name) else: self.locked = True need_restore = True self.experiment.items[parent_item_name].remove_child_item(item_name, index) self.locked = False if self.drop_event_item_new(data, e, target_treeitem=target_treeitem): return if need_restore: self.experiment.items[parent_item_name].insert_child_item(item_name, index) self.experiment.build_item_tree()
def drop_hint(self): if self.treeWidget().overview_mode or self.parent() == None: if self.item.item_type == u'loop': return _(u'Set as item to run for %s') % self.name if self.item.item_type == u'sequence': return _(u'Insert into %s') % self.name return _(u'Drop below %s') % self.name
def __init__(self, main_window, treeitem): """ desc: Constructor. arguments: main_window: desc: The main-window object. type: qtopensesame treeitem: desc: The tree item. type: tree_item_item """ super(item_context_menu, self).__init__(main_window) self.setup(main_window) self.treeitem = treeitem self.addAction(self.theme.qicon(self.item.item_type), _('Open'), self.item.open_tab) self.addSeparator() self.add_action(u"accessories-text-editor", _("Rename"), self.treeitem.start_rename, cfg.shortcut_rename) if not self.treewidget.overview_mode and self.treeitem.parent() is not None: self.add_action(u"accessories-text-editor", _("Edit run-if statement"), self.treeitem.start_edit_runif, cfg.shortcut_edit_runif) self.addSeparator() self.add_action(u"edit-copy", _("Copy (unlinked)"), self.treeitem.copy_unlinked, cfg.shortcut_copy_clipboard_unlinked) self.add_action(u"edit-copy", _("Copy (linked)"), self.treeitem.copy_linked, cfg.shortcut_copy_clipboard_linked) if self.treeitem.clipboard_data() is not None: self.add_action(u"edit-paste", _("Paste"), self.treeitem.paste, cfg.shortcut_paste_clipboard) if self.treeitem.is_deletable(): self.addSeparator() self.add_action(u"list-remove", _("Delete"), self.treeitem.delete, cfg.shortcut_delete) self.add_action(u"list-remove", _("Permanently delete all linked copies"), self.treeitem.permanently_delete, cfg.shortcut_permanently_delete) elif self.treeitem.is_unused(): self.addSeparator() self.add_action(u"list-remove", _("Permanently delete"), self.treeitem.permanently_delete, cfg.shortcut_permanently_delete) if self.treeitem.has_append_menu(): # An append menu for sequence items menu = tree_append_menu(self.treeitem.treeWidget(), self.treeitem) action = QtGui.QAction(self.theme.qicon(u'list-add'), u'Append item', self) action.setMenu(menu) self.addSeparator() self.addAction(action) self.addSeparator() self.add_action(u"help", _("Help"), self.item.open_help_tab)
def check_for_updates(self, always=True): import urllib if not always and not cfg.auto_update_check: debug.msg(u"skipping update check") return debug.msg(u"opening %s" % cfg.version_check_url) try: fd = urllib.urlopen(cfg.version_check_url) mrv = float(fd.read().strip()) except Exception as e: if always: self.update_dialog( \ _(u"... and is sorry to say that the attempt to check for updates has failed. Please make sure that you are connected to the internet and try again later. If this problem persists, please visit <a href='http://www.cogsci.nl/opensesame'>http://www.cogsci.nl/opensesame</a> for more information.")) return # The most recent version as downloaded is always a float. Therefore, we # must convert the various possible version numbers to analogous floats. # We do this by dividing each subversion number by 100. The only # exception is that prereleases should be counted as older than stable # releases, so for pre-release we substract one bugfix version. # 0.27 -> 0.27.0.0 -> 0.27 # 0.27.1 -> 0.27.1.0 -> 0.2701 # 0.27~pre1 -> 0.27.0.1 - .0001 -> 0.269901 # 0.27.1~pre1 -> 0.27.1.1 - .0001 -> 0.270001 v = self.main_window.version l = v.split(u"~pre") if len(l) == 2: lastSubVer = l[1] v = l[0] ver = -.0001 else: lastSubVer = 0 ver = .0 lvl = 0 fct = .01 for subVer in v.split(u'.') + [lastSubVer]: try: _subVer = int(subVer) except: debug.msg(u'Failed to process version segment %s' % subVer, \ reason=u'warning') return ver += fct**lvl * _subVer lvl += 1 debug.msg(u'identifying as version %s' % ver) debug.msg(u'latest stable version is %s' % mrv) if mrv > ver: self.update_dialog( \ _(u"... and is happy to report that a new version of OpenSesame (%s) is available at <a href='http://www.cogsci.nl/opensesame'>http://www.cogsci.nl/opensesame</a>!") % mrv) else: if always: self.update_dialog( \ _(u" ... and is happy to report that you are running the most recent version of OpenSesame."))
def init_edit_widget(self): """Construct the GUI controls""" qtitem.qtitem.init_edit_widget(self, False) tabwidget_script = QtGui.QTabWidget(self._edit_widget) py_ver = "Python %d.%d.%d" % (sys.version_info[0], \ sys.version_info[1], sys.version_info[2]) # Construct prepare editor self.textedit_prepare = inline_editor.inline_editor(self.experiment, \ notification=py_ver, syntax="python") self.textedit_prepare.apply.clicked.connect(self.apply_edit_changes) QtCore.QObject.connect(self.textedit_prepare.edit, QtCore.SIGNAL( \ "focusLost"), self.apply_edit_changes) hbox = QtGui.QHBoxLayout() hbox.addStretch() hbox.setContentsMargins(0, 0, 0, 0) hbox_widget = QtGui.QWidget() hbox_widget.setLayout(hbox) vbox = QtGui.QVBoxLayout() vbox.addWidget(self.textedit_prepare) vbox.addWidget(hbox_widget) widget = QtGui.QWidget() widget.setLayout(vbox) tabwidget_script.addTab(widget, self.experiment.icon("inline_script"), \ _("Prepare phase")) # Construct run editor self.textedit_run = inline_editor.inline_editor(self.experiment, \ notification=py_ver, syntax="python") self.textedit_run.apply.clicked.connect(self.apply_edit_changes) QtCore.QObject.connect(self.textedit_run.edit, QtCore.SIGNAL( \ "focusLost"), self.apply_edit_changes) hbox = QtGui.QHBoxLayout() hbox.addStretch() hbox.setContentsMargins(0, 0, 0, 0) hbox_widget = QtGui.QWidget() hbox_widget.setLayout(hbox) vbox = QtGui.QVBoxLayout() vbox.addWidget(self.textedit_run) vbox.addWidget(hbox_widget) widget = QtGui.QWidget() widget.setLayout(vbox) tabwidget_script.addTab(widget, self.experiment.icon("inline_script"), \ _("Run phase")) # Switch to the run script by default, unless there is only content for # the prepare script. if self._run == "" and self._prepare != "": tabwidget_script.setCurrentIndex(0) else: tabwidget_script.setCurrentIndex(1) # Add the tabwidget to the controls self.edit_vbox.addWidget(tabwidget_script)
def context_menu(self, e): """ Show the context menu Arguments: e -- a QMouseEvent """ # First clear the grid, so we don't select the grid items for l in self.grid_list: self.removeItem(l) self.grid_list = [] # Get the item under the cursor item = self.itemAt(e.scenePos()) if item != None: # Get the corresponding item from the sketchpad_widget for g, _item in self.sketchpad_widget.item_list: if g == item: # Draw a highlight box brush = QtGui.QBrush() brush.setColor(QtGui.QColor(self.grid_color)) brush.setStyle(QtCore.Qt.Dense2Pattern) # For some items the boundingrect does not appear to work # correctly, so we have to use the pos() function x = max(item.pos().x(), item.boundingRect().x()) - 4 y = max(item.pos().y(), item.boundingRect().y()) - 4 w = item.boundingRect().width() + 8 h = item.boundingRect().height() + 8 l = self.addRect(x, y, w, h, brush = brush) l.setOpacity(0.25) # Show the context menu menu = QtGui.QMenu() menu.addAction( \ self.sketchpad_widget.sketchpad.experiment.icon( \ "delete"), _("Delete")) menu.addAction( \ self.sketchpad_widget.sketchpad.experiment.icon( \ "edit"), _("Edit")) action = menu.exec_(e.screenPos()) # Execute the chosen action if action != None: if unicode(action.text()) == _("Delete"): self.delete_item(_item) elif unicode(action.text()) == _("Edit"): self.edit_item(_item) self.sketchpad_widget.sketchpad.apply_edit_changes() self.sketchpad_widget.refresh()
def event_startup(self): """ desc: On startup, a widget is created with a menu that copies all actions from the main menu bar. """ self.menu = QtGui.QMenu() for action in self.menubar.actions(): self.menu.addAction(action) self.stretch = QtGui.QWidget() self.stretch.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.button = QtGui.QPushButton(self.theme.qicon(self.icon()), _(u'Menu')) self.button.setMenu(self.menu) self.button.setIconSize(QtCore.QSize(cfg.toolbar_size, cfg.toolbar_size)) self.button.setFlat(True) self.toolbar.addWidget(self.stretch) self.menu_action = self.toolbar.addWidget(self.button) if cfg.toolbar_menu_active: self.activate_toolbar_menu() self.set_checked(True) else: self.deactivate_toolbar_menu()
def add_editor_control(self, var, label, syntax=False, tooltip=None, \ default=None): """ Adds a QProgEdit that is linked to a variable. Arguments: var -- Name of the associated variable. label -- Label text. Keyword arguments: syntax -- A boolean indicating whether Python syntax highlighting should be activated. (default=False) tooltip -- A tooltip text. (default=None) default -- DEPRECATED Returns: A QProgEdit widget. """ from QProgEdit import QTabManager if syntax: lang = u'python' else: lang = u'text' qprogedit = QTabManager(cfg=cfg) qprogedit.focusLost.connect(self.apply_edit_changes) qprogedit.handlerButtonClicked.connect(self.apply_edit_changes) qprogedit.addTab(_(label, context=self.name)).setLang(lang) if var is not None: self.auto_editor[var] = qprogedit self.edit_vbox.addWidget(qprogedit) self.set_focus_widget(qprogedit) return qprogedit
def show_edit_dialog(self): """ desc: The show-edit dialog for the textline only edits the text, not the full element script. """ text = self.experiment.text_input(_(u'Edit text'), message=_(u'Please enter a text for the textline'), content=self.properties[u'text'].replace(u'<br />', u'\n'), parent=self.sketchpad._edit_widget) if text is None: return self.properties[u'text'] = self.clean_text(text) self.sketchpad.draw()
def regenerate(self, script): """ Handles a full regeneration of the experiment. Arguments: script -- A definition Unicode string / QString. """ self.main_window.set_busy(True) script = self.main_window.experiment.unistr(script) try: # Generate the new experiment tmp = experiment.experiment(self.main_window, \ self.main_window.experiment.title, script, \ self.main_window.experiment.pool_folder) except Exception as error: # If something is wrong with the script, notify the user and print # a traceback to the debug window self.main_window.experiment.notify( \ _(u'Failed to parse script (see traceback in debug window): %s') \ % error) self.main_window.print_debug_window(error) return # Apply the new experiment self.main_window.experiment = tmp self.main_window.experiment.build_item_tree() self.main_window.ui.tabwidget.close_all() self.main_window.ui.tabwidget.open_general_script() self.main_window.set_busy(False) self.main_window.set_unsaved()
def apply_button(self, label=u'Apply', icon=u'apply', \ tooltip=u'Apply changes'): """ Returns a right-outlined apply QPushButton. The widget is not added automatically to the controls. I.e. you need to implement your own connections to make the button functional. Keyword arguments: label -- A label text. (default=u'Apply') icon -- An icon name. (default=u'Apply') tooltip -- A tooltip text. (default=u'Apply changes') Returns: A QPushButton widget. """ button_apply = QtGui.QPushButton(_(label)) button_apply.setIcon(self.experiment.icon(icon)) button_apply.setIconSize(QtCore.QSize(16, 16)) button_apply.clicked.connect(self.apply_edit_changes) button_apply.setToolTip(tooltip) hbox = QtGui.QHBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) hbox.addStretch() hbox.addWidget(button_apply) widget = QtGui.QWidget() widget.setLayout(hbox) return widget
def draw(self, e): """ Handle drawing operations Arguments: e -- a QMouseEvent """ x, y = self.cursor_pos(e) if x == None: return if self.sketchpad_widget.sketchpad.get("coordinates") == "relative": x += 0.5 * self.sketchpad_widget.sketchpad.get("width") y += 0.5 * self.sketchpad_widget.sketchpad.get("height") if x < 0 or y < 0 or x >= self.sketchpad_widget.sketchpad.get("width") \ or y >= self.sketchpad_widget.sketchpad.get("height"): self.sketchpad_widget.ui.label_mouse_pos.setText( \ _("(Out of sketchpad)")) return if self.from_pos == None and not self.oneshot: self.addEllipse(x - self.r, y - self.r, 2 * self.r, 2 * self.r, \ self.pen) self.from_pos = x, y else: pos = self.from_pos self.from_pos = None self.sketchpad_widget.add(pos, (x, y))
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 open_markdown(self, md, icon=u'dialog-information', title=u'Attention!'): """ desc: Opens a Markdown-formatted tab. arguments: md: desc: If it ends with `.md`, this is interpreted as a file name. Otherwise, it is interpreted as markdown text. type: str keywords: icon: desc: The name of the tab icon. type: str title: desc: The tab title. type: str """ from libqtopensesame.widgets.webbrowser import webbrowser wb = webbrowser(self.main_window) if md.endswith(u'.md'): wb.load(md) else: wb.load_markdown(md) self.main_window.tabwidget.add(wb, icon, _(title))
def update_recent_files(self): """Recreate the list with recent documents""" from libqtopensesame.actions import recent_action # Add the current path to the front of the list if self.current_path != None and os.path.exists(self.current_path): if self.current_path in self.recent_files: self.recent_files.remove(self.current_path) self.recent_files.insert(0, self.current_path) # Trim the list self.recent_files = self.recent_files[:5] # Build the menu self.ui.menu_recent_files.clear() if len(self.recent_files) == 0: a = QtGui.QAction(_(u"(No recent files)"), \ self.ui.menu_recent_files) a.setDisabled(True) self.ui.menu_recent_files.addAction(a) else: for path in self.recent_files: self.ui.menu_recent_files.addAction( \ recent_action.recent_action(path, self, \ self.ui.menu_recent_files))
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 __init__(self, parent, item, pixmap): """ desc: Constructor. arguments: parent: desc: The parent. type: QWidget item: desc: An item. type: qtitem pixmap: desc: A pixmap for the icon. type: QPixmap """ super(toolbar_items_item, self).__init__(parent) self.setup(parent) self.item = item self.pixmap = pixmap self.setMargin(6) self.setToolTip( _("Drag this <b>%s</b> item to the intended location in the overview " "area or into the item list of a sequence tab") % self.item) self.setCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor)) self.setPixmap(self.pixmap)
def regenerate(self, script): """ Handles a full regeneration of the experiment Arguments: script -- a definition string (unicode/ QString) """ self.main_window.set_busy(True) script = self.main_window.experiment.unistr(script) try: # Generate the new experiment tmp = experiment.experiment(self.main_window, \ self.main_window.experiment.title, script, \ self.main_window.experiment.pool_folder) except libopensesame.exceptions.script_error as error: # If something is wrong with the script, notify the user self.main_window.experiment.notify(_("Could not parse script: %s") \ % error) return # Apply the new experiment self.main_window.experiment = tmp self.main_window.experiment.build_item_tree() self.main_window.ui.tabwidget.close_all() self.main_window.ui.tabwidget.open_general_script() self.main_window.set_busy(False) self.main_window.set_unsaved()
def image(self, path, center, x, y, scale): """Draw image""" pixmap = QtGui.QPixmap(path) if pixmap.isNull(): # Qt4 cannot handle certain funky bitmaps that PyGame can. So if # loading the image directly fails, we fall back to loading the # image with PyGame and converting it to a QPixmap. In addition, we # notify the user that he/ she is using a funky bitmap. import pygame im = pygame.image.load(path) data = pygame.image.tostring(im, "RGBA") size = im.get_size() image = QtGui.QImage(data, size[0], size[1], \ QtGui.QImage.Format_ARGB32) pixmap = QtGui.QPixmap.fromImage(image) self.notifications.append( \ _("Funky image alert: '%s' has a non-standard format. It is recommended to convert this image to .png format, for example with Gimp <http://www.gimp.org/>.") \ % os.path.basename(path)) w = pixmap.width() * scale pixmap = pixmap.scaledToWidth(w) _item = self.scene.addPixmap(pixmap) if center: _item.setPos(x - 0.5 * pixmap.width(), y - 0.5 * pixmap.height()) else: _item.setPos(x, y) return _item
def regenerate(self, script): """ desc: Regenerates the current experiment from script, and updates the GUI. argument: script: desc: The new experiment script. type: str """ try: exp = experiment.experiment(self, name=self.experiment.var.title, string=script, pool_folder=self.experiment.pool.folder(), experiment_path=self.experiment.experiment_path, resources=self.experiment.resources) except osexception as e: md = _(u'# Parsing error\n\nFailed to parse the script for the ' u'following reason:\n\n- ') + e.markdown() self.tabwidget.open_markdown(md) self.console.write(e) return self.extension_manager.fire(u'prepare_regenerate') self.experiment = exp self.tabwidget.close_all() self.experiment.build_item_tree() self.extension_manager.fire(u'regenerate')
def insert_child_item(self, item_name, index=0): if not self.is_coroutine(item_name): self.experiment.notify( _(u'"%s" does not support coroutines.') % item_name) return sequence.insert_child_item(self, item_name, index=index)
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 prepare(self): """Prepare for the run phase""" item.item.prepare(self) # Prepare the form try: cols = [float(i) for i in unicode(self.cols).split(';')] rows = [float(i) for i in unicode(self.rows).split(';')] margins = [float(i) for i in unicode(self.margins).split(';')] except: raise exceptions.runtime_error( \ _('cols, rows, and margins should be numeric values separated by a semi-colon')) self._form = widgets.form(self.experiment, cols=cols, rows=rows, \ margins=margins, spacing=self.spacing, theme=self.theme, item=self) # Prepare the widgets for _w in self._widgets: # Evaluate all keyword arguments w = {} for var, val in _w.iteritems(): w[var] = self.eval_text(val) _type = w['type'] col = w['col'] row = w['row'] colspan = w['colspan'] rowspan = w['rowspan'] del w['type'] del w['col'] del w['row'] del w['colspan'] del w['rowspan'] # Translate paths into full file names if 'path' in w: w['path'] = self.experiment.get_file(w['path']) # Process focus keyword if 'focus' in w: focus = True del w['focus'] else: focus = False # Create the widget and add it to the form try: _w = eval('widgets.%s(self._form, **w)' % _type) except Exception as e: raise exceptions.runtime_error( \ 'Failed to create widget "%s": %s' % (_type, e)) self._form.set_widget(_w, (col, row), colspan=colspan, \ rowspan=rowspan) # Add as focus widget if focus: self.focus_widget = _w return True
def apply_button(self, label="Apply", icon="apply", \ tooltip="Apply changes"): """ Returns a right-outlined apply button. The widget is not added automatically to the controls. Keyword arguments: label -- a button label (default="Apply") icon -- an icon name (default="Apply") tooltip -- a tooltip (default="Apply changes") Returns: A QPushButton """ button_apply = QtGui.QPushButton(_(label)) button_apply.setIcon(self.experiment.icon(icon)) button_apply.setIconSize(QtCore.QSize(16, 16)) button_apply.clicked.connect(self.apply_edit_changes) button_apply.setToolTip(tooltip) hbox = QtGui.QHBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) hbox.addStretch() hbox.addWidget(button_apply) widget = QtGui.QWidget() widget.setLayout(hbox) return widget
def parse_line(self, line): """ Allows for arbitrary line parsing, for item-specific requirements Arguments: line -- a single definition line """ l = self.split(line.strip()) if len(l) < 6 or l[0] != 'widget': return # Verify that the position variables are integers try: col = int(l[1]) row = int(l[2]) colspan = int(l[3]) rowspan = int(l[4]) except: raise exceptions.script_error( \ _('Widget column and row should be numeric')) # Parse the widget into a dictionary and store it in the list of # widgets w = self.parse_keywords(line) w['type'] = l[5] w['col'] = col w['row'] = row w['colspan'] = colspan w['rowspan'] = rowspan self._widgets.append(w)
def __init__(self, item): """ Constructor Arguments: item -- the item to provide a header for """ QtGui.QWidget.__init__(self) self.setCursor(QtCore.Qt.IBeamCursor) self.setToolTip(_(u"Click to edit")) self.item = item self.label_name = QtGui.QLabel() self.label_name.id = u"name" self.edit_name = QtGui.QLineEdit() self.edit_name.editingFinished.connect(self.restore_name) self.edit_name.hide() self.label_desc = QtGui.QLabel() self.label_desc.id = u"desc" self.edit_desc = QtGui.QLineEdit() self.edit_desc.editingFinished.connect(self.restore_desc) self.edit_desc.hide() vbox = QtGui.QVBoxLayout() vbox.setContentsMargins(8, 0, 0, 0) vbox.setSpacing(0) vbox.addWidget(self.label_name) vbox.addWidget(self.edit_name) vbox.addWidget(self.label_desc) vbox.addWidget(self.edit_desc) self.refresh() self.setLayout(vbox)
def __init__(self, item): """ desc: Constructor. arguments: item: A qtitem object. """ super(header_widget, self).__init__(item.main_window) self.setCursor(QtCore.Qt.IBeamCursor) self.setToolTip(_(u"Click to edit")) self.item = item self.label_name = QtGui.QLabel() self.edit_name = QtGui.QLineEdit() self.edit_name.editingFinished.connect(self.apply_name) self.edit_name.hide() self.label_desc = QtGui.QLabel() self.label_desc.setWordWrap(True) self.edit_desc = QtGui.QLineEdit() self.edit_desc.editingFinished.connect(self.apply_desc) self.edit_desc.hide() vbox = QtGui.QVBoxLayout() vbox.setContentsMargins(8, 0, 0, 0) vbox.setSpacing(0) vbox.addWidget(self.label_name) vbox.addWidget(self.edit_name) vbox.addWidget(self.label_desc) vbox.addWidget(self.edit_desc) self.refresh() self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) self.setLayout(vbox)
def save_file_as(self): """ desc: Saves the current experiment after asking for a file name. """ # Choose a default file name based on the experiment title if self.current_path is None: cfg.file_dialog_path = os.path.join( self.home_folder, self.experiment.syntax.sanitize(self.experiment.var.title, strict=True, allow_vars=False)) else: cfg.file_dialog_path = self.current_path path, file_type = QtGui.QFileDialog.getSaveFileNameAndFilter( self.ui.centralwidget, _(u'Save file as ...'), directory=cfg.file_dialog_path, filter=self.save_file_filter) if path is None or path == u"": return if not path.lower().endswith(u'.osexp'): path += u'.osexp' cfg.file_dialog_path = os.path.dirname(path) self.current_path = path cfg.default_logfile_folder = os.path.dirname(self.current_path) self.save_file()
def list_keys(self): """Show a dialog with available key names""" my_keyboard = keyboard(self.experiment) s = _('The following key names are valid:<br />') \ + '<br />'.join(my_keyboard.valid_keys()) self.experiment.notify(s)
def __init__(self, item, extra_info=None, parent_item=None, index=None): """ desc: Constructor. arguments: item: desc: An item. type: qtitem keywords: extra_info: desc: Extra info that is shown in the second column. Not shown in overview mode. type: [NoneType, unicode] """ super(tree_item_item, self).__init__() self.setup(item.main_window) self.item = item tooltip = _(u"Type: %s\nDescription: %s") % (item.item_type, item.var.description) self.setText(0, item.name) if extra_info is not None: self.setText(1, extra_info) self.setFlags(QtCore.Qt.ItemIsEditable | self.flags()) self.setIcon(0, self.theme.qicon(item.item_icon())) self.name = item.name # Only allow drops on used items self._droppable = self.item.name in self.item.experiment.items.used() self._draggable = True self._lock = False self.setToolTip(0, tooltip)
def save_file(self): """ desc: Saves the current experiment. keywords: dummy: A dummy argument passed by the signal handler. remember: desc: Indicates whether the file should be included in the list of recent files. type: bool catch: desc: Indicates whether exceptions should be caught and displayed in a notification. type: bool """ if self.current_path is None: self.save_file_as() return self.extension_manager.fire(u'save_experiment', path=self.current_path) # Indicate that we're busy self.set_busy(True) QtGui.QApplication.processEvents() # Get ready try: self.get_ready() except osexception as e: self.ui.console.write(e) self.experiment.notify( _(u"The following error occured while trying to save:<br/>%s") \ % e) self.set_busy(False) return # Try to save the experiment if it doesn't exist already try: self.experiment.save(self.current_path, overwrite=True) self.set_busy(False) except Exception as e: self.ui.console.write(e) self.experiment.notify(_(u"Failed to save file. Error: %s") % e) self.set_busy(False) return self.update_recent_files() self.set_unsaved(False) self.window_message(self.current_path) self.set_busy(False)
def rename(self, from_name, to_name): """ desc: Renames an item and updates the interface. This function may show a notification dialog. arguments: from_name: desc: The old item name. type: unicode to_name: desc: The desired new item name. type: unicode returns: desc: The new name of the item, if renaming was successful, None otherwise. type: [NoneType, unicode] """ if from_name not in self: self.experiment.notify(_(u'Item "%s" doesn\'t exist' % from_name)) return None if from_name == to_name: return None to_name = self.valid_name(self[from_name].item_type, suggestion=to_name) if to_name in self: self.experiment.notify( _(u'An item with that name already exists.')) return None if to_name == u'': self.experiment.notify(_(u'An item name cannot be empty.')) return None # Copy the item in the __items__dictionary self.__items__[to_name] = self.__items__[from_name] del self.__items__[from_name] # Give all items a chance to update for item in self.values(): item.rename(from_name, to_name) self.experiment.rename(from_name, to_name) self.itemtree.rename(from_name, to_name) self.main_window.set_unsaved(True) self.extension_manager.fire(u'rename_item', from_name=from_name, to_name=to_name) return to_name
def wizard(self): """Present the variable wizard dialog""" icons = {} icons["cut"] = self.experiment.icon("cut") icons["copy"] = self.experiment.icon("copy") icons["paste"] = self.experiment.icon("paste") icons["clear"] = self.experiment.icon("clear") # Set up the wizard dialog a = QtGui.QDialog(self.experiment.main_window.ui.centralwidget) a.ui = loop_wizard_dialog_ui.Ui_loop_wizard_dialog() a.ui.setupUi(a) self.experiment.main_window.theme.apply_theme(a) a.ui.table_example.build_context_menu(icons) a.ui.table_wizard.build_context_menu(icons) a.ui.table_example.hide() a.ui.table_wizard.setRowCount(255) a.ui.table_wizard.setColumnCount(255) a.ui.table_wizard.set_contents(cfg.loop_wizard) if a.exec_() == QtGui.QDialog.Accepted: cfg.loop_wizard = a.ui.table_wizard.get_contents() debug.msg("filling loop table") # First read the table into a dictionary of variables var_dict = {} for col in range(a.ui.table_wizard.columnCount()): var = None for row in range(a.ui.table_wizard.rowCount()): item = a.ui.table_wizard.item(row, col) if item == None: break s = unicode(item.text()) if s == u'': break if row == 0: var = self.experiment.sanitize(s, True) var_dict[var] = [] elif var != None: var_dict[var].append(s) # If the variable wizard was not parsed correctly, provide a # notification and do nothin if len(var_dict) == 0: self.experiment.notify( _('You provided an empty or invalid variable definition. For an example of a valid variable definition, open the variable wizard and select "Show example".')) return # Then fill the loop table self.i = 0 self.matrix = {} self.wizard_process(var_dict) self.set_cycle_count(len(self.matrix)) self.lock = True self.loop_widget.ui.spin_cycles.setValue(self.cycle_count()) self.lock = False self.refresh_loop_table() self.refresh_summary()
def execute(self): """See base_runner.execute().""" import multiprocessing from libqtopensesame.misc import process, _ from libopensesame import misc, debug from StringIO import StringIO if os.name == u'nt': # Under Windows, the multiprocess runner assumes that there is a # file called `opensesame.py` or `opensesame.pyc`. If this file does # not exist, try to copy it from the main script (`opensesame`). If # this fails, provide an informative error message. os_folder = misc.opensesame_folder() if not os.path.exists(os.path.join(os_folder, u'opensesame.pyc')) \ and not os.path.exists(os.path.join(os_folder, u'opensesame.py')): import shutil try: shutil.copyfile(os.path.join(os_folder, u'opensesame'), \ os.path.join(os_folder, u'opensesame.py')) except Exception as e: return osexception( \ _(u'Failed to copy `opensesame` to `opensesame.py`, which is required for the multiprocess runner. Please copy the file manually, or select a different runner under Preferences.'), exception=e) self.channel = multiprocessing.Queue() self.exp_process = process.ExperimentProcess(self.experiment, \ self.channel) # Start process! self.exp_process.start() # Variables used for ugly hack to suppress 'None' print by Queue.get() _stdout = sys.stdout _pit = StringIO() # Wait for experiment to finish. # Listen for incoming messages in the meantime. while self.exp_process.is_alive() or not self.channel.empty(): QtGui.QApplication.processEvents() # Make sure None is not printed. Ugly hack for a bug in the Queue # class? sys.stdout = _pit # Wait for messages. Will throw Exception if no message is received # before timeout. try: msg = self.channel.get(True, 0.05) except: msg = None # Restore connection to stdout sys.stdout = _stdout # For standard print statements if isinstance(msg, basestring): sys.stdout.write(msg) # Errors arrive as a tuple with (Error object, traceback) elif isinstance(msg, Exception): return msg # Anything that is not a string, not an Exception, and not None is # unexpected elif msg != None: return osexception( \ u"Illegal message type received from child process: %s (%s)" \ % (msg, type(msg))) # Return None if experiment finished without problems return None
def handle_exception(self, e): """ desc: Shows a summary when the experiment was aborted. arguments: e: desc: The Exception that caused the experiment to stop. type: Exception """ if not isinstance(e, osexception): e = osexception(msg=u'Unexpected error', exception=e) self.console.write(e) md = _(u'# Stopped\n\nThe experiment did not finish normally for the ' u'following reason:\n\n- ') + e.markdown() self.tabwidget.open_markdown(md, u'process-stop', _(u'Stopped'))
def event_startup(self): """ desc: Build the menu on startup. """ self.menu = self.menubar.addMenu(_(u'Help')) menu = self.online_help_menu() if menu != None: self.action_online_help = self.menu.addMenu(menu) menu = self.psychopy_help_menu() if menu != None: self.action_psychopy_help = self.menu.addMenu(menu) self.action_offline_help = self.menu.addAction( self.theme.qicon(u'help-contents'), _(u'Offline help')) self.action_offline_help.triggered.connect(self.open_offline_help) self.menu.addSeparator()