From 21f98d07062dd477bf03cd9f97197900237d72a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Sat, 1 Feb 2025 18:53:24 +0100 Subject: [PATCH 1/8] Refactor widgets creation, add smaller methods #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 104 +++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index bbc46040e0..fa37cc15aa 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -132,30 +132,43 @@ def __init__(self, manager, expurl): self.manager = manager self.expurl = expurl + self.scheduling = manager.get_submission_scheduling(expurl) + self.options = manager.get_submission_options(expurl) + self.hdf5_load_directory = os.path.expanduser("~") - editor_class = self.manager.get_argument_editor_class(expurl) + self._create_argument_editor() + self._create_due_date_widgets() + self._create_pipeline_widgets() + self._create_priority_widgets() + self._create_flush_widgets() + self._create_devarg_override_widgets() + self._create_log_level_widgets() + self._create_repo_rev_widgets() + self._create_submit_widgets() + self._create_reqterm_widgets() + + def _create_argument_editor(self): + editor_class = self.manager.get_argument_editor_class(self.expurl) self.argeditor = editor_class(self.manager, self, self.expurl) self.layout.addWidget(self.argeditor, 0, 0, 1, 5) self.layout.setRowStretch(0, 1) - scheduling = manager.get_submission_scheduling(expurl) - options = manager.get_submission_options(expurl) - + def _create_due_date_widgets(self): datetime = QtWidgets.QDateTimeEdit() datetime.setDisplayFormat("MMM d yyyy hh:mm:ss") datetime_en = QtWidgets.QCheckBox("Due date:") self.layout.addWidget(datetime_en, 1, 0) self.layout.addWidget(datetime, 1, 1) - if scheduling["due_date"] is None: + if self.scheduling["due_date"] is None: datetime.setDate(QtCore.QDate.currentDate()) else: datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch( - int(scheduling["due_date"] * 1000))) - datetime_en.setChecked(scheduling["due_date"] is not None) + int(self.scheduling["due_date"] * 1000))) + datetime_en.setChecked(self.scheduling["due_date"] is not None) def update_datetime(dt): - scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000 + self.scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000 datetime_en.setChecked(True) datetime.dateTimeChanged.connect(update_datetime) @@ -164,44 +177,47 @@ def update_datetime_en(checked): due_date = datetime.dateTime().toMSecsSinceEpoch() / 1000 else: due_date = None - scheduling["due_date"] = due_date + self.scheduling["due_date"] = due_date datetime_en.stateChanged.connect(update_datetime_en) + def _create_pipeline_widgets(self): self.pipeline_name = QtWidgets.QLineEdit() pipeline_name = self.pipeline_name self.layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2) self.layout.addWidget(pipeline_name, 1, 3) - pipeline_name.setText(scheduling["pipeline_name"]) + pipeline_name.setText(self.scheduling["pipeline_name"]) def update_pipeline_name(text): - scheduling["pipeline_name"] = text + self.scheduling["pipeline_name"] = text pipeline_name.textChanged.connect(update_pipeline_name) + def _create_priority_widgets(self): self.priority = QtWidgets.QSpinBox() priority = self.priority priority.setRange(-99, 99) self.layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0) self.layout.addWidget(priority, 2, 1) - - priority.setValue(scheduling["priority"]) + priority.setValue(self.scheduling["priority"]) def update_priority(value): - scheduling["priority"] = value + self.scheduling["priority"] = value priority.valueChanged.connect(update_priority) + def _create_flush_widgets(self): self.flush = QtWidgets.QCheckBox("Flush") flush = self.flush flush.setToolTip("Flush the pipeline (of current- and higher-priority " "experiments) before starting the experiment") self.layout.addWidget(flush, 2, 2) - flush.setChecked(scheduling["flush"]) + flush.setChecked(self.scheduling["flush"]) def update_flush(checked): - scheduling["flush"] = bool(checked) + self.scheduling["flush"] = bool(checked) flush.stateChanged.connect(update_flush) + def _create_devarg_override_widgets(self): devarg_override = QtWidgets.QComboBox() devarg_override.setEditable(True) devarg_override.lineEdit().setPlaceholderText("Override device arguments") @@ -210,13 +226,14 @@ def update_flush(checked): devarg_override.insertItem(1, "core:report_invariants=True") self.layout.addWidget(devarg_override, 2, 3) - devarg_override.setCurrentText(options["devarg_override"]) + devarg_override.setCurrentText(self.options["devarg_override"]) def update_devarg_override(text): - options["devarg_override"] = text + self.options["devarg_override"] = text devarg_override.editTextChanged.connect(update_devarg_override) self.devarg_override = devarg_override + def _create_log_level_widgets(self): log_level = QtWidgets.QComboBox() log_level.addItems(log_levels) log_level.setCurrentIndex(1) @@ -227,14 +244,15 @@ def update_devarg_override(text): self.layout.addWidget(log_level, 3, 1) log_level.setCurrentIndex(log_levels.index( - log_level_to_name(options["log_level"]))) + log_level_to_name(self.options["log_level"]))) def update_log_level(index): - options["log_level"] = getattr(logging, log_level.currentText()) + self.options["log_level"] = getattr(logging, log_level.currentText()) log_level.currentIndexChanged.connect(update_log_level) self.log_level = log_level - if "repo_rev" in options: + def _create_repo_rev_widgets(self): + if "repo_rev" in self.options: repo_rev = QtWidgets.QLineEdit() repo_rev.setPlaceholderText("current") repo_rev.setClearButtonEnabled(True) @@ -245,17 +263,18 @@ def update_log_level(index): self.layout.addWidget(repo_rev_label, 3, 2) self.layout.addWidget(repo_rev, 3, 3) - if options["repo_rev"] is not None: - repo_rev.setText(options["repo_rev"]) + if self.options["repo_rev"] is not None: + repo_rev.setText(self.options["repo_rev"]) def update_repo_rev(text): if text: - options["repo_rev"] = text + self.options["repo_rev"] = text else: - options["repo_rev"] = None + self.options["repo_rev"] = None repo_rev.textChanged.connect(update_repo_rev) self.repo_rev = repo_rev + def _create_submit_widgets(self): submit = QtWidgets.QPushButton("Submit") submit.setIcon(QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) @@ -266,6 +285,17 @@ def update_repo_rev(text): self.layout.addWidget(submit, 1, 4, 2, 1) submit.clicked.connect(self.submit_clicked) + def submit_clicked(self): + self.argeditor.about_to_submit() + try: + self.manager.submit(self.expurl) + except Exception: + # May happen when experiment has been removed + # from repository/explist + logger.error("Failed to submit '%s'", + self.expurl, exc_info=True) + + def _create_reqterm_widgets(self): reqterm = QtWidgets.QPushButton("Terminate instances") reqterm.setIcon(QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton)) @@ -276,22 +306,10 @@ def update_repo_rev(text): self.layout.addWidget(reqterm, 3, 4) reqterm.clicked.connect(self.reqterm_clicked) - self.hdf5_load_directory = os.path.expanduser("~") - - def submit_clicked(self): - self.argeditor.about_to_submit() - try: - self.manager.submit(self.expurl) - except: - # May happen when experiment has been removed - # from repository/explist - logger.error("Failed to submit '%s'", - self.expurl, exc_info=True) - def reqterm_clicked(self): try: self.manager.request_inst_term(self.expurl) - except: + except Exception: # May happen when experiment has been removed # from repository/explist logger.error("Failed to request termination of instances of '%s'", @@ -395,11 +413,11 @@ async def _recompute_sched_options_task(self): return sched_defaults = expdesc["scheduler_defaults"] - scheduling = self.manager.get_submission_scheduling(self.expurl) - scheduling.update(sched_defaults) - self.priority.setValue(scheduling["priority"]) - self.pipeline_name.setText(scheduling["pipeline_name"]) - self.flush.setChecked(scheduling["flush"]) + self.scheduling = self.manager.get_submission_scheduling(self.expurl) + self.scheduling.update(sched_defaults) + self.priority.setValue(self.scheduling["priority"]) + self.pipeline_name.setText(self.scheduling["pipeline_name"]) + self.flush.setChecked(self.scheduling["flush"]) def _load_hdf5_clicked(self): asyncio.ensure_future(self._load_hdf5_task()) From 83377975116920533670807b0ef4a1ce0829f3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Sat, 1 Feb 2025 19:43:02 +0100 Subject: [PATCH 2/8] Split layout into always_visible_layout and foldable_layout #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 92 +++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index fa37cc15aa..c46a3cd8b4 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -115,28 +115,59 @@ def drawComplexControl(self, control, option, painter, widget=None): class _ExperimentDock(QtWidgets.QMdiSubWindow): sigClosed = QtCore.pyqtSignal() + def __init__(self, manager, expurl): - QtWidgets.QMdiSubWindow.__init__(self) + super(_ExperimentDock, self).__init__() qfm = QtGui.QFontMetrics(self.font()) self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing()) self.setWindowTitle(expurl) self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView)) - self.layout = QtWidgets.QGridLayout() - top_widget = QtWidgets.QWidget() - top_widget.setLayout(self.layout) - self.setWidget(top_widget) - self.layout.setSpacing(5) - self.layout.setContentsMargins(5, 5, 5, 5) - self.manager = manager self.expurl = expurl self.scheduling = manager.get_submission_scheduling(expurl) self.options = manager.get_submission_options(expurl) self.hdf5_load_directory = os.path.expanduser("~") + master_layout = QtWidgets.QVBoxLayout() self._create_argument_editor() + master_layout.addWidget(self.argeditor) + + # Create a toggle button that will collapse/expand the options. + self.fold_toggle = QtWidgets.QToolButton(text="Collapse scheduler settings", + checkable=True) + self.fold_toggle.setChecked(False) + self.fold_toggle.setToolTip("Collapse/Expand scheduler settings") + self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.DownArrow) + self.fold_toggle.clicked.connect(self.on_fold_toggle) + master_layout.addWidget(self.fold_toggle) + + # Create a container widget (with a grid layout) for all the foldable + # options. + self.foldable_container = QtWidgets.QWidget() + self.foldable_layout = QtWidgets.QGridLayout() + self.foldable_layout.setSpacing(5) + self.foldable_layout.setContentsMargins(5, 5, 5, 5) + self.foldable_container.setLayout(self.foldable_layout) + master_layout.addWidget(self.foldable_container) + + # Create a container widget (with a horizontal layout) for + # always-visible buttons. + self.always_visible_container = QtWidgets.QWidget() + self.always_visible_layout = QtWidgets.QHBoxLayout() + self.always_visible_layout.setSpacing(5) + self.always_visible_layout.setContentsMargins(5, 5, 5, 5) + self.always_visible_container.setLayout(self.always_visible_layout) + master_layout.addWidget(self.always_visible_container) + + # Set the master layout on the top widget. + top_widget = QtWidgets.QWidget() + top_widget.setLayout(master_layout) + self.setWidget(top_widget) + + # --- Create the various widget groups --- + # Place all “foldable” widgets into the foldable_layout. self._create_due_date_widgets() self._create_pipeline_widgets() self._create_priority_widgets() @@ -144,21 +175,34 @@ def __init__(self, manager, expurl): self._create_devarg_override_widgets() self._create_log_level_widgets() self._create_repo_rev_widgets() + # Place the submit and termination buttons into the always-visible + # container. self._create_submit_widgets() self._create_reqterm_widgets() + self.on_fold_toggle() + + def on_fold_toggle(self): + """Toggle the visibility of the options.""" + if self.fold_toggle.isChecked(): + self.foldable_container.show() + self.fold_toggle.setText("Collapse scheduler settings") + self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.DownArrow) + else: + self.foldable_container.hide() + self.fold_toggle.setText("Expand scheduler settings") + self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.RightArrow) + def _create_argument_editor(self): editor_class = self.manager.get_argument_editor_class(self.expurl) self.argeditor = editor_class(self.manager, self, self.expurl) - self.layout.addWidget(self.argeditor, 0, 0, 1, 5) - self.layout.setRowStretch(0, 1) def _create_due_date_widgets(self): datetime = QtWidgets.QDateTimeEdit() datetime.setDisplayFormat("MMM d yyyy hh:mm:ss") datetime_en = QtWidgets.QCheckBox("Due date:") - self.layout.addWidget(datetime_en, 1, 0) - self.layout.addWidget(datetime, 1, 1) + self.foldable_layout.addWidget(datetime_en, 1, 0) + self.foldable_layout.addWidget(datetime, 1, 1) if self.scheduling["due_date"] is None: datetime.setDate(QtCore.QDate.currentDate()) @@ -183,8 +227,8 @@ def update_datetime_en(checked): def _create_pipeline_widgets(self): self.pipeline_name = QtWidgets.QLineEdit() pipeline_name = self.pipeline_name - self.layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2) - self.layout.addWidget(pipeline_name, 1, 3) + self.foldable_layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2) + self.foldable_layout.addWidget(pipeline_name, 1, 3) pipeline_name.setText(self.scheduling["pipeline_name"]) @@ -196,8 +240,8 @@ def _create_priority_widgets(self): self.priority = QtWidgets.QSpinBox() priority = self.priority priority.setRange(-99, 99) - self.layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0) - self.layout.addWidget(priority, 2, 1) + self.foldable_layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0) + self.foldable_layout.addWidget(priority, 2, 1) priority.setValue(self.scheduling["priority"]) def update_priority(value): @@ -209,7 +253,7 @@ def _create_flush_widgets(self): flush = self.flush flush.setToolTip("Flush the pipeline (of current- and higher-priority " "experiments) before starting the experiment") - self.layout.addWidget(flush, 2, 2) + self.foldable_layout.addWidget(flush, 2, 2) flush.setChecked(self.scheduling["flush"]) @@ -224,7 +268,7 @@ def _create_devarg_override_widgets(self): devarg_override.lineEdit().setClearButtonEnabled(True) devarg_override.insertItem(0, "core:analyze_at_run_end=True") devarg_override.insertItem(1, "core:report_invariants=True") - self.layout.addWidget(devarg_override, 2, 3) + self.foldable_layout.addWidget(devarg_override, 2, 3) devarg_override.setCurrentText(self.options["devarg_override"]) @@ -240,8 +284,8 @@ def _create_log_level_widgets(self): log_level.setToolTip("Minimum level for log entry production") log_level_label = QtWidgets.QLabel("Logging level:") log_level_label.setToolTip("Minimum level for log message production") - self.layout.addWidget(log_level_label, 3, 0) - self.layout.addWidget(log_level, 3, 1) + self.foldable_layout.addWidget(log_level_label, 3, 0) + self.foldable_layout.addWidget(log_level, 3, 1) log_level.setCurrentIndex(log_levels.index( log_level_to_name(self.options["log_level"]))) @@ -260,8 +304,8 @@ def _create_repo_rev_widgets(self): repo_rev_label.setToolTip("Experiment repository revision " "(commit ID) or reference (branch " "or tag) to use") - self.layout.addWidget(repo_rev_label, 3, 2) - self.layout.addWidget(repo_rev, 3, 3) + self.foldable_layout.addWidget(repo_rev_label, 3, 2) + self.foldable_layout.addWidget(repo_rev, 3, 3) if self.options["repo_rev"] is not None: repo_rev.setText(self.options["repo_rev"]) @@ -282,7 +326,7 @@ def _create_submit_widgets(self): submit.setShortcut("CTRL+RETURN") submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) - self.layout.addWidget(submit, 1, 4, 2, 1) + self.always_visible_layout.addWidget(submit) submit.clicked.connect(self.submit_clicked) def submit_clicked(self): @@ -303,7 +347,7 @@ def _create_reqterm_widgets(self): reqterm.setShortcut("CTRL+BACKSPACE") reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) - self.layout.addWidget(reqterm, 3, 4) + self.always_visible_layout.addWidget(reqterm) reqterm.clicked.connect(self.reqterm_clicked) def reqterm_clicked(self): From 010dc34fc0433d499e2731b150a40b548c1b899f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Sat, 1 Feb 2025 22:00:23 +0100 Subject: [PATCH 3/8] Refactor objects ownership #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 128 ++++++++++++++++----------------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index c46a3cd8b4..fbdb3f9472 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -226,108 +226,104 @@ def update_datetime_en(checked): def _create_pipeline_widgets(self): self.pipeline_name = QtWidgets.QLineEdit() - pipeline_name = self.pipeline_name self.foldable_layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2) - self.foldable_layout.addWidget(pipeline_name, 1, 3) + self.foldable_layout.addWidget(self.pipeline_name, 1, 3) - pipeline_name.setText(self.scheduling["pipeline_name"]) + self.pipeline_name.setText(self.scheduling["pipeline_name"]) def update_pipeline_name(text): self.scheduling["pipeline_name"] = text - pipeline_name.textChanged.connect(update_pipeline_name) + self.pipeline_name.textChanged.connect(update_pipeline_name) def _create_priority_widgets(self): self.priority = QtWidgets.QSpinBox() - priority = self.priority - priority.setRange(-99, 99) + self.priority.setRange(-99, 99) self.foldable_layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0) - self.foldable_layout.addWidget(priority, 2, 1) - priority.setValue(self.scheduling["priority"]) + self.foldable_layout.addWidget(self.priority, 2, 1) + self.priority.setValue(self.scheduling["priority"]) def update_priority(value): self.scheduling["priority"] = value - priority.valueChanged.connect(update_priority) + self.priority.valueChanged.connect(update_priority) def _create_flush_widgets(self): self.flush = QtWidgets.QCheckBox("Flush") - flush = self.flush - flush.setToolTip("Flush the pipeline (of current- and higher-priority " - "experiments) before starting the experiment") - self.foldable_layout.addWidget(flush, 2, 2) + self.flush.setToolTip("Flush the pipeline (of current- and higher-priority " + "experiments) before starting the experiment") + self.foldable_layout.addWidget(self.flush, 2, 2) - flush.setChecked(self.scheduling["flush"]) + self.flush.setChecked(self.scheduling["flush"]) - def update_flush(checked): - self.scheduling["flush"] = bool(checked) - flush.stateChanged.connect(update_flush) + def update_flush(state): + self.scheduling["flush"] = bool(state) + self.flush.stateChanged.connect(update_flush) def _create_devarg_override_widgets(self): - devarg_override = QtWidgets.QComboBox() - devarg_override.setEditable(True) - devarg_override.lineEdit().setPlaceholderText("Override device arguments") - devarg_override.lineEdit().setClearButtonEnabled(True) - devarg_override.insertItem(0, "core:analyze_at_run_end=True") - devarg_override.insertItem(1, "core:report_invariants=True") - self.foldable_layout.addWidget(devarg_override, 2, 3) + self.devarg_override = QtWidgets.QComboBox() + self.devarg_override.setEditable(True) + self.devarg_override.lineEdit().setPlaceholderText( + "Override device arguments") + self.devarg_override.lineEdit().setClearButtonEnabled(True) + self.devarg_override.insertItem(0, "core:analyze_at_run_end=True") + self.devarg_override.insertItem(1, "core:report_invariants=True") + self.foldable_layout.addWidget(self.devarg_override, 2, 3) - devarg_override.setCurrentText(self.options["devarg_override"]) + self.devarg_override.setCurrentText(self.options["devarg_override"]) def update_devarg_override(text): self.options["devarg_override"] = text - devarg_override.editTextChanged.connect(update_devarg_override) - self.devarg_override = devarg_override + self.devarg_override.editTextChanged.connect(update_devarg_override) def _create_log_level_widgets(self): - log_level = QtWidgets.QComboBox() - log_level.addItems(log_levels) - log_level.setCurrentIndex(1) - log_level.setToolTip("Minimum level for log entry production") - log_level_label = QtWidgets.QLabel("Logging level:") - log_level_label.setToolTip("Minimum level for log message production") - self.foldable_layout.addWidget(log_level_label, 3, 0) - self.foldable_layout.addWidget(log_level, 3, 1) - - log_level.setCurrentIndex(log_levels.index( + self.log_level = QtWidgets.QComboBox() + self.log_level.addItems(log_levels) + self.log_level.setCurrentIndex(1) + self.log_level.setToolTip("Minimum level for log entry production") + self.log_level_label = QtWidgets.QLabel("Logging level:") + self.log_level_label.setToolTip( + "Minimum level for log message production") + self.foldable_layout.addWidget(self.log_level_label, 3, 0) + self.foldable_layout.addWidget(self.log_level, 3, 1) + + self.log_level.setCurrentIndex(log_levels.index( log_level_to_name(self.options["log_level"]))) def update_log_level(index): - self.options["log_level"] = getattr(logging, log_level.currentText()) - log_level.currentIndexChanged.connect(update_log_level) - self.log_level = log_level + self.options["log_level"] = getattr(logging, self.log_level.currentText()) + self.log_level.currentIndexChanged.connect(update_log_level) def _create_repo_rev_widgets(self): if "repo_rev" in self.options: - repo_rev = QtWidgets.QLineEdit() - repo_rev.setPlaceholderText("current") - repo_rev.setClearButtonEnabled(True) - repo_rev_label = QtWidgets.QLabel("Rev / ref:") - repo_rev_label.setToolTip("Experiment repository revision " - "(commit ID) or reference (branch " - "or tag) to use") - self.foldable_layout.addWidget(repo_rev_label, 3, 2) - self.foldable_layout.addWidget(repo_rev, 3, 3) + self.repo_rev = QtWidgets.QLineEdit() + self.repo_rev.setPlaceholderText("current") + self.repo_rev.setClearButtonEnabled(True) + self.repo_rev_label = QtWidgets.QLabel("Rev / ref:") + self.repo_rev_label.setToolTip("Experiment repository revision " + "(commit ID) or reference (branch " + "or tag) to use") + self.foldable_layout.addWidget(self.repo_rev_label, 3, 2) + self.foldable_layout.addWidget(self.repo_rev, 3, 3) if self.options["repo_rev"] is not None: - repo_rev.setText(self.options["repo_rev"]) + self.repo_rev.setText(self.options["repo_rev"]) def update_repo_rev(text): if text: self.options["repo_rev"] = text else: self.options["repo_rev"] = None - repo_rev.textChanged.connect(update_repo_rev) - self.repo_rev = repo_rev + self.repo_rev.textChanged.connect(update_repo_rev) def _create_submit_widgets(self): - submit = QtWidgets.QPushButton("Submit") - submit.setIcon(QtWidgets.QApplication.style().standardIcon( + self.submit = QtWidgets.QPushButton("Submit") + self.submit.setIcon(QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) - submit.setToolTip("Schedule the experiment (Ctrl+Return)") - submit.setShortcut("CTRL+RETURN") - submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, + self.submit.setToolTip("Schedule the experiment (Ctrl+Return)") + self.submit.setShortcut("CTRL+RETURN") + self.submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) - self.always_visible_layout.addWidget(submit) - submit.clicked.connect(self.submit_clicked) + self.always_visible_layout.addWidget(self.submit) + self.submit.clicked.connect(self.submit_clicked) def submit_clicked(self): self.argeditor.about_to_submit() @@ -340,15 +336,15 @@ def submit_clicked(self): self.expurl, exc_info=True) def _create_reqterm_widgets(self): - reqterm = QtWidgets.QPushButton("Terminate instances") - reqterm.setIcon(QtWidgets.QApplication.style().standardIcon( + self.reqterm = QtWidgets.QPushButton("Terminate instances") + self.reqterm.setIcon(QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton)) - reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)") - reqterm.setShortcut("CTRL+BACKSPACE") - reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, + self.reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)") + self.reqterm.setShortcut("CTRL+BACKSPACE") + self.reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) - self.always_visible_layout.addWidget(reqterm) - reqterm.clicked.connect(self.reqterm_clicked) + self.always_visible_layout.addWidget(self.reqterm) + self.reqterm.clicked.connect(self.reqterm_clicked) def reqterm_clicked(self): try: From f653377225f94af658c847f85f07350390cead64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Tue, 4 Feb 2025 23:41:31 +0100 Subject: [PATCH 4/8] Resizing adjustments #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index fbdb3f9472..d80288f405 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -11,7 +11,7 @@ from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget from artiq.gui.fuzzy_select import FuzzySelectWidget -from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name) +from artiq.gui.tools import (log_level_to_name, get_open_file_name) from artiq.tools import parse_devarg_override, unparse_devarg_override @@ -47,20 +47,16 @@ def __init__(self, manager, dock, expurl): QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_BrowserReload)) recompute_arguments.clicked.connect(dock._recompute_arguments_clicked) + recompute_arguments.setMinimumWidth(200) load_hdf5 = QtWidgets.QPushButton("Load HDF5") load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton)) load_hdf5.clicked.connect(dock._load_hdf5_clicked) + load_hdf5.setMinimumWidth(120) - buttons = LayoutWidget() - buttons.addWidget(recompute_arguments, 1, 1) - buttons.addWidget(load_hdf5, 1, 2) - buttons.layout.setColumnStretch(0, 1) - buttons.layout.setColumnStretch(1, 0) - buttons.layout.setColumnStretch(2, 0) - buttons.layout.setColumnStretch(3, 1) - self.setItemWidget(self.bottom_item, 1, buttons) + self.setItemWidget(self.bottom_item, 0, load_hdf5) + self.setItemWidget(self.bottom_item, 1, recompute_arguments) def reset_entry(self, key): asyncio.ensure_future(self._recompute_argument(key)) @@ -188,10 +184,17 @@ def on_fold_toggle(self): self.foldable_container.show() self.fold_toggle.setText("Collapse scheduler settings") self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.DownArrow) + if self.width() < 550: + ideal_width = self.sizeHint().width() + self.resize(ideal_width, self.height()) + if self.height() < 285: + self.resize(self.width(), 285) + else: self.foldable_container.hide() self.fold_toggle.setText("Expand scheduler settings") self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.RightArrow) + self.always_visible_container.setMaximumHeight(30) def _create_argument_editor(self): editor_class = self.manager.get_argument_editor_class(self.expurl) @@ -321,7 +324,9 @@ def _create_submit_widgets(self): self.submit.setToolTip("Schedule the experiment (Ctrl+Return)") self.submit.setShortcut("CTRL+RETURN") self.submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, - QtWidgets.QSizePolicy.Policy.Expanding) + QtWidgets.QSizePolicy.Policy.Expanding) + self.submit.setMaximumHeight(25) + self.submit.setMinimumWidth(175) self.always_visible_layout.addWidget(self.submit) self.submit.clicked.connect(self.submit_clicked) @@ -342,7 +347,9 @@ def _create_reqterm_widgets(self): self.reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)") self.reqterm.setShortcut("CTRL+BACKSPACE") self.reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, - QtWidgets.QSizePolicy.Policy.Expanding) + QtWidgets.QSizePolicy.Policy.Expanding) + self.reqterm.setMaximumHeight(25) + self.reqterm.setMinimumWidth(175) self.always_visible_layout.addWidget(self.reqterm) self.reqterm.clicked.connect(self.reqterm_clicked) From 92355ea66ca35b7f620e7f818dce3852bf175b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Tue, 4 Feb 2025 23:51:13 +0100 Subject: [PATCH 5/8] Make sure that the expand button text appears #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index d80288f405..5c4da91178 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -133,6 +133,8 @@ def __init__(self, manager, expurl): # Create a toggle button that will collapse/expand the options. self.fold_toggle = QtWidgets.QToolButton(text="Collapse scheduler settings", checkable=True) + self.fold_toggle.setToolButtonStyle( + QtCore.Qt.ToolButtonStyle.ToolButtonTextBesideIcon) self.fold_toggle.setChecked(False) self.fold_toggle.setToolTip("Collapse/Expand scheduler settings") self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.DownArrow) From 66d58ca8282f10318f463e89db34ee77638b7a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Wed, 5 Feb 2025 00:05:20 +0100 Subject: [PATCH 6/8] Remember state of managment expansion #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index 5c4da91178..bc37baf87c 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -517,13 +517,17 @@ def save_state(self): return { "args": self.argeditor.save_state(), "geometry": bytes(self.saveGeometry()), - "hdf5_load_directory": self.hdf5_load_directory + "hdf5_load_directory": self.hdf5_load_directory, + "options_expanded": self.fold_toggle.isChecked() } def restore_state(self, state): self.argeditor.restore_state(state["args"]) self.restoreGeometry(QtCore.QByteArray(state["geometry"])) self.hdf5_load_directory = state["hdf5_load_directory"] + if "options_expanded" in state: # Only useful for the upgrade + self.fold_toggle.setChecked(state["options_expanded"]) + self.on_fold_toggle() class _QuickOpenDialog(QtWidgets.QDialog): From 6b26da1b6b397065b64f22e97ce3ce6be68416de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Fri, 14 Mar 2025 18:58:27 +0100 Subject: [PATCH 7/8] Stretch the first entry element #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/gui/entries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/gui/entries.py b/artiq/gui/entries.py index 1283f001ba..aac24dee93 100644 --- a/artiq/gui/entries.py +++ b/artiq/gui/entries.py @@ -23,7 +23,7 @@ def __init__(self): set_resize_mode = self.header().setSectionResizeMode else: set_resize_mode = self.header().setResizeMode - set_resize_mode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) + set_resize_mode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) set_resize_mode(1, QtWidgets.QHeaderView.ResizeMode.Stretch) set_resize_mode(2, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.header().setVisible(False) From 01e18d428a61dd3b06bdc77d27995c1caeddb397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Malczak?= Date: Wed, 26 Mar 2025 21:06:43 +0100 Subject: [PATCH 8/8] Add argeditor to master layout when recomputing arguments #3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miłosz Malczak --- artiq/dashboard/experiments.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index bc37baf87c..e66f75d516 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -126,9 +126,9 @@ def __init__(self, manager, expurl): self.options = manager.get_submission_options(expurl) self.hdf5_load_directory = os.path.expanduser("~") - master_layout = QtWidgets.QVBoxLayout() + self.master_layout = QtWidgets.QVBoxLayout() self._create_argument_editor() - master_layout.addWidget(self.argeditor) + self.master_layout.addWidget(self.argeditor) # Create a toggle button that will collapse/expand the options. self.fold_toggle = QtWidgets.QToolButton(text="Collapse scheduler settings", @@ -139,7 +139,7 @@ def __init__(self, manager, expurl): self.fold_toggle.setToolTip("Collapse/Expand scheduler settings") self.fold_toggle.setArrowType(QtCore.Qt.ArrowType.DownArrow) self.fold_toggle.clicked.connect(self.on_fold_toggle) - master_layout.addWidget(self.fold_toggle) + self.master_layout.addWidget(self.fold_toggle) # Create a container widget (with a grid layout) for all the foldable # options. @@ -148,7 +148,7 @@ def __init__(self, manager, expurl): self.foldable_layout.setSpacing(5) self.foldable_layout.setContentsMargins(5, 5, 5, 5) self.foldable_container.setLayout(self.foldable_layout) - master_layout.addWidget(self.foldable_container) + self.master_layout.addWidget(self.foldable_container) # Create a container widget (with a horizontal layout) for # always-visible buttons. @@ -157,11 +157,11 @@ def __init__(self, manager, expurl): self.always_visible_layout.setSpacing(5) self.always_visible_layout.setContentsMargins(5, 5, 5, 5) self.always_visible_container.setLayout(self.always_visible_layout) - master_layout.addWidget(self.always_visible_container) + self.master_layout.addWidget(self.always_visible_container) # Set the master layout on the top widget. top_widget = QtWidgets.QWidget() - top_widget.setLayout(master_layout) + top_widget.setLayout(self.master_layout) self.setWidget(top_widget) # --- Create the various widget groups --- @@ -388,7 +388,7 @@ async def _recompute_arguments_task(self, overrides=dict()): editor_class = self.manager.get_argument_editor_class(self.expurl) self.argeditor = editor_class(self.manager, self, self.expurl) - self.layout.addWidget(self.argeditor, 0, 0, 1, 5) + self.master_layout.insertWidget(0, self.argeditor) self.argeditor.restore_state(argeditor_state) self.apply_window_color()