Skip to content

Commit

Permalink
Merge pull request #1 from int-brain-lab/develop
Browse files Browse the repository at this point in the history
0.2.0
  • Loading branch information
bimac authored Oct 8, 2024
2 parents 64a9589 + 0c3e161 commit ed30c38
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 25 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [0.2.0] - 2024-10-08

### Added

- widgets.CheckBoxDelegate: render checkboxes in a QTableView or similar widget
- start adding unit tests

### Changed

- core.DataFrameTableModel: stop use of QVariant
- core.DataFrameTableModel: set dataFrame _after_ connecting signals in initialization
- core.DataFrameTableModel: default to horizontal orientation

## [0.1.2] - 2024-10-01

### Changed
Expand Down Expand Up @@ -36,6 +49,7 @@ _First release._
- core.ColoredDataFrameTableModel: An extension of DataFrameTableModel providing color-mapped numerical data.
- widgets.StatefulButton: A QPushButton that maintains an active/inactive state.

[0.2.0]: https://github.com/int-brain-lab/iblqt/releases/tag/v0.2.0
[0.1.2]: https://github.com/int-brain-lab/iblqt/releases/tag/v0.1.2
[0.1.1]: https://github.com/int-brain-lab/iblqt/releases/tag/v0.1.1
[0.1.0]: https://github.com/int-brain-lab/iblqt/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion iblqt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""A collection of extensions to the Qt framework."""

__version__ = '0.1.2'
__version__ = '0.2.0'
49 changes: 29 additions & 20 deletions iblqt/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
QModelIndex,
QObject,
Property,
QVariant,
Signal,
Slot,
)
Expand Down Expand Up @@ -56,7 +55,7 @@ def __init__(
Keyword arguments passed to the parent class.
"""
super().__init__(parent, *args, **kwargs)
self._dataFrame: DataFrame = DataFrame() if dataFrame is None else dataFrame
self._dataFrame = DataFrame() if dataFrame is None else dataFrame.copy()

def getDataFrame(self) -> DataFrame:
"""
Expand Down Expand Up @@ -88,32 +87,38 @@ def setDataFrame(self, dataFrame: DataFrame):
def headerData(
self,
section: int,
orientation: Qt.Orientation,
orientation: Qt.Orientation = Qt.Orientation.Horizontal,
role: int = Qt.ItemDataRole.DisplayRole,
) -> QVariant:
) -> Any | None:
"""
Get the header data for the specified section.
Parameters
----------
section : int
The section index.
orientation : Qt.Orientation
The orientation of the header.
orientation : Qt.Orientation, optional
The orientation of the header. Defaults to Horizontal.
role : int, optional
The role of the header data.
The role of the header data. Only DisplayRole is supported at this time.
Returns
-------
QVariant
Any or None
The header data.
"""
if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.ToolTipRole):
if orientation == Qt.Orientation.Horizontal:
return QVariant(self._dataFrame.columns[section])
else:
return QVariant(str(self._dataFrame.index[section]))
return QVariant()
if role == Qt.ItemDataRole.DisplayRole:
if (
orientation == Qt.Orientation.Horizontal
and 0 <= section < self.columnCount()
):
return self._dataFrame.columns[section]
elif (
orientation == Qt.Orientation.Vertical
and 0 <= section < self.rowCount()
):
return self._dataFrame.index[section]
return None

def rowCount(self, parent: QModelIndex | None = None) -> int:
"""
Expand Down Expand Up @@ -151,7 +156,9 @@ def columnCount(self, parent: QModelIndex | None = None) -> int:
return 0
return self._dataFrame.columns.size

def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
def data(
self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole
) -> Any | None:
"""
Get the data for the specified index.
Expand All @@ -164,15 +171,15 @@ def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> A
Returns
-------
Any
Any or None
The data for the specified index.
"""
if index.isValid() and role == Qt.ItemDataRole.DisplayRole:
data = self._dataFrame.iloc[index.row(), index.column()]
if isinstance(data, np.generic):
return data.item()
return data
return QVariant()
return None

def setData(
self, index: QModelIndex, value: Any, role: int = Qt.ItemDataRole.DisplayRole
Expand Down Expand Up @@ -209,7 +216,7 @@ def sort(self, column: int, order: Qt.SortOrder = Qt.SortOrder.AscendingOrder):
column : int
The column index to sort by.
order : Qt.SortOrder, optional
The sort order.
The sort order. Defaults to Ascending order.
"""
if self.columnCount() == 0:
return
Expand All @@ -231,7 +238,7 @@ class ColoredDataFrameTableModel(DataFrameTableModel):
_normData = DataFrame()
_background: npt.NDArray[np.int_]
_foreground: npt.NDArray[np.int_]
_cmap: ColorMap
_cmap: ColorMap = colormap.get('plasma')
_alpha: int

