mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Some refactoring, and add cell_key to DataTable.CellSelected
This commit is contained in:
@@ -239,10 +239,10 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
show_cursor = Reactive(True)
|
show_cursor = Reactive(True)
|
||||||
cursor_type = Reactive(CELL)
|
cursor_type = Reactive(CELL)
|
||||||
|
|
||||||
cursor_cell: Reactive[Coordinate] = Reactive(
|
cursor_coordinate: Reactive[Coordinate] = Reactive(
|
||||||
Coordinate(0, 0), repaint=False, always_update=True
|
Coordinate(0, 0), repaint=False, always_update=True
|
||||||
)
|
)
|
||||||
hover_cell: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False)
|
hover_coordinate: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False)
|
||||||
|
|
||||||
class CellHighlighted(Message, bubble=True):
|
class CellHighlighted(Message, bubble=True):
|
||||||
"""Emitted when the cursor moves to highlight a new cell.
|
"""Emitted when the cursor moves to highlight a new cell.
|
||||||
@@ -274,6 +274,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
yield "sender", self.sender
|
yield "sender", self.sender
|
||||||
yield "value", self.value
|
yield "value", self.value
|
||||||
yield "coordinate", self.coordinate
|
yield "coordinate", self.coordinate
|
||||||
|
yield "cell_key", self.cell_key
|
||||||
|
|
||||||
class CellSelected(Message, bubble=True):
|
class CellSelected(Message, bubble=True):
|
||||||
"""Emitted by the `DataTable` widget when a cell is selected.
|
"""Emitted by the `DataTable` widget when a cell is selected.
|
||||||
@@ -284,19 +285,26 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
Attributes:
|
Attributes:
|
||||||
value: The value in the cell that was selected.
|
value: The value in the cell that was selected.
|
||||||
coordinate: The coordinate of the cell that was selected.
|
coordinate: The coordinate of the cell that was selected.
|
||||||
|
cell_key: The key for the selected cell.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, sender: DataTable, value: CellType, coordinate: Coordinate
|
self,
|
||||||
|
sender: DataTable,
|
||||||
|
value: CellType,
|
||||||
|
coordinate: Coordinate,
|
||||||
|
cell_key: CellKey,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.value: CellType = value
|
self.value: CellType = value
|
||||||
self.coordinate: Coordinate = coordinate
|
self.coordinate: Coordinate = coordinate
|
||||||
|
self.cell_key: CellKey = cell_key
|
||||||
super().__init__(sender)
|
super().__init__(sender)
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield "sender", self.sender
|
yield "sender", self.sender
|
||||||
yield "value", self.value
|
yield "value", self.value
|
||||||
yield "coordinate", self.coordinate
|
yield "coordinate", self.coordinate
|
||||||
|
yield "cell_key", self.cell_key
|
||||||
|
|
||||||
class RowHighlighted(Message, bubble=True):
|
class RowHighlighted(Message, bubble=True):
|
||||||
"""Emitted when a row is highlighted. This message is only emitted when the
|
"""Emitted when a row is highlighted. This message is only emitted when the
|
||||||
@@ -420,19 +428,19 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def hover_row(self) -> int:
|
def hover_row(self) -> int:
|
||||||
return self.hover_cell.row
|
return self.hover_coordinate.row
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hover_column(self) -> int:
|
def hover_column(self) -> int:
|
||||||
return self.hover_cell.column
|
return self.hover_coordinate.column
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cursor_row(self) -> int:
|
def cursor_row(self) -> int:
|
||||||
return self.cursor_cell.row
|
return self.cursor_coordinate.row
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cursor_column(self) -> int:
|
def cursor_column(self) -> int:
|
||||||
return self.cursor_cell.column
|
return self.cursor_coordinate.column
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def row_count(self) -> int:
|
def row_count(self) -> int:
|
||||||
@@ -492,8 +500,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
row = self.data.get(row_key)
|
row = self.data.get(row_key)
|
||||||
yield row.get(column_key)
|
yield row.get(column_key)
|
||||||
|
|
||||||
def get_cell_value(self, coordinate: Coordinate) -> CellType:
|
def get_value_at(self, coordinate: Coordinate) -> CellType:
|
||||||
"""Get the value from the cell at the given coordinate.
|
"""Get the value from the cell occupying the given coordinate.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coordinate: The coordinate to retrieve the value from.
|
coordinate: The coordinate to retrieve the value from.
|
||||||
@@ -504,11 +512,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
Raises:
|
Raises:
|
||||||
CellDoesNotExist: If there is no cell with the given coordinate.
|
CellDoesNotExist: If there is no cell with the given coordinate.
|
||||||
"""
|
"""
|
||||||
# TODO: Rename to get_value_at()?
|
row_key, column_key = self.coordinate_to_cell_key(coordinate)
|
||||||
# We need to clearly distinguish between coordinates and cell keys
|
|
||||||
row_index, column_index = coordinate
|
|
||||||
row_key = self._row_locations.get_key(row_index)
|
|
||||||
column_key = self._column_locations.get_key(column_index)
|
|
||||||
try:
|
try:
|
||||||
cell_value = self.data[row_key][column_key]
|
cell_value = self.data[row_key][column_key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -537,7 +541,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
# emit the appropriate [Row|Column|Cell]Highlighted event.
|
# emit the appropriate [Row|Column|Cell]Highlighted event.
|
||||||
self._scroll_cursor_into_view(animate=False)
|
self._scroll_cursor_into_view(animate=False)
|
||||||
if self.cursor_type == "cell":
|
if self.cursor_type == "cell":
|
||||||
self._highlight_cell(self.cursor_cell)
|
self._highlight_cell(self.cursor_coordinate)
|
||||||
elif self.cursor_type == "row":
|
elif self.cursor_type == "row":
|
||||||
self._highlight_row(self.cursor_row)
|
self._highlight_row(self.cursor_row)
|
||||||
elif self.cursor_type == "column":
|
elif self.cursor_type == "column":
|
||||||
@@ -576,7 +580,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
"""Apply highlighting to the cell at the coordinate, and emit event."""
|
"""Apply highlighting to the cell at the coordinate, and emit event."""
|
||||||
self.refresh_cell(*coordinate)
|
self.refresh_cell(*coordinate)
|
||||||
try:
|
try:
|
||||||
cell_value = self.get_cell_value(coordinate)
|
cell_value = self.get_value_at(coordinate)
|
||||||
except CellDoesNotExist:
|
except CellDoesNotExist:
|
||||||
# The cell may not exist e.g. when the table is cleared.
|
# The cell may not exist e.g. when the table is cleared.
|
||||||
# In that case, there's nothing for us to do here.
|
# In that case, there's nothing for us to do here.
|
||||||
@@ -632,7 +636,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
|
|
||||||
# Refresh cells that were previously impacted by the cursor
|
# Refresh cells that were previously impacted by the cursor
|
||||||
# but may no longer be.
|
# but may no longer be.
|
||||||
row_index, column_index = self.cursor_cell
|
row_index, column_index = self.cursor_coordinate
|
||||||
if old == "cell":
|
if old == "cell":
|
||||||
self.refresh_cell(row_index, column_index)
|
self.refresh_cell(row_index, column_index)
|
||||||
elif old == "row":
|
elif old == "row":
|
||||||
@@ -643,11 +647,11 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._scroll_cursor_into_view()
|
self._scroll_cursor_into_view()
|
||||||
|
|
||||||
def _highlight_cursor(self) -> None:
|
def _highlight_cursor(self) -> None:
|
||||||
row_index, column_index = self.cursor_cell
|
row_index, column_index = self.cursor_coordinate
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
# Apply the highlighting to the newly relevant cells
|
# Apply the highlighting to the newly relevant cells
|
||||||
if cursor_type == "cell":
|
if cursor_type == "cell":
|
||||||
self._highlight_cell(self.cursor_cell)
|
self._highlight_cell(self.cursor_coordinate)
|
||||||
elif cursor_type == "row":
|
elif cursor_type == "row":
|
||||||
self._highlight_row(row_index)
|
self._highlight_row(row_index)
|
||||||
elif cursor_type == "column":
|
elif cursor_type == "column":
|
||||||
@@ -756,8 +760,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self.columns.clear()
|
self.columns.clear()
|
||||||
self._line_no = 0
|
self._line_no = 0
|
||||||
self._require_update_dimensions = True
|
self._require_update_dimensions = True
|
||||||
self.cursor_cell = Coordinate(0, 0)
|
self.cursor_coordinate = Coordinate(0, 0)
|
||||||
self.hover_cell = Coordinate(0, 0)
|
self.hover_coordinate = Coordinate(0, 0)
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def add_column(
|
def add_column(
|
||||||
@@ -834,7 +838,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self.rows[row_key] = Row(row_key, height)
|
self.rows[row_key] = Row(row_key, height)
|
||||||
self._new_rows.add(row_key)
|
self._new_rows.add(row_key)
|
||||||
self._require_update_dimensions = True
|
self._require_update_dimensions = True
|
||||||
self.cursor_cell = self.cursor_cell
|
self.cursor_coordinate = self.cursor_coordinate
|
||||||
|
|
||||||
# If a position has opened for the cursor to appear, where it previously
|
# If a position has opened for the cursor to appear, where it previously
|
||||||
# could not (e.g. when there's no data in the table), then a highlighted
|
# could not (e.g. when there's no data in the table), then a highlighted
|
||||||
@@ -1212,8 +1216,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
x1,
|
x1,
|
||||||
x2,
|
x2,
|
||||||
width,
|
width,
|
||||||
self.cursor_cell,
|
self.cursor_coordinate,
|
||||||
self.hover_cell,
|
self.hover_coordinate,
|
||||||
base_style,
|
base_style,
|
||||||
self.cursor_type,
|
self.cursor_type,
|
||||||
self._show_hover_cursor,
|
self._show_hover_cursor,
|
||||||
@@ -1226,8 +1230,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
row_key,
|
row_key,
|
||||||
y_offset_in_row,
|
y_offset_in_row,
|
||||||
base_style,
|
base_style,
|
||||||
cursor_location=self.cursor_cell,
|
cursor_location=self.cursor_coordinate,
|
||||||
hover_location=self.hover_cell,
|
hover_location=self.hover_coordinate,
|
||||||
)
|
)
|
||||||
fixed_width = sum(
|
fixed_width = sum(
|
||||||
column.render_width
|
column.render_width
|
||||||
@@ -1268,7 +1272,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
meta = event.style.meta
|
meta = event.style.meta
|
||||||
if meta and self.show_cursor and self.cursor_type != "none":
|
if meta and self.show_cursor and self.cursor_type != "none":
|
||||||
try:
|
try:
|
||||||
self.hover_cell = Coordinate(meta["row"], meta["column"])
|
self.hover_coordinate = Coordinate(meta["row"], meta["column"])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1351,7 +1355,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
elif cursor_type == "row":
|
elif cursor_type == "row":
|
||||||
self.refresh_row(self.hover_row)
|
self.refresh_row(self.hover_row)
|
||||||
elif cursor_type == "cell":
|
elif cursor_type == "cell":
|
||||||
self.refresh_cell(*self.hover_cell)
|
self.refresh_cell(*self.hover_coordinate)
|
||||||
|
|
||||||
def on_click(self, event: events.Click) -> None:
|
def on_click(self, event: events.Click) -> None:
|
||||||
self._set_hover_cursor(True)
|
self._set_hover_cursor(True)
|
||||||
@@ -1360,7 +1364,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._emit_selected_message()
|
self._emit_selected_message()
|
||||||
meta = self.get_style_at(event.x, event.y).meta
|
meta = self.get_style_at(event.x, event.y).meta
|
||||||
if meta:
|
if meta:
|
||||||
self.cursor_cell = Coordinate(meta["row"], meta["column"])
|
self.cursor_coordinate = Coordinate(meta["row"], meta["column"])
|
||||||
self._scroll_cursor_into_view(animate=True)
|
self._scroll_cursor_into_view(animate=True)
|
||||||
event.stop()
|
event.stop()
|
||||||
|
|
||||||
@@ -1368,7 +1372,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._set_hover_cursor(False)
|
self._set_hover_cursor(False)
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
||||||
self.cursor_cell = self.cursor_cell.up()
|
self.cursor_coordinate = self.cursor_coordinate.up()
|
||||||
self._scroll_cursor_into_view()
|
self._scroll_cursor_into_view()
|
||||||
else:
|
else:
|
||||||
# If the cursor doesn't move up (e.g. column cursor can't go up),
|
# If the cursor doesn't move up (e.g. column cursor can't go up),
|
||||||
@@ -1379,7 +1383,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._set_hover_cursor(False)
|
self._set_hover_cursor(False)
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
if self.show_cursor and (cursor_type == "cell" or cursor_type == "row"):
|
||||||
self.cursor_cell = self.cursor_cell.down()
|
self.cursor_coordinate = self.cursor_coordinate.down()
|
||||||
self._scroll_cursor_into_view()
|
self._scroll_cursor_into_view()
|
||||||
else:
|
else:
|
||||||
super().action_scroll_down()
|
super().action_scroll_down()
|
||||||
@@ -1388,7 +1392,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._set_hover_cursor(False)
|
self._set_hover_cursor(False)
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "column"):
|
if self.show_cursor and (cursor_type == "cell" or cursor_type == "column"):
|
||||||
self.cursor_cell = self.cursor_cell.right()
|
self.cursor_coordinate = self.cursor_coordinate.right()
|
||||||
self._scroll_cursor_into_view(animate=True)
|
self._scroll_cursor_into_view(animate=True)
|
||||||
else:
|
else:
|
||||||
super().action_scroll_right()
|
super().action_scroll_right()
|
||||||
@@ -1397,7 +1401,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
self._set_hover_cursor(False)
|
self._set_hover_cursor(False)
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
if self.show_cursor and (cursor_type == "cell" or cursor_type == "column"):
|
if self.show_cursor and (cursor_type == "cell" or cursor_type == "column"):
|
||||||
self.cursor_cell = self.cursor_cell.left()
|
self.cursor_coordinate = self.cursor_coordinate.left()
|
||||||
self._scroll_cursor_into_view(animate=True)
|
self._scroll_cursor_into_view(animate=True)
|
||||||
else:
|
else:
|
||||||
super().action_scroll_left()
|
super().action_scroll_left()
|
||||||
@@ -1409,19 +1413,20 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
|
|
||||||
def _emit_selected_message(self):
|
def _emit_selected_message(self):
|
||||||
"""Emit the appropriate message for a selection based on the `cursor_type`."""
|
"""Emit the appropriate message for a selection based on the `cursor_type`."""
|
||||||
cursor_cell = self.cursor_cell
|
cursor_coordinate = self.cursor_coordinate
|
||||||
cursor_type = self.cursor_type
|
cursor_type = self.cursor_type
|
||||||
if cursor_type == "cell":
|
if cursor_type == "cell":
|
||||||
self.emit_no_wait(
|
self.emit_no_wait(
|
||||||
DataTable.CellSelected(
|
DataTable.CellSelected(
|
||||||
self,
|
self,
|
||||||
self.get_cell_value(cursor_cell),
|
self.get_value_at(cursor_coordinate),
|
||||||
cursor_cell,
|
coordinate=cursor_coordinate,
|
||||||
|
cell_key=self.coordinate_to_cell_key(cursor_coordinate),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif cursor_type == "row":
|
elif cursor_type == "row":
|
||||||
row, _ = cursor_cell
|
row, _ = cursor_coordinate
|
||||||
self.emit_no_wait(DataTable.RowSelected(self, row))
|
self.emit_no_wait(DataTable.RowSelected(self, row))
|
||||||
elif cursor_type == "column":
|
elif cursor_type == "column":
|
||||||
_, column = cursor_cell
|
_, column = cursor_coordinate
|
||||||
self.emit_no_wait(DataTable.ColumnSelected(self, column))
|
self.emit_no_wait(DataTable.ColumnSelected(self, column))
|
||||||
|
|||||||
@@ -193,19 +193,19 @@ async def test_clear():
|
|||||||
app = DataTableApp()
|
app = DataTableApp()
|
||||||
async with app.run_test():
|
async with app.run_test():
|
||||||
table = app.query_one(DataTable)
|
table = app.query_one(DataTable)
|
||||||
assert table.cursor_cell == Coordinate(0, 0)
|
assert table.cursor_coordinate == Coordinate(0, 0)
|
||||||
assert table.hover_cell == Coordinate(0, 0)
|
assert table.hover_coordinate == Coordinate(0, 0)
|
||||||
|
|
||||||
# Add some data and update cursor positions
|
# Add some data and update cursor positions
|
||||||
table.add_column("Column0")
|
table.add_column("Column0")
|
||||||
table.add_rows([["Row0"], ["Row1"], ["Row2"]])
|
table.add_rows([["Row0"], ["Row1"], ["Row2"]])
|
||||||
table.cursor_cell = Coordinate(1, 0)
|
table.cursor_coordinate = Coordinate(1, 0)
|
||||||
table.hover_cell = Coordinate(2, 0)
|
table.hover_coordinate = Coordinate(2, 0)
|
||||||
|
|
||||||
# Ensure the cursor positions are reset to origin on clear()
|
# Ensure the cursor positions are reset to origin on clear()
|
||||||
table.clear()
|
table.clear()
|
||||||
assert table.cursor_cell == Coordinate(0, 0)
|
assert table.cursor_coordinate == Coordinate(0, 0)
|
||||||
assert table.hover_cell == Coordinate(0, 0)
|
assert table.hover_coordinate == Coordinate(0, 0)
|
||||||
|
|
||||||
# Ensure that the table has been cleared
|
# Ensure that the table has been cleared
|
||||||
assert table.data == {}
|
assert table.data == {}
|
||||||
@@ -253,7 +253,7 @@ async def test_get_cell_value_returns_value_at_cell():
|
|||||||
table = app.query_one(DataTable)
|
table = app.query_one(DataTable)
|
||||||
table.add_columns("A", "B")
|
table.add_columns("A", "B")
|
||||||
table.add_rows(ROWS)
|
table.add_rows(ROWS)
|
||||||
assert table.get_cell_value(Coordinate(0, 0)) == Text("0/0")
|
assert table.get_value_at(Coordinate(0, 0)) == Text("0/0")
|
||||||
|
|
||||||
|
|
||||||
async def test_get_cell_value_exception():
|
async def test_get_cell_value_exception():
|
||||||
@@ -263,7 +263,7 @@ async def test_get_cell_value_exception():
|
|||||||
table.add_columns("A", "B")
|
table.add_columns("A", "B")
|
||||||
table.add_rows(ROWS)
|
table.add_rows(ROWS)
|
||||||
with pytest.raises(CellDoesNotExist):
|
with pytest.raises(CellDoesNotExist):
|
||||||
table.get_cell_value(Coordinate(9999, 0))
|
table.get_value_at(Coordinate(9999, 0))
|
||||||
|
|
||||||
|
|
||||||
def test_key_equals_equivalent_string():
|
def test_key_equals_equivalent_string():
|
||||||
|
|||||||
Reference in New Issue
Block a user