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 []"]
|
||||||
|
}
|
||||||
|
)
|