Skip to content

Commit 0c391a4

Browse files
authored
Merge pull request #23 from Textualize/new-dismiss-method
Move the input dialogs over to the newer `Screen` result approach
2 parents 7a70c80 + 9a7b444 commit 0c391a4

File tree

6 files changed

+47
-138
lines changed

6 files changed

+47
-138
lines changed

frogmouth/dialogs/input_dialog.py

+4-50
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
6-
75
from textual.app import ComposeResult
86
from textual.binding import Binding
97
from textual.containers import Horizontal, Vertical
10-
from textual.message import Message
118
from textual.screen import ModalScreen
12-
from textual.widget import Widget
139
from textual.widgets import Button, Input, Label
1410

1511

16-
class InputDialog(ModalScreen[None]):
12+
class InputDialog(ModalScreen[str]):
1713
"""A modal dialog for getting a single input from the user."""
1814

1915
DEFAULT_CSS = """
@@ -59,32 +55,18 @@ class InputDialog(ModalScreen[None]):
5955
]
6056
"""Bindings for the dialog."""
6157

62-
def __init__( # pylint:disable=redefined-builtin,too-many-arguments
63-
self,
64-
requester: Widget,
65-
prompt: str,
66-
initial: str | None = None,
67-
cargo: Any = None,
68-
id: str | None = None,
69-
) -> None:
58+
def __init__(self, prompt: str, initial: str | None = None) -> None:
7059
"""Initialise the input dialog.
7160
7261
Args:
73-
requester: The widget requesting the input.
7462
prompt: The prompt for the input.
7563
initial: The initial value for the input.
76-
cargo: Any cargo value for the input.
77-
id: The ID for the dialog.
7864
"""
79-
super().__init__(id=id)
80-
self._requester = requester
81-
"""A reference to the widget requesting the input."""
65+
super().__init__()
8266
self._prompt = prompt
8367
"""The prompt to display for the input."""
8468
self._initial = initial
8569
"""The initial value to use for the input."""
86-
self._cargo = cargo
87-
"""Any cargo data for the input dialog."""
8870

8971
def compose(self) -> ComposeResult:
9072
"""Compose the child widgets."""
@@ -100,33 +82,6 @@ def on_mount(self) -> None:
10082
"""Set up the dialog once the DOM is ready."""
10183
self.query_one(Input).focus()
10284

103-
class Result(Message):
104-
"""The input dialog result message."""
105-
106-
def __init__(
107-
self, sender_id: str | None, value: str, cargo: Any = None
108-
) -> None:
109-
"""Initialise the result message.
110-
111-
Args:
112-
sender_id: The ID of the dialog sending the message.
113-
value: The value to attach as the result.
114-
cargo: Any cargo data for the result.
115-
"""
116-
super().__init__()
117-
self.sender_id: str | None = sender_id
118-
"""The ID of the sending dialog."""
119-
self.value: str = value
120-
"""The value of the result."""
121-
self.cargo: Any = cargo
122-
"""Cargo data for the result."""
123-
124-
def _return_input(self) -> None:
125-
"""Return the input value from the dialog."""
126-
self._requester.post_message(
127-
self.Result(self.id, self.query_one(Input).value, self._cargo)
128-
)
129-
13085
def on_button_pressed(self, event: Button.Pressed) -> None:
13186
"""Handle one of the dialog's buttons been pressed.
13287
@@ -136,8 +91,7 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
13691
if event.button.id == "cancel":
13792
self.app.pop_screen()
13893
elif event.button.id == "ok" and self.query_one(Input).value.strip():
139-
self._return_input()
140-
self.app.pop_screen()
94+
self.dismiss(self.query_one(Input).value)
14195

14296
def on_input_submitted(self) -> None:
14397
"""Do default processing when the user hits enter in the input."""

frogmouth/dialogs/yes_no_dialog.py

+4-42
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
6-
75
from textual.app import ComposeResult
86
from textual.binding import Binding
97
from textual.containers import Center, Horizontal, Vertical
10-
from textual.message import Message
118
from textual.screen import ModalScreen
12-
from textual.widget import Widget
139
from textual.widgets import Button, Static
1410

1511

16-
class YesNoDialog(ModalScreen[None]):
12+
class YesNoDialog(ModalScreen[bool]):
1713
"""A dialog for asking a user a yes/no question."""
1814

