Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions menubar.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ class AppDelegate(NSObject):
_history_entries_cache = objc.ivar()
_history_entries_cache_fingerprint = objc.ivar()
_quota_notifier = objc.ivar()
_switch_menu_action_taken = objc.ivar()
language = objc.ivar()

def initWithMock_interval_(self, mock: bool, interval: int) -> AppDelegate:
Expand All @@ -359,6 +360,7 @@ def initWithMock_interval_(self, mock: bool, interval: int) -> AppDelegate:
self._fs_stream = None
self._history_entries_cache = None
self._history_entries_cache_fingerprint = None
self._switch_menu_action_taken = False
return self

def applicationDidFinishLaunching_(self, notification: Any) -> None:
Expand Down Expand Up @@ -518,13 +520,20 @@ def switchPanel_(self, sender: Any) -> None:
butler_item.setState_(1 if _session_resume_enabled() else 0)
butler_item.setToolTip_(_t(self.language, "project_butler_tooltip"))
menu.addItem_(butler_item)
self._switch_menu_action_taken = False
menu.popUpMenuPositioningItem_atLocation_inView_(None, NSMakePoint(0, 0), sender)
if self._switch_menu_action_taken:
self._resync_popover_after_menu()
else:
self._close_popover_after_menu()

def selectPanel_(self, sender: Any) -> None:
self._mark_switch_menu_action()
panel_id = str(sender.representedObject())
self._set_active_panel_id(panel_id)

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

def toggleAutoUpdateCheck_(self, sender: Any) -> None:
self._mark_switch_menu_action()
prefs = _load_preferences()
enabled = not _auto_update_check_enabled(prefs)
prefs["auto_update_check"] = enabled
Expand All @@ -550,6 +560,7 @@ def toggleAutoUpdateCheck_(self, sender: Any) -> None:
thread.start()

def toggleHideCodex_(self, sender: Any) -> None:
self._mark_switch_menu_action()
prefs = _load_preferences()
enabled = not _hide_codex_enabled(prefs)
prefs["hide_codex_section"] = enabled
Expand All @@ -560,6 +571,7 @@ def toggleHideCodex_(self, sender: Any) -> None:
self.popover_controller.setState_(self.latest_state)

def toggleQuotaNotifications_(self, sender: Any) -> None:
self._mark_switch_menu_action()
prefs = _load_preferences()
enabled = not _quota_notifications_enabled(prefs)
prefs["quota_notifications"] = enabled
Expand All @@ -570,6 +582,7 @@ def toggleQuotaNotifications_(self, sender: Any) -> None:
self._request_notification_authorization()

def toggleSessionResume_(self, sender: Any) -> None:
self._mark_switch_menu_action()
thread = threading.Thread(target=self._toggle_session_resume_in_background, daemon=True)
thread.start()

Expand Down Expand Up @@ -749,6 +762,28 @@ def _set_active_panel_id(self, panel_id: str) -> None:
NSMinYEdge,
)

def _mark_switch_menu_action(self) -> None:
self._switch_menu_action_taken = True

def _close_popover_after_menu(self) -> None:
if not hasattr(self, "popover") or self.popover is None:
return
if not self.popover.isShown():
return
self.popover.performClose_(None)

def _resync_popover_after_menu(self) -> None:
if not hasattr(self, "popover") or not hasattr(self, "popover_controller"):
return
if not hasattr(self, "status_item"):
return
if self.popover is None or self.popover_controller is None or self.status_item is None:
return
if not self.popover.isShown():
return
self.popover_controller.setState_(self.latest_state)
self.popover.setContentSize_(_popover_size(self.latest_state, self.active_panel))

def togglePopover_(self, sender: Any) -> None:
if self.popover.isShown():
self.popover.performClose_(sender)
Expand Down
75 changes: 75 additions & 0 deletions tests/test_menubar.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,81 @@ def test_switch_panel_menu_contains_update_items(monkeypatch: pytest.MonkeyPatch
assert "Show in report" not in main_titles


def test_switch_panel_cancel_closes_visible_popover(
monkeypatch: pytest.MonkeyPatch,
) -> None:
class FakeController:
def __init__(self) -> None:
self.states: list[object] = []

def setState_(self, state: object) -> None:
self.states.append(state)

class FakeButton:
def bounds(self) -> str:
return "button-bounds"

class FakeStatusItem:
def __init__(self) -> None:
self._button = FakeButton()

def button(self) -> FakeButton:
return self._button

class FakePopover:
def __init__(self) -> None:
self.closed = 0
self.sizes: list[object] = []
self.shown: list[tuple[object, object, object]] = []

def isShown(self) -> bool:
return True

def performClose_(self, sender: object) -> None:
self.closed += 1

def setContentSize_(self, size: object) -> None:
self.sizes.append(size)

def showRelativeToRect_ofView_preferredEdge_(
self,
rect: object,
view: object,
edge: object,
) -> None:
self.shown.append((rect, view, edge))

class FakePanel:
id = "classic"
codex_card_height = 0.0

def preferred_size(self) -> tuple[float, float]:
return (300.0, 400.0)

delegate = menubar.AppDelegate.alloc().initWithMock_interval_(True, 60)
delegate.language = "en"
delegate.latest_state = menubar._empty_state(language="en")
delegate.active_panel = FakePanel()
delegate.popover_controller = FakeController()
delegate.popover = FakePopover()
delegate.status_item = FakeStatusItem()

monkeypatch.setattr(menubar, "NSMenu", _FakeMenu)
monkeypatch.setattr(menubar, "NSMenuItem", _FakeMenuItem)
monkeypatch.setattr(
"menubar.panels.all_panels",
lambda: [SimpleNamespace(id="classic", i18n_key="panel_default_name")],
)
monkeypatch.setattr("menubar.login_item.is_enabled", lambda: False)

menubar.AppDelegate.switchPanel_(delegate, object())

assert delegate.popover.closed == 1
assert delegate.popover_controller.states == []
assert delegate.popover.sizes == []
assert delegate.popover.shown == []


def test_auto_update_disabled_skips_background_check(monkeypatch: pytest.MonkeyPatch) -> None:
called = False

Expand Down