initials
0
.gitignore
vendored
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# BlibBlop
|
||||
|
||||
A PyQt5 based cross-platform experiment for measuring reaction times in human observers.
|
||||
|
||||
|
||||
|
||||
|
||||
## Dependencies
|
||||
- PyQt5
|
||||
- numpy
|
5
blipblob.py
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
from blipblop import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main.main()
|
0
blipblop/__init__.py
Normal file
BIN
blipblop/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
blipblop/__pycache__/constants.cpython-38.pyc
Normal file
BIN
blipblop/__pycache__/main.cpython-38.pyc
Normal file
25
blipblop/constants.py
Normal file
@ -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")
|
||||
|
20
blipblop/info.json
Normal file
@ -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"
|
||||
]
|
||||
}
|
15
blipblop/main.py
Normal file
@ -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()
|
0
blipblop/ui/__init__.py
Normal file
BIN
blipblop/ui/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
blipblop/ui/__pycache__/about.cpython-38.pyc
Normal file
BIN
blipblop/ui/__pycache__/help.cpython-38.pyc
Normal file
BIN
blipblop/ui/__pycache__/helpdialog.cpython-38.pyc
Normal file
BIN
blipblop/ui/__pycache__/mainwindow.cpython-38.pyc
Normal file
58
blipblop/ui/about.py
Normal file
@ -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)
|
22
blipblop/ui/audioblop.py
Normal file
@ -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
|
||||
|
45
blipblop/ui/centralwidget.py
Normal file
@ -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)
|
142
blipblop/ui/filescreen.py
Normal file
@ -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)
|
65
blipblop/ui/help.py
Normal file
@ -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)
|
133
blipblop/ui/mainwindow.py
Normal file
@ -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()
|
19
blipblop/ui/resultsscreen.py
Normal file
@ -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
|
71
blipblop/ui/startscreen.py
Normal file
@ -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
|
22
blipblop/ui/visualblip.py
Normal file
@ -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
|
||||
|
0
blipblop/util/__init__.py
Normal file
24
blipblop/util/results.py
Normal file
@ -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
|
4
docs/index.md
Normal file
@ -0,0 +1,4 @@
|
||||
# BlipBlop
|
||||
|
||||
Tiny tool for measuring reaction times upon auditory or visual stimuli.
|
||||
|
BIN
icons/back_btn.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/fwd_btn.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
icons/home_btn.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
icons/nix_block_1d.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
icons/nix_close.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/nix_data_array.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
icons/nix_data_frame.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
icons/nix_dimension.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
icons/nix_fav1_cut.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
icons/nix_feature.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
icons/nix_group.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
icons/nix_logo.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
icons/nix_open.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/nix_plot.png
Normal file
After Width: | Height: | Size: 1007 B |
BIN
icons/nix_property.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
icons/nix_section.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
icons/nix_source.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
icons/nix_table.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
icons/nix_tag.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
icons/nixview.icns
Normal file
BIN
icons/nixview.ico
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
icons/nixview128.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
icons/nixview256.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
icons/nixview32.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/nixview64.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
icons/nixview_close.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
icons/nixview_help.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
icons/nixview_open.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
icons/nixview_quit.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
icons/nixview_transparent.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
icons/quit.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
57
setup.py
Normal file
@ -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 []"]
|
||||
}
|
||||
)
|