diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b63dc91f..c2fc41a3c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,13 +17,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Widget scrolling methods (such as `Widget.scroll_home` and `Widget.scroll_end`) now perform the scroll after the next refresh https://github.com/Textualize/textual/issues/1774 - Buttons no longer accept arbitrary renderables https://github.com/Textualize/textual/issues/1870 +- `DataTable`-related events are now generics over the type of the `DataTable` that posts those events (see `Tree` for another example) https://github.com/Textualize/textual/issues/1813 -### Fixed +### Removed -- Scrolling with cursor keys now moves just one cell https://github.com/Textualize/textual/issues/1897 +- Removed the attribute `Button.Pressed.button` in favour of `Button.Pressed.sender` https://github.com/Textualize/textual/issues/1813 +- Removed the attributes `Input.Changed.input` and `Input.Submitted.input` in favour of the respective `Input.Changed.sender` and `Input.Submitted.sender` attributes https://github.com/Textualize/textual/issues/1813 +- Removed the attribute `RadioSet.Changed.input` in favour of `RadioSet.Changed.sender` https://github.com/Textualize/textual/issues/1813 +- Removed the attribute `Switch.Changed.input` in favour of `Switch.Changed.sender` https://github.com/Textualize/textual/issues/1813 +- Removed the attribute `ToggleButton.Changed.input` in favour of `ToggleButton.Changed.sender` https://github.com/Textualize/textual/issues/1813 ### Fixed +- Scrolling with cursor keys now moves just one cell https://github.com/Textualize/textual/issues/1897 - Fix exceptions in watch methods being hidden on startup https://github.com/Textualize/textual/issues/1886 ## [0.12.1] - 2023-02-25 diff --git a/docs/examples/app/question01.py b/docs/examples/app/question01.py index 04c4e86898..48a80ce4b2 100644 --- a/docs/examples/app/question01.py +++ b/docs/examples/app/question01.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.widgets import Label, Button +from textual.widgets import Button, Label class QuestionApp(App[str]): @@ -9,7 +9,7 @@ def compose(self) -> ComposeResult: yield Button("No", id="no", variant="error") def on_button_pressed(self, event: Button.Pressed) -> None: - self.exit(event.button.id) + self.exit(event.sender.id) if __name__ == "__main__": diff --git a/docs/examples/app/question02.py b/docs/examples/app/question02.py index 65eb79f655..a73a4c2465 100644 --- a/docs/examples/app/question02.py +++ b/docs/examples/app/question02.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.widgets import Label, Button +from textual.widgets import Button, Label class QuestionApp(App[str]): @@ -11,7 +11,7 @@ def compose(self) -> ComposeResult: yield Button("No", id="no", variant="error") def on_button_pressed(self, event: Button.Pressed) -> None: - self.exit(event.button.id) + self.exit(event.sender.id) if __name__ == "__main__": diff --git a/docs/examples/app/question03.py b/docs/examples/app/question03.py index 6fc372dedb..5cb70deed0 100644 --- a/docs/examples/app/question03.py +++ b/docs/examples/app/question03.py @@ -1,5 +1,5 @@ from textual.app import App, ComposeResult -from textual.widgets import Label, Button +from textual.widgets import Button, Label class QuestionApp(App[str]): @@ -29,7 +29,7 @@ def compose(self) -> ComposeResult: yield Button("No", id="no", variant="error") def on_button_pressed(self, event: Button.Pressed) -> None: - self.exit(event.button.id) + self.exit(event.sender.id) if __name__ == "__main__": diff --git a/docs/examples/app/question_title01.py b/docs/examples/app/question_title01.py index 55dc43599a..6d8b234800 100644 --- a/docs/examples/app/question_title01.py +++ b/docs/examples/app/question_title01.py @@ -14,7 +14,7 @@ def compose(self) -> ComposeResult: yield Button("No", id="no", variant="error") def on_button_pressed(self, event: Button.Pressed) -> None: - self.exit(event.button.id) + self.exit(event.sender.id) if __name__ == "__main__": diff --git a/docs/examples/app/question_title02.py b/docs/examples/app/question_title02.py index c279d7e200..11aff8dbd0 100644 --- a/docs/examples/app/question_title02.py +++ b/docs/examples/app/question_title02.py @@ -15,7 +15,7 @@ def compose(self) -> ComposeResult: yield Button("No", id="no", variant="error") def on_button_pressed(self, event: Button.Pressed) -> None: - self.exit(event.button.id) + self.exit(event.sender.id) def on_key(self, event: Key): self.title = event.key diff --git a/docs/examples/guide/reactivity/computed01.py b/docs/examples/guide/reactivity/computed01.py index dcef731ff4..4e6a50aba6 100644 --- a/docs/examples/guide/reactivity/computed01.py +++ b/docs/examples/guide/reactivity/computed01.py @@ -34,9 +34,9 @@ def on_input_changed(self, event: Input.Changed) -> None: except ValueError: self.bell() else: - if event.input.id == "red": + if event.sender.id == "red": self.red = component - elif event.input.id == "green": + elif event.sender.id == "green": self.green = component else: self.blue = component diff --git a/docs/examples/guide/reactivity/validate01.py b/docs/examples/guide/reactivity/validate01.py index 348a9f1d8d..f1bf36b0f8 100644 --- a/docs/examples/guide/reactivity/validate01.py +++ b/docs/examples/guide/reactivity/validate01.py @@ -26,7 +26,7 @@ def compose(self) -> ComposeResult: yield TextLog(highlight=True) def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "plus": + if event.sender.id == "plus": self.count += 1 else: self.count -= 1 diff --git a/docs/examples/guide/screens/modal01.py b/docs/examples/guide/screens/modal01.py index 2ae1dd990a..416693ec42 100644 --- a/docs/examples/guide/screens/modal01.py +++ b/docs/examples/guide/screens/modal01.py @@ -1,7 +1,7 @@ from textual.app import App, ComposeResult from textual.containers import Grid from textual.screen import Screen -from textual.widgets import Static, Header, Footer, Button +from textual.widgets import Button, Footer, Header, Static class QuitScreen(Screen): @@ -14,7 +14,7 @@ def compose(self) -> ComposeResult: ) def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "quit": + if event.sender.id == "quit": self.app.exit() else: self.app.pop_screen() diff --git a/docs/examples/tutorial/stopwatch.py b/docs/examples/tutorial/stopwatch.py index ffb87ea4c2..0142401c34 100644 --- a/docs/examples/tutorial/stopwatch.py +++ b/docs/examples/tutorial/stopwatch.py @@ -3,7 +3,7 @@ from textual.app import App, ComposeResult from textual.containers import Container from textual.reactive import reactive -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -49,7 +49,7 @@ class Stopwatch(Static): def on_button_pressed(self, event: Button.Pressed) -> None: """Event handler called when a button is pressed.""" - button_id = event.button.id + button_id = event.sender.id time_display = self.query_one(TimeDisplay) if button_id == "start": time_display.start() diff --git a/docs/examples/tutorial/stopwatch04.py b/docs/examples/tutorial/stopwatch04.py index 2394fd20ca..13cc3d3b00 100644 --- a/docs/examples/tutorial/stopwatch04.py +++ b/docs/examples/tutorial/stopwatch04.py @@ -1,6 +1,6 @@ from textual.app import App, ComposeResult from textual.containers import Container -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -12,9 +12,9 @@ class Stopwatch(Static): def on_button_pressed(self, event: Button.Pressed) -> None: """Event handler called when a button is pressed.""" - if event.button.id == "start": + if event.sender.id == "start": self.add_class("started") - elif event.button.id == "stop": + elif event.sender.id == "stop": self.remove_class("started") def compose(self) -> ComposeResult: diff --git a/docs/examples/tutorial/stopwatch05.py b/docs/examples/tutorial/stopwatch05.py index 6543ac5ebc..320529e373 100644 --- a/docs/examples/tutorial/stopwatch05.py +++ b/docs/examples/tutorial/stopwatch05.py @@ -3,7 +3,7 @@ from textual.app import App, ComposeResult from textual.containers import Container from textual.reactive import reactive -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -32,9 +32,9 @@ class Stopwatch(Static): def on_button_pressed(self, event: Button.Pressed) -> None: """Event handler called when a button is pressed.""" - if event.button.id == "start": + if event.sender.id == "start": self.add_class("started") - elif event.button.id == "stop": + elif event.sender.id == "stop": self.remove_class("started") def compose(self) -> ComposeResult: diff --git a/docs/examples/tutorial/stopwatch06.py b/docs/examples/tutorial/stopwatch06.py index 84e862f98f..526564402c 100644 --- a/docs/examples/tutorial/stopwatch06.py +++ b/docs/examples/tutorial/stopwatch06.py @@ -3,7 +3,7 @@ from textual.app import App, ComposeResult from textual.containers import Container from textual.reactive import reactive -from textual.widgets import Button, Header, Footer, Static +from textual.widgets import Button, Footer, Header, Static class TimeDisplay(Static): @@ -49,7 +49,7 @@ class Stopwatch(Static): def on_button_pressed(self, event: Button.Pressed) -> None: """Event handler called when a button is pressed.""" - button_id = event.button.id + button_id = event.sender.id time_display = self.query_one(TimeDisplay) if button_id == "start": time_display.start() diff --git a/docs/examples/widgets/button.py b/docs/examples/widgets/button.py index 4c3509c325..eb14d758a9 100644 --- a/docs/examples/widgets/button.py +++ b/docs/examples/widgets/button.py @@ -27,7 +27,7 @@ def compose(self) -> ComposeResult: ) def on_button_pressed(self, event: Button.Pressed) -> None: - self.exit(str(event.button)) + self.exit(str(event.sender)) if __name__ == "__main__": diff --git a/examples/calculator.py b/examples/calculator.py index f494185ab9..fe747d3115 100644 --- a/examples/calculator.py +++ b/examples/calculator.py @@ -94,7 +94,7 @@ def press(button_id: str) -> None: def on_button_pressed(self, event: Button.Pressed) -> None: """Called when a button is pressed.""" - button_id = event.button.id + button_id = event.sender.id assert button_id is not None def do_math() -> None: diff --git a/examples/five_by_five.py b/examples/five_by_five.py index c89bea49e5..8190f766f7 100644 --- a/examples/five_by_five.py +++ b/examples/five_by_five.py @@ -263,7 +263,7 @@ def on_button_pressed(self, event: GameCell.Pressed) -> None: Args: event (GameCell.Pressed): The event to react to. """ - self.make_move_on(cast(GameCell, event.button)) + self.make_move_on(cast(GameCell, event.sender)) def action_new_game(self) -> None: """Start a new game.""" diff --git a/src/textual/cli/previews/borders.py b/src/textual/cli/previews/borders.py index a33320a0a7..e27828a1b0 100644 --- a/src/textual/cli/previews/borders.py +++ b/src/textual/cli/previews/borders.py @@ -57,7 +57,7 @@ def compose(self): def on_button_pressed(self, event: Button.Pressed) -> None: self.text.styles.border = ( - event.button.id, + event.sender.id, self.stylesheet._variables["secondary"], ) self.bell() diff --git a/src/textual/cli/previews/colors.py b/src/textual/cli/previews/colors.py index b9d8da3eba..1f223b02c0 100644 --- a/src/textual/cli/previews/colors.py +++ b/src/textual/cli/previews/colors.py @@ -69,7 +69,7 @@ def update_view(self) -> None: def on_button_pressed(self, event: Button.Pressed) -> None: self.query(ColorGroup).remove_class("-active") - group = self.query_one(f"#group-{event.button.id}", ColorGroup) + group = self.query_one(f"#group-{event.sender.id}", ColorGroup) group.add_class("-active") group.scroll_visible(top=True, speed=150) diff --git a/src/textual/cli/previews/easing.py b/src/textual/cli/previews/easing.py index 70dc45758a..4559f2d57c 100644 --- a/src/textual/cli/previews/easing.py +++ b/src/textual/cli/previews/easing.py @@ -92,13 +92,13 @@ def _animation_complete(): target_position = ( END_POSITION if self.position == START_POSITION else START_POSITION ) - assert event.button.id is not None # Should be set to an easing function str. + assert event.sender.id is not None # Should be set to an easing function str. self.animate( "position", value=target_position, final_value=target_position, duration=self.duration, - easing=event.button.id, + easing=event.sender.id, on_complete=_animation_complete, ) @@ -107,7 +107,7 @@ def watch_position(self, value: int): self.opacity_widget.styles.opacity = 1 - value / END_POSITION def on_input_changed(self, event: Input.Changed): - if event.input.id == "duration-input": + if event.sender.id == "duration-input": new_duration = _try_float(event.value) if new_duration is not None: self.duration = new_duration diff --git a/src/textual/cli/previews/keys.py b/src/textual/cli/previews/keys.py index 4be99e022d..b6030831c5 100644 --- a/src/textual/cli/previews/keys.py +++ b/src/textual/cli/previews/keys.py @@ -60,9 +60,9 @@ def on_key(self, event: events.Key) -> None: self.last_key = event.key def on_button_pressed(self, event: Button.Pressed) -> None: - if event.button.id == "quit": + if event.sender.id == "quit": self.exit() - elif event.button.id == "clear": + elif event.sender.id == "clear": self.query_one(KeyLog).clear() diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index 3e9acf913c..6bea8ff631 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -10,7 +10,6 @@ from rich.style import Style, StyleType from . import events -from ._types import MessageTarget from .geometry import Offset from .message import Message from .reactive import Reactive @@ -19,7 +18,10 @@ class ScrollMessage(Message, bubble=False): - pass + """Base class for all messages posted by the scrollbar.""" + + sender: ScrollBar + """The scrollbar that posted the message.""" @rich.repr.auto @@ -47,7 +49,7 @@ class ScrollTo(ScrollMessage, verbose=True): def __init__( self, - sender: MessageTarget, + sender: ScrollBar, x: float | None = None, y: float | None = None, animate: bool = True, diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index f8b22a9694..52d59919fd 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -155,14 +155,10 @@ class Pressed(Message, bubble=True): Can be handled using `on_button_pressed` in a subclass of `Button` or in a parent widget in the DOM. - - Attributes: - button: The button that was pressed. """ - @property - def button(self) -> Button: - return cast(Button, self.sender) + sender: Button + """The button that posted the message.""" def __init__( self, diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 3ae85ca767..69dee8d441 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -3,7 +3,9 @@ import functools from dataclasses import dataclass from itertools import chain, zip_longest +from multiprocessing import Event from operator import itemgetter +from tkinter import E from typing import Any, ClassVar, Generic, Iterable, NamedTuple, TypeVar, cast import rich.repr @@ -38,6 +40,9 @@ ) CursorType = Literal["cell", "row", "column", "none"] CellType = TypeVar("CellType") +"""Used to type a `DataTable` as a container of cells.""" +EventCellType = TypeVar("EventCellType") +"""Used to type the sender attribute of `DataTable`-related events.""" CELL_X_PADDING = 2 @@ -307,7 +312,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): ) hover_coordinate: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False) - class CellHighlighted(Message, bubble=True): + class CellHighlighted(Generic[EventCellType], Message, bubble=True): """Posted when the cursor moves to highlight a new cell. This is only relevant when the `cursor_type` is `"cell"`. @@ -317,19 +322,25 @@ class CellHighlighted(Message, bubble=True): a subclass of `DataTable` or in a parent widget in the DOM. """ + cell_key: CellKey + """The key for the highlighted cell.""" + coordinate: Coordinate + """The coordinate of the highlighted cell.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + value: EventCellType + """The value in the highlighted cell.""" + def __init__( self, - sender: DataTable, - value: CellType, + sender: DataTable[EventCellType], + value: EventCellType, coordinate: Coordinate, cell_key: CellKey, ) -> None: - self.value: CellType = value - """The value in the highlighted cell.""" - self.coordinate: Coordinate = coordinate - """The coordinate of the highlighted cell.""" - self.cell_key: CellKey = cell_key - """The key for the highlighted cell.""" + self.value = value + self.coordinate = coordinate + self.cell_key = cell_key super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -338,7 +349,7 @@ def __rich_repr__(self) -> rich.repr.Result: yield "coordinate", self.coordinate yield "cell_key", self.cell_key - class CellSelected(Message, bubble=True): + class CellSelected(Generic[EventCellType], Message, bubble=True): """Posted by the `DataTable` widget when a cell is selected. This is only relevant when the `cursor_type` is `"cell"`. Can be handled using @@ -346,19 +357,25 @@ class CellSelected(Message, bubble=True): widget in the DOM. """ + cell_key: CellKey + """The key for the selected cell.""" + coordinate: Coordinate + """The coordinate of the selected cell.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + value: EventCellType + """The value in the selected cell.""" + def __init__( self, - sender: DataTable, - value: CellType, + sender: DataTable[EventCellType], + value: EventCellType, coordinate: Coordinate, cell_key: CellKey, ) -> None: - self.value: CellType = value - """The value in the cell that was selected.""" - self.coordinate: Coordinate = coordinate - """The coordinate of the cell that was selected.""" - self.cell_key: CellKey = cell_key - """The key for the selected cell.""" + self.value = value + self.coordinate = coordinate + self.cell_key = cell_key super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -367,7 +384,7 @@ def __rich_repr__(self) -> rich.repr.Result: yield "coordinate", self.coordinate yield "cell_key", self.cell_key - class RowHighlighted(Message, bubble=True): + class RowHighlighted(Generic[EventCellType], Message, bubble=True): """Posted when a row is highlighted. This message is only posted when the @@ -376,11 +393,18 @@ class RowHighlighted(Message, bubble=True): widget in the DOM. """ - def __init__(self, sender: DataTable, cursor_row: int, row_key: RowKey) -> None: - self.cursor_row: int = cursor_row - """The y-coordinate of the cursor that highlighted the row.""" - self.row_key: RowKey = row_key - """The key of the row that was highlighted.""" + cursor_row: int + """The y coordinate of the row that was highlighted.""" + row_key: RowKey + """The key of the row that was highlighted.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + + def __init__( + self, sender: DataTable[EventCellType], cursor_row: int, row_key: RowKey + ) -> None: + self.cursor_row = cursor_row + self.row_key = row_key super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -388,7 +412,7 @@ def __rich_repr__(self) -> rich.repr.Result: yield "cursor_row", self.cursor_row yield "row_key", self.row_key - class RowSelected(Message, bubble=True): + class RowSelected(Generic[EventCellType], Message, bubble=True): """Posted when a row is selected. This message is only posted when the @@ -397,11 +421,18 @@ class RowSelected(Message, bubble=True): widget in the DOM. """ - def __init__(self, sender: DataTable, cursor_row: int, row_key: RowKey) -> None: - self.cursor_row: int = cursor_row - """The y-coordinate of the cursor that made the selection.""" - self.row_key: RowKey = row_key - """The key of the row that was selected.""" + cursor_row: int + """The y coordinate of the row that was selected.""" + row_key: RowKey + """The key of the row that was selected.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + + def __init__( + self, sender: DataTable[EventCellType], cursor_row: int, row_key: RowKey + ) -> None: + self.cursor_row = cursor_row + self.row_key = row_key super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -409,7 +440,7 @@ def __rich_repr__(self) -> rich.repr.Result: yield "cursor_row", self.cursor_row yield "row_key", self.row_key - class ColumnHighlighted(Message, bubble=True): + class ColumnHighlighted(Generic[EventCellType], Message, bubble=True): """Posted when a column is highlighted. This message is only posted when the @@ -418,13 +449,21 @@ class ColumnHighlighted(Message, bubble=True): widget in the DOM. """ + column_key: ColumnKey + """The key of the column that was highlighted.""" + cursor_column: int + """The x coordinate of the column that was highlighted.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + def __init__( - self, sender: DataTable, cursor_column: int, column_key: ColumnKey + self, + sender: DataTable[EventCellType], + cursor_column: int, + column_key: ColumnKey, ) -> None: - self.cursor_column: int = cursor_column - """The x-coordinate of the column that was highlighted.""" + self.cursor_column = cursor_column self.column_key = column_key - """The key of the column that was highlighted.""" super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -432,7 +471,7 @@ def __rich_repr__(self) -> rich.repr.Result: yield "cursor_column", self.cursor_column yield "column_key", self.column_key - class ColumnSelected(Message, bubble=True): + class ColumnSelected(Generic[EventCellType], Message, bubble=True): """Posted when a column is selected. This message is only posted when the @@ -441,13 +480,21 @@ class ColumnSelected(Message, bubble=True): widget in the DOM. """ + column_key: ColumnKey + """The key of the column that was selected.""" + cursor_column: int + """The x coordinate of the column that was selected.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + def __init__( - self, sender: DataTable, cursor_column: int, column_key: ColumnKey + self, + sender: DataTable[EventCellType], + cursor_column: int, + column_key: ColumnKey, ) -> None: - self.cursor_column: int = cursor_column - """The x-coordinate of the column that was selected.""" + self.cursor_column = cursor_column self.column_key = column_key - """The key of the column that was selected.""" super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -455,22 +502,28 @@ def __rich_repr__(self) -> rich.repr.Result: yield "cursor_column", self.cursor_column yield "column_key", self.column_key - class HeaderSelected(Message, bubble=True): + class HeaderSelected(Generic[EventCellType], Message, bubble=True): """Posted when a column header/label is clicked.""" + column_index: int + """The index for the column.""" + column_key: ColumnKey + """The key for the column.""" + label: Text + """The text of the label.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + def __init__( self, - sender: DataTable, + sender: DataTable[EventCellType], column_key: ColumnKey, column_index: int, label: Text, ): self.column_key = column_key - """The key for the column.""" self.column_index = column_index - """The index for the column.""" self.label = label - """The text of the label.""" super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: @@ -479,22 +532,28 @@ def __rich_repr__(self) -> rich.repr.Result: yield "column_index", self.column_index yield "label", self.label.plain - class RowLabelSelected(Message, bubble=True): + class RowLabelSelected(Generic[EventCellType], Message, bubble=True): """Posted when a row label is clicked.""" + label: Text + """The text of the label.""" + row_index: int + """The index for the row.""" + row_key: RowKey + """The key for the row.""" + sender: DataTable[EventCellType] + """The data table that posted this message.""" + def __init__( self, - sender: DataTable, + sender: DataTable[EventCellType], row_key: RowKey, row_index: int, label: Text, ): self.row_key = row_key - """The key for the column.""" self.row_index = row_index - """The index for the column.""" self.label = label - """The text of the label.""" super().__init__(sender) def __rich_repr__(self) -> rich.repr.Result: diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 8b481a5522..1ec4aac432 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -77,6 +77,8 @@ class FileSelected(Message, bubble=True): path: The path of the file that was selected. """ + sender: DirectoryTree + def __init__(self, sender: MessageTarget, path: str) -> None: self.path: str = path super().__init__(sender) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 4a6816ac77..f0d23834e2 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from typing import ClassVar +from typing import ClassVar, Optional from rich.cells import cell_len, get_character_cell_size from rich.console import Console, ConsoleOptions, RenderableType, RenderResult @@ -133,39 +133,39 @@ class Input(Widget, can_focus=True): width = reactive(1) _cursor_visible = reactive(True) password = reactive(False) - max_size: reactive[int | None] = reactive(None) + max_size = reactive[Optional[int]](None) class Changed(Message, bubble=True): """Posted when the value changes. Can be handled using `on_input_changed` in a subclass of `Input` or in a parent widget in the DOM. - - Attributes: - value: The value that the input was changed to. - input: The `Input` widget that was changed. """ + sender: Input + """The input that posted the message.""" + value: str + """The value that the input was changed to.""" + def __init__(self, sender: Input, value: str) -> None: super().__init__(sender) - self.value: str = value - self.input: Input = sender + self.value = value class Submitted(Message, bubble=True): """Posted when the enter key is pressed within an `Input`. Can be handled using `on_input_submitted` in a subclass of `Input` or in a parent widget in the DOM. - - Attributes: - value: The value of the `Input` being submitted. - input: The `Input` widget that is being submitted. """ + sender: Input + """The input that posted the message.""" + value: str + """The value of the input being submitted.""" + def __init__(self, sender: Input, value: str) -> None: super().__init__(sender) - self.value: str = value - self.input: Input = sender + self.value = value def __init__( self, diff --git a/src/textual/widgets/_list_item.py b/src/textual/widgets/_list_item.py index b8bf513672..baaad195a2 100644 --- a/src/textual/widgets/_list_item.py +++ b/src/textual/widgets/_list_item.py @@ -42,6 +42,7 @@ class _ChildClicked(Message): """For informing with the parent ListView that we were clicked""" sender: "ListItem" + """The list item that posted the message.""" def on_click(self, event: events.Click) -> None: self.post_message_no_wait(self._ChildClicked(self)) diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index e24ecf31b5..e529bfe5fd 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -43,14 +43,16 @@ class Highlighted(Message, bubble=True): Highlighted item is controlled using up/down keys. Can be handled using `on_list_view_highlighted` in a subclass of `ListView` or in a parent widget in the DOM. - - Attributes: - item: The highlighted item, if there is one highlighted. """ + item: ListItem | None + """The highlighted item, if there is one highlighted.""" + sender: ListView + """The list view that posted the message.""" + def __init__(self, sender: ListView, item: ListItem | None) -> None: super().__init__(sender) - self.item: ListItem | None = item + self.item = item class Selected(Message, bubble=True): """Posted when a list item is selected, e.g. when you press the enter key on it. diff --git a/src/textual/widgets/_markdown.py b/src/textual/widgets/_markdown.py index d7aabe628b..2be243e3b0 100644 --- a/src/textual/widgets/_markdown.py +++ b/src/textual/widgets/_markdown.py @@ -524,28 +524,40 @@ def __init__( class TableOfContentsUpdated(Message, bubble=True): """The table of contents was updated.""" + sender: Markdown + """The markdown widget that posted the message.""" + table_of_contents: TableOfContentsType + """The updated table of contents.""" + def __init__( - self, table_of_contents: TableOfContentsType, *, sender: Widget + self, table_of_contents: TableOfContentsType, *, sender: Markdown ) -> None: super().__init__(sender=sender) - self.table_of_contents: TableOfContentsType = table_of_contents - """Table of contents.""" + self.table_of_contents = table_of_contents class TableOfContentsSelected(Message, bubble=True): """An item in the TOC was selected.""" - def __init__(self, block_id: str, *, sender: Widget) -> None: + block_id: str + """ID of the block that was selected.""" + sender: MarkdownTableOfContents + """The table of contents that posted the message.""" + + def __init__(self, block_id: str, *, sender: MarkdownTableOfContents) -> None: super().__init__(sender=sender) self.block_id = block_id - """ID of the block that was selected.""" class LinkClicked(Message, bubble=True): """A link in the document was clicked.""" - def __init__(self, href: str, *, sender: Widget) -> None: + href: str + """The link that was selected.""" + sender: MarkdownBlock + """The markdown block that posted the event.""" + + def __init__(self, href: str, *, sender: MarkdownBlock) -> None: super().__init__(sender=sender) - self.href: str = href - """The link that was selected.""" + self.href = href async def on_mount(self) -> None: if self._markdown is not None: diff --git a/src/textual/widgets/_radio_set.py b/src/textual/widgets/_radio_set.py index fd40615116..49da5423e4 100644 --- a/src/textual/widgets/_radio_set.py +++ b/src/textual/widgets/_radio_set.py @@ -37,6 +37,13 @@ class Changed(Message, bubble=True): This message can be handled using an `on_radio_set_changed` method. """ + index: int + """The index of the radio button that was pressed to make the change.""" + pressed: RadioButton + """The `RadioButton` that was pressed to make the change.""" + sender: RadioSet + """The radio set that posted the message.""" + def __init__(self, sender: RadioSet, pressed: RadioButton) -> None: """Initialise the message. @@ -45,17 +52,13 @@ def __init__(self, sender: RadioSet, pressed: RadioButton) -> None: pressed: The radio button that was pressed. """ super().__init__(sender) - self.input = sender - """A reference to the `RadioSet` that was changed.""" self.pressed = pressed - """The `RadioButton` that was pressed to make the change.""" # Note: it would be cleaner to use `sender.pressed_index` here, # but we can't be 100% sure all of the updates have happened at # this point, and so we can't go looking for the index of the # pressed button via the normal route. So here we go under the # hood. self.index = sender._nodes.index(pressed) - """The index of the `RadioButton` that was pressed to make the change.""" def __init__( self, @@ -114,16 +117,16 @@ def on_radio_button_changed(self, event: RadioButton.Changed) -> None: event: The event. """ # If the button is changing to be the pressed button... - if event.input.value: + if event.sender.value: # ...send off a message to say that the pressed state has # changed. self.post_message_no_wait( - self.Changed(self, cast(RadioButton, event.input)) + self.Changed(self, cast(RadioButton, event.sender)) ) # ...then look for the button that was previously the pressed # one and unpress it. for button in self._buttons.filter(".-on"): - if button != event.input: + if button != event.sender: button.value = False break else: @@ -134,7 +137,7 @@ def on_radio_button_changed(self, event: RadioButton.Changed) -> None: event.stop() if not self._buttons.filter(".-on"): with self.prevent(RadioButton.Changed): - event.input.value = True + event.sender.value = True @property def pressed_button(self) -> RadioButton | None: diff --git a/src/textual/widgets/_switch.py b/src/textual/widgets/_switch.py index 97e3e22233..412065a989 100644 --- a/src/textual/widgets/_switch.py +++ b/src/textual/widgets/_switch.py @@ -81,16 +81,16 @@ class Changed(Message, bubble=True): Can be handled using `on_switch_changed` in a subclass of `Switch` or in a parent widget in the DOM. - - Attributes: - value: The value that the switch was changed to. - input: The `Switch` widget that was changed. """ + sender: Switch + """The switch that posted the message.""" + value: bool + """The value that the switch was changed to.""" + def __init__(self, sender: Switch, value: bool) -> None: super().__init__(sender) - self.value: bool = value - self.input: Switch = sender + self.value = value def __init__( self, diff --git a/src/textual/widgets/_toggle_button.py b/src/textual/widgets/_toggle_button.py index 40be4e6186..34b9cdaebc 100644 --- a/src/textual/widgets/_toggle_button.py +++ b/src/textual/widgets/_toggle_button.py @@ -216,7 +216,16 @@ def on_click(self) -> None: self.toggle() class Changed(Message, bubble=True): - """Posted when the value of the toggle button changes.""" + """Posted when the value of the toggle button changes. + + Can be handled using `on_toggle_button_changed` in a subclass of `ToggleButton` + or in a parent widget in the DOM. + """ + + sender: ToggleButton + """The toggle button that posted the message.""" + value: bool + """The value of the toggle button after the change.""" def __init__(self, sender: ToggleButton, value: bool) -> None: """Initialise the message. @@ -226,10 +235,7 @@ def __init__(self, sender: ToggleButton, value: bool) -> None: value: The value of the toggle button. """ super().__init__(sender) - self.input = sender - """A reference to the toggle button that was changed.""" self.value = value - """The value of the toggle button after the change.""" def watch_value(self) -> None: """React to the value being changed. diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index 3c5e28ceba..444134fa58 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass +from multiprocessing import Event from typing import TYPE_CHECKING, ClassVar, Generic, Iterable, NewType, TypeVar, cast import rich.repr @@ -431,15 +432,17 @@ class NodeCollapsed(Generic[EventTreeDataType], Message, bubble=True): Can be handled using `on_tree_node_collapsed` in a subclass of `Tree` or in a parent node in the DOM. - - Attributes: - node: The node that was collapsed. """ + node: TreeNode[EventTreeDataType] + """The node that was collapsed.""" + sender: Tree[EventTreeDataType] + """The tree that posted this message.""" + def __init__( - self, sender: MessageTarget, node: TreeNode[EventTreeDataType] + self, sender: Tree[EventTreeDataType], node: TreeNode[EventTreeDataType] ) -> None: - self.node: TreeNode[EventTreeDataType] = node + self.node = node super().__init__(sender) class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True): @@ -447,15 +450,17 @@ class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True): Can be handled using `on_tree_node_expanded` in a subclass of `Tree` or in a parent node in the DOM. - - Attributes: - node: The node that was expanded. """ + node: TreeNode[EventTreeDataType] + """The node that was expanded.""" + sender: Tree[EventTreeDataType] + """The tree that posted this message.""" + def __init__( - self, sender: MessageTarget, node: TreeNode[EventTreeDataType] + self, sender: Tree[EventTreeDataType], node: TreeNode[EventTreeDataType] ) -> None: - self.node: TreeNode[EventTreeDataType] = node + self.node = node super().__init__(sender) class NodeHighlighted(Generic[EventTreeDataType], Message, bubble=True): @@ -463,15 +468,17 @@ class NodeHighlighted(Generic[EventTreeDataType], Message, bubble=True): Can be handled using `on_tree_node_highlighted` in a subclass of `Tree` or in a parent node in the DOM. - - Attributes: - node: The node that was highlighted. """ + node: TreeNode[EventTreeDataType] + """The node that was highlighted.""" + sender: Tree[EventTreeDataType] + """The tree that posted this message.""" + def __init__( - self, sender: MessageTarget, node: TreeNode[EventTreeDataType] + self, sender: Tree[EventTreeDataType], node: TreeNode[EventTreeDataType] ) -> None: - self.node: TreeNode[EventTreeDataType] = node + self.node = node super().__init__(sender) class NodeSelected(Generic[EventTreeDataType], Message, bubble=True): @@ -479,15 +486,17 @@ class NodeSelected(Generic[EventTreeDataType], Message, bubble=True): Can be handled using `on_tree_node_selected` in a subclass of `Tree` or in a parent node in the DOM. - - Attributes: - node: The node that was selected. """ + node: TreeNode[EventTreeDataType] + """The node that was selected.""" + sender: Tree[EventTreeDataType] + """The tree that posted this message.""" + def __init__( - self, sender: MessageTarget, node: TreeNode[EventTreeDataType] + self, sender: Tree[EventTreeDataType], node: TreeNode[EventTreeDataType] ) -> None: - self.node: TreeNode[EventTreeDataType] = node + self.node = node super().__init__(sender) def __init__( diff --git a/tests/toggles/test_checkbox.py b/tests/toggles/test_checkbox.py index 04ad687571..6ddf990180 100644 --- a/tests/toggles/test_checkbox.py +++ b/tests/toggles/test_checkbox.py @@ -15,7 +15,7 @@ def compose(self) -> ComposeResult: yield Checkbox(value=True, id="cb3") def on_checkbox_changed(self, event: Checkbox.Changed) -> None: - self.events_received.append((event.input.id, event.input.value)) + self.events_received.append((event.sender.id, event.sender.value)) async def test_checkbox_initial_state() -> None: diff --git a/tests/toggles/test_radiobutton.py b/tests/toggles/test_radiobutton.py index a0a012ea01..898eb7a4ec 100644 --- a/tests/toggles/test_radiobutton.py +++ b/tests/toggles/test_radiobutton.py @@ -15,7 +15,7 @@ def compose(self) -> ComposeResult: yield RadioButton(value=True, id="rb3") def on_radio_button_changed(self, event: RadioButton.Changed) -> None: - self.events_received.append((event.input.id, event.input.value)) + self.events_received.append((event.sender.id, event.sender.value)) async def test_radio_button_initial_state() -> None: diff --git a/tests/toggles/test_radioset.py b/tests/toggles/test_radioset.py index 1a38fe7ffb..41abb0c792 100644 --- a/tests/toggles/test_radioset.py +++ b/tests/toggles/test_radioset.py @@ -19,9 +19,9 @@ def compose(self) -> ComposeResult: def on_radio_set_changed(self, event: RadioSet.Changed) -> None: self.events_received.append( ( - event.input.id, + event.sender.id, event.index, - [button.value for button in event.input.query(RadioButton)], + [button.value for button in event.sender.query(RadioButton)], ) )