Merge branch 'master' of https://github.com/qutebrowser/qutebrowser into jay/visible-update-titles
This commit is contained in:
commit
322b053cbf
@ -43,6 +43,10 @@ Added
|
||||
- Running qutebrowser with QtWebKit or Qt < 5.9 now shows a warning (only
|
||||
once), as support for those is going to be removed in a future release.
|
||||
- New t[iI][hHu] default bindings (similar to `tsh` etc.) to toggle images.
|
||||
- New `tabs.max_width` setting which allows to have a more "normal" look for
|
||||
tabs.
|
||||
- New `content.mute` setting which allows to mute pages (or all tabs) by
|
||||
default.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
@ -73,6 +77,11 @@ Changed
|
||||
- Editing text in an external editor now simulates a JS "input" event, which
|
||||
improves compatibility with websites reacting via JS to input.
|
||||
- The `qute://settings` page is now properly sorted on Python 3.5.
|
||||
- `:zoom`, `:zoom-in` and `:zoom-out` now have a `--quiet` switch which causes
|
||||
them to not display a message.
|
||||
- The `scrolling.bar` setting now takes three values instead of being a
|
||||
boolean: `always`, `never`, and `when-searching` (which only displays it
|
||||
while a search is active).
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
@ -1413,7 +1413,7 @@ Yank something to the clipboard or primary selection.
|
||||
|
||||
[[zoom]]
|
||||
=== zoom
|
||||
Syntax: +:zoom ['zoom']+
|
||||
Syntax: +:zoom [*--quiet*] ['zoom']+
|
||||
|
||||
Set the zoom level for the current tab.
|
||||
|
||||
@ -1422,20 +1422,33 @@ The zoom can be given as argument or as [count]. If neither is given, the zoom i
|
||||
==== positional arguments
|
||||
* +'zoom'+: The zoom percentage to set.
|
||||
|
||||
==== optional arguments
|
||||
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
|
||||
|
||||
==== count
|
||||
The zoom percentage to set.
|
||||
|
||||
[[zoom-in]]
|
||||
=== zoom-in
|
||||
Syntax: +:zoom-in [*--quiet*]+
|
||||
|
||||
Increase the zoom level for the current tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
|
||||
|
||||
==== count
|
||||
How many steps to zoom in.
|
||||
|
||||
[[zoom-out]]
|
||||
=== zoom-out
|
||||
Syntax: +:zoom-out [*--quiet*]+
|
||||
|
||||
Decrease the zoom level for the current tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-q*+, +*--quiet*+: Don't show a zoom level message.
|
||||
|
||||
==== count
|
||||
How many steps to zoom out.
|
||||
|
||||
|
@ -145,6 +145,7 @@
|
||||
|<<content.local_storage,content.local_storage>>|Enable support for HTML 5 local storage and Web SQL.
|
||||
|<<content.media_capture,content.media_capture>>|Allow websites to record audio/video.
|
||||
|<<content.mouse_lock,content.mouse_lock>>|Allow websites to lock your mouse pointer.
|
||||
|<<content.mute,content.mute>>|Automatically mute tabs.
|
||||
|<<content.netrc_file,content.netrc_file>>|Netrc-file for HTTP authentication.
|
||||
|<<content.notifications,content.notifications>>|Allow websites to show notifications.
|
||||
|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser.
|
||||
@ -231,7 +232,7 @@
|
||||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||
|<<qt.low_end_device_mode,qt.low_end_device_mode>>|When to use Chromium's low-end device mode.
|
||||
|<<qt.process_model,qt.process_model>>|Which Chromium process model to use.
|
||||
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
||||
|<<scrolling.bar,scrolling.bar>>|When to show the scrollbar.
|
||||
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
||||
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|
||||
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
|
||||
@ -250,6 +251,7 @@
|
||||
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|
||||
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|
||||
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|
||||
|<<tabs.max_width,tabs.max_width>>|Maximum width (in pixels) of tabs (-1 for no maximum).
|
||||
|<<tabs.min_width,tabs.min_width>>|Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|
||||
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|
||||
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
|
||||
@ -1915,6 +1917,17 @@ On QtWebEngine, this setting requires Qt 5.8 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.mute]]
|
||||
=== content.mute
|
||||
Automatically mute tabs.
|
||||
Note that if the `:tab-mute` command is used, the mute status for the affected tab is now controlled manually, and this setting doesn't have any effect.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[content.netrc_file]]
|
||||
=== content.netrc_file
|
||||
Netrc-file for HTTP authentication.
|
||||
@ -2815,11 +2828,17 @@ This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[scrolling.bar]]
|
||||
=== scrolling.bar
|
||||
Show a scrollbar.
|
||||
When to show the scrollbar.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
Type: <<types,String>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
Valid values:
|
||||
|
||||
* +always+: Always show the scrollbar.
|
||||
* +never+: Never show the scrollbar.
|
||||
* +when-searching+: Show the scrollbar when searching for text in the webpage. With the QtWebKit backend, this is equal to `never`.
|
||||
|
||||
Default: +pass:[when-searching]+
|
||||
|
||||
[[scrolling.smooth]]
|
||||
=== scrolling.smooth
|
||||
@ -3086,6 +3105,17 @@ Valid values:
|
||||
|
||||
Default: +pass:[ignore]+
|
||||
|
||||
[[tabs.max_width]]
|
||||
=== tabs.max_width
|
||||
Maximum width (in pixels) of tabs (-1 for no maximum).
|
||||
This setting only applies when tabs are horizontal.
|
||||
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
|
||||
This setting may not apply properly if max_width is smaller than the minimum size of tab contents, or smaller than tabs.min_width.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[-1]+
|
||||
|
||||
[[tabs.min_width]]
|
||||
=== tabs.min_width
|
||||
Minimum width (in pixels) of tabs (-1 for the default minimum size behavior).
|
||||
|
@ -248,10 +248,19 @@ class AbstractSearch(QObject):
|
||||
this view.
|
||||
_flags: The flags of the last search (needs to be set by subclasses).
|
||||
_widget: The underlying WebView widget.
|
||||
|
||||
Signals:
|
||||
finished: Emitted when a search was finished.
|
||||
arg: True if the text was found, False otherwise.
|
||||
cleared: Emitted when an existing search was cleared.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
finished = pyqtSignal(bool)
|
||||
cleared = pyqtSignal()
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
self.text = None
|
||||
self.search_displayed = False
|
||||
@ -668,20 +677,27 @@ class AbstractAudio(QObject):
|
||||
muted_changed = pyqtSignal(bool)
|
||||
recently_audible_changed = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._widget = None
|
||||
self._tab = tab
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
"""Set this tab as muted or not."""
|
||||
def set_muted(self, muted: bool, override: bool = False):
|
||||
"""Set this tab as muted or not.
|
||||
|
||||
Arguments:
|
||||
override: If set to True, muting/unmuting was done manually and
|
||||
overrides future automatic mute/unmute changes based on
|
||||
the URL.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_muted(self):
|
||||
"""Whether this tab is muted."""
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle_muted(self):
|
||||
self.set_muted(not self.is_muted())
|
||||
def toggle_muted(self, *, override: bool = False):
|
||||
self.set_muted(not self.is_muted(), override=override)
|
||||
|
||||
def is_recently_audible(self):
|
||||
"""Whether this tab has had audio playing recently."""
|
||||
|
@ -870,37 +870,41 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def zoom_in(self, count=1):
|
||||
def zoom_in(self, count=1, quiet=False):
|
||||
"""Increase the zoom level for the current tab.
|
||||
|
||||
Args:
|
||||
count: How many steps to zoom in.
|
||||
quiet: Don't show a zoom level message.
|
||||
"""
|
||||
tab = self._current_widget()
|
||||
try:
|
||||
perc = tab.zoom.offset(count)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
if not quiet:
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def zoom_out(self, count=1):
|
||||
def zoom_out(self, count=1, quiet=False):
|
||||
"""Decrease the zoom level for the current tab.
|
||||
|
||||
Args:
|
||||
count: How many steps to zoom out.
|
||||
quiet: Don't show a zoom level message.
|
||||
"""
|
||||
tab = self._current_widget()
|
||||
try:
|
||||
perc = tab.zoom.offset(-count)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
if not quiet:
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def zoom(self, zoom=None, count=None):
|
||||
def zoom(self, zoom=None, count=None, quiet=False):
|
||||
"""Set the zoom level for the current tab.
|
||||
|
||||
The zoom can be given as argument or as [count]. If neither is
|
||||
@ -910,6 +914,7 @@ class CommandDispatcher:
|
||||
Args:
|
||||
zoom: The zoom percentage to set.
|
||||
count: The zoom percentage to set.
|
||||
quiet: Don't show a zoom level message.
|
||||
"""
|
||||
if zoom is not None:
|
||||
try:
|
||||
@ -927,7 +932,8 @@ class CommandDispatcher:
|
||||
tab.zoom.set_factor(float(level) / 100)
|
||||
except ValueError:
|
||||
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
|
||||
message.info("Zoom level: {}%".format(int(level)), replace=True)
|
||||
if not quiet:
|
||||
message.info("Zoom level: {}%".format(int(level)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_only(self, prev=False, next_=False, force=False):
|
||||
@ -2231,6 +2237,6 @@ class CommandDispatcher:
|
||||
if tab is None:
|
||||
return
|
||||
try:
|
||||
tab.audio.toggle_muted()
|
||||
tab.audio.toggle_muted(override=True)
|
||||
except browsertab.WebTabError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
@ -274,7 +274,7 @@ def get_tab(win_id, target):
|
||||
return tabbed_browser.tabopen(url=None, background=bg_tab)
|
||||
|
||||
|
||||
def get_user_stylesheet():
|
||||
def get_user_stylesheet(searching=False):
|
||||
"""Get the combined user-stylesheet."""
|
||||
css = ''
|
||||
stylesheets = config.val.content.user_stylesheets
|
||||
@ -283,7 +283,8 @@ def get_user_stylesheet():
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
css += f.read()
|
||||
|
||||
if not config.val.scrolling.bar:
|
||||
if (config.val.scrolling.bar == 'never' or
|
||||
config.val.scrolling.bar == 'when-searching' and not searching):
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
|
@ -162,8 +162,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
||||
back yet.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._flags = QWebEnginePage.FindFlags(0)
|
||||
self._pending_searches = 0
|
||||
|
||||
@ -191,8 +191,11 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
||||
flag_text = ''
|
||||
log.webview.debug(' '.join([caller, found_text, text, flag_text])
|
||||
.strip())
|
||||
|
||||
if callback is not None:
|
||||
callback(found)
|
||||
self.finished.emit(found)
|
||||
|
||||
self._widget.findText(text, flags, wrapped_callback)
|
||||
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
@ -213,6 +216,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
||||
self._find(text, self._flags, result_cb, 'search')
|
||||
|
||||
def clear(self):
|
||||
if self.search_displayed:
|
||||
self.cleared.emit()
|
||||
self.search_displayed = False
|
||||
self._widget.findText('')
|
||||
|
||||
@ -637,14 +642,26 @@ class WebEngineElements(browsertab.AbstractElements):
|
||||
|
||||
class WebEngineAudio(browsertab.AbstractAudio):
|
||||
|
||||
"""QtWebEngine implemementations related to audio/muting."""
|
||||
"""QtWebEngine implemementations related to audio/muting.
|
||||
|
||||
Attributes:
|
||||
_overridden: Whether the user toggled muting manually.
|
||||
If that's the case, we leave it alone.
|
||||
"""
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._overridden = False
|
||||
|
||||
def _connect_signals(self):
|
||||
page = self._widget.page()
|
||||
page.audioMutedChanged.connect(self.muted_changed)
|
||||
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
|
||||
self._tab.url_changed.connect(self._on_url_changed)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
def set_muted(self, muted: bool, override: bool = False):
|
||||
self._overridden = override
|
||||
page = self._widget.page()
|
||||
page.setAudioMuted(muted)
|
||||
|
||||
@ -656,6 +673,17 @@ class WebEngineAudio(browsertab.AbstractAudio):
|
||||
page = self._widget.page()
|
||||
return page.recentlyAudible()
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url):
|
||||
if self._overridden:
|
||||
return
|
||||
mute = config.instance.get('content.mute', url=url)
|
||||
self.set_muted(mute)
|
||||
|
||||
@config.change_filter('content.mute')
|
||||
def _on_config_changed(self):
|
||||
self._on_url_changed(self._tab.url())
|
||||
|
||||
|
||||
class _WebEnginePermissions(QObject):
|
||||
|
||||
@ -812,17 +840,23 @@ class _WebEngineScripts(QObject):
|
||||
self._greasemonkey = objreg.get('greasemonkey')
|
||||
|
||||
def connect_signals(self):
|
||||
"""Connect signals to our private slots."""
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
self._tab.search.cleared.connect(functools.partial(
|
||||
self._update_stylesheet, searching=False))
|
||||
self._tab.search.finished.connect(self._update_stylesheet)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
||||
self._init_stylesheet()
|
||||
self._update_stylesheet()
|
||||
|
||||
def _update_stylesheet(self):
|
||||
@pyqtSlot(bool)
|
||||
def _update_stylesheet(self, searching=False):
|
||||
"""Update the custom stylesheet in existing tabs."""
|
||||
css = shared.get_user_stylesheet()
|
||||
css = shared.get_user_stylesheet(searching=searching)
|
||||
code = javascript.assemble('stylesheet', 'set_css', css)
|
||||
self._tab.run_js_async(code)
|
||||
|
||||
@ -991,16 +1025,16 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
private=private, parent=parent)
|
||||
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
|
||||
private=private)
|
||||
self.history = WebEngineHistory(self)
|
||||
self.scroller = WebEngineScroller(self, parent=self)
|
||||
self.history = WebEngineHistory(tab=self)
|
||||
self.scroller = WebEngineScroller(tab=self, parent=self)
|
||||
self.caret = WebEngineCaret(mode_manager=mode_manager,
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||
self.search = WebEngineSearch(parent=self)
|
||||
self.search = WebEngineSearch(tab=self, parent=self)
|
||||
self.printing = WebEnginePrinting(tab=self)
|
||||
self.elements = WebEngineElements(tab=self)
|
||||
self.action = WebEngineAction(tab=self)
|
||||
self.audio = WebEngineAudio(parent=self)
|
||||
self.audio = WebEngineAudio(tab=self, parent=self)
|
||||
self._permissions = _WebEnginePermissions(tab=self, parent=self)
|
||||
self._scripts = _WebEngineScripts(tab=self, parent=self)
|
||||
# We're assigning settings in _set_widget
|
||||
|
@ -84,8 +84,8 @@ class WebKitSearch(browsertab.AbstractSearch):
|
||||
|
||||
"""QtWebKit implementations related to searching on the page."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._flags = QWebPage.FindFlags(0)
|
||||
|
||||
def _call_cb(self, callback, found, text, flags, caller):
|
||||
@ -115,7 +115,11 @@ class WebKitSearch(browsertab.AbstractSearch):
|
||||
if callback is not None:
|
||||
QTimer.singleShot(0, functools.partial(callback, found))
|
||||
|
||||
self.finished.emit(found)
|
||||
|
||||
def clear(self):
|
||||
if self.search_displayed:
|
||||
self.cleared.emit()
|
||||
self.search_displayed = False
|
||||
# We first clear the marked text, then the highlights
|
||||
self._widget.findText('')
|
||||
@ -637,7 +641,7 @@ class WebKitAudio(browsertab.AbstractAudio):
|
||||
|
||||
"""Dummy handling of audio status for QtWebKit."""
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
def set_muted(self, muted: bool, override: bool = False):
|
||||
raise browsertab.WebTabError('Muting is not supported on QtWebKit!')
|
||||
|
||||
def is_muted(self):
|
||||
@ -658,16 +662,16 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
private=private, tab=self)
|
||||
if private:
|
||||
self._make_private(widget)
|
||||
self.history = WebKitHistory(self)
|
||||
self.scroller = WebKitScroller(self, parent=self)
|
||||
self.history = WebKitHistory(tab=self)
|
||||
self.scroller = WebKitScroller(tab=self, parent=self)
|
||||
self.caret = WebKitCaret(mode_manager=mode_manager,
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebKitZoom(tab=self, parent=self)
|
||||
self.search = WebKitSearch(parent=self)
|
||||
self.search = WebKitSearch(tab=self, parent=self)
|
||||
self.printing = WebKitPrinting(tab=self)
|
||||
self.elements = WebKitElements(tab=self)
|
||||
self.action = WebKitAction(tab=self)
|
||||
self.audio = WebKitAudio(parent=self)
|
||||
self.audio = WebKitAudio(tab=self, parent=self)
|
||||
# We're assigning settings in _set_widget
|
||||
self.settings = webkitsettings.WebKitSettings(settings=None)
|
||||
self._set_widget(widget)
|
||||
|
@ -312,10 +312,14 @@ class Config(QObject):
|
||||
name, deleted=deleted, renamed=renamed)
|
||||
raise exception from None
|
||||
|
||||
def get(self, name, url=None):
|
||||
"""Get the given setting converted for Python code."""
|
||||
def get(self, name, url=None, *, fallback=True):
|
||||
"""Get the given setting converted for Python code.
|
||||
|
||||
Args:
|
||||
fallback: Use the global value if there's no URL-specific one.
|
||||
"""
|
||||
opt = self.get_opt(name)
|
||||
obj = self.get_obj(name, url=url)
|
||||
obj = self.get_obj(name, url=url, fallback=fallback)
|
||||
return opt.typ.to_py(obj)
|
||||
|
||||
def _maybe_copy(self, value):
|
||||
@ -329,14 +333,14 @@ class Config(QObject):
|
||||
assert value.__hash__ is not None, value
|
||||
return value
|
||||
|
||||
def get_obj(self, name, *, url=None):
|
||||
def get_obj(self, name, *, url=None, fallback=True):
|
||||
"""Get the given setting as object (for YAML/config.py).
|
||||
|
||||
Note that the returned values are not watched for mutation.
|
||||
If a URL is given, return the value which should be used for that URL.
|
||||
"""
|
||||
self.get_opt(name) # To make sure it exists
|
||||
value = self._values[name].get_for_url(url)
|
||||
value = self._values[name].get_for_url(url, fallback=fallback)
|
||||
return self._maybe_copy(value)
|
||||
|
||||
def get_obj_for_pattern(self, name, *, pattern):
|
||||
|
@ -806,6 +806,17 @@ content.xss_auditing:
|
||||
Suspicious scripts will be blocked and reported in the inspector's
|
||||
JavaScript console.
|
||||
|
||||
content.mute:
|
||||
default: false
|
||||
type: Bool
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Automatically mute tabs.
|
||||
|
||||
Note that if the `:tab-mute` command is used, the mute status for the
|
||||
affected tab is now controlled manually, and this setting doesn't have any
|
||||
effect.
|
||||
|
||||
# emacs: '
|
||||
|
||||
## completion
|
||||
@ -1271,9 +1282,15 @@ prompt.radius:
|
||||
## scrolling
|
||||
|
||||
scrolling.bar:
|
||||
type: Bool
|
||||
default: false
|
||||
desc: Show a scrollbar.
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- always: Always show the scrollbar.
|
||||
- never: Never show the scrollbar.
|
||||
- when-searching: Show the scrollbar when searching for text in the
|
||||
webpage. With the QtWebKit backend, this is equal to `never`.
|
||||
default: when-searching
|
||||
desc: When to show the scrollbar.
|
||||
|
||||
scrolling.smooth:
|
||||
type: Bool
|
||||
@ -1602,6 +1619,23 @@ tabs.min_width:
|
||||
|
||||
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is False.
|
||||
|
||||
tabs.max_width:
|
||||
default: -1
|
||||
type:
|
||||
name: Int
|
||||
minval: -1
|
||||
maxval: maxint
|
||||
desc: >-
|
||||
Maximum width (in pixels) of tabs (-1 for no maximum).
|
||||
|
||||
This setting only applies when tabs are horizontal.
|
||||
|
||||
This setting does not apply to pinned tabs, unless `tabs.pinned.shrink` is
|
||||
False.
|
||||
|
||||
This setting may not apply properly if max_width is smaller than the
|
||||
minimum size of tab contents, or smaller than tabs.min_width.
|
||||
|
||||
tabs.width.indicator:
|
||||
renamed: tabs.indicator.width
|
||||
|
||||
|
@ -292,6 +292,8 @@ class YamlConfig(QObject):
|
||||
self._mark_changed()
|
||||
|
||||
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
|
||||
self._migrate_bool(settings, 'scrolling.bar',
|
||||
'when-searching', 'never')
|
||||
self._migrate_bool(settings, 'qt.force_software_rendering',
|
||||
'software-opengl', 'none')
|
||||
|
||||
|
@ -60,7 +60,7 @@ from PyQt5.QtGui import QColor, QFont
|
||||
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
||||
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.config import configexc
|
||||
from qutebrowser.config import configexc, configutils
|
||||
from qutebrowser.utils import standarddir, utils, qtutils, urlutils, urlmatch
|
||||
from qutebrowser.keyinput import keyutils
|
||||
|
||||
@ -149,6 +149,9 @@ class BaseType:
|
||||
value: The value to check.
|
||||
pytype: A Python type to check the value against.
|
||||
"""
|
||||
if value is configutils.UNSET:
|
||||
return
|
||||
|
||||
if (value is None or (pytype == list and value == []) or
|
||||
(pytype == dict and value == {})):
|
||||
if not self.none_ok:
|
||||
@ -309,7 +312,9 @@ class MappingType(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
self._validate_valid_values(value.lower())
|
||||
return self.MAPPING[value.lower()]
|
||||
@ -367,7 +372,9 @@ class String(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
self._validate_encoding(value)
|
||||
@ -399,7 +406,9 @@ class UniqueCharString(String):
|
||||
|
||||
def to_py(self, value):
|
||||
value = super().to_py(value)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
# Check for duplicate values
|
||||
@ -455,7 +464,9 @@ class List(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, list)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return []
|
||||
|
||||
for val in value:
|
||||
@ -534,6 +545,9 @@ class ListOrValue(BaseType):
|
||||
return value
|
||||
|
||||
def to_py(self, value):
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
|
||||
try:
|
||||
return [self.valtype.to_py(value)]
|
||||
except configexc.ValidationError:
|
||||
@ -577,7 +591,8 @@ class FlagList(List):
|
||||
|
||||
def to_py(self, value):
|
||||
vals = super().to_py(value)
|
||||
self._check_duplicates(vals)
|
||||
if vals is not configutils.UNSET:
|
||||
self._check_duplicates(vals)
|
||||
return vals
|
||||
|
||||
def complete(self):
|
||||
@ -764,7 +779,9 @@ class Perc(_Numeric):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, (float, int, str))
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
if isinstance(value, str):
|
||||
@ -907,7 +924,9 @@ class QtColor(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
color = QColor(value)
|
||||
@ -936,7 +955,9 @@ class QssColor(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
functions = ['rgb', 'rgba', 'hsv', 'hsva', 'qlineargradient',
|
||||
@ -981,7 +1002,9 @@ class Font(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
if not self.font_regex.fullmatch(value): # pragma: no cover
|
||||
@ -1000,7 +1023,9 @@ class FontFamily(Font):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
match = self.font_regex.fullmatch(value)
|
||||
@ -1024,7 +1049,9 @@ class QtFont(Font):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
style_map = {
|
||||
@ -1136,7 +1163,9 @@ class Regex(BaseType):
|
||||
def to_py(self, value):
|
||||
"""Get a compiled regex from either a string or a regex object."""
|
||||
self._basic_py_validation(value, (str, self._regex_type))
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
elif isinstance(value, str):
|
||||
return self._compile_regex(value)
|
||||
@ -1214,7 +1243,9 @@ class Dict(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, dict)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return self._fill_fixed_keys({})
|
||||
|
||||
self._validate_keys(value)
|
||||
@ -1256,7 +1287,9 @@ class File(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
value = os.path.expanduser(value)
|
||||
@ -1282,7 +1315,9 @@ class Directory(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
value = os.path.expandvars(value)
|
||||
value = os.path.expanduser(value)
|
||||
@ -1309,7 +1344,9 @@ class FormatString(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1341,8 +1378,10 @@ class ShellCommand(List):
|
||||
|
||||
def to_py(self, value):
|
||||
value = super().to_py(value)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return []
|
||||
|
||||
if (self.placeholder and
|
||||
'{}' not in ' '.join(value) and
|
||||
@ -1365,7 +1404,9 @@ class Proxy(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1401,7 +1442,9 @@ class SearchEngineUrl(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
if not ('{}' in value or '{0}' in value):
|
||||
@ -1429,7 +1472,9 @@ class FuzzyUrl(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1463,6 +1508,9 @@ class Padding(Dict):
|
||||
|
||||
def to_py(self, value):
|
||||
d = super().to_py(value)
|
||||
if d is configutils.UNSET:
|
||||
return d
|
||||
|
||||
return PaddingValues(**d)
|
||||
|
||||
|
||||
@ -1472,7 +1520,9 @@ class Encoding(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
try:
|
||||
codecs.lookup(value)
|
||||
@ -1529,7 +1579,9 @@ class Url(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
qurl = QUrl.fromUserInput(value)
|
||||
@ -1545,7 +1597,9 @@ class SessionName(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
if value.startswith('_'):
|
||||
raise configexc.ValidationError(value, "may not start with '_'!")
|
||||
@ -1593,8 +1647,10 @@ class ConfirmQuit(FlagList):
|
||||
|
||||
def to_py(self, value):
|
||||
values = super().to_py(value)
|
||||
if not values:
|
||||
if values is configutils.UNSET:
|
||||
return values
|
||||
elif not values:
|
||||
return []
|
||||
|
||||
# Never can't be set with other options
|
||||
if 'never' in values and len(values) > 1:
|
||||
@ -1630,7 +1686,9 @@ class TimestampTemplate(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1654,7 +1712,9 @@ class Key(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1673,7 +1733,9 @@ class UrlPattern(BaseType):
|
||||
|
||||
def to_py(self, value):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
if value is configutils.UNSET:
|
||||
return value
|
||||
elif not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
|
@ -642,6 +642,9 @@ class TabBar(QTabBar):
|
||||
# Qt shrink us down. If for some reason (tests, bugs)
|
||||
# self.width() gives 0, use a sane min of 10 px
|
||||
width = max(self.width(), 10)
|
||||
max_width = config.cache['tabs.max_width']
|
||||
if max_width > 0:
|
||||
width = min(max_width, width)
|
||||
size = QSize(width, height)
|
||||
qtutils.ensure_valid(size)
|
||||
return size
|
||||
|
@ -257,7 +257,7 @@ class FakeWebTab(browsertab.AbstractTab):
|
||||
self.history = FakeWebTabHistory(self, can_go_back=can_go_back,
|
||||
can_go_forward=can_go_forward)
|
||||
self.scroller = FakeWebTabScroller(self, scroll_pos_perc)
|
||||
self.audio = FakeWebTabAudio()
|
||||
self.audio = FakeWebTabAudio(self)
|
||||
wrapped = QWidget()
|
||||
self._layout.wrap(self, wrapped)
|
||||
|
||||
|
@ -480,6 +480,17 @@ class TestConfig:
|
||||
conf.set_obj(name, False, pattern=pattern)
|
||||
assert conf.get(name, url=QUrl('https://example.com/')) is False
|
||||
|
||||
@pytest.mark.parametrize('fallback, expected', [
|
||||
(True, True),
|
||||
(False, configutils.UNSET)
|
||||
])
|
||||
def test_get_for_url_fallback(self, conf, fallback, expected):
|
||||
"""Test conf.get() with an URL and fallback."""
|
||||
value = conf.get('content.javascript.enabled',
|
||||
url=QUrl('https://example.com/'),
|
||||
fallback=fallback)
|
||||
assert value is expected
|
||||
|
||||
@pytest.mark.parametrize('value', [{}, {'normal': {'a': 'nop'}}])
|
||||
def test_get_bindings(self, config_stub, conf, value):
|
||||
"""Test conf.get() with bindings which have missing keys."""
|
||||
|
@ -34,7 +34,7 @@ from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QColor, QFont
|
||||
from PyQt5.QtNetwork import QNetworkProxy
|
||||
|
||||
from qutebrowser.config import configtypes, configexc
|
||||
from qutebrowser.config import configtypes, configexc, configutils
|
||||
from qutebrowser.utils import debug, utils, qtutils, urlmatch
|
||||
from qutebrowser.browser.network import pac
|
||||
from qutebrowser.keyinput import keyutils
|
||||
@ -274,6 +274,11 @@ class TestAll:
|
||||
with pytest.raises(configexc.ValidationError):
|
||||
meth(value)
|
||||
|
||||
@pytest.mark.parametrize('none_ok', [True, False])
|
||||
def test_unset(self, klass, none_ok):
|
||||
typ = klass(none_ok=none_ok)
|
||||
assert typ.to_py(configutils.UNSET) is configutils.UNSET
|
||||
|
||||
def test_to_str_none(self, klass):
|
||||
assert klass().to_str(None) == ''
|
||||
|
||||
|
@ -38,6 +38,7 @@ class TestTabWidget:
|
||||
qtbot.addWidget(w)
|
||||
monkeypatch.setattr(tabwidget.objects, 'backend',
|
||||
usertypes.Backend.QtWebKit)
|
||||
w.show()
|
||||
return w
|
||||
|
||||
def test_small_icon_doesnt_crash(self, widget, qtbot, fake_web_tab):
|
||||
@ -120,6 +121,19 @@ class TestTabWidget:
|
||||
|
||||
benchmark(widget.update_tab_titles)
|
||||
|
||||
def test_tab_min_width(self, widget, fake_web_tab, config_stub, qtbot):
|
||||
widget.addTab(fake_web_tab(), 'foobar')
|
||||
widget.addTab(fake_web_tab(), 'foobar1')
|
||||
min_size = widget.tabBar().tabRect(0).width() + 10
|
||||
config_stub.val.tabs.min_width = min_size
|
||||
assert widget.tabBar().tabRect(0).width() == min_size
|
||||
|
||||
def test_tab_max_width(self, widget, fake_web_tab, config_stub, qtbot):
|
||||
widget.addTab(fake_web_tab(), 'foobar')
|
||||
max_size = widget.tabBar().tabRect(0).width() - 10
|
||||
config_stub.val.tabs.max_width = max_size
|
||||
assert widget.tabBar().tabRect(0).width() == max_size
|
||||
|
||||
@pytest.mark.parametrize("num_tabs", [4, 100])
|
||||
@pytest.mark.parametrize("rev", [True, False])
|
||||
def test_add_remove_tab_benchmark(self, benchmark, widget,
|
||||
|
Loading…
Reference in New Issue
Block a user