make changes more extensible and less intrusive

This commit is contained in:
Stilian Iliev 2025-12-29 22:14:12 +02:00
parent 68ccc3c136
commit 33c17fd238
2 changed files with 76 additions and 43 deletions

View File

@ -237,7 +237,7 @@ class CommandDispatcher:
else:
old_selection_behavior = tabbar.selectionBehaviorOnRemove()
tabbar.setSelectionBehaviorOnRemove(selection_override)
self._tabbed_browser.close_tab(tab, allow_firefox_behavior=False)
self._tabbed_browser.close_tab(tab, allow_selection_strategy=False)
tabbar.setSelectionBehaviorOnRemove(old_selection_behavior)
@cmdutils.register(instance='command-dispatcher', scope='window')

View File

@ -135,6 +135,62 @@ class TabDeletedError(Exception):
"""Exception raised when _tab_index is called for a deleted tab."""
class SelectionStrategy:
"""Base class for tab selection strategies (on remove)."""
def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab,
related: bool, background: bool) -> None:
"""Called when a new tab is opened."""
def on_current_changed(self, tabbed_browser: "TabbedBrowser",
tab: browsertab.AbstractTab) -> None:
"""Called when the current tab changes."""
def should_select_parent(self, tabbed_browser: "TabbedBrowser",
tab: browsertab.AbstractTab) -> bool:
"""Return True if we should select the parent/opener instead of default behavior."""
return False
class FirefoxSelectionStrategy(SelectionStrategy):
"""Strategy implementing Firefox-like "return to parent" behavior."""
def __init__(self) -> None:
self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None
def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab,
related: bool, background: bool) -> None:
# Track opened tab
if tabbed_browser.widget.count() > 0:
if self._opened_tab is not None and background:
self._opened_tab = None
else:
self._opened_tab = weakref.ref(tab)
def on_current_changed(self, tabbed_browser: "TabbedBrowser",
tab: browsertab.AbstractTab) -> None:
# Clear state if user switched to a tab that's not the opened tab
if self._opened_tab is not None:
opened = self._opened_tab()
if tab is not opened:
# User navigated to a third tab, forget opened tab
self._opened_tab = None
def should_select_parent(self, tabbed_browser: "TabbedBrowser",
tab: browsertab.AbstractTab) -> bool:
if self._opened_tab is None:
return False
opened = self._opened_tab()
if opened is tab:
self._opened_tab = None # Consume state
return True
return False
class TabbedBrowser(QWidget):
"""A TabWidget with QWebViews inside.
@ -242,8 +298,8 @@ class TabbedBrowser(QWidget):
self.default_window_icon = self._window().windowIcon()
self.is_private = private
self.tab_deque = TabDeque()
# Last opened tab tracking for tabs.select_on_remove = 'firefox'
self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None
self._selection_strategy: SelectionStrategy = SelectionStrategy()
self._update_selection_strategy()
config.instance.changed.connect(self._on_config_changed)
quitter.instance.shutting_down.connect(self.shutdown)
@ -254,6 +310,16 @@ class TabbedBrowser(QWidget):
# We can't resize a collections.deque so just recreate it >:(
self.undo_stack = collections.deque(self.undo_stack, maxlen=newsize)
def _update_selection_strategy(self):
"""Update the selection strategy based on config."""
strategy = config.val.tabs.select_on_remove
if strategy == 'firefox':
if not isinstance(self._selection_strategy, FirefoxSelectionStrategy):
self._selection_strategy = FirefoxSelectionStrategy()
else:
if isinstance(self._selection_strategy, FirefoxSelectionStrategy):
self._selection_strategy = SelectionStrategy()
def __repr__(self):
return utils.get_repr(self, count=self.widget.count())
@ -269,6 +335,8 @@ class TabbedBrowser(QWidget):
self.widget.update_tab_titles()
elif option == "tabs.focus_stack_size":
self.tab_deque.update_size()
elif option == "tabs.select_on_remove":
self._update_selection_strategy()
def _tab_index(self, tab):
"""Get the index of a given tab.
@ -446,32 +514,8 @@ class TabbedBrowser(QWidget):
else:
yes_action()
def _should_select_opener(self, tab):
"""Check if we should select the opener tab (behave like last-used).
Args:
tab: The tab that is about to be closed.
Return:
True if we should fall back to 'last-used' behavior to select the opener.
False if we should stick to the default (next).
"""
# Only apply if config is 'firefox' mode
if config.val.tabs.select_on_remove != 'firefox':
return False
if self._opened_tab is None:
return False
opened = self._opened_tab()
if opened is tab:
self._opened_tab = None # Consume state
return True
return False
def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False,
allow_firefox_behavior=True):
allow_selection_strategy=True):
"""Close a tab.
Args:
@ -479,7 +523,7 @@ class TabbedBrowser(QWidget):
add_undo: Whether the tab close can be undone.
new_undo: Whether the undo entry should be a new item in the stack.
transfer: Whether the tab is closing because it is moving to a new window.
allow_firefox_behavior: Whether to try selecting the 'opener' tab (if configured).
allow_selection_strategy: Whether to try selecting the 'opener' tab (if configured).
"""
if config.val.tabs.tabs_are_windows or transfer:
last_close = 'close'
@ -491,11 +535,9 @@ class TabbedBrowser(QWidget):
if last_close == 'ignore' and count == 1:
return
# Handle 'firefox' selection mode
restore_behavior = None
if allow_firefox_behavior and self._should_select_opener(tab):
if allow_selection_strategy and self._selection_strategy.should_select_parent(self, tab):
# Temporarily switch to 'last-used' behavior, which will select the 'opener'
# since we verified the relationship and navigation state.
tabbar = self.widget.tab_bar()
restore_behavior = tabbar.selectionBehaviorOnRemove()
tabbar.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectPreviousTab)
@ -700,12 +742,7 @@ class TabbedBrowser(QWidget):
if background is None:
background = config.val.tabs.background
# Track opened tab for tabs.select_on_remove = 'firefox'
if self.widget.count() > 0:
if self._opened_tab is not None and background:
self._opened_tab = None
else:
self._opened_tab = weakref.ref(tab)
self._selection_strategy.on_tab_opened(self, tab, related, background)
if background:
# Make sure the background tab has the correct initial size.
@ -953,11 +990,7 @@ class TabbedBrowser(QWidget):
return
# Clear state if user switched to a tab that's not the opened tab
if self._opened_tab is not None:
opened = self._opened_tab()
if tab is not opened:
# User navigated to a third tab, forget opened tab
self._opened_tab = None
self._selection_strategy.on_current_changed(self, tab)
log.modes.debug("Current tab changed, focusing {!r}".format(tab))
tab.setFocus()