Skip to content

Commit af0f63c

Browse files
Merge pull request #19 from danielparthier:GUI_interface
GUI_interface
2 parents 66a0ff5 + e28cc28 commit af0f63c

29 files changed

+3141
-500
lines changed

ephys/GUI/GUI_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
theme = "dark"

ephys/GUI/__init__.py

Whitespace-only changes.

ephys/GUI/gui_app.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from typing import TYPE_CHECKING
5+
6+
from PySide6.QtCore import (
7+
Qt,
8+
QThreadPool,
9+
QRunnable,
10+
Slot,
11+
QObject,
12+
Signal,
13+
)
14+
from PySide6.QtGui import QAction, QIcon
15+
from PySide6.QtWidgets import (
16+
QApplication,
17+
QFrame,
18+
QMainWindow,
19+
QSplitter,
20+
QTabWidget,
21+
QWidget,
22+
)
23+
24+
# from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
25+
# from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
26+
27+
28+
from .styles import apply_style
29+
from ephys.classes.experiment_objects import ExpData
30+
from ephys.GUI.sidemenu import SideMenu
31+
from ephys.GUI.sessioninfo import SessionInfo
32+
from ephys.GUI.trace_view import TracePlotWindow
33+
from ephys.GUI.sidebar_right import SideBarRight
34+
from ephys.GUI.menubar import FileMenu
35+
from ephys.GUI.mainwindow import MainWindow
36+
37+
# filepath: /home/daniel/Work/RETAIN/Code/MossyFibre/ephys/GUI/gui_app.py
38+
39+
40+
class DataLoader(QRunnable):
41+
def __init__(self, file_path, experimenter_name):
42+
super().__init__()
43+
self.file_path = file_path
44+
self.experimenter_name = experimenter_name
45+
self.data: ExpData | None = None
46+
self.signals = WorkerSignals()
47+
48+
@Slot()
49+
def run(self):
50+
self.data = ExpData(
51+
file_path=self.file_path, experimenter=self.experimenter_name
52+
)
53+
54+
try:
55+
results = self.get_data()
56+
except Exception as e:
57+
self.signals.error_occurred.emit(str(e))
58+
else:
59+
if results is not None:
60+
self.signals.data_ready.emit(results)
61+
else:
62+
self.signals.error_occurred.emit("No data loaded")
63+
finally:
64+
self.signals.finished.emit()
65+
66+
def get_data(self) -> None | ExpData:
67+
if self.data is None:
68+
print("Data could not be loaded")
69+
return None
70+
return self.data
71+
72+
73+
class WorkerSignals(QObject):
74+
"""
75+
Custom signal class to emit data from the worker thread.
76+
"""
77+
78+
finished = Signal()
79+
data_ready = Signal(ExpData)
80+
error_occurred = Signal(str)
81+
82+
83+
app = QApplication(sys.argv)
84+
theme = "dark"
85+
style: str = apply_style(theme=theme)
86+
app.setStyleSheet(style)
87+
88+
window = MainWindow()
89+
window.show()
90+
91+
app.exec()

