From 7136823584fa1727bc3eb40ed4c81b7375c66af1 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 7 Jun 2023 18:35:02 +0100
Subject: [PATCH 01/18] Start working on new dialog to select data to open
---
damnit/gui/main_window.py | 7 ++
damnit/gui/open_dialog.py | 50 ++++++++++
damnit/gui/open_dialog.ui | 185 +++++++++++++++++++++++++++++++++++
damnit/gui/open_dialog_ui.py | 77 +++++++++++++++
4 files changed, 319 insertions(+)
create mode 100644 damnit/gui/open_dialog.py
create mode 100644 damnit/gui/open_dialog.ui
create mode 100644 damnit/gui/open_dialog_ui.py
diff --git a/damnit/gui/main_window.py b/damnit/gui/main_window.py
index c2c072a4..2cb85f4e 100644
--- a/damnit/gui/main_window.py
+++ b/damnit/gui/main_window.py
@@ -33,6 +33,7 @@
from .plot import Canvas, Plot
from .user_variables import AddUserVariableDialog
from .editor import Editor, ContextTestResult
+from .open_dialog import OpenDBDialog
log = logging.getLogger(__name__)
@@ -1033,6 +1034,12 @@ def run_app(context_dir, connect_to_kafka=True):
application = QtWidgets.QApplication(sys.argv)
application.setStyle(TableViewStyle())
+ if context_dir is None:
+ dialog = OpenDBDialog()
+ if dialog.exec() == QtWidgets.QDialog.Rejected:
+ return 0
+ context_dir = dialog.get_chosen_dir()
+
window = MainWindow(context_dir=context_dir, connect_to_kafka=connect_to_kafka)
window.show()
return application.exec()
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
new file mode 100644
index 00000000..39593615
--- /dev/null
+++ b/damnit/gui/open_dialog.py
@@ -0,0 +1,50 @@
+import os.path
+
+from extra_data.read_machinery import find_proposal
+from PyQt5.QtGui import QIntValidator
+from PyQt5.QtWidgets import QDialog, QFileDialog, QDialogButtonBox
+
+from .open_dialog_ui import Ui_Dialog
+
+class OpenDBDialog(QDialog):
+ def __init__(self):
+ super().__init__()
+ self.ui = Ui_Dialog()
+ self.ui.setupUi(self)
+ self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+ self.ui.proposal_rb.toggled.connect(self.update_ok)
+ self.ui.proposal_edit.textChanged(self.update_ok)
+ self.ui.folder_edit.textChanged(self.update_ok)
+ self.ui.browse_button.clicked.connect(self.browse_for_folder)
+ self.ui.proposal_edit.setValidator(QIntValidator(1000, 999999))
+ self.ui.proposal_edit.setFocus()
+
+ def get_proposal_dir(self):
+ if not self.ui.proposal_edit.hasAcceptableInput():
+ return None
+ prop_no = int(self.ui.proposal_edit.text())
+ return find_proposal(f"p{prop_no:06}")
+
+ def update_ok(self):
+ dir = self.get_chosen_dir()
+ valid = (dir is not None) and os.path.isdir(dir)
+ self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(valid)
+
+ def browse_for_folder(self):
+ path = QFileDialog.getExistingDirectory()
+ if path:
+ self.ui.folder_edit.setText()
+
+ def get_chosen_dir(self):
+ if self.ui.proposal_rb.isChecked():
+ return self.get_proposal_dir()
+ else:
+ return self.ui.folder_edit.text()
+
+def select_amore_dir():
+ dlg = QDialog()
+ ui = Ui_Dialog()
+ ui.setupUi(dlg)
+ ui.browse_button.clicked.connect()
+
+ dlg.exec()
diff --git a/damnit/gui/open_dialog.ui b/damnit/gui/open_dialog.ui
new file mode 100644
index 00000000..cd485772
--- /dev/null
+++ b/damnit/gui/open_dialog.ui
@@ -0,0 +1,185 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 400
+ 265
+
+
+
+ Dialog
+
+
+ -
+
+
+ Please select the data to open:
+
+
+
+ -
+
+
-
+
+
+ Open proposal number:
+
+
+ true
+
+
+
+ -
+
+
+ 009999
+
+
+
+
+
+ -
+
+
-
+
+
+ Open DAMNIT folder:
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ Browse
+
+
+
+
+
+ -
+
+
+ <html><head/><body><p>DAMNIT will open an existing database if this folder contains <span style=" font-family:'Source Code Pro';">runs.sqlite</span> and <span style=" font-family:'Source Code Pro';">context.py</span>, or create a new database if not.</p></body></html>
+
+
+ Qt::RichText
+
+
+ true
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 224
+ 272
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+ proposal_rb
+ toggled(bool)
+ proposal_edit
+ setEnabled(bool)
+
+
+ 62
+ 63
+
+
+ 224
+ 67
+
+
+
+
+ folder_rb
+ toggled(bool)
+ folder_edit
+ setEnabled(bool)
+
+
+ 64
+ 145
+
+
+ 83
+ 214
+
+
+
+
+ folder_rb
+ toggled(bool)
+ browse_button
+ setEnabled(bool)
+
+
+ 323
+ 147
+
+
+ 347
+ 197
+
+
+
+
+
diff --git a/damnit/gui/open_dialog_ui.py b/damnit/gui/open_dialog_ui.py
new file mode 100644
index 00000000..0b99b1f4
--- /dev/null
+++ b/damnit/gui/open_dialog_ui.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'open_dialog.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.9
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(400, 265)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.label = QtWidgets.QLabel(Dialog)
+ self.label.setObjectName("label")
+ self.verticalLayout.addWidget(self.label)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.proposal_rb = QtWidgets.QRadioButton(Dialog)
+ self.proposal_rb.setChecked(True)
+ self.proposal_rb.setObjectName("proposal_rb")
+ self.horizontalLayout.addWidget(self.proposal_rb)
+ self.proposal_edit = QtWidgets.QLineEdit(Dialog)
+ self.proposal_edit.setObjectName("proposal_edit")
+ self.horizontalLayout.addWidget(self.proposal_edit)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.folder_rb = QtWidgets.QRadioButton(Dialog)
+ self.folder_rb.setObjectName("folder_rb")
+ self.verticalLayout_2.addWidget(self.folder_rb)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.folder_edit = QtWidgets.QLineEdit(Dialog)
+ self.folder_edit.setEnabled(False)
+ self.folder_edit.setObjectName("folder_edit")
+ self.horizontalLayout_2.addWidget(self.folder_edit)
+ self.browse_button = QtWidgets.QPushButton(Dialog)
+ self.browse_button.setEnabled(False)
+ self.browse_button.setObjectName("browse_button")
+ self.horizontalLayout_2.addWidget(self.browse_button)
+ self.verticalLayout_2.addLayout(self.horizontalLayout_2)
+ self.label_2 = QtWidgets.QLabel(Dialog)
+ self.label_2.setTextFormat(QtCore.Qt.RichText)
+ self.label_2.setWordWrap(True)
+ self.label_2.setObjectName("label_2")
+ self.verticalLayout_2.addWidget(self.label_2)
+ self.verticalLayout.addLayout(self.verticalLayout_2)
+ self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName("buttonBox")
+ self.verticalLayout.addWidget(self.buttonBox)
+
+ self.retranslateUi(Dialog)
+ self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
+ self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
+ self.proposal_rb.toggled['bool'].connect(self.proposal_edit.setEnabled) # type: ignore
+ self.folder_rb.toggled['bool'].connect(self.folder_edit.setEnabled) # type: ignore
+ self.folder_rb.toggled['bool'].connect(self.browse_button.setEnabled) # type: ignore
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.label.setText(_translate("Dialog", "Please select the data to open:"))
+ self.proposal_rb.setText(_translate("Dialog", "Open proposal number:"))
+ self.proposal_edit.setInputMask(_translate("Dialog", "009999"))
+ self.folder_rb.setText(_translate("Dialog", "Open DAMNIT folder:"))
+ self.browse_button.setText(_translate("Dialog", "Browse"))
+ self.label_2.setText(_translate("Dialog", "
DAMNIT will open an existing database if this folder contains runs.sqlite and context.py, or create a new database if not.
"))
From e754ebc99254863007151d2cef376fcdbd53c105 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 7 Jun 2023 18:40:54 +0100
Subject: [PATCH 02/18] Remove inputMask from proposal edit widget
---
damnit/gui/open_dialog.ui | 6 +-----
damnit/gui/open_dialog_ui.py | 1 -
2 files changed, 1 insertion(+), 6 deletions(-)
diff --git a/damnit/gui/open_dialog.ui b/damnit/gui/open_dialog.ui
index cd485772..b2cd4cef 100644
--- a/damnit/gui/open_dialog.ui
+++ b/damnit/gui/open_dialog.ui
@@ -34,11 +34,7 @@
-
-
-
- 009999
-
-
+
diff --git a/damnit/gui/open_dialog_ui.py b/damnit/gui/open_dialog_ui.py
index 0b99b1f4..a038b02d 100644
--- a/damnit/gui/open_dialog_ui.py
+++ b/damnit/gui/open_dialog_ui.py
@@ -71,7 +71,6 @@ def retranslateUi(self, Dialog):
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Please select the data to open:"))
self.proposal_rb.setText(_translate("Dialog", "Open proposal number:"))
- self.proposal_edit.setInputMask(_translate("Dialog", "009999"))
self.folder_rb.setText(_translate("Dialog", "Open DAMNIT folder:"))
self.browse_button.setText(_translate("Dialog", "Browse"))
self.label_2.setText(_translate("Dialog", "DAMNIT will open an existing database if this folder contains runs.sqlite and context.py, or create a new database if not.
"))
From bfc4ba63fa0bd357457e101056c18815bfc0e618 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 8 Jun 2023 12:31:54 +0100
Subject: [PATCH 03/18] Find proposal dirs in a thread
---
damnit/gui/open_dialog.py | 47 ++++++++++++++++++++++++++++++---------
1 file changed, 36 insertions(+), 11 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 39593615..31b34714 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -1,33 +1,58 @@
import os.path
from extra_data.read_machinery import find_proposal
-from PyQt5.QtGui import QIntValidator
+from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import QDialog, QFileDialog, QDialogButtonBox
from .open_dialog_ui import Ui_Dialog
+class ProposalFinder(QObject):
+ find_result = pyqtSignal(str, str)
+
+ def find_proposal(self, propnum: str):
+ if str.isdecimal() and len(str) >= 4
+ try:
+ dir = find_proposal(f"p{int(propnum):06}")
+ except:
+ dir = ''
+ else:
+ dir = ''
+ self.find_result.emit(propnum, dir)
+
class OpenDBDialog(QDialog):
+ proposal_num_changed = pyqtSignal(str)
+ proposal_dir = ''
+
def __init__(self):
super().__init__()
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
self.ui.proposal_rb.toggled.connect(self.update_ok)
- self.ui.proposal_edit.textChanged(self.update_ok)
self.ui.folder_edit.textChanged(self.update_ok)
self.ui.browse_button.clicked.connect(self.browse_for_folder)
- self.ui.proposal_edit.setValidator(QIntValidator(1000, 999999))
self.ui.proposal_edit.setFocus()
- def get_proposal_dir(self):
- if not self.ui.proposal_edit.hasAcceptableInput():
- return None
- prop_no = int(self.ui.proposal_edit.text())
- return find_proposal(f"p{prop_no:06}")
+ self.proposal_finder_thread = QThread()
+ self.proposal_finder = ProposalFinder()
+ self.proposal_finder.moveToThread(self.proposal_finder_thread)
+ self.ui.proposal_edit.textChanged(self.proposal_finder.find_proposal)
+ self.proposal_finder.find_result.connect(self.proposal_dir_result)
+ self.finished.connect(self.proposal_finder_thread.quit)
+
+ def proposal_dir_result(self, propnum, dir):
+ if propnum != self.ui.proposal_edit.text():
+ return # Text field has been changed
+ self.proposal_dir = dir
+ if self.ui.proposal_rb.isChecked():
+ self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(bool(dir))
+
def update_ok(self):
- dir = self.get_chosen_dir()
- valid = (dir is not None) and os.path.isdir(dir)
+ if self.ui.proposal_rb.isChecked():
+ valid = bool(self.proposal_dir)
+ else:
+ valid = os.path.isdir(self.ui.folder_edit.text())
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(valid)
def browse_for_folder(self):
@@ -37,7 +62,7 @@ def browse_for_folder(self):
def get_chosen_dir(self):
if self.ui.proposal_rb.isChecked():
- return self.get_proposal_dir()
+ return os.path.join(self.proposal_dir, "usr/Shared/amore")
else:
return self.ui.folder_edit.text()
From 159dd8c559c2516e4a2797171e40711290d532fd Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 8 Jun 2023 13:37:00 +0200
Subject: [PATCH 04/18] Various minor fixes
---
damnit/gui/open_dialog.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 31b34714..dc306d33 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -10,7 +10,7 @@ class ProposalFinder(QObject):
find_result = pyqtSignal(str, str)
def find_proposal(self, propnum: str):
- if str.isdecimal() and len(str) >= 4
+ if propnum.isdecimal() and len(propnum) >= 4:
try:
dir = find_proposal(f"p{int(propnum):06}")
except:
@@ -29,16 +29,17 @@ def __init__(self):
self.ui.setupUi(self)
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
self.ui.proposal_rb.toggled.connect(self.update_ok)
- self.ui.folder_edit.textChanged(self.update_ok)
+ self.ui.folder_edit.textChanged.connect(self.update_ok)
self.ui.browse_button.clicked.connect(self.browse_for_folder)
self.ui.proposal_edit.setFocus()
self.proposal_finder_thread = QThread()
self.proposal_finder = ProposalFinder()
self.proposal_finder.moveToThread(self.proposal_finder_thread)
- self.ui.proposal_edit.textChanged(self.proposal_finder.find_proposal)
+ self.ui.proposal_edit.textChanged.connect(self.proposal_finder.find_proposal)
self.proposal_finder.find_result.connect(self.proposal_dir_result)
self.finished.connect(self.proposal_finder_thread.quit)
+ self.proposal_finder_thread.start()
def proposal_dir_result(self, propnum, dir):
if propnum != self.ui.proposal_edit.text():
From 705f6ee466177f21714ecbbfb2d70c6d87e8ae7a Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 8 Jun 2023 13:58:09 +0200
Subject: [PATCH 05/18] Simplify validity checking a bit
---
damnit/gui/open_dialog.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index dc306d33..6938a413 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -45,9 +45,7 @@ def proposal_dir_result(self, propnum, dir):
if propnum != self.ui.proposal_edit.text():
return # Text field has been changed
self.proposal_dir = dir
- if self.ui.proposal_rb.isChecked():
- self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(bool(dir))
-
+ self.update_ok()
def update_ok(self):
if self.ui.proposal_rb.isChecked():
From 50d89eec56eaaaa55cc8f045478f46df12bba19a Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 8 Jun 2023 13:02:28 +0100
Subject: [PATCH 06/18] Use pathlib instead of os.path
---
damnit/gui/open_dialog.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 6938a413..745c126a 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -1,4 +1,4 @@
-import os.path
+from pathlib import Path
from extra_data.read_machinery import find_proposal
from PyQt5.QtCore import QObject, QThread, pyqtSignal
@@ -41,7 +41,7 @@ def __init__(self):
self.finished.connect(self.proposal_finder_thread.quit)
self.proposal_finder_thread.start()
- def proposal_dir_result(self, propnum, dir):
+ def proposal_dir_result(self, propnum: str, dir: str):
if propnum != self.ui.proposal_edit.text():
return # Text field has been changed
self.proposal_dir = dir
@@ -51,7 +51,7 @@ def update_ok(self):
if self.ui.proposal_rb.isChecked():
valid = bool(self.proposal_dir)
else:
- valid = os.path.isdir(self.ui.folder_edit.text())
+ valid = Path(self.ui.folder_edit.text()).is_dir()
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(valid)
def browse_for_folder(self):
@@ -61,9 +61,9 @@ def browse_for_folder(self):
def get_chosen_dir(self):
if self.ui.proposal_rb.isChecked():
- return os.path.join(self.proposal_dir, "usr/Shared/amore")
+ return Path(self.proposal_dir, "usr/Shared/amore")
else:
- return self.ui.folder_edit.text()
+ return Path(self.ui.folder_edit.text())
def select_amore_dir():
dlg = QDialog()
From 6585dc356b7baa26ef08a31de944229c09b23e69 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 21 Jun 2023 17:12:16 +0200
Subject: [PATCH 07/18] Fix setting selected folder path
---
damnit/gui/open_dialog.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 745c126a..8640b79b 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -57,7 +57,7 @@ def update_ok(self):
def browse_for_folder(self):
path = QFileDialog.getExistingDirectory()
if path:
- self.ui.folder_edit.setText()
+ self.ui.folder_edit.setText(path)
def get_chosen_dir(self):
if self.ui.proposal_rb.isChecked():
From 43700e15243b535c2b739862997c6804564166fd Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 5 Jul 2023 14:23:21 +0100
Subject: [PATCH 08/18] Remove unused function
---
damnit/gui/open_dialog.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 8640b79b..08b9bf5b 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -64,11 +64,3 @@ def get_chosen_dir(self):
return Path(self.proposal_dir, "usr/Shared/amore")
else:
return Path(self.ui.folder_edit.text())
-
-def select_amore_dir():
- dlg = QDialog()
- ui = Ui_Dialog()
- ui.setupUi(dlg)
- ui.browse_button.clicked.connect()
-
- dlg.exec()
From d3e47937918e5bcd166f8e0f72dcc3ecc139578c Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 5 Jul 2023 15:09:10 +0100
Subject: [PATCH 09/18] Prompt for creating database & starting backend, use
dialog from menu bar
---
damnit/gui/main_window.py | 108 ++++++++++++++++----------------------
damnit/gui/open_dialog.py | 10 +++-
2 files changed, 53 insertions(+), 65 deletions(-)
diff --git a/damnit/gui/main_window.py b/damnit/gui/main_window.py
index 2cb85f4e..4103b298 100644
--- a/damnit/gui/main_window.py
+++ b/damnit/gui/main_window.py
@@ -190,71 +190,16 @@ def _menu_bar_help(self) -> None:
dialog.exec()
def _menu_bar_autoconfigure(self) -> None:
- proposal_dir = ""
-
- # If we're on a system with access to GPFS, prompt for the proposal
- # number so we can preset the prompt for the AMORE directory.
- if self.gpfs_accessible():
- prompt = True
- while prompt:
- prop_no, prompt = QtWidgets.QInputDialog.getInt(self, "Select proposal",
- "Which proposal is this for?")
- if not prompt:
- break
-
- proposal = f"p{prop_no:06}"
- try:
- proposal_dir = find_proposal(proposal)
- prompt = False
- except Exception:
- button = QtWidgets.QMessageBox.warning(self, "Bad proposal number",
- "Could not find a proposal with this number, try again?",
- buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
- if button != QtWidgets.QMessageBox.Yes:
- prompt = False
- else:
- prop_no = None
-
- # By convention the AMORE directory is often stored at usr/Shared/amore,
- # so if this directory exists, then we use it.
- standard_path = Path(proposal_dir) / "usr/Shared/amore"
- if standard_path.is_dir() and db_path(standard_path).is_file():
- path = standard_path
- else:
- # Helper lambda to open a prompt for the user
- prompt_for_path = lambda: QFileDialog.getExistingDirectory(self,
- "Select context directory",
- proposal_dir)
-
- if self.gpfs_accessible() and prop_no is not None:
- button = QMessageBox.question(self, "Database not found",
- f"Proposal {prop_no} does not have an AMORE database, " \
- "would you like to create one and start the backend?")
- if button == QMessageBox.Yes:
- initialize_and_start_backend(standard_path, prop_no)
- path = standard_path
- else:
- # Otherwise, we prompt the user
- path = prompt_for_path()
- else:
- path = prompt_for_path()
-
- # If we found a database, make sure we're working with a Path object
- if path:
- path = Path(path)
- else:
- # Otherwise just return
+ dialog = OpenDBDialog(self)
+ if dialog.exec() == QtWidgets.QDialog.Rejected:
+ return
+ context_dir = dialog.get_chosen_dir()
+ prop_no = dialog.get_proposal_num()
+ if not prompt_setup_db_and_backend(context_dir, prop_no, parent=self):
+ # User said no to setting up a new database
return
- # Check if the backend is running
- if not backend_is_running(path):
- button = QMessageBox.question(self, "Backend not running",
- "The AMORE backend is not running, would you like to start it? " \
- "This is only necessary if new runs are expected.")
- if button == QMessageBox.Yes:
- initialize_and_start_backend(path)
-
- self.autoconfigure(Path(path), proposal=prop_no)
+ self.autoconfigure(context_dir, proposal=prop_no)
def gpfs_accessible(self):
return os.path.isdir("/gpfs/exfel/exp")
@@ -1027,6 +972,39 @@ def drawControl(self, element, option, painter, widget=None):
super().drawControl(element, option, painter, widget)
+def prompt_setup_db_and_backend(context_dir: Path, prop_no=None, parent=None):
+ if not db_path(context_dir).is_file():
+
+ button = QMessageBox.question(
+ parent, "Database not found",
+ f"{context_dir} does not contain a DAMNIT database, "
+ "would you like to create one and start the backend?"
+ )
+ if button != QMessageBox.Yes:
+ return False
+
+
+ if prop_no is None:
+ prop_no, ok = QtWidgets.QInputDialog.getInt(
+ parent, "Select proposal", "Which proposal is this for?"
+ )
+ if not ok:
+ return False
+ initialize_and_start_backend(context_dir, prop_no)
+
+ # Check if the backend is running
+ elif not backend_is_running(context_dir):
+ button = QMessageBox.question(
+ parent, "Backend not running",
+ "The DAMNIT backend is not running, would you like to start it? "
+ "This is only necessary if new runs are expected."
+ )
+ if button == QMessageBox.Yes:
+ initialize_and_start_backend(context_dir, prop_no)
+
+ return True
+
+
def run_app(context_dir, connect_to_kafka=True):
QtWidgets.QApplication.setAttribute(
QtCore.Qt.ApplicationAttribute.AA_DontUseNativeMenuBar
@@ -1039,6 +1017,10 @@ def run_app(context_dir, connect_to_kafka=True):
if dialog.exec() == QtWidgets.QDialog.Rejected:
return 0
context_dir = dialog.get_chosen_dir()
+ prop_no = dialog.get_proposal_num()
+ if not prompt_setup_db_and_backend(context_dir, prop_no):
+ # User said no to setting up a new database
+ return 0
window = MainWindow(context_dir=context_dir, connect_to_kafka=connect_to_kafka)
window.show()
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 08b9bf5b..d92dbd60 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -1,4 +1,5 @@
from pathlib import Path
+from typing import Optional
from extra_data.read_machinery import find_proposal
from PyQt5.QtCore import QObject, QThread, pyqtSignal
@@ -23,8 +24,8 @@ class OpenDBDialog(QDialog):
proposal_num_changed = pyqtSignal(str)
proposal_dir = ''
- def __init__(self):
- super().__init__()
+ def __init__(self, parent=None):
+ super().__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
@@ -64,3 +65,8 @@ def get_chosen_dir(self):
return Path(self.proposal_dir, "usr/Shared/amore")
else:
return Path(self.ui.folder_edit.text())
+
+ def get_proposal_num(self) -> Optional[int]:
+ if self.ui.proposal_rb.isChecked():
+ return int(self.ui.proposal_edit.text())
+ return None
From 929c7dbf63710047b1fd492a94ac16935d093f8a Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 5 Jul 2023 15:35:52 +0100
Subject: [PATCH 10/18] Fix test for automatically creating database & starting
backend
---
damnit/gui/main_window.py | 12 ++++--------
damnit/gui/open_dialog.py | 7 +++++++
tests/test_gui.py | 13 +++++++++++--
3 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/damnit/gui/main_window.py b/damnit/gui/main_window.py
index 4103b298..e8777af3 100644
--- a/damnit/gui/main_window.py
+++ b/damnit/gui/main_window.py
@@ -190,11 +190,9 @@ def _menu_bar_help(self) -> None:
dialog.exec()
def _menu_bar_autoconfigure(self) -> None:
- dialog = OpenDBDialog(self)
- if dialog.exec() == QtWidgets.QDialog.Rejected:
+ context_dir, prop_no = OpenDBDialog.run_get_result(parent=self)
+ if context_dir is None:
return
- context_dir = dialog.get_chosen_dir()
- prop_no = dialog.get_proposal_num()
if not prompt_setup_db_and_backend(context_dir, prop_no, parent=self):
# User said no to setting up a new database
return
@@ -1013,11 +1011,9 @@ def run_app(context_dir, connect_to_kafka=True):
application.setStyle(TableViewStyle())
if context_dir is None:
- dialog = OpenDBDialog()
- if dialog.exec() == QtWidgets.QDialog.Rejected:
+ context_dir, prop_no = OpenDBDialog.run_get_result()
+ if context_dir is None:
return 0
- context_dir = dialog.get_chosen_dir()
- prop_no = dialog.get_proposal_num()
if not prompt_setup_db_and_backend(context_dir, prop_no):
# User said no to setting up a new database
return 0
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index d92dbd60..5dcaebd7 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -42,6 +42,13 @@ def __init__(self, parent=None):
self.finished.connect(self.proposal_finder_thread.quit)
self.proposal_finder_thread.start()
+ @staticmethod
+ def run_get_result(parent=None) -> (Optional[str], Optional[int]):
+ dlg = OpenDBDialog(parent)
+ if dlg.exec() == QDialog.Rejected:
+ return None, None
+ return dlg.get_chosen_dir(), dlg.get_proposal_num()
+
def proposal_dir_result(self, propnum: str, dir: str):
if propnum != self.ui.proposal_edit.text():
return # Text field has been changed
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 451de8a9..63adb7c0 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -307,7 +307,7 @@ def helper_patch():
# p1234, and the user always wants to create a database and start the
# backend.
with (patch.object(win, "gpfs_accessible", return_value=True),
- patch.object(QInputDialog, "getInt", return_value=(1234, True)),
+ patch(f"{pkg}.OpenDBDialog.run_get_result", return_value=(db_dir, 1234)),
patch(f"{pkg}.find_proposal", return_value=tmp_path),
patch.object(QMessageBox, "question", return_value=QMessageBox.Yes),
patch(f"{pkg}.initialize_and_start_backend") as initialize_and_start_backend,
@@ -327,6 +327,15 @@ def helper_patch():
db_dir.mkdir(parents=True)
db_path(db_dir).touch()
+ # Autoconfigure with database present & backend 'running':
+ with (helper_patch() as initialize_and_start_backend,
+ patch(f"{pkg}.backend_is_running", return_value=True)):
+ win._menu_bar_autoconfigure()
+
+ # We expect the database to be initialized and the backend started
+ win.autoconfigure.assert_called_once_with(db_dir, proposal=1234)
+ initialize_and_start_backend.assert_not_called()
+
# Autoconfigure again, the GUI should start the backend again
with (helper_patch() as initialize_and_start_backend,
patch(f"{pkg}.backend_is_running", return_value=False)):
@@ -334,7 +343,7 @@ def helper_patch():
# This time the database is already initialized
win.autoconfigure.assert_called_once_with(db_dir, proposal=1234)
- initialize_and_start_backend.assert_called_once_with(db_dir)
+ initialize_and_start_backend.assert_called_once_with(db_dir, 1234)
def test_user_vars(mock_ctx_user, mock_user_vars, mock_db, qtbot):
From f931f80040457f758d6002e7ea780c7919089f24 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 5 Jul 2023 16:04:30 +0100
Subject: [PATCH 11/18] Add test for OpenDBDialog
---
damnit/gui/open_dialog.py | 2 +-
tests/test_gui.py | 29 ++++++++++++++++++++++++++++-
2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 5dcaebd7..9b3255b7 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -43,7 +43,7 @@ def __init__(self, parent=None):
self.proposal_finder_thread.start()
@staticmethod
- def run_get_result(parent=None) -> (Optional[str], Optional[int]):
+ def run_get_result(parent=None) -> (Optional[Path], Optional[int]):
dlg = OpenDBDialog(parent)
if dlg.exec() == QDialog.Rejected:
return None, None
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 63adb7c0..2840db53 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -8,13 +8,14 @@
import numpy as np
import pandas as pd
from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QMessageBox, QInputDialog, QDialog, QStyledItemDelegate, QLineEdit
+from PyQt5.QtWidgets import QMessageBox, QFileDialog, QDialog, QStyledItemDelegate, QLineEdit
from damnit.ctxsupport.ctxrunner import ContextFile, Results
from damnit.backend.db import db_path
from damnit.backend.extract_data import add_to_db
from damnit.gui.editor import ContextTestResult
from damnit.gui.main_window import MainWindow, Settings, AddUserVariableDialog
+from damnit.gui.open_dialog import OpenDBDialog
# Check if a PID exists by using `kill -0`
@@ -695,3 +696,29 @@ def get_index(title, row=0):
# Edit a standalone comment
comment_index = get_index("Comment", row=1)
win.table.setData(comment_index, "Foo", Qt.EditRole)
+
+
+def test_open_dialog(mock_db, qtbot):
+ db_dir, db = mock_db
+ dlg = OpenDBDialog()
+
+ # Test supplying a proposal number:
+ with patch("damnit.gui.open_dialog.find_proposal", return_value=str(db_dir)):
+ with qtbot.waitSignal(dlg.proposal_finder.find_result):
+ dlg.ui.proposal_edit.setText('1234')
+ with qtbot.waitSignal(dlg.proposal_finder_thread.finished):
+ dlg.accept()
+
+ assert dlg.get_chosen_dir() == db_dir / 'usr/Shared/amore'
+ assert dlg.get_proposal_num() == 1234
+
+ # Test selecting a folder:
+ dlg = OpenDBDialog()
+ dlg.ui.folder_rb.setChecked(True)
+ with patch.object(QFileDialog, 'getExistingDirectory', return_value=str(db_dir)):
+ dlg.ui.browse_button.click()
+ with qtbot.waitSignal(dlg.proposal_finder_thread.finished):
+ dlg.accept()
+
+ assert dlg.get_chosen_dir() == db_dir
+ assert dlg.get_proposal_num() is None
From 9cc07fa4b6f6a586f6e62f624ebebedb979dbba3 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Wed, 5 Jul 2023 17:38:16 +0100
Subject: [PATCH 12/18] Make path joining more explicit
Co-authored-by: James Wrigley
---
damnit/gui/open_dialog.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 9b3255b7..791d1205 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -69,7 +69,7 @@ def browse_for_folder(self):
def get_chosen_dir(self):
if self.ui.proposal_rb.isChecked():
- return Path(self.proposal_dir, "usr/Shared/amore")
+ return Path(self.proposal_dir) / "usr/Shared/amore"
else:
return Path(self.ui.folder_edit.text())
From 6d1cc278ef0a213f1932b9ffc047a253bfb72b70 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 6 Jul 2023 11:19:21 +0100
Subject: [PATCH 13/18] Fix: avoid destroying QThread object before it can
finish
---
damnit/gui/main_window.py | 6 ++++--
damnit/gui/open_dialog.py | 8 +++-----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/damnit/gui/main_window.py b/damnit/gui/main_window.py
index e8777af3..8225f6e2 100644
--- a/damnit/gui/main_window.py
+++ b/damnit/gui/main_window.py
@@ -190,7 +190,8 @@ def _menu_bar_help(self) -> None:
dialog.exec()
def _menu_bar_autoconfigure(self) -> None:
- context_dir, prop_no = OpenDBDialog.run_get_result(parent=self)
+ open_dialog = OpenDBDialog(self)
+ context_dir, prop_no = open_dialog.run_get_result()
if context_dir is None:
return
if not prompt_setup_db_and_backend(context_dir, prop_no, parent=self):
@@ -1011,7 +1012,8 @@ def run_app(context_dir, connect_to_kafka=True):
application.setStyle(TableViewStyle())
if context_dir is None:
- context_dir, prop_no = OpenDBDialog.run_get_result()
+ open_dialog = OpenDBDialog()
+ context_dir, prop_no = open_dialog.run_get_result()
if context_dir is None:
return 0
if not prompt_setup_db_and_backend(context_dir, prop_no):
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 791d1205..53e2a757 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -42,12 +42,10 @@ def __init__(self, parent=None):
self.finished.connect(self.proposal_finder_thread.quit)
self.proposal_finder_thread.start()
- @staticmethod
- def run_get_result(parent=None) -> (Optional[Path], Optional[int]):
- dlg = OpenDBDialog(parent)
- if dlg.exec() == QDialog.Rejected:
+ def run_get_result(self) -> (Optional[Path], Optional[int]):
+ if self.exec() == QDialog.Rejected:
return None, None
- return dlg.get_chosen_dir(), dlg.get_proposal_num()
+ return self.get_chosen_dir(), self.get_proposal_num()
def proposal_dir_result(self, propnum: str, dir: str):
if propnum != self.ui.proposal_edit.text():
From 08413fdadaaed59527b0c43a0fe6b8f66b0483e9 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 6 Jul 2023 13:09:43 +0100
Subject: [PATCH 14/18] Don't start thread unless dialog is shown
---
damnit/gui/open_dialog.py | 5 +++--
tests/test_gui.py | 2 ++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/damnit/gui/open_dialog.py b/damnit/gui/open_dialog.py
index 53e2a757..f3967239 100644
--- a/damnit/gui/open_dialog.py
+++ b/damnit/gui/open_dialog.py
@@ -34,15 +34,16 @@ def __init__(self, parent=None):
self.ui.browse_button.clicked.connect(self.browse_for_folder)
self.ui.proposal_edit.setFocus()
- self.proposal_finder_thread = QThread()
+ self.proposal_finder_thread = QThread(parent=parent)
self.proposal_finder = ProposalFinder()
self.proposal_finder.moveToThread(self.proposal_finder_thread)
self.ui.proposal_edit.textChanged.connect(self.proposal_finder.find_proposal)
self.proposal_finder.find_result.connect(self.proposal_dir_result)
self.finished.connect(self.proposal_finder_thread.quit)
- self.proposal_finder_thread.start()
+ self.proposal_finder_thread.finished.connect(self.proposal_finder_thread.deleteLater)
def run_get_result(self) -> (Optional[Path], Optional[int]):
+ self.proposal_finder_thread.start()
if self.exec() == QDialog.Rejected:
return None, None
return self.get_chosen_dir(), self.get_proposal_num()
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 2840db53..7501f7cb 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -701,6 +701,7 @@ def get_index(title, row=0):
def test_open_dialog(mock_db, qtbot):
db_dir, db = mock_db
dlg = OpenDBDialog()
+ dlg.proposal_finder_thread.start()
# Test supplying a proposal number:
with patch("damnit.gui.open_dialog.find_proposal", return_value=str(db_dir)):
@@ -714,6 +715,7 @@ def test_open_dialog(mock_db, qtbot):
# Test selecting a folder:
dlg = OpenDBDialog()
+ dlg.proposal_finder_thread.start()
dlg.ui.folder_rb.setChecked(True)
with patch.object(QFileDialog, 'getExistingDirectory', return_value=str(db_dir)):
dlg.ui.browse_button.click()
From eca1a8b31c408f53f8a6f13823c79ede5b592b21 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 6 Jul 2023 13:28:08 +0100
Subject: [PATCH 15/18] Simpler way to wait for thread to finish in test
---
tests/test_gui.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 7501f7cb..3dc3d49d 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -707,8 +707,8 @@ def test_open_dialog(mock_db, qtbot):
with patch("damnit.gui.open_dialog.find_proposal", return_value=str(db_dir)):
with qtbot.waitSignal(dlg.proposal_finder.find_result):
dlg.ui.proposal_edit.setText('1234')
- with qtbot.waitSignal(dlg.proposal_finder_thread.finished):
- dlg.accept()
+ dlg.accept()
+ dlg.proposal_finder_thread.wait(2000)
assert dlg.get_chosen_dir() == db_dir / 'usr/Shared/amore'
assert dlg.get_proposal_num() == 1234
@@ -719,8 +719,8 @@ def test_open_dialog(mock_db, qtbot):
dlg.ui.folder_rb.setChecked(True)
with patch.object(QFileDialog, 'getExistingDirectory', return_value=str(db_dir)):
dlg.ui.browse_button.click()
- with qtbot.waitSignal(dlg.proposal_finder_thread.finished):
- dlg.accept()
+ dlg.accept()
+ dlg.proposal_finder_thread.wait(2000)
assert dlg.get_chosen_dir() == db_dir
assert dlg.get_proposal_num() is None
From 086eec960894f48cd9cfde33066e055ac2ba8130 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 6 Jul 2023 13:51:21 +0100
Subject: [PATCH 16/18] Remove some unused imports
---
damnit/gui/main_window.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/damnit/gui/main_window.py b/damnit/gui/main_window.py
index 8225f6e2..ca88c789 100644
--- a/damnit/gui/main_window.py
+++ b/damnit/gui/main_window.py
@@ -15,11 +15,10 @@
from pandas.api.types import infer_dtype
from kafka.errors import NoBrokersAvailable
-from extra_data.read_machinery import find_proposal
from PyQt5 import QtCore, QtGui, QtWidgets, QtSvg
from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTabWidget
+from PyQt5.QtWidgets import QMessageBox, QTabWidget
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
from ..backend.db import db_path, DamnitDB
From 04fa3345146b693f9b6fc9ad73fdb9c081d82f74 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 6 Jul 2023 13:54:45 +0100
Subject: [PATCH 17/18] Remove unused mocking
---
tests/test_gui.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 3dc3d49d..6d7d2df1 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -307,9 +307,7 @@ def helper_patch():
# Patch things such that the GUI thinks we're on GPFS trying to open
# p1234, and the user always wants to create a database and start the
# backend.
- with (patch.object(win, "gpfs_accessible", return_value=True),
- patch(f"{pkg}.OpenDBDialog.run_get_result", return_value=(db_dir, 1234)),
- patch(f"{pkg}.find_proposal", return_value=tmp_path),
+ with (patch(f"{pkg}.OpenDBDialog.run_get_result", return_value=(db_dir, 1234)),
patch.object(QMessageBox, "question", return_value=QMessageBox.Yes),
patch(f"{pkg}.initialize_and_start_backend") as initialize_and_start_backend,
patch.object(win, "autoconfigure")):
From d7125566ddc3563db48809f49ab5ae88eec629ba Mon Sep 17 00:00:00 2001
From: Thomas Kluyver
Date: Thu, 6 Jul 2023 13:56:08 +0100
Subject: [PATCH 18/18] Remove unused method
---
damnit/gui/main_window.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/damnit/gui/main_window.py b/damnit/gui/main_window.py
index ca88c789..88d6b379 100644
--- a/damnit/gui/main_window.py
+++ b/damnit/gui/main_window.py
@@ -199,9 +199,6 @@ def _menu_bar_autoconfigure(self) -> None:
self.autoconfigure(context_dir, proposal=prop_no)
- def gpfs_accessible(self):
- return os.path.isdir("/gpfs/exfel/exp")
-
def save_settings(self):
self._settings_db_path.parent.mkdir(parents=True, exist_ok=True)