Skip to content
5 changes: 4 additions & 1 deletion tests/core/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,17 @@ def test_stream_muting_confirmation_popup(
stream_name: str = "PTEST",
) -> None:
pop_up = mocker.patch(MODULE + ".PopUpConfirmationView")
pop_up.return_value.width = 30
pop_up.return_value.height = 6

text = mocker.patch(MODULE + ".urwid.Text")
partial = mocker.patch(MODULE + ".partial")
controller.model.muted_streams = muted_streams
controller.loop = mocker.Mock()

controller.stream_muting_confirmation_popup(stream_id, stream_name)

text.assert_called_with(
text.assert_any_call(
("bold", f"Confirm {action} of stream '{stream_name}' ?"),
"center",
)
Expand Down
20 changes: 16 additions & 4 deletions tests/ui_tools/test_popups.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,20 @@ class TestPopUpConfirmationView:
@pytest.fixture
def popup_view(self, mocker: MockerFixture) -> PopUpConfirmationView:
self.controller = mocker.Mock()
self.controller.maximum_popup_dimensions.return_value = (70, 25)

self.text = Text("Some question?")

self.callback = mocker.Mock()

self.list_walker = mocker.patch(LISTWALKER, return_value=[])

self.divider = mocker.patch(MODULE + ".urwid.Divider")
self.text = mocker.patch(MODULE + ".urwid.Text")
self.divider.return_value.rows.return_value = 1

self.wrapper_w = mocker.patch(MODULE + ".urwid.WidgetWrap")
self.wrapper_w.return_value.rows.return_value = 1

return PopUpConfirmationView(
self.controller,
self.text,
Expand All @@ -68,25 +77,28 @@ def test_exit_popup_yes(
self, mocker: MockerFixture, popup_view: PopUpConfirmationView
) -> None:
popup_view.exit_popup_yes(mocker.Mock())

self.callback.assert_called_once_with()
assert self.controller.exit_popup.called

def test_exit_popup_no(
self, mocker: MockerFixture, popup_view: PopUpConfirmationView
) -> None:
popup_view.exit_popup_no(mocker.Mock())

self.callback.assert_not_called()
assert self.controller.exit_popup.called

@pytest.mark.parametrize("key", keys_for_command("EXIT_POPUP"))
def test_exit_popup_EXIT_POPUP(
def test_keypress__EXIT_POPUP(
self,
popup_view: PopUpConfirmationView,
key: str,
widget_size: Callable[[Widget], urwid_Size],
) -> None:
size = widget_size(popup_view)
popup_view.keypress(size, key)

self.callback.assert_not_called()
assert self.controller.exit_popup.called

Expand Down Expand Up @@ -119,8 +131,8 @@ def pop_up_view_autouse(self, mocker: MockerFixture) -> None:
self.command,
self.width,
self.title,
self.header,
self.footer,
header=self.header,
footer=self.footer,
)

def test_init(self, mocker: MockerFixture) -> None:
Expand Down
50 changes: 23 additions & 27 deletions zulipterminal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from typing_extensions import Literal

from zulipterminal.api_types import Composition, Message
from zulipterminal.config.symbols import POPUP_CONTENT_BORDER, POPUP_TOP_LINE
from zulipterminal.config.themes import ThemeSpec
from zulipterminal.config.ui_sizes import (
MAX_LINEAR_SCALING_WIDTH,
Expand All @@ -42,6 +41,7 @@
MsgInfoView,
NoticeView,
PopUpConfirmationView,
PopUpFrame,
StreamInfoView,
StreamMembersView,
UserInfoView,
Expand Down Expand Up @@ -222,23 +222,21 @@ def clamp(n: int, minn: int, maxn: int) -> int:
return max_popup_cols, max_popup_rows

def show_pop_up(self, to_show: Any, style: str) -> None:
text = urwid.Text(to_show.title, align="center")
title_map = urwid.AttrMap(urwid.Filler(text), style)
title_box_adapter = urwid.BoxAdapter(title_map, height=1)
title_top = urwid.AttrMap(urwid.Divider(POPUP_TOP_LINE), "popup_border")
title = urwid.Pile([title_top, title_box_adapter])

content = urwid.LineBox(to_show, **POPUP_CONTENT_BORDER)
if to_show.title is not None:
# +2 to height, due to title enhancement
# TODO: Ideally this would be in PopUpFrame
extra_height = 2
else:
extra_height = 0

self.loop.widget = urwid.Overlay(
urwid.AttrMap(urwid.Frame(header=title, body=content), "popup_border"),
PopUpFrame(to_show, to_show.title, style),
self.view,
align="center",
valign="middle",
# +2 to both of the following, due to LineBox
# +2 to height, due to title enhancement
width=to_show.width + 2,
height=to_show.height + 4,
height=to_show.height + 2 + extra_height,
)

def is_any_popup_open(self) -> bool:
Expand Down Expand Up @@ -494,9 +492,8 @@ def show_media_confirmation_popup(
"?",
]
)
self.loop.widget = PopUpConfirmationView(
self, question, callback, location="center"
)
popup = PopUpConfirmationView(self, question, callback)
self.show_pop_up(popup, "area:msg")

def search_messages(self, text: str) -> None:
# Search for a text in messages
Expand All @@ -523,9 +520,9 @@ def save_draft_confirmation_popup(self, draft: Composition) -> None:
"center",
)
save_draft = partial(self.model.save_draft, draft)
self.loop.widget = PopUpConfirmationView(
self, question, save_draft, location="center"
)

popup = PopUpConfirmationView(self, question, save_draft)
self.show_pop_up(popup, "area:msg")

def stream_muting_confirmation_popup(
self, stream_id: int, stream_name: str
Expand All @@ -537,7 +534,8 @@ def stream_muting_confirmation_popup(
"center",
)
mute_this_stream = partial(self.model.toggle_stream_muted_status, stream_id)
self.loop.widget = PopUpConfirmationView(self, question, mute_this_stream)
popup = PopUpConfirmationView(self, question, mute_this_stream)
self.show_pop_up(popup, "area:msg")

def exit_compose_confirmation_popup(self) -> None:
question = urwid.Text(
Expand All @@ -549,10 +547,9 @@ def exit_compose_confirmation_popup(self) -> None:
"center",
)
write_box = self.view.write_box
popup_view = PopUpConfirmationView(
self, question, write_box.exit_compose_box, location="center"
)
self.loop.widget = popup_view

popup_view = PopUpConfirmationView(self, question, write_box.exit_compose_box)
self.show_pop_up(popup_view, "area:msg")

def copy_to_clipboard(self, text: str, text_category: str) -> None:
try:
Expand Down Expand Up @@ -667,11 +664,10 @@ def prompting_exit_handler(self, signum: int, frame: Any) -> None:
("bold", " Please confirm that you wish to exit Zulip-Terminal "),
"center",
)
popup_view = PopUpConfirmationView(
self, question, self.deregister_client, location="center"
)
self.loop.widget = popup_view
self.loop.run()

popup_view = PopUpConfirmationView(self, question, self.deregister_client)
self.show_pop_up(popup_view, "area:msg")
self.loop.run() # Appears necessary to return control from signal handler

def _raise_exception(self, *args: Any, **kwargs: Any) -> Literal[True]:
if self._exception_info is not None:
Expand Down
74 changes: 40 additions & 34 deletions zulipterminal/ui_tools/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
CHECK_MARK,
COLUMN_TITLE_BAR_LINE,
PINNED_STREAMS_DIVIDER,
POPUP_CONTENT_BORDER,
POPUP_TOP_LINE,
SECTION_DIVIDER_LINE,
)
from zulipterminal.config.ui_mappings import (
Expand All @@ -34,7 +36,6 @@
STREAM_ACCESS_TYPE,
STREAM_POST_POLICY,
)
from zulipterminal.config.ui_sizes import LEFT_WIDTH
from zulipterminal.helper import (
TidiedUserInfo,
asynch,
Expand Down Expand Up @@ -949,6 +950,26 @@ def __init__(self, text: str) -> None:
PopUpViewTableContent = Sequence[Tuple[str, Sequence[Union[str, Tuple[str, Any]]]]]


class PopUpFrame(urwid.WidgetDecoration, urwid.WidgetWrap):
def __init__(self, body: Any, title: Optional[str], style: str) -> None:
content = urwid.LineBox(body, **POPUP_CONTENT_BORDER)

if title is not None:
text = urwid.Text(title, align="center")
title_map = urwid.AttrMap(urwid.Filler(text), style)
title_box_adapter = urwid.BoxAdapter(title_map, height=1)
title_top = urwid.AttrMap(urwid.Divider(POPUP_TOP_LINE), "popup_border")
frame_title = urwid.Pile([title_top, title_box_adapter])
titled_content = urwid.Frame(body=content, header=frame_title)
else:
titled_content = urwid.Frame(body=content)

styled = urwid.AttrMap(titled_content, "popup_border")

urwid.WidgetDecoration.__init__(self, body)
urwid.WidgetWrap.__init__(self, styled)


class PopUpView(urwid.Frame):
def __init__(
self,
Expand All @@ -957,6 +978,7 @@ def __init__(
command: str,
requested_width: int,
title: str,
*,
header: Optional[Any] = None,
footer: Optional[Any] = None,
) -> None:
Expand Down Expand Up @@ -1294,19 +1316,17 @@ def __init__(self, controller: Any, title: str) -> None:
[("", rendered_menu_content)], column_widths
)

super().__init__(controller, body, "MARKDOWN_HELP", popup_width, title, header)


PopUpConfirmationViewLocation = Literal["top-left", "center"]
super().__init__(
controller, body, "MARKDOWN_HELP", popup_width, title, header=header
)


class PopUpConfirmationView(urwid.Overlay):
class PopUpConfirmationView(urwid.Frame):
def __init__(
self,
controller: Any,
question: Any,
success_callback: Callable[[], None],
location: PopUpConfirmationViewLocation = "top-left",
) -> None:
self.controller = controller
self.success_callback = success_callback
Expand All @@ -1317,32 +1337,18 @@ def __init__(
display_widget = urwid.GridFlow([yes, no], 3, 5, 1, "center")
wrapped_widget = urwid.WidgetWrap(display_widget)
widgets = [question, urwid.Divider(), wrapped_widget]
prompt = urwid.LineBox(urwid.ListBox(urwid.SimpleFocusListWalker(widgets)))
prompt = urwid.ListBox(urwid.SimpleFocusListWalker(widgets))

if location == "top-left":
align = "left"
valign = "top"
width = LEFT_WIDTH + 1
height = 8
else:
align = "center"
valign = "middle"

max_cols, max_rows = controller.maximum_popup_dimensions()
# +2 to compensate for the LineBox characters.
width = min(max_cols, max(question.pack()[0], len("Yes"), len("No"))) + 2
height = min(max_rows, sum(widget.rows((width,)) for widget in widgets)) + 2

urwid.Overlay.__init__(
self,
prompt,
self.controller.view,
align=align,
valign=valign,
width=width,
height=height,
self.title = None

max_cols, max_rows = controller.maximum_popup_dimensions()
self.width = min(max_cols, max(question.pack()[0], len("Yes"), len("No")))
self.height = min(
max_rows, sum(widget.rows((self.width,)) for widget in widgets)
)

super().__init__(prompt)

def exit_popup_yes(self, args: Any) -> None:
self.success_callback()
self.controller.exit_popup()
Expand Down Expand Up @@ -1932,8 +1938,8 @@ def __init__(
"MSG_INFO",
max_cols,
title,
urwid.Pile(msg_box.header),
urwid.Pile(msg_box.footer),
header=urwid.Pile(msg_box.header),
footer=urwid.Pile(msg_box.footer),
)

def keypress(self, size: urwid_Size, key: str) -> str:
Expand Down Expand Up @@ -1984,8 +1990,8 @@ def __init__(
"MSG_INFO",
max_cols,
title,
urwid.Pile(msg_box.header),
urwid.Pile(msg_box.footer),
header=urwid.Pile(msg_box.header),
footer=urwid.Pile(msg_box.footer),
)

def keypress(self, size: urwid_Size, key: str) -> str:
Expand Down