def __init__(
Expand Down Expand Up @@ -260,12 +267,14 @@ def __init__(
Keyword arguments passed to the parent class.
"""
super().__init__(parent=parent, dataFrame=dataFrame)
super().__init__(parent=parent)
self.modelReset.connect(self._normalizeData)
self.dataChanged.connect(self._normalizeData)
self.colormapChanged.connect(self._defineColors)
self.setProperty('colormap', colormap)
self.setProperty('alpha', alpha)
if dataFrame is not None:
self.setDataFrame(dataFrame)

def getColormap(self) -> str:
"""
Expand Down
108 changes: 106 additions & 2 deletions iblqt/widgets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,111 @@
"""Graphical user interface components."""

from qtpy.QtWidgets import QPushButton
from qtpy.QtCore import Signal, Slot, Property
from typing import Any

from qtpy.QtWidgets import (
QPushButton,
QStyledItemDelegate,
QStyleOptionButton,
QCheckBox,
QStyle,
QApplication,
QStyleOptionViewItem,
)
from qtpy.QtCore import (
Signal,
Slot,
Property,
QRect,
QEvent,
QModelIndex,
QAbstractItemModel,
)
from qtpy.QtGui import QPainter, QMouseEvent


class CheckBoxDelegate(QStyledItemDelegate):
"""
A custom delegate for rendering checkboxes in a QTableView or similar widget.
This delegate allows for the display and interaction with boolean data as checkboxes.
"""

def paint(
self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
) -> None:
"""
Paints the checkbox in the view.
Parameters
----------
painter : QPainter
The painter used to draw the checkbox.
option : QStyleOptionButton
The style option containing the information needed for painting.
index : QModelIndex
The index of the item in the model.
"""
super().paint(painter, option, index)
control = QStyleOptionButton()
control.rect = QRect(option.rect.topLeft(), QCheckBox().sizeHint())
control.rect.moveCenter(option.rect.center())
control.state = QStyle.State_On if index.data() is True else QStyle.State_Off
QApplication.style().drawControl(
QStyle.ControlElement.CE_CheckBox, control, painter
)

def displayText(self, value: Any, locale: Any) -> str:
"""
Return an empty string to hide the text representation of the data.
Parameters
----------
value : Any
The value to be displayed (not used).
locale : Any
The locale to be used (not used).
Returns
-------
str
An empty string.
"""
return ''

def editorEvent(
self,
event: QEvent,
model: QAbstractItemModel,
option: QStyleOptionViewItem,
index: QModelIndex,
) -> bool:
"""
Handle user interaction with the checkbox.
Parameters
----------
event : QEvent
The event that occurred (e.g., mouse click).
model : QAbstractItemModel
The model associated with the view.
option : QStyleOptionViewItem
The style option containing the information needed for handling the event.
index : QModelIndex
The index of the item in the model.
Returns
-------
bool
True if the event was handled, False otherwise.
"""
if isinstance(event, QMouseEvent) and event.type() == QEvent.MouseButtonRelease:
checkbox_rect = QRect(option.rect.topLeft(), QCheckBox().sizeHint())
checkbox_rect.moveCenter(option.rect.center())
if checkbox_rect.contains(event.pos()):
model.setData(index, not model.data(index))
event.accept()
return True
return super().editorEvent(event, model, option, index)


class StatefulButton(QPushButton):
Expand Down
18 changes: 17 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ lint = [
test = [
"pytest>=8.3.2",
"pytest-cov>=5.0.0",
"pytest-qt>=4.4.0",
]
typing = [
"mypy>=1.11.2",
Expand Down Expand Up @@ -80,6 +81,7 @@ convention = "numpy"
files = ["iblqt/**/*.py", "tests/**/*.py"]

[tool.pytest.ini_options]
addopts = "--cov=tycmd --cov-report=html --cov-report=xml"
addopts = "--cov=iblqt --cov-report=html --cov-report=xml"
minversion = "6.0"
testpaths = [ "tests" ]
qt_api = 'pyqt5'
Loading

0 comments on commit ed30c38

Please sign in to comment.