diff -Nru opensesame-3.1.7+a5/debian/changelog opensesame-3.1.7+b1/debian/changelog --- opensesame-3.1.7+a5/debian/changelog 2017-06-13 10:54:55.000000000 +0000 +++ opensesame-3.1.7+b1/debian/changelog 2017-07-06 08:43:33.000000000 +0000 @@ -1,5 +1,5 @@ -opensesame (3.1.7+a5-1) xenial; urgency=low +opensesame (3.1.7+b1-1) xenial; urgency=low * source package automatically created by stdeb 0.8.5 - -- Sebastiaan Mathot Tue, 13 Jun 2017 12:54:55 +0200 + -- Sebastiaan Mathot Thu, 06 Jul 2017 10:43:33 +0200 diff -Nru opensesame-3.1.7+a5/debian/control opensesame-3.1.7+b1/debian/control --- opensesame-3.1.7+a5/debian/control 2017-06-13 10:54:55.000000000 +0000 +++ opensesame-3.1.7+b1/debian/control 2017-07-06 08:43:33.000000000 +0000 @@ -2,7 +2,7 @@ Maintainer: Sebastiaan Mathot Section: python Priority: optional -Build-Depends: dh-python, python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 9), pyqt5-dev-tools, python-stdeb, dh-make +Build-Depends: dh-python, python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 9), pyqt5-dev-tools, python-stdeb Standards-Version: 3.9.6 Homepage: http://osdoc.cogsci.nl/ diff -Nru opensesame-3.1.7+a5/debian/rules opensesame-3.1.7+b1/debian/rules --- opensesame-3.1.7+a5/debian/rules 2017-06-13 10:54:55.000000000 +0000 +++ opensesame-3.1.7+b1/debian/rules 2017-07-06 08:43:33.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/make -f # This file was automatically generated by stdeb 0.8.5 at -# Tue, 13 Jun 2017 12:54:55 +0200 +# Thu, 06 Jul 2017 10:43:33 +0200 export PYBUILD_NAME=opensesame %: dh $@ --with python2 --buildsystem=pybuild diff -Nru opensesame-3.1.7+a5/libopensesame/exceptions.py opensesame-3.1.7+b1/libopensesame/exceptions.py --- opensesame-3.1.7+a5/libopensesame/exceptions.py 2016-08-17 08:29:31.000000000 +0000 +++ opensesame-3.1.7+b1/libopensesame/exceptions.py 2017-06-14 08:47:30.000000000 +0000 @@ -28,13 +28,12 @@ """ import re -from libopensesame.misc import escape_html from libopensesame import debug from libopensesame.item_stack import item_stack_singleton from libopensesame.py3compat import * import traceback import time -import sys + class osexception(Exception): @@ -45,7 +44,7 @@ a (usually harmless) bug somewhere. """ - def __init__(self, msg, exception=None, line_offset=0, **info): + def __init__(self, msg, exception=None, **info): """ desc: @@ -61,13 +60,6 @@ desc: An exception that was intercepted or None for self-generated exceptions. type: [Exception, NoneType] - line_offset: - desc: A value to decrease/ increase line numbers. The primary - goal for this keyword is to re-align line numbers that - arise in inline_scripts, which need to be compensated - for the source encoding line that is added to the - source. - type: int keyword-dict: info: Optional additional info for the exception. @@ -75,65 +67,130 @@ super(osexception, self).__init__(msg) # Create both HTML and plain text representations of the Exception. - self._md = u'%s\n\n' % msg - self._plaintext = u'\n%s\n\n' % msg self.enc = u'utf-8' - self.user_triggered = u'user_triggered' in info \ - and info[u'user_triggered'] - # If an Exception is passed, i.e. if we are catching an Exception, - # summarize this exception here. + self.user_triggered = info.get(u'user_triggered', False) self.exception = exception - if self.exception is not None: - info[u'exception type'] = safe_decode( - self.exception.__class__.__name__, enc=self.enc, - errors=u'ignore') - try: - msg = safe_decode(self.exception, errors=u'ignore') - except: - msg = u'Description unavailable' - info[u'exception message'] = msg - if isinstance(self.exception, SyntaxError): - # Syntax errors are dealt with specially, because they provide - # introspective information. - info[u'line'] = self.exception.lineno + line_offset - if self.exception.text is not None: - info[u'code'] = safe_decode(self.exception.text, - enc=self.enc, errors=u'ignore') - info[u'item-stack'] = str(item_stack_singleton) - info[u'time'] = time.ctime() - # List any additional information that was passed - self._md += u'## Details\n\n' - for key, val in info.items(): - self._md += u'- %s: `%s`\n' % (key, val) - self._plaintext += u'%s: %s\n' % (key, val) - self._md += u'\n' - # If an Exception is passed, we should include a traceback. + info = self._exception_info(msg, info) + self._md, self._plaintext = self._exception_details(msg, info) if self.exception is None: return + tb_md, tb_plaintext = self._parse_traceback(info) + self._md += tb_md + self._plaintext += tb_plaintext + + def _traceback(self): + + """ + desc: + Returns the traceback as a formatted string. + """ + if py3: - tb = traceback.format_exc() - else: - tb = safe_decode(traceback.format_exc(self.exception), enc=self.enc, - errors=u'ignore') - self._md += u'## Traceback (also in debug window)\n\n' - self._plaintext += u'\nTraceback:\n' + return traceback.format_exc() + return safe_decode( traceback.format_exc(self.exception), enc=self.enc, + errors=u'ignore') + + def _parse_traceback(self, info): + + """ + desc: + Processes the traceback by replacing generic references to + Inline script, and by correct the line offset to compensate for the + added utf-8 encoding header, which increments the line number by + one. + """ + + tb = self._traceback() + md = u'## Traceback (also in debug window)\n\n' + plaintext = u'\nTraceback:\n' _tb = u'' for l in tb.split(u'\n')[1:]: - # It is confusing that the contents of the inline script are - # described as , so replace that. In addition, we need to - # decrease the line numer by 1, to compensate for the extra (hidden) - # source-encoding line that the inline script has. - if u'item' in info and info[u'item'] == u'inline_script': + if u'line_offset' in info: for g in re.finditer( - u'File "", line (?P\d+),', l): + u'File "", line (?P\d+)', l): try: - l = l.replace(g.group(), u'Inline_script, line %d,' % \ - (int(g.group(u'linenr')) + line_offset)) + l = l.replace(g.group(), u'%s, line %d' % \ + (info.get(u'item_type', u'Inline script'), + int(g.group(u'linenr')) + info[u'line_offset']) + ) except: debug.msg(u'Failed to correct inline_script exception') _tb += l + u'\n' - self._plaintext += _tb - self._md += u'~~~ .traceback\n%s\n~~~\n' % _tb + plaintext += _tb + md += u'~~~ .traceback\n%s\n~~~\n' % _tb + return md, plaintext + + def _exception_details(self, msg, info): + + """ + desc: + Provides a markdown and plaintext overview of relevant information. + """ + + md = u'%s\n\n## Details\n\n' % msg + plaintext = u'\n%s\n\n' % msg + for key, val in info.items(): + if key == u'line_offset': # For internal use only + continue + md += u'- %s: `%s`\n' % (key, val) + plaintext += u'%s: %s\n' % (key, val) + md += u'\n' + return md, plaintext + + def _exception_info(self, msg, info): + + """ + desc: + Updates the info dict based on the type of Exception and the + exception message. + """ + + if isinstance(self.exception, SyntaxError): + return self._syntaxerror_info(msg, info) + return self._defaultexception_info(msg, info) + + def _syntaxerror_info(self, msg, info): + + """ + desc: + Updates the info dict specifically for SyntaxErrors + """ + + info = self._defaultexception_info(msg, info) + # Syntax errors are dealt with specially, because they provide + # introspective information. + for g in re.finditer(u', line (?P\d+)', msg): + msg = msg.replace(g.group(), u'%s, line %d' % ( + info.get(u'item_type', u'Inline script'), + int(g.group(u'linenr')) \ + + info.get(u'line_offset', -1)) + ) + info[u'exception message'] = msg + info[u'line'] = self.exception.lineno + info.get(u'line_offset', -1) + if self.exception.text is not None: + info[u'code'] = safe_decode(self.exception.text, enc=self.enc, + errors=u'ignore') + return info + + def _defaultexception_info(self, msg, info): + + """ + desc: + Updates the info dict for all Exceptions. + """ + + info[u'item-stack'] = str(item_stack_singleton) + info[u'time'] = time.ctime() + if self.exception is None: + return info + info[u'exception type'] = safe_decode( + self.exception.__class__.__name__, enc=self.enc, + errors=u'ignore') + try: + info[u'exception message'] = safe_decode(self.exception, errors=u'ignore') + except: + info[u'exception message'] = u'Description unavailable' + return info def __unicode__(self): diff -Nru opensesame-3.1.7+a5/libopensesame/experiment.py opensesame-3.1.7+b1/libopensesame/experiment.py --- opensesame-3.1.7+a5/libopensesame/experiment.py 2017-05-31 17:55:21.000000000 +0000 +++ opensesame-3.1.7+b1/libopensesame/experiment.py 2017-06-14 11:22:27.000000000 +0000 @@ -29,13 +29,11 @@ from libopensesame.py3compat import * import os import pickle -import shutil import time -import tarfile -import tempfile import warnings import gc + class experiment(item.item): """ diff -Nru opensesame-3.1.7+a5/libopensesame/metadata.py opensesame-3.1.7+b1/libopensesame/metadata.py --- opensesame-3.1.7+a5/libopensesame/metadata.py 2017-06-13 10:54:41.000000000 +0000 +++ opensesame-3.1.7+b1/libopensesame/metadata.py 2017-07-06 08:43:20.000000000 +0000 @@ -21,7 +21,7 @@ from distutils.version import StrictVersion import sys -__version__ = u'3.1.7a5' +__version__ = u'3.1.7b1' strict_version = StrictVersion(__version__) # The version without the prerelease (if any): e.g. 3.0.0 main_version = u'.'.join([str(i) for i in strict_version.version]) diff -Nru opensesame-3.1.7+a5/libqtopensesame/items/loop.py opensesame-3.1.7+b1/libqtopensesame/items/loop.py --- opensesame-3.1.7+a5/libqtopensesame/items/loop.py 2017-02-09 14:48:55.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/items/loop.py 2017-06-14 10:08:48.000000000 +0000 @@ -347,7 +347,8 @@ if item_name == self._item: self._item = u'' - self.update() + if not self.update(): + self.extension_manager.fire(u'change_item', name=self.name) self.main_window.set_unsaved(True) @qtstructure_item.clears_children_cache diff -Nru opensesame-3.1.7+a5/libqtopensesame/items/qtitem.py opensesame-3.1.7+b1/libqtopensesame/items/qtitem.py --- opensesame-3.1.7+a5/libqtopensesame/items/qtitem.py 2017-02-02 09:34:26.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/items/qtitem.py 2017-06-14 10:07:51.000000000 +0000 @@ -379,9 +379,10 @@ # Items are updated when their tab is shown, so we don't need to update # them if they aren't shown. if self.tabwidget.current_item() != self.name: - return + return False self.update_script() self.edit_widget() + return True def update_script(self): diff -Nru opensesame-3.1.7+a5/libqtopensesame/items/qtstructure_item.py opensesame-3.1.7+b1/libqtopensesame/items/qtstructure_item.py --- opensesame-3.1.7+a5/libqtopensesame/items/qtstructure_item.py 2016-10-20 09:44:54.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/items/qtstructure_item.py 2017-06-14 09:47:10.000000000 +0000 @@ -63,8 +63,7 @@ def inner(self, *args, **kwargs): - for item in self.experiment.items.values(): - item._children = None + self.experiment.items.clear_cache() return fnc(self, *args, **kwargs) return inner diff -Nru opensesame-3.1.7+a5/libqtopensesame/items/sequence.py opensesame-3.1.7+b1/libqtopensesame/items/sequence.py --- opensesame-3.1.7+a5/libqtopensesame/items/sequence.py 2016-10-20 09:44:54.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/items/sequence.py 2017-06-14 10:08:33.000000000 +0000 @@ -121,14 +121,14 @@ """See qtitem.""" if item_parent is None or (item_parent == self.name and index is None): - redo = True - while redo: - redo = False - for i in range(len(self.items)): - if self.items[i][0] == item_name: + while True: + for i, (child_item_name, child_run_if) in enumerate(self.items): + if child_item_name == item_name: self.items = self.items[:i]+self.items[i+1:] - redo = True break + else: + # Break the while loop if no break occurred in the for loop + break elif item_parent == self.name and index is not None: if self.items[index][0] == item_name: self.items = self.items[:index]+self.items[index+1:] @@ -218,5 +218,6 @@ # it's run-if statement if it is re-added. self.last_removed_child = self.items[index] del self.items[index] - self.update() + if not self.update(): + self.extension_manager.fire(u'change_item', name=self.name) self.main_window.set_unsaved(True) diff -Nru opensesame-3.1.7+a5/libqtopensesame/misc/base_component.py opensesame-3.1.7+b1/libqtopensesame/misc/base_component.py --- opensesame-3.1.7+a5/libqtopensesame/misc/base_component.py 2017-06-09 14:25:43.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/misc/base_component.py 2017-07-04 14:45:57.000000000 +0000 @@ -126,16 +126,16 @@ return main_window @staticmethod - def connect(slot, signals): - + def quick_connect(slot, signals): + """ desc: A convenience function to connect many signals to one slot. - + arguments: slot: a slot signals: a list of signals """ - + for signal in signals: signal.connect(slot) diff -Nru opensesame-3.1.7+a5/libqtopensesame/misc/qtitem_store.py opensesame-3.1.7+b1/libqtopensesame/misc/qtitem_store.py --- opensesame-3.1.7+a5/libqtopensesame/misc/qtitem_store.py 2016-09-28 14:11:21.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/misc/qtitem_store.py 2017-06-14 10:39:26.000000000 +0000 @@ -225,3 +225,14 @@ if _type in self.built_in_types: return True return False + + def clear_cache(self): + + """ + desc: + Clears the cache, currently only the cache with children for each + item. + """ + + for item in self.values(): + item._children = None diff -Nru opensesame-3.1.7+a5/libqtopensesame/widgets/general_properties.py opensesame-3.1.7+b1/libqtopensesame/widgets/general_properties.py --- opensesame-3.1.7+a5/libqtopensesame/widgets/general_properties.py 2017-06-09 14:38:27.000000000 +0000 +++ opensesame-3.1.7+b1/libqtopensesame/widgets/general_properties.py 2017-07-04 14:45:57.000000000 +0000 @@ -66,13 +66,13 @@ icon = info[u"icon"] self.ui.combobox_backend.addItem(self.main_window.theme.qicon( icon), self.backend_format % (name, desc)) - self.connect( + self.quick_connect( slot=self.main_window.ui.tabwidget.open_general_script, signals=[ self.ui.button_script_editor.clicked, self.ui.button_backend_settings.clicked - ]) - self.connect( + ]) + self.quick_connect( slot=self.apply_changes, signals=[ self.ui.combobox_backend.currentIndexChanged, @@ -83,7 +83,7 @@ self.ui.checkbox_uniform_coordinates.stateChanged, self.ui.edit_foreground.textEdited, self.ui.edit_background.textEdited, - self.ui.widget_font.font_changed, + self.ui.widget_font.font_changed, ]) self.tab_name = u'__general_properties__' self.on_activate = self.refresh diff -Nru opensesame-3.1.7+a5/openexp/_canvas/legacy.py opensesame-3.1.7+b1/openexp/_canvas/legacy.py --- opensesame-3.1.7+a5/openexp/_canvas/legacy.py 2017-04-13 08:37:19.000000000 +0000 +++ opensesame-3.1.7+b1/openexp/_canvas/legacy.py 2017-06-14 11:57:00.000000000 +0000 @@ -19,10 +19,11 @@ from libopensesame.py3compat import * from pygame.locals import * -import pygame import os +import pygame +import platform from libopensesame.exceptions import osexception -from libopensesame import debug, misc +from libopensesame import debug from openexp.backend import configurable from openexp._canvas import canvas from openexp._coordinates.legacy import legacy as legacy_coordinates @@ -82,6 +83,8 @@ self.antialias = True self.surface = self.experiment.surface.copy() self.clear() + if platform.system() == u'Darwin': + self.show = self._show_macos def set_config(self, **cfg): @@ -108,7 +111,24 @@ self.experiment.last_shown_canvas = self.surface pygame.display.flip() return pygame.time.get_ticks() + + def _show_macos(self): + + """ + visible: False + + desc: + On Mac OS, the display is sometimes not refreshed unless there is + some interaction with the event loop. Therefor we implement this + hack which is only used on Mac OS. + """ + self.experiment.surface.blit(self.surface, (0, 0)) + self.experiment.last_shown_canvas = self.surface + pygame.display.flip() + pygame.event.pump() + return pygame.time.get_ticks() + @configurable def clear(self): diff -Nru opensesame-3.1.7+a5/openexp/_sampler/legacy.py opensesame-3.1.7+b1/openexp/_sampler/legacy.py --- opensesame-3.1.7+a5/openexp/_sampler/legacy.py 2016-08-17 08:29:31.000000000 +0000 +++ opensesame-3.1.7+b1/openexp/_sampler/legacy.py 2017-06-14 11:25:28.000000000 +0000 @@ -186,9 +186,14 @@ print( u'openexp.sampler._legacy.init_sound(): mixer already initialized, closing') pygame.mixer.quit() - mixer.pre_init(experiment.var.sound_freq, experiment.var.sound_sample_size, \ - experiment.var.sound_channels, experiment.var.sound_buf_size) - mixer.init() + mixer.pre_init(experiment.var.sound_freq, + experiment.var.sound_sample_size, + experiment.var.sound_channels, + experiment.var.sound_buf_size) + try: + mixer.init() + except pygame.error: + print(u'openexp.sampler._legacy.init_sound(): failed to initialize mixer') @staticmethod def close_sound(experiment): diff -Nru opensesame-3.1.7+a5/opensesame_extensions/after_experiment/after_experiment.py opensesame-3.1.7+b1/opensesame_extensions/after_experiment/after_experiment.py --- opensesame-3.1.7+a5/opensesame_extensions/after_experiment/after_experiment.py 2017-02-02 09:34:26.000000000 +0000 +++ opensesame-3.1.7+b1/opensesame_extensions/after_experiment/after_experiment.py 2017-06-14 11:42:00.000000000 +0000 @@ -26,6 +26,7 @@ from libqtopensesame.misc.translate import translation_context _ = translation_context(u'after_experiment', category=u'extension') + class after_experiment(base_extension): """ @@ -50,7 +51,8 @@ else: self.handle_exception(ret_val) - def logfile(self): + @property + def _logfile(self): """ returns: @@ -63,6 +65,19 @@ if u'var' not in d or u'logfile' not in d[u'var']: return None return d[u'var'].logfile + + @property + def _extra_data_files(self): + + """ + returns: + A list of extra data files, such as eye-tracking data. + """ + + return list(filter( + lambda path: path != self._logfile, + self.console.get_workspace_globals().get(u'data_files', []) + )) def event_after_experiment_copy_logfile(self): @@ -71,9 +86,9 @@ Copies the logfile to the file pool. """ - if self.logfile() is None: + if self._logfile is None: return - self.main_window.ui.pool_widget.add([self.logfile()], + self.main_window.ui.pool_widget.add([self._logfile], rename=True) def event_after_experiment_open_logfile_folder(self): @@ -83,9 +98,9 @@ Opens the logfile folder. """ - if self.logfile() is None: + if self._logfile is None: return - misc.open_url(os.path.dirname(self.logfile())) + misc.open_url(os.path.dirname(self._logfile)) def event_after_experiment_open_logfile(self): @@ -94,9 +109,9 @@ Opens the logfile. """ - if self.logfile() is None: + if self._logfile is None: return - misc.open_url(self.logfile()) + misc.open_url(self._logfile) def handle_success(self): @@ -105,13 +120,19 @@ Shows a summary after successful completion of the experiment. """ - logfile = self.logfile() + logfile = self._logfile if logfile is None: logfile = u'Unknown logfile' md = safe_read(self.ext_resource(u'finished.md')) % { u'time': time.ctime(), u'logfile': logfile } + if self._extra_data_files: + md += u'\n' \ + + _(u'The following extra data files where created:') + u'\n\n' + for data_file in self._extra_data_files: + md += u'- `' + data_file + u'`\n' + md += u'\n' self.tabwidget.open_markdown(md, u'os-finished-success', _(u'Finished')) def handle_exception(self, e): diff -Nru opensesame-3.1.7+a5/opensesame_extensions/undo_manager/undo_manager.py opensesame-3.1.7+b1/opensesame_extensions/undo_manager/undo_manager.py --- opensesame-3.1.7+a5/opensesame_extensions/undo_manager/undo_manager.py 2016-08-17 08:29:31.000000000 +0000 +++ opensesame-3.1.7+b1/opensesame_extensions/undo_manager/undo_manager.py 2017-06-14 09:55:09.000000000 +0000 @@ -19,6 +19,7 @@ from libopensesame.py3compat import * from libqtopensesame.extensions import base_extension, suspend_events +from libqtopensesame.items.qtstructure_item import qtstructure_item from qtpy.QtWidgets import QMenu, QToolBar from undo_stack import undo_stack import difflib @@ -66,6 +67,15 @@ """ self.remember_item_state(name) + + def event_new_item(self, name, _type): + + """ + desc: + Remember addition of a new item. + """ + + self.remember_new_item(name) def activate(self): @@ -152,6 +162,12 @@ self.stack.add(u'__experiment__', script) self.set_enabled(self.stack.can_redo()) self.undo_action.setEnabled(self.stack.can_undo()) + + def remember_new_item(self, name): + + self.stack.add(u'__newitem__', name) + self.set_enabled(self.stack.can_redo()) + self.undo_action.setEnabled(self.stack.can_undo()) def prune_unchanged_experiment(self): @@ -161,6 +177,8 @@ there is no change between them, discard them. """ + if len(self.stack) < 2: + return False key1, state1 = self.stack.peek(-1) key2, state2 = self.stack.peek(-2) if key1 == key2 == u'__experiment__' and state1 == state2: @@ -176,17 +194,23 @@ Perform an undo action. """ - if len(self.stack) == 0: + if not self.stack: return item, script = self.stack.undo() if item == u'__experiment__': self.main_window.regenerate(script) + elif item == u'__newitem__': + del self.experiment.items[script] + self.tabwidget.close_all() else: if item not in self.experiment.items: return self.experiment.items[item].from_string(script) self.experiment.items[item].update() self.experiment.items[item].open_tab() + if isinstance(self.experiment.items[item], qtstructure_item): + self.experiment.items.clear_cache() + self.experiment.build_item_tree() self.set_enabled(self.stack.can_redo()) self.undo_action.setEnabled(self.stack.can_undo()) @@ -204,6 +228,9 @@ self.experiment.items[item].from_string(script) self.experiment.items[item].update() self.experiment.items[item].open_tab() + if isinstance(self.experiment.items[item], qtstructure_item): + self.experiment.items.clear_cache() + self.experiment.build_item_tree() self.set_enabled(self.stack.can_redo()) self.undo_action.setEnabled(self.stack.can_undo()) @@ -222,6 +249,8 @@ if item == u'__experiment__': old_script = self.stack.peek(-2)[1] new_script = self.stack.peek(-1)[1] + elif item == u'__newitem__': + new_script = old_script else: new_script = self.stack.current[item][0] for line in difflib.ndiff(old_script.splitlines(), diff -Nru opensesame-3.1.7+a5/opensesame_extensions/undo_manager/undo_stack.py opensesame-3.1.7+b1/opensesame_extensions/undo_manager/undo_stack.py --- opensesame-3.1.7+a5/opensesame_extensions/undo_manager/undo_stack.py 2016-08-17 08:29:31.000000000 +0000 +++ opensesame-3.1.7+b1/opensesame_extensions/undo_manager/undo_stack.py 2017-06-14 09:31:05.000000000 +0000 @@ -36,8 +36,8 @@ def add(self, key, state): self.future = [] - if key == u'__experiment__': - self.history.append( (u'__experiment__', state) ) + if key in (u'__experiment__', u'__newitem__'): + self.history.append( (key, state) ) return timestamp = time.time() if key in self.current: @@ -54,11 +54,11 @@ def can_undo(self): - return len(self.history) > 0 + return bool(self.history) def can_redo(self): - return len(self.future) > 0 + return bool(self.future) def __len__(self): @@ -66,15 +66,20 @@ def pop(self, l1, l2): - if len(l1) == 0: + if not l1: return None, None key, state = l1.pop() + # Both undoing experiment changes and adding new items clears the future + # That is, these cannot be redone if key == u'__experiment__': self.future = [] _key, _state = l1.pop() if _key != u'__experiment__': return None, None return _key, _state + if key == u'__newitem__': + self.future = [] + return u'__newitem__', state l2.append( (key, self.current[key][0]) ) self.current[key] = state, time.time() return key, state @@ -89,6 +94,9 @@ def peek(self, i=-1): - if len(self.history) == 0: + if not(self.history): + return None, None + try: + return self.history[i] + except IndexError: return None, None - return self.history[i] diff -Nru opensesame-3.1.7+a5/opensesame_resources/android/android.json opensesame-3.1.7+b1/opensesame_resources/android/android.json --- opensesame-3.1.7+a5/opensesame_resources/android/android.json 2017-02-07 09:16:17.000000000 +0000 +++ opensesame-3.1.7+b1/opensesame_resources/android/android.json 2017-07-06 08:21:38.000000000 +0000 @@ -4,7 +4,7 @@ "include_pil": true, "name": "OpenSesame runtime for Android", "icon_name": "OpenSesame", -"version": "3.1.6", +"version": "3.1.7", "permissions": ["INTERNET", "VIBRATE"], "include_sqlite": false, -"numeric_version": "69"} +"numeric_version": "70"} diff -Nru opensesame-3.1.7+a5/PKG-INFO opensesame-3.1.7+b1/PKG-INFO --- opensesame-3.1.7+a5/PKG-INFO 2017-06-13 10:54:56.000000000 +0000 +++ opensesame-3.1.7+b1/PKG-INFO 2017-07-06 08:43:35.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-opensesame -Version: 3.1.7+a5 +Version: 3.1.7+b1 Summary: A graphical experiment builder for the social sciences Home-page: http://osdoc.cogsci.nl/ Author: Sebastiaan Mathot diff -Nru opensesame-3.1.7+a5/python_opensesame.egg-info/PKG-INFO opensesame-3.1.7+b1/python_opensesame.egg-info/PKG-INFO --- opensesame-3.1.7+a5/python_opensesame.egg-info/PKG-INFO 2017-06-13 10:54:54.000000000 +0000 +++ opensesame-3.1.7+b1/python_opensesame.egg-info/PKG-INFO 2017-07-06 08:43:33.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-opensesame -Version: 3.1.7+a5 +Version: 3.1.7+b1 Summary: A graphical experiment builder for the social sciences Home-page: http://osdoc.cogsci.nl/ Author: Sebastiaan Mathot