Skip to content

Commit

Permalink
ref: migrate from QTableView to QTableWidget (issues with Windows 11)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmueller committed May 22, 2024
1 parent 1c1d6b7 commit edf9926
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 108 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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
Expand Down
32 changes: 18 additions & 14 deletions chipstream/gui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ..path_cache import PathCache
from .._version import version

from .manager import ChipStreamJobManager
from . import splash


Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -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()
Expand All @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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]
Expand All @@ -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):
Expand All @@ -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)

Expand All @@ -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:
Expand Down
41 changes: 34 additions & 7 deletions chipstream/gui/main_window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="ProgressTable" name="tableView_input">
<widget class="ProgressTable" name="tableWidget_input">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>3</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
<enum>QAbstractItemView::NoDragDrop</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
Expand All @@ -60,21 +63,45 @@
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>50</number>
<number>100</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>100</number>
</attribute>
<attribute name="verticalHeaderVisible">
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Path</string>
</property>
</column>
<column>
<property name="text">
<string>State</string>
</property>
</column>
<column>
<property name="text">
<string>Progress</string>
</property>
</column>
</widget>
<widget class="QTextBrowser" name="textBrowser">
<property name="sizePolicy">
Expand Down Expand Up @@ -428,7 +455,7 @@
<customwidgets>
<customwidget>
<class>ProgressTable</class>
<extends>QTableView</extends>
<extends>QTableWidget</extends>
<header>chipstream.gui.table_progress</header>
</customwidget>
</customwidgets>
Expand Down
149 changes: 66 additions & 83 deletions chipstream/gui/table_progress.py
Original file line number Diff line number Diff line change
@@ -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))
Loading

0 comments on commit edf9926

Please sign in to comment.