1915
DEFAULT_CSS = """
@@ -66,16 +62,13 @@ class YesNoDialog(ModalScreen[None]):
6662
]
6763
"""Bindings for the yes/no dialog."""
6864

69-
def __init__( # pylint:disable=redefined-builtin,too-many-arguments
65+
def __init__( # pylint:disable=too-many-arguments
7066
self,
71-
requester: Widget,
7267
title: str,
7368
question: str,
7469
yes_label: str = "Yes",
7570
no_label: str = "No",
7671
yes_first: bool = True,
77-
cargo: Any = None,
78-
id: str | None = None,
7972
) -> None:
8073
"""Initialise the yes/no dialog.
8174
@@ -89,9 +82,7 @@ def __init__( # pylint:disable=redefined-builtin,too-many-arguments
8982
cargo: Any cargo value for the question.
9083
id: The ID for the dialog.
9184
"""
92-
super().__init__(id=id)
93-
self._requester = requester
94-
"""A reference to the widget asking the question."""
85+
super().__init__()
9586
self._title = title
9687
"""The title for the dialog."""
9788
self._question = question
@@ -102,8 +93,6 @@ def __init__( # pylint:disable=redefined-builtin,too-many-arguments
10293
"""The label for the no button."""
10394
self._aye_first = yes_first
10495
"""Should the positive button come first?"""
105-
self._cargo = cargo
106-
"""Any cargo data for the reply."""
10796

10897
def compose(self) -> ComposeResult:
10998
"""Compose the content of the dialog."""
@@ -127,37 +116,10 @@ def on_mount(self) -> None:
127116
"""Configure the dialog once the DOM is ready."""
128117
self.query(Button).first().focus()
129118

130-
class Reply(Message):
131-
"""Base class for replies from the yes/no dialog."""
132-
133-
def __init__(self, sender_id: str | None, cargo: Any = None) -> None:
134-
"""Initialise the reply message.
135-
136-
Args:
137-
sender_id: The ID of the dialog sending the message.
138-
cargo: Any cargo data for the result.
139-
"""
140-
super().__init__()
141-
self.sender_id: str | None = sender_id
142-
"""The ID of the sending dialog."""
143-
self.cargo: Any = cargo
144-
"""Cargo data for the result."""
145-
146-
class PositiveReply(Reply):
147-
"""A positive reply from the yes/no dialog."""
148-
149-
class NegativeReply(Reply):
150-
"""A negative reply from the yes/no dialog."""
151-
152119
def on_button_pressed(self, event: Button.Pressed) -> None:
153120
"""Handle a button being pressed on the dialog.
154121
155122
Args:
156123
event: The event to handle.
157124
"""
158-
self._requester.post_message(
159-
(self.PositiveReply if event.button.id == "yes" else self.NegativeReply)(
160-
self.id, self._cargo
161-
)
162-
)
163-
self.app.pop_screen()
125+
self.dismiss(event.button.id == "yes")

frogmouth/screens/main.py

+12-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from functools import partial
56
from pathlib import Path
67
from typing import Awaitable, Callable
78
from webbrowser import open as open_url
@@ -471,6 +472,15 @@ def action_about(self) -> None:
471472
)
472473
)
473474

475+
def add_bookmark(self, location: Path | URL, bookmark: str) -> None:
476+
"""Handle adding the bookmark.
477+
478+
Args:
479+
location: The location to bookmark.
480+
bookmark: The bookmark to add.
481+
"""
482+
self.query_one(Navigation).bookmarks.add_bookmark(bookmark, location)
483+
474484
def action_bookmark_this(self) -> None:
475485
"""Add a bookmark for the currently-viewed file."""
476486

@@ -493,23 +503,10 @@ def action_bookmark_this(self) -> None:
493503

494504
# Give the user a chance to edit the title.
495505
self.app.push_screen(
496-
InputDialog(self, "Bookmark title:", title, location, id="add_bookmark")
506+
InputDialog("Bookmark title:", title),
507+
partial(self.add_bookmark, location),
497508
)
498509

499-
def on_input_dialog_result(self, event: InputDialog.Result) -> None:
500-
"""Handle a result coming back from an input dialog.
501-
502-
Args:
503-
event: The input dialog result event.
504-
"""
505-
# Handle the correct source dialog.
506-
if event.sender_id == "add_bookmark":
507-
# The cargo value for the result should be the location, which
508-
# should be a path or a URL.
509-
assert isinstance(event.cargo, (Path, URL))
510-
# Save the bookmark.
511-
self.query_one(Navigation).bookmarks.add_bookmark(event.value, event.cargo)
512-
513510
def action_toggle_theme(self) -> None:
514511
"""Toggle the light/dark mode theme."""
515512
config = load_config()

frogmouth/widgets/navigation.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
from textual.widgets import TabbedContent, Tabs
1212
from typing_extensions import Self
1313

14-
from .navigation_panes.navigation_pane import NavigationPane
1514
from .navigation_panes.bookmarks import Bookmarks
1615
from .navigation_panes.history import History
1716
from .navigation_panes.local_files import LocalFiles
17+
from .navigation_panes.navigation_pane import NavigationPane
1818
from .navigation_panes.table_of_contents import TableOfContents
1919

2020

frogmouth/widgets/navigation_panes/bookmarks.py

+25-27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from functools import partial
56
from pathlib import Path
67

78
from httpx import URL
@@ -123,50 +124,47 @@ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> No
123124
assert isinstance(event.option, Entry)
124125
self.post_message(self.Goto(event.option.bookmark))
125126

127+
def delete_bookmark(self, bookmark: int, delete_it: bool) -> None:
128+
"""Delete a given bookmark.
129+
130+
Args:
131+
bookmark: The bookmark to delete.
132+
delete_it: Should it be deleted?
133+
"""
134+
if delete_it:
135+
del self._bookmarks[bookmark]
136+
self._bookmarks_updated()
137+
126138
def action_delete(self) -> None:
127139
"""Delete the highlighted bookmark."""
128-
if self.query_one(OptionList).highlighted is not None:
140+
if (bookmark := self.query_one(OptionList).highlighted) is not None:
129141
self.app.push_screen(
130142
YesNoDialog(
131-
self,
132143
"Delete bookmark",
133144
"Are you sure you want to delete the bookmark?",
134-
id="delete",
135-
)
145+
),
146+
partial(self.delete_bookmark, bookmark),
136147
)
137148

138-
def on_yes_no_dialog_positive_reply(self, event: YesNoDialog.PositiveReply) -> None:
139-
"""Handle a yes/no dialog giving a positive reply.
149+
def rename_bookmark(self, bookmark: int, new_name: str) -> None:
150+
"""Rename the current bookmark.
140151
141152
Args:
142-
event: The event to handle.
153+
bookmark: The location of the bookmark to rename.
154+
new_name: The input dialog result that is the new name.
143155
"""
144-
bookmarks = self.query_one(OptionList)
145-
if event.sender_id == "delete" and bookmarks.highlighted is not None:
146-
del self._bookmarks[bookmarks.highlighted]
147-
self._bookmarks_updated()
156+
self._bookmarks[bookmark] = Bookmark(
157+
new_name, self._bookmarks[bookmark].location
158+
)
159+
self._bookmarks_updated()
148160

149161
def action_rename(self) -> None:
150162
"""Rename the highlighted bookmark."""
151163
if (bookmark := self.query_one(OptionList).highlighted) is not None:
152164
self.app.push_screen(
153165
InputDialog(
154-
self,
155166
"Bookmark title:",
156167
self._bookmarks[bookmark].title,
157-
bookmark,
158-
id="edit_title",
159-
)
168+
),
169+
partial(self.rename_bookmark, bookmark),
160170
)
161-
162-
def on_input_dialog_result(self, event: InputDialog.Result) -> None:
163-
"""Handle an input dialog result being passed to us."""
164-
if event.sender_id == "edit_title":
165-
# The cargo value for the result should be the index of the
166-
# bookmark we're changing.
167-
assert isinstance(event.cargo, int)
168-
# Everything looks good, update the bookmarks.
169-
self._bookmarks[event.cargo] = Bookmark(
170-
event.value, self._bookmarks[event.cargo].location
171-
)
172-
self._bookmarks_updated()

frogmouth/widgets/navigation_panes/navigation_pane.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
"""Provides a base class for all navigation panes."""
22

3-
from typing_extensions import Self
4-
5-
63
from textual.widgets import TabbedContent, TabPane
4+
from typing_extensions import Self
75

86

97
class NavigationPane(TabPane):

0 commit comments

Comments
 (0)