From 6a7926ddebd62bd4b5047f48c49eb5656e782d54 Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Thu, 5 Sep 2024 16:35:17 +0100 Subject: [PATCH] dashboard: Allow moving/hiding of Schedule view columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is very handy to make good use of screen real estate, as e.g. file path and Git revision are often not as important to judge the state of the system at a glance – particularly, as the schedule entries can then fit on a single line each. --- artiq/dashboard/schedule.py | 13 ++++++++-- artiq/gui/tools.py | 47 ++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/artiq/dashboard/schedule.py b/artiq/dashboard/schedule.py index 8ea213239..ac77398d7 100644 --- a/artiq/dashboard/schedule.py +++ b/artiq/dashboard/schedule.py @@ -6,6 +6,7 @@ import logging from PyQt6 import QtCore, QtWidgets, QtGui from artiq.gui.models import DictSyncModel +from artiq.gui.tools import SelectableColumnTableView from artiq.tools import elide @@ -66,7 +67,7 @@ class ScheduleDock(QtWidgets.QDockWidget): self.schedule_ctl = schedule_ctl - self.table = QtWidgets.QTableView() + self.table = SelectableColumnTableView() self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) self.table.verticalHeader().setSectionResizeMode( @@ -104,6 +105,9 @@ class ScheduleDock(QtWidgets.QDockWidget): h.resizeSection(6, 20 * cw) h.resizeSection(7, 20 * cw) + # Allow user to reorder or disable columns. + h.setSectionsMovable(True) + def set_model(self, model): self.table_model = model self.table.setModel(self.table_model) @@ -154,4 +158,9 @@ class ScheduleDock(QtWidgets.QDockWidget): return bytes(self.table.horizontalHeader().saveState()) def restore_state(self, state): - self.table.horizontalHeader().restoreState(QtCore.QByteArray(state)) + h = self.table.horizontalHeader() + h.restoreState(QtCore.QByteArray(state)) + + # The state includes the sectionsMovable property, so set it again to be able to + # deal with pre-existing save files from when we used not to enable it. + h.setSectionsMovable(True) diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index 0fc9e4615..63b228632 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -1,7 +1,7 @@ import asyncio import logging -from PyQt6 import QtCore, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets class DoubleClickLineEdit(QtWidgets.QLineEdit): @@ -88,6 +88,51 @@ class LayoutWidget(QtWidgets.QWidget): self.layout.addWidget(item, row, col, rowspan, colspan) +class SelectableColumnTableView(QtWidgets.QTableView): + """A QTableView packaged up with a header row context menu that allows users to + show/hide columns using checkable entries. + + By default, all columns are shown. If only one shown column remains, the entry is + disabled to prevent a situation where no columns are shown, which might be confusing + to the user. + + Qt considers whether columns are shown to be part of the header state, i.e. it is + included in saveState()/restoreState(). + """ + + def __init__(self): + super().__init__() + + self.horizontalHeader().setContextMenuPolicy( + QtCore.Qt.ContextMenuPolicy.CustomContextMenu) + self.horizontalHeader().customContextMenuRequested.connect( + self.show_header_context_menu) + + def show_header_context_menu(self, pos): + menu = QtWidgets.QMenu(self) + + num_columns_total = self.model().columnCount() + num_columns_shown = sum( + (not self.isColumnHidden(i)) for i in range(num_columns_total)) + for i in range(num_columns_total): + name = self.model().headerData(i, QtCore.Qt.Orientation.Horizontal) + action = QtGui.QAction(name, self) + action.setCheckable(True) + + is_currently_hidden = self.isColumnHidden(i) + action.setChecked(not is_currently_hidden) + if not is_currently_hidden: + if num_columns_shown == 1: + # Don't allow hiding of the last visible column. + action.setEnabled(False) + + action.triggered.connect( + lambda checked, i=i: self.setColumnHidden(i, not checked)) + menu.addAction(action) + + menu.exec(self.horizontalHeader().mapToGlobal(pos)) + + async def get_open_file_name(parent, caption, dir, filter): """like QtWidgets.QFileDialog.getOpenFileName(), but a coroutine""" dialog = QtWidgets.QFileDialog(parent, caption, dir, filter)