ephys/GUI/labfolder.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from __future__ import annotations
2+
from typing import TYPE_CHECKING
3+
from PySide6.QtWidgets import (
4+
QMainWindow,
5+
QVBoxLayout,
6+
QWidget,
7+
QLineEdit,
8+
QPushButton,
9+
QHBoxLayout,
10+
QMessageBox,
11+
)
12+
from PySide6.QtCore import Qt
13+
from ephys.labfolder.classes.labfolder_access import LabFolderUserInfo, labfolder_login
14+
from ephys.labfolder.labfolder_config import labfolder_url
15+
16+
if TYPE_CHECKING:
17+
from ephys.GUI.sidemenu import SideMenu
18+
19+
20+
class LabfolderLoginWindow(QMainWindow):
21+
def __init__(self, on_confirm=None) -> None:
22+
self.on_confirm = on_confirm
23+
self.username = None
24+
self.password = None
25+
self.clicked_status = False
26+
super().__init__()
27+
self.setWindowTitle("Labfolder Login")
28+
self.setGeometry(100, 100, 300, 100)
29+
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
30+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
31+
self.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
32+
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
33+
self.setAttribute(Qt.WidgetAttribute.WA_AlwaysShowToolTips)
34+
self.setAttribute(Qt.WidgetAttribute.WA_AlwaysStackOnTop)
35+
36+
# Create a layout for the login window
37+
login_layout = QVBoxLayout()
38+
# Create a QLineEdit for the username
39+
username_input = QLineEdit()
40+
username_input.setPlaceholderText("user.name@email.com")
41+
login_layout.addWidget(username_input)
42+
# Create a QLineEdit for the password
43+
password_input = QLineEdit()
44+
password_input.setPlaceholderText("Password")
45+
password_input.setEchoMode(QLineEdit.EchoMode.Password)
46+
login_layout.addWidget(password_input)
47+
# Create a button to confirm login
48+
button_layout = QHBoxLayout()
49+
login_button = QPushButton("Login")
50+
login_button.setCheckable(False)
51+
# Button as default button (enter key)
52+
login_button.setDefault(True)
53+
login_button.clicked.connect(self.confirm_and_close)
54+
button_layout.addWidget(login_button)
55+
# Create a button to cancel login
56+
cancel_button = QPushButton("Cancel")
57+
cancel_button.setCheckable(False)
58+
cancel_button.clicked.connect(self.close)
59+
button_layout.addWidget(cancel_button)
60+
login_layout.addLayout(button_layout)
61+
# Create a central widget to hold the layout
62+
central_widget = QWidget()
63+
central_widget.setLayout(login_layout)
64+
self.setCentralWidget(central_widget)
65+
# Set the layout to the central widget
66+
self.setLayout(login_layout)
67+
68+
self.username_input: QLineEdit = username_input
69+
self.password_input: QLineEdit = password_input
70+
71+
def confirm_and_close(self) -> None:
72+
if self.on_confirm:
73+
self.on_confirm(
74+
self.username_input.text(),
75+
self.password_input.text(),
76+
)
77+
self.close()
78+
79+
80+
class LabfolderWindow(QPushButton):
81+
def __init__(self, side_menu: SideMenu):
82+
super().__init__("Labfolder Login")
83+
self.side_menu: SideMenu = side_menu
84+
self.username = ""
85+
86+
self.setMaximumWidth(150)
87+
self.setCheckable(False)
88+
self.clicked.connect(self.login_labfolder)
89+
90+
def login_labfolder(self) -> None:
91+
# Open the LabfolderLoginWindow as a separate window and keep it open
92+
self.labfolder_login_window = LabfolderLoginWindow(
93+
on_confirm=self.get_labfolder_auth
94+
)
95+
self.labfolder_login_window.show()
96+
# make sure that content of labfolder_login_window is transferred to the main window
97+
print("Labfolder login window opened")
98+
99+
def get_labfolder_auth(self, username: str, password: str) -> None:
100+
self.username = username
101+
self.labfolder_auth: LabFolderUserInfo = labfolder_login(
102+
labfolder_url=labfolder_url,
103+
user=username,
104+
password=password,
105+
allow_input=False,
106+
)
107+
if len(self.labfolder_auth.auth_token) == 0:
108+
print("Labfolder authentication failed")
109+
return None
110+
111+
popup_message = QMessageBox(self)
112+
popup_message.setWindowTitle("Labfolder Login Successful")
113+
popup_message.setText("Hello " + self.labfolder_auth.first_name + "!")
114+
popup_message.setWindowOpacity(0.5)
115+
popup_message.exec()
116+
self.current_user = (
117+
self.labfolder_auth.first_name + " " + self.labfolder_auth.last_name
118+
)
119+
self.side_menu.current_user_label.setPlaceholderText(self.current_user)
120+
# add popup message that login was successful
121+
# Close the login window
122+
self.labfolder_login_window.close()

