Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/magicgui/backends/_qtpy/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1512,8 +1512,35 @@ def _mgui_remove_row(self, row: int) -> None:
def _mgui_remove_column(self, column: int) -> None:
self._qwidget.removeColumn(column)

def _normalize_cell_index(self, row: int, col: int) -> tuple[int, int]:
num_rows = self._mgui_get_row_count()
num_cols = self._mgui_get_column_count()
# Offset negative indices because Qt doesn't natively support them
if row < 0:
normalized_row = row + num_rows
else:
normalized_row = row
if col < 0:
normalized_col = col + num_cols
else:
normalized_col = col
# Qt will not raise an error for attempting to index into an invalid cell, but
# normal Python does.
if (
normalized_row < 0
or normalized_col < 0
or normalized_row >= num_rows
or normalized_col >= num_cols
):
raise IndexError(
f"Index ({row}, {col}) is out of bounds for table of shape"
f" ({num_rows}, {num_cols})."
)
return normalized_row, normalized_col

def _mgui_get_cell(self, row: int, col: int) -> Any:
"""Get current value of the widget."""
row, col = self._normalize_cell_index(row, col)
item = self._qwidget.item(row, col)
if item:
return item.data(_DATA_ROLE)
Expand All @@ -1523,6 +1550,7 @@ def _mgui_get_cell(self, row: int, col: int) -> Any:

def _mgui_set_cell(self, row: int, col: int, value: Any) -> None:
"""Set current value of the widget."""
row, col = self._normalize_cell_index(row, col)
if value is None:
self._qwidget.setItem(row, col, None)
self._qwidget.removeCellWidget(row, col)
Expand Down
31 changes: 30 additions & 1 deletion tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ def test_table_from_numpy():
INDICES = (
1,
(2, 2),
-2,
(-1, -1),
(slice(None), 2),
(slice(None), slice(None)),
(slice(1, 3), slice(3)),
Expand All @@ -181,6 +183,8 @@ def test_table_from_numpy():
VALUES = (
(7,) * 4,
6,
(14,) * 4,
25,
(7,) * 6,
[[1] * 4] * 6,
[[1] * 3] * 2,
Expand All @@ -200,7 +204,7 @@ def test_dataview_getitem(index):
assert np.allclose(table.data[index], data[index])


@pytest.mark.parametrize("index, value", zip(INDICES, VALUES))
@pytest.mark.parametrize("index, value", tuple(zip(INDICES, VALUES)))
def test_dataview_setitem(index, value):
"""Test that table.data can be indexed like a numpy array."""
np = pytest.importorskip("numpy")
Expand All @@ -213,6 +217,31 @@ def test_dataview_setitem(index, value):
assert np.allclose(table.data.to_list(), data)


INVALID_INDICES = ((6, 0), (0, 4), (-7, 0), (0, -5))


@pytest.mark.parametrize("index", INVALID_INDICES)
def test_dataview_getitem_invalid_index(index):
"""Test that invalid cell indices raise IndexError."""
np = pytest.importorskip("numpy")
data = np.arange(24).reshape(6, 4)

table = Table(value=data)
with pytest.raises(IndexError):
table.data[index]


@pytest.mark.parametrize("index", INVALID_INDICES)
def test_dataview_setitem_invalid_index(index):
"""Test that invalid cell indices raise IndexError."""
np = pytest.importorskip("numpy")
data = np.arange(24).reshape(6, 4)

table = Table(value=data)
with pytest.raises(IndexError):
table.data[index] = 999


def test_dataview_delitem():
"""Test that table.data can be indexed like a numpy array."""
_input = _TABLE_DATA["dict"]
Expand Down
Loading