Skip to content

Commit b95a5a6

Browse files
authored
fix: close popover when switch menu is cancelled (aqua5230#26)
Co-authored-by: ericweichun <ericweichun@users.noreply.github.com>
1 parent d840586 commit b95a5a6

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

menubar.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ class AppDelegate(NSObject):
333333
_history_entries_cache = objc.ivar()
334334
_history_entries_cache_fingerprint = objc.ivar()
335335
_quota_notifier = objc.ivar()
336+
_switch_menu_action_taken = objc.ivar()
336337
language = objc.ivar()
337338

338339
def initWithMock_interval_(self, mock: bool, interval: int) -> AppDelegate:
@@ -359,6 +360,7 @@ def initWithMock_interval_(self, mock: bool, interval: int) -> AppDelegate:
359360
self._fs_stream = None
360361
self._history_entries_cache = None
361362
self._history_entries_cache_fingerprint = None
363+
self._switch_menu_action_taken = False
362364
return self
363365

364366
def applicationDidFinishLaunching_(self, notification: Any) -> None:
@@ -518,13 +520,20 @@ def switchPanel_(self, sender: Any) -> None:
518520
butler_item.setState_(1 if _session_resume_enabled() else 0)
519521
butler_item.setToolTip_(_t(self.language, "project_butler_tooltip"))
520522
menu.addItem_(butler_item)
523+
self._switch_menu_action_taken = False
521524
menu.popUpMenuPositioningItem_atLocation_inView_(None, NSMakePoint(0, 0), sender)
525+
if self._switch_menu_action_taken:
526+
self._resync_popover_after_menu()
527+
else:
528+
self._close_popover_after_menu()
522529

523530
def selectPanel_(self, sender: Any) -> None:
531+
self._mark_switch_menu_action()
524532
panel_id = str(sender.representedObject())
525533
self._set_active_panel_id(panel_id)
526534

527535
def toggleLaunchAtLogin_(self, sender: Any) -> None:
536+
self._mark_switch_menu_action()
528537
try:
529538
if login_item.is_enabled():
530539
login_item.disable()
@@ -535,6 +544,7 @@ def toggleLaunchAtLogin_(self, sender: Any) -> None:
535544
logger.warning("toggle launch at login failed", exc_info=True)
536545

537546
def toggleAutoUpdateCheck_(self, sender: Any) -> None:
547+
self._mark_switch_menu_action()
538548
prefs = _load_preferences()
539549
enabled = not _auto_update_check_enabled(prefs)
540550
prefs["auto_update_check"] = enabled
@@ -550,6 +560,7 @@ def toggleAutoUpdateCheck_(self, sender: Any) -> None:
550560
thread.start()
551561

552562
def toggleHideCodex_(self, sender: Any) -> None:
563+
self._mark_switch_menu_action()
553564
prefs = _load_preferences()
554565
enabled = not _hide_codex_enabled(prefs)
555566
prefs["hide_codex_section"] = enabled
@@ -560,6 +571,7 @@ def toggleHideCodex_(self, sender: Any) -> None:
560571
self.popover_controller.setState_(self.latest_state)
561572

562573
def toggleQuotaNotifications_(self, sender: Any) -> None:
574+
self._mark_switch_menu_action()
563575
prefs = _load_preferences()
564576
enabled = not _quota_notifications_enabled(prefs)
565577
prefs["quota_notifications"] = enabled
@@ -570,6 +582,7 @@ def toggleQuotaNotifications_(self, sender: Any) -> None:
570582
self._request_notification_authorization()
571583

572584
def toggleSessionResume_(self, sender: Any) -> None:
585+
self._mark_switch_menu_action()
573586
thread = threading.Thread(target=self._toggle_session_resume_in_background, daemon=True)
574587
thread.start()
575588

@@ -749,6 +762,28 @@ def _set_active_panel_id(self, panel_id: str) -> None:
749762
NSMinYEdge,
750763
)
751764

