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.edit_script.edit.toPlainText() # 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.edit_script.setModified(False) 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 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(): self.header.edit_name.setText(self.name) return 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 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 = _("Invalid or missing value for variable '%s' (edit script to fix this)") \ % var_name if 'msg' in criteria: msg += ': ' + criteria['msg'] if not self.has(var_name) and 'required' in criteria and \ criteria['required']: self.experiment.notify(msg) return False else: var = self.get(var_name, _eval=False) if 'type' in criteria: _type = criteria['type'] if type(_type) != list: _type = [_type] if type(var) not in _type: self.experiment.notify(msg) return False if 'func' in criteria: if not criteria['func'](var): self.experiment.notify(msg) return False return True
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 load_mod(path, mod, pkg=None): """ Dynamically loads a module from a path. Arguments: path -- A folder or file name. If a filename is specified, the file's directory will be used as path. mod -- The name of a module, which corresponds to the name of the source file without the `.py` extension. pkg -- The module's package. This is effectively a subfolder that is added to the path. (default=None) Returns: A Python module. """ import imp if isinstance(path, str): path = path.decode(sys.getfilesystemencoding()) if not os.path.isdir(path): path = os.path.dirname(path) if pkg != None: path = os.path.join(path, pkg) path = os.path.join(path, mod+u'.py') debug.msg(u'loading module from %s' % path) return imp.load_source(mod, path)
def _color(color): """ See canvas.color() """ if type(color) in (str, unicode): return pygame.Color(str(color)) elif type(color) == int: pygame.Color(color, color, color, 255) elif type(color) == float: i = int(255 * color) pygame.Color(i, i, i, 255) elif type(color) == tuple: if len(color) == 3: return pygame.Color(color[0], color[1], color[2], 255) elif len(color) > 3: return pygame.Color(color[0], color[1], color[2], color[3]) else: return pygame.Color("white") elif type(color) == pygame.Color: return color else: debug.msg('Cannot interpret %s (%s), falling back to white' % (color, \ type(color))) return pygame.Color("white")
def setTheme(self): """ desc: Sets the theme, based on the QProgEdit settings. """ from QProgEdit import QColorScheme if not hasattr(QColorScheme, cfg.qProgEditColorScheme): debug.msg(u'Failed to set debug-output colorscheme') return u'' cs = getattr(QColorScheme, cfg.qProgEditColorScheme) if not self.validTheme(cs): debug.msg(u'Invalid debug-output colorscheme') return u'' self.control._highlighter.set_style(pygments_style_factory(cs)) qss = u'''QPlainTextEdit, QTextEdit { background-color: %(Background)s; color: %(Default)s; } .in-prompt { color: %(Prompt in)s; } .in-prompt-number { font-weight: bold; } .out-prompt { color: %(Prompt out)s; } .out-prompt-number { font-weight: bold; } ''' % cs self.control.style_sheet = qss self.control._control.setFont(QtGui.QFont(cfg.qProgEditFontFamily, cfg.qProgEditFontSize))
def parse_cmdline_args(self, args): """ Apply settings that were specified on the command line. The expected format is as follows: [name]=[val];[name]=[val];... Arguments: args -- the string of command line arguments """ if args is None: return for arg in args.split(u";"): a = arg.split(u"=") if len(a) == 2: # Automagically determine the data type if a[1] == u"True": val = True elif a[1] == u"False": val = False else: try: val = int(a[1]) except: try: val = float(a[1]) except: val = a[1] # Apply the argument try: self.__setattr__(a[0], val) debug.msg(u"%s = %s" % (a[0], val)) except: debug.msg(u"Failed to parse argument: %s" % arg)
def event_startup(self): """ desc: Initializes the extension on OpenSesame startup. """ # Create the autosave folder if it doesn't exist yet if not os.path.exists(os.path.join(self.main_window.home_folder, u".opensesame", u"backup")): os.mkdir(os.path.join(self.main_window.home_folder, u".opensesame", u"backup")) self.autosave_folder = os.path.join(self.main_window.home_folder, u".opensesame", u"backup") # Remove expired backups for path in os.listdir(self.autosave_folder): _path = os.path.join(self.autosave_folder, path) t = os.path.getctime(_path) age = (time.time() - t)/(60*60*24) if age > cfg.autosave_max_age: debug.msg(u"removing '%s'" % path) try: os.remove(_path) except: debug.msg(u"failed to remove '%s'" % path) self.start_autosave_timer()
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 change_working_dir(): """A horrifyingly ugly hack to change the working directory under Windows""" import libqtopensesame.qtopensesame if os.name == "nt": try: # Extract the part of the description containing the path s = unicode(libqtopensesame.qtopensesame) i = s.find("from '") + 6 j = s.find("'>'") - 1 s = s[i:j] # Go up the tree until the path of the current script while not os.path.exists(os.path.join(s, "opensesame")) and \ not os.path.exists(os.path.join(s, "opensesame.exe"))and \ not os.path.exists(os.path.join(s, "opensesamerun")) and \ not os.path.exists(os.path.join(s, "opensesamerun.exe")): s = os.path.dirname(s) os.chdir(s) if s not in sys.path: sys.path.append(s) debug.msg(s) except Exception as e: debug.msg("failed to change working directory: %s" % e)
def qicon(self, icon): """ Get an icon from the theme Arguments: icon -- the icon name Returns: A QIcon """ if isinstance(icon, QtGui.QIcon): return icon if hasattr(self, u'experiment') and u'%s_large.png' % icon in \ self.experiment.resources: return QtGui.QIcon(self.experiment.resource(u'%s_large.png' % icon)) if icon in self.icon_map: name = self.icon_map[icon][0] else: name = icon icon = QtGui.QIcon.fromTheme(name, self.fallback_icon) if icon.name() != name: debug.msg(u'missing icon %s' % name, reason=u'warning') return icon
def __init__(self, sketchpad, string, defaults=[]): """ desc: Constructor. arguments: sketchpad: A sketchpad object. string: A definition string. keywords: defaults: A list with (name, default_value) tuples for all keywords. """ self._type = self.__class__.__name__ # These keywords provide compatibility with older versions of # OpenSesame. `only_keywords` specifies whether all parameters should be # written as keywords, which will prevent < 2.9.0 from reading the # sketchpad elements. If not, keywords with a None default will be # written value-only style. `fix_coordinates` specifies whether # coordinates should be translated to top-left = 0,0. self.only_keywords = False self.fix_coordinates = sketchpad.var.uniform_coordinates!=u'yes' debug.msg(self._type) self.defaults = defaults + [ (u'z_index', 0), (u'show_if', u'always') ] self.sketchpad = sketchpad self.var = self.sketchpad.var self.from_string(string)
def suggest_variables(self): """ Smart select all variables that should probably be logged. Variables in unused items are ignored. """ i = 0 for item in self.experiment.items: if item in self.experiment.unused_items: debug.msg("ignoring variables from '%s'" % item) continue if self.experiment.items[item].item_type in ("loop", \ "keyboard_response", "mouse_response"): for var, val in self.experiment.items[item].var_info(): if var not in self.logvars and var != "time_%s" % item and \ var != "count_%s" % item: self.logvars.append(var) if self.experiment.items[item].item_type == "sequence": for var, val in self.experiment.items[item].var_info(): if var not in self.logvars and var == "count_%s" % item: self.logvars.append(var) self.edit_widget() self.experiment.main_window.refresh(self.name)
def run(self): """ desc: Runs the item. """ # Set the onset time self.set_item_onset() # Flush keyboard, so the escape key can be used self._keyboard.flush() # If no start response interval has been set, set it to the onset of # the current response item if self.experiment._start_response_interval is None: self.experiment._start_response_interval = self.var.get( u'time_%s' % self.name) if self.var._dummy == u'yes': # In dummy mode, no one can hear you scream! Oh, and we simply # take input from the keyboard resp, self.experiment.end_response_interval = self._resp_func( keylist=None, timeout=self._timeout) else: # Get the response resp, self.experiment.end_response_interval = self._resp_func( self._allowed_responses, self._timeout) debug.msg(u'received %s' % resp) self.experiment.var.response = resp generic_response.generic_response.response_bookkeeping(self)
def prepare_response_func(self): """See base_response_item.""" self._keyboard = keyboard(self.experiment, keylist=self._allowed_responses, timeout=self._timeout) if self.var._dummy == u'yes': return self._keyboard.get_key # Prepare the device string dev = self.var.dev if dev == u"autodetect": dev = None # Dynamically create an srbox instance if not hasattr(self.experiment, "srbox"): self.experiment.srbox = libsrbox.libsrbox(self.experiment, dev) self.experiment.cleanup_functions.append(self.close) self.python_workspace[u'srbox'] = self.experiment.srbox # Prepare the light byte s = "010" # Control string for i in range(5): if str(5 - i) in str(self.var.lights): s += "1" else: s += "0" self._lights = chr(int(s, 2)) debug.msg(u"lights string set to %s (%s)" % (s, self.var.lights)) if self._allowed_responses is not None: self._allowed_responses = [int(r) for r in self._allowed_responses] self._require_state_change = self.var.require_state_change == u'yes' return self._get_button_press
def prepare(self): """The preparation phase of the plug-in.""" item.item.prepare(self) # Sanity check on the duration value, which should be a positive numeric # value. if type(self.get('duration')) not in (int, float) or \ self.get('duration') < 0: raise osexception( \ u'Duration should be a positive numeric value in advanced_delay %s' \ % self.name) if self.get(u'jitter_mode') == u'Uniform': self._duration = random.uniform(self.get(u'duration')-self.get( \ u'jitter')/2, self.get(u'duration')+self.get(u'jitter')/2) elif self.get(u'jitter_mode') == u'Std. Dev.': self._duration = random.gauss(self.get(u'duration'), self.get( \ u'jitter')) else: raise osexception( \ u'Unknown jitter mode in advanced_delay %s' % self.name) # Don't allow negative durations. if self._duration < 0: self._duration = 0 self._duration = int(self._duration) self.experiment.set(u'delay_%s' % self.name, self._duration) debug.msg(u"delay for %s ms" % self._duration)
def auto_responder(self, dev=u'keyboard'): """ Mimicks participant responses. Keyword arguments: dev -- The device that should be simulated. (default=u'keyboard') Returns: A simulated (response_time, response) tuple """ if self._timeout == None: self.sleep(random.randint(200, 1000)) else: self.sleep(random.randint(min(self._timeout, 200), self._timeout)) if self._allowed_responses == None: resp = self.auto_response else: resp = random.choice(self._allowed_responses) debug.msg(u"generic_response.auto_responder(): responding '%s'" % resp) if dev == u'mouse': pos = random.randint(0, self.get(u'width')), random.randint( \ 0, self.get(u'height')) return resp, pos, self.time() return resp, self.time()
def delete_selected_files(self): """ desc: Deletes all selected files from the pool. Asks for confimation\ first. """ # Prepare the confirmation dialog, which contains a limited nr of # filenames l = [] suffix = u'' for item in self.ui.list_pool.selectedItems()[:self.max_len]: l.append(self.pool[item.text()]) if len(self.ui.list_pool.selectedItems()) > self.max_len: suffix = _('And %d more file(s)') % \ (len(self.ui.list_pool.selectedItems())-self.max_len) msg = _(u"<p>Are you sure you want to remove the following files?</p>" u"<p>- %s</p> <p>%s</p>") % (u"<br /> - ".join(l), suffix) c = confirmation(self.main_window, msg) if not c.show(): return # Create a list of files to be removed dL = [] for item in self.ui.list_pool.selectedItems(): dL.append(item.path) # Remove the files try: for f in dL: del self.pool[f] debug.msg(u"removed '%s'" % f) except: debug.msg(u"failed to remove '%s'" % f) self.refresh() self.main_window.set_unsaved()
def __init__(self, main_window, theme=None): """ Constructor Arguments: main_window -- the main_window object Keyword arguments: theme -- the theme to be used or None to use config (default=None) """ self.main_window = main_window if theme == None: self.theme = config.get_config(u"theme") else: self.theme = theme self.theme_folder = misc.resource(os.path.join(u"theme", \ self.theme)) debug.msg(u"theme = '%s' (%s)" % (self.theme, self.theme_folder)) if self.theme_folder == None or not os.path.exists(self.theme_folder): debug.msg(u"theme '%s' does not exist, using 'default'" % theme, \ reason=u"warning") self.theme = u"default" self.theme_folder = misc.resource(os.path.join(u"theme", \ self.theme)) self.theme_info = os.path.join(self.theme_folder, u"__theme__.py") if os.path.exists(self.theme_info): info = imp.load_source(self.theme, self.theme_info) self._qss = path = \ open(os.path.join(self.theme_folder, info.qss)).read() self._icon_map = info.icon_map self._icon_theme = info.icon_theme self.load_icon_map() self.apply_theme(self.main_window)
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) self.set_focus_widget(widget) 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.activated.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.clicked.connect(self.apply_edit_changes) self.auto_checkbox[var] = widget else: raise Exception(u"Cannot auto-add widget of type %s" % widget)
def image(self, fname, center=True, x=None, y=None, scale=None): _fname = safe_str(fname, enc=misc.filesystem_encoding()) if not os.path.isfile(_fname): raise osexception(u'"%s" does not exist' % fname) try: surface = pygame.image.load(_fname) except pygame.error: raise osexception(u"'%s' is not a supported image format" % fname) if scale is not None: try: surface = pygame.transform.smoothscale( surface, (int(surface.get_width() * scale), int(surface.get_height() * scale)) ) except: debug.msg(u"smooth scaling failed for '%s'" % fname, reason=u"warning") surface = pygame.transform.scale( surface, (int(surface.get_width() * scale), int(surface.get_height() * scale)) ) size = surface.get_size() x, y = self.to_xy(x, y) if center: x -= size[0] / 2 y -= size[1] / 2 self.surface.blit(surface, (x, y))
def load_ui(self, ui=None): """ desc: Dynamically loads the ui, if any. keywords: ui: An id for a user-interface file, or None. """ if ui is not None: # If the UI file has been explicitly registered, which is the case # for extensions if hasattr(self, u'experiment') and ui in self.experiment.resources: ui_path = self.experiment.resources[ui] else: # Dot-split the ui id, append a `.ui` extension, and assume it's # relative to the resources/ui subfolder. path_list = [u'ui'] + ui.split(u'.') if hasattr(self, u'experiment'): # If an experiment object is available, use that to find the # resources ... ui_path = self.experiment.resource( os.path.join(*path_list)+u'.ui') else: # ... otherwise use the static resources function. from libopensesame import misc ui_path = misc.resource(os.path.join(*path_list)+u'.ui') debug.msg(u'dynamically loading ui: %s' % ui_path) with open(ui_path) as fd: self.ui = uic.loadUi(fd, self) else: self.ui = None
def reset(self): """ desc: Initialize/ reset the plug-in. """ self.file_loaded = False self.resizeVideo = u"yes" self.duration = u"keypress" self.playaudio = u"yes" self.sendInfoToEyelink = u"no" self.event_handler = u"" self.event_handler_trigger = u"on keypress" self.vlc_event_handler = None self.media = None self.framerate = 0 self.frame_duration = 0 self.startPlaybackTime = 0 self.playbackStarted = False self.hasMediaInfo = False #See if MediaInfo functions are available try: MediaInfo.parse(u"") self.hasMediaInfo = True except: debug.msg( \ u"MediaInfo CLI not found. Frame info might be unavailable.", reason=u"warning") self.hasMediaInfo = False
def parse_definition(self, item_type, item_name, string): """ Initializes a single definition, using the string, and adds it to the dictionary of items. Arguments: item_type -- The item's type. item_name -- The item's name. string -- The item's definition string. """ if plugins.is_plugin(item_type): # Load a plug-in try: item = plugins.load_plugin(item_type, item_name, self, \ string, self.item_prefix()) except Exception as e: raise osexception(u"Failed to load plugin '%s'" % \ item_type, exception=e) self.items[item_name] = item else: # Load one of the core items debug.msg(u"loading core item '%s' from '%s'" % (item_type, \ self.module_container())) item_module = __import__(u'%s.%s' % (self.module_container(), \ item_type), fromlist=[u'dummy']) item_class = getattr(item_module, item_type) item = item_class(item_name, self, string) self.items[item_name] = item
def edit_widget(self): """Set the loop controls from the variables""" self.lock = True debug.msg() # Update the item combobox self.experiment.item_combobox(self.item, self.parents(), \ self.loop_widget.ui.combobox_item) qtitem.qtitem.edit_widget(self) self.refresh_loop_table(lock=False) self.loop_widget.ui.spin_cycles.setValue(self.cycle_count()) if self.get("order") == "random": self.loop_widget.ui.label_skip.setDisabled(True) self.loop_widget.ui.spin_skip.setDisabled(True) self.loop_widget.ui.checkbox_offset.setDisabled(True) else: self.loop_widget.ui.label_skip.setDisabled(False) self.loop_widget.ui.spin_skip.setDisabled(False) self.loop_widget.ui.checkbox_offset.setDisabled( \ self.get("skip") < 1) self.refresh_summary() self.lock = False return self._edit_widget
def from_string(self, string): """ Reads the entire experiment from a string. Arguments: string -- The definition string. """ debug.msg(u"building experiment") s = iter(string.split("\n")); line = next(s, None) while line != None: get_next = True try: l = self.split(line) except ValueError as e: raise osexception( \ u"Failed to parse script. Maybe it contains illegal characters or unclosed quotes?", \ exception=e) if len(l) > 0: self.parse_variable(line) # Parse definitions if l[0] == u"define": if len(l) != 3: raise osexception( \ u'Failed to parse definition', line=line) item_type = l[1] item_name = self.sanitize(l[2]) line, def_str = self.read_definition(s) get_next = False self.parse_definition(item_type, item_name, def_str) # Advance to next line if get_next: line = next(s, None)
def run(self): """The run phase of the plug-in goes here.""" if self.get(u"auto_log") == u"yes": # Log everything self.logvars = [] for logvar, val, item in self.experiment.var_list(): if (self.has(logvar) or self.get(u"ignore_missing") == u"yes") and logvar not in self.logvars: self.logvars.append(logvar) debug.msg(u'auto-logging "%s"' % logvar) else: # Parse variable to log from user input (stopgap function, until # proper UI can be used. self.logvars = self._vars_string.strip(" ").split("\n") trial_data = dict() for var in self.logvars: try: val = self.experiment.get(var) except exceptions.runtime_error as e: if self.get(u"ignore_missing") == u"yes": val = u"NA" else: raise exceptions.runtime_error( u"Logger '%s' tries to log the variable '%s', but this variable is not available. Please deselect '%s' in logger '%s' or enable the 'Use NA for variables that have not been set' option." % (self.name, var, var, self.name) ) trial_data[var] = val self.experiment.log_list.append(trial_data)
def closePlayer(self): self.player.release() self.vlcInstance.release() self.media = None debug.msg("Released VLC modules") if hasattr(self, "screen"): self.screen = None
def handle_starttag(self, tag, attrs): """ Handle an opening tag Arguments: tag -- the closing tag attrs -- the tag attributes """ if tag not in self.valid_start_tags: return if tag == u'br': self.text.append(self.paragraph) self.paragraph = [] return self.current_tag = tag if tag == u'span': style = {} for var, val in attrs: style[str(var)] = val self.push_style(**style) elif tag == u'b': self.push_style(bold=True) elif tag == u'i': self.push_style(italic=True) elif tag == u'u': self.push_style(underline=True) else: debug.msg(u'Unrecognized tag: %s' % tag)
def from_string(self, string): """ Parses the item from a definition string. Arguments: string -- The definition string. """ debug.msg() textblock_var = None self.variables = {} self.reset() self.comments = [] for line in string.split(u'\n'): line_stripped = line.strip() # The end of a textblock if line_stripped == u'__end__': if textblock_var == None: self.experiment.notify( \ u'It appears that a textblock has been closed without being opened. The most likely reason is that you have used the string "__end__", which has a special meaning for OpenSesame.') else: self.set(textblock_var, textblock_val) textblock_var = None # The beginning of a textblock. A new textblock is only started when # a textblock is not already ongoing, and only if the textblock # start is of the format __VARNAME__ elif line_stripped[:2] == u'__' and line_stripped[-2:] == u'__' \ and textblock_var == None: textblock_var = line_stripped[2:-2] if textblock_var in self.reserved_words: textblock_var = u'_' + textblock_var if textblock_var != u'': textblock_val = u'' else: textblock_var = None # We cannot just strip the multiline code, because that may mess # up indentation. So we have to detect if the string is indented # based on the opening __varname__ line. strip_tab = line[0] == u'\t' # Collect the contents of a textblock elif textblock_var != None: if strip_tab: textblock_val += line[1:] + u'\n' else: textblock_val += line + u'\n' # Parse regular variables elif not self.parse_variable(line): self.parse_line(line)
def update_resolution(self, width, height): """ desc: Updates the resolution in a way that preserves display centering. This is kind of a quick hack. First generate the script, change the resolution in the script and then re-parse the script. arguments: width: The display width in pixels. height: The display height in pixels. """ debug.msg(u"changing resolution to %d x %d" % (width, height)) try: script = self.experiment.to_string() except Exception as e: if not isinstance(e, osexception): e = osexception(u'Failed to change the display resolution', exception=e) md = _(u'# Error\n\nFailed to change display resolution for the ' u'following reason:\n\n- ') + e.markdown() self.tabwidget.open_markdown(md) return old_cmd = self.experiment.syntax.create_cmd( u'set', [u'height', self.experiment.var.height]) new_cmd = self.experiment.syntax.create_cmd(u'set', [u'height', height]) script = script.replace(old_cmd, new_cmd) old_cmd = self.experiment.syntax.create_cmd( u'set', [u'width', self.experiment.var.width]) new_cmd = self.experiment.syntax.create_cmd(u'set', [u'width', width]) script = script.replace(old_cmd, new_cmd) try: tmp = 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 error: self.experiment.notify(_(u"Could not parse script: %s") % error) self.edit_script.edit.setText(self.experiment.to_string()) return self.experiment = tmp self.ui.tabwidget.close_other() self.update_overview_area() self.extension_manager.fire(u'regenerate')
def open_edit_tab(self, index=None, focus=True): """ Opens/ shows the GUI control 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("%s (#%s)" % (self.name, hash(self))) # Switch to edit mode and close the script tab if it was open self.edit_mode = "edit" for i in range(self.experiment.ui.tabwidget.count()): w = self.experiment.ui.tabwidget.widget(i) if hasattr(w, "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, "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("%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), "%s" % self.name) else: self.experiment.ui.tabwidget.insertTab(index, widget, \ self.experiment.icon(self.item_type), "%s" % self.name) self.edit_tab_index = index if focus: self.experiment.ui.tabwidget.setCurrentIndex(self.edit_tab_index)
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 type(var) == str: val = unicode(edit.text()).strip() if val != "": 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, "default"): self.set(var, edit.default) else: self.unset(var) for var, combobox in self.auto_combobox.iteritems(): if type(var) == str: self.set(var, unicode(combobox.currentText())) for var, spinbox in self.auto_spinbox.iteritems(): if type(var) == str: self.set(var, spinbox.value()) for var, slider in self.auto_slider.iteritems(): if type(var) == str: self.set(var, slider.value()) for var, checkbox in self.auto_checkbox.iteritems(): if type(var) == str: if checkbox.isChecked(): val = "yes" else: val = "no" self.set(var, val) for var, editor in self.auto_editor.iteritems(): if type(var) == str: self.set(var, editor.edit.toPlainText()) editor.setModified(False) return True
def plugin_property(plugin, _property, default=0): """ Returns a property of a plug-in. Arguments: plugin -- The name of the plugin. _property -- The name of the property. Keywords arguments: default -- A default property value. (default=0) Returns: The property value. """ global _properties if plugin in _properties and _property in _properties[plugin]: return _properties[plugin][_property] if plugin not in _properties: _properties[plugin] = {} info_txt = os.path.join(plugin_folder(plugin), u'info.txt') info_json = os.path.join(plugin_folder(plugin), u'info.json') # New-style plug-ins, using info.json if os.path.exists(info_json): try: _json = json.load(open(info_json)) except: debug.msg(u'Failed to parse %s' % info_json) _json = {} if _property in _json: return _json[_property] # Old-style plug-ins, using info.txt elif os.path.exists(info_txt): for l in open(info_txt, u'r'): a = l.split(":") if len(a) == 2 and a[0] == _property: val = a[1].strip() try: val = int(val) finally: _properties[plugin][_property] = val return val _properties[plugin][_property] = default else: debug.msg( \ u'Failed to read plug-in information (%s) from info.[txt|json]' \ % plugin) return default
def prepare(self): """Prepares the item.""" # Pass the word on to the parent item.item.prepare(self) # Prepare the allowed responses if self.has(u"allowed_responses"): self._allowed_responses = [] for r in self.unistr(self.get(u"allowed_responses")).split(u";"): if r.strip() != "": try: r = int(r) except: raise osexception( \ u"'%s' is not a valid response on your joystick/gamepad. Expecting a number in the range of 1 to the amount of buttons." \ % (r,self.name)) if r < 0 or r > 255: raise osexception( \ u"'%s' is not a valid response on your joystick/gamepad. Expecting a number in the range of 1 to the amount of buttons." \ % (r, self.name)) self._allowed_responses.append(r) if len(self._allowed_responses) == 0: self._allowed_responses = None else: self._allowed_responses = None debug.msg(u"allowed responses has been set to %s" % self._allowed_responses) # In case of dummy-mode: self._keyboard = openexp.keyboard.keyboard(self.experiment) if self.has(u"_dummy") and self.get(u"_dummy") == u"yes": self._resp_func = self._keyboard.get_key # Not in dummy-mode: else: timeout = self.get(u"timeout") # Dynamically load a joystick instance if not hasattr(self.experiment, u"joystick"): path = os.path.join(os.path.dirname(__file__), \ u"libjoystick.py") _joystick = imp.load_source(u"libjoystick", path) self.experiment.joystick = _joystick.libjoystick( \ self.experiment) # Prepare auto response if self.experiment.auto_response: self._resp_func = self.auto_responder else: self._resp_func = self.experiment.joystick.get_joybutton self.prepare_timeout()
def event_rename_item(self, from_name, to_name): """ desc: Called after an item has been renamed. arguments: from_name: desc: The old name. type: unicode to_name: desc: The new name. type: unicode """ debug.msg(u'Event fired: rename_item(from_name=%s, to_name=%s)' \ % (from_name, to_name))
def get_ready(self): """ desc: Applies pending script changes. returns: True if changes have been made, False otherwise. """ for var, qprogedit in self.auto_editor.items(): if qprogedit.isAnyModified(): debug.msg(u'applying pending editor changes') self.apply_edit_changes() return True return qtitem.qtitem.get_ready(self)
def set_validator(self): """ desc: Sets the validator class, that is, the class that is used to parse scripts to see if they are syntactically correct. Currently, we use the class that defines from_string(). """ import inspect meth = self.from_string for cls in inspect.getmro(self.__class__): if meth.__name__ in cls.__dict__: break debug.msg(u'validator: %s' % cls) self.validator = cls
def get_ready(self): """Give all items the opportunity to get ready for running or saving""" # Redo the get_ready loop until no items report having done # anything redo = True done = [] while redo: redo = False for item in self.experiment.items: if item not in done: done.append(item) if self.experiment.items[item].get_ready(): debug.msg(u"'%s' did something" % item) redo = True break
def load_icons(self, ui): """ Add icons to all icon supporting widgets in a ui object Arguments: ui -- the ui object to load icons into """ debug.msg() for i in dir(ui): if i in self.icon_map: a = getattr(ui, i) if hasattr(a, u"setIcon"): a.setIcon(self.qicon(i)) elif hasattr(a, u"setPixmap"): a.setPixmap(self.qpixmap(i))
def __init__(self): """Constructor""" # 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.init_edit_widget() self.lock = False self.maximized = False self.set_validator() debug.msg(u'created %s' % self.name)
def get_ready(self): """ Apply pending script changes Returns: True if changes have been made, False otherwise """ for var, editor in self.auto_editor.iteritems(): if editor.isModified(): debug.msg("applying pending Python script changes") self.apply_edit_changes() return True return qtitem.qtitem.get_ready(self)
def start_autosave_timer(self): """ desc: Starts the autosave timer. """ if cfg.autosave_interval > 0: debug.msg(u"autosave interval = %d ms" % cfg.autosave_interval) self.autosave_timer = QtCore.QTimer() self.autosave_timer.setInterval(cfg.autosave_interval) self.autosave_timer.setSingleShot(True) self.autosave_timer.timeout.connect(self.autosave) self.autosave_timer.start() else: debug.msg(u"autosave disabled") self.autosave_timer = None
def register_font(font_path): """ desc: Register a font with PyGlet. If the font has already been registered, this function does nothing. arguments: font_path: The full path to the font file. """ global _registered_fonts if font_path in _registered_fonts: return debug.msg(u'registering %s' % font_path) pyglet.font.add_file(font_path) _registered_fonts.append(font_path)
def handle_endtag(self, tag): """ Handle a closing tag Arguments: tag -- the closing tag """ if tag not in self.valid_end_tags: return if self.current_tag != tag: debug.msg(u'Warning: expecting closing tag for %s, got %s' % \ (self.current_tag, tag), reason=u'warning') self.pop_style()
def edit_widget(self, stretch=True): """ A dummy edit widget, to be overridden Keywords arguments: stretch -- a deprecated argument (default=True) """ if not stretch: debug.msg("passing the stretch argument is deprecated", \ reason="deprecation") self.header.restore_name(False) self.header.refresh() self._edit_widget.edit_item = self.name self.auto_edit_widget() return self._edit_widget
def qicon(self, icon): """ desc: Gets an icon from the theme. arguments: icon: One of the following: - A QIcon, which returned as is - The name of an image file - The name of a plug-in with a hardcoded icon - The name of an entry in the icon map - A theme icon name. returns: desc: An icon, or a fallback icon if the specified wasn't found. type: QIcon """ if isinstance(icon, QtGui.QIcon): return icon if os.path.isfile(icon): qicon = QtGui.QIcon() if icon.endswith(u'_large.png'): size = 32 else: size = 16 qicon.addFile(icon, size=QtCore.QSize(size, size)) return qicon if hasattr(self, u'experiment') and (u'%s_large.png' % icon in \ self.experiment.resources or '%s.png' in self.experiment.resources): qicon = QtGui.QIcon() if u'%s_large.png' % icon in self.experiment.resources: qicon.addFile(self.experiment.resource( u'%s_large.png' % icon), size=QtCore.QSize(32,32)) if u'%s.png' % icon in self.experiment.resources: qicon.addFile(self.experiment.resource(u'%s.png' % icon), size=QtCore.QSize(16,16)) return qicon if icon in self.icon_map: name = self.icon_map[icon][0] else: name = icon icon = QtGui.QIcon.fromTheme(name, self.fallback_icon) if icon.name() != name: debug.msg(u'missing icon %s' % name, reason=u'warning') return icon
def apply_script_changes(self, *deprecated, **_deprecated): """ desc: Applies changes to the script, by re-parsing the item from string. """ debug.msg() old_script = self.to_string() new_script = self._script_widget.text() try: self.from_string(new_script) except osexception as e: self.experiment.notify(e.html()) self.main_window.print_debug_window(e) self.from_string(old_script) self.edit_widget() self.main_window.set_unsaved(True)
def run(self): """Run phase""" # self.set_item_onset() sets the time_[item name] variable. Optionally, # you can pass a timestamp, such as returned by canvas.show(). # Set the pptrigger value if self.pptrigger_dummy == u'no': ## turn trigger on try: if os.name == 'nt': self.set_item_onset(self.experiment.pptrigger.DlPortWritePortUchar(int(self.pptrigger_port,0), self.pptrigger_value)) else: self.set_item_onset(self.experiment.pptrigger.setData(self.pptrigger_value)) debug.msg(u'Sending value %s to the parallel port on address: %s' % (self.pptrigger_value,self.pptrigger_port)) except Exception as e: raise osexception( u'Wrong port address, could not access the Parallel Port', exception=e) ## Executing duration and reset if self.pptrigger_duration_check == u'yes': # use keyboard as timeout, allowing for Escape presses to abort experiment if self.pptrigger_duration !=0: self.kb.get_key(timeout=self.pptrigger_duration) debug.msg(u'Waiting %s ms to reset' % (self.pptrigger_duration)) try: if os.name == 'nt': self.set_item_onset(self.experiment.pptrigger.DlPortWritePortUchar(int(self.pptrigger_port,0), 0)) else: self.set_item_onset(self.experiment.pptrigger.setData(0)) debug.msg(u'Resetting the parallel port to zero') except Exception as e: raise osexception( u'Wrong port address, could not access the Parallel Port', exception=e) elif self.pptrigger_dummy == u'yes': debug.msg(u'Dummy mode enabled, NOT sending value %s to the parallel port on address: %s' % (self.pptrigger_value,self.experiment.pptrigger_port)) else: debug.msg(u'Error with dummy mode!')
def autosave(self): """ desc: Autosave the experiment if there are unsaved changes. """ if self.main_window.unsaved_changes: path = os.path.join( self.autosave_folder, u'%s.osexp' % str(time.ctime()).replace(u':', u'_')) try: self.main_window.get_ready() self.experiment.save(path, overwrite=True, update_path=False) debug.msg(u"saving backup as %s" % path) except: pass self.start_autosave_timer()
def __init__(self): """Constructor""" # 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.init_edit_widget() self.init_script_widget() self.script_tab = None self.lock = False self.edit_mode = "edit" debug.msg("created %s" % self.name)
def from_string(self, string): """ desc: Reads the entire experiment from a string. arguments: string: The definition string. """ self.var.clear(preserve=[u'experiment_path', u'experiment_file']) self.reset() self.comments = [] debug.msg(u"building experiment") if string is None: return self.front_matter, string = self._syntax.parse_front_matter(string) if self.experiment.front_matter[u'API'].version[0] < 2: # Old experiment scripts were saved in ASCII, and require decoding # of U+XXXX unicode characters. string = self.syntax.from_ascii(string) s = iter(string.split(u'\n')) line = next(s, None) while line is not None: get_next = True try: l = self.syntax.split(line) except ValueError as e: raise osexception( u'Failed to parse script. Maybe it contains ' u'illegal characters or unclosed quotes?', exception=e) if l: self.parse_variable(line) # Parse definitions if l[0] == u"define": if len(l) != 3: raise osexception(u'Failed to parse definition', line=line) item_type = l[1] item_name = self.syntax.sanitize(l[2]) line, def_str = self.read_definition(s) get_next = False self.items.new(item_type, item_name, def_str) # Advance to next line if get_next: line = next(s, None)
def restore_name(self, apply_name_change=True): """ Apply the name change and revert the edit control back to the static label Keywords arguments: apply_name_change -- indicates of the name change should be applied (default=True) """ debug.msg(u"apply_name_change = %s" % apply_name_change) if apply_name_change: self.item.apply_name_change() self.refresh() self.label_name.show() self.edit_name.hide()
def supported_events(self): """ desc: Gives the events that are supported by the extension. This is done by introspecting which `event_[event name]` functions exist. returns: desc: A list of supported events. type: list """ events = [] for event in dir(self): if event.startswith(u'event_'): events.append(event[6:]) debug.msg(u'extension %s supports %s' % (self.name(), event)) return events
def refresh(self): """Update the controls of the general tab""" debug.msg() # Lock the general tab to prevent a recursive loop self.lock = True # Set the header containing the titel etc self.set_header_label() # Select the backend backend = openexp.backend_info.match(self.main_window.experiment) if backend == u"custom": self.ui.combobox_backend.setDisabled(True) else: self.ui.combobox_backend.setDisabled(False) desc = openexp.backend_info.backend_list[backend][u"description"] i = self.ui.combobox_backend.findText(self.backend_format \ % (backend, desc)) self.ui.combobox_backend.setCurrentIndex(i) # Set the resolution try: self.ui.spinbox_width.setValue(int( \ self.main_window.experiment.width)) self.ui.spinbox_height.setValue(int( \ self.main_window.experiment.height)) except: self.main_window.experiment.notify( \ _(u"Failed to parse the resolution. Expecting positive numeric values.")) # Set the colors self.ui.edit_foreground.setText(self.main_window.experiment.unistr( \ self.main_window.experiment.foreground)) self.ui.edit_background.setText(self.main_window.experiment.unistr( \ self.main_window.experiment.background)) # Set the font self.ui.widget_font.initialize(self.main_window.experiment) # Set variable transparency self.ui.checkbox_transparent_variables.setChecked( \ self.main_window.experiment.get(u'transparent_variables') == u'yes') # Release the general tab self.lock = False
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_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) if a.exec_() == QtGui.QDialog.Accepted: 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 = item.text() if row == 0: var = self.experiment.sanitize(s, True) var_dict[var] = [] elif var != None: var_dict[var].append(self.experiment.usanitize(s)) # 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()
def apply_desc(self): """ desc: Applies the description change and revert the edit back to the label. """ if self.label_desc.isVisible(): return debug.msg() description = unicode(self.edit_desc.text()) description = self.item.sanitize(description) self.item.set(u'description', description) self.label_desc.setText(description) self.label_desc.show() self.edit_desc.hide() self.item.apply_edit_changes()
def initialize(self, experiment, color=None): """ Initializes the widget. Arguments: experiment -- The experiment object. Keyword arguments: color -- A color to start with or None for experiment foreground default. (default=None) """ debug.msg(u'color = %s' % color) self.experiment = experiment if color == None: color = self.experiment.get(u'foreground', _eval=False) self.setText(color) self.button.setIcon(self.experiment.icon(u'colorpicker'))
def execute(self): """See base_runner.execute().""" # Exceptions during the run phase are important and returned so that the # user is notified. e = None try: self.experiment.run() except Exception as e: if not isinstance(e, osexception): e = osexception(u'Unexpected error', e) # Exceptions during the end phase are less important and only printed # to the debug window. try: self.experiment.end() except Exception as _e: debug.msg(u'Exception during experiment.end(): %s' % _e) return e