Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dialog to select proposal/directory when starting GUI #9

Merged
merged 18 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 51 additions & 68 deletions damnit/gui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,6 +32,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__)
Expand Down Expand Up @@ -189,74 +189,15 @@ 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
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):
# 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)

def gpfs_accessible(self):
return os.path.isdir("/gpfs/exfel/exp")
self.autoconfigure(context_dir, proposal=prop_no)

def save_settings(self):
self._settings_db_path.parent.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -1026,13 +967,55 @@ 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
)
application = QtWidgets.QApplication(sys.argv)
application.setStyle(TableViewStyle())

if context_dir is None:
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):
# 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()
return application.exec()
Expand Down
78 changes: 78 additions & 0 deletions damnit/gui/open_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from pathlib import Path
from typing import Optional

from extra_data.read_machinery import find_proposal
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 propnum.isdecimal() and len(propnum) >= 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, parent=None):
super().__init__(parent)
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.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(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.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()

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
self.update_ok()

def update_ok(self):
if self.ui.proposal_rb.isChecked():
valid = bool(self.proposal_dir)
else:
valid = Path(self.ui.folder_edit.text()).is_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(path)

def get_chosen_dir(self):
if self.ui.proposal_rb.isChecked():
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
Loading