ephys/GUI/mainwindow.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from typing import cast
2+
3+
from PySide6.QtCore import Qt, QThreadPool
4+
from PySide6.QtGui import QAction, QIcon
5+
from PySide6.QtWidgets import QFrame, QMainWindow, QSplitter, QTabWidget
6+
7+
from ephys.classes.experiment_objects import ExpData
8+
from ephys.GUI.sessioninfo import SessionInfo
9+
from ephys.GUI.sidemenu import SideMenu, SideMenuContainer
10+
from ephys.GUI.sidebar_right import SideBarRight
11+
from ephys.GUI.menubar import FileMenu
12+
from ephys.GUI.trace_view import TracePlotWindow
13+
14+
15+
class MainWindow(QMainWindow):
16+
def __init__(self):
17+
super().__init__()
18+
self.clicked_status = False
19+
self.data: ExpData | None = None
20+
self.session_info = SessionInfo()
21+
22+
self.threadpool = QThreadPool()
23+
thread_count = self.threadpool.maxThreadCount()
24+
print(f"Multithreading enabled with {thread_count} threads.")
25+
26+
self.setWindowTitle("EphysTools")
27+
self.setGeometry(100, 100, 1200, 800)
28+
29+
file_menu = FileMenu()
30+
self.setMenuBar(file_menu)
31+
32+
icon = QIcon("logos/Logo_short.svg")
33+
icon_action = QAction(icon, "", self)
34+
35+
# Add a status bar
36+
self.status_bar = self.statusBar()
37+
self.status_bar.addAction(icon_action)
38+
self.status_bar.showMessage("Ready")
39+
40+
# Create a splitter
41+
splitter = QSplitter()
42+
splitter.setOrientation(Qt.Orientation.Horizontal)
43+
splitter.setMaximumHeight(2400)
44+
self.setCentralWidget(splitter)
45+
# Create a frame
46+
47+
sidebar_frame = QFrame()
48+
sidebar_frame.setFrameShape(QFrame.Shape.StyledPanel)
49+
sidebar_frame.setMinimumWidth(150)
50+
sidebar_frame.setMaximumWidth(300)
51+
sidebar_frame.setMinimumHeight(150)
52+
sidebar_frame.setMaximumHeight(1200)
53+
54+
self.side_menu_container = SideMenuContainer()
55+
self.side_menu_container.setParent(sidebar_frame)
56+
self.side_menu_container.set_background_svg(svg_path="logos/Logo_short.svg")
57+
# splitter.addWidget(sidebar_frame)
58+
# Create a layout for the frame
59+
menu_layout_left = SideMenu(self)
60+
61+
sidebar_frame.setLayout(menu_layout_left)
62+
63+
splitter.addWidget(sidebar_frame)
64+
# splitter.addWidget(menu_layout_left)
65+
66+
self.trace_plot = QTabWidget()
67+
self.trace_plot.setTabsClosable(True)
68+
self.trace_plot.setMovable(True)
69+
self.trace_plot.setMinimumWidth(800)
70+
self.trace_plot.setMinimumHeight(600)
71+
self.trace_plot.setMaximumWidth(2400)
72+
self.trace_plot.setMaximumHeight(1200)
73+
self.trace_plot.setTabsClosable(True)
74+
self.trace_plot.setMovable(True)
75+
self.trace_plot.tabCloseRequested.connect(self.close_tab)
76+
splitter.addWidget(self.trace_plot)
77+
78+
# Add a sidebar on the right side
79+
sidebar_right = SideBarRight(self)
80+
sidebar_right.setMinimumWidth(180)
81+
sidebar_right.setMaximumWidth(300)
82+
sidebar_right.setMinimumHeight(150)
83+
sidebar_right.setMaximumHeight(1200)
84+
splitter.addWidget(sidebar_right)
85+
86+
menu_layout_left.addWidget(self.side_menu_container)
87+
# Add the session info widget
88+
89+
# decouple tab_widget from the splitter
90+
91+
self.show()
92+
93+
def closeEvent(self, event):
94+
print("Window closed")
95+
event.accept()
96+
97+
def close_tab(self, index: int) -> None:
98+
"""Close the tab at the given index."""
99+
tab_selected: TracePlotWindow = cast(
100+
TracePlotWindow, self.trace_plot.widget(index)
101+
)
102+
print(index, tab_selected)
103+
if index >= 0:
104+
self.trace_plot.removeTab(index)
105+
print(f"Closed tab at index {index}")
106+
if hasattr(tab_selected, "cleanup") and callable(
107+
getattr(tab_selected, "cleanup", None)
108+
):
109+
tab_selected.cleanup()
110+
if tab_selected is not None:
111+
# Ensure the tab is properly deleted
112+
if hasattr(tab_selected, "deleteLater"):
113+
tab_selected.deleteLater()
114+
else:
115+
print("Tab selected does not have deleteLater method")
116+
117+
# def choose_file(self):
118+
# # Store the dialog as an instance attribute to prevent it from being deleted
119+
# self.get_file = FileSelector()
120+
# # self.get_file.exec()
121+
# file_path = self.get_file.getOpenFileNames(
122+
# self, "Select WCP files", "data/WCP/WholeCell", "WCP files (*.wcp)"
123+
# )[0]
124+
# # if not file_path:
125+
# # print("No files selected")
126+
# # return None
127+
# print(f"Selected files: {file_path}")
128+
# self.the_button_was_clicked(file_path)
129+
# if isinstance(file_path, str):
130+
# file_path = [file_path]
131+
# self.file_list = file_path

0 commit comments

Comments
 (0)