765+
def _mark_switch_menu_action(self) -> None:
766+
self._switch_menu_action_taken = True
767+
768+
def _close_popover_after_menu(self) -> None:
769+
if not hasattr(self, "popover") or self.popover is None:
770+
return
771+
if not self.popover.isShown():
772+
return
773+
self.popover.performClose_(None)
774+
775+
def _resync_popover_after_menu(self) -> None:
776+
if not hasattr(self, "popover") or not hasattr(self, "popover_controller"):
777+
return
778+
if not hasattr(self, "status_item"):
779+
return
780+
if self.popover is None or self.popover_controller is None or self.status_item is None:
781+
return
782+
if not self.popover.isShown():
783+
return
784+
self.popover_controller.setState_(self.latest_state)
785+
self.popover.setContentSize_(_popover_size(self.latest_state, self.active_panel))
786+
752787
def togglePopover_(self, sender: Any) -> None:
753788
if self.popover.isShown():
754789
self.popover.performClose_(sender)

tests/test_menubar.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,81 @@ def test_switch_panel_menu_contains_update_items(monkeypatch: pytest.MonkeyPatch
416416
assert "Show in report" not in main_titles
417417

418418

419+
def test_switch_panel_cancel_closes_visible_popover(
420+
monkeypatch: pytest.MonkeyPatch,
421+
) -> None:
422+
class FakeController:
423+
def __init__(self) -> None:
424+
self.states: list[object] = []
425+
426+
def setState_(self, state: object) -> None:
427+
self.states.append(state)
428+
429+
class FakeButton:
430+
def bounds(self) -> str:
431+
return "button-bounds"
432+
433+
class FakeStatusItem:
434+
def __init__(self) -> None:
435+
self._button = FakeButton()
436+
437+
def button(self) -> FakeButton:
438+
return self._button
439+
440+
class FakePopover:
441+
def __init__(self) -> None:
442+
self.closed = 0
443+
self.sizes: list[object] = []
444+
self.shown: list[tuple[object, object, object]] = []
445+
446+
def isShown(self) -> bool:
447+
return True
448+
449+
def performClose_(self, sender: object) -> None:
450+
self.closed += 1
451+
452+
def setContentSize_(self, size: object) -> None:
453+
self.sizes.append(size)
454+
455+
def showRelativeToRect_ofView_preferredEdge_(
456+
self,
457+
rect: object,
458+
view: object,
459+
edge: object,
460+
) -> None:
461+
self.shown.append((rect, view, edge))
462+
463+
class FakePanel:
464+
id = "classic"
465+
codex_card_height = 0.0
466+
467+
def preferred_size(self) -> tuple[float, float]:
468+
return (300.0, 400.0)
469+
470+
delegate = menubar.AppDelegate.alloc().initWithMock_interval_(True, 60)
471+
delegate.language = "en"
472+
delegate.latest_state = menubar._empty_state(language="en")
473+
delegate.active_panel = FakePanel()
474+
delegate.popover_controller = FakeController()
475+
delegate.popover = FakePopover()
476+
delegate.status_item = FakeStatusItem()
477+
478+
monkeypatch.setattr(menubar, "NSMenu", _FakeMenu)
479+
monkeypatch.setattr(menubar, "NSMenuItem", _FakeMenuItem)
480+
monkeypatch.setattr(
481+
"menubar.panels.all_panels",
482+
lambda: [SimpleNamespace(id="classic", i18n_key="panel_default_name")],
483+
)
484+
monkeypatch.setattr("menubar.login_item.is_enabled", lambda: False)
485+
486+
menubar.AppDelegate.switchPanel_(delegate, object())
487+
488+
assert delegate.popover.closed == 1
489+
assert delegate.popover_controller.states == []
490+
assert delegate.popover.sizes == []
491+
assert delegate.popover.shown == []
492+
493+
419494
def test_auto_update_disabled_skips_background_check(monkeypatch: pytest.MonkeyPatch) -> None:
420495
called = False
421496

0 commit comments

Comments
 (0)