From 8d7bf052637f390a2bb0cad389917ea68c3bb322 Mon Sep 17 00:00:00 2001 From: MAKOMO Date: Tue, 24 Sep 2024 14:08:53 +0200 Subject: [PATCH] - limit visible items of device type popups and statistic types - pip upgrade of requirements-dev.txt in GitHub Actions - lib updates --- .github/workflows/mypy.yml | 2 +- .github/workflows/pylint.yaml | 2 +- .github/workflows/pytest.yaml | 2 +- src/artisanlib/devices.py | 46 ++++++++++++++++++++--------------- src/artisanlib/main.py | 14 +++++++++-- src/artisanlib/statistics.py | 13 +++++----- src/artisanlib/widgets.py | 9 +++++++ src/pyproject.toml | 1 + src/requirements-dev.txt | 4 +-- src/requirements.txt | 6 ++--- 10 files changed, 62 insertions(+), 37 deletions(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 3ccaf9507..e161574eb 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -39,7 +39,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip==24.0 # downgrade pip as pip v24.1 fails with pip._vendor.packaging.version.InvalidVersion: Invalid version: '6.5.0-1022-azure' (image name!) - pip install -r src/requirements-dev.txt + pip install --upgrade -r src/requirements-dev.txt pip install -r src/requirements.txt - uses: tsuyoshicho/action-mypy@v4 with: diff --git a/.github/workflows/pylint.yaml b/.github/workflows/pylint.yaml index f9b592c96..ac76e8d0d 100644 --- a/.github/workflows/pylint.yaml +++ b/.github/workflows/pylint.yaml @@ -41,7 +41,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip==24.0 # downgrade pip as pip v24.1 fails with pip._vendor.packaging.version.InvalidVersion: Invalid version: '6.5.0-1022-azure' (image name!) - pip install -r src/requirements-dev.txt + pip install --upgrade -r src/requirements-dev.txt pip install -r src/requirements.txt - name: Analysing the code with pylint env: diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 877ad2592..cf01ee21d 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -59,7 +59,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r src/requirements-dev.txt + pip install --upgrade -r src/requirements-dev.txt pip install -r src/requirements.txt - name: Test with pytest run: | diff --git a/src/artisanlib/devices.py b/src/artisanlib/devices.py index 7d203c783..1018a84e7 100644 --- a/src/artisanlib/devices.py +++ b/src/artisanlib/devices.py @@ -28,7 +28,7 @@ from artisanlib.util import deltaLabelUTF8, setDeviceDebugLogLevel, argb_colorname2rgba_colorname, rgba_colorname2argb_colorname from artisanlib.dialogs import ArtisanResizeablDialog -from artisanlib.widgets import MyQComboBox, MyQDoubleSpinBox +from artisanlib.widgets import MyContentLimitedQComboBox, MyQComboBox, MyQDoubleSpinBox _log: Final[logging.Logger] = logging.getLogger(__name__) @@ -39,14 +39,14 @@ from PyQt6.QtWidgets import (QApplication, QWidget, QCheckBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, # @UnusedImport @Reimport @UnresolvedImport QPushButton, QSpinBox, QTabWidget, QComboBox, QDialogButtonBox, QGridLayout, # @UnusedImport @Reimport @UnresolvedImport QGroupBox, QRadioButton, # @UnusedImport @Reimport @UnresolvedImport - QTableWidget, QMessageBox, QHeaderView, QTableWidgetItem) # @UnusedImport @Reimport @UnresolvedImport + QTableWidget, QMessageBox, QHeaderView, QTableWidgetItem, QSizePolicy) # @UnusedImport @Reimport @UnresolvedImport except ImportError: from PyQt5.QtCore import (Qt, pyqtSlot, QSettings, QTimer, QRegularExpression) # type: ignore # @UnusedImport @Reimport @UnresolvedImport from PyQt5.QtGui import (QStandardItemModel, QStandardItem, QColor, QIntValidator, QRegularExpressionValidator) # type: ignore # @UnusedImport @Reimport @UnresolvedImport from PyQt5.QtWidgets import (QApplication, QWidget, QCheckBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, # type: ignore # @UnusedImport @Reimport @UnresolvedImport QPushButton, QSpinBox, QTabWidget, QComboBox, QDialogButtonBox, QGridLayout, # @UnusedImport @Reimport @UnresolvedImport QGroupBox, QRadioButton, # @UnusedImport @Reimport @UnresolvedImport - QTableWidget, QMessageBox, QHeaderView, QTableWidgetItem) # @UnusedImport @Reimport @UnresolvedImport + QTableWidget, QMessageBox, QHeaderView, QTableWidgetItem, QSizePolicy) # @UnusedImport @Reimport @UnresolvedImport class DeviceAssignmentDlg(ArtisanResizeablDialog): @@ -122,7 +122,7 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) -> dev.pop(i) #note: pop() makes the list smaller that's why there are 2 FOR statements break self.sorted_devices = sorted(dev) - self.devicetypeComboBox = MyQComboBox() + self.devicetypeComboBox = MyContentLimitedQComboBox() ## self.devicetypeComboBox.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents) ## self.devicetypeComboBox.view().setTextElideMode(Qt.TextElideMode.ElideNone) @@ -1002,6 +1002,9 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) -> self.yoctoBoxRemoteFlag.stateChanged.connect(self.yoctoBoxRemoteFlagStateChanged) yoctoServerIdLabel = QLabel(QApplication.translate('Label','VirtualHub')) self.yoctoServerId = QLineEdit(self.aw.qmc.yoctoServerID) + self.yoctoServerId.setAlignment(Qt.AlignmentFlag.AlignRight) + self.yoctoServerId.setMinimumWidth(100) + self.yoctoServerId.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) self.yoctoServerId.setEnabled(self.aw.qmc.yoctoRemoteFlag) YoctoEmissivityLabel = QLabel(QApplication.translate('Label','Emissivity')) self.yoctoEmissivitySpinBox = MyQDoubleSpinBox() @@ -1013,6 +1016,7 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', activeTab:int = 0) -> yoctoServerBox.addWidget(yoctoServerIdLabel) yoctoServerBox.addSpacing(10) yoctoServerBox.addWidget(self.yoctoServerId) + yoctoServerBox.addStretch() yoctoServerBox.setContentsMargins(0,0,0,0) yoctoServerBox.setSpacing(10) yoctoNetworkGrid = QGridLayout() @@ -1611,6 +1615,7 @@ def createDeviceTable(self) -> None: if vheader is not None: vheader.setSectionResizeMode(QHeaderView.ResizeMode.Fixed) + fixed_size_sections = [7,8,9,10,11,12,13,14] if nddevices: dev = self.aw.qmc.devices[:] #deep copy limit = len(dev) @@ -1623,7 +1628,7 @@ def createDeviceTable(self) -> None: for i in range(nddevices): try: # 0: device type - typeComboBox = MyQComboBox() + typeComboBox = MyContentLimitedQComboBox() # typeComboBox.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) # default typeComboBox.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) # typeComboBox.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents) @@ -1742,7 +1747,6 @@ def createDeviceTable(self) -> None: except Exception as e: # pylint: disable=broad-except _log.exception(e) - fixed_size_sections = [7,8,9,10,11,12,13,14] header = self.devicetable.horizontalHeader() if header is not None: header.setStretchLastSection(False) @@ -1750,20 +1754,22 @@ def createDeviceTable(self) -> None: for i in fixed_size_sections: header.setSectionResizeMode(i, QHeaderView.ResizeMode.Fixed) header.resizeSection(i, header.sectionSize(i) + 5) - if not self.aw.qmc.devicetablecolumnwidths: - self.devicetable.setColumnWidth(0, 100) - self.devicetable.setColumnWidth(3, 100) - self.devicetable.setColumnWidth(4, 100) - self.devicetable.setColumnWidth(5, 40) - self.devicetable.setColumnWidth(6, 40) - else: - # remember the columnwidth - for i, _ in enumerate(self.aw.qmc.devicetablecolumnwidths): - if i not in fixed_size_sections: - try: - self.devicetable.setColumnWidth(i, self.aw.qmc.devicetablecolumnwidths[i]) - except Exception: # pylint: disable=broad-except - pass + if not self.aw.qmc.devicetablecolumnwidths: + self.devicetable.setColumnWidth(0, 230) + self.devicetable.setColumnWidth(1, 80) + self.devicetable.setColumnWidth(2, 80) + self.devicetable.setColumnWidth(3, 80) + self.devicetable.setColumnWidth(4, 80) + self.devicetable.setColumnWidth(5, 40) + self.devicetable.setColumnWidth(6, 40) + else: + # remember the columnwidth + for i, _ in enumerate(self.aw.qmc.devicetablecolumnwidths): + if i not in fixed_size_sections: + try: + self.devicetable.setColumnWidth(i, self.aw.qmc.devicetablecolumnwidths[i]) + except Exception: # pylint: disable=broad-except + pass except Exception as e: # pylint: disable=broad-except _t, _e, exc_tb = sys.exc_info() self.aw.qmc.adderror((QApplication.translate('Error Message', 'Exception:') + ' createDeviceTable(): {0}').format(str(e)),getattr(exc_tb, 'tb_lineno', '?')) diff --git a/src/artisanlib/main.py b/src/artisanlib/main.py index 88463a429..ccea2f0a8 100644 --- a/src/artisanlib/main.py +++ b/src/artisanlib/main.py @@ -117,7 +117,7 @@ QInputDialog, QGroupBox, QLineEdit, # @Reimport @UnresolvedImport @UnusedImport QSizePolicy, QVBoxLayout, QHBoxLayout, QPushButton, # @Reimport @UnresolvedImport @UnusedImport QLCDNumber, QSpinBox, QComboBox, # @Reimport @UnresolvedImport @UnusedImport - QSlider, # @Reimport @UnresolvedImport @UnusedImport + QSlider, QProxyStyle, QStyle, QStyleOption, QStyleHintReturn, # @Reimport @UnresolvedImport @UnusedImport QColorDialog, QFrame, QScrollArea, QProgressDialog, # @Reimport @UnresolvedImport @UnusedImport QStyleFactory, QMenu, QLayout) # @Reimport @UnresolvedImport @UnusedImport from PyQt6.QtGui import (QScreen, QPageLayout, QAction, QImageReader, QWindow, # @Reimport @UnresolvedImport @UnusedImport @@ -144,7 +144,7 @@ QInputDialog, QGroupBox, QLineEdit, # @Reimport @UnresolvedImport @UnusedImport QSizePolicy, QVBoxLayout, QHBoxLayout, QPushButton, # @Reimport @UnresolvedImport @UnusedImport QLCDNumber, QSpinBox, QComboBox, # @Reimport @UnresolvedImport @UnusedImport - QSlider, # @Reimport @UnresolvedImport @UnusedImport + QSlider, QProxyStyle, QStyle, QStyleOption, QStyleHintReturn, # @Reimport @UnresolvedImport @UnusedImport QColorDialog, QFrame, QScrollArea, QProgressDialog, # @Reimport @UnresolvedImport @UnusedImport QStyleFactory, QMenu, QLayout, QShortcut) # @Reimport @UnresolvedImport @UnusedImport from PyQt5.QtGui import (QScreen, QPageLayout, QImageReader, QWindow, # type: ignore # @Reimport @UnresolvedImport @UnusedImport @@ -591,6 +591,12 @@ def permissionUpdated(permission:'QPermission') -> None: # pass +class MenuProxyStyle(QProxyStyle): # pyright: ignore [reportGeneralTypeIssues] # Argument to class must be a base class + + def styleHint(self, hint:QStyle.StyleHint, option:Optional[QStyleOption] = None, widget:Optional[QWidget] = None, returnData:Optional[QStyleHintReturn] = None) -> int: + if hint == QStyle.StyleHint.SH_ComboBox_Popup and isinstance(widget, MyContentLimitedQComboBox): + return 0 + return QProxyStyle.styleHint(self, hint, option, widget, returnData) app_args = sys.argv @@ -606,6 +612,9 @@ def permissionUpdated(permission:'QPermission') -> None: # except Exception as e: # pylint: disable=broad-except # pass app = Artisan(app_args) +# to limit the number of items displayed in a popup at once we fall back to non-native Qt QComboboxes in MyContentLimitedQComboBox +# by resetting the style hint via a MenuProxy style object +app.setStyle(MenuProxyStyle()) # On the first run if there are legacy settings under "YourQuest" but no new settings under "artisan-scope" then the legacy settings @@ -736,6 +745,7 @@ def permissionUpdated(permission:'QPermission') -> None: from artisanlib.notifications import Notification, NotificationManager, NotificationType from artisanlib.canvas import tgraphcanvas from artisanlib.phases_canvas import tphasescanvas +from artisanlib.widgets import MyContentLimitedQComboBox # import artisan.plus module diff --git a/src/artisanlib/statistics.py b/src/artisanlib/statistics.py index 109aac928..045b61318 100644 --- a/src/artisanlib/statistics.py +++ b/src/artisanlib/statistics.py @@ -20,8 +20,7 @@ from typing import Optional, List, Any, cast, TYPE_CHECKING, Final from artisanlib.dialogs import ArtisanResizeablDialog from artisanlib.util import deltaLabelUTF8 -from artisanlib.widgets import MyQComboBox - +from artisanlib.widgets import MyContentLimitedQComboBox import logging try: @@ -609,7 +608,7 @@ def createSummarystatsTable(self) -> None: for i in range(nstats): #0 Type - typeComboBox = MyQComboBox() + typeComboBox = MyContentLimitedQComboBox() # set the combox width to the full width of the table typeComboBox.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) typeComboBox.setToolTip(QApplication.translate('Tooltip','Choose a statistic to display')) @@ -663,7 +662,7 @@ def copyEventButtonTabletoClipboard(self, _:bool=False) -> None: rows = [] rows.append(str(r+1)) # type - typeComboBox = cast(MyQComboBox, self.summarystatstable.cellWidget(r,1)) + typeComboBox = cast(MyContentLimitedQComboBox, self.summarystatstable.cellWidget(r,1)) rows.append(typeComboBox.currentText()) tbl.add_row(rows) clipboard = tbl.get_string() @@ -678,7 +677,7 @@ def copyEventButtonTabletoClipboard(self, _:bool=False) -> None: clipboard += '\n' for r in range(nrows): clipboard += str(r+1) + '\t' - typeComboBox = cast(MyQComboBox, self.summarystatstable.cellWidget(r,1)) + typeComboBox = cast(MyContentLimitedQComboBox, self.summarystatstable.cellWidget(r,1)) clipboard += typeComboBox.currentText() + '\t' # copy to the system clipboard sys_clip = QApplication.clipboard() @@ -710,7 +709,7 @@ def savetablesummarystats(self, forceRedraw:bool = False) -> None: def setitemsummarystat(self, _:int) -> None: i = self.aw.findWidgetsRow(self.summarystatstable,self.sender(),1) if i is not None: - typecombobox = cast(MyQComboBox, self.summarystatstable.cellWidget(i,1)) + typecombobox = cast(MyContentLimitedQComboBox, self.summarystatstable.cellWidget(i,1)) if i < len(self.summarystatstypes): self.summarystatstypes[i] = self.summarystats_types.index(self.summarystats_types_sorted[typecombobox.currentIndex()]) self.savetablesummarystats() @@ -718,7 +717,7 @@ def setitemsummarystat(self, _:int) -> None: def disconnectTableItemActions(self) -> None: for x in range(self.summarystatstable.rowCount()): try: - typeComboBox = cast(MyQComboBox, self.summarystatstable.cellWidget(x,1)) + typeComboBox = cast(MyContentLimitedQComboBox, self.summarystatstable.cellWidget(x,1)) typeComboBox.currentIndexChanged.disconnect() # type combo except Exception: # pylint: disable=broad-except pass diff --git a/src/artisanlib/widgets.py b/src/artisanlib/widgets.py index 1a094e2e0..157f29e7b 100644 --- a/src/artisanlib/widgets.py +++ b/src/artisanlib/widgets.py @@ -48,6 +48,15 @@ def wheelEvent(self, event:'Optional[QWheelEvent]') -> None: if self.hasFocus(): super().wheelEvent(event) +class MyContentLimitedQComboBox(MyQComboBox): # pylint: disable=too-few-public-methods # pyright: ignore [reportGeneralTypeIssues]# Argument to class must be a base class + def __init__(self, parent:Optional['QWidget'] = None, **kwargs:Dict[Any,Any]) -> None: + super().__init__(parent, **kwargs) + # setting max number visible limit + self.setMaxVisibleItems(20) + view = self.view() + if view is not None: + view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + class MyQDoubleSpinBox(QDoubleSpinBox): # pyright: ignore [reportGeneralTypeIssues] # Argument to class must be a base class def __init__(self, parent:Optional['QWidget'] = None, **kwargs:Dict[str,Any]) -> None: super().__init__(parent, **kwargs) diff --git a/src/pyproject.toml b/src/pyproject.toml index 2e3586abb..ccdf76013 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -135,6 +135,7 @@ disable=''' too-many-locals, too-many-boolean-expressions, too-many-nested-blocks, + too-many-positional-arguments, cyclic-import, duplicate-code, ''' diff --git a/src/requirements-dev.txt b/src/requirements-dev.txt index 6dc5a3be3..d224a5634 100644 --- a/src/requirements-dev.txt +++ b/src/requirements-dev.txt @@ -12,8 +12,8 @@ types-urllib3>=1.26.25.14 lxml-stubs>=0.5.1 mypy==1.11.2 pyright==1.1.381 -ruff>=0.6.4 -pylint==3.2.7 +ruff>=0.6.7 +pylint==3.3.0 pre-commit>=3.8.0 pytest>=8.3.3 pytest-cov==5.0.0 diff --git a/src/requirements.txt b/src/requirements.txt index f60846a36..4d825d53c 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -42,11 +42,11 @@ pyusb==1.2.1 persist-queue==1.0.0 portalocker==2.10.1 xlrd==2.0.1 -websockets==13.0.1 +websockets==13.1 PyYAML==6.0.2 psutil==6.0.0 typing-extensions==4.10.0; python_version < '3.8' # required for supporting Final and TypeDict on Python <3.8 -protobuf==5.28.1 +protobuf==5.28.2 numpy==1.24.3; python_version < '3.9' # last Python 3.8 release numpy==2.1.1; python_version >= '3.9' scipy==1.10.1; python_version < '3.9' # last Python 3.8 release @@ -54,7 +54,7 @@ scipy==1.14.1; python_version >= '3.9' wquantiles==0.6 colorspacious==1.1.2 openpyxl==3.1.5 -keyring==25.4.0 +keyring==25.4.1 prettytable==3.11.0 lxml==5.3.0 matplotlib==3.7.3; python_version < '3.9' # last Python 3.8 release