summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJay Kamat <jaygkamat@gmail.com>2018-09-29 12:29:50 -0700
committerJay Kamat <jaygkamat@gmail.com>2018-09-29 12:29:50 -0700
commit322b053cbf3d3118e89568ab5793147943af64f2 (patch)
tree7e018ad601fa76389302572b3bca6a6ed301942d
parenta5f9115b2f13305a31bc7e0e6236211d9696498e (diff)
parent2630f779cdbeadaf0438718dd27bcd975deccd7f (diff)
downloadqutebrowser-322b053cbf3d3118e89568ab5793147943af64f2.tar.gz
qutebrowser-322b053cbf3d3118e89568ab5793147943af64f2.zip
Merge branch 'master' of https://github.com/qutebrowser/qutebrowser into jay/visible-update-titles
-rw-r--r--doc/changelog.asciidoc9
-rw-r--r--doc/help/commands.asciidoc15
-rw-r--r--doc/help/settings.asciidoc38
-rw-r--r--qutebrowser/browser/browsertab.py28
-rw-r--r--qutebrowser/browser/commands.py20
-rw-r--r--qutebrowser/browser/shared.py5
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py54
-rw-r--r--qutebrowser/browser/webkit/webkittab.py18
-rw-r--r--qutebrowser/config/config.py14
-rw-r--r--qutebrowser/config/configdata.yml40
-rw-r--r--qutebrowser/config/configfiles.py2
-rw-r--r--qutebrowser/config/configtypes.py118
-rw-r--r--qutebrowser/mainwindow/tabwidget.py3
-rw-r--r--tests/helpers/stubs.py2
-rw-r--r--tests/unit/config/test_config.py11
-rw-r--r--tests/unit/config/test_configtypes.py7
-rw-r--r--tests/unit/mainwindow/test_tabwidget.py14
17 files changed, 323 insertions, 75 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index c8bfe5258..28992a1ea 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -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
~~~~~
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 99d8b1e33..9632f6331 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -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.
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index be3ceae77..962e9ebae 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -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).
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index 0eb9b6493..eb67cf091 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -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, override: bool = False):
+ """Set this tab as muted or not.
- def set_muted(self, muted: bool):
- """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."""
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 6cdbc0246..d4e896cf7 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -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)
diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py
index 2398ca2e4..eb222cbe8 100644
--- a/qutebrowser/browser/shared.py
+++ b/qutebrowser/browser/shared.py
@@ -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
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index 36ac2a99a..3b72719e7 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -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
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index 7f0740b65..2edea1777 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -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)
diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py
index 98f2c67b3..facfcc553 100644
--- a/qutebrowser/config/config.py
+++ b/qutebrowser/config/config.py
@@ -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):
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index c4a61e203..ae9351899 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -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
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index f74c44f32..b4c8ea4ec 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -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')
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 1c3bff8fb..f4857fc36 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -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:
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 69f0b1b92..5a70304db 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -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
diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py
index 9af7a6fcc..173135d73 100644
--- a/tests/helpers/stubs.py
+++ b/tests/helpers/stubs.py
@@ -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)
diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py
index bf4f7c02d..6cef14130 100644
--- a/tests/unit/config/test_config.py
+++ b/tests/unit/config/test_config.py
@@ -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."""
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 46119be7e..ee5223a38 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -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) == ''
diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py
index bc5ee9deb..24acb6d87 100644
--- a/tests/unit/mainwindow/test_tabwidget.py
+++ b/tests/unit/mainwindow/test_tabwidget.py
@@ -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,