diff --git a/CHANGELOG b/CHANGELOG
index 3719a03..1bdb659 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
+0.3.1
+ - ref: migrate from QTableView to QTableWidget (issues with Windows 11)
0.3.0
- BREAKING CHANGE: major changes in dcnum postprocessing
- feat: allow to select whether volume should be computed
diff --git a/chipstream/gui/main_window.py b/chipstream/gui/main_window.py
index 006a1d5..26ff3cd 100644
--- a/chipstream/gui/main_window.py
+++ b/chipstream/gui/main_window.py
@@ -16,6 +16,7 @@
from ..path_cache import PathCache
from .._version import version
+from .manager import ChipStreamJobManager
from . import splash
@@ -29,13 +30,15 @@ def __init__(self, *arguments):
application will print the version after initialization
and exit.
"""
+ self.job_manager = ChipStreamJobManager()
QtWidgets.QMainWindow.__init__(self)
ref_ui = resources.files("chipstream.gui") / "main_window.ui"
with resources.as_file(ref_ui) as path_ui:
uic.loadUi(path_ui, self)
+ self.tableWidget_input.set_job_manager(self.job_manager)
+
self.logger = logging.getLogger(__name__)
- self.manager = self.tableView_input.model.manager
# Populate segmenter combobox
self.comboBox_segmenter.blockSignals(True)
@@ -92,7 +95,7 @@ def __init__(self, *arguments):
# Signals
self.run_completed.connect(self.on_run_completed)
- self.tableView_input.row_selected.connect(self.on_select_job)
+ self.tableWidget_input.row_selected.connect(self.on_select_job)
# if "--version" was specified, print the version and exit
if "--version" in arguments:
@@ -103,7 +106,7 @@ def __init__(self, *arguments):
# Create a timer that continuously updates self.textBrowser
self.timer = QtCore.QTimer()
- self.timer.timeout.connect(self.tableView_input.on_selection_changed)
+ self.timer.timeout.connect(self.tableWidget_input.on_selection_changed)
self.timer.start(1000)
splash.splash_close()
@@ -115,9 +118,10 @@ def __init__(self, *arguments):
def append_paths(self, path_list):
"""Add input paths to the table"""
- if not self.manager.is_busy():
+ if not self.job_manager.is_busy():
for pp in path_list:
- self.tableView_input.add_input_path(pp)
+ self.job_manager.add_path(pp)
+ self.tableWidget_input.update_from_job_manager()
@QtCore.pyqtSlot(QtCore.QEvent)
def dragEnterEvent(self, e):
@@ -187,7 +191,7 @@ def get_job_kwargs(self):
return job_kwargs
def is_running(self):
- return self.manager.is_alive()
+ return self.job_manager.is_busy()
@QtCore.pyqtSlot()
def on_action_about(self) -> None:
@@ -220,7 +224,8 @@ def on_action_add(self):
@QtCore.pyqtSlot()
def on_action_clear(self):
"""Clear the current table view"""
- self.manager.clear()
+ self.job_manager.clear()
+ self.tableWidget_input.update_from_job_manager()
@QtCore.pyqtSlot()
def on_action_docs(self):
@@ -258,7 +263,7 @@ def on_path_out(self):
data = self.comboBox_output.currentData()
if data == "input":
# Store output data alongside input data
- self.manager.set_output_path(None)
+ self.job_manager.set_output_path(None)
elif data == "new":
# New output path
default = "." if len(self.path_cache) == 0 else self.path_cache[-1]
@@ -277,16 +282,15 @@ def on_path_out(self):
)
self.comboBox_output.setCurrentIndex(len(self.path_cache) + 1)
self.path_cache.add_path(pathlib.Path(path))
- self.manager.set_output_path(path)
+ self.job_manager.set_output_path(path)
else:
# User pressed cancel
self.comboBox_output.setCurrentIndex(0)
- self.manager.set_output_path(None)
+ self.job_manager.set_output_path(None)
self.comboBox_output.blockSignals(False)
else:
# Data is an integer index for `self.path_cache`
- self.manager.set_output_path(self.path_cache[data])
- print(self.manager._path_out)
+ self.job_manager.set_output_path(self.path_cache[data])
@QtCore.pyqtSlot()
def on_run(self):
@@ -295,7 +299,7 @@ def on_run(self):
# finished. The user can still add items to the list but not
# change the pipeline.
self.widget_options.setEnabled(False)
- self.manager.run_all_in_thread(
+ self.job_manager.run_all_in_thread(
job_kwargs=self.get_job_kwargs(),
callback_when_done=self.run_completed.emit)
@@ -309,7 +313,7 @@ def on_select_job(self, row):
info = "No job selected."
else:
# Display some information in the lower text box.
- info = self.manager.get_info(row)
+ info = self.job_manager.get_info(row)
# Compare the text to the current text.
old_text = self.textBrowser.toPlainText()
if info != old_text:
diff --git a/chipstream/gui/main_window.ui b/chipstream/gui/main_window.ui
index 35f18cc..a336c8f 100644
--- a/chipstream/gui/main_window.ui
+++ b/chipstream/gui/main_window.ui
@@ -41,15 +41,18 @@
Qt::Vertical
-
+
1
3
+
+ QAbstractItemView::NoEditTriggers
+
- QAbstractItemView::InternalMove
+ QAbstractItemView::NoDragDrop
true
@@ -60,6 +63,12 @@
QAbstractItemView::SelectRows
+
+ Qt::ElideMiddle
+
+
+ QAbstractItemView::ScrollPerPixel
+
true
@@ -67,14 +76,32 @@
false
- 50
+ 100
-
- true
+
+ 100
-
+
false
+
+ true
+
+
+
+ Path
+
+
+
+
+ State
+
+
+
+
+ Progress
+
+
@@ -428,7 +455,7 @@
ProgressTable
- QTableView
+ QTableWidget
chipstream.gui.table_progress
diff --git a/chipstream/gui/table_progress.py b/chipstream/gui/table_progress.py
index 00c7846..1104c86 100644
--- a/chipstream/gui/table_progress.py
+++ b/chipstream/gui/table_progress.py
@@ -1,100 +1,83 @@
from PyQt6 import QtCore, QtWidgets
-from .manager import ChipStreamJobManager
-
-ItemProgressRole = QtCore.Qt.ItemDataRole.UserRole + 1001
-
-
-class ProgressDelegate(QtWidgets.QStyledItemDelegate):
- def paint(self, painter, option, index):
- progress = index.data(ItemProgressRole)
- opt = QtWidgets.QStyleOptionProgressBar()
- opt.rect = option.rect
- opt.minimum = 0
- opt.maximum = 100
- opt.progress = int(progress * 100)
- opt.text = f"{progress:.1%}"
- opt.textVisible = True
- QtWidgets.QApplication.style().drawControl(
- QtWidgets.QStyle.ControlElement.CE_ProgressBar, opt, painter)
-
-
-class ProgressModel(QtCore.QAbstractTableModel):
- def __init__(self, *args, **kwargs):
- super(ProgressModel, self).__init__(*args, **kwargs)
- self.manager = ChipStreamJobManager()
- self.map_columns = ["path", "state", "progress"]
- self.headers = [m.capitalize() for m in self.map_columns]
- self.monitor_timer = QtCore.QTimer(self)
- self.monitor_timer.timeout.connect(self.monitor_current_job)
- self.monitor_timer.start(300)
-
- def add_input_path(self, path):
- self.manager.add_path(path)
- self.layoutChanged.emit()
-
- def data(self, index, role):
- status = self.manager[index.row()]
- key = self.map_columns[index.column()]
- if role in [ItemProgressRole, QtCore.Qt.ItemDataRole.DisplayRole]:
- return status[key]
- else:
- return QtCore.QVariant()
-
- def columnCount(self, parent):
- return len(self.headers)
-
- def headerData(self, section, orientation, role):
- if role != QtCore.Qt.ItemDataRole.DisplayRole:
- return QtCore.QVariant()
- return self.headers[section]
-
- def rowCount(self, parent):
- return len(self.manager)
-
- @QtCore.pyqtSlot()
- def monitor_current_job(self):
- current_index = self.manager.current_index
- if current_index is not None:
- self.update_index(current_index)
-
- @QtCore.pyqtSlot(int)
- def update_index(self, index):
- # update the row
- index_1 = self.index(index, 0)
- index_2 = self.index(index, len(self.headers))
- self.dataChanged.emit(index_1,
- index_2,
- [QtCore.Qt.ItemDataRole.DisplayRole,
- QtCore.Qt.ItemDataRole.DisplayRole,
- ItemProgressRole])
-
-
-class ProgressTable(QtWidgets.QTableView):
+class ProgressTable(QtWidgets.QTableWidget):
row_selected = QtCore.pyqtSignal(int)
def __init__(self, *args, **kwargs):
super(ProgressTable, self).__init__(*args, **kwargs)
- pbar_delegate = ProgressDelegate(self)
- self.setItemDelegateForColumn(2, pbar_delegate)
- self.model = ProgressModel()
- self.setModel(self.model)
- self.setColumnWidth(0, 400)
- self.setColumnWidth(1, 80)
+ self.job_manager = None
# signals
- self.selectionModel().selectionChanged.connect(
+ self.itemSelectionChanged.connect(
self.on_selection_changed)
- def add_input_path(self, path):
- self.model.add_input_path(path)
+ # timer for updating table contents
+ self.monitor_timer = QtCore.QTimer(self)
+ self.monitor_timer.timeout.connect(
+ self.update_from_job_manager_progress)
+ self.monitor_timer.start(300)
@QtCore.pyqtSlot()
def on_selection_changed(self):
"""Emit a row-selected signal"""
- row = self.selectionModel().currentIndex().row()
+ row = self.currentIndex().row()
self.row_selected.emit(row)
- def update(self):
- self.model.dataChanged.emit()
+ def set_job_manager(self, job_manager):
+ if self.job_manager is None:
+ self.job_manager = job_manager
+ else:
+ raise ValueError("Job manager already set!")
+
+ def set_item_label(self, row, col, label, align=None):
+ """Get/Create a Qlabel at the specified position
+ """
+ label = f"{label}"
+ item = self.item(row, col)
+ if item is None:
+ item = QtWidgets.QTableWidgetItem(label)
+ self.setItem(row, col, item)
+ if align is not None:
+ item.setTextAlignment(align)
+ else:
+ if item.text() != label:
+ item.setText(label)
+
+ def set_item_progress(self, row, col, progress):
+ """Get/Create a QProgressBar at the specified position
+ """
+ pb = self.cellWidget(row, col)
+ if pb is None:
+ pb = QtWidgets.QProgressBar(self)
+ pb.setMaximum(1000)
+ self.setCellWidget(row, col, pb)
+ else:
+ if pb.value() != int(progress*1000):
+ pb.setValue(int(progress*1000))
+
+ @QtCore.pyqtSlot()
+ def update_from_job_manager(self):
+ if self.job_manager is None:
+ raise ValueError("Job manager not set!")
+ self.setRowCount(len(self.job_manager))
+ # Check rows and populate new items
+ for ii in range(len(self.job_manager)):
+ status = self.job_manager[ii]
+ self.set_item_label(ii, 0, str(status["path"]))
+ self.set_item_label(ii, 1, str(status["state"]),
+ align=QtCore.Qt.AlignmentFlag.AlignCenter)
+ self.set_item_progress(ii, 2, status["progress"])
+ # Set path column width to something large (does not work during init)
+ if self.columnWidth(0) == 100:
+ self.setColumnWidth(0, 500)
+
+ @QtCore.pyqtSlot()
+ def update_from_job_manager_progress(self):
+ for ii in range(len(self.job_manager)):
+ st = self.item(ii, 1)
+ st.setText(self.job_manager[ii]["state"])
+ pb = self.cellWidget(ii, 2)
+ progress = self.job_manager[ii]["progress"]
+ if pb is not None and pb.value() != int(progress*1000):
+ pb.setValue(int(progress*1000))
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 36f39d3..b72dbf4 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -37,7 +37,7 @@ def test_gui_basic(mw):
# Just check some known properties in the UI.
assert mw.spinBox_thresh.value() == -6
assert mw.checkBox_feat_bright.isChecked()
- assert len(mw.manager) == 0
+ assert len(mw.job_manager) == 0
@pytest.mark.parametrize("correct_offset", [True, False])
@@ -49,7 +49,7 @@ def test_gui_correct_offset(mw, correct_offset):
mw.checkBox_bg_flickering.setChecked(correct_offset)
mw.doubleSpinBox_pixel_size.setValue(0.666)
mw.on_run()
- while mw.manager.is_busy():
+ while mw.job_manager.is_busy():
time.sleep(.1)
out_path = path.with_name(path.stem + "_dcn.rtdc")
assert out_path.exists()
@@ -68,7 +68,7 @@ def test_gui_set_pixel_size(mw):
mw.checkBox_pixel_size.setChecked(True)
mw.doubleSpinBox_pixel_size.setValue(0.666)
mw.on_run()
- while mw.manager.is_busy():
+ while mw.job_manager.is_busy():
time.sleep(.1)
out_path = path.with_name(path.stem + "_dcn.rtdc")
assert out_path.exists()
@@ -88,7 +88,7 @@ def test_gui_use_volume(mw, use_volume):
mw.checkBox_feat_volume.setChecked(use_volume)
mw.doubleSpinBox_pixel_size.setValue(0.666)
mw.on_run()
- while mw.manager.is_busy():
+ while mw.job_manager.is_busy():
time.sleep(.1)
out_path = path.with_name(path.stem + "_dcn.rtdc")
assert out_path.exists()