commit 1a53050174a421e859e388ef586d0d4d04419a79 Author: Jan Grewe Date: Thu Mar 4 17:55:40 2021 +0100 initials diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8152df1 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# BlibBlop + +A PyQt5 based cross-platform experiment for measuring reaction times in human observers. + + + + +## Dependencies +- PyQt5 +- numpy diff --git a/blipblob.py b/blipblob.py new file mode 100644 index 0000000..9e145ca --- /dev/null +++ b/blipblob.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +from blipblop import main + +if __name__ == "__main__": + main.main() \ No newline at end of file diff --git a/blipblop/__init__.py b/blipblop/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blipblop/__pycache__/__init__.cpython-38.pyc b/blipblop/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d48c5aa Binary files /dev/null and b/blipblop/__pycache__/__init__.cpython-38.pyc differ diff --git a/blipblop/__pycache__/constants.cpython-38.pyc b/blipblop/__pycache__/constants.cpython-38.pyc new file mode 100644 index 0000000..3bee8fc Binary files /dev/null and b/blipblop/__pycache__/constants.cpython-38.pyc differ diff --git a/blipblop/__pycache__/main.cpython-38.pyc b/blipblop/__pycache__/main.cpython-38.pyc new file mode 100644 index 0000000..76b2ee1 Binary files /dev/null and b/blipblop/__pycache__/main.cpython-38.pyc differ diff --git a/blipblop/constants.py b/blipblop/constants.py new file mode 100644 index 0000000..9faa640 --- /dev/null +++ b/blipblop/constants.py @@ -0,0 +1,25 @@ +import os +import glob +from PyQt5.QtGui import QIcon + +organization = "bendalab" +application = "blipblop" +version = 0.1 + +PACKAGE_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) +ICONS_FOLDER = os.path.join(PACKAGE_ROOT, "icons") +DOCS_ROOT_FILE = os.path.join(PACKAGE_ROOT, "docs", "index.md") + +ICONS_PATHS = glob.glob(os.path.join(ICONS_FOLDER, "*.png")) +ICONS_PATHS.extend(glob.glob(os.path.join(ICONS_FOLDER, "*.icns"))) +ICONS_PATHS = sorted(ICONS_PATHS) +ICON_DICT = {} +for icon in ICONS_PATHS: + ICON_DICT[icon.split(os.sep)[-1].split(".")[0]] = icon + +def get_icon(name): + if name in ICON_DICT.keys(): + return QIcon(ICON_DICT[name]) + else: + return QIcon("nix_logo.png") + diff --git a/blipblop/info.json b/blipblop/info.json new file mode 100644 index 0000000..391b2ce --- /dev/null +++ b/blipblop/info.json @@ -0,0 +1,20 @@ +{ + "NAME": "BlibBlop", + "VERSION": "0.1", + "DESCRIPTION": "Measure reaction times to visual or auditory signals", + "AUTHOR": "Jan Grewe", + "COPYRIGHT": "(c) 2021, Neuroethology lab, Uni Tuebingen", + "CONTACT": "jan.grewe@g-node.org", + "HOMEPAGE": "https://github.com/bendalab/nixview-python", + "CLASSIFIERS": [ + "Programming Language :: Python", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Scientific/Engineering", + "Intended Audience :: Science/Research", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: BSD License" + ] +} \ No newline at end of file diff --git a/blipblop/main.py b/blipblop/main.py new file mode 100644 index 0000000..797dff2 --- /dev/null +++ b/blipblop/main.py @@ -0,0 +1,15 @@ +#!/usr/bin/python3 +import sys +from PyQt5.QtWidgets import QApplication +from blipblop.ui.mainwindow import BlipBlop + +def main(): + app = QApplication(sys.argv) + window = BlipBlop() + window.setMinimumWidth(800) + window.setMinimumHeight(600) + window.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/blipblop/ui/__init__.py b/blipblop/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blipblop/ui/__pycache__/__init__.cpython-38.pyc b/blipblop/ui/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..7eaab7d Binary files /dev/null and b/blipblop/ui/__pycache__/__init__.cpython-38.pyc differ diff --git a/blipblop/ui/__pycache__/about.cpython-38.pyc b/blipblop/ui/__pycache__/about.cpython-38.pyc new file mode 100644 index 0000000..cee00e8 Binary files /dev/null and b/blipblop/ui/__pycache__/about.cpython-38.pyc differ diff --git a/blipblop/ui/__pycache__/help.cpython-38.pyc b/blipblop/ui/__pycache__/help.cpython-38.pyc new file mode 100644 index 0000000..1662bf9 Binary files /dev/null and b/blipblop/ui/__pycache__/help.cpython-38.pyc differ diff --git a/blipblop/ui/__pycache__/helpdialog.cpython-38.pyc b/blipblop/ui/__pycache__/helpdialog.cpython-38.pyc new file mode 100644 index 0000000..6637e6a Binary files /dev/null and b/blipblop/ui/__pycache__/helpdialog.cpython-38.pyc differ diff --git a/blipblop/ui/__pycache__/mainwindow.cpython-38.pyc b/blipblop/ui/__pycache__/mainwindow.cpython-38.pyc new file mode 100644 index 0000000..1d2da90 Binary files /dev/null and b/blipblop/ui/__pycache__/mainwindow.cpython-38.pyc differ diff --git a/blipblop/ui/about.py b/blipblop/ui/about.py new file mode 100644 index 0000000..3a837d0 --- /dev/null +++ b/blipblop/ui/about.py @@ -0,0 +1,58 @@ +import os +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QWidget +from PyQt5.QtCore import Qt + +import blipblop.constants as cnst + + +class AboutDialog(QDialog): + + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + self.setModal(True) + about = About(self) + self.setLayout(QVBoxLayout()) + self.layout().addWidget(about) + bbox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) + bbox.accepted.connect(self.accept) + self.layout().addWidget(bbox) + + +class About(QWidget): + + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + self.setLayout(QVBoxLayout()) + + heading = QLabel("BlipBlop") + font = heading.font() + font.setPointSize(18) + font.setBold(True) + heading.setFont(font) + heading.setAlignment(Qt.AlignCenter) + subheading = QLabel("How fast are you?\nmeasure your reaction times to visual and auditory stimuli.\nby Jan Grewe") + subheading.setAlignment(Qt.AlignCenter) + nix_link = QLabel("https://github.com/jgrewe/blipblop") + nix_link.setOpenExternalLinks(True) + nix_link.setAlignment(Qt.AlignCenter) + + # rtd_link = QLabel("https://nixio.readthedocs.io/en/master/") + # rtd_link.setOpenExternalLinks(True) + # rtd_link.setAlignment(Qt.AlignCenter) + + iconlabel = QLabel() + pixmap = QPixmap(os.path.join(cnst.ICONS_FOLDER, "nix_logo.png")) + s = pixmap.size() + new_height = int(s.height() * 300/s.width()) + pixmap = pixmap.scaled(300, new_height, Qt.KeepAspectRatio, Qt.FastTransformation) + iconlabel.setPixmap(pixmap) + iconlabel.setMaximumWidth(300) + iconlabel.setAlignment(Qt.AlignCenter) + iconlabel.setScaledContents(True) + + self.layout().addWidget(heading) + self.layout().addWidget(subheading) + self.layout().addWidget(iconlabel) + self.layout().addWidget(nix_link) + # self.layout().addWidget(rtd_link) diff --git a/blipblop/ui/audioblop.py b/blipblop/ui/audioblop.py new file mode 100644 index 0000000..7934ea7 --- /dev/null +++ b/blipblop/ui/audioblop.py @@ -0,0 +1,22 @@ +from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QSplitter, QTextEdit, QVBoxLayout, QWidget +from PyQt5.QtCore import QItemSelectionModel, Qt + +import blipblop.constants as cnst + + +class AudioBlop(QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + + vbox = QVBoxLayout() + self.setLayout(vbox) + + main_splitter = QSplitter(Qt.Vertical) + self.layout().addWidget(main_splitter) + + + + + def reset(self): + pass + diff --git a/blipblop/ui/centralwidget.py b/blipblop/ui/centralwidget.py new file mode 100644 index 0000000..12b9128 --- /dev/null +++ b/blipblop/ui/centralwidget.py @@ -0,0 +1,45 @@ +from PyQt5.QtWidgets import QStackedLayout, QWidget + +from blipblop.ui.startscreen import StartScreen +from blipblop.ui.visualblip import VisualBlip +from blipblop.ui.audioblop import AudioBlop +from blipblop.ui.resultsscreen import ResultsScreen + +class CentralWidget(QWidget): + + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + + self._splash = StartScreen() + self._visual_screen = VisualBlip(self) + self._visual_screen.close_signal.connect(self.on_plot_close) + + self._auditory_screen = AudioBlop(self) + self._auditory_screen.close_signal.connect(self.on_plot_close) + + self._results_screen = AudioBlop(self) + self._results_screen.close_signal.connect(self.on_plot_close) + + self._stack = QStackedLayout(self) + self._stack.addWidget(self._splash) + self._stack.addWidget(self._visual_screen) + self._stack.addWidget(self._auditory_screen) + self._stack.addWidget(self._results_screen) + + self.setLayout(self._stack) + + def show_file_content(self): + self._stack.setCurrentIndex(1) + self._visual_stims_screen.update() + + def plot_item(self, item_descriptor): + self._stack.setCurrentIndex(2) + self._auditory_screen.plot(item_descriptor) + + def on_plot_close(self): + self._stack.setCurrentIndex(1) + + def reset(self): + self._visual_stims_screen.reset() + self._splash.reset() + self._stack.setCurrentIndex(0) diff --git a/blipblop/ui/filescreen.py b/blipblop/ui/filescreen.py new file mode 100644 index 0000000..8ea6c98 --- /dev/null +++ b/blipblop/ui/filescreen.py @@ -0,0 +1,142 @@ +from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QSplitter, QTextEdit, QVBoxLayout, QWidget +from PyQt5.QtCore import QItemSelectionModel, Qt + +from nixview.util.file_handler import FileHandler +from nixview.util.descriptors import ItemDescriptor +import nixview.communicator as comm +import nixview.constants as cnst +from nixview.data_models.tree_model import NixTreeView, TreeModel, TreeType + + +class FileScreen(QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + self._file_handler = FileHandler() + + vbox = QVBoxLayout() + self.setLayout(vbox) + + main_splitter = QSplitter(Qt.Vertical) + self.layout().addWidget(main_splitter) + + self._info = EntityInfo(self) + main_splitter.addWidget(self._info) + + self._data_tree = NixTreeView(self) + + self._tree_type_combo = QComboBox() + self._tree_type_combo.adjustSize() + self._tree_type_combo.addItems([TreeType.Data.value, TreeType.Full.value, TreeType.Metadata.value]) + self._tree_type_combo.currentTextChanged.connect(self.update) + + hbox = QHBoxLayout() + hbox.addWidget(QLabel("Tree type:")) + hbox.addWidget(self._tree_type_combo) + hbox.addStretch() + data_group = QGroupBox("Data") + data_vbox = QVBoxLayout() + data_vbox.setContentsMargins(1, 10, 1, 1) + + data_vbox.addLayout(hbox) + data_vbox.addWidget(self._data_tree) + data_group.setLayout(data_vbox) + + main_splitter.addWidget(data_group) + main_splitter.setSizes([200, 600]) + vbox.addWidget(main_splitter) + + def dataTreeSelection(self, current_index, last_index): + if not current_index.isValid(): + return + item = current_index.internalPointer() + comm.communicator.item_selected.emit(item) + self._info.setEntityInfo(item.node_descriptor) + + def update(self): + tt = TreeType.Data + if self._tree_type_combo.currentText() == TreeType.Data.value: + tt = TreeType.Data + elif self._tree_type_combo.currentText() == TreeType.Full.value: + tt = TreeType.Full + elif self._tree_type_combo.currentText() == TreeType.Metadata.value: + tt = TreeType.Metadata + self._info.setEntityInfo(None) + data_model = TreeModel(self._file_handler, tt) + self._data_tree.setModel(data_model) + selection_model = QItemSelectionModel(data_model) + self._data_tree.setSelectionModel(selection_model) + selection_model.currentChanged.connect(self.dataTreeSelection) + for i in range(data_model.columnCount(None)): + self._data_tree.resizeColumnToContents(i) + self._info.setFileInfo(self._file_handler.file_descriptor) + + def reset(self): + pass + + +class EntityInfo(QWidget): + + def __init__(self, parent): + super().__init__(parent=parent) + self._file_handler = FileHandler() + self.setLayout(QHBoxLayout()) + + self._metadata_tree = NixTreeView() + + mdata_grp = QGroupBox("Metadata") + mdata_grp.setLayout(QVBoxLayout()) + mdata_grp.layout().setContentsMargins(1, 10, 1, 1) + mdata_grp.layout().addWidget(self._metadata_tree) + + file_info_grp = QGroupBox("File info") + file_info_grp.setLayout(QVBoxLayout()) + file_info_grp.layout().setContentsMargins(1, 10, 1, 1) + self._file_info = QTextEdit("File information") + self._file_info.setEnabled(True) + self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) + self._file_info.setFrameShape(QFrame.NoFrame) + self._file_info.setLineWrapMode(QTextEdit.WidgetWidth) + file_info_grp.layout().addWidget(self._file_info) + + entity_info_grp = QGroupBox("Entity info") + entity_info_grp.setLayout(QVBoxLayout()) + entity_info_grp.layout().setContentsMargins(1, 10, 1, 1) + self._entity_info = QTextEdit("Entity information") + self._file_info.setEnabled(True) + self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) + self._file_info.setFrameShape(QFrame.NoFrame) + self._file_info.setLineWrapMode(QTextEdit.WidgetWidth) + entity_info_grp.layout().addWidget(self._entity_info) + + self._splitter = QSplitter(Qt.Horizontal) + self._splitter.addWidget(file_info_grp) + self._splitter.addWidget(entity_info_grp) + self._splitter.addWidget(mdata_grp) + self._splitter.setSizes([200, 400, 0]) + self._splitter.setStretchFactor(0, 0) + self._splitter.setStretchFactor(1, 1) + self._splitter.setStretchFactor(2, 1) + + self.layout().addWidget(self._splitter) + + + def setFileInfo(self, file_info): + if file_info is not None: + self._file_info.setText(file_info.toHtml()) + + def setEntityInfo(self, entity_info): + if entity_info is None or not isinstance(entity_info, ItemDescriptor): + self._splitter.setSizes([200, 400, 0]) + self._entity_info.setText("") + self._metadata_tree.setModel(None) + return + + if entity_info.metadata_id is not None: + self._splitter.setSizes([200, 400, 400]) + else: + self._splitter.setSizes([200, 400, 0]) + self._entity_info.setText(entity_info.to_html()) + metadata_model = TreeModel(self._file_handler, TreeType.Metadata, root_section_id=entity_info.metadata_id) + self._metadata_tree.setModel(metadata_model) + for i in range(metadata_model.columnCount(None)): + self._metadata_tree.resizeColumnToContents(i) diff --git a/blipblop/ui/help.py b/blipblop/ui/help.py new file mode 100644 index 0000000..8baa3b5 --- /dev/null +++ b/blipblop/ui/help.py @@ -0,0 +1,65 @@ +import os +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QPushButton, QSizePolicy, QTextBrowser, QVBoxLayout, QWidget +from PyQt5.QtCore import QUrl + +import blipblop.constants as cnst + +class HelpDialog(QDialog): + + def __init__(self, parent = None) -> None: + super().__init__(parent=parent) + + self.setModal(True) + self.setMinimumSize(500, 750) + + self.help = HelpBrowser() + + self.help._edit.historyChanged.connect(self._on_history_changed) + + self.back_btn = QPushButton(QIcon(os.path.join(cnst.ICONS_FOLDER, "back_btn")), "back") + self.back_btn.setEnabled(False) + self.back_btn.clicked.connect(self.help._edit.backward) + self.home_btn = QPushButton(QIcon(os.path.join(cnst.ICONS_FOLDER, "home_btn")),"home") + self.home_btn.clicked.connect(self.help._edit.home) + self.fwd_btn = QPushButton(QIcon(os.path.join(cnst.ICONS_FOLDER, "fwd_btn")),"forward") + self.fwd_btn.setEnabled(False) + self.fwd_btn.clicked.connect(self.help._edit.forward) + + empty = QWidget() + empty.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + + hbox = QHBoxLayout() + hbox.addWidget(self.back_btn) + hbox.addWidget(self.home_btn) + hbox.addWidget(self.fwd_btn) + hbox.addWidget(empty) + + bbox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok) + bbox.accepted.connect(self.accept) + layout = QVBoxLayout() + + layout.addLayout(hbox) + layout.addWidget(self.help) + layout.addWidget(bbox) + self.setLayout(layout) + + def _on_history_changed(self): + self.back_btn.setEnabled(self.help._edit.isBackwardAvailable()) + self.fwd_btn.setEnabled(self.help._edit.isForwardAvailable()) + + +class HelpBrowser(QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + self.setLayout(QVBoxLayout()) + + doc_url = QUrl.fromLocalFile(cnst.DOCS_ROOT_FILE) + self._edit = QTextBrowser() + self._edit.setOpenLinks(True) + self._edit.setOpenExternalLinks(True) + self._edit.setSource(doc_url) + self._edit.setEnabled(True) + self._edit.setFrameShape(QFrame.NoFrame) + + self.layout().addWidget(self._edit) diff --git a/blipblop/ui/mainwindow.py b/blipblop/ui/mainwindow.py new file mode 100644 index 0000000..f0ef405 --- /dev/null +++ b/blipblop/ui/mainwindow.py @@ -0,0 +1,133 @@ +import sys +from PyQt5.QtWidgets import QWidget, QFileDialog, QMainWindow, QMenuBar, QToolBar, QAction, QStatusBar, QSizePolicy +from PyQt5.QtGui import QKeySequence +from PyQt5.QtCore import QSize, QSettings, Qt + +import blipblop.constants as cnst +from blipblop.ui.help import HelpDialog +from blipblop.ui.about import AboutDialog + + +class BlipBlop(QMainWindow): + + def __init__(self, *args, **kwargs): + super(BlipBlop, self).__init__(*args, **kwargs) + self._current_item = None + self.setWindowTitle("BlipBlop") + + self.setStatusBar(QStatusBar(self)) + self.setMenuBar(QMenuBar(self)) + self.create_actions() + self._task_results = [] + + self.show() + + def create_actions(self): + self._quit_action = QAction(cnst.get_icon("nixview_quit"), "Quit", self) + self._quit_action.setStatusTip("Close current file and quit") + self._quit_action.setShortcut(QKeySequence("Ctrl+q")) + self._quit_action.triggered.connect(self.on_quit) + + self._new_action = QAction(cnst.get_icon("nixview_quit"), "New session", self) + self._new_action.setStatusTip("Start a new session discarding previous results") + self._new_action.setShortcut(QKeySequence("Ctrl+n")) + self._new_action.triggered.connect(self.on_new) + + """ + self._plot_action = QAction(cnst.get_icon("nix_data_array"), "Plot", self) + self._plot_action.setStatusTip("Plot currently selected entity") + self._plot_action.setShortcut(QKeySequence("Ctrl+p")) + self._plot_action.setEnabled(False) + self._plot_action.triggered.connect(self.on_item_plot) + """ + + self._results_action = QAction(cnst.get_icon("nix_data_frame"), "Show table", self) + self._results_action.setStatusTip("Show data as table") + self._results_action.setShortcut(QKeySequence("Ctrl+t")) + self._results_action.setEnabled(False) + # self._table_action.triggered.connect(self.on_file_close) + + self._about_action = QAction("about") + self._about_action.setStatusTip("Show about dialog") + self._about_action.setEnabled(True) + self._about_action.triggered.connect(self.on_about) + + self._help_action = QAction(cnst.get_icon("nixview_help"), "help") + self._help_action.setStatusTip("Show help dialog") + self._help_action.setShortcut(QKeySequence("F1")) + self._help_action.setEnabled(True) + self._help_action.triggered.connect(self.on_help) + + self._visual_task_action = QAction("visual") + self._visual_task_action.setStatusTip("Start measuring visual reaction times") + self._visual_task_action.setEnabled(False) + + self._auditory_task_action = QAction("auditory") + self._auditory_task_action.setStatusTip("Start measuring auditory reaction times") + self._auditory_task_action.setEnabled(False) + + self.create_toolbar() + self.create_menu() + + def create_toolbar(self): + self._toolbar = QToolBar("My main toolbar") + #self._toolbar.setStyleSheet("QToolButton:!hover {background-color:none}") + self._toolbar.setAllowedAreas(Qt.LeftToolBarArea | Qt.TopToolBarArea) + self._toolbar.setIconSize(QSize(32, 32)) + + # self._toolbar.addAction(self._file_open_action) + #self._toolbar.addAction(self._file_close_action) + self._toolbar.addAction(self._new_action) + + self._toolbar.addSeparator() + self._toolbar.addAction(self._visual_task_action) + self._toolbar.addAction(self._auditory_task_action) + self._toolbar.addAction(self._results_action) + self._toolbar.addAction(self._help_action) + + empty = QWidget() + empty.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self._toolbar.addWidget(empty) + self._toolbar.addSeparator() + self._toolbar.addAction(self._quit_action) + + self.addToolBar(Qt.LeftToolBarArea, self._toolbar) + + def create_menu(self): + menu = self.menuBar() + file_menu = menu.addMenu("&File") + # file_menu.addAction(self._file_open_action) + # file_menu.addAction(self._file_close_action) + # file_menu.addSeparator() + file_menu.addAction(self._quit_action) + + task_menu = menu.addMenu("&Tasks") + task_menu.addAction(self._new_action) + task_menu.addSeparator() + task_menu.addAction(self._visual_task_action) + task_menu.addAction(self._auditory_task_action) + #plot_menu = menu.addMenu("&Plot") + #plot_menu.addAction(self._plot_action) + #plot_menu.addAction(self._table_action) + + help_menu = menu.addMenu("&Help") + help_menu.addAction(self._about_action) + help_menu.addAction(self._help_action) + self.setMenuBar(menu) + + def on_quit(self, s): + sys.exit() + + def on_new(self): + self._task_results = [] + self.__ + pass + + def on_about(self): + about = AboutDialog(self) + about.show() + pass + + def on_help(self, e): + help = HelpDialog(self) + help.show() \ No newline at end of file diff --git a/blipblop/ui/resultsscreen.py b/blipblop/ui/resultsscreen.py new file mode 100644 index 0000000..f18333a --- /dev/null +++ b/blipblop/ui/resultsscreen.py @@ -0,0 +1,19 @@ +from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QSplitter, QTextEdit, QVBoxLayout, QWidget +from PyQt5.QtCore import QItemSelectionModel, Qt + +import blipblop.constants as cnst + + +class ResultsScreen(QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + + vbox = QVBoxLayout() + self.setLayout(vbox) + + main_splitter = QSplitter(Qt.Vertical) + self.layout().addWidget(main_splitter) + + + def reset(self): + pass diff --git a/blipblop/ui/startscreen.py b/blipblop/ui/startscreen.py new file mode 100644 index 0000000..fe3b674 --- /dev/null +++ b/blipblop/ui/startscreen.py @@ -0,0 +1,71 @@ +import os +from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel, QFrame, QVBoxLayout, QListWidget, QAbstractItemView, QListWidgetItem +from PyQt5.QtGui import QPixmap +from PyQt5.QtCore import Qt, QSettings, pyqtSignal + +import blipblop.constants as cnst + + +class StartScreen(QWidget): + keyPressed = pyqtSignal(int) + + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + #self.setStyleSheet("background-color: white;") + + layout = QGridLayout() + layout.setColumnStretch(0, 2) + layout.setColumnStretch(1, 1) + layout.setColumnStretch(2, 1) + layout.setColumnStretch(3, 1) + layout.setColumnStretch(4, 2) + + layout.setRowStretch(0, 1) + layout.setRowStretch(1, 0) + layout.setRowStretch(2, 1) + layout.setRowStretch(3, 2) + self.setLayout(layout) + + label = QLabel("Measure your reaction times!\nselect a task!") + # label.setPixmap(QPixmap(os.path.join(cnst.ICONS_FOLDER, "nixview_transparent.png"))) + # label.setMaximumWidth(300) + label.setAlignment(Qt.AlignCenter) + layout.addWidget(label, 1, 1, 1, 3, Qt.AlignCenter) + + frame = QFrame() + l = QVBoxLayout() + l.addWidget(QLabel("Recently opened files:")) + self._file_list = QListWidget(self) + self._file_list.setSelectionMode(QAbstractItemView.SingleSelection) + self._file_list.itemClicked.connect(self._on_file_clicked) + self._file_list.setFrameShape(QFrame.NoFrame) + self.keyPressed.connect(self._on_key_pressed) + l.addWidget(self._file_list) + frame.setLayout(l) + layout.addWidget(frame, 3, 1, 1, 3) + self._file_map = {} + + def keyPressEvent(self, event): + super(StartScreen, self).keyPressEvent(event) + if event.key() == Qt.Key_Return: + self.keyPressed.emit(event.key()) + + def _create_short_filename(self, original, index, max_len=40): + short = original + parts = original.split(os.sep) + if len(parts) == 1: + short = "%i: %s" % (index, short[:max_len]) + else: + post = parts[-1] + if len(post) > max_len - 4: + post = post[:max_len - 4] + short = str("%i: " % index) + "... " + post + else: + short = str("%i: " % index) + " ... ".join([original[:max_len - len(post) - 4], post]) + return short + + def reset(self): + pass + + def _on_key_pressed(self, key): + pass diff --git a/blipblop/ui/visualblip.py b/blipblop/ui/visualblip.py new file mode 100644 index 0000000..85fc044 --- /dev/null +++ b/blipblop/ui/visualblip.py @@ -0,0 +1,22 @@ +from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QSplitter, QTextEdit, QVBoxLayout, QWidget +from PyQt5.QtCore import QItemSelectionModel, Qt + +import blipblop.constants as cnst + + +class VisualBlip(QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + + vbox = QVBoxLayout() + self.setLayout(vbox) + + main_splitter = QSplitter(Qt.Vertical) + self.layout().addWidget(main_splitter) + + + + + def reset(self): + pass + diff --git a/blipblop/util/__init__.py b/blipblop/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blipblop/util/results.py b/blipblop/util/results.py new file mode 100644 index 0000000..d12461c --- /dev/null +++ b/blipblop/util/results.py @@ -0,0 +1,24 @@ +import datetime as dt + +class MeasurementResults(): + + def __init__(self, task_name, metadata={}): + self._task_name = task_name + self._task_timestamp = dt.datetime.now() + self._reaction_times = [] + self._metadata = metadata + + def append(self, reaction_time): + self._reaction_times.append(reaction_time) + + @property + def name(self): + return self._task_name + + @property + def starttime(self): + return str(self._task_timestamp) + + @property + def results(self): + return self._reaction_times diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..8bb5014 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,4 @@ +# BlipBlop + +Tiny tool for measuring reaction times upon auditory or visual stimuli. + diff --git a/icons/back_btn.png b/icons/back_btn.png new file mode 100644 index 0000000..e6d74b8 Binary files /dev/null and b/icons/back_btn.png differ diff --git a/icons/fwd_btn.png b/icons/fwd_btn.png new file mode 100644 index 0000000..d13566e Binary files /dev/null and b/icons/fwd_btn.png differ diff --git a/icons/home_btn.png b/icons/home_btn.png new file mode 100644 index 0000000..36bb18b Binary files /dev/null and b/icons/home_btn.png differ diff --git a/icons/nix_block_1d.png b/icons/nix_block_1d.png new file mode 100644 index 0000000..c304899 Binary files /dev/null and b/icons/nix_block_1d.png differ diff --git a/icons/nix_close.png b/icons/nix_close.png new file mode 100644 index 0000000..c75ec2c Binary files /dev/null and b/icons/nix_close.png differ diff --git a/icons/nix_data_array.png b/icons/nix_data_array.png new file mode 100644 index 0000000..88d1897 Binary files /dev/null and b/icons/nix_data_array.png differ diff --git a/icons/nix_data_frame.png b/icons/nix_data_frame.png new file mode 100644 index 0000000..f86b335 Binary files /dev/null and b/icons/nix_data_frame.png differ diff --git a/icons/nix_dimension.png b/icons/nix_dimension.png new file mode 100644 index 0000000..169541e Binary files /dev/null and b/icons/nix_dimension.png differ diff --git a/icons/nix_fav1_cut.png b/icons/nix_fav1_cut.png new file mode 100644 index 0000000..67b2a53 Binary files /dev/null and b/icons/nix_fav1_cut.png differ diff --git a/icons/nix_feature.png b/icons/nix_feature.png new file mode 100644 index 0000000..efedf04 Binary files /dev/null and b/icons/nix_feature.png differ diff --git a/icons/nix_group.png b/icons/nix_group.png new file mode 100644 index 0000000..838ab2c Binary files /dev/null and b/icons/nix_group.png differ diff --git a/icons/nix_logo.png b/icons/nix_logo.png new file mode 100644 index 0000000..acb1a1d Binary files /dev/null and b/icons/nix_logo.png differ diff --git a/icons/nix_open.png b/icons/nix_open.png new file mode 100644 index 0000000..a679253 Binary files /dev/null and b/icons/nix_open.png differ diff --git a/icons/nix_plot.png b/icons/nix_plot.png new file mode 100644 index 0000000..5f1bbf8 Binary files /dev/null and b/icons/nix_plot.png differ diff --git a/icons/nix_property.png b/icons/nix_property.png new file mode 100644 index 0000000..cdb18d7 Binary files /dev/null and b/icons/nix_property.png differ diff --git a/icons/nix_section.png b/icons/nix_section.png new file mode 100644 index 0000000..8672df2 Binary files /dev/null and b/icons/nix_section.png differ diff --git a/icons/nix_source.png b/icons/nix_source.png new file mode 100644 index 0000000..92df782 Binary files /dev/null and b/icons/nix_source.png differ diff --git a/icons/nix_table.png b/icons/nix_table.png new file mode 100644 index 0000000..a6ea080 Binary files /dev/null and b/icons/nix_table.png differ diff --git a/icons/nix_tag.png b/icons/nix_tag.png new file mode 100644 index 0000000..076de3e Binary files /dev/null and b/icons/nix_tag.png differ diff --git a/icons/nixview.icns b/icons/nixview.icns new file mode 100644 index 0000000..ccad903 Binary files /dev/null and b/icons/nixview.icns differ diff --git a/icons/nixview.ico b/icons/nixview.ico new file mode 100644 index 0000000..c4ad993 Binary files /dev/null and b/icons/nixview.ico differ diff --git a/icons/nixview128.png b/icons/nixview128.png new file mode 100644 index 0000000..9bb37f7 Binary files /dev/null and b/icons/nixview128.png differ diff --git a/icons/nixview256.png b/icons/nixview256.png new file mode 100644 index 0000000..34a5c79 Binary files /dev/null and b/icons/nixview256.png differ diff --git a/icons/nixview32.png b/icons/nixview32.png new file mode 100644 index 0000000..3fb5ab4 Binary files /dev/null and b/icons/nixview32.png differ diff --git a/icons/nixview64.png b/icons/nixview64.png new file mode 100644 index 0000000..17bdb3b Binary files /dev/null and b/icons/nixview64.png differ diff --git a/icons/nixview_close.png b/icons/nixview_close.png new file mode 100644 index 0000000..ca9600c Binary files /dev/null and b/icons/nixview_close.png differ diff --git a/icons/nixview_help.png b/icons/nixview_help.png new file mode 100644 index 0000000..97eaaf4 Binary files /dev/null and b/icons/nixview_help.png differ diff --git a/icons/nixview_open.png b/icons/nixview_open.png new file mode 100644 index 0000000..67ecaf6 Binary files /dev/null and b/icons/nixview_open.png differ diff --git a/icons/nixview_quit.png b/icons/nixview_quit.png new file mode 100644 index 0000000..c35abf9 Binary files /dev/null and b/icons/nixview_quit.png differ diff --git a/icons/nixview_transparent.png b/icons/nixview_transparent.png new file mode 100644 index 0000000..e2ed381 Binary files /dev/null and b/icons/nixview_transparent.png differ diff --git a/icons/quit.png b/icons/quit.png new file mode 100644 index 0000000..ed27c97 Binary files /dev/null and b/icons/quit.png differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bba1e7d --- /dev/null +++ b/setup.py @@ -0,0 +1,57 @@ +import glob +import json +import os + +# Use setuptools compulsorily, as the distutils doesn't work out well for the +# installation procedure. The 'install_requires' and 'data_files' have better +# support in setuptools. +from setuptools import setup + + +with open(os.path.join("blipblop", "info.json")) as infofile: + infodict = json.load(infofile) + +NAME = infodict["NAME"] +VERSION = infodict["VERSION"] +AUTHOR = infodict["AUTHOR"] +CONTACT = infodict["CONTACT"] +HOMEPAGE = infodict["HOMEPAGE"] +CLASSIFIERS = infodict["CLASSIFIERS"] +DESCRIPTION = infodict["DESCRIPTION"] + +README = "README.md" +with open(README) as f: + description_text = f.read() + +packages = [ + "blipblop", +] + +install_req = ["PyQt5", "numpy"] + +data_files = [("icons", glob.glob(os.path.join("icons", "*.png"))), + ("icons", glob.glob(os.path.join("icons", "*.ic*"))), + (".", ["LICENSE"]), + ("docs", glob.glob(os.path.join("docs", "*.md"))) + ] + +setup( + name=NAME, + version=VERSION, + description=DESCRIPTION, + author=AUTHOR, + author_email=CONTACT, + url=HOMEPAGE, + packages=packages, + install_requires=install_req, + include_package_data=True, + data_files=data_files, + long_description=description_text, + long_description_content_type="text/markdown", + classifiers=CLASSIFIERS, + license="BSD", + entry_points={ + "gui_scripts": ["blipblop = blipblop:main []"], + "console_scripts": ["blipblop = blipblop:main []"] + } +)