Merge branch 'new-config'
This commit is contained in:
commit
624c3a4c27
2
.flake8
2
.flake8
@ -43,6 +43,8 @@ putty-ignore =
|
||||
tests/helpers/fixtures.py : +N806
|
||||
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
|
||||
scripts/dev/ci/appveyor_install.py : +FI53
|
||||
# FIXME:conf
|
||||
tests/unit/completion/test_models.py : +F821
|
||||
copyright-check = True
|
||||
copyright-regexp = # Copyright [\d-]+ .*
|
||||
copyright-min-file-size = 110
|
||||
|
@ -30,6 +30,7 @@ disable=no-self-use,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
ungrouped-imports,
|
||||
suppressed-message,
|
||||
too-many-return-statements,
|
||||
|
@ -193,7 +193,7 @@ Configuration not saved after modifying config.::
|
||||
|
||||
Unable to view flash content.::
|
||||
If you have flash installed for on your system, it's necessary to enable plugins
|
||||
to use the flash plugin. Using the command `:set content allow-plugins true`
|
||||
to use the flash plugin. Using the command `:set content.plugins true`
|
||||
in qutebrowser will enable plugins. Packages for flash should
|
||||
be provided for your platform or it can be obtained from
|
||||
http://get.adobe.com/flashplayer/[Adobe].
|
||||
|
@ -17,6 +17,7 @@ include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
include misc/cheatsheet.svg
|
||||
include qutebrowser/config/configdata.yml
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
|
@ -50,6 +50,7 @@ image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding
|
||||
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
|
||||
* link:FAQ.asciidoc[Frequently asked questions]
|
||||
* link:doc/help/configuring.html[Configuring qutebrowser]
|
||||
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
|
||||
* link:INSTALL.asciidoc[INSTALL]
|
||||
* link:CHANGELOG.asciidoc[Change Log]
|
||||
|
@ -88,7 +88,6 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<undo,undo>>|Re-open a closed tab.
|
||||
|<<view-source,view-source>>|Show the source of the current page in a new tab.
|
||||
|<<window-only,window-only>>|Close all windows except for the current one.
|
||||
|<<wq,wq>>|Save open pages and quit.
|
||||
|<<yank,yank>>|Yank something to the clipboard or primary selection.
|
||||
|<<zoom,zoom>>|Set the zoom level for the current tab.
|
||||
|<<zoom-in,zoom-in>>|Increase the zoom level for the current tab.
|
||||
@ -126,7 +125,8 @@ Bind a key to a command.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
|
||||
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the
|
||||
available modes.
|
||||
|
||||
* +*-f*+, +*--force*+: Rebind the key if it is already bound.
|
||||
|
||||
@ -281,7 +281,7 @@ Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+
|
||||
|
||||
Navigate to a url formed in an external editor.
|
||||
|
||||
The editor which should be launched can be configured via the `general -> editor` config option.
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: URL to edit; defaults to the current page url.
|
||||
@ -338,7 +338,7 @@ Show help about a command or setting.
|
||||
* +'topic'+: The topic to show help for.
|
||||
|
||||
- :__command__ for commands.
|
||||
- __section__\->__option__ for settings.
|
||||
- __section__.__option__ for settings.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
@ -368,7 +368,7 @@ Start hinting.
|
||||
- `normal`: Open the link.
|
||||
- `current`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab (honoring the
|
||||
background-tabs setting).
|
||||
`tabs.background_tabs` setting).
|
||||
- `tab-fg`: Open the link in a new foreground tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
@ -402,13 +402,13 @@ Start hinting.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`,
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
|
||||
* +*-m*+, +*--mode*+: The hinting mode to use.
|
||||
|
||||
- `number`: Use numeric hints.
|
||||
- `letter`: Use the chars in the hints->chars settings.
|
||||
- `letter`: Use the chars in the hints.chars setting.
|
||||
- `word`: Use hint words based on the html elements and the
|
||||
extra words.
|
||||
|
||||
@ -545,7 +545,7 @@ For `increment` and `decrement`, the number to change the URL by. For `up`, the
|
||||
|
||||
[[open]]
|
||||
=== open
|
||||
Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
|
||||
Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
|
||||
['url']+
|
||||
|
||||
Open a URL in the current/[count]th tab.
|
||||
@ -556,7 +556,7 @@ If the URL contains newlines, each line gets opened in its own tab.
|
||||
* +'url'+: The URL to open.
|
||||
|
||||
==== optional arguments
|
||||
* +*-i*+, +*--implicit*+: If opening a new tab, treat the tab as implicit (like clicking on a link).
|
||||
* +*-r*+, +*--related*+: If opening a new tab, position the tab as related to the current one (like clicking on a link).
|
||||
|
||||
* +*-b*+, +*--bg*+: Open in a new background tab.
|
||||
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||
@ -632,8 +632,17 @@ Save the current page as a quickmark.
|
||||
|
||||
[[quit]]
|
||||
=== quit
|
||||
Syntax: +:quit [*--save*] ['session']+
|
||||
|
||||
Quit qutebrowser.
|
||||
|
||||
==== positional arguments
|
||||
* +'session'+: The name of the session to save.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--save*+: When given, save the open windows even if auto_save.session is turned off.
|
||||
|
||||
|
||||
[[record-macro]]
|
||||
=== record-macro
|
||||
Syntax: +:record-macro ['register']+
|
||||
@ -752,7 +761,7 @@ Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-win
|
||||
Save a session.
|
||||
|
||||
==== positional arguments
|
||||
* +'name'+: The name of the session. If not given, the session configured in general -> session-default-name is saved.
|
||||
* +'name'+: The name of the session. If not given, the session configured in session_default_name is saved.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
@ -764,19 +773,18 @@ Save a session.
|
||||
|
||||
[[set]]
|
||||
=== set
|
||||
Syntax: +:set [*--temp*] [*--print*] ['section'] ['option'] ['values' ['values' ...]]+
|
||||
Syntax: +:set [*--temp*] [*--print*] ['option'] ['values' ['values' ...]]+
|
||||
|
||||
Set an option.
|
||||
|
||||
If the option name ends with '?', the value of the option is shown instead. If the option name ends with '!' and it is a boolean value, toggle it.
|
||||
|
||||
==== positional arguments
|
||||
* +'section'+: The section where the option is in.
|
||||
* +'option'+: The name of the option.
|
||||
* +'values'+: The value to set, or the values to cycle through.
|
||||
|
||||
==== optional arguments
|
||||
* +*-t*+, +*--temp*+: Set value temporarily.
|
||||
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
|
||||
* +*-p*+, +*--print*+: Print the value after setting.
|
||||
|
||||
[[set-cmd-text]]
|
||||
@ -843,7 +851,7 @@ Close the current/[count]th tab.
|
||||
==== optional arguments
|
||||
* +*-p*+, +*--prev*+: Force selecting the tab before the current tab.
|
||||
* +*-n*+, +*--next*+: Force selecting the tab after the current tab.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs.select_on_remove'.
|
||||
|
||||
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
|
||||
|
||||
@ -911,7 +919,7 @@ Close all tabs except for the current one.
|
||||
=== tab-pin
|
||||
Pin/Unpin the current/[count]th tab.
|
||||
|
||||
Pinning a tab shrinks it to tabs->pinned-width size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
|
||||
Pinning a tab shrinks it to `tabs.width.pinned` size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
|
||||
|
||||
==== count
|
||||
The tab index to pin or unpin
|
||||
@ -925,13 +933,15 @@ How many tabs to switch back.
|
||||
|
||||
[[unbind]]
|
||||
=== unbind
|
||||
Syntax: +:unbind 'key' ['mode']+
|
||||
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||
|
||||
Unbind a keychain.
|
||||
|
||||
==== positional arguments
|
||||
* +'key'+: The keychain or special key (inside <...>) to unbind.
|
||||
* +'mode'+: A comma-separated list of modes to unbind the key in (default: `normal`).
|
||||
|
||||
==== optional arguments
|
||||
* +*-m*+, +*--mode*+: A mode to unbind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
|
||||
|
||||
|
||||
[[undo]]
|
||||
@ -946,15 +956,6 @@ Show the source of the current page in a new tab.
|
||||
=== window-only
|
||||
Close all windows except for the current one.
|
||||
|
||||
[[wq]]
|
||||
=== wq
|
||||
Syntax: +:wq ['name']+
|
||||
|
||||
Save open pages and quit.
|
||||
|
||||
==== positional arguments
|
||||
* +'name'+: The name of the session.
|
||||
|
||||
[[yank]]
|
||||
=== yank
|
||||
Syntax: +:yank [*--sel*] [*--keep*] ['what']+
|
||||
@ -1043,6 +1044,7 @@ How many steps to zoom out.
|
||||
|<<move-to-start-of-line,move-to-start-of-line>>|Move the cursor or selection to the start of the line.
|
||||
|<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block.
|
||||
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|
||||
|<<nop,nop>>|Do nothing.
|
||||
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|
||||
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|
||||
@ -1290,11 +1292,15 @@ Move the cursor or selection to the start of previous block.
|
||||
==== count
|
||||
How many blocks to move.
|
||||
|
||||
[[nop]]
|
||||
=== nop
|
||||
Do nothing.
|
||||
|
||||
[[open-editor]]
|
||||
=== open-editor
|
||||
Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the `general -> editor` config option.
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
[[prompt-accept]]
|
||||
=== prompt-accept
|
||||
|
180
doc/help/configuring.asciidoc
Normal file
180
doc/help/configuring.asciidoc
Normal file
@ -0,0 +1,180 @@
|
||||
Configuring qutebrowser
|
||||
=======================
|
||||
|
||||
IMPORTANT: qutebrowser's configuration system was completely rewritten in
|
||||
September 2017. This information is not applicable to older releases, and older
|
||||
information elsewhere might be outdated. **If you had an old configuration
|
||||
around and upgraded, this page will automatically open once**. To view it at a
|
||||
later time, use the `:help` command.
|
||||
|
||||
Migrating older configurations
|
||||
------------------------------
|
||||
|
||||
qutebrowser does no automatic migration for the new configuration. However,
|
||||
there's a special link:qute://configdiff[config diff page] which will show you
|
||||
the changes you did in your old configuration, compared to the old defaults.
|
||||
|
||||
Configuring qutebrowser via the user interface
|
||||
----------------------------------------------
|
||||
|
||||
The easy (but less flexible) way to configure qutebrowser is using its user
|
||||
interface or command line. Changes you make this way are immediately active
|
||||
(with the exception of a few settings, where this is pointed out in the
|
||||
documentation) and are persisted in an `autoconfig.yml` file.
|
||||
|
||||
Using the link:commands.html#set[`:set`] command and command completion, you
|
||||
can quickly set settings interactively, for example `:set tabs.position left`.
|
||||
|
||||
To get more help about a setting, use e.g. `:help tabs.position`.
|
||||
|
||||
If you want to customize many settings, you can open the link:qute://settings[]
|
||||
page by running `:set` without any arguments, where all settings are listed and
|
||||
customizable.
|
||||
|
||||
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
|
||||
link:commands.html#unbind[`:unbind`] commands:
|
||||
|
||||
- Binding a key: `:bind ,v spawn mpv {url}`
|
||||
- Unbinding: `:unbind ,v`
|
||||
- Changing an existing binding: `bind --force ,v message-info foo`
|
||||
|
||||
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
||||
will never be used in a default keybinding.
|
||||
|
||||
The `autoconfig.yml` file is located in the "config" folder listed on the
|
||||
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
|
||||
different from where hand-written config files are kept.
|
||||
|
||||
**Do not** edit `autoconfig.yml` by hand. Instead, see the next section.
|
||||
|
||||
Configuring qutebrowser via config.py
|
||||
-------------------------------------
|
||||
|
||||
For more powerful configuration possibilities, you can create a `config.py`
|
||||
file. Since it's a Python file, you have much more flexibility for
|
||||
configuration. Note that qutebrowser will never touch this file - this means
|
||||
you'll be responsible for updating it when upgrading to a newer qutebrowser
|
||||
version.
|
||||
|
||||
The file should be located in the "config" location listed on
|
||||
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
|
||||
Linux, `~/.qutebrowser/config.py` on macOS, and
|
||||
`%APPDATA%/qutebrowser/config.py` on Windows.
|
||||
|
||||
Two global objects are pre-defined when executing the file: `c` and `config`.
|
||||
|
||||
Changing settings
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
`c` is a shorthand object to easily set settings like this:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
c.tabs.position = "left"
|
||||
c.completion.shrink = True
|
||||
----
|
||||
|
||||
See the link:settings.html[settings help page] for all available settings. The
|
||||
accepted values depend on the type of the option. Commonly used are:
|
||||
|
||||
- Strings: `c.tabs.position = "left"`
|
||||
- Booleans: `c.completion.shrink = True`
|
||||
- Integers: `c.messages.timeout = 5000`
|
||||
- Dictionaries:
|
||||
* `c.headers.custom = {'X-Hello': 'World'}` to override any other values in the
|
||||
dictionary.
|
||||
* `c.aliases['foo'] = ':message-info foo'` to add a single value.
|
||||
- Lists:
|
||||
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
|
||||
previous elements.
|
||||
* `c.url.start_pages.append("https://www.python.org/")` to add a new value.
|
||||
|
||||
Any other config types (e.g. a color) are specified as a string, with the
|
||||
exception of the `Regex` type which can take either a string (with an `r` prefix
|
||||
to preserve backslashes) or a Python regex object:
|
||||
|
||||
- `c.hints.next_regexes.append(r'\bvor\b')`
|
||||
- `c.hints.prev_regexes.append(re.compile(r'\bzurück\b'))`
|
||||
|
||||
If you want to read a setting, you can use the `c` object to do so as well:
|
||||
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
|
||||
|
||||
|
||||
Using strings for setting names
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to set settings based on their name as a string, use the
|
||||
`config.set` method:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.set('content.javascript.enabled', False)
|
||||
----
|
||||
|
||||
To read a setting, use the `config.get` method:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
color = config.get('colors.completion.fg')
|
||||
----
|
||||
|
||||
Binding keys
|
||||
~~~~~~~~~~~~
|
||||
|
||||
While it's possible to change the `bindings.commands` setting to bind keys, it's
|
||||
preferred to use the `config.bind` command. Doing so ensures the commands are
|
||||
valid and normalizes different expressions which map to the same key.
|
||||
|
||||
For details on how to specify keys and the available modes, see the
|
||||
link:settings.html#bindings.commands[documentation] for the `bindings.commands`
|
||||
setting.
|
||||
|
||||
To bind a key:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.bind(',v', 'spawn mpv {url}', mode='normal')
|
||||
----
|
||||
|
||||
If the key is already bound, `force=True` needs to be given to rebind it:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.bind(',v', 'message-info foo', mode='normal', force=True)
|
||||
----
|
||||
|
||||
To unbind a key (either a key which has been bound before, or a default binding):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.unbind(',v', mode='normal')
|
||||
----
|
||||
|
||||
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
||||
will never be used in a default keybinding.
|
||||
|
||||
To suppress loading of any default keybindings, you can set `c.bindings.defaults
|
||||
= {}`.
|
||||
|
||||
Prevent loading `autoconfig.yml`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want all customization done via `:set`, `:bind` and `:unbind` to be
|
||||
temporary, you can suppress loading `autoconfig.yml` in your `config.py` by
|
||||
doing:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
config.load_autoconfig = False
|
||||
----
|
||||
|
||||
Handling errors
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
If there are errors in your `config.py`, qutebrowser will try to apply as much
|
||||
of it as possible, and show an error dialog before starting.
|
||||
|
||||
qutebrowser tries to display errors which are easy to understand even for people
|
||||
who are not used to writing Python. If you see a config error which you find
|
||||
confusing or you think qutebrowser could handle better, please
|
||||
https://github.com/qutebrowser/qutebrowser/issues[open an issue]!
|
@ -10,6 +10,7 @@ The following help pages are currently available:
|
||||
* link:../../FAQ.html[Frequently asked questions]
|
||||
* link:../../CHANGELOG.html[Change Log]
|
||||
* link:commands.html[Documentation of commands]
|
||||
* link:configuring.html[Configuring qutebrowser]
|
||||
* link:settings.html[Documentation of settings]
|
||||
* link:../userscripts.html[How to write userscripts]
|
||||
* link:../../CONTRIBUTING.html[Contributing to qutebrowser]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -44,7 +44,7 @@ show it.
|
||||
*-V*, *--version*::
|
||||
Show version and quit.
|
||||
|
||||
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
|
||||
*-s* 'OPTION' 'VALUE', *--set* 'OPTION' 'VALUE'::
|
||||
Set a temporary setting for this session.
|
||||
|
||||
*-r* 'SESSION', *--restore* 'SESSION'::
|
||||
|
@ -15,7 +15,8 @@ def get_data_files():
|
||||
('../qutebrowser/img', 'img'),
|
||||
('../qutebrowser/javascript', 'javascript'),
|
||||
('../qutebrowser/html/doc', 'html/doc'),
|
||||
('../qutebrowser/git-commit-id', '')
|
||||
('../qutebrowser/git-commit-id', ''),
|
||||
('../qutebrowser/config/configdata.yml', 'config'),
|
||||
]
|
||||
|
||||
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
|
||||
|
@ -23,7 +23,7 @@
|
||||
# If run from qutebrowser as a userscript, it runs :open on the URL
|
||||
# If not, it opens a new qutebrowser window at the URL
|
||||
#
|
||||
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
|
||||
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
|
||||
# :bind o spawn --userscript dmenu_qutebrowser
|
||||
#
|
||||
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window
|
||||
|
@ -12,7 +12,7 @@
|
||||
# - rofi (in a recent version)
|
||||
# - xdg-open and xdg-mime
|
||||
# - You should configure qutebrowser to download files to a single directory
|
||||
# - It comes in handy if you enable remove-finished-downloads. If you want to
|
||||
# - It comes in handy if you enable downloads.remove_finished. If you want to
|
||||
# see the recent downloads, just press "sd".
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
# Opens all links to feeds defined in the head of a site
|
||||
#
|
||||
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
|
||||
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
|
||||
# :bind gF spawn --userscript openfeeds
|
||||
#
|
||||
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open
|
||||
|
@ -15,6 +15,7 @@ markers =
|
||||
end2end: End to end tests which run qutebrowser as subprocess
|
||||
xfail_norun: xfail the test with out running it
|
||||
ci: Tests which should only run on CI.
|
||||
no_ci: Tests which should not run on CI.
|
||||
qtwebengine_todo: Features still missing with QtWebEngine
|
||||
qtwebengine_skip: Tests not applicable with QtWebEngine
|
||||
qtwebkit_skip: Tests not applicable with QtWebKit
|
||||
@ -26,6 +27,7 @@ markers =
|
||||
this: Used to mark tests during development
|
||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
||||
qt55: Tests only running on Qt 5.5 or later
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
@ -50,7 +52,8 @@ qt_log_ignore =
|
||||
^Error when parsing the netrc file
|
||||
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
|
||||
^QPainter::end: Painter ended with \d+ saved states
|
||||
^QSslSocket: cannot resolve SSLv[23]_(client|server)_method
|
||||
^QSslSocket: cannot resolve *
|
||||
^Incompatible version of OpenSSL
|
||||
^QQuickWidget::invalidateRenderControl could not make context current
|
||||
^libpng warning: iCCP: known incorrect sRGB profile
|
||||
xfail_strict = true
|
||||
|
@ -22,7 +22,6 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import configparser
|
||||
import functools
|
||||
import json
|
||||
import shutil
|
||||
@ -44,8 +43,7 @@ import qutebrowser
|
||||
import qutebrowser.resources
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import style, config, websettings, configexc
|
||||
from qutebrowser.config.parsers import keyconf
|
||||
from qutebrowser.config import config, websettings, configexc
|
||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||
downloads)
|
||||
from qutebrowser.browser.network import proxy
|
||||
@ -54,7 +52,7 @@ from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.keyinput import macros
|
||||
from qutebrowser.mainwindow import mainwindow, prompt
|
||||
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
||||
crashsignal, earlyinit, objects, sql)
|
||||
crashsignal, earlyinit, objects, sql, cmdhistory)
|
||||
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
|
||||
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
|
||||
objreg, usertypes, standarddir, error)
|
||||
@ -148,8 +146,6 @@ def init(args, crash_handler):
|
||||
objreg.register('event-filter', event_filter)
|
||||
|
||||
log.init.debug("Connecting signals...")
|
||||
config_obj = objreg.get('config')
|
||||
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||
qApp.focusChanged.connect(on_focus_changed)
|
||||
|
||||
_process_args(args)
|
||||
@ -184,11 +180,10 @@ def _init_icon():
|
||||
|
||||
def _process_args(args):
|
||||
"""Open startpage etc. and process commandline args."""
|
||||
config_obj = objreg.get('config')
|
||||
for sect, opt, val in args.temp_settings:
|
||||
for opt, val in args.temp_settings:
|
||||
try:
|
||||
config_obj.set('temp', sect, opt, val)
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
config.instance.set_str(opt, val)
|
||||
except configexc.Error as e:
|
||||
message.error("set: {} - {}".format(e.__class__.__name__, e))
|
||||
|
||||
if not args.override_restore:
|
||||
@ -274,7 +269,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
if via_ipc and target_arg and target_arg != 'auto':
|
||||
open_target = target_arg
|
||||
else:
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
open_target = config.val.new_instance_open_target
|
||||
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
@ -289,7 +284,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
|
||||
else:
|
||||
background = open_target in ['tab-bg', 'tab-bg-silent']
|
||||
tabbed_browser.tabopen(url, background=background,
|
||||
explicit=True)
|
||||
related=False)
|
||||
|
||||
|
||||
def _open_startpage(win_id=None):
|
||||
@ -309,15 +304,9 @@ def _open_startpage(win_id=None):
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=cur_win_id)
|
||||
if tabbed_browser.count() == 0:
|
||||
log.init.debug("Opening startpage")
|
||||
for urlstr in config.get('general', 'startpage'):
|
||||
try:
|
||||
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
message.error("Error when opening startpage: {}".format(e))
|
||||
tabbed_browser.tabopen(QUrl('about:blank'))
|
||||
else:
|
||||
tabbed_browser.tabopen(url)
|
||||
log.init.debug("Opening start pages")
|
||||
for url in config.val.url.start_pages:
|
||||
tabbed_browser.tabopen(url)
|
||||
|
||||
|
||||
def _open_special_pages(args):
|
||||
@ -335,6 +324,7 @@ def _open_special_pages(args):
|
||||
return
|
||||
|
||||
state_config = objreg.get('state-config')
|
||||
general_sect = state_config['general']
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
|
||||
@ -342,21 +332,32 @@ def _open_special_pages(args):
|
||||
|
||||
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
|
||||
not qtutils.is_qtwebkit_ng())
|
||||
warning_shown = state_config['general'].get('backend-warning-shown') == '1'
|
||||
warning_shown = general_sect.get('backend-warning-shown') == '1'
|
||||
|
||||
if not warning_shown and needs_warning:
|
||||
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
|
||||
background=False)
|
||||
state_config['general']['backend-warning-shown'] = '1'
|
||||
general_sect['backend-warning-shown'] = '1'
|
||||
|
||||
# Quickstart page
|
||||
|
||||
quickstart_done = state_config['general'].get('quickstart-done') == '1'
|
||||
quickstart_done = general_sect.get('quickstart-done') == '1'
|
||||
|
||||
if not quickstart_done:
|
||||
tabbed_browser.tabopen(
|
||||
QUrl('https://www.qutebrowser.org/quickstart.html'))
|
||||
state_config['general']['quickstart-done'] = '1'
|
||||
general_sect['quickstart-done'] = '1'
|
||||
|
||||
# Setting migration page
|
||||
|
||||
needs_migration = os.path.exists(
|
||||
os.path.join(standarddir.config(), 'qutebrowser.conf'))
|
||||
migration_shown = general_sect.get('config-migration-shown') == '1'
|
||||
|
||||
if needs_migration and not migration_shown:
|
||||
tabbed_browser.tabopen(QUrl('qute://help/configuring.html'),
|
||||
background=False)
|
||||
general_sect['config-migration-shown'] = '1'
|
||||
|
||||
|
||||
def _save_version():
|
||||
@ -422,9 +423,6 @@ def _init_modules(args, crash_handler):
|
||||
config.init(qApp)
|
||||
save_manager.init_autosave()
|
||||
|
||||
log.init.debug("Initializing keys...")
|
||||
keyconf.init(qApp)
|
||||
|
||||
log.init.debug("Initializing sql...")
|
||||
try:
|
||||
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
||||
@ -433,6 +431,9 @@ def _init_modules(args, crash_handler):
|
||||
pre_text='Error initializing SQL')
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
|
||||
log.init.debug("Initializing command history...")
|
||||
cmdhistory.init()
|
||||
|
||||
log.init.debug("Initializing web history...")
|
||||
history.init(qApp)
|
||||
|
||||
@ -470,7 +471,7 @@ def _init_modules(args, crash_handler):
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Misc initialization...")
|
||||
if config.get('ui', 'hide-wayland-decoration'):
|
||||
if config.val.window.hide_wayland_decoration:
|
||||
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
|
||||
else:
|
||||
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
|
||||
@ -640,8 +641,25 @@ class Quitter:
|
||||
else:
|
||||
return True
|
||||
|
||||
@cmdutils.register(instance='quitter', name=['quit', 'q'],
|
||||
ignore_args=True)
|
||||
@cmdutils.register(instance='quitter', name='quit')
|
||||
@cmdutils.argument('session', completion=miscmodels.session)
|
||||
def quit(self, save=False, session=None):
|
||||
"""Quit qutebrowser.
|
||||
|
||||
Args:
|
||||
save: When given, save the open windows even if auto_save.session
|
||||
is turned off.
|
||||
session: The name of the session to save.
|
||||
"""
|
||||
if session is not None and not save:
|
||||
raise cmdexc.CommandError("Session name given without --save!")
|
||||
if save:
|
||||
if session is None:
|
||||
session = sessions.default
|
||||
self.shutdown(session=session)
|
||||
else:
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self, status=0, session=None, last_window=False,
|
||||
restart=False):
|
||||
"""Quit qutebrowser.
|
||||
@ -663,7 +681,7 @@ class Quitter:
|
||||
if session is not None:
|
||||
session_manager.save(session, last_window=last_window,
|
||||
load_next_time=True)
|
||||
elif config.get('general', 'save-session'):
|
||||
elif config.val.auto_save.session:
|
||||
session_manager.save(sessions.default, last_window=last_window,
|
||||
load_next_time=True)
|
||||
|
||||
@ -742,16 +760,6 @@ class Quitter:
|
||||
# segfaults.
|
||||
QTimer.singleShot(0, functools.partial(qApp.exit, status))
|
||||
|
||||
@cmdutils.register(instance='quitter', name='wq')
|
||||
@cmdutils.argument('name', completion=miscmodels.session)
|
||||
def save_and_quit(self, name=sessions.default):
|
||||
"""Save open pages and quit.
|
||||
|
||||
Args:
|
||||
name: The name of the session.
|
||||
"""
|
||||
self.shutdown(session=name)
|
||||
|
||||
|
||||
class Application(QApplication):
|
||||
|
||||
|
@ -67,11 +67,7 @@ def is_whitelisted_host(host):
|
||||
Args:
|
||||
host: The host of the request as string.
|
||||
"""
|
||||
whitelist = config.get('content', 'host-blocking-whitelist')
|
||||
if whitelist is None:
|
||||
return False
|
||||
|
||||
for pattern in whitelist:
|
||||
for pattern in config.val.content.host_blocking.whitelist:
|
||||
if fnmatch.fnmatch(host, pattern.lower()):
|
||||
return True
|
||||
return False
|
||||
@ -114,16 +110,16 @@ class HostBlocker:
|
||||
|
||||
data_dir = standarddir.data()
|
||||
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
self.on_config_changed()
|
||||
self._update_files()
|
||||
|
||||
config_dir = standarddir.config()
|
||||
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
|
||||
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
config.instance.changed.connect(self._update_files)
|
||||
|
||||
def is_blocked(self, url):
|
||||
"""Check if the given URL (as QUrl) is blocked."""
|
||||
if not config.get('content', 'host-blocking-enabled'):
|
||||
if not config.val.content.host_blocking.enabled:
|
||||
return False
|
||||
host = url.host()
|
||||
return ((host in self._blocked_hosts or
|
||||
@ -164,9 +160,9 @@ class HostBlocker:
|
||||
|
||||
if not found:
|
||||
args = objreg.get('args')
|
||||
if (config.get('content', 'host-block-lists') is not None and
|
||||
if (config.val.content.host_blocking.lists and
|
||||
args.basedir is None and
|
||||
config.get('content', 'host-blocking-enabled')):
|
||||
config.val.content.host_blocking.enabled):
|
||||
message.info("Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
@ -180,18 +176,16 @@ class HostBlocker:
|
||||
self._config_blocked_hosts)
|
||||
self._blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window='last-focused')
|
||||
if urls is None:
|
||||
return
|
||||
for url in urls:
|
||||
for url in config.val.content.host_blocking.lists:
|
||||
if url.scheme() == 'file':
|
||||
filename = url.toLocalFile()
|
||||
try:
|
||||
fileobj = open(url.path(), 'rb')
|
||||
fileobj = open(filename, 'rb')
|
||||
except OSError as e:
|
||||
message.error("adblock: Error while reading {}: {}".format(
|
||||
url.path(), e.strerror))
|
||||
filename, e.strerror))
|
||||
continue
|
||||
download = FakeDownload(fileobj)
|
||||
self._in_progress.append(download)
|
||||
@ -292,11 +286,10 @@ class HostBlocker:
|
||||
message.info("adblock: Read {} hosts from {} sources.".format(
|
||||
len(self._blocked_hosts), self._done_count))
|
||||
|
||||
@config.change_filter('content', 'host-block-lists')
|
||||
def on_config_changed(self):
|
||||
@config.change_filter('content.host_blocking.lists')
|
||||
def _update_files(self):
|
||||
"""Update files when the config changed."""
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
if urls is None:
|
||||
if not config.val.content.host_blocking.lists:
|
||||
try:
|
||||
os.remove(self._local_hosts_file)
|
||||
except FileNotFoundError:
|
||||
|
@ -61,9 +61,6 @@ def init():
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
webenginetab.init()
|
||||
else:
|
||||
from qutebrowser.browser.webkit import webkittab
|
||||
webkittab.init()
|
||||
|
||||
|
||||
class WebTabError(Exception):
|
||||
@ -188,13 +185,28 @@ class AbstractSearch(QObject):
|
||||
self.text = None
|
||||
self.search_displayed = False
|
||||
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
def _is_case_sensitive(self, ignore_case):
|
||||
"""Check if case-sensitivity should be used.
|
||||
|
||||
This assumes self.text is already set properly.
|
||||
|
||||
Arguments:
|
||||
ignore_case: The ignore_case value from the config.
|
||||
"""
|
||||
mapping = {
|
||||
'smart': not self.text.islower(),
|
||||
'never': True,
|
||||
'always': False,
|
||||
}
|
||||
return mapping[ignore_case]
|
||||
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
"""Find the given text on the page.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
ignore_case: Search case-insensitively. (True/False/'smart')
|
||||
ignore_case: Search case-insensitively. ('always'/'never/'smart')
|
||||
reverse: Reverse search direction.
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
@ -236,7 +248,7 @@ class AbstractZoom(QObject):
|
||||
self._win_id = win_id
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
objreg.get('config').changed.connect(self._on_config_changed)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
# # FIXME:qtwebengine is this needed?
|
||||
# # For some reason, this signal doesn't get disconnected automatically
|
||||
@ -245,21 +257,21 @@ class AbstractZoom(QObject):
|
||||
# self.destroyed.connect(functools.partial(
|
||||
# cfg.changed.disconnect, self.init_neighborlist))
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def _on_config_changed(self, section, option):
|
||||
if section == 'ui' and option in ['zoom-levels', 'default-zoom']:
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option in ['zoom.levels', 'zoom.default']:
|
||||
if not self._default_zoom_changed:
|
||||
factor = float(config.get('ui', 'default-zoom')) / 100
|
||||
factor = float(config.val.zoom.default) / 100
|
||||
self._set_factor_internal(factor)
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
|
||||
def _init_neighborlist(self):
|
||||
"""Initialize self._neighborlist."""
|
||||
levels = config.get('ui', 'zoom-levels')
|
||||
levels = config.val.zoom.levels
|
||||
self._neighborlist = usertypes.NeighborList(
|
||||
levels, mode=usertypes.NeighborList.Modes.edge)
|
||||
self._neighborlist.fuzzyval = config.get('ui', 'default-zoom')
|
||||
self._neighborlist.fuzzyval = config.val.zoom.default
|
||||
|
||||
def offset(self, offset):
|
||||
"""Increase/Decrease the zoom level by the given offset.
|
||||
@ -295,8 +307,7 @@ class AbstractZoom(QObject):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_default(self):
|
||||
default_zoom = config.get('ui', 'default-zoom')
|
||||
self._set_factor_internal(float(default_zoom) / 100)
|
||||
self._set_factor_internal(float(config.val.zoom.default) / 100)
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
@ -705,8 +716,8 @@ class AbstractTab(QWidget):
|
||||
self.load_started.emit()
|
||||
|
||||
def _handle_auto_insert_mode(self, ok):
|
||||
"""Handle auto-insert-mode after loading finished."""
|
||||
if not config.get('input', 'auto-insert-mode') or not ok:
|
||||
"""Handle `input.insert_mode.auto_load` after loading finished."""
|
||||
if not config.val.input.insert_mode.auto_load or not ok:
|
||||
return
|
||||
|
||||
cur_mode = self._mode_manager.mode
|
||||
|
@ -34,7 +34,7 @@ import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
|
||||
webelem, downloads)
|
||||
from qutebrowser.keyinput import modeman
|
||||
@ -111,7 +111,7 @@ class CommandDispatcher:
|
||||
return widget
|
||||
|
||||
def _open(self, url, tab=False, background=False, window=False,
|
||||
explicit=True, private=None):
|
||||
related=False, private=None):
|
||||
"""Helper function to open a page.
|
||||
|
||||
Args:
|
||||
@ -132,9 +132,9 @@ class CommandDispatcher:
|
||||
tabbed_browser = self._new_tabbed_browser(private)
|
||||
tabbed_browser.tabopen(url)
|
||||
elif tab:
|
||||
tabbed_browser.tabopen(url, background=False, explicit=explicit)
|
||||
tabbed_browser.tabopen(url, background=False, related=related)
|
||||
elif background:
|
||||
tabbed_browser.tabopen(url, background=True, explicit=explicit)
|
||||
tabbed_browser.tabopen(url, background=True, related=related)
|
||||
else:
|
||||
widget = self._current_widget()
|
||||
widget.openurl(url)
|
||||
@ -179,7 +179,7 @@ class CommandDispatcher:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
what's configured in 'tabs.select_on_remove'.
|
||||
|
||||
Return:
|
||||
QTabBar.SelectLeftTab, QTabBar.SelectRightTab, or None if no change
|
||||
@ -191,17 +191,17 @@ class CommandDispatcher:
|
||||
elif next_:
|
||||
return QTabBar.SelectRightTab
|
||||
elif opposite:
|
||||
conf_selection = config.get('tabs', 'select-on-remove')
|
||||
conf_selection = config.val.tabs.select_on_remove
|
||||
if conf_selection == QTabBar.SelectLeftTab:
|
||||
return QTabBar.SelectRightTab
|
||||
elif conf_selection == QTabBar.SelectRightTab:
|
||||
return QTabBar.SelectLeftTab
|
||||
elif conf_selection == QTabBar.SelectPreviousTab:
|
||||
raise cmdexc.CommandError(
|
||||
"-o is not supported with 'tabs->select-on-remove' set to "
|
||||
"-o is not supported with 'tabs.select_on_remove' set to "
|
||||
"'last-used'!")
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid select-on-remove value "
|
||||
raise ValueError("Invalid select_on_remove value "
|
||||
"{!r}!".format(conf_selection))
|
||||
return None
|
||||
|
||||
@ -213,7 +213,7 @@ class CommandDispatcher:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
what's configured in 'tabs.select_on_remove'.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
tabbar = self._tabbed_browser.tabBar()
|
||||
@ -238,7 +238,7 @@ class CommandDispatcher:
|
||||
prev: Force selecting the tab before the current tab.
|
||||
next_: Force selecting the tab after the current tab.
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
what's configured in 'tabs.select_on_remove'.
|
||||
force: Avoid confirmation for pinned tabs.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
@ -256,7 +256,7 @@ class CommandDispatcher:
|
||||
def tab_pin(self, count=None):
|
||||
"""Pin/Unpin the current/[count]th tab.
|
||||
|
||||
Pinning a tab shrinks it to tabs->pinned-width size.
|
||||
Pinning a tab shrinks it to `tabs.width.pinned` size.
|
||||
Attempting to close a pinned tab will cause a confirmation,
|
||||
unless --force is passed.
|
||||
|
||||
@ -274,7 +274,7 @@ class CommandDispatcher:
|
||||
maxsplit=0, scope='window')
|
||||
@cmdutils.argument('url', completion=urlmodel.url)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def openurl(self, url=None, implicit=False,
|
||||
def openurl(self, url=None, related=False,
|
||||
bg=False, tab=False, window=False, count=None, secure=False,
|
||||
private=False):
|
||||
"""Open a URL in the current/[count]th tab.
|
||||
@ -286,14 +286,14 @@ class CommandDispatcher:
|
||||
bg: Open in a new background tab.
|
||||
tab: Open in a new tab.
|
||||
window: Open in a new window.
|
||||
implicit: If opening a new tab, treat the tab as implicit (like
|
||||
clicking on a link).
|
||||
related: If opening a new tab, position the tab as related to the
|
||||
current one (like clicking on a link).
|
||||
count: The tab index to open the URL in, or None.
|
||||
secure: Force HTTPS.
|
||||
private: Open a new window in private browsing mode.
|
||||
"""
|
||||
if url is None:
|
||||
urls = [config.get('general', 'default-page')]
|
||||
urls = [config.val.url.default_page]
|
||||
else:
|
||||
urls = self._parse_url_input(url)
|
||||
|
||||
@ -305,7 +305,7 @@ class CommandDispatcher:
|
||||
bg = True
|
||||
|
||||
if tab or bg or window or private:
|
||||
self._open(cur_url, tab, bg, window, explicit=not implicit,
|
||||
self._open(cur_url, tab, bg, window, related=related,
|
||||
private=private)
|
||||
else:
|
||||
curtab = self._cntwidget(count)
|
||||
@ -490,7 +490,7 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
# The new tab could be in a new tabbed_browser (e.g. because of
|
||||
# tabs-are-windows being set)
|
||||
# tabs.tabs_are_windows being set)
|
||||
if window:
|
||||
new_tabbed_browser = self._new_tabbed_browser(
|
||||
private=self._tabbed_browser.private)
|
||||
@ -502,9 +502,9 @@ class CommandDispatcher:
|
||||
idx = new_tabbed_browser.indexOf(newtab)
|
||||
|
||||
new_tabbed_browser.set_page_title(idx, cur_title)
|
||||
if config.get('tabs', 'show-favicons'):
|
||||
if config.val.tabs.favicons.show:
|
||||
new_tabbed_browser.setTabIcon(idx, curtab.icon())
|
||||
if config.get('tabs', 'tabs-are-windows'):
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
new_tabbed_browser.window().setWindowIcon(curtab.icon())
|
||||
|
||||
newtab.data.keep_icon = True
|
||||
@ -622,7 +622,7 @@ class CommandDispatcher:
|
||||
tab=tab, background=bg, window=window)
|
||||
elif where in ['up', 'increment', 'decrement']:
|
||||
new_url = handlers[where](url, count)
|
||||
self._open(new_url, tab, bg, window, explicit=False)
|
||||
self._open(new_url, tab, bg, window, related=True)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Got called with invalid value {} for "
|
||||
"`where'.".format(where))
|
||||
@ -771,7 +771,7 @@ class CommandDispatcher:
|
||||
url_query.setQueryDelimiters('=', ';')
|
||||
url_query.setQuery(url_query_str)
|
||||
for key in dict(url_query.queryItems()):
|
||||
if key in config.get('general', 'yank-ignored-url-parameters'):
|
||||
if key in config.val.url.yank_ignored_parameters:
|
||||
url_query.removeQueryItem(key)
|
||||
url.setQuery(url_query)
|
||||
return url.toString(flags)
|
||||
@ -842,7 +842,7 @@ class CommandDispatcher:
|
||||
perc = tab.zoom.offset(count)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@ -857,7 +857,7 @@ class CommandDispatcher:
|
||||
perc = tab.zoom.offset(-count)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||
message.info("Zoom level: {}%".format(int(perc)), replace=True)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@ -881,14 +881,14 @@ class CommandDispatcher:
|
||||
|
||||
level = count if count is not None else zoom
|
||||
if level is None:
|
||||
level = config.get('ui', 'default-zoom')
|
||||
level = config.val.zoom.default
|
||||
tab = self._current_widget()
|
||||
|
||||
try:
|
||||
tab.zoom.set_factor(float(level) / 100)
|
||||
except ValueError:
|
||||
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
|
||||
message.info("Zoom level: {}%".format(level), replace=True)
|
||||
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):
|
||||
@ -947,7 +947,7 @@ class CommandDispatcher:
|
||||
newidx = self._current_index() - count
|
||||
if newidx >= 0:
|
||||
self._set_current_index(newidx)
|
||||
elif config.get('tabs', 'wrap'):
|
||||
elif config.val.tabs.wrap:
|
||||
self._set_current_index(newidx % self._count())
|
||||
else:
|
||||
raise cmdexc.CommandError("First tab")
|
||||
@ -967,7 +967,7 @@ class CommandDispatcher:
|
||||
newidx = self._current_index() + count
|
||||
if newidx < self._count():
|
||||
self._set_current_index(newidx)
|
||||
elif config.get('tabs', 'wrap'):
|
||||
elif config.val.tabs.wrap:
|
||||
self._set_current_index(newidx % self._count())
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
@ -1124,7 +1124,7 @@ class CommandDispatcher:
|
||||
elif index == '+': # pragma: no branch
|
||||
new_idx += delta
|
||||
|
||||
if config.get('tabs', 'wrap'):
|
||||
if config.val.tabs.wrap:
|
||||
new_idx %= self._count()
|
||||
else:
|
||||
# absolute moving
|
||||
@ -1186,7 +1186,7 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
self.openurl(config.get('general', 'startpage')[0])
|
||||
self._current_widget().openurl(config.val.url.start_pages[0])
|
||||
|
||||
def _run_userscript(self, cmd, *args, verbose=False):
|
||||
"""Run a userscript given as argument.
|
||||
@ -1532,7 +1532,7 @@ class CommandDispatcher:
|
||||
topic: The topic to show help for.
|
||||
|
||||
- :__command__ for commands.
|
||||
- __section__\->__option__ for settings.
|
||||
- __section__.__option__ for settings.
|
||||
"""
|
||||
if topic is None:
|
||||
path = 'index.html'
|
||||
@ -1542,20 +1542,8 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError("Invalid command {}!".format(
|
||||
command))
|
||||
path = 'commands.html#{}'.format(command)
|
||||
elif '->' in topic:
|
||||
parts = topic.split('->')
|
||||
if len(parts) != 2:
|
||||
raise cmdexc.CommandError("Invalid help topic {}!".format(
|
||||
topic))
|
||||
try:
|
||||
config.get(*parts)
|
||||
except configexc.NoSectionError:
|
||||
raise cmdexc.CommandError("Invalid section {}!".format(
|
||||
parts[0]))
|
||||
except configexc.NoOptionError:
|
||||
raise cmdexc.CommandError("Invalid option {}!".format(
|
||||
parts[1]))
|
||||
path = 'settings.html#{}'.format(topic.replace('->', '-'))
|
||||
elif topic in configdata.DATA:
|
||||
path = 'settings.html#{}'.format(topic)
|
||||
else:
|
||||
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
|
||||
url = QUrl('qute://help/{}'.format(path))
|
||||
@ -1608,7 +1596,7 @@ class CommandDispatcher:
|
||||
"""Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the
|
||||
`general -> editor` config option.
|
||||
`editor.command` config option.
|
||||
"""
|
||||
tab = self._current_widget()
|
||||
tab.elements.find_focused(self._open_editor_cb)
|
||||
@ -1749,7 +1737,7 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
options = {
|
||||
'ignore_case': config.get('general', 'ignore-case'),
|
||||
'ignore_case': config.val.ignore_case,
|
||||
'reverse': reverse,
|
||||
}
|
||||
|
||||
@ -2103,7 +2091,7 @@ class CommandDispatcher:
|
||||
"""Navigate to a url formed in an external editor.
|
||||
|
||||
The editor which should be launched can be configured via the
|
||||
`general -> editor` config option.
|
||||
`editor.command` config option.
|
||||
|
||||
Args:
|
||||
url: URL to edit; defaults to the current page url.
|
||||
|
@ -69,15 +69,22 @@ class UnsupportedOperationError(Exception):
|
||||
|
||||
def download_dir():
|
||||
"""Get the download directory to use."""
|
||||
directory = config.get('storage', 'download-directory')
|
||||
remember_dir = config.get('storage', 'remember-download-directory')
|
||||
directory = config.val.downloads.location.directory
|
||||
remember_dir = config.val.downloads.location.remember
|
||||
|
||||
if remember_dir and last_used_directory is not None:
|
||||
return last_used_directory
|
||||
ddir = last_used_directory
|
||||
elif directory is None:
|
||||
return standarddir.download()
|
||||
ddir = standarddir.download()
|
||||
else:
|
||||
return directory
|
||||
ddir = directory
|
||||
|
||||
try:
|
||||
os.makedirs(ddir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
return ddir
|
||||
|
||||
|
||||
def immediate_download_path(prompt_download_directory=None):
|
||||
@ -88,11 +95,10 @@ def immediate_download_path(prompt_download_directory=None):
|
||||
Args:
|
||||
prompt_download_directory: If this is something else than None, it
|
||||
will overwrite the
|
||||
storage->prompt-download-directory setting.
|
||||
downloads.location.prompt setting.
|
||||
"""
|
||||
if prompt_download_directory is None:
|
||||
prompt_download_directory = config.get('storage',
|
||||
'prompt-download-directory')
|
||||
prompt_download_directory = config.val.downloads.location.prompt
|
||||
|
||||
if not prompt_download_directory:
|
||||
return download_dir()
|
||||
@ -104,7 +110,7 @@ def _path_suggestion(filename):
|
||||
Args:
|
||||
filename: The filename to use if included in the suggestion.
|
||||
"""
|
||||
suggestion = config.get('completion', 'download-path-suggestion')
|
||||
suggestion = config.val.downloads.location.suggestion
|
||||
if suggestion == 'path':
|
||||
# add trailing '/' if not present
|
||||
return os.path.join(download_dir(), '')
|
||||
@ -494,13 +500,13 @@ class AbstractDownloadItem(QObject):
|
||||
Args:
|
||||
position: The color type requested, can be 'fg' or 'bg'.
|
||||
"""
|
||||
# pylint: disable=bad-config-call
|
||||
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/
|
||||
assert position in ["fg", "bg"]
|
||||
start = config.get('colors', 'downloads.{}.start'.format(position))
|
||||
stop = config.get('colors', 'downloads.{}.stop'.format(position))
|
||||
system = config.get('colors', 'downloads.{}.system'.format(position))
|
||||
error = config.get('colors', 'downloads.{}.error'.format(position))
|
||||
# pylint: disable=bad-config-option
|
||||
start = getattr(config.val.colors.downloads.start, position)
|
||||
stop = getattr(config.val.colors.downloads.stop, position)
|
||||
system = getattr(config.val.colors.downloads.system, position)
|
||||
error = getattr(config.val.colors.downloads.error, position)
|
||||
# pylint: enable=bad-config-option
|
||||
if self.error_msg is not None:
|
||||
assert not self.successful
|
||||
return error
|
||||
@ -572,7 +578,7 @@ class AbstractDownloadItem(QObject):
|
||||
Args:
|
||||
cmdline: The command to use as string. A `{}` is expanded to the
|
||||
filename. None means to use the system's default
|
||||
application or `default-open-dispatcher` if set. If no
|
||||
application or `downloads.open_dispatcher` if set. If no
|
||||
`{}` is found, the filename is appended to the cmdline.
|
||||
"""
|
||||
assert self.successful
|
||||
@ -757,7 +763,7 @@ class AbstractDownloadManager(QObject):
|
||||
download.remove_requested.connect(functools.partial(
|
||||
self._remove_item, download))
|
||||
|
||||
delay = config.get('ui', 'remove-finished-downloads')
|
||||
delay = config.val.downloads.remove_finished
|
||||
if delay > -1:
|
||||
download.finished.connect(
|
||||
lambda: QTimer.singleShot(delay, download.remove))
|
||||
|
@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import qtutils, utils, objreg
|
||||
|
||||
|
||||
@ -64,8 +64,8 @@ class DownloadView(QListView):
|
||||
|
||||
STYLESHEET = """
|
||||
QListView {
|
||||
background-color: {{ color['downloads.bg.bar'] }};
|
||||
font: {{ font['downloads'] }};
|
||||
background-color: {{ conf.colors.downloads.bar.bg }};
|
||||
font: {{ conf.fonts.downloads }};
|
||||
}
|
||||
|
||||
QListView::item {
|
||||
@ -76,7 +76,7 @@ class DownloadView(QListView):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
self.setResizeMode(QListView.Adjust)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
||||
|
@ -29,7 +29,7 @@ from string import ascii_lowercase
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman, modeparsers
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
@ -65,10 +65,10 @@ class HintLabel(QLabel):
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel {
|
||||
background-color: {{ color['hints.bg'] }};
|
||||
color: {{ color['hints.fg'] }};
|
||||
font: {{ font['hints'] }};
|
||||
border: {{ config.get('hints', 'border') }};
|
||||
background-color: {{ conf.colors.hints.bg }};
|
||||
color: {{ conf.colors.hints.fg }};
|
||||
font: {{ conf.fonts.hints }};
|
||||
border: {{ conf.hints.border }};
|
||||
padding-left: -3px;
|
||||
padding-right: -3px;
|
||||
}
|
||||
@ -80,7 +80,7 @@ class HintLabel(QLabel):
|
||||
self.elem = elem
|
||||
|
||||
self.setAttribute(Qt.WA_StyledBackground, True)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
|
||||
self._context.tab.contents_size_changed.connect(self._move_to_elem)
|
||||
self._move_to_elem()
|
||||
@ -100,7 +100,7 @@ class HintLabel(QLabel):
|
||||
matched: The part of the text which was typed.
|
||||
unmatched: The part of the text which was not typed yet.
|
||||
"""
|
||||
if (config.get('hints', 'uppercase') and
|
||||
if (config.val.hints.uppercase and
|
||||
self._context.hint_mode in ['letter', 'word']):
|
||||
matched = html.escape(matched.upper())
|
||||
unmatched = html.escape(unmatched.upper())
|
||||
@ -108,7 +108,7 @@ class HintLabel(QLabel):
|
||||
matched = html.escape(matched)
|
||||
unmatched = html.escape(unmatched)
|
||||
|
||||
match_color = html.escape(config.get('colors', 'hints.fg.match'))
|
||||
match_color = html.escape(config.val.colors.hints.match.fg)
|
||||
self.setText('<font color="{}">{}</font>{}'.format(
|
||||
match_color, matched, unmatched))
|
||||
self.adjustSize()
|
||||
@ -121,7 +121,7 @@ class HintLabel(QLabel):
|
||||
log.hints.debug("Frame for {!r} vanished!".format(self))
|
||||
self.hide()
|
||||
return
|
||||
no_js = config.get('hints', 'find-implementation') != 'javascript'
|
||||
no_js = config.val.hints.find_implementation != 'javascript'
|
||||
rect = self.elem.rect_on_view(no_js=no_js)
|
||||
self.move(rect.x(), rect.y())
|
||||
|
||||
@ -203,7 +203,7 @@ class HintActions:
|
||||
Target.window: usertypes.ClickTarget.window,
|
||||
Target.hover: usertypes.ClickTarget.normal,
|
||||
}
|
||||
if config.get('tabs', 'background-tabs'):
|
||||
if config.val.tabs.background:
|
||||
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target_mapping[Target.tab] = usertypes.ClickTarget.tab
|
||||
@ -421,9 +421,9 @@ class HintManager(QObject):
|
||||
if hint_mode == 'number':
|
||||
chars = '0123456789'
|
||||
else:
|
||||
chars = config.get('hints', 'chars')
|
||||
min_chars = config.get('hints', 'min-chars')
|
||||
if config.get('hints', 'scatter') and hint_mode != 'number':
|
||||
chars = config.val.hints.chars
|
||||
min_chars = config.val.hints.min_chars
|
||||
if config.val.hints.scatter and hint_mode != 'number':
|
||||
return self._hint_scattered(min_chars, chars, elems)
|
||||
else:
|
||||
return self._hint_linear(min_chars, chars, elems)
|
||||
@ -603,7 +603,7 @@ class HintManager(QObject):
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
# to make auto-follow == 'always' work
|
||||
# to make auto_follow == 'always' work
|
||||
self._handle_auto_follow()
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
||||
@ -615,7 +615,7 @@ class HintManager(QObject):
|
||||
|
||||
Args:
|
||||
rapid: Whether to do rapid hinting. This is only possible with
|
||||
targets `tab` (with background-tabs=true), `tab-bg`,
|
||||
targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
add_history: Whether to add the spawned or yanked link to the
|
||||
browsing history.
|
||||
@ -631,7 +631,7 @@ class HintManager(QObject):
|
||||
- `normal`: Open the link.
|
||||
- `current`: Open the link in the current tab.
|
||||
- `tab`: Open the link in a new tab (honoring the
|
||||
background-tabs setting).
|
||||
`tabs.background_tabs` setting).
|
||||
- `tab-fg`: Open the link in a new foreground tab.
|
||||
- `tab-bg`: Open the link in a new background tab.
|
||||
- `window`: Open the link in a new window.
|
||||
@ -649,7 +649,7 @@ class HintManager(QObject):
|
||||
mode: The hinting mode to use.
|
||||
|
||||
- `number`: Use numeric hints.
|
||||
- `letter`: Use the chars in the hints->chars settings.
|
||||
- `letter`: Use the chars in the hints.chars setting.
|
||||
- `word`: Use hint words based on the html elements and the
|
||||
extra words.
|
||||
|
||||
@ -684,8 +684,7 @@ class HintManager(QObject):
|
||||
Target.hover, Target.userscript, Target.spawn,
|
||||
Target.download, Target.normal, Target.current]:
|
||||
pass
|
||||
elif (target == Target.tab and
|
||||
config.get('tabs', 'background-tabs')):
|
||||
elif target == Target.tab and config.val.tabs.background:
|
||||
pass
|
||||
else:
|
||||
name = target.name.replace('_', '-')
|
||||
@ -693,7 +692,7 @@ class HintManager(QObject):
|
||||
"target {}!".format(name))
|
||||
|
||||
if mode is None:
|
||||
mode = config.get('hints', 'mode')
|
||||
mode = config.val.hints.mode
|
||||
|
||||
self._check_args(target, *args)
|
||||
self._context = HintContext()
|
||||
@ -720,7 +719,7 @@ class HintManager(QObject):
|
||||
return self._context.hint_mode
|
||||
|
||||
def _handle_auto_follow(self, keystr="", filterstr="", visible=None):
|
||||
"""Handle the auto-follow option."""
|
||||
"""Handle the auto_follow option."""
|
||||
if visible is None:
|
||||
visible = {string: label
|
||||
for string, label in self._context.labels.items()
|
||||
@ -729,7 +728,7 @@ class HintManager(QObject):
|
||||
if len(visible) != 1:
|
||||
return
|
||||
|
||||
auto_follow = config.get('hints', 'auto-follow')
|
||||
auto_follow = config.val.hints.auto_follow
|
||||
|
||||
if auto_follow == "always":
|
||||
follow = True
|
||||
@ -746,8 +745,8 @@ class HintManager(QObject):
|
||||
self._context.to_follow = list(visible.keys())[0]
|
||||
|
||||
if follow:
|
||||
# apply auto-follow-timeout
|
||||
timeout = config.get('hints', 'auto-follow-timeout')
|
||||
# apply auto_follow_timeout
|
||||
timeout = config.val.hints.auto_follow_timeout
|
||||
keyparsers = objreg.get('keyparsers', scope='window',
|
||||
window=self._win_id)
|
||||
normal_parser = keyparsers[usertypes.KeyMode.normal]
|
||||
@ -771,9 +770,9 @@ class HintManager(QObject):
|
||||
label.show()
|
||||
else:
|
||||
# element doesn't match anymore -> hide it, unless in rapid
|
||||
# mode and hide-unmatched-rapid-hints is false (see #1799)
|
||||
# mode and hide_unmatched_rapid_hints is false (see #1799)
|
||||
if (not self._context.rapid or
|
||||
config.get('hints', 'hide-unmatched-rapid-hints')):
|
||||
config.val.hints.hide_unmatched_rapid_hints):
|
||||
label.hide()
|
||||
except webelem.Error:
|
||||
pass
|
||||
@ -793,6 +792,8 @@ class HintManager(QObject):
|
||||
else:
|
||||
self._context.filterstr = filterstr
|
||||
|
||||
log.hints.debug("Filtering hints on {!r}".format(filterstr))
|
||||
|
||||
visible = []
|
||||
for label in self._context.all_labels:
|
||||
try:
|
||||
@ -938,7 +939,7 @@ class WordHinter:
|
||||
|
||||
def ensure_initialized(self):
|
||||
"""Generate the used words if yet uninitialized."""
|
||||
dictionary = config.get("hints", "dictionary")
|
||||
dictionary = config.val.hints.dictionary
|
||||
if not self.words or self.dictionary != dictionary:
|
||||
self.words.clear()
|
||||
self.dictionary = dictionary
|
||||
|
@ -85,7 +85,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
is_rocker_gesture = (config.get('input', 'rocker-gestures') and
|
||||
is_rocker_gesture = (config.val.input.rocker_gestures and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
@ -119,7 +119,7 @@ class MouseEventFilter(QObject):
|
||||
return True
|
||||
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
divider = config.val.zoom.mouse_divider
|
||||
if divider == 0:
|
||||
return False
|
||||
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
|
||||
@ -139,7 +139,7 @@ class MouseEventFilter(QObject):
|
||||
|
||||
def _handle_context_menu(self, _e):
|
||||
"""Suppress context menus if rocker gestures are turned on."""
|
||||
return config.get('input', 'rocker-gestures')
|
||||
return config.val.input.rocker_gestures
|
||||
|
||||
def _mousepress_insertmode_cb(self, elem):
|
||||
"""Check if the clicked element is editable."""
|
||||
@ -157,7 +157,7 @@ class MouseEventFilter(QObject):
|
||||
'click', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', maybe=True)
|
||||
|
||||
@ -179,7 +179,7 @@ class MouseEventFilter(QObject):
|
||||
'click-delayed', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element (delayed)!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed', maybe=True)
|
||||
|
||||
|
@ -42,7 +42,7 @@ def incdec(url, count, inc_or_dec):
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
segments = set(config.get('general', 'url-incdec-segments'))
|
||||
segments = set(config.val.url.incdec_segments)
|
||||
try:
|
||||
new_url = urlutils.incdec_number(url, inc_or_dec, count,
|
||||
segments=segments)
|
||||
@ -80,10 +80,13 @@ def _find_prevnext(prev, elems):
|
||||
|
||||
# Then check for regular links/buttons.
|
||||
elems = [e for e in elems if e.tag_name() != 'link']
|
||||
option = 'prev-regexes' if prev else 'next-regexes'
|
||||
option = 'prev_regexes' if prev else 'next_regexes'
|
||||
if not elems:
|
||||
return None
|
||||
for regex in config.get('hints', option):
|
||||
|
||||
# pylint: disable=bad-config-option
|
||||
for regex in getattr(config.val.hints, option):
|
||||
# pylint: enable=bad-config-option
|
||||
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
|
||||
for e in elems:
|
||||
text = str(e)
|
||||
|
@ -247,10 +247,21 @@ class PACFetcher(QObject):
|
||||
self._pac_url = url
|
||||
self._manager = QNetworkAccessManager()
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
|
||||
self._reply = self._manager.get(QNetworkRequest(url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
self._pac = None
|
||||
self._error_message = None
|
||||
self._reply = None
|
||||
|
||||
def __eq__(self, other):
|
||||
# pylint: disable=protected-access
|
||||
return self._pac_url == other._pac_url
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, url=self._pac_url, constructor=True)
|
||||
|
||||
def fetch(self):
|
||||
"""Fetch the proxy from the remote URL."""
|
||||
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
|
||||
@pyqtSlot()
|
||||
def _finish(self):
|
||||
|
@ -44,7 +44,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
Return:
|
||||
None if proxy is correct, otherwise an error message.
|
||||
"""
|
||||
proxy = config.get('network', 'proxy')
|
||||
proxy = config.val.content.proxy
|
||||
if isinstance(proxy, pac.PACFetcher):
|
||||
return proxy.fetch_error()
|
||||
else:
|
||||
@ -59,7 +59,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
Return:
|
||||
A list of QNetworkProxy objects in order of preference.
|
||||
"""
|
||||
proxy = config.get('network', 'proxy')
|
||||
proxy = config.val.content.proxy
|
||||
if proxy is configtypes.SYSTEM_PROXY:
|
||||
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
elif isinstance(proxy, pac.PACFetcher):
|
||||
@ -69,7 +69,7 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
for p in proxies:
|
||||
if p.type() != QNetworkProxy.NoProxy:
|
||||
capabilities = p.capabilities()
|
||||
if config.get('network', 'proxy-dns-requests'):
|
||||
if config.val.content.proxy_dns_requests:
|
||||
capabilities |= QNetworkProxy.HostNameLookupCapability
|
||||
else:
|
||||
capabilities &= ~QNetworkProxy.HostNameLookupCapability
|
||||
|
@ -368,7 +368,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
super().__init__(parent)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id=win_id, tab_id=None,
|
||||
private=config.get('general', 'private-browsing'), parent=self)
|
||||
private=config.val.content.private_browsing, parent=self)
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def get(self, url, *, user_agent=None, **kwargs):
|
||||
@ -483,7 +483,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
reply: The QNetworkReply to download.
|
||||
target: Where to save the download as downloads.DownloadTarget.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to -1.
|
||||
downloads.remove_finished is set to -1.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
|
@ -29,12 +29,13 @@ import os
|
||||
import time
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import textwrap
|
||||
import pkg_resources
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, usertypes, qtutils)
|
||||
from qutebrowser.misc import objects
|
||||
@ -122,8 +123,7 @@ class add_handler: # pylint: disable=invalid-name
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()),
|
||||
icon='')
|
||||
'backend'.format(url.toDisplayString()))
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@ -225,14 +225,14 @@ def qute_history(url):
|
||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||
else:
|
||||
if (
|
||||
config.get('content', 'allow-javascript') and
|
||||
config.val.content.javascript.enabled and
|
||||
(objects.backend == usertypes.Backend.QtWebEngine or
|
||||
qtutils.is_qtwebkit_ng())
|
||||
):
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
session_interval=config.get('ui', 'history-session-interval')
|
||||
gap_interval=config.val.history_gap_interval
|
||||
)
|
||||
else:
|
||||
# Get current date from query parameter, if not given choose today.
|
||||
@ -351,20 +351,6 @@ def qute_gpl(_url):
|
||||
@add_handler('help')
|
||||
def qute_help(url):
|
||||
"""Handler for qute://help."""
|
||||
try:
|
||||
utils.read_file('html/doc/index.html')
|
||||
except OSError:
|
||||
html = jinja.render(
|
||||
'error.html',
|
||||
title="Error while loading documentation",
|
||||
url=url.toDisplayString(),
|
||||
error="This most likely means the documentation was not generated "
|
||||
"properly. If you are running qutebrowser from the git "
|
||||
"repository, please run scripts/asciidoc2html.py. "
|
||||
"If you're running a released version this is a bug, please "
|
||||
"use :report to report it.",
|
||||
icon='')
|
||||
return 'text/html', html
|
||||
urlpath = url.path()
|
||||
if not urlpath or urlpath == '/':
|
||||
urlpath = 'index.html'
|
||||
@ -373,11 +359,45 @@ def qute_help(url):
|
||||
if not docutils.docs_up_to_date(urlpath):
|
||||
message.error("Your documentation is outdated! Please re-run "
|
||||
"scripts/asciidoc2html.py.")
|
||||
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
if urlpath.endswith('.png'):
|
||||
return 'image/png', utils.read_file(path, binary=True)
|
||||
else:
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
except OSError:
|
||||
# No .html around, let's see if we find the asciidoc
|
||||
asciidoc_path = path.replace('.html', '.asciidoc')
|
||||
if asciidoc_path.startswith('html/doc/'):
|
||||
asciidoc_path = asciidoc_path.replace('html/doc/', '../doc/help/')
|
||||
|
||||
try:
|
||||
asciidoc = utils.read_file(asciidoc_path)
|
||||
except OSError:
|
||||
asciidoc = None
|
||||
|
||||
if asciidoc is None:
|
||||
raise
|
||||
|
||||
preamble = textwrap.dedent("""
|
||||
There was an error loading the documentation!
|
||||
|
||||
This most likely means the documentation was not generated
|
||||
properly. If you are running qutebrowser from the git repository,
|
||||
please (re)run scripts/asciidoc2html.py and reload this page.
|
||||
|
||||
If you're running a released version this is a bug, please use
|
||||
:report to report it.
|
||||
|
||||
Falling back to the plaintext version.
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
""")
|
||||
return 'text/plain', (preamble + asciidoc).encode('utf-8')
|
||||
else:
|
||||
return 'text/html', data
|
||||
|
||||
|
||||
@ -390,3 +410,47 @@ def qute_backend_warning(_url):
|
||||
version=pkg_resources.parse_version,
|
||||
title="Legacy backend warning")
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def _qute_settings_set(url):
|
||||
"""Handler for qute://settings/set."""
|
||||
query = QUrlQuery(url)
|
||||
option = query.queryItemValue('option', QUrl.FullyDecoded)
|
||||
value = query.queryItemValue('value', QUrl.FullyDecoded)
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if option == 'content.javascript.enabled' and value == 'false':
|
||||
msg = ("Refusing to disable javascript via qute://settings "
|
||||
"as it needs javascript support.")
|
||||
message.error(msg)
|
||||
return 'text/html', b'error: ' + msg.encode('utf-8')
|
||||
|
||||
try:
|
||||
config.instance.set_str(option, value, save_yaml=True)
|
||||
return 'text/html', b'ok'
|
||||
except configexc.Error as e:
|
||||
message.error(str(e))
|
||||
return 'text/html', b'error: ' + str(e).encode('utf-8')
|
||||
|
||||
|
||||
@add_handler('settings')
|
||||
def qute_settings(url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
if url.path() == '/set':
|
||||
return _qute_settings_set(url)
|
||||
|
||||
html = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
confget=config.instance.get_str)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(_url):
|
||||
"""Handler for qute://configdiff."""
|
||||
try:
|
||||
return 'text/html', configdiff.get_diff()
|
||||
except OSError as e:
|
||||
error = (b'Failed to read old config: ' +
|
||||
str(e.strerror).encode('utf-8'))
|
||||
return 'text/plain', error
|
||||
|
@ -21,10 +21,8 @@
|
||||
|
||||
import html
|
||||
|
||||
import jinja2
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
@ -35,16 +33,18 @@ class CallSuper(Exception):
|
||||
def custom_headers():
|
||||
"""Get the combined custom headers."""
|
||||
headers = {}
|
||||
dnt = b'1' if config.get('network', 'do-not-track') else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
config_headers = config.get('network', 'custom-headers')
|
||||
if config_headers is not None:
|
||||
for header, value in config_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
dnt_config = config.val.content.headers.do_not_track
|
||||
if dnt_config is not None:
|
||||
dnt = b'1' if dnt_config else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
accept_language = config.get('network', 'accept-language')
|
||||
conf_headers = config.val.content.headers.custom
|
||||
for header, value in conf_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
if accept_language is not None:
|
||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||
|
||||
@ -72,7 +72,7 @@ def authentication_required(url, authenticator, abort_on):
|
||||
def javascript_confirm(url, js_msg, abort_on):
|
||||
"""Display a javascript confirm prompt."""
|
||||
log.js.debug("confirm: {}".format(js_msg))
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
@ -86,9 +86,9 @@ def javascript_confirm(url, js_msg, abort_on):
|
||||
def javascript_prompt(url, js_msg, default, abort_on):
|
||||
"""Display a javascript prompt."""
|
||||
log.js.debug("prompt: {}".format(js_msg))
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
if config.get('content', 'ignore-javascript-prompt'):
|
||||
if not config.val.content.javascript.prompt:
|
||||
return (False, "")
|
||||
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
@ -107,10 +107,10 @@ def javascript_prompt(url, js_msg, default, abort_on):
|
||||
def javascript_alert(url, js_msg, abort_on):
|
||||
"""Display a javascript alert."""
|
||||
log.js.debug("alert: {}".format(js_msg))
|
||||
if config.get('ui', 'modal-js-dialog'):
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
if config.get('content', 'ignore-javascript-alert'):
|
||||
if not config.val.content.javascript.alert:
|
||||
return
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
@ -119,6 +119,22 @@ def javascript_alert(url, js_msg, abort_on):
|
||||
abort_on=abort_on)
|
||||
|
||||
|
||||
def javascript_log_message(level, source, line, msg):
|
||||
"""Display a JavaScript log message."""
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
# Needs to line up with the values allowed for the
|
||||
# content.javascript.log setting.
|
||||
logmap = {
|
||||
'none': lambda arg: None,
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'warning': log.js.warning,
|
||||
'error': log.js.error,
|
||||
}
|
||||
logger = logmap[config.val.content.javascript.log[level.name]]
|
||||
logger(logstring)
|
||||
|
||||
|
||||
def ignore_certificate_errors(url, errors, abort_on):
|
||||
"""Display a certificate error question.
|
||||
|
||||
@ -129,7 +145,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.get('network', 'ssl-strict')
|
||||
ssl_strict = config.val.content.ssl_strict
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
@ -137,7 +153,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if ssl_strict == 'ask':
|
||||
err_template = jinja2.Template("""
|
||||
err_template = jinja.environment.from_string("""
|
||||
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
@ -155,7 +171,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
ignore = False
|
||||
return ignore
|
||||
elif ssl_strict is False:
|
||||
log.webview.debug("ssl-strict is False, only warning about errors")
|
||||
log.webview.debug("ssl_strict is False, only warning about errors")
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/114
|
||||
@ -173,7 +189,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
option: A (section, option) tuple for the option to check.
|
||||
option: An option name to check.
|
||||
msg: A string like "show notifications"
|
||||
yes_action: A callable to call if the request was approved
|
||||
no_action: A callable to call if the request was denied
|
||||
@ -182,7 +198,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
Return:
|
||||
The Question object if a question was asked, None otherwise.
|
||||
"""
|
||||
config_val = config.get(*option)
|
||||
config_val = config.instance.get(option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
@ -233,15 +249,14 @@ def get_tab(win_id, target):
|
||||
|
||||
def get_user_stylesheet():
|
||||
"""Get the combined user-stylesheet."""
|
||||
filename = config.get('ui', 'user-stylesheet')
|
||||
css = ''
|
||||
stylesheets = config.val.content.user_stylesheets
|
||||
|
||||
if filename is None:
|
||||
css = ''
|
||||
else:
|
||||
for filename in stylesheets:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
css = f.read()
|
||||
css += f.read()
|
||||
|
||||
if config.get('ui', 'hide-scrollbar'):
|
||||
if not config.val.scrolling.bar:
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
|
@ -182,7 +182,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
# at least a classid attribute. Oh, and let's hope images/...
|
||||
# DON'T have a classid attribute. HTML sucks.
|
||||
log.webelem.debug("<object type='{}'> clicked.".format(objtype))
|
||||
return config.get('input', 'insert-mode-on-plugins')
|
||||
return config.val.input.insert_mode.plugins
|
||||
else:
|
||||
# Image/Audio/...
|
||||
return False
|
||||
@ -247,7 +247,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
return self.is_writable()
|
||||
elif tag in ['embed', 'applet']:
|
||||
# Flash/Java/...
|
||||
return config.get('input', 'insert-mode-on-plugins') and not strict
|
||||
return config.val.input.insert_mode.plugins and not strict
|
||||
elif tag == 'object':
|
||||
return self._is_editable_object() and not strict
|
||||
elif tag in ['div', 'pre']:
|
||||
@ -329,7 +329,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
usertypes.ClickTarget.tab: Qt.ControlModifier,
|
||||
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
|
||||
}
|
||||
if config.get('tabs', 'background-tabs'):
|
||||
if config.val.tabs.background:
|
||||
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
|
||||
else:
|
||||
modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
|
||||
|
@ -63,6 +63,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||
for header, value in shared.custom_headers():
|
||||
info.setHttpHeader(header, value)
|
||||
|
||||
user_agent = config.get('network', 'user-agent')
|
||||
user_agent = config.val.content.headers.user_agent
|
||||
if user_agent is not None:
|
||||
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
||||
|
@ -162,7 +162,7 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
top = rect['top']
|
||||
if width > 1 and height > 1:
|
||||
# Fix coordinates according to zoom level
|
||||
# We're not checking for zoom-text-only here as that doesn't
|
||||
# We're not checking for zoom.text_only here as that doesn't
|
||||
# exist for QtWebEngine.
|
||||
zoom = self._tab.zoom.factor()
|
||||
rect = QRect(left * zoom, top * zoom,
|
||||
|
@ -38,7 +38,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import objreg, utils, standarddir, javascript, qtutils
|
||||
from qutebrowser.utils import utils, standarddir, javascript, qtutils
|
||||
|
||||
|
||||
# The default QWebEngineProfile
|
||||
@ -112,7 +112,7 @@ class DefaultProfileSetter(websettings.Base):
|
||||
|
||||
class PersistentCookiePolicy(DefaultProfileSetter):
|
||||
|
||||
"""The cookies -> store setting is different from other settings."""
|
||||
"""The content.cookies.store setting is different from other settings."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__('setPersistentCookiesPolicy')
|
||||
@ -158,26 +158,29 @@ def _init_stylesheet(profile):
|
||||
profile.scripts().insert(script)
|
||||
|
||||
|
||||
def _set_user_agent(profile):
|
||||
"""Set the user agent for the given profile.
|
||||
def _set_http_headers(profile):
|
||||
"""Set the user agent and accept-language for the given profile.
|
||||
|
||||
We override this per request in the URL interceptor (to allow for
|
||||
per-domain user agents), but this one still gets used for things like
|
||||
window.navigator.userAgent in JS.
|
||||
We override those per request in the URL interceptor (to allow for
|
||||
per-domain values), but this one still gets used for things like
|
||||
window.navigator.userAgent/.languages in JS.
|
||||
"""
|
||||
user_agent = config.get('network', 'user-agent')
|
||||
profile.setHttpUserAgent(user_agent)
|
||||
profile.setHttpUserAgent(config.val.content.headers.user_agent)
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
if accept_language is not None:
|
||||
profile.setHttpAcceptLanguage(accept_language)
|
||||
|
||||
|
||||
def update_settings(section, option):
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
websettings.update_mappings(MAPPINGS, section, option)
|
||||
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
|
||||
websettings.update_mappings(MAPPINGS, option)
|
||||
if option in ['scrollbar.hide', 'content.user_stylesheets']:
|
||||
_init_stylesheet(default_profile)
|
||||
_init_stylesheet(private_profile)
|
||||
elif section == 'network' and option == 'user-agent':
|
||||
_set_user_agent(default_profile)
|
||||
_set_user_agent(private_profile)
|
||||
elif option in ['content.headers.user_agent',
|
||||
'content.headers.accept_language']:
|
||||
_set_http_headers(default_profile)
|
||||
_set_http_headers(private_profile)
|
||||
|
||||
|
||||
def _init_profiles():
|
||||
@ -189,12 +192,12 @@ def _init_profiles():
|
||||
default_profile.setPersistentStoragePath(
|
||||
os.path.join(standarddir.data(), 'webengine'))
|
||||
_init_stylesheet(default_profile)
|
||||
_set_user_agent(default_profile)
|
||||
_set_http_headers(default_profile)
|
||||
|
||||
private_profile = QWebEngineProfile()
|
||||
assert private_profile.isOffTheRecord()
|
||||
_init_stylesheet(private_profile)
|
||||
_set_user_agent(private_profile)
|
||||
_set_http_headers(private_profile)
|
||||
|
||||
|
||||
def init(args):
|
||||
@ -212,11 +215,11 @@ def init(args):
|
||||
# We need to do this here as a WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
if not qtutils.version_check('5.9'):
|
||||
PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
|
||||
PersistentCookiePolicy().set(config.val.content.cookies.store)
|
||||
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
|
||||
|
||||
websettings.init_mappings(MAPPINGS)
|
||||
objreg.get('config').changed.connect(update_settings)
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
|
||||
def shutdown():
|
||||
@ -237,79 +240,70 @@ def shutdown():
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content': {
|
||||
'allow-images':
|
||||
Attribute(QWebEngineSettings.AutoLoadImages),
|
||||
'allow-javascript':
|
||||
Attribute(QWebEngineSettings.JavascriptEnabled),
|
||||
'javascript-can-open-windows-automatically':
|
||||
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
'javascript-can-access-clipboard':
|
||||
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
|
||||
'allow-plugins':
|
||||
Attribute(QWebEngineSettings.PluginsEnabled),
|
||||
'hyperlink-auditing':
|
||||
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
'local-content-can-access-remote-urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
'local-content-can-access-file-urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
'webgl':
|
||||
Attribute(QWebEngineSettings.WebGLEnabled),
|
||||
},
|
||||
'input': {
|
||||
'spatial-navigation':
|
||||
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
'links-included-in-focus-chain':
|
||||
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
},
|
||||
'fonts': {
|
||||
'web-family-standard':
|
||||
FontFamilySetter(QWebEngineSettings.StandardFont),
|
||||
'web-family-fixed':
|
||||
FontFamilySetter(QWebEngineSettings.FixedFont),
|
||||
'web-family-serif':
|
||||
FontFamilySetter(QWebEngineSettings.SerifFont),
|
||||
'web-family-sans-serif':
|
||||
FontFamilySetter(QWebEngineSettings.SansSerifFont),
|
||||
'web-family-cursive':
|
||||
FontFamilySetter(QWebEngineSettings.CursiveFont),
|
||||
'web-family-fantasy':
|
||||
FontFamilySetter(QWebEngineSettings.FantasyFont),
|
||||
'web-size-minimum':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumFontSize]),
|
||||
'web-size-minimum-logical':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumLogicalFontSize]),
|
||||
'web-size-default':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFontSize]),
|
||||
'web-size-default-fixed':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFixedFontSize]),
|
||||
},
|
||||
'ui': {
|
||||
'smooth-scrolling':
|
||||
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
},
|
||||
'storage': {
|
||||
'local-storage':
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'cache-size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
|
||||
},
|
||||
'general': {
|
||||
'xss-auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'default-encoding':
|
||||
Setter(QWebEngineSettings.setDefaultTextEncoding),
|
||||
}
|
||||
'content.images':
|
||||
Attribute(QWebEngineSettings.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attribute(QWebEngineSettings.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
|
||||
'content.plugins':
|
||||
Attribute(QWebEngineSettings.PluginsEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
'content.webgl':
|
||||
Attribute(QWebEngineSettings.WebGLEnabled),
|
||||
'content.local_storage':
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'content.cache.size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
Setter(QWebEngineSettings.setDefaultTextEncoding),
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
|
||||
'fonts.web.family.standard':
|
||||
FontFamilySetter(QWebEngineSettings.StandardFont),
|
||||
'fonts.web.family.fixed':
|
||||
FontFamilySetter(QWebEngineSettings.FixedFont),
|
||||
'fonts.web.family.serif':
|
||||
FontFamilySetter(QWebEngineSettings.SerifFont),
|
||||
'fonts.web.family.sans_serif':
|
||||
FontFamilySetter(QWebEngineSettings.SansSerifFont),
|
||||
'fonts.web.family.cursive':
|
||||
FontFamilySetter(QWebEngineSettings.CursiveFont),
|
||||
'fonts.web.family.fantasy':
|
||||
FontFamilySetter(QWebEngineSettings.FantasyFont),
|
||||
'fonts.web.size.minimum':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumFontSize]),
|
||||
'fonts.web.size.minimum_logical':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.MinimumLogicalFontSize]),
|
||||
'fonts.web.size.default':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFontSize]),
|
||||
'fonts.web.size.default_fixed':
|
||||
Setter(QWebEngineSettings.setFontSize,
|
||||
args=[QWebEngineSettings.DefaultFixedFontSize]),
|
||||
|
||||
'scrolling.smooth':
|
||||
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
||||
try:
|
||||
MAPPINGS['general']['print-element-backgrounds'] = Attribute(
|
||||
MAPPINGS['content.print_element_backgrounds'] = Attribute(
|
||||
QWebEngineSettings.PrintElementBackgrounds)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.8
|
||||
@ -318,4 +312,4 @@ except AttributeError:
|
||||
|
||||
if qtutils.version_check('5.9'):
|
||||
# https://bugreports.qt.io/browse/QTBUG-58650
|
||||
MAPPINGS['content']['cookies-store'] = PersistentCookiePolicy()
|
||||
MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()
|
||||
|
@ -153,20 +153,16 @@ class WebEngineSearch(browsertab.AbstractSearch):
|
||||
callback(found)
|
||||
self._widget.findText(text, flags, wrapped_callback)
|
||||
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
flags = QWebEnginePage.FindFlags(0)
|
||||
if ignore_case == 'smart':
|
||||
if not text.islower():
|
||||
flags |= QWebEnginePage.FindCaseSensitively
|
||||
elif not ignore_case:
|
||||
flags |= QWebEnginePage.FindCaseSensitively
|
||||
if reverse:
|
||||
flags |= QWebEnginePage.FindBackward
|
||||
|
||||
self.text = text
|
||||
self._flags = flags
|
||||
self._find(text, flags, result_cb, 'search')
|
||||
self._flags = QWebEnginePage.FindFlags(0)
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebEnginePage.FindCaseSensitively
|
||||
if reverse:
|
||||
self._flags |= QWebEnginePage.FindBackward
|
||||
|
||||
self._find(text, self._flags, result_cb, 'search')
|
||||
|
||||
def clear(self):
|
||||
self.search_displayed = False
|
||||
@ -699,7 +695,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
error_page = jinja.render(
|
||||
'error.html',
|
||||
title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error="Authentication required", icon='')
|
||||
url=url_string, error="Authentication required")
|
||||
self.set_html(error_page)
|
||||
|
||||
@pyqtSlot('QWebEngineFullScreenRequest')
|
||||
|
@ -28,8 +28,7 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
|
||||
objreg)
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, urlutils, message
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@ -80,10 +79,10 @@ class WebEngineView(QWebEngineView):
|
||||
The new QWebEngineView object.
|
||||
"""
|
||||
debug_type = debug.qenum_key(QWebEnginePage, wintype)
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
background = config.val.tabs.background
|
||||
|
||||
log.webview.debug("createWindow with type {}, background_tabs "
|
||||
"{}".format(debug_type, background_tabs))
|
||||
log.webview.debug("createWindow with type {}, background {}".format(
|
||||
debug_type, background))
|
||||
|
||||
if wintype == QWebEnginePage.WebBrowserWindow:
|
||||
# Shift-Alt-Click
|
||||
@ -95,13 +94,13 @@ class WebEngineView(QWebEngineView):
|
||||
elif wintype == QWebEnginePage.WebBrowserTab:
|
||||
# Middle-click / Ctrl-Click with Shift
|
||||
# FIXME:qtwebengine this also affects target=_blank links...
|
||||
if background_tabs:
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
elif wintype == QWebEnginePage.WebBrowserBackgroundTab:
|
||||
# Middle-click / Ctrl-Click
|
||||
if background_tabs:
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
@ -135,11 +134,11 @@ class WebEnginePage(QWebEnginePage):
|
||||
self._on_feature_permission_requested)
|
||||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
objreg.get('config').changed.connect(self._set_bg_color)
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
|
||||
@config.change_filter('colors', 'webpage.bg')
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
col = config.get('colors', 'webpage.bg')
|
||||
col = config.val.colors.webpage.bg
|
||||
if col is None:
|
||||
col = self._theme_color
|
||||
self.setBackgroundColor(col)
|
||||
@ -148,11 +147,10 @@ class WebEnginePage(QWebEnginePage):
|
||||
def _on_feature_permission_requested(self, url, feature):
|
||||
"""Ask the user for approval for geolocation/media/etc.."""
|
||||
options = {
|
||||
QWebEnginePage.Geolocation: ('content', 'geolocation'),
|
||||
QWebEnginePage.MediaAudioCapture: ('content', 'media-capture'),
|
||||
QWebEnginePage.MediaVideoCapture: ('content', 'media-capture'),
|
||||
QWebEnginePage.MediaAudioVideoCapture:
|
||||
('content', 'media-capture'),
|
||||
QWebEnginePage.Geolocation: 'content.geolocation',
|
||||
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
|
||||
}
|
||||
messages = {
|
||||
QWebEnginePage.Geolocation: 'access your location',
|
||||
@ -214,7 +212,7 @@ class WebEnginePage(QWebEnginePage):
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error=str(error), icon='')
|
||||
url=url_string, error=str(error))
|
||||
|
||||
if error.is_overridable():
|
||||
ignore = shared.ignore_certificate_errors(
|
||||
@ -276,19 +274,12 @@ class WebEnginePage(QWebEnginePage):
|
||||
|
||||
def javaScriptConsoleMessage(self, level, msg, line, source):
|
||||
"""Log javascript messages to qutebrowser's log."""
|
||||
# FIXME:qtwebengine maybe unify this in the tab api somehow?
|
||||
setting = config.get('general', 'log-javascript-console')
|
||||
if setting == 'none':
|
||||
return
|
||||
|
||||
level_to_logger = {
|
||||
QWebEnginePage.InfoMessageLevel: log.js.info,
|
||||
QWebEnginePage.WarningMessageLevel: log.js.warning,
|
||||
QWebEnginePage.ErrorMessageLevel: log.js.error,
|
||||
level_map = {
|
||||
QWebEnginePage.InfoMessageLevel: usertypes.JsLogLevel.info,
|
||||
QWebEnginePage.WarningMessageLevel: usertypes.JsLogLevel.warning,
|
||||
QWebEnginePage.ErrorMessageLevel: usertypes.JsLogLevel.error,
|
||||
}
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
logger = level_to_logger[level]
|
||||
logger(logstring)
|
||||
shared.javascript_log_message(level_map[level], source, line, msg)
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
url: QUrl,
|
||||
|
@ -24,7 +24,7 @@ import os.path
|
||||
from PyQt5.QtNetwork import QNetworkDiskCache
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg, qtutils
|
||||
from qutebrowser.utils import utils, qtutils
|
||||
|
||||
|
||||
class DiskCache(QNetworkDiskCache):
|
||||
@ -35,17 +35,17 @@ class DiskCache(QNetworkDiskCache):
|
||||
super().__init__(parent)
|
||||
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
|
||||
self._set_cache_size()
|
||||
objreg.get('config').changed.connect(self._set_cache_size)
|
||||
config.instance.changed.connect(self._set_cache_size)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, size=self.cacheSize(),
|
||||
maxsize=self.maximumCacheSize(),
|
||||
path=self.cacheDirectory())
|
||||
|
||||
@config.change_filter('storage', 'cache-size')
|
||||
@config.change_filter('content.cache.size')
|
||||
def _set_cache_size(self):
|
||||
"""Set the cache size based on the config."""
|
||||
size = config.get('storage', 'cache-size')
|
||||
size = config.val.content.cache.size
|
||||
if size is None:
|
||||
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
|
||||
|
@ -50,7 +50,7 @@ class RAMCookieJar(QNetworkCookieJar):
|
||||
Return:
|
||||
True if one or more cookies are set for 'url', otherwise False.
|
||||
"""
|
||||
if config.get('content', 'cookies-accept') == 'never':
|
||||
if config.val.content.cookies.accept == 'never':
|
||||
return False
|
||||
else:
|
||||
self.changed.emit()
|
||||
@ -74,10 +74,10 @@ class CookieJar(RAMCookieJar):
|
||||
self._lineparser = lineparser.LineParser(
|
||||
standarddir.data(), 'cookies', binary=True, parent=self)
|
||||
self.parse_cookies()
|
||||
objreg.get('config').changed.connect(self.cookies_store_changed)
|
||||
config.instance.changed.connect(self._on_cookies_store_changed)
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'cookies', self.save, self.changed,
|
||||
config_opt=('content', 'cookies-store'))
|
||||
config_opt='content.cookies.store')
|
||||
|
||||
def parse_cookies(self):
|
||||
"""Parse cookies from lineparser and store them."""
|
||||
@ -105,10 +105,10 @@ class CookieJar(RAMCookieJar):
|
||||
self._lineparser.data = lines
|
||||
self._lineparser.save()
|
||||
|
||||
@config.change_filter('content', 'cookies-store')
|
||||
def cookies_store_changed(self):
|
||||
"""Delete stored cookies if cookies-store changed."""
|
||||
if not config.get('content', 'cookies-store'):
|
||||
@config.change_filter('content.cookies.store')
|
||||
def _on_cookies_store_changed(self):
|
||||
"""Delete stored cookies if cookies.store changed."""
|
||||
if not config.val.content.cookies.store:
|
||||
self._lineparser.data = []
|
||||
self._lineparser.save()
|
||||
self.changed.emit()
|
||||
|
@ -101,13 +101,12 @@ def dirbrowser_html(path):
|
||||
except OSError as e:
|
||||
html = jinja.render('error.html',
|
||||
title="Error while reading directory",
|
||||
url='file:///{}'.format(path), error=str(e),
|
||||
icon='')
|
||||
url='file:///{}'.format(path), error=str(e))
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
files = get_file_list(path, all_files, os.path.isfile)
|
||||
directories = get_file_list(path, all_files, os.path.isdir)
|
||||
html = jinja.render('dirbrowser.html', title=title, url=path, icon='',
|
||||
html = jinja.render('dirbrowser.html', title=title, url=path,
|
||||
parent=parent, files=files, directories=directories)
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
@ -274,7 +274,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
# altogether.
|
||||
reply.netrc_used = True
|
||||
try:
|
||||
net = netrc.netrc(config.get('network', 'netrc-file'))
|
||||
net = netrc.netrc(config.val.content.netrc_file)
|
||||
authenticators = net.authenticators(reply.url().host())
|
||||
if authenticators is not None:
|
||||
(user, _account, password) = authenticators
|
||||
@ -338,7 +338,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
def set_referer(self, req, current_url):
|
||||
"""Set the referer header."""
|
||||
referer_header_conf = config.get('network', 'referer-header')
|
||||
referer_header_conf = config.val.content.headers.referer
|
||||
|
||||
try:
|
||||
if referer_header_conf == 'never':
|
||||
|
@ -20,16 +20,12 @@
|
||||
"""QtWebKit specific qute://* handlers and glue code."""
|
||||
|
||||
import mimetypes
|
||||
import functools
|
||||
import configparser
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
from qutebrowser.browser import pdfjs, qutescheme
|
||||
from qutebrowser.browser.webkit.network import schemehandler, networkreply
|
||||
from qutebrowser.utils import jinja, log, message, objreg, usertypes, qtutils
|
||||
from qutebrowser.config import configexc, configdata
|
||||
from qutebrowser.utils import log, usertypes, qtutils
|
||||
|
||||
|
||||
class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
@ -70,34 +66,6 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
|
||||
self.parent())
|
||||
|
||||
|
||||
class JSBridge(QObject):
|
||||
|
||||
"""Javascript-bridge for special qute://... pages."""
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def set(self, sectname, optname, value):
|
||||
"""Slot to set a setting from qute://settings."""
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if ((sectname, optname) == ('content', 'allow-javascript') and
|
||||
value == 'false'):
|
||||
message.error("Refusing to disable javascript via qute://settings "
|
||||
"as it needs javascript support.")
|
||||
return
|
||||
try:
|
||||
objreg.get('config').set('conf', sectname, optname, value)
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
message.error(str(e))
|
||||
|
||||
|
||||
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)
|
||||
def qute_settings(_url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
config_getter = functools.partial(objreg.get('config').get, raw=True)
|
||||
html = jinja.render('settings.html', title='settings', config=configdata,
|
||||
confget=config_getter)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)
|
||||
def qute_pdfjs(url):
|
||||
"""Handler for qute://pdfjs. Return the pdf.js viewer."""
|
||||
|
@ -168,7 +168,7 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
if width > 1 and height > 1:
|
||||
# fix coordinates according to zoom level
|
||||
zoom = self._elem.webFrame().zoomFactor()
|
||||
if not config.get('ui', 'zoom-text-only'):
|
||||
if not config.val.zoom.text_only:
|
||||
rect["left"] *= zoom
|
||||
rect["top"] *= zoom
|
||||
width *= zoom
|
||||
|
@ -36,9 +36,9 @@ class WebKitInspector(inspector.AbstractWebInspector):
|
||||
self._set_widget(qwebinspector)
|
||||
|
||||
def inspect(self, page):
|
||||
if not config.get('general', 'developer-extras'):
|
||||
if not config.val.content.developer_extras:
|
||||
raise inspector.WebInspectorError(
|
||||
"Please enable developer-extras before using the "
|
||||
"Please enable content.developer_extras before using the "
|
||||
"webinspector!")
|
||||
self._widget.setPage(page)
|
||||
self.show()
|
||||
|
@ -33,7 +33,7 @@ from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import standarddir, objreg, urlutils, qtutils
|
||||
from qutebrowser.utils import standarddir, urlutils, qtutils
|
||||
from qutebrowser.browser import shared
|
||||
|
||||
|
||||
@ -111,12 +111,11 @@ def _set_user_stylesheet():
|
||||
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
|
||||
|
||||
|
||||
def update_settings(section, option):
|
||||
def _update_settings(option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
|
||||
if option in ['scrollbar.hide', 'content.user_stylesheets']:
|
||||
_set_user_stylesheet()
|
||||
|
||||
websettings.update_mappings(MAPPINGS, section, option)
|
||||
websettings.update_mappings(MAPPINGS, option)
|
||||
|
||||
|
||||
def init(_args):
|
||||
@ -132,7 +131,7 @@ def init(_args):
|
||||
QWebSettings.setOfflineStoragePath(
|
||||
os.path.join(data_path, 'offline-storage'))
|
||||
|
||||
if (config.get('general', 'private-browsing') and
|
||||
if (config.val.content.private_browsing and
|
||||
not qtutils.version_check('5.4.2')):
|
||||
# WORKAROUND for https://codereview.qt-project.org/#/c/108936/
|
||||
# Won't work when private browsing is not enabled globally, but that's
|
||||
@ -141,7 +140,7 @@ def init(_args):
|
||||
|
||||
websettings.init_mappings(MAPPINGS)
|
||||
_set_user_stylesheet()
|
||||
objreg.get('config').changed.connect(update_settings)
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
|
||||
def shutdown():
|
||||
@ -152,96 +151,79 @@ def shutdown():
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content': {
|
||||
'allow-images':
|
||||
Attribute(QWebSettings.AutoLoadImages),
|
||||
'allow-javascript':
|
||||
Attribute(QWebSettings.JavascriptEnabled),
|
||||
'javascript-can-open-windows-automatically':
|
||||
Attribute(QWebSettings.JavascriptCanOpenWindows),
|
||||
'javascript-can-close-windows':
|
||||
Attribute(QWebSettings.JavascriptCanCloseWindows),
|
||||
'javascript-can-access-clipboard':
|
||||
Attribute(QWebSettings.JavascriptCanAccessClipboard),
|
||||
'allow-plugins':
|
||||
Attribute(QWebSettings.PluginsEnabled),
|
||||
'webgl':
|
||||
Attribute(QWebSettings.WebGLEnabled),
|
||||
'hyperlink-auditing':
|
||||
Attribute(QWebSettings.HyperlinkAuditingEnabled),
|
||||
'local-content-can-access-remote-urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
'local-content-can-access-file-urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
'cookies-accept':
|
||||
CookiePolicy(),
|
||||
},
|
||||
'network': {
|
||||
'dns-prefetch':
|
||||
Attribute(QWebSettings.DnsPrefetchEnabled),
|
||||
},
|
||||
'input': {
|
||||
'spatial-navigation':
|
||||
Attribute(QWebSettings.SpatialNavigationEnabled),
|
||||
'links-included-in-focus-chain':
|
||||
Attribute(QWebSettings.LinksIncludedInFocusChain),
|
||||
},
|
||||
'fonts': {
|
||||
'web-family-standard':
|
||||
FontFamilySetter(QWebSettings.StandardFont),
|
||||
'web-family-fixed':
|
||||
FontFamilySetter(QWebSettings.FixedFont),
|
||||
'web-family-serif':
|
||||
FontFamilySetter(QWebSettings.SerifFont),
|
||||
'web-family-sans-serif':
|
||||
FontFamilySetter(QWebSettings.SansSerifFont),
|
||||
'web-family-cursive':
|
||||
FontFamilySetter(QWebSettings.CursiveFont),
|
||||
'web-family-fantasy':
|
||||
FontFamilySetter(QWebSettings.FantasyFont),
|
||||
'web-size-minimum':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumFontSize]),
|
||||
'web-size-minimum-logical':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumLogicalFontSize]),
|
||||
'web-size-default':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFontSize]),
|
||||
'web-size-default-fixed':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFixedFontSize]),
|
||||
},
|
||||
'ui': {
|
||||
'zoom-text-only':
|
||||
Attribute(QWebSettings.ZoomTextOnly),
|
||||
'frame-flattening':
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
# user-stylesheet is handled separately
|
||||
'smooth-scrolling':
|
||||
Attribute(QWebSettings.ScrollAnimatorEnabled),
|
||||
#'accelerated-compositing':
|
||||
# Attribute(QWebSettings.AcceleratedCompositingEnabled),
|
||||
#'tiled-backing-store':
|
||||
# Attribute(QWebSettings.TiledBackingStoreEnabled),
|
||||
},
|
||||
'storage': {
|
||||
'offline-web-application-cache':
|
||||
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
'local-storage':
|
||||
Attribute(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
'maximum-pages-in-cache':
|
||||
StaticSetter(QWebSettings.setMaximumPagesInCache),
|
||||
},
|
||||
'general': {
|
||||
'developer-extras':
|
||||
Attribute(QWebSettings.DeveloperExtrasEnabled),
|
||||
'print-element-backgrounds':
|
||||
Attribute(QWebSettings.PrintElementBackgrounds),
|
||||
'xss-auditing':
|
||||
Attribute(QWebSettings.XSSAuditingEnabled),
|
||||
'default-encoding':
|
||||
Setter(QWebSettings.setDefaultTextEncoding),
|
||||
}
|
||||
'content.images':
|
||||
Attribute(QWebSettings.AutoLoadImages),
|
||||
'content.javascript.enabled':
|
||||
Attribute(QWebSettings.JavascriptEnabled),
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attribute(QWebSettings.JavascriptCanOpenWindows),
|
||||
'content.javascript.can_close_tabs':
|
||||
Attribute(QWebSettings.JavascriptCanCloseWindows),
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attribute(QWebSettings.JavascriptCanAccessClipboard),
|
||||
'content.plugins':
|
||||
Attribute(QWebSettings.PluginsEnabled),
|
||||
'content.webgl':
|
||||
Attribute(QWebSettings.WebGLEnabled),
|
||||
'content.hyperlink_auditing':
|
||||
Attribute(QWebSettings.HyperlinkAuditingEnabled),
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
'content.cookies.accept':
|
||||
CookiePolicy(),
|
||||
'content.dns_prefetch':
|
||||
Attribute(QWebSettings.DnsPrefetchEnabled),
|
||||
'content.frame_flattening':
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
'content.cache.appcache':
|
||||
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
'content.local_storage':
|
||||
Attribute(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
'content.cache.maximum_pages':
|
||||
StaticSetter(QWebSettings.setMaximumPagesInCache),
|
||||
'content.developer_extras':
|
||||
Attribute(QWebSettings.DeveloperExtrasEnabled),
|
||||
'content.print_element_backgrounds':
|
||||
Attribute(QWebSettings.PrintElementBackgrounds),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
Setter(QWebSettings.setDefaultTextEncoding),
|
||||
# content.user_stylesheets is handled separately
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attribute(QWebSettings.SpatialNavigationEnabled),
|
||||
'input.links_included_in_focus_chain':
|
||||
Attribute(QWebSettings.LinksIncludedInFocusChain),
|
||||
|
||||
'fonts.web.family.standard':
|
||||
FontFamilySetter(QWebSettings.StandardFont),
|
||||
'fonts.web.family.fixed':
|
||||
FontFamilySetter(QWebSettings.FixedFont),
|
||||
'fonts.web.family.serif':
|
||||
FontFamilySetter(QWebSettings.SerifFont),
|
||||
'fonts.web.family.sans_serif':
|
||||
FontFamilySetter(QWebSettings.SansSerifFont),
|
||||
'fonts.web.family.cursive':
|
||||
FontFamilySetter(QWebSettings.CursiveFont),
|
||||
'fonts.web.family.fantasy':
|
||||
FontFamilySetter(QWebSettings.FantasyFont),
|
||||
'fonts.web.size.minimum':
|
||||
Setter(QWebSettings.setFontSize, args=[QWebSettings.MinimumFontSize]),
|
||||
'fonts.web.size.minimum_logical':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumLogicalFontSize]),
|
||||
'fonts.web.size.default':
|
||||
Setter(QWebSettings.setFontSize, args=[QWebSettings.DefaultFontSize]),
|
||||
'fonts.web.size.default_fixed':
|
||||
Setter(QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFixedFontSize]),
|
||||
|
||||
'zoom.text_only':
|
||||
Attribute(QWebSettings.ZoomTextOnly),
|
||||
'scrolling.smooth':
|
||||
Attribute(QWebSettings.ScrollAnimatorEnabled),
|
||||
}
|
||||
|
@ -27,25 +27,15 @@ import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
||||
QSize)
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.browser.webkit import webview, tabhistory, webkitelem
|
||||
from qutebrowser.browser.webkit.network import webkitqutescheme
|
||||
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize QtWebKit-specific modules."""
|
||||
qapp = QApplication.instance()
|
||||
log.init.debug("Initializing js-bridge...")
|
||||
js_bridge = webkitqutescheme.JSBridge(qapp)
|
||||
objreg.register('js-bridge', js_bridge)
|
||||
|
||||
|
||||
class WebKitAction(browsertab.AbstractAction):
|
||||
|
||||
"""QtWebKit implementations related to web actions."""
|
||||
@ -133,24 +123,21 @@ class WebKitSearch(browsertab.AbstractSearch):
|
||||
self._widget.findText('')
|
||||
self._widget.findText('', QWebPage.HighlightAllOccurrences)
|
||||
|
||||
def search(self, text, *, ignore_case=False, reverse=False,
|
||||
def search(self, text, *, ignore_case='never', reverse=False,
|
||||
result_cb=None):
|
||||
self.search_displayed = True
|
||||
flags = QWebPage.FindWrapsAroundDocument
|
||||
if ignore_case == 'smart':
|
||||
if not text.islower():
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
elif not ignore_case:
|
||||
flags |= QWebPage.FindCaseSensitively
|
||||
self.text = text
|
||||
self._flags = QWebPage.FindWrapsAroundDocument
|
||||
if self._is_case_sensitive(ignore_case):
|
||||
self._flags |= QWebPage.FindCaseSensitively
|
||||
if reverse:
|
||||
flags |= QWebPage.FindBackward
|
||||
self._flags |= QWebPage.FindBackward
|
||||
# We actually search *twice* - once to highlight everything, then again
|
||||
# to get a mark so we can navigate.
|
||||
found = self._widget.findText(text, flags)
|
||||
self._widget.findText(text, flags | QWebPage.HighlightAllOccurrences)
|
||||
self.text = text
|
||||
self._flags = flags
|
||||
self._call_cb(result_cb, found, text, flags, 'search')
|
||||
found = self._widget.findText(text, self._flags)
|
||||
self._widget.findText(text,
|
||||
self._flags | QWebPage.HighlightAllOccurrences)
|
||||
self._call_cb(result_cb, found, text, self._flags, 'search')
|
||||
|
||||
def next_result(self, *, result_cb=None):
|
||||
self.search_displayed = True
|
||||
|
@ -170,7 +170,7 @@ class BrowserPage(QWebPage):
|
||||
title = "Error loading page: {}".format(urlstr)
|
||||
error_html = jinja.render(
|
||||
'error.html',
|
||||
title=title, url=urlstr, error=error_str, icon='')
|
||||
title=title, url=urlstr, error=error_str)
|
||||
errpage.content = error_html.encode('utf-8')
|
||||
errpage.encoding = 'utf-8'
|
||||
return True
|
||||
@ -277,7 +277,7 @@ class BrowserPage(QWebPage):
|
||||
reply.finished.connect(functools.partial(
|
||||
self.display_content, reply, 'image/jpeg'))
|
||||
elif (mimetype in ['application/pdf', 'application/x-pdf'] and
|
||||
config.get('content', 'enable-pdfjs')):
|
||||
config.val.content.pdfjs):
|
||||
# Use pdf.js to display the page
|
||||
self._show_pdfjs(reply)
|
||||
else:
|
||||
@ -304,8 +304,8 @@ class BrowserPage(QWebPage):
|
||||
return
|
||||
|
||||
options = {
|
||||
QWebPage.Notifications: ('content', 'notifications'),
|
||||
QWebPage.Geolocation: ('content', 'geolocation'),
|
||||
QWebPage.Notifications: 'content.notifications',
|
||||
QWebPage.Geolocation: 'content.geolocation',
|
||||
}
|
||||
messages = {
|
||||
QWebPage.Notifications: 'show notifications',
|
||||
@ -384,7 +384,7 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
ua = config.get('network', 'user-agent')
|
||||
ua = config.val.content.headers.user_agent
|
||||
if ua is None:
|
||||
return super().userAgentForUrl(url)
|
||||
else:
|
||||
@ -446,15 +446,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
"""Override javaScriptConsoleMessage to use debug log."""
|
||||
log_javascript_console = config.get('general',
|
||||
'log-javascript-console')
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
logmap = {
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'none': lambda arg: None
|
||||
}
|
||||
logmap[log_javascript_console](logstring)
|
||||
shared.javascript_log_message(usertypes.JsLogLevel.unknown,
|
||||
source, line, msg)
|
||||
|
||||
def acceptNavigationRequest(self,
|
||||
_frame: QWebFrame,
|
||||
|
@ -25,7 +25,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWidgets import QStyleFactory
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
@ -88,7 +88,7 @@ class WebView(QWebView):
|
||||
window=win_id)
|
||||
mode_manager.entered.connect(self.on_mode_entered)
|
||||
mode_manager.left.connect(self.on_mode_left)
|
||||
objreg.get('config').changed.connect(self._set_bg_color)
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
|
||||
def __repr__(self):
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), 100)
|
||||
@ -107,10 +107,10 @@ class WebView(QWebView):
|
||||
# deleted
|
||||
pass
|
||||
|
||||
@config.change_filter('colors', 'webpage.bg')
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
"""Set the webpage background color as configured."""
|
||||
col = config.get('colors', 'webpage.bg')
|
||||
col = config.val.colors.webpage.bg
|
||||
palette = self.palette()
|
||||
if col is None:
|
||||
col = self.style().standardPalette().color(QPalette.Base)
|
||||
@ -135,22 +135,6 @@ class WebView(QWebView):
|
||||
url: The URL to load as QUrl
|
||||
"""
|
||||
self.load(url)
|
||||
if url.scheme() == 'qute':
|
||||
frame = self.page().mainFrame()
|
||||
frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge)
|
||||
|
||||
@pyqtSlot()
|
||||
def add_js_bridge(self):
|
||||
"""Add the javascript bridge for qute://... pages."""
|
||||
frame = self.sender()
|
||||
if not isinstance(frame, QWebFrame):
|
||||
log.webview.error("Got non-QWebFrame {!r} in "
|
||||
"add_js_bridge!".format(frame))
|
||||
return
|
||||
|
||||
if frame.url().scheme() == 'qute':
|
||||
bridge = objreg.get('js-bridge')
|
||||
frame.addToJavaScriptWindowObject('qute', bridge)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
@ -285,10 +269,10 @@ class WebView(QWebView):
|
||||
This is implemented here as we don't need it for QtWebEngine.
|
||||
"""
|
||||
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
background = config.val.tabs.background
|
||||
if e.modifiers() & Qt.ShiftModifier:
|
||||
background_tabs = not background_tabs
|
||||
if background_tabs:
|
||||
background = not background
|
||||
if background:
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
target = usertypes.ClickTarget.tab
|
||||
|
@ -23,33 +23,33 @@ Defined here to avoid circular dependency hell.
|
||||
"""
|
||||
|
||||
|
||||
class CommandError(Exception):
|
||||
class Error(Exception):
|
||||
|
||||
"""Base class for all cmdexc errors."""
|
||||
|
||||
|
||||
class CommandError(Error):
|
||||
|
||||
"""Raised when a command encounters an error while running."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CommandMetaError(Exception):
|
||||
|
||||
"""Common base class for exceptions occurring before a command is run."""
|
||||
|
||||
|
||||
class NoSuchCommandError(CommandMetaError):
|
||||
class NoSuchCommandError(Error):
|
||||
|
||||
"""Raised when a command wasn't found."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentTypeError(CommandMetaError):
|
||||
class ArgumentTypeError(Error):
|
||||
|
||||
"""Raised when an argument had an invalid type."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PrerequisitesError(CommandMetaError):
|
||||
class PrerequisitesError(Error):
|
||||
|
||||
"""Raised when a cmd can't be used because some prerequisites aren't met.
|
||||
|
||||
|
@ -21,7 +21,6 @@
|
||||
|
||||
Module attributes:
|
||||
cmd_dict: A mapping from command-strings to command objects.
|
||||
aliases: A list of all aliases, needed for doc generation.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
@ -30,7 +29,6 @@ from qutebrowser.utils import qtutils, log
|
||||
from qutebrowser.commands import command, cmdexc
|
||||
|
||||
cmd_dict = {}
|
||||
aliases = []
|
||||
|
||||
|
||||
def check_overflow(arg, ctype):
|
||||
@ -88,28 +86,6 @@ class register: # pylint: disable=invalid-name
|
||||
self._name = name
|
||||
self._kwargs = kwargs
|
||||
|
||||
def _get_names(self, func):
|
||||
"""Get the name(s) which should be used for the current command.
|
||||
|
||||
If the name hasn't been overridden explicitly, the function name is
|
||||
transformed.
|
||||
|
||||
If it has been set, it can either be a string which is
|
||||
used directly, or an iterable.
|
||||
|
||||
Args:
|
||||
func: The function to get the name of.
|
||||
|
||||
Return:
|
||||
A list of names, with the main name being the first item.
|
||||
"""
|
||||
if self._name is None:
|
||||
return [func.__name__.lower().replace('_', '-')]
|
||||
elif isinstance(self._name, str):
|
||||
return [self._name]
|
||||
else:
|
||||
return self._name
|
||||
|
||||
def __call__(self, func):
|
||||
"""Register the command before running the function.
|
||||
|
||||
@ -124,17 +100,17 @@ class register: # pylint: disable=invalid-name
|
||||
Return:
|
||||
The original function (unmodified).
|
||||
"""
|
||||
global aliases
|
||||
names = self._get_names(func)
|
||||
log.commands.vdebug("Registering command {}".format(names[0]))
|
||||
for name in names:
|
||||
if name in cmd_dict:
|
||||
raise ValueError("{} is already registered!".format(name))
|
||||
cmd = command.Command(name=names[0], instance=self._instance,
|
||||
if self._name is None:
|
||||
name = func.__name__.lower().replace('_', '-')
|
||||
else:
|
||||
assert isinstance(self._name, str), self._name
|
||||
name = self._name
|
||||
log.commands.vdebug("Registering command {}".format(name))
|
||||
if name in cmd_dict:
|
||||
raise ValueError("{} is already registered!".format(name))
|
||||
cmd = command.Command(name=name, instance=self._instance,
|
||||
handler=func, **self._kwargs)
|
||||
for name in names:
|
||||
cmd_dict[name] = cmd
|
||||
aliases += names[1:]
|
||||
cmd_dict[name] = cmd
|
||||
return func
|
||||
|
||||
|
||||
|
@ -90,7 +90,7 @@ class Command:
|
||||
|
||||
def __init__(self, *, handler, name, instance=None, maxsplit=None,
|
||||
hide=False, modes=None, not_modes=None, debug=False,
|
||||
ignore_args=False, deprecated=False, no_cmd_split=False,
|
||||
deprecated=False, no_cmd_split=False,
|
||||
star_args_optional=False, scope='global', backend=None,
|
||||
no_replace_variables=False):
|
||||
# I really don't know how to solve this in a better way, I tried.
|
||||
@ -121,7 +121,6 @@ class Command:
|
||||
self._scope = scope
|
||||
self._star_args_optional = star_args_optional
|
||||
self.debug = debug
|
||||
self.ignore_args = ignore_args
|
||||
self.handler = handler
|
||||
self.no_cmd_split = no_cmd_split
|
||||
self.backend = backend
|
||||
@ -225,33 +224,31 @@ class Command:
|
||||
else:
|
||||
self.desc = ""
|
||||
|
||||
if not self.ignore_args:
|
||||
for param in signature.parameters.values():
|
||||
# https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
|
||||
# "Python has no explicit syntax for defining positional-only
|
||||
# parameters, but many built-in and extension module functions
|
||||
# (especially those that accept only one or two parameters)
|
||||
# accept them."
|
||||
assert param.kind != inspect.Parameter.POSITIONAL_ONLY
|
||||
if param.name == 'self':
|
||||
continue
|
||||
if self._inspect_special_param(param):
|
||||
continue
|
||||
if (param.kind == inspect.Parameter.KEYWORD_ONLY and
|
||||
param.default is inspect.Parameter.empty):
|
||||
raise TypeError("{}: handler has keyword only argument "
|
||||
"{!r} without default!".format(self.name,
|
||||
param.name))
|
||||
typ = self._get_type(param)
|
||||
is_bool = typ is bool
|
||||
kwargs = self._param_to_argparse_kwargs(param, is_bool)
|
||||
args = self._param_to_argparse_args(param, is_bool)
|
||||
callsig = debug_utils.format_call(
|
||||
self.parser.add_argument, args, kwargs,
|
||||
full=False)
|
||||
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||
param.name, typ, callsig))
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
for param in signature.parameters.values():
|
||||
# https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
|
||||
# "Python has no explicit syntax for defining positional-only
|
||||
# parameters, but many built-in and extension module functions
|
||||
# (especially those that accept only one or two parameters) accept
|
||||
# them."
|
||||
assert param.kind != inspect.Parameter.POSITIONAL_ONLY
|
||||
if param.name == 'self':
|
||||
continue
|
||||
if self._inspect_special_param(param):
|
||||
continue
|
||||
if (param.kind == inspect.Parameter.KEYWORD_ONLY and
|
||||
param.default is inspect.Parameter.empty):
|
||||
raise TypeError("{}: handler has keyword only argument {!r} "
|
||||
"without default!".format(
|
||||
self.name, param.name))
|
||||
typ = self._get_type(param)
|
||||
is_bool = typ is bool
|
||||
kwargs = self._param_to_argparse_kwargs(param, is_bool)
|
||||
args = self._param_to_argparse_args(param, is_bool)
|
||||
callsig = debug_utils.format_call(self.parser.add_argument, args,
|
||||
kwargs, full=False)
|
||||
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||
param.name, typ, callsig))
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
return signature.parameters.values()
|
||||
|
||||
def _param_to_argparse_kwargs(self, param, is_bool):
|
||||
@ -453,12 +450,6 @@ class Command:
|
||||
kwargs = {}
|
||||
signature = inspect.signature(self.handler)
|
||||
|
||||
if self.ignore_args:
|
||||
if self._instance is not None:
|
||||
param = list(signature.parameters.values())[0]
|
||||
self._get_self_arg(win_id, param, args)
|
||||
return args, kwargs
|
||||
|
||||
for i, param in enumerate(signature.parameters.values()):
|
||||
arg_info = self.get_arg_info(param)
|
||||
if i == 0 and self._instance is not None:
|
||||
|
@ -25,7 +25,7 @@ import re
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
|
||||
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
|
||||
from qutebrowser.misc import split
|
||||
@ -81,19 +81,17 @@ def replace_variables(win_id, arglist):
|
||||
return args
|
||||
|
||||
|
||||
class CommandRunner(QObject):
|
||||
class CommandParser:
|
||||
|
||||
"""Parse and run qutebrowser commandline commands.
|
||||
"""Parse qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window this CommandRunner is associated with.
|
||||
|
||||
_partial_match: Whether to allow partial command matches.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, partial_match=False, parent=None):
|
||||
super().__init__(parent)
|
||||
def __init__(self, partial_match=False):
|
||||
self._partial_match = partial_match
|
||||
self._win_id = win_id
|
||||
|
||||
def _get_alias(self, text, default=None):
|
||||
"""Get an alias from the config.
|
||||
@ -108,9 +106,10 @@ class CommandRunner(QObject):
|
||||
"""
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
try:
|
||||
alias = config.get('aliases', parts[0])
|
||||
except (configexc.NoOptionError, configexc.NoSectionError):
|
||||
alias = config.val.aliases[parts[0]]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
@ -119,7 +118,7 @@ class CommandRunner(QObject):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def parse_all(self, text, aliases=True, *args, **kwargs):
|
||||
def _parse_all_gen(self, text, aliases=True, *args, **kwargs):
|
||||
"""Split a command on ;; and parse all parts.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
@ -154,6 +153,10 @@ class CommandRunner(QObject):
|
||||
for sub in sub_texts:
|
||||
yield self.parse(sub, *args, **kwargs)
|
||||
|
||||
def parse_all(self, *args, **kwargs):
|
||||
"""Wrapper over parse_all."""
|
||||
return list(self._parse_all_gen(*args, **kwargs))
|
||||
|
||||
def parse(self, text, *, fallback=False, keep=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
@ -253,6 +256,20 @@ class CommandRunner(QObject):
|
||||
# already.
|
||||
return split_args
|
||||
|
||||
|
||||
class CommandRunner(QObject):
|
||||
|
||||
"""Parse and run qutebrowser commandline commands.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window this CommandRunner is associated with.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, partial_match=False, parent=None):
|
||||
super().__init__(parent)
|
||||
self._parser = CommandParser(partial_match=partial_match)
|
||||
self._win_id = win_id
|
||||
|
||||
def run(self, text, count=None):
|
||||
"""Parse a command from a line of text and run it.
|
||||
|
||||
@ -267,7 +284,7 @@ class CommandRunner(QObject):
|
||||
window=self._win_id)
|
||||
cur_mode = mode_manager.mode
|
||||
|
||||
for result in self.parse_all(text):
|
||||
for result in self._parser.parse_all(text):
|
||||
if result.cmd.no_replace_variables:
|
||||
args = result.args
|
||||
else:
|
||||
@ -294,7 +311,7 @@ class CommandRunner(QObject):
|
||||
"""Run a command and display exceptions in the statusbar."""
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
except cmdexc.Error as e:
|
||||
message.error(str(e), stack=traceback.format_exc())
|
||||
|
||||
@pyqtSlot(str, int)
|
||||
@ -306,5 +323,5 @@ class CommandRunner(QObject):
|
||||
"""
|
||||
try:
|
||||
self.run(text, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
except cmdexc.Error as e:
|
||||
message.error(str(e), stack=traceback.format_exc())
|
||||
|
@ -376,7 +376,7 @@ def _lookup_path(cmd):
|
||||
"""
|
||||
directories = [
|
||||
os.path.join(standarddir.data(), "userscripts"),
|
||||
os.path.join(standarddir.system_data(), "userscripts"),
|
||||
os.path.join(standarddir.data(system=True), "userscripts"),
|
||||
]
|
||||
for directory in directories:
|
||||
cmd_path = os.path.join(directory, cmd)
|
||||
@ -417,7 +417,7 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
|
||||
lambda cmd:
|
||||
log.commands.debug("Got userscript command: {}".format(cmd)))
|
||||
runner.got_cmd.connect(commandrunner.run_safely)
|
||||
user_agent = config.get('network', 'user-agent')
|
||||
user_agent = config.val.content.headers.user_agent
|
||||
if user_agent is not None:
|
||||
env['QUTE_USER_AGENT'] = user_agent
|
||||
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
"""Completer attached to a CompletionView."""
|
||||
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
@ -27,6 +29,11 @@ from qutebrowser.utils import log, utils, debug
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
|
||||
|
||||
# Context passed into all completion functions
|
||||
CompletionInfo = collections.namedtuple('CompletionInfo',
|
||||
['config', 'keyconf'])
|
||||
|
||||
|
||||
class Completer(QObject):
|
||||
|
||||
"""Completer which manages completions in a CompletionView.
|
||||
@ -34,7 +41,6 @@ class Completer(QObject):
|
||||
Attributes:
|
||||
_cmd: The statusbar Command object this completer belongs to.
|
||||
_ignore_change: Whether to ignore the next completion update.
|
||||
_win_id: The window ID this completer is in.
|
||||
_timer: The timer used to trigger the completion update.
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
@ -42,9 +48,8 @@ class Completer(QObject):
|
||||
_last_completion_func: The completion function used for the last text.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, win_id, parent=None):
|
||||
def __init__(self, cmd, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._cmd = cmd
|
||||
self._ignore_change = False
|
||||
self._timer = QTimer()
|
||||
@ -106,7 +111,7 @@ class Completer(QObject):
|
||||
"""
|
||||
if not s:
|
||||
return "''"
|
||||
elif any(c in s for c in ' \'\t\n\\'):
|
||||
elif any(c in s for c in ' "\'\t\n\\'):
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
@ -123,8 +128,8 @@ class Completer(QObject):
|
||||
if not text or not text.strip():
|
||||
# Only ":", empty part under the cursor with nothing before/after
|
||||
return [], '', []
|
||||
runner = runners.CommandRunner(self._win_id)
|
||||
result = runner.parse(text, fallback=True, keep=True)
|
||||
parser = runners.CommandParser()
|
||||
result = parser.parse(text, fallback=True, keep=True)
|
||||
parts = [x for x in result.cmdline if x]
|
||||
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
||||
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
||||
@ -164,7 +169,7 @@ class Completer(QObject):
|
||||
if maxsplit is None:
|
||||
text = self._quote(text)
|
||||
model = self._model()
|
||||
if model.count() == 1 and config.get('completion', 'quick-complete'):
|
||||
if model.count() == 1 and config.val.completion.quick:
|
||||
# If we only have one item, we want to apply it immediately
|
||||
# and go on to the next part.
|
||||
self._change_completed_part(text, before, after, immediate=True)
|
||||
@ -233,7 +238,9 @@ class Completer(QObject):
|
||||
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
||||
with debug.log_time(log.completion,
|
||||
'Starting {} completion'.format(func.__name__)):
|
||||
model = func(*args)
|
||||
info = CompletionInfo(config=config.instance,
|
||||
keyconf=config.key_instance)
|
||||
model = func(*args, info=info)
|
||||
with debug.log_time(log.completion, 'Set completion model'):
|
||||
completion.set_model(model)
|
||||
|
||||
|
@ -30,8 +30,8 @@ from PyQt5.QtCore import QRectF, QSize, Qt
|
||||
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
|
||||
QAbstractTextDocumentLayout)
|
||||
|
||||
from qutebrowser.config import config, configexc, style
|
||||
from qutebrowser.utils import qtutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import qtutils, jinja
|
||||
|
||||
|
||||
class CompletionItemDelegate(QStyledItemDelegate):
|
||||
@ -147,16 +147,15 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
# We can't use drawContents because then the color would be ignored.
|
||||
clip = QRectF(0, 0, rect.width(), rect.height())
|
||||
self._painter.save()
|
||||
|
||||
if self._opt.state & QStyle.State_Selected:
|
||||
option = 'completion.item.selected.fg'
|
||||
color = config.val.colors.completion.item.selected.fg
|
||||
elif not self._opt.state & QStyle.State_Enabled:
|
||||
option = 'completion.category.fg'
|
||||
color = config.val.colors.completion.category.fg
|
||||
else:
|
||||
option = 'completion.fg'
|
||||
try:
|
||||
self._painter.setPen(config.get('colors', option))
|
||||
except configexc.NoOptionError:
|
||||
self._painter.setPen(config.get('colors', 'completion.fg'))
|
||||
color = config.val.colors.completion.fg
|
||||
self._painter.setPen(color)
|
||||
|
||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||
ctx.palette.setColor(QPalette.Text, self._painter.pen().color())
|
||||
if clip.isValid():
|
||||
@ -188,13 +187,17 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
self._doc = QTextDocument(self)
|
||||
self._doc.setDefaultFont(self._opt.font)
|
||||
self._doc.setDefaultTextOption(text_option)
|
||||
self._doc.setDefaultStyleSheet(style.get_stylesheet("""
|
||||
.highlight {
|
||||
color: {{ color['completion.match.fg'] }};
|
||||
}
|
||||
"""))
|
||||
self._doc.setDocumentMargin(2)
|
||||
|
||||
stylesheet = """
|
||||
.highlight {
|
||||
color: {{ conf.colors.completion.match.fg }};
|
||||
}
|
||||
"""
|
||||
with jinja.environment.no_autoescape():
|
||||
template = jinja.environment.from_string(stylesheet)
|
||||
self._doc.setDefaultStyleSheet(template.render(conf=config.val))
|
||||
|
||||
if index.parent().isValid():
|
||||
view = self.parent()
|
||||
pattern = view.pattern
|
||||
@ -209,7 +212,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
else:
|
||||
self._doc.setHtml(
|
||||
'<span style="font: {};">{}</span>'.format(
|
||||
html.escape(config.get('fonts', 'completion.category')),
|
||||
html.escape(config.val.fonts.completion.category),
|
||||
html.escape(self._opt.text)))
|
||||
|
||||
def _draw_focus_rect(self):
|
||||
|
@ -26,9 +26,9 @@ subclasses to provide completions.
|
||||
from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy, QStyleFactory
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.completion import completiondelegate
|
||||
from qutebrowser.utils import utils, usertypes, objreg, debug, log
|
||||
from qutebrowser.utils import utils, usertypes, debug, log
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
|
||||
|
||||
@ -57,27 +57,27 @@ class CompletionView(QTreeView):
|
||||
# don't define that in this stylesheet.
|
||||
STYLESHEET = """
|
||||
QTreeView {
|
||||
font: {{ font['completion'] }};
|
||||
background-color: {{ color['completion.bg'] }};
|
||||
alternate-background-color: {{ color['completion.alternate-bg'] }};
|
||||
font: {{ conf.fonts.completion.entry }};
|
||||
background-color: {{ conf.colors.completion.even.bg }};
|
||||
alternate-background-color: {{ conf.colors.completion.odd.bg }};
|
||||
outline: 0;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
QTreeView::item:disabled {
|
||||
background-color: {{ color['completion.category.bg'] }};
|
||||
background-color: {{ conf.colors.completion.category.bg }};
|
||||
border-top: 1px solid
|
||||
{{ color['completion.category.border.top'] }};
|
||||
{{ conf.colors.completion.category.border.top }};
|
||||
border-bottom: 1px solid
|
||||
{{ color['completion.category.border.bottom'] }};
|
||||
{{ conf.colors.completion.category.border.bottom }};
|
||||
}
|
||||
|
||||
QTreeView::item:selected, QTreeView::item:selected:hover {
|
||||
border-top: 1px solid
|
||||
{{ color['completion.item.selected.border.top'] }};
|
||||
{{ conf.colors.completion.item.selected.border.top }};
|
||||
border-bottom: 1px solid
|
||||
{{ color['completion.item.selected.border.bottom'] }};
|
||||
background-color: {{ color['completion.item.selected.bg'] }};
|
||||
{{ conf.colors.completion.item.selected.border.bottom }};
|
||||
background-color: {{ conf.colors.completion.item.selected.bg }};
|
||||
}
|
||||
|
||||
QTreeView:item::hover {
|
||||
@ -85,14 +85,14 @@ class CompletionView(QTreeView):
|
||||
}
|
||||
|
||||
QTreeView QScrollBar {
|
||||
width: {{ config.get('completion', 'scrollbar-width') }}px;
|
||||
background: {{ color['completion.scrollbar.bg'] }};
|
||||
width: {{ conf.completion.scrollbar.width }}px;
|
||||
background: {{ conf.colors.completion.scrollbar.bg }};
|
||||
}
|
||||
|
||||
QTreeView QScrollBar::handle {
|
||||
background: {{ color['completion.scrollbar.fg'] }};
|
||||
border: {{ config.get('completion', 'scrollbar-padding') }}px solid
|
||||
{{ color['completion.scrollbar.bg'] }};
|
||||
background: {{ conf.colors.completion.scrollbar.fg }};
|
||||
border: {{ conf.completion.scrollbar.padding }}px solid
|
||||
{{ conf.colors.completion.scrollbar.bg }};
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
@ -109,14 +109,14 @@ class CompletionView(QTreeView):
|
||||
super().__init__(parent)
|
||||
self.pattern = ''
|
||||
self._win_id = win_id
|
||||
objreg.get('config').changed.connect(self._on_config_changed)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
self._active = False
|
||||
|
||||
self._delegate = completiondelegate.CompletionItemDelegate(self)
|
||||
self.setItemDelegate(self._delegate)
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self.setHeaderHidden(True)
|
||||
self.setAlternatingRowColors(True)
|
||||
@ -139,11 +139,9 @@ class CompletionView(QTreeView):
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def _on_config_changed(self, section, option):
|
||||
if section != 'completion':
|
||||
return
|
||||
if option in ['height', 'shrink']:
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option in ['completion.height', 'completion.shrink']:
|
||||
self.update_geometry.emit()
|
||||
|
||||
def _resize_columns(self):
|
||||
@ -262,9 +260,9 @@ class CompletionView(QTreeView):
|
||||
count = self.model().count()
|
||||
if count == 0:
|
||||
self.hide()
|
||||
elif count == 1 and config.get('completion', 'quick-complete'):
|
||||
elif count == 1 and config.val.completion.quick:
|
||||
self.hide()
|
||||
elif config.get('completion', 'show') == 'auto':
|
||||
elif config.val.completion.show == 'auto':
|
||||
self.show()
|
||||
|
||||
def set_model(self, model):
|
||||
@ -306,7 +304,7 @@ class CompletionView(QTreeView):
|
||||
self._maybe_show()
|
||||
|
||||
def _maybe_show(self):
|
||||
if (config.get('completion', 'show') == 'always' and
|
||||
if (config.val.completion.show == 'always' and
|
||||
self.model().count() > 0):
|
||||
self.show()
|
||||
else:
|
||||
@ -314,7 +312,7 @@ class CompletionView(QTreeView):
|
||||
|
||||
def _maybe_update_geometry(self):
|
||||
"""Emit the update_geometry signal if the config says so."""
|
||||
if config.get('completion', 'shrink'):
|
||||
if config.val.completion.shrink:
|
||||
self.update_geometry.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
@ -329,14 +327,14 @@ class CompletionView(QTreeView):
|
||||
def sizeHint(self):
|
||||
"""Get the completion size according to the config."""
|
||||
# Get the configured height/percentage.
|
||||
confheight = str(config.get('completion', 'height'))
|
||||
confheight = str(config.val.completion.height)
|
||||
if confheight.endswith('%'):
|
||||
perc = int(confheight.rstrip('%'))
|
||||
height = self.window().height() * perc / 100
|
||||
else:
|
||||
height = int(confheight)
|
||||
# Shrink to content size if needed and shrinking is enabled
|
||||
if config.get('completion', 'shrink'):
|
||||
if config.val.completion.shrink:
|
||||
contents_height = (
|
||||
self.viewportSizeHint().height() +
|
||||
self.horizontalScrollBar().sizeHint().height())
|
||||
|
@ -20,78 +20,63 @@
|
||||
"""Functions that return config-related completion models."""
|
||||
|
||||
from qutebrowser.config import configdata, configexc
|
||||
from qutebrowser.completion.models import completionmodel, listcategory
|
||||
from qutebrowser.utils import objreg
|
||||
from qutebrowser.completion.models import completionmodel, listcategory, util
|
||||
from qutebrowser.commands import cmdutils
|
||||
|
||||
|
||||
def section():
|
||||
"""A CompletionModel filled with settings sections."""
|
||||
def option(*, info):
|
||||
"""A CompletionModel filled with settings and their descriptions."""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
sections = ((name, configdata.SECTION_DESC[name].splitlines()[0].strip())
|
||||
for name in configdata.DATA)
|
||||
model.add_category(listcategory.ListCategory("Sections", sorted(sections)))
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt in configdata.DATA.values())
|
||||
model.add_category(listcategory.ListCategory("Options", sorted(options)))
|
||||
return model
|
||||
|
||||
|
||||
def option(sectname):
|
||||
"""A CompletionModel filled with settings and their descriptions.
|
||||
|
||||
Args:
|
||||
sectname: The name of the config section this model shows.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
try:
|
||||
sectdata = configdata.DATA[sectname]
|
||||
except KeyError:
|
||||
return None
|
||||
options = []
|
||||
for name in sectdata:
|
||||
try:
|
||||
desc = sectdata.descriptions[name]
|
||||
except (KeyError, AttributeError):
|
||||
# Some stuff (especially ValueList items) don't have a
|
||||
# description.
|
||||
desc = ""
|
||||
else:
|
||||
desc = desc.splitlines()[0]
|
||||
config = objreg.get('config')
|
||||
val = config.get(sectname, name, raw=True)
|
||||
options.append((name, desc, val))
|
||||
model.add_category(listcategory.ListCategory(sectname, sorted(options)))
|
||||
return model
|
||||
|
||||
|
||||
def value(sectname, optname):
|
||||
def value(optname, *_values, info):
|
||||
"""A CompletionModel filled with setting values.
|
||||
|
||||
Args:
|
||||
sectname: The name of the config section this model shows.
|
||||
optname: The name of the config option this model shows.
|
||||
_values: The values already provided on the command line.
|
||||
info: A CompletionInfo instance.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
config = objreg.get('config')
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
|
||||
try:
|
||||
current = config.get(sectname, optname, raw=True) or '""'
|
||||
except (configexc.NoSectionError, configexc.NoOptionError):
|
||||
current = info.config.get_str(optname) or '""'
|
||||
except configexc.NoOptionError:
|
||||
return None
|
||||
|
||||
default = configdata.DATA[sectname][optname].default() or '""'
|
||||
|
||||
if hasattr(configdata.DATA[sectname], 'valtype'):
|
||||
# Same type for all values (ValueList)
|
||||
vals = configdata.DATA[sectname].valtype.complete()
|
||||
else:
|
||||
if optname is None:
|
||||
raise ValueError("optname may only be None for ValueList "
|
||||
"sections, but {} is not!".format(sectname))
|
||||
# Different type for each value (KeyValue)
|
||||
vals = configdata.DATA[sectname][optname].typ.complete()
|
||||
|
||||
opt = info.config.get_opt(optname)
|
||||
default = opt.typ.to_str(opt.default)
|
||||
cur_cat = listcategory.ListCategory("Current/Default",
|
||||
[(current, "Current value"), (default, "Default value")])
|
||||
model.add_category(cur_cat)
|
||||
|
||||
vals = opt.typ.complete()
|
||||
if vals is not None:
|
||||
model.add_category(listcategory.ListCategory("Completions",
|
||||
sorted(vals)))
|
||||
return model
|
||||
|
||||
|
||||
def bind(key, *, info):
|
||||
"""A CompletionModel filled with all bindable commands and descriptions.
|
||||
|
||||
Args:
|
||||
key: the key being bound.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
|
||||
cmd_text = info.keyconf.get_command(key, 'normal')
|
||||
|
||||
if cmd_text:
|
||||
cmd_name = cmd_text.split(' ')[0]
|
||||
cmd = cmdutils.cmd_dict.get(cmd_name)
|
||||
data = [(cmd_text, cmd.desc, key)]
|
||||
model.add_category(listcategory.ListCategory("Current", data))
|
||||
|
||||
cmdlist = util.get_cmd_completions(info, include_hidden=True,
|
||||
include_aliases=True)
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
return model
|
||||
|
@ -38,9 +38,9 @@ class HistoryCategory(QSqlQueryModel):
|
||||
self.name = "History"
|
||||
|
||||
# replace ' in timestamp-format to avoid breaking the query
|
||||
timestamp_format = config.val.completion.timestamp_format
|
||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||
.format(config.get('completion', 'timestamp-format')
|
||||
.replace("'", "`")))
|
||||
.format(timestamp_format.replace("'", "`")))
|
||||
|
||||
self._query = sql.Query(' '.join([
|
||||
"SELECT url, title, {}".format(timefmt),
|
||||
@ -58,7 +58,7 @@ class HistoryCategory(QSqlQueryModel):
|
||||
|
||||
def _atime_expr(self):
|
||||
"""If max_items is set, return an expression to limit the query."""
|
||||
max_items = config.get('completion', 'web-history-max-items')
|
||||
max_items = config.val.completion.web_history_max_items
|
||||
# HistoryCategory should not be added to the completion in that case.
|
||||
assert max_items != 0
|
||||
|
||||
|
@ -19,46 +19,35 @@
|
||||
|
||||
"""Functions that return miscellaneous completion models."""
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.config import configdata
|
||||
from qutebrowser.utils import objreg, log
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.completion.models import completionmodel, listcategory
|
||||
from qutebrowser.completion.models import completionmodel, listcategory, util
|
||||
|
||||
|
||||
def command():
|
||||
def command(*, info):
|
||||
"""A CompletionModel filled with non-hidden commands and descriptions."""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
|
||||
cmdlist = _get_cmd_completions(include_aliases=True, include_hidden=False)
|
||||
cmdlist = util.get_cmd_completions(info, include_aliases=True,
|
||||
include_hidden=False)
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
return model
|
||||
|
||||
|
||||
def helptopic():
|
||||
def helptopic(*, info):
|
||||
"""A CompletionModel filled with help topics."""
|
||||
model = completionmodel.CompletionModel()
|
||||
|
||||
cmdlist = _get_cmd_completions(include_aliases=False, include_hidden=True,
|
||||
prefix=':')
|
||||
settings = []
|
||||
for sectname, sectdata in configdata.DATA.items():
|
||||
for optname in sectdata:
|
||||
try:
|
||||
desc = sectdata.descriptions[optname]
|
||||
except (KeyError, AttributeError):
|
||||
# Some stuff (especially ValueList items) don't have a
|
||||
# description.
|
||||
desc = ""
|
||||
else:
|
||||
desc = desc.splitlines()[0]
|
||||
name = '{}->{}'.format(sectname, optname)
|
||||
settings.append((name, desc))
|
||||
cmdlist = util.get_cmd_completions(info, include_aliases=False,
|
||||
include_hidden=True, prefix=':')
|
||||
settings = ((opt.name, opt.description)
|
||||
for opt in configdata.DATA.values())
|
||||
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
model.add_category(listcategory.ListCategory("Settings", sorted(settings)))
|
||||
return model
|
||||
|
||||
|
||||
def quickmark():
|
||||
def quickmark(*, info=None): # pylint: disable=unused-argument
|
||||
"""A CompletionModel filled with all quickmarks."""
|
||||
def delete(data):
|
||||
"""Delete a quickmark from the completion menu."""
|
||||
@ -74,7 +63,7 @@ def quickmark():
|
||||
return model
|
||||
|
||||
|
||||
def bookmark():
|
||||
def bookmark(*, info=None): # pylint: disable=unused-argument
|
||||
"""A CompletionModel filled with all bookmarks."""
|
||||
def delete(data):
|
||||
"""Delete a bookmark from the completion menu."""
|
||||
@ -90,7 +79,7 @@ def bookmark():
|
||||
return model
|
||||
|
||||
|
||||
def session():
|
||||
def session(*, info=None): # pylint: disable=unused-argument
|
||||
"""A CompletionModel filled with session names."""
|
||||
model = completionmodel.CompletionModel()
|
||||
try:
|
||||
@ -103,7 +92,7 @@ def session():
|
||||
return model
|
||||
|
||||
|
||||
def buffer():
|
||||
def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
"""A model to complete on open tabs across all windows.
|
||||
|
||||
Used for switching the buffer command.
|
||||
@ -133,51 +122,3 @@ def buffer():
|
||||
model.add_category(cat)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def bind(key):
|
||||
"""A CompletionModel filled with all bindable commands and descriptions.
|
||||
|
||||
Args:
|
||||
key: the key being bound.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
|
||||
cmd_text = objreg.get('key-config').get_bindings_for('normal').get(key)
|
||||
|
||||
if cmd_text:
|
||||
cmd_name = cmd_text.split(' ')[0]
|
||||
cmd = cmdutils.cmd_dict.get(cmd_name)
|
||||
data = [(cmd_text, cmd.desc, key)]
|
||||
model.add_category(listcategory.ListCategory("Current", data))
|
||||
|
||||
cmdlist = _get_cmd_completions(include_hidden=True, include_aliases=True)
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
return model
|
||||
|
||||
|
||||
def _get_cmd_completions(include_hidden, include_aliases, prefix=''):
|
||||
"""Get a list of completions info for commands, sorted by name.
|
||||
|
||||
Args:
|
||||
include_hidden: True to include commands annotated with hide=True.
|
||||
include_aliases: True to include command aliases.
|
||||
prefix: String to append to the command name.
|
||||
|
||||
Return: A list of tuples of form (name, description, bindings).
|
||||
"""
|
||||
assert cmdutils.cmd_dict
|
||||
cmdlist = []
|
||||
cmd_to_keys = objreg.get('key-config').get_reverse_bindings_for('normal')
|
||||
for obj in set(cmdutils.cmd_dict.values()):
|
||||
hide_debug = obj.debug and not objreg.get('args').debug
|
||||
hide_hidden = obj.hide and not include_hidden
|
||||
if not (hide_debug or hide_hidden or obj.deprecated):
|
||||
bindings = ', '.join(cmd_to_keys.get(obj.name, []))
|
||||
cmdlist.append((prefix + obj.name, obj.desc, bindings))
|
||||
|
||||
if include_aliases:
|
||||
for name, cmd in config.section('aliases').items():
|
||||
bindings = ', '.join(cmd_to_keys.get(name, []))
|
||||
cmdlist.append((name, "Alias for '{}'".format(cmd), bindings))
|
||||
|
||||
return sorted(cmdlist)
|
||||
|
@ -22,7 +22,6 @@
|
||||
from qutebrowser.completion.models import (completionmodel, listcategory,
|
||||
histcategory)
|
||||
from qutebrowser.utils import log, objreg
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
_URLCOL = 0
|
||||
@ -50,7 +49,7 @@ def _delete_quickmark(data):
|
||||
quickmark_manager.delete(name)
|
||||
|
||||
|
||||
def url():
|
||||
def url(*, info):
|
||||
"""A model which combines bookmarks, quickmarks and web history URLs.
|
||||
|
||||
Used for the `open` command.
|
||||
@ -66,7 +65,7 @@ def url():
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark))
|
||||
|
||||
if config.get('completion', 'web-history-max-items') != 0:
|
||||
if info.config.get('completion.web_history_max_items') != 0:
|
||||
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
|
||||
model.add_category(hist_cat)
|
||||
return model
|
||||
|
52
qutebrowser/completion/models/util.py
Normal file
52
qutebrowser/completion/models/util.py
Normal file
@ -0,0 +1,52 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Utility functions for completion models."""
|
||||
|
||||
from qutebrowser.utils import objreg
|
||||
from qutebrowser.commands import cmdutils
|
||||
|
||||
|
||||
def get_cmd_completions(info, include_hidden, include_aliases, prefix=''):
|
||||
"""Get a list of completions info for commands, sorted by name.
|
||||
|
||||
Args:
|
||||
info: The CompletionInfo.
|
||||
include_hidden: True to include commands annotated with hide=True.
|
||||
include_aliases: True to include command aliases.
|
||||
prefix: String to append to the command name.
|
||||
|
||||
Return: A list of tuples of form (name, description, bindings).
|
||||
"""
|
||||
assert cmdutils.cmd_dict
|
||||
cmdlist = []
|
||||
cmd_to_keys = info.keyconf.get_reverse_bindings_for('normal')
|
||||
for obj in set(cmdutils.cmd_dict.values()):
|
||||
hide_debug = obj.debug and not objreg.get('args').debug
|
||||
hide_hidden = obj.hide and not include_hidden
|
||||
if not (hide_debug or hide_hidden or obj.deprecated):
|
||||
bindings = ', '.join(cmd_to_keys.get(obj.name, []))
|
||||
cmdlist.append((prefix + obj.name, obj.desc, bindings))
|
||||
|
||||
if include_aliases:
|
||||
for name, cmd in info.config.get('aliases').items():
|
||||
bindings = ', '.join(cmd_to_keys.get(name, []))
|
||||
cmdlist.append((name, "Alias for '{}'".format(cmd), bindings))
|
||||
|
||||
return sorted(cmdlist)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2209
qutebrowser/config/configdata.yml
Normal file
2209
qutebrowser/config/configdata.yml
Normal file
File diff suppressed because it is too large
Load Diff
760
qutebrowser/config/configdiff.py
Normal file
760
qutebrowser/config/configdiff.py
Normal file
@ -0,0 +1,760 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Code to show a diff of the legacy config format."""
|
||||
|
||||
import difflib
|
||||
import os.path
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.utils import standarddir
|
||||
|
||||
|
||||
OLD_CONF = """
|
||||
[general]
|
||||
ignore-case = smart
|
||||
startpage = https://start.duckduckgo.com
|
||||
yank-ignored-url-parameters = ref,utm_source,utm_medium,utm_campaign,utm_term,utm_content
|
||||
default-open-dispatcher =
|
||||
default-page = ${startpage}
|
||||
auto-search = naive
|
||||
auto-save-config = true
|
||||
auto-save-interval = 15000
|
||||
editor = gvim -f "{}"
|
||||
editor-encoding = utf-8
|
||||
private-browsing = false
|
||||
developer-extras = false
|
||||
print-element-backgrounds = true
|
||||
xss-auditing = false
|
||||
default-encoding = iso-8859-1
|
||||
new-instance-open-target = tab
|
||||
new-instance-open-target.window = last-focused
|
||||
log-javascript-console = debug
|
||||
save-session = false
|
||||
session-default-name =
|
||||
url-incdec-segments = path,query
|
||||
[ui]
|
||||
history-session-interval = 30
|
||||
zoom-levels = 25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,200%,250%,300%,400%,500%
|
||||
default-zoom = 100%
|
||||
downloads-position = top
|
||||
status-position = bottom
|
||||
message-timeout = 2000
|
||||
message-unfocused = false
|
||||
confirm-quit = never
|
||||
zoom-text-only = false
|
||||
frame-flattening = false
|
||||
user-stylesheet =
|
||||
hide-scrollbar = true
|
||||
smooth-scrolling = false
|
||||
remove-finished-downloads = -1
|
||||
hide-statusbar = false
|
||||
statusbar-padding = 1,1,0,0
|
||||
window-title-format = {perc}{title}{title_sep}qutebrowser
|
||||
modal-js-dialog = false
|
||||
hide-wayland-decoration = false
|
||||
keyhint-blacklist =
|
||||
keyhint-delay = 500
|
||||
prompt-radius = 8
|
||||
prompt-filebrowser = true
|
||||
[network]
|
||||
do-not-track = true
|
||||
accept-language = en-US,en
|
||||
referer-header = same-domain
|
||||
user-agent =
|
||||
proxy = system
|
||||
proxy-dns-requests = true
|
||||
ssl-strict = ask
|
||||
dns-prefetch = true
|
||||
custom-headers =
|
||||
netrc-file =
|
||||
[completion]
|
||||
show = always
|
||||
download-path-suggestion = path
|
||||
timestamp-format = %Y-%m-%d
|
||||
height = 50%
|
||||
cmd-history-max-items = 100
|
||||
web-history-max-items = -1
|
||||
quick-complete = true
|
||||
shrink = false
|
||||
scrollbar-width = 12
|
||||
scrollbar-padding = 2
|
||||
[input]
|
||||
timeout = 500
|
||||
partial-timeout = 5000
|
||||
insert-mode-on-plugins = false
|
||||
auto-leave-insert-mode = true
|
||||
auto-insert-mode = false
|
||||
forward-unbound-keys = auto
|
||||
spatial-navigation = false
|
||||
links-included-in-focus-chain = true
|
||||
rocker-gestures = false
|
||||
mouse-zoom-divider = 512
|
||||
[tabs]
|
||||
background-tabs = false
|
||||
select-on-remove = next
|
||||
new-tab-position = next
|
||||
new-tab-position-explicit = last
|
||||
last-close = ignore
|
||||
show = always
|
||||
show-switching-delay = 800
|
||||
wrap = true
|
||||
movable = true
|
||||
close-mouse-button = middle
|
||||
position = top
|
||||
show-favicons = true
|
||||
favicon-scale = 1.0
|
||||
width = 20%
|
||||
pinned-width = 43
|
||||
indicator-width = 3
|
||||
tabs-are-windows = false
|
||||
title-format = {index}: {title}
|
||||
title-format-pinned = {index}
|
||||
title-alignment = left
|
||||
mousewheel-tab-switching = true
|
||||
padding = 0,0,5,5
|
||||
indicator-padding = 2,2,0,4
|
||||
[storage]
|
||||
download-directory =
|
||||
prompt-download-directory = true
|
||||
remember-download-directory = true
|
||||
maximum-pages-in-cache = 0
|
||||
offline-web-application-cache = true
|
||||
local-storage = true
|
||||
cache-size =
|
||||
[content]
|
||||
allow-images = true
|
||||
allow-javascript = true
|
||||
allow-plugins = false
|
||||
webgl = true
|
||||
hyperlink-auditing = false
|
||||
geolocation = ask
|
||||
notifications = ask
|
||||
media-capture = ask
|
||||
javascript-can-open-windows-automatically = false
|
||||
javascript-can-close-windows = false
|
||||
javascript-can-access-clipboard = false
|
||||
ignore-javascript-prompt = false
|
||||
ignore-javascript-alert = false
|
||||
local-content-can-access-remote-urls = false
|
||||
local-content-can-access-file-urls = true
|
||||
cookies-accept = no-3rdparty
|
||||
cookies-store = true
|
||||
host-block-lists = https://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext
|
||||
host-blocking-enabled = true
|
||||
host-blocking-whitelist = piwik.org
|
||||
enable-pdfjs = false
|
||||
[hints]
|
||||
border = 1px solid #E3BE23
|
||||
mode = letter
|
||||
chars = asdfghjkl
|
||||
min-chars = 1
|
||||
scatter = true
|
||||
uppercase = false
|
||||
dictionary = /usr/share/dict/words
|
||||
auto-follow = unique-match
|
||||
auto-follow-timeout = 0
|
||||
next-regexes = \\bnext\\b,\\bmore\\b,\\bnewer\\b,\\b[>\u2192\u226b]\\b,\\b(>>|\xbb)\\b,\\bcontinue\\b
|
||||
prev-regexes = \\bprev(ious)?\\b,\\bback\\b,\\bolder\\b,\\b[<\u2190\u226a]\\b,\\b(<<|\xab)\\b
|
||||
find-implementation = python
|
||||
hide-unmatched-rapid-hints = true
|
||||
[searchengines]
|
||||
DEFAULT = https://duckduckgo.com/?q={}
|
||||
[aliases]
|
||||
[colors]
|
||||
completion.fg = white
|
||||
completion.bg = #333333
|
||||
completion.alternate-bg = #444444
|
||||
completion.category.fg = white
|
||||
completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #888888, stop:1 #505050)
|
||||
completion.category.border.top = black
|
||||
completion.category.border.bottom = ${completion.category.border.top}
|
||||
completion.item.selected.fg = black
|
||||
completion.item.selected.bg = #e8c000
|
||||
completion.item.selected.border.top = #bbbb00
|
||||
completion.item.selected.border.bottom = ${completion.item.selected.border.top}
|
||||
completion.match.fg = #ff4444
|
||||
completion.scrollbar.fg = ${completion.fg}
|
||||
completion.scrollbar.bg = ${completion.bg}
|
||||
statusbar.fg = white
|
||||
statusbar.bg = black
|
||||
statusbar.fg.private = ${statusbar.fg}
|
||||
statusbar.bg.private = #666666
|
||||
statusbar.fg.insert = ${statusbar.fg}
|
||||
statusbar.bg.insert = darkgreen
|
||||
statusbar.fg.command = ${statusbar.fg}
|
||||
statusbar.bg.command = ${statusbar.bg}
|
||||
statusbar.fg.command.private = ${statusbar.fg.private}
|
||||
statusbar.bg.command.private = ${statusbar.bg.private}
|
||||
statusbar.fg.caret = ${statusbar.fg}
|
||||
statusbar.bg.caret = purple
|
||||
statusbar.fg.caret-selection = ${statusbar.fg}
|
||||
statusbar.bg.caret-selection = #a12dff
|
||||
statusbar.progress.bg = white
|
||||
statusbar.url.fg = ${statusbar.fg}
|
||||
statusbar.url.fg.success = white
|
||||
statusbar.url.fg.success.https = lime
|
||||
statusbar.url.fg.error = orange
|
||||
statusbar.url.fg.warn = yellow
|
||||
statusbar.url.fg.hover = aqua
|
||||
tabs.fg.odd = white
|
||||
tabs.bg.odd = grey
|
||||
tabs.fg.even = white
|
||||
tabs.bg.even = darkgrey
|
||||
tabs.fg.selected.odd = white
|
||||
tabs.bg.selected.odd = black
|
||||
tabs.fg.selected.even = ${tabs.fg.selected.odd}
|
||||
tabs.bg.selected.even = ${tabs.bg.selected.odd}
|
||||
tabs.bg.bar = #555555
|
||||
tabs.indicator.start = #0000aa
|
||||
tabs.indicator.stop = #00aa00
|
||||
tabs.indicator.error = #ff0000
|
||||
tabs.indicator.system = rgb
|
||||
hints.fg = black
|
||||
hints.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 247, 133, 0.8), stop:1 rgba(255, 197, 66, 0.8))
|
||||
hints.fg.match = green
|
||||
downloads.bg.bar = black
|
||||
downloads.fg.start = white
|
||||
downloads.bg.start = #0000aa
|
||||
downloads.fg.stop = ${downloads.fg.start}
|
||||
downloads.bg.stop = #00aa00
|
||||
downloads.fg.system = rgb
|
||||
downloads.bg.system = rgb
|
||||
downloads.fg.error = white
|
||||
downloads.bg.error = red
|
||||
webpage.bg = white
|
||||
keyhint.fg = #FFFFFF
|
||||
keyhint.fg.suffix = #FFFF00
|
||||
keyhint.bg = rgba(0, 0, 0, 80%)
|
||||
messages.fg.error = white
|
||||
messages.bg.error = red
|
||||
messages.border.error = #bb0000
|
||||
messages.fg.warning = white
|
||||
messages.bg.warning = darkorange
|
||||
messages.border.warning = #d47300
|
||||
messages.fg.info = white
|
||||
messages.bg.info = black
|
||||
messages.border.info = #333333
|
||||
prompts.fg = white
|
||||
prompts.bg = darkblue
|
||||
prompts.selected.bg = #308cc6
|
||||
[fonts]
|
||||
_monospace = xos4 Terminus, Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal
|
||||
completion = 8pt ${_monospace}
|
||||
completion.category = bold ${completion}
|
||||
tabbar = 8pt ${_monospace}
|
||||
statusbar = 8pt ${_monospace}
|
||||
downloads = 8pt ${_monospace}
|
||||
hints = bold 13px ${_monospace}
|
||||
debug-console = 8pt ${_monospace}
|
||||
web-family-standard =
|
||||
web-family-fixed =
|
||||
web-family-serif =
|
||||
web-family-sans-serif =
|
||||
web-family-cursive =
|
||||
web-family-fantasy =
|
||||
web-size-minimum = 0
|
||||
web-size-minimum-logical = 6
|
||||
web-size-default = 16
|
||||
web-size-default-fixed = 13
|
||||
keyhint = 8pt ${_monospace}
|
||||
messages.error = 8pt ${_monospace}
|
||||
messages.warning = 8pt ${_monospace}
|
||||
messages.info = 8pt ${_monospace}
|
||||
prompts = 8pt sans-serif
|
||||
"""
|
||||
|
||||
OLD_KEYS_CONF = """
|
||||
[!normal]
|
||||
leave-mode
|
||||
<escape>
|
||||
<ctrl-[>
|
||||
[normal]
|
||||
clear-keychain ;; search ;; fullscreen --leave
|
||||
<escape>
|
||||
<ctrl-[>
|
||||
set-cmd-text -s :open
|
||||
o
|
||||
set-cmd-text :open {url:pretty}
|
||||
go
|
||||
set-cmd-text -s :open -t
|
||||
O
|
||||
set-cmd-text :open -t -i {url:pretty}
|
||||
gO
|
||||
set-cmd-text -s :open -b
|
||||
xo
|
||||
set-cmd-text :open -b -i {url:pretty}
|
||||
xO
|
||||
set-cmd-text -s :open -w
|
||||
wo
|
||||
set-cmd-text :open -w {url:pretty}
|
||||
wO
|
||||
set-cmd-text /
|
||||
/
|
||||
set-cmd-text ?
|
||||
?
|
||||
set-cmd-text :
|
||||
:
|
||||
open -t
|
||||
ga
|
||||
<ctrl-t>
|
||||
open -w
|
||||
<ctrl-n>
|
||||
tab-close
|
||||
d
|
||||
<ctrl-w>
|
||||
tab-close -o
|
||||
D
|
||||
tab-only
|
||||
co
|
||||
tab-focus
|
||||
T
|
||||
tab-move
|
||||
gm
|
||||
tab-move -
|
||||
gl
|
||||
tab-move +
|
||||
gr
|
||||
tab-next
|
||||
J
|
||||
<ctrl-pgdown>
|
||||
tab-prev
|
||||
K
|
||||
<ctrl-pgup>
|
||||
tab-clone
|
||||
gC
|
||||
reload
|
||||
r
|
||||
<f5>
|
||||
reload -f
|
||||
R
|
||||
<ctrl-f5>
|
||||
back
|
||||
H
|
||||
<back>
|
||||
back -t
|
||||
th
|
||||
back -w
|
||||
wh
|
||||
forward
|
||||
L
|
||||
<forward>
|
||||
forward -t
|
||||
tl
|
||||
forward -w
|
||||
wl
|
||||
fullscreen
|
||||
<f11>
|
||||
hint
|
||||
f
|
||||
hint all tab
|
||||
F
|
||||
hint all window
|
||||
wf
|
||||
hint all tab-bg
|
||||
;b
|
||||
hint all tab-fg
|
||||
;f
|
||||
hint all hover
|
||||
;h
|
||||
hint images
|
||||
;i
|
||||
hint images tab
|
||||
;I
|
||||
hint links fill :open {hint-url}
|
||||
;o
|
||||
hint links fill :open -t -i {hint-url}
|
||||
;O
|
||||
hint links yank
|
||||
;y
|
||||
hint links yank-primary
|
||||
;Y
|
||||
hint --rapid links tab-bg
|
||||
;r
|
||||
hint --rapid links window
|
||||
;R
|
||||
hint links download
|
||||
;d
|
||||
hint inputs
|
||||
;t
|
||||
scroll left
|
||||
h
|
||||
scroll down
|
||||
j
|
||||
scroll up
|
||||
k
|
||||
scroll right
|
||||
l
|
||||
undo
|
||||
u
|
||||
<ctrl-shift-t>
|
||||
scroll-perc 0
|
||||
gg
|
||||
scroll-perc
|
||||
G
|
||||
search-next
|
||||
n
|
||||
search-prev
|
||||
N
|
||||
enter-mode insert
|
||||
i
|
||||
enter-mode caret
|
||||
v
|
||||
enter-mode set_mark
|
||||
`
|
||||
enter-mode jump_mark
|
||||
'
|
||||
yank
|
||||
yy
|
||||
yank -s
|
||||
yY
|
||||
yank title
|
||||
yt
|
||||
yank title -s
|
||||
yT
|
||||
yank domain
|
||||
yd
|
||||
yank domain -s
|
||||
yD
|
||||
yank pretty-url
|
||||
yp
|
||||
yank pretty-url -s
|
||||
yP
|
||||
open -- {clipboard}
|
||||
pp
|
||||
open -- {primary}
|
||||
pP
|
||||
open -t -- {clipboard}
|
||||
Pp
|
||||
open -t -- {primary}
|
||||
PP
|
||||
open -w -- {clipboard}
|
||||
wp
|
||||
open -w -- {primary}
|
||||
wP
|
||||
quickmark-save
|
||||
m
|
||||
set-cmd-text -s :quickmark-load
|
||||
b
|
||||
set-cmd-text -s :quickmark-load -t
|
||||
B
|
||||
set-cmd-text -s :quickmark-load -w
|
||||
wb
|
||||
bookmark-add
|
||||
M
|
||||
set-cmd-text -s :bookmark-load
|
||||
gb
|
||||
set-cmd-text -s :bookmark-load -t
|
||||
gB
|
||||
set-cmd-text -s :bookmark-load -w
|
||||
wB
|
||||
save
|
||||
sf
|
||||
set-cmd-text -s :set
|
||||
ss
|
||||
set-cmd-text -s :set -t
|
||||
sl
|
||||
set-cmd-text -s :bind
|
||||
sk
|
||||
zoom-out
|
||||
-
|
||||
zoom-in
|
||||
+
|
||||
zoom
|
||||
=
|
||||
navigate prev
|
||||
[[
|
||||
navigate next
|
||||
]]
|
||||
navigate prev -t
|
||||
{{
|
||||
navigate next -t
|
||||
}}
|
||||
navigate up
|
||||
gu
|
||||
navigate up -t
|
||||
gU
|
||||
navigate increment
|
||||
<ctrl-a>
|
||||
navigate decrement
|
||||
<ctrl-x>
|
||||
inspector
|
||||
wi
|
||||
download
|
||||
gd
|
||||
download-cancel
|
||||
ad
|
||||
download-clear
|
||||
cd
|
||||
view-source
|
||||
gf
|
||||
set-cmd-text -s :buffer
|
||||
gt
|
||||
tab-focus last
|
||||
<ctrl-tab>
|
||||
<ctrl-6>
|
||||
<ctrl-^>
|
||||
enter-mode passthrough
|
||||
<ctrl-v>
|
||||
quit
|
||||
<ctrl-q>
|
||||
ZQ
|
||||
wq
|
||||
ZZ
|
||||
scroll-page 0 1
|
||||
<ctrl-f>
|
||||
scroll-page 0 -1
|
||||
<ctrl-b>
|
||||
scroll-page 0 0.5
|
||||
<ctrl-d>
|
||||
scroll-page 0 -0.5
|
||||
<ctrl-u>
|
||||
tab-focus 1
|
||||
<alt-1>
|
||||
g0
|
||||
g^
|
||||
tab-focus 2
|
||||
<alt-2>
|
||||
tab-focus 3
|
||||
<alt-3>
|
||||
tab-focus 4
|
||||
<alt-4>
|
||||
tab-focus 5
|
||||
<alt-5>
|
||||
tab-focus 6
|
||||
<alt-6>
|
||||
tab-focus 7
|
||||
<alt-7>
|
||||
tab-focus 8
|
||||
<alt-8>
|
||||
tab-focus -1
|
||||
<alt-9>
|
||||
g$
|
||||
home
|
||||
<ctrl-h>
|
||||
stop
|
||||
<ctrl-s>
|
||||
print
|
||||
<ctrl-alt-p>
|
||||
open qute://settings
|
||||
Ss
|
||||
follow-selected
|
||||
<return>
|
||||
<ctrl-m>
|
||||
<ctrl-j>
|
||||
<shift-return>
|
||||
<enter>
|
||||
<shift-enter>
|
||||
follow-selected -t
|
||||
<ctrl-return>
|
||||
<ctrl-enter>
|
||||
repeat-command
|
||||
.
|
||||
tab-pin
|
||||
<ctrl-p>
|
||||
record-macro
|
||||
q
|
||||
run-macro
|
||||
@
|
||||
[insert]
|
||||
open-editor
|
||||
<ctrl-e>
|
||||
insert-text {primary}
|
||||
<shift-ins>
|
||||
[hint]
|
||||
follow-hint
|
||||
<return>
|
||||
<ctrl-m>
|
||||
<ctrl-j>
|
||||
<shift-return>
|
||||
<enter>
|
||||
<shift-enter>
|
||||
hint --rapid links tab-bg
|
||||
<ctrl-r>
|
||||
hint links
|
||||
<ctrl-f>
|
||||
hint all tab-bg
|
||||
<ctrl-b>
|
||||
[passthrough]
|
||||
[command]
|
||||
command-history-prev
|
||||
<ctrl-p>
|
||||
command-history-next
|
||||
<ctrl-n>
|
||||
completion-item-focus prev
|
||||
<shift-tab>
|
||||
<up>
|
||||
completion-item-focus next
|
||||
<tab>
|
||||
<down>
|
||||
completion-item-focus next-category
|
||||
<ctrl-tab>
|
||||
completion-item-focus prev-category
|
||||
<ctrl-shift-tab>
|
||||
completion-item-del
|
||||
<ctrl-d>
|
||||
command-accept
|
||||
<return>
|
||||
<ctrl-m>
|
||||
<ctrl-j>
|
||||
<shift-return>
|
||||
<enter>
|
||||
<shift-enter>
|
||||
[prompt]
|
||||
prompt-accept
|
||||
<return>
|
||||
<ctrl-m>
|
||||
<ctrl-j>
|
||||
<shift-return>
|
||||
<enter>
|
||||
<shift-enter>
|
||||
prompt-accept yes
|
||||
y
|
||||
prompt-accept no
|
||||
n
|
||||
prompt-open-download
|
||||
<ctrl-x>
|
||||
prompt-item-focus prev
|
||||
<shift-tab>
|
||||
<up>
|
||||
prompt-item-focus next
|
||||
<tab>
|
||||
<down>
|
||||
[command,prompt]
|
||||
rl-backward-char
|
||||
<ctrl-b>
|
||||
rl-forward-char
|
||||
<ctrl-f>
|
||||
rl-backward-word
|
||||
<alt-b>
|
||||
rl-forward-word
|
||||
<alt-f>
|
||||
rl-beginning-of-line
|
||||
<ctrl-a>
|
||||
rl-end-of-line
|
||||
<ctrl-e>
|
||||
rl-unix-line-discard
|
||||
<ctrl-u>
|
||||
rl-kill-line
|
||||
<ctrl-k>
|
||||
rl-kill-word
|
||||
<alt-d>
|
||||
rl-unix-word-rubout
|
||||
<ctrl-w>
|
||||
rl-backward-kill-word
|
||||
<alt-backspace>
|
||||
rl-yank
|
||||
<ctrl-y>
|
||||
rl-delete-char
|
||||
<ctrl-?>
|
||||
rl-backward-delete-char
|
||||
<ctrl-h>
|
||||
[caret]
|
||||
toggle-selection
|
||||
v
|
||||
<space>
|
||||
drop-selection
|
||||
<ctrl-space>
|
||||
enter-mode normal
|
||||
c
|
||||
move-to-next-line
|
||||
j
|
||||
move-to-prev-line
|
||||
k
|
||||
move-to-next-char
|
||||
l
|
||||
move-to-prev-char
|
||||
h
|
||||
move-to-end-of-word
|
||||
e
|
||||
move-to-next-word
|
||||
w
|
||||
move-to-prev-word
|
||||
b
|
||||
move-to-start-of-next-block
|
||||
]
|
||||
move-to-start-of-prev-block
|
||||
[
|
||||
move-to-end-of-next-block
|
||||
}
|
||||
move-to-end-of-prev-block
|
||||
{
|
||||
move-to-start-of-line
|
||||
0
|
||||
move-to-end-of-line
|
||||
$
|
||||
move-to-start-of-document
|
||||
gg
|
||||
move-to-end-of-document
|
||||
G
|
||||
yank selection -s
|
||||
Y
|
||||
yank selection
|
||||
y
|
||||
<return>
|
||||
<ctrl-m>
|
||||
<ctrl-j>
|
||||
<shift-return>
|
||||
<enter>
|
||||
<shift-enter>
|
||||
scroll left
|
||||
H
|
||||
scroll down
|
||||
J
|
||||
scroll up
|
||||
K
|
||||
scroll right
|
||||
L
|
||||
"""
|
||||
|
||||
|
||||
def get_diff():
|
||||
"""Get a HTML diff for the old config files."""
|
||||
old_conf_lines = []
|
||||
old_key_lines = []
|
||||
|
||||
for filename, dest in [('qutebrowser.conf', old_conf_lines),
|
||||
('keys.conf', old_key_lines)]:
|
||||
path = os.path.join(standarddir.config(), filename)
|
||||
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if not line.strip() or line.startswith('#'):
|
||||
continue
|
||||
dest.append(line.rstrip())
|
||||
|
||||
conf_delta = difflib.unified_diff(OLD_CONF.lstrip().splitlines(),
|
||||
old_conf_lines)
|
||||
key_delta = difflib.unified_diff(OLD_KEYS_CONF.lstrip().splitlines(),
|
||||
old_key_lines)
|
||||
|
||||
conf_diff = '\n'.join(conf_delta)
|
||||
key_diff = '\n'.join(key_delta)
|
||||
|
||||
# pylint: disable=no-member
|
||||
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/
|
||||
lexer = pygments.lexers.DiffLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table',
|
||||
title='Config diff')
|
||||
# pylint: enable=no-member
|
||||
return pygments.highlight(conf_diff + key_diff, lexer, formatter)
|
@ -41,46 +41,79 @@ class ValidationError(Error):
|
||||
"""Raised when a value for a config type was invalid.
|
||||
|
||||
Attributes:
|
||||
section: Section in which the error occurred (added when catching and
|
||||
re-raising the exception).
|
||||
option: Option in which the error occurred.
|
||||
value: Config value that triggered the error.
|
||||
msg: Additional error message.
|
||||
"""
|
||||
|
||||
def __init__(self, value, msg):
|
||||
super().__init__("Invalid value '{}' - {}".format(value, msg))
|
||||
self.section = None
|
||||
self.option = None
|
||||
|
||||
|
||||
class NoSectionError(Error):
|
||||
class KeybindingError(Error):
|
||||
|
||||
"""Raised when no section matches a requested option."""
|
||||
"""Raised for issues with keybindings."""
|
||||
|
||||
def __init__(self, section):
|
||||
super().__init__("Section {!r} does not exist!".format(section))
|
||||
self.section = section
|
||||
|
||||
class DuplicateKeyError(KeybindingError):
|
||||
|
||||
"""Raised when there was a duplicate key."""
|
||||
|
||||
def __init__(self, key):
|
||||
super().__init__("Duplicate key {}".format(key))
|
||||
|
||||
|
||||
class NoOptionError(Error):
|
||||
|
||||
"""Raised when an option was not found."""
|
||||
|
||||
def __init__(self, option, section):
|
||||
super().__init__("No option {!r} in section {!r}".format(
|
||||
option, section))
|
||||
def __init__(self, option):
|
||||
super().__init__("No option {!r}".format(option))
|
||||
self.option = option
|
||||
self.section = section
|
||||
|
||||
|
||||
class InterpolationSyntaxError(Error):
|
||||
class ConfigErrorDesc:
|
||||
|
||||
"""Raised when the source text contains invalid syntax.
|
||||
"""A description of an error happening while reading the config.
|
||||
|
||||
Current implementation raises this exception when the source text into
|
||||
which substitutions are made does not conform to the required syntax.
|
||||
Attributes:
|
||||
text: The text to show.
|
||||
exception: The exception which happened.
|
||||
traceback: The formatted traceback of the exception.
|
||||
"""
|
||||
|
||||
def __init__(self, option, section, msg):
|
||||
super().__init__(msg)
|
||||
self.option = option
|
||||
self.section = section
|
||||
def __init__(self, text, exception, traceback=None):
|
||||
self.text = text
|
||||
self.exception = exception
|
||||
self.traceback = traceback
|
||||
|
||||
|
||||
class ConfigFileErrors(Error):
|
||||
|
||||
"""Raised when multiple errors occurred inside the config."""
|
||||
|
||||
def __init__(self, basename, errors):
|
||||
super().__init__("Errors occurred while reading {}".format(basename))
|
||||
self.basename = basename
|
||||
self.errors = errors
|
||||
|
||||
def to_html(self):
|
||||
"""Get the error texts as a HTML snippet."""
|
||||
from qutebrowser.utils import jinja
|
||||
template = jinja.environment.from_string("""
|
||||
Errors occurred while reading {{ basename }}:
|
||||
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>
|
||||
<b>{{ error.text }}</b>: {{ error.exception }}
|
||||
{% if error.traceback != none %}
|
||||
<pre>
|
||||
""".rstrip() + "\n{{ error.traceback }}" + """
|
||||
</pre>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
""")
|
||||
return template.render(basename=self.basename, errors=self.errors)
|
||||
|
235
qutebrowser/config/configfiles.py
Normal file
235
qutebrowser/config/configfiles.py
Normal file
@ -0,0 +1,235 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Configuration files residing on disk."""
|
||||
|
||||
import types
|
||||
import os.path
|
||||
import textwrap
|
||||
import traceback
|
||||
import configparser
|
||||
import contextlib
|
||||
|
||||
import yaml
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from qutebrowser.config import configexc
|
||||
from qutebrowser.utils import objreg, standarddir, utils, qtutils
|
||||
|
||||
|
||||
class StateConfig(configparser.ConfigParser):
|
||||
|
||||
"""The "state" file saving various application state."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
save_manager = objreg.get('save-manager')
|
||||
self._filename = os.path.join(standarddir.data(), 'state')
|
||||
self.read(self._filename, encoding='utf-8')
|
||||
for sect in ['general', 'geometry']:
|
||||
try:
|
||||
self.add_section(sect)
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
# See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
|
||||
self['general'].pop('fooled', None)
|
||||
save_manager.add_saveable('state-config', self._save)
|
||||
|
||||
def _save(self):
|
||||
"""Save the state file to the configured location."""
|
||||
with open(self._filename, 'w', encoding='utf-8') as f:
|
||||
self.write(f)
|
||||
|
||||
|
||||
class YamlConfig:
|
||||
|
||||
"""A config stored on disk as YAML file.
|
||||
|
||||
Class attributes:
|
||||
VERSION: The current version number of the config file.
|
||||
"""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self):
|
||||
save_manager = objreg.get('save-manager')
|
||||
self._filename = os.path.join(standarddir.config(auto=True),
|
||||
'autoconfig.yml')
|
||||
save_manager.add_saveable('yaml-config', self._save)
|
||||
self.values = {}
|
||||
|
||||
def _save(self):
|
||||
"""Save the changed settings to the YAML file."""
|
||||
data = {'config_version': self.VERSION, 'global': self.values}
|
||||
with qtutils.savefile_open(self._filename) as f:
|
||||
f.write(textwrap.dedent("""
|
||||
# DO NOT edit this file by hand, qutebrowser will overwrite it.
|
||||
# Instead, create a config.py - see :help for details.
|
||||
|
||||
""".lstrip('\n')))
|
||||
utils.yaml_dump(data, f)
|
||||
|
||||
def load(self):
|
||||
"""Load self.values from the configured YAML file."""
|
||||
try:
|
||||
with open(self._filename, 'r', encoding='utf-8') as f:
|
||||
yaml_data = utils.yaml_load(f)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
except OSError as e:
|
||||
desc = configexc.ConfigErrorDesc("While reading", e)
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||
except yaml.YAMLError as e:
|
||||
desc = configexc.ConfigErrorDesc("While parsing", e)
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||
|
||||
try:
|
||||
global_obj = yaml_data['global']
|
||||
except KeyError:
|
||||
desc = configexc.ConfigErrorDesc(
|
||||
"While loading data",
|
||||
"Toplevel object does not contain 'global' key")
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||
except TypeError:
|
||||
desc = configexc.ConfigErrorDesc("While loading data",
|
||||
"Toplevel object is not a dict")
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||
|
||||
if not isinstance(global_obj, dict):
|
||||
desc = configexc.ConfigErrorDesc(
|
||||
"While loading data",
|
||||
"'global' object is not a dict")
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||
|
||||
self.values = global_obj
|
||||
|
||||
|
||||
class ConfigAPI:
|
||||
|
||||
"""Object which gets passed to config.py as "config" object.
|
||||
|
||||
This is a small wrapper over the Config object, but with more
|
||||
straightforward method names (get/set call get_obj/set_obj) and a more
|
||||
shallow API.
|
||||
|
||||
Attributes:
|
||||
_config: The main Config object to use.
|
||||
_keyconfig: The KeyConfig object.
|
||||
load_autoconfig: Whether autoconfig.yml should be loaded.
|
||||
errors: Errors which occurred while setting options.
|
||||
"""
|
||||
|
||||
def __init__(self, config, keyconfig):
|
||||
self._config = config
|
||||
self._keyconfig = keyconfig
|
||||
self.load_autoconfig = True
|
||||
self.errors = []
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_error(self, action, name):
|
||||
try:
|
||||
yield
|
||||
except configexc.Error as e:
|
||||
text = "While {} '{}'".format(action, name)
|
||||
self.errors.append(configexc.ConfigErrorDesc(text, e))
|
||||
|
||||
def finalize(self):
|
||||
"""Do work which needs to be done after reading config.py."""
|
||||
self._config.update_mutables()
|
||||
|
||||
def get(self, name):
|
||||
with self._handle_error('getting', name):
|
||||
return self._config.get_obj(name)
|
||||
|
||||
def set(self, name, value):
|
||||
with self._handle_error('setting', name):
|
||||
self._config.set_obj(name, value)
|
||||
|
||||
def bind(self, key, command, *, mode, force=False):
|
||||
with self._handle_error('binding', key):
|
||||
self._keyconfig.bind(key, command, mode=mode, force=force)
|
||||
|
||||
def unbind(self, key, *, mode):
|
||||
with self._handle_error('unbinding', key):
|
||||
self._keyconfig.unbind(key, mode=mode)
|
||||
|
||||
|
||||
def read_config_py(filename=None):
|
||||
"""Read a config.py file."""
|
||||
from qutebrowser.config import config
|
||||
api = ConfigAPI(config.instance, config.key_instance)
|
||||
|
||||
if filename is None:
|
||||
filename = os.path.join(standarddir.config(), 'config.py')
|
||||
if not os.path.exists(filename):
|
||||
return api
|
||||
|
||||
container = config.ConfigContainer(config.instance, configapi=api)
|
||||
basename = os.path.basename(filename)
|
||||
|
||||
module = types.ModuleType('config')
|
||||
module.config = api
|
||||
module.c = container
|
||||
module.__file__ = filename
|
||||
|
||||
try:
|
||||
with open(filename, mode='rb') as f:
|
||||
source = f.read()
|
||||
except OSError as e:
|
||||
text = "Error while reading {}".format(basename)
|
||||
desc = configexc.ConfigErrorDesc(text, e)
|
||||
raise configexc.ConfigFileErrors(basename, [desc])
|
||||
|
||||
try:
|
||||
code = compile(source, filename, 'exec')
|
||||
except (ValueError, TypeError) as e:
|
||||
# source contains NUL bytes
|
||||
desc = configexc.ConfigErrorDesc("Error while compiling", e)
|
||||
raise configexc.ConfigFileErrors(basename, [desc])
|
||||
except SyntaxError as e:
|
||||
desc = configexc.ConfigErrorDesc("Syntax Error", e,
|
||||
traceback=traceback.format_exc())
|
||||
raise configexc.ConfigFileErrors(basename, [desc])
|
||||
|
||||
try:
|
||||
exec(code, module.__dict__)
|
||||
except Exception as e:
|
||||
api.errors.append(configexc.ConfigErrorDesc(
|
||||
"Unhandled exception",
|
||||
exception=e, traceback=traceback.format_exc()))
|
||||
|
||||
api.finalize()
|
||||
return api
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize config storage not related to the main config."""
|
||||
state = StateConfig()
|
||||
objreg.register('state-config', state)
|
||||
|
||||
# Set the QSettings path to something like
|
||||
# ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it
|
||||
# doesn't overwrite our config.
|
||||
#
|
||||
# This fixes one of the corruption issues here:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/515
|
||||
|
||||
path = os.path.join(standarddir.config(auto=True), 'qsettings')
|
||||
for fmt in [QSettings.NativeFormat, QSettings.IniFormat]:
|
||||
QSettings.setPath(fmt, QSettings.UserScope, path)
|
File diff suppressed because it is too large
Load Diff
@ -1,20 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Parser for different configuration formats."""
|
@ -1,74 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Parsers for INI-like config files, based on Python's ConfigParser."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import configparser
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils
|
||||
|
||||
|
||||
class ReadConfigParser(configparser.ConfigParser):
|
||||
|
||||
"""Our own ConfigParser subclass to read the main config.
|
||||
|
||||
Attributes:
|
||||
_configdir: The directory to read the config from.
|
||||
_fname: The filename of the config.
|
||||
_configfile: The config file path.
|
||||
"""
|
||||
|
||||
def __init__(self, configdir, fname):
|
||||
"""Config constructor.
|
||||
|
||||
Args:
|
||||
configdir: Directory to read the config from.
|
||||
fname: Filename of the config file.
|
||||
"""
|
||||
super().__init__(interpolation=None, comment_prefixes='#')
|
||||
self.optionxform = lambda opt: opt # be case-insensitive
|
||||
self._configdir = configdir
|
||||
self._fname = fname
|
||||
self._configfile = os.path.join(self._configdir, fname)
|
||||
|
||||
if not os.path.isfile(self._configfile):
|
||||
return
|
||||
log.init.debug("Reading config from {}".format(self._configfile))
|
||||
self.read(self._configfile, encoding='utf-8')
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True,
|
||||
configdir=self._configdir, fname=self._fname)
|
||||
|
||||
|
||||
class ReadWriteConfigParser(ReadConfigParser):
|
||||
|
||||
"""ConfigParser subclass used for auxiliary config files."""
|
||||
|
||||
def save(self):
|
||||
"""Save the config file."""
|
||||
if self._configdir is None:
|
||||
return
|
||||
if not os.path.exists(self._configdir):
|
||||
os.makedirs(self._configdir, 0o755)
|
||||
log.destroy.debug("Saving config to {}".format(self._configfile))
|
||||
with qtutils.savefile_open(self._configfile) as f:
|
||||
self.write(f)
|
@ -1,486 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Parser for the key configuration."""
|
||||
|
||||
import collections
|
||||
import os.path
|
||||
import itertools
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
|
||||
from qutebrowser.config import configdata, textwrapper
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.utils import (log, utils, qtutils, message, usertypes, objreg,
|
||||
standarddir, error)
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
|
||||
|
||||
def init(parent=None):
|
||||
"""Read and save keybindings.
|
||||
|
||||
Args:
|
||||
parent: The parent to use for the KeyConfigParser.
|
||||
"""
|
||||
args = objreg.get('args')
|
||||
try:
|
||||
key_config = KeyConfigParser(standarddir.config(), 'keys.conf',
|
||||
args.relaxed_config, parent=parent)
|
||||
except (KeyConfigError, UnicodeDecodeError) as e:
|
||||
log.init.exception(e)
|
||||
errstr = "Error while reading key config:\n"
|
||||
if e.lineno is not None:
|
||||
errstr += "In line {}: ".format(e.lineno)
|
||||
error.handle_fatal_exc(e, args, "Error while reading key config!",
|
||||
pre_text=errstr)
|
||||
# We didn't really initialize much so far, so we just quit hard.
|
||||
sys.exit(usertypes.Exit.err_key_config)
|
||||
else:
|
||||
objreg.register('key-config', key_config)
|
||||
save_manager = objreg.get('save-manager')
|
||||
filename = os.path.join(standarddir.config(), 'keys.conf')
|
||||
save_manager.add_saveable(
|
||||
'key-config', key_config.save, key_config.config_dirty,
|
||||
config_opt=('general', 'auto-save-config'), filename=filename,
|
||||
dirty=key_config.is_dirty)
|
||||
|
||||
|
||||
class KeyConfigError(Exception):
|
||||
|
||||
"""Raised on errors with the key config.
|
||||
|
||||
Attributes:
|
||||
lineno: The config line in which the exception occurred.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None):
|
||||
super().__init__(msg)
|
||||
self.lineno = None
|
||||
|
||||
|
||||
class DuplicateKeychainError(KeyConfigError):
|
||||
|
||||
"""Error raised when there's a duplicate key binding."""
|
||||
|
||||
def __init__(self, keychain):
|
||||
super().__init__("Duplicate key chain {}!".format(keychain))
|
||||
self.keychain = keychain
|
||||
|
||||
|
||||
class KeyConfigParser(QObject):
|
||||
|
||||
"""Parser for the keybind config.
|
||||
|
||||
Attributes:
|
||||
_configfile: The filename of the config or None.
|
||||
_cur_section: The section currently being processed by _read().
|
||||
_cur_command: The command currently being processed by _read().
|
||||
is_dirty: Whether the config is currently dirty.
|
||||
|
||||
Class attributes:
|
||||
UNBOUND_COMMAND: The special command used for unbound keybindings.
|
||||
|
||||
Signals:
|
||||
changed: Emitted when the internal data has changed.
|
||||
arg: Name of the mode which was changed.
|
||||
config_dirty: Emitted when the config should be re-saved.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal(str)
|
||||
config_dirty = pyqtSignal()
|
||||
UNBOUND_COMMAND = '<unbound>'
|
||||
|
||||
def __init__(self, configdir, fname, relaxed=False, parent=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
configdir: The directory to save the configs in.
|
||||
fname: The filename of the config.
|
||||
relaxed: If given, unknown commands are ignored.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.is_dirty = False
|
||||
self._cur_section = None
|
||||
self._cur_command = None
|
||||
# Mapping of section name(s) to key binding -> command dicts.
|
||||
self.keybindings = collections.OrderedDict()
|
||||
self._configfile = os.path.join(configdir, fname)
|
||||
|
||||
if not os.path.exists(self._configfile):
|
||||
self._load_default()
|
||||
else:
|
||||
self._read(relaxed)
|
||||
self._load_default(only_new=True)
|
||||
log.init.debug("Loaded bindings: {}".format(self.keybindings))
|
||||
|
||||
def __str__(self):
|
||||
"""Get the config as string."""
|
||||
lines = configdata.KEY_FIRST_COMMENT.strip('\n').splitlines()
|
||||
lines.append('')
|
||||
for sectname, sect in self.keybindings.items():
|
||||
lines.append('[{}]'.format(sectname))
|
||||
lines += self._str_section_desc(sectname)
|
||||
lines.append('')
|
||||
data = collections.OrderedDict()
|
||||
for key, cmd in sect.items():
|
||||
if cmd in data:
|
||||
data[cmd].append(key)
|
||||
else:
|
||||
data[cmd] = [key]
|
||||
for cmd, keys in data.items():
|
||||
lines.append(cmd)
|
||||
for k in keys:
|
||||
lines.append(' ' * 4 + k)
|
||||
lines.append('')
|
||||
return '\n'.join(lines) + '\n'
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True,
|
||||
configfile=self._configfile)
|
||||
|
||||
def _str_section_desc(self, sectname):
|
||||
"""Get the section description string for sectname."""
|
||||
wrapper = textwrapper.TextWrapper()
|
||||
lines = []
|
||||
try:
|
||||
seclines = configdata.KEY_SECTION_DESC[sectname].splitlines()
|
||||
except KeyError:
|
||||
return []
|
||||
else:
|
||||
for secline in seclines:
|
||||
if 'http://' in secline or 'https://' in secline:
|
||||
lines.append('# ' + secline)
|
||||
else:
|
||||
lines += wrapper.wrap(secline)
|
||||
return lines
|
||||
|
||||
def save(self):
|
||||
"""Save the key config file."""
|
||||
log.destroy.debug("Saving key config to {}".format(self._configfile))
|
||||
|
||||
try:
|
||||
with qtutils.savefile_open(self._configfile,
|
||||
encoding='utf-8') as f:
|
||||
data = str(self)
|
||||
f.write(data)
|
||||
except OSError as e:
|
||||
message.error("Could not save key config: {}".format(e))
|
||||
|
||||
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True,
|
||||
no_replace_variables=True)
|
||||
@cmdutils.argument('command', completion=miscmodels.bind)
|
||||
def bind(self, key, command=None, *, mode='normal', force=False):
|
||||
"""Bind a key to a command.
|
||||
|
||||
Args:
|
||||
key: The keychain or special key (inside `<...>`) to bind.
|
||||
command: The command to execute, with optional args, or None to
|
||||
print the current binding.
|
||||
mode: A comma-separated list of modes to bind the key in
|
||||
(default: `normal`).
|
||||
force: Rebind the key if it is already bound.
|
||||
"""
|
||||
if utils.is_special_key(key):
|
||||
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
|
||||
key = key.lower()
|
||||
|
||||
if command is None:
|
||||
cmd = self.get_bindings_for(mode).get(key, None)
|
||||
if cmd is None:
|
||||
message.info("{} is unbound in {} mode".format(key, mode))
|
||||
else:
|
||||
message.info("{} is bound to '{}' in {} mode".format(key, cmd,
|
||||
mode))
|
||||
return
|
||||
|
||||
modenames = self._normalize_sectname(mode).split(',')
|
||||
for m in modenames:
|
||||
if m not in configdata.KEY_DATA:
|
||||
raise cmdexc.CommandError("Invalid mode {}!".format(m))
|
||||
try:
|
||||
modes = [usertypes.KeyMode[m] for m in modenames]
|
||||
self._validate_command(command, modes)
|
||||
except KeyConfigError as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
try:
|
||||
self._add_binding(mode, key, command, force=force)
|
||||
except DuplicateKeychainError as e:
|
||||
raise cmdexc.CommandError("Duplicate keychain {} - use --force to "
|
||||
"override!".format(str(e.keychain)))
|
||||
except KeyConfigError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
for m in modenames:
|
||||
self.changed.emit(m)
|
||||
self._mark_config_dirty()
|
||||
|
||||
@cmdutils.register(instance='key-config')
|
||||
def unbind(self, key, mode='normal'):
|
||||
"""Unbind a keychain.
|
||||
|
||||
Args:
|
||||
key: The keychain or special key (inside <...>) to unbind.
|
||||
mode: A comma-separated list of modes to unbind the key in
|
||||
(default: `normal`).
|
||||
"""
|
||||
if utils.is_special_key(key):
|
||||
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
|
||||
key = key.lower()
|
||||
|
||||
mode = self._normalize_sectname(mode)
|
||||
for m in mode.split(','):
|
||||
if m not in configdata.KEY_DATA:
|
||||
raise cmdexc.CommandError("Invalid mode {}!".format(m))
|
||||
try:
|
||||
sect = self.keybindings[mode]
|
||||
except KeyError:
|
||||
raise cmdexc.CommandError("Can't find mode section '{}'!".format(
|
||||
mode))
|
||||
try:
|
||||
del sect[key]
|
||||
except KeyError:
|
||||
raise cmdexc.CommandError("Can't find binding '{}' in section "
|
||||
"'{}'!".format(key, mode))
|
||||
else:
|
||||
if key in itertools.chain.from_iterable(
|
||||
configdata.KEY_DATA[mode].values()):
|
||||
try:
|
||||
self._add_binding(mode, key, self.UNBOUND_COMMAND)
|
||||
except DuplicateKeychainError:
|
||||
pass
|
||||
for m in mode.split(','):
|
||||
self.changed.emit(m)
|
||||
self._mark_config_dirty()
|
||||
|
||||
def _normalize_sectname(self, s):
|
||||
"""Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'."""
|
||||
if s.startswith('!'):
|
||||
inverted = True
|
||||
s = s[1:]
|
||||
else:
|
||||
inverted = False
|
||||
sections = ','.join(sorted(s.split(',')))
|
||||
if inverted:
|
||||
sections = '!' + sections
|
||||
return sections
|
||||
|
||||
def _load_default(self, *, only_new=False):
|
||||
"""Load the built-in default key bindings.
|
||||
|
||||
Args:
|
||||
only_new: If set, only keybindings which are completely unused
|
||||
(same command/key not bound) are added.
|
||||
"""
|
||||
# {'sectname': {'keychain1': 'command', 'keychain2': 'command'}, ...}
|
||||
bindings_to_add = collections.OrderedDict()
|
||||
mark_dirty = False
|
||||
|
||||
for sectname, sect in configdata.KEY_DATA.items():
|
||||
sectname = self._normalize_sectname(sectname)
|
||||
bindings_to_add[sectname] = collections.OrderedDict()
|
||||
for command, keychains in sect.items():
|
||||
for e in keychains:
|
||||
if not only_new or self._is_new(sectname, command, e):
|
||||
assert e not in bindings_to_add[sectname]
|
||||
bindings_to_add[sectname][e] = command
|
||||
mark_dirty = True
|
||||
|
||||
for sectname, sect in bindings_to_add.items():
|
||||
if not sect:
|
||||
if not only_new:
|
||||
self.keybindings[sectname] = collections.OrderedDict()
|
||||
else:
|
||||
for keychain, command in sect.items():
|
||||
self._add_binding(sectname, keychain, command)
|
||||
self.changed.emit(sectname)
|
||||
|
||||
if mark_dirty:
|
||||
self._mark_config_dirty()
|
||||
|
||||
def _is_new(self, sectname, command, keychain):
|
||||
"""Check if a given binding is new.
|
||||
|
||||
A binding is considered new if both the command is not bound to any key
|
||||
yet, and the key isn't used anywhere else in the same section.
|
||||
"""
|
||||
if utils.is_special_key(keychain):
|
||||
keychain = keychain.lower()
|
||||
|
||||
try:
|
||||
bindings = self.keybindings[sectname]
|
||||
except KeyError:
|
||||
return True
|
||||
if keychain in bindings:
|
||||
return False
|
||||
else:
|
||||
return command not in bindings.values()
|
||||
|
||||
def _read(self, relaxed=False):
|
||||
"""Read the config file from disk and parse it.
|
||||
|
||||
Args:
|
||||
relaxed: Ignore unknown commands.
|
||||
"""
|
||||
try:
|
||||
with open(self._configfile, 'r', encoding='utf-8') as f:
|
||||
for i, line in enumerate(f):
|
||||
line = line.rstrip()
|
||||
try:
|
||||
if not line.strip() or line.startswith('#'):
|
||||
continue
|
||||
elif line.startswith('[') and line.endswith(']'):
|
||||
sectname = line[1:-1]
|
||||
self._cur_section = self._normalize_sectname(
|
||||
sectname)
|
||||
elif line.startswith((' ', '\t')):
|
||||
line = line.strip()
|
||||
self._read_keybinding(line)
|
||||
else:
|
||||
line = line.strip()
|
||||
self._read_command(line)
|
||||
except (KeyConfigError, cmdexc.CommandError) as e:
|
||||
if relaxed:
|
||||
continue
|
||||
else:
|
||||
e.lineno = i
|
||||
raise
|
||||
except OSError:
|
||||
log.keyboard.exception("Failed to read key bindings!")
|
||||
for sectname in self.keybindings:
|
||||
self.changed.emit(sectname)
|
||||
|
||||
def _mark_config_dirty(self):
|
||||
"""Mark the config as dirty."""
|
||||
self.is_dirty = True
|
||||
self.config_dirty.emit()
|
||||
|
||||
def _validate_command(self, line, modes=None):
|
||||
"""Check if a given command is valid.
|
||||
|
||||
Args:
|
||||
line: The commandline to validate.
|
||||
modes: A list of modes to validate the commands for, or None.
|
||||
"""
|
||||
from qutebrowser.config import config
|
||||
if line == self.UNBOUND_COMMAND:
|
||||
return
|
||||
commands = line.split(';;')
|
||||
try:
|
||||
first_cmd = commands[0].split(maxsplit=1)[0].strip()
|
||||
cmd = cmdutils.cmd_dict[first_cmd]
|
||||
if cmd.no_cmd_split:
|
||||
commands = [line]
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
for cmd in commands:
|
||||
if not cmd.strip():
|
||||
raise KeyConfigError("Got empty command (line: {!r})!".format(
|
||||
line))
|
||||
commands = [c.split(maxsplit=1)[0].strip() for c in commands]
|
||||
for cmd in commands:
|
||||
aliases = config.section('aliases')
|
||||
if cmd in cmdutils.cmd_dict:
|
||||
cmdname = cmd
|
||||
elif cmd in aliases:
|
||||
cmdname = aliases[cmd].split(maxsplit=1)[0].strip()
|
||||
else:
|
||||
raise KeyConfigError("Invalid command '{}'!".format(cmd))
|
||||
cmd_obj = cmdutils.cmd_dict[cmdname]
|
||||
for m in modes or []:
|
||||
cmd_obj.validate_mode(m)
|
||||
|
||||
def _read_command(self, line):
|
||||
"""Read a command from a line."""
|
||||
if self._cur_section is None:
|
||||
raise KeyConfigError("Got command '{}' without getting a "
|
||||
"section!".format(line))
|
||||
else:
|
||||
for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
|
||||
if rgx.match(line):
|
||||
line = rgx.sub(repl, line)
|
||||
self._mark_config_dirty()
|
||||
break
|
||||
self._validate_command(line)
|
||||
self._cur_command = line
|
||||
|
||||
def _read_keybinding(self, line):
|
||||
"""Read a key binding from a line."""
|
||||
if self._cur_command is None:
|
||||
raise KeyConfigError("Got key binding '{}' without getting a "
|
||||
"command!".format(line))
|
||||
else:
|
||||
assert self._cur_section is not None
|
||||
self._add_binding(self._cur_section, line, self._cur_command)
|
||||
|
||||
def _add_binding(self, sectname, keychain, command, *, force=False):
|
||||
"""Add a new binding from keychain to command in section sectname."""
|
||||
if utils.is_special_key(keychain):
|
||||
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
|
||||
keychain = keychain.lower()
|
||||
log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
|
||||
keychain, command, sectname))
|
||||
if sectname not in self.keybindings:
|
||||
self.keybindings[sectname] = collections.OrderedDict()
|
||||
if keychain in self.get_bindings_for(sectname):
|
||||
if force or command == self.UNBOUND_COMMAND:
|
||||
self.unbind(keychain, mode=sectname)
|
||||
else:
|
||||
raise DuplicateKeychainError(keychain)
|
||||
section = self.keybindings[sectname]
|
||||
if (command != self.UNBOUND_COMMAND and
|
||||
section.get(keychain, None) == self.UNBOUND_COMMAND):
|
||||
# re-binding an unbound keybinding
|
||||
del section[keychain]
|
||||
self.keybindings[sectname][keychain] = command
|
||||
|
||||
def get_bindings_for(self, section):
|
||||
"""Get a dict with all merged key bindings for a section."""
|
||||
bindings = {}
|
||||
for sectstring, d in self.keybindings.items():
|
||||
if sectstring.startswith('!'):
|
||||
inverted = True
|
||||
sectstring = sectstring[1:]
|
||||
else:
|
||||
inverted = False
|
||||
sects = [s.strip() for s in sectstring.split(',')]
|
||||
matches = any(s == section for s in sects)
|
||||
if (not inverted and matches) or (inverted and not matches):
|
||||
bindings.update(d)
|
||||
try:
|
||||
bindings.update(self.keybindings['all'])
|
||||
except KeyError:
|
||||
pass
|
||||
bindings = {k: v for k, v in bindings.items()
|
||||
if v != self.UNBOUND_COMMAND}
|
||||
return bindings
|
||||
|
||||
def get_reverse_bindings_for(self, section):
|
||||
"""Get a dict of commands to a list of bindings for the section."""
|
||||
cmd_to_keys = {}
|
||||
for key, full_cmd in self.get_bindings_for(section).items():
|
||||
for cmd in full_cmd.split(';;'):
|
||||
cmd = cmd.strip()
|
||||
cmd_to_keys.setdefault(cmd, [])
|
||||
# put special bindings last
|
||||
if utils.is_special_key(key):
|
||||
cmd_to_keys[cmd].append(key)
|
||||
else:
|
||||
cmd_to_keys[cmd].insert(0, key)
|
||||
return cmd_to_keys
|
@ -1,231 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Setting sections used for qutebrowser."""
|
||||
|
||||
import collections
|
||||
|
||||
from qutebrowser.config import value as confvalue
|
||||
|
||||
|
||||
class Section:
|
||||
|
||||
"""Base class for KeyValue/ValueList sections.
|
||||
|
||||
Attributes:
|
||||
_readonly: Whether this section is read-only.
|
||||
values: An OrderedDict with key as index and value as value.
|
||||
key: string
|
||||
value: SettingValue
|
||||
descriptions: A dict with the description strings for the keys.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.values = None
|
||||
self.descriptions = {}
|
||||
self._readonly = False
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get the value for key.
|
||||
|
||||
Args:
|
||||
key: The key to get a value for, as a string.
|
||||
|
||||
Return:
|
||||
The value, as value class.
|
||||
"""
|
||||
return self.values[key]
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over all set values."""
|
||||
return iter(self.values)
|
||||
|
||||
def __bool__(self):
|
||||
"""Get boolean state of section."""
|
||||
return bool(self.values)
|
||||
|
||||
def __contains__(self, key):
|
||||
"""Return whether the section contains a given key."""
|
||||
return key in self.values
|
||||
|
||||
def items(self):
|
||||
"""Get dict items."""
|
||||
return self.values.items()
|
||||
|
||||
def keys(self):
|
||||
"""Get value keys."""
|
||||
return self.values.keys()
|
||||
|
||||
def delete(self, key):
|
||||
"""Delete item with given key."""
|
||||
del self.values[key]
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
"""Set the value on a layer.
|
||||
|
||||
Args:
|
||||
layer: The layer to set the value on, an element name of the
|
||||
ValueLayers dict.
|
||||
key: The key of the element to set.
|
||||
value: The value to set.
|
||||
interpolated: The interpolated value, for checking, or None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def dump_userconfig(self):
|
||||
"""Dump the part of the config which was changed by the user.
|
||||
|
||||
Return:
|
||||
A list of (key, valuestr) tuples.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class KeyValue(Section):
|
||||
|
||||
"""Representation of a section with ordinary key-value mappings.
|
||||
|
||||
This is a section which contains normal "key = value" pairs with a fixed
|
||||
set of keys.
|
||||
"""
|
||||
|
||||
def __init__(self, *defaults, readonly=False):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
*defaults: A (key, value, description) list of defaults.
|
||||
readonly: Whether this config is readonly.
|
||||
"""
|
||||
super().__init__()
|
||||
self._readonly = readonly
|
||||
if not defaults:
|
||||
return
|
||||
self.values = collections.OrderedDict()
|
||||
for (k, v, desc) in defaults:
|
||||
assert k not in self.values, k
|
||||
self.values[k] = v
|
||||
self.descriptions[k] = desc
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
if self._readonly:
|
||||
raise ValueError("Trying to modify a read-only config!")
|
||||
self.values[key].setv(layer, value, interpolated)
|
||||
|
||||
def dump_userconfig(self):
|
||||
changed = []
|
||||
for k, v in self.items():
|
||||
vals = v.values
|
||||
if vals['temp'] is not None and vals['temp'] != vals['default']:
|
||||
changed.append((k, vals['temp']))
|
||||
elif vals['conf'] is not None and vals['conf'] != vals['default']:
|
||||
changed.append((k, vals['conf']))
|
||||
return changed
|
||||
|
||||
|
||||
class ValueList(Section):
|
||||
|
||||
"""This class represents a section with a list key-value settings.
|
||||
|
||||
These are settings inside sections which don't have fixed keys, but instead
|
||||
have a dynamic list of "key = value" pairs, like key bindings or
|
||||
searchengines.
|
||||
|
||||
They basically consist of two different SettingValues.
|
||||
|
||||
Attributes:
|
||||
layers: An OrderedDict of the config layers.
|
||||
keytype: The type to use for the key (only used for validating)
|
||||
valtype: The type to use for the value.
|
||||
_ordered_value_cache: A ChainMap-like OrderedDict of all values.
|
||||
_readonly: Whether this section is read-only.
|
||||
"""
|
||||
|
||||
def __init__(self, keytype, valtype, *defaults, readonly=False):
|
||||
"""Wrap types over default values. Take care when overriding this.
|
||||
|
||||
Args:
|
||||
keytype: The type instance to be used for keys.
|
||||
valtype: The type instance to be used for values.
|
||||
*defaults: A (key, value) list of default values.
|
||||
readonly: Whether this config is readonly.
|
||||
"""
|
||||
super().__init__()
|
||||
self._readonly = readonly
|
||||
self._ordered_value_cache = None
|
||||
self.keytype = keytype
|
||||
self.valtype = valtype
|
||||
self.layers = collections.OrderedDict([
|
||||
('default', collections.OrderedDict()),
|
||||
('conf', collections.OrderedDict()),
|
||||
('temp', collections.OrderedDict()),
|
||||
])
|
||||
defaultlayer = self.layers['default']
|
||||
for key, value in defaults:
|
||||
assert key not in defaultlayer, key
|
||||
defaultlayer[key] = confvalue.SettingValue(valtype, value)
|
||||
self.values = collections.ChainMap(
|
||||
self.layers['temp'], self.layers['conf'], self.layers['default'])
|
||||
|
||||
def _ordered_values(self):
|
||||
"""Get ordered values in layers.
|
||||
|
||||
This is more expensive than the ChainMap, but we need this for
|
||||
iterating/items/etc. when order matters.
|
||||
"""
|
||||
if self._ordered_value_cache is None:
|
||||
self._ordered_value_cache = collections.OrderedDict()
|
||||
for layer in self.layers.values():
|
||||
self._ordered_value_cache.update(layer)
|
||||
return self._ordered_value_cache
|
||||
|
||||
def setv(self, layer, key, value, interpolated):
|
||||
if self._readonly:
|
||||
raise ValueError("Trying to modify a read-only config!")
|
||||
self.keytype.validate(key)
|
||||
if key in self.layers[layer]:
|
||||
self.layers[layer][key].setv(layer, value, interpolated)
|
||||
else:
|
||||
val = confvalue.SettingValue(self.valtype)
|
||||
val.setv(layer, value, interpolated)
|
||||
self.layers[layer][key] = val
|
||||
self._ordered_value_cache = None
|
||||
|
||||
def dump_userconfig(self):
|
||||
changed = []
|
||||
mapping = collections.ChainMap(self.layers['temp'],
|
||||
self.layers['conf'])
|
||||
for k, v in mapping.items():
|
||||
try:
|
||||
if v.value() != self.layers['default'][k].value():
|
||||
changed.append((k, v.value()))
|
||||
except KeyError:
|
||||
changed.append((k, v.value()))
|
||||
return changed
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over all set values."""
|
||||
return self._ordered_values().__iter__()
|
||||
|
||||
def items(self):
|
||||
"""Get dict items."""
|
||||
return self._ordered_values().items()
|
||||
|
||||
def keys(self):
|
||||
"""Get value keys."""
|
||||
return self._ordered_values().keys()
|
@ -1,100 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Utilities related to the look&feel of qutebrowser."""
|
||||
|
||||
import functools
|
||||
import collections
|
||||
|
||||
import jinja2
|
||||
import sip
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, objreg
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=16)
|
||||
def get_stylesheet(template_str):
|
||||
"""Format a stylesheet based on a template.
|
||||
|
||||
Args:
|
||||
template_str: The stylesheet template as string.
|
||||
|
||||
Return:
|
||||
The formatted template as string.
|
||||
"""
|
||||
colordict = ColorDict(config.section('colors'))
|
||||
template = jinja2.Template(template_str)
|
||||
return template.render(color=colordict, font=config.section('fonts'),
|
||||
config=objreg.get('config'))
|
||||
|
||||
|
||||
def set_register_stylesheet(obj):
|
||||
"""Set the stylesheet for an object based on it's STYLESHEET attribute.
|
||||
|
||||
Also, register an update when the config is changed.
|
||||
|
||||
Args:
|
||||
obj: The object to set the stylesheet for and register.
|
||||
Must have a STYLESHEET attribute.
|
||||
"""
|
||||
qss = get_stylesheet(obj.STYLESHEET)
|
||||
log.config.vdebug("stylesheet for {}: {}".format(
|
||||
obj.__class__.__name__, qss))
|
||||
obj.setStyleSheet(qss)
|
||||
objreg.get('config').changed.connect(
|
||||
functools.partial(_update_stylesheet, obj))
|
||||
|
||||
|
||||
def _update_stylesheet(obj):
|
||||
"""Update the stylesheet for obj."""
|
||||
get_stylesheet.cache_clear()
|
||||
if not sip.isdeleted(obj):
|
||||
obj.setStyleSheet(get_stylesheet(obj.STYLESHEET))
|
||||
|
||||
|
||||
class ColorDict(collections.UserDict):
|
||||
|
||||
"""A dict aimed at Qt stylesheet colors."""
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Override dict __getitem__.
|
||||
|
||||
Args:
|
||||
key: The key to get from the dict.
|
||||
|
||||
Return:
|
||||
If a value wasn't found, return an empty string.
|
||||
(Color not defined, so no output in the stylesheet)
|
||||
|
||||
else, return the plain value.
|
||||
"""
|
||||
try:
|
||||
val = self.data[key]
|
||||
except KeyError:
|
||||
log.config.exception("No color defined for {}!".format(key))
|
||||
return ''
|
||||
if isinstance(val, QColor):
|
||||
# This could happen when accidentally declaring something as
|
||||
# QtColor instead of Color in the config, and it'd go unnoticed as
|
||||
# the CSS is invalid then.
|
||||
raise TypeError("QColor passed to ColorDict!")
|
||||
else:
|
||||
return val
|
@ -1,39 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Textwrapper used for config files."""
|
||||
|
||||
import textwrap
|
||||
|
||||
|
||||
class TextWrapper(textwrap.TextWrapper):
|
||||
|
||||
"""Text wrapper customized to be used in configs."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kw = {
|
||||
'width': 72,
|
||||
'replace_whitespace': False,
|
||||
'break_long_words': False,
|
||||
'break_on_hyphens': False,
|
||||
'initial_indent': '# ',
|
||||
'subsequent_indent': '# ',
|
||||
}
|
||||
kw.update(kwargs)
|
||||
super().__init__(**kw)
|
@ -1,101 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""A single value (with multiple layers possibly) in the config."""
|
||||
|
||||
import collections
|
||||
|
||||
|
||||
class SettingValue:
|
||||
|
||||
"""Base class for setting values.
|
||||
|
||||
Intended to be sub-classed by config value "types".
|
||||
|
||||
Attributes:
|
||||
typ: A BaseType subclass instance.
|
||||
value: (readonly property) The currently valid, most important value.
|
||||
values: An OrderedDict with the values on different layers, with the
|
||||
most significant layer first.
|
||||
"""
|
||||
|
||||
def __init__(self, typ, default=None, *, backends=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
typ: The BaseType to use.
|
||||
default: Raw value to set.
|
||||
backend: A list of usertypes.Backend enum members to mark this
|
||||
setting as unsupported with other backends.
|
||||
"""
|
||||
self.typ = typ
|
||||
self.values = collections.OrderedDict.fromkeys(
|
||||
['temp', 'conf', 'default'])
|
||||
self.values['default'] = default
|
||||
self.backends = backends
|
||||
|
||||
def __str__(self):
|
||||
"""Get raw string value."""
|
||||
return self.value()
|
||||
|
||||
def default(self):
|
||||
"""Get the default value."""
|
||||
return self.values['default']
|
||||
|
||||
def getlayers(self, startlayer):
|
||||
"""Get a dict of values starting with startlayer.
|
||||
|
||||
Args:
|
||||
startlayer: The first layer to include.
|
||||
"""
|
||||
idx = list(self.values.keys()).index(startlayer)
|
||||
d = collections.OrderedDict(list(self.values.items())[idx:])
|
||||
return d
|
||||
|
||||
def value(self, startlayer=None):
|
||||
"""Get the first valid value starting from startlayer.
|
||||
|
||||
Args:
|
||||
startlayer: The first layer to include.
|
||||
"""
|
||||
if startlayer is None:
|
||||
d = self.values
|
||||
else:
|
||||
d = self.getlayers(startlayer)
|
||||
for val in d.values():
|
||||
if val is not None:
|
||||
return val
|
||||
raise ValueError("No valid config value found!")
|
||||
|
||||
def transformed(self):
|
||||
"""Get the transformed value."""
|
||||
return self.typ.transform(self.value())
|
||||
|
||||
def setv(self, layer, value, interpolated):
|
||||
"""Set the value on a layer.
|
||||
|
||||
Args:
|
||||
layer: The layer to set the value on, an element name of the
|
||||
ValueLayers dict.
|
||||
value: The value to set.
|
||||
interpolated: The interpolated value, for typechecking (or None).
|
||||
"""
|
||||
if interpolated is not None:
|
||||
self.typ.validate(interpolated)
|
||||
self.values[layer] = value
|
@ -192,21 +192,19 @@ class FontFamilySetter(Setter):
|
||||
|
||||
def init_mappings(mappings):
|
||||
"""Initialize all settings based on a settings mapping."""
|
||||
for sectname, section in mappings.items():
|
||||
for optname, mapping in section.items():
|
||||
value = config.get(sectname, optname)
|
||||
log.config.vdebug("Setting {} -> {} to {!r}".format(
|
||||
sectname, optname, value))
|
||||
mapping.set(value)
|
||||
for option, mapping in mappings.items():
|
||||
value = config.instance.get(option)
|
||||
log.config.vdebug("Setting {} to {!r}".format(option, value))
|
||||
mapping.set(value)
|
||||
|
||||
|
||||
def update_mappings(mappings, section, option):
|
||||
def update_mappings(mappings, option):
|
||||
"""Update global settings when QWeb(Engine)Settings changed."""
|
||||
try:
|
||||
mapping = mappings[section][option]
|
||||
mapping = mappings[option]
|
||||
except KeyError:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
value = config.instance.get(option)
|
||||
mapping.set(value)
|
||||
|
||||
|
||||
|
@ -7,9 +7,6 @@ vim: ft=html fileencoding=utf-8 sts=4 sw=4 et:
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ title }}</title>
|
||||
{% if icon %}
|
||||
<link rel='icon' type='image/png' href="{{ icon }}">
|
||||
{% endif %}
|
||||
<style type="text/css">
|
||||
{% block style %}
|
||||
body {
|
||||
|
@ -54,13 +54,13 @@ ul.files > li {
|
||||
|
||||
<ul class="folders">
|
||||
{% for item in directories %}
|
||||
<li><a href="{{ file_url(item.absname) }}">{{item.name}}</a></li>
|
||||
<li><a href="{{ file_url(item['absname']) }}">{{item['name']}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<ul class="files">
|
||||
{% for item in files %}
|
||||
<li><a href="{{ file_url(item.absname) }}">{{item.name}}</a></li>
|
||||
<li><a href="{{ file_url(item['absname']) }}">{{item['name']}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -54,7 +54,7 @@ table {
|
||||
<a href="#" id="load" style="display: none">Show more</a>
|
||||
<script type="text/javascript" src="qute://javascript/history.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.SESSION_INTERVAL = {{session_interval}} * 60 * 1000;
|
||||
window.GAP_INTERVAL = {{gap_interval}} * 60 * 1000;
|
||||
|
||||
window.onload = function() {
|
||||
var loadLink = document.getElementById('load');
|
||||
|
@ -74,7 +74,7 @@ li {
|
||||
<p>Error while opening {{ url }}<br>
|
||||
<p id="error-message-text" style="color: #a31a1a;">qutebrowser can't find a suitable pdf.js installation</p></p>
|
||||
|
||||
<p>It looks like you set <code>content -> enable-pdfjs</code>
|
||||
<p>It looks like you set <code>content.pdfjs</code>
|
||||
to <em>true</em> but qutebrowser can't find the required files.</p>
|
||||
|
||||
<br>
|
||||
@ -82,7 +82,7 @@ li {
|
||||
<h2>Possible fixes</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Disable <code>content -> enable-pdfjs</code> and reload the page.
|
||||
Disable <code>content.pdfjs</code> and reload the page.
|
||||
You will need to download the pdf-file and open it with an external
|
||||
tool instead.
|
||||
</li>
|
||||
|
@ -1,9 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block script %}
|
||||
var cset = function(section, option, el) {
|
||||
value = el.value;
|
||||
window.qute.set(section, option, value);
|
||||
var cset = function(option, value) {
|
||||
// FIXME:conf we might want some error handling here?
|
||||
var url = "qute://settings/set?option=" + encodeURIComponent(option);
|
||||
url += "&value=" + encodeURIComponent(value);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url);
|
||||
xhr.send();
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
@ -22,24 +26,23 @@ th pre { color: grey; text-align: left; }
|
||||
<noscript><h1 class="noscript">View Only</h1><p class="noscript-text">Changing settings requires javascript to be enabled!</p></noscript>
|
||||
<header><h1>{{ title }}</h1></header>
|
||||
<table>
|
||||
{% for section in config.DATA %}
|
||||
<tr><th colspan="2"><h3>{{ section }}</h3><pre>{{ config.SECTION_DESC.get(section)|wordwrap(width=120) }}</pre></th></tr>
|
||||
{% for d, e in config.DATA.get(section).items() %}
|
||||
{% for option in configdata.DATA.values() %}
|
||||
<tr>
|
||||
<td>{{ d }} (Current: {{ confget(section, d)|truncate(100) }})
|
||||
{% if config.DATA.get(section).descriptions[d] %}
|
||||
<p class="option_description">{{ config.DATA.get(section).descriptions[d]|e }}</p>
|
||||
<!-- FIXME: convert to string properly -->
|
||||
<td>{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }})
|
||||
{% if option.description %}
|
||||
<p class="option_description">{{ option.description|e }}</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
onblur="cset('{{ section }}', '{{ d }}', this)"
|
||||
value="{{ confget(section, d) }}">
|
||||
id="input-{{ option.name }}"
|
||||
onblur="cset('{{ option.name }}', this.value)"
|
||||
value="{{ confget(option.name) }}">
|
||||
</input>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
@ -79,9 +79,9 @@ window.loadHistory = (function() {
|
||||
|
||||
// Create session-separator and new tbody if necessary
|
||||
if (tbody.lastChild !== null && lastItemDate !== null &&
|
||||
window.SESSION_INTERVAL > 0) {
|
||||
window.GAP_INTERVAL > 0) {
|
||||
var interval = lastItemDate.getTime() - date.getTime();
|
||||
if (interval > window.SESSION_INTERVAL) {
|
||||
if (interval > window.GAP_INTERVAL) {
|
||||
// Add session-separator
|
||||
var sessionSeparator = document.createElement("td");
|
||||
sessionSeparator.className = "session-separator";
|
||||
|
@ -20,13 +20,12 @@
|
||||
"""Base class for vim-like key sequence parser."""
|
||||
|
||||
import re
|
||||
import functools
|
||||
import unicodedata
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, log, utils, objreg
|
||||
from qutebrowser.utils import usertypes, log, utils
|
||||
|
||||
|
||||
class BaseKeyParser(QObject):
|
||||
@ -41,7 +40,6 @@ class BaseKeyParser(QObject):
|
||||
partial: No keychain matched yet, but it's still possible in the
|
||||
future.
|
||||
definitive: Keychain matches exactly.
|
||||
ambiguous: There are both a partial and a definitive match.
|
||||
none: No more matches possible.
|
||||
|
||||
Types: type of a key binding.
|
||||
@ -59,7 +57,6 @@ class BaseKeyParser(QObject):
|
||||
_warn_on_keychains: Whether a warning should be logged when binding
|
||||
keychains in a section which does not support them.
|
||||
_keystring: The currently entered key sequence
|
||||
_ambiguous_timer: Timer for delayed execution with ambiguous bindings.
|
||||
_modename: The name of the input mode associated with this keyparser.
|
||||
_supports_count: Whether count is supported
|
||||
_supports_chains: Whether keychains are supported
|
||||
@ -78,16 +75,13 @@ class BaseKeyParser(QObject):
|
||||
do_log = True
|
||||
passthrough = False
|
||||
|
||||
Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous',
|
||||
'other', 'none'])
|
||||
Match = usertypes.enum('Match', ['partial', 'definitive', 'other', 'none'])
|
||||
Type = usertypes.enum('Type', ['chain', 'special'])
|
||||
|
||||
def __init__(self, win_id, parent=None, supports_count=None,
|
||||
supports_chains=False):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._ambiguous_timer = usertypes.Timer(self, 'ambiguous-match')
|
||||
self._ambiguous_timer.setSingleShot(True)
|
||||
self._modename = None
|
||||
self._keystring = ''
|
||||
if supports_count is None:
|
||||
@ -97,6 +91,7 @@ class BaseKeyParser(QObject):
|
||||
self._warn_on_keychains = True
|
||||
self.bindings = {}
|
||||
self.special_bindings = {}
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, supports_count=self._supports_count,
|
||||
@ -126,7 +121,13 @@ class BaseKeyParser(QObject):
|
||||
if binding is None:
|
||||
self._debug_log("Ignoring only-modifier keyeevent.")
|
||||
return False
|
||||
binding = binding.lower()
|
||||
|
||||
key_mappings = config.val.bindings.key_mappings
|
||||
try:
|
||||
binding = key_mappings['<{}>'.format(binding)][1:-1]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
cmdstr = self.special_bindings[binding]
|
||||
except KeyError:
|
||||
@ -182,7 +183,8 @@ class BaseKeyParser(QObject):
|
||||
self._debug_log("Ignoring, no text char")
|
||||
return self.Match.none
|
||||
|
||||
self._stop_timers()
|
||||
key_mappings = config.val.bindings.key_mappings
|
||||
txt = key_mappings.get(txt, txt)
|
||||
self._keystring += txt
|
||||
|
||||
count, cmd_input = self._split_count()
|
||||
@ -198,10 +200,6 @@ class BaseKeyParser(QObject):
|
||||
self._keystring))
|
||||
self.clear_keystring()
|
||||
self.execute(binding, self.Type.chain, count)
|
||||
elif match == self.Match.ambiguous:
|
||||
self._debug_log("Ambiguous match for '{}'.".format(
|
||||
self._keystring))
|
||||
self._handle_ambiguous_match(binding, count)
|
||||
elif match == self.Match.partial:
|
||||
self._debug_log("No match for '{}' (added {})".format(
|
||||
self._keystring, txt))
|
||||
@ -221,11 +219,9 @@ class BaseKeyParser(QObject):
|
||||
|
||||
Return:
|
||||
A tuple (matchtype, binding).
|
||||
matchtype: Match.definitive, Match.ambiguous, Match.partial or
|
||||
Match.none
|
||||
binding: - None with Match.partial/Match.none
|
||||
- The found binding with Match.definitive/
|
||||
Match.ambiguous
|
||||
matchtype: Match.definitive, Match.partial or Match.none.
|
||||
binding: - None with Match.partial/Match.none.
|
||||
- The found binding with Match.definitive.
|
||||
"""
|
||||
# A (cmd_input, binding) tuple (k, v of bindings) or None.
|
||||
definitive_match = None
|
||||
@ -243,58 +239,13 @@ class BaseKeyParser(QObject):
|
||||
elif binding.startswith(cmd_input):
|
||||
partial_match = True
|
||||
break
|
||||
if definitive_match is not None and partial_match:
|
||||
return (self.Match.ambiguous, definitive_match[1])
|
||||
elif definitive_match is not None:
|
||||
if definitive_match is not None:
|
||||
return (self.Match.definitive, definitive_match[1])
|
||||
elif partial_match:
|
||||
return (self.Match.partial, None)
|
||||
else:
|
||||
return (self.Match.none, None)
|
||||
|
||||
def _stop_timers(self):
|
||||
"""Stop a delayed execution if any is running."""
|
||||
if self._ambiguous_timer.isActive() and self.do_log:
|
||||
log.keyboard.debug("Stopping delayed execution.")
|
||||
self._ambiguous_timer.stop()
|
||||
try:
|
||||
self._ambiguous_timer.timeout.disconnect()
|
||||
except TypeError:
|
||||
# no connections
|
||||
pass
|
||||
|
||||
def _handle_ambiguous_match(self, binding, count):
|
||||
"""Handle an ambiguous match.
|
||||
|
||||
Args:
|
||||
binding: The command-string to execute.
|
||||
count: The count to pass.
|
||||
"""
|
||||
self._debug_log("Ambiguous match for '{}'".format(self._keystring))
|
||||
time = config.get('input', 'timeout')
|
||||
if time == 0:
|
||||
# execute immediately
|
||||
self.clear_keystring()
|
||||
self.execute(binding, self.Type.chain, count)
|
||||
else:
|
||||
# execute in `time' ms
|
||||
self._debug_log("Scheduling execution of {} in {}ms".format(
|
||||
binding, time))
|
||||
self._ambiguous_timer.setInterval(time)
|
||||
self._ambiguous_timer.timeout.connect(
|
||||
functools.partial(self.delayed_exec, binding, count))
|
||||
self._ambiguous_timer.start()
|
||||
|
||||
def delayed_exec(self, command, count):
|
||||
"""Execute a delayed command.
|
||||
|
||||
Args:
|
||||
command/count: As if passed to self.execute()
|
||||
"""
|
||||
self._debug_log("Executing delayed command now!")
|
||||
self.clear_keystring()
|
||||
self.execute(command, self.Type.chain, count)
|
||||
|
||||
def handle(self, e):
|
||||
"""Handle a new keypress and call the respective handlers.
|
||||
|
||||
@ -314,7 +265,11 @@ class BaseKeyParser(QObject):
|
||||
self.keystring_updated.emit(self._keystring)
|
||||
return match != self.Match.none
|
||||
|
||||
def read_config(self, modename=None):
|
||||
@config.change_filter('bindings')
|
||||
def _on_config_changed(self):
|
||||
self._read_config()
|
||||
|
||||
def _read_config(self, modename=None):
|
||||
"""Read the configuration.
|
||||
|
||||
Config format: key = command, e.g.:
|
||||
@ -332,16 +287,15 @@ class BaseKeyParser(QObject):
|
||||
self._modename = modename
|
||||
self.bindings = {}
|
||||
self.special_bindings = {}
|
||||
keyconfparser = objreg.get('key-config')
|
||||
for (key, cmd) in keyconfparser.get_bindings_for(modename).items():
|
||||
|
||||
for key, cmd in config.key_instance.get_bindings_for(modename).items():
|
||||
assert cmd
|
||||
self._parse_key_command(modename, key, cmd)
|
||||
|
||||
def _parse_key_command(self, modename, key, cmd):
|
||||
"""Parse the keys and their command and store them in the object."""
|
||||
if utils.is_special_key(key):
|
||||
keystr = utils.normalize_keystr(key[1:-1])
|
||||
self.special_bindings[keystr] = cmd
|
||||
self.special_bindings[key[1:-1]] = cmd
|
||||
elif self._supports_chains:
|
||||
self.bindings[key] = cmd
|
||||
elif self._warn_on_keychains:
|
||||
@ -359,15 +313,6 @@ class BaseKeyParser(QObject):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_keyconfig_changed(self, mode):
|
||||
"""Re-read the config if a key binding was changed."""
|
||||
if self._modename is None:
|
||||
raise AssertionError("on_keyconfig_changed called but no section "
|
||||
"defined!")
|
||||
if mode == self._modename:
|
||||
self.read_config()
|
||||
|
||||
def clear_keystring(self):
|
||||
"""Clear the currently entered key sequence."""
|
||||
if self._keystring:
|
||||
|
@ -42,7 +42,7 @@ class CommandKeyParser(BaseKeyParser):
|
||||
def execute(self, cmdstr, _keytype, count=None):
|
||||
try:
|
||||
self._commandrunner.run(cmdstr, count)
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
|
||||
except cmdexc.Error as e:
|
||||
message.error(str(e), stack=traceback.format_exc())
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ class PassthroughKeyParser(CommandKeyParser):
|
||||
"""
|
||||
super().__init__(win_id, parent, supports_chains=False)
|
||||
self._warn_on_keychains = warn
|
||||
self.read_config(mode)
|
||||
self._read_config(mode)
|
||||
self._mode = mode
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -144,9 +144,6 @@ class ModeManager(QObject):
|
||||
self._parsers = {}
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self._releaseevents_to_pass = set()
|
||||
self._forward_unbound_keys = config.get(
|
||||
'input', 'forward-unbound-keys')
|
||||
objreg.get('config').changed.connect(self.set_forward_unbound_keys)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, mode=self.mode)
|
||||
@ -171,10 +168,12 @@ class ModeManager(QObject):
|
||||
event.modifiers() not in [Qt.NoModifier, Qt.ShiftModifier] or
|
||||
not event.text().strip())
|
||||
|
||||
forward_unbound_keys = config.val.input.forward_unbound_keys
|
||||
|
||||
if handled:
|
||||
filter_this = True
|
||||
elif (parser.passthrough or self._forward_unbound_keys == 'all' or
|
||||
(self._forward_unbound_keys == 'auto' and is_non_alnum)):
|
||||
elif (parser.passthrough or forward_unbound_keys == 'all' or
|
||||
(forward_unbound_keys == 'auto' and is_non_alnum)):
|
||||
filter_this = False
|
||||
else:
|
||||
filter_this = True
|
||||
@ -184,10 +183,10 @@ class ModeManager(QObject):
|
||||
|
||||
if curmode != usertypes.KeyMode.insert:
|
||||
focus_widget = QApplication.instance().focusWidget()
|
||||
log.modes.debug("handled: {}, forward-unbound-keys: {}, "
|
||||
log.modes.debug("handled: {}, forward_unbound_keys: {}, "
|
||||
"passthrough: {}, is_non_alnum: {} --> "
|
||||
"filter: {} (focused: {!r})".format(
|
||||
handled, self._forward_unbound_keys,
|
||||
handled, forward_unbound_keys,
|
||||
parser.passthrough, is_non_alnum, filter_this,
|
||||
focus_widget))
|
||||
return filter_this
|
||||
@ -313,12 +312,6 @@ class ModeManager(QObject):
|
||||
raise ValueError("Can't leave normal mode!")
|
||||
self.leave(self.mode, 'leave current')
|
||||
|
||||
@config.change_filter('input', 'forward-unbound-keys')
|
||||
def set_forward_unbound_keys(self):
|
||||
"""Update local setting when config changed."""
|
||||
self._forward_unbound_keys = config.get(
|
||||
'input', 'forward-unbound-keys')
|
||||
|
||||
def eventFilter(self, event):
|
||||
"""Filter all events based on the currently set mode.
|
||||
|
||||
|
@ -48,7 +48,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent, supports_count=True,
|
||||
supports_chains=True)
|
||||
self.read_config('normal')
|
||||
self._read_config('normal')
|
||||
self._partial_timer = usertypes.Timer(self, 'partial-match')
|
||||
self._partial_timer.setSingleShot(True)
|
||||
self._inhibited = False
|
||||
@ -74,7 +74,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
return self.Match.none
|
||||
match = super()._handle_single_key(e)
|
||||
if match == self.Match.partial:
|
||||
timeout = config.get('input', 'partial-timeout')
|
||||
timeout = config.val.input.partial_timeout
|
||||
if timeout != 0:
|
||||
self._partial_timer.setInterval(timeout)
|
||||
self._partial_timer.timeout.connect(self._clear_partial_match)
|
||||
@ -130,7 +130,7 @@ class PromptKeyParser(keyparser.CommandKeyParser):
|
||||
supports_chains=True)
|
||||
# We don't want an extra section for this in the config, so we just
|
||||
# abuse the prompt section.
|
||||
self.read_config('prompt')
|
||||
self._read_config('prompt')
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
@ -150,7 +150,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
||||
supports_chains=True)
|
||||
self._filtertext = ''
|
||||
self._last_press = LastPress.none
|
||||
self.read_config('hint')
|
||||
self._read_config('hint')
|
||||
self.keystring_updated.connect(self.on_keystring_updated)
|
||||
|
||||
def _handle_special_key(self, e):
|
||||
@ -264,7 +264,7 @@ class CaretKeyParser(keyparser.CommandKeyParser):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(win_id, parent, supports_count=True,
|
||||
supports_chains=True)
|
||||
self.read_config('caret')
|
||||
self._read_config('caret')
|
||||
|
||||
|
||||
class RegisterKeyParser(keyparser.CommandKeyParser):
|
||||
@ -280,7 +280,7 @@ class RegisterKeyParser(keyparser.CommandKeyParser):
|
||||
super().__init__(win_id, parent, supports_count=False,
|
||||
supports_chains=False)
|
||||
self._mode = mode
|
||||
self.read_config('register')
|
||||
self._read_config('register')
|
||||
|
||||
def handle(self, e):
|
||||
"""Override handle to always match the next key and use the register.
|
||||
@ -316,14 +316,9 @@ class RegisterKeyParser(keyparser.CommandKeyParser):
|
||||
else:
|
||||
raise ValueError(
|
||||
"{} is not a valid register mode".format(self._mode))
|
||||
except (cmdexc.CommandMetaError, cmdexc.CommandError) as err:
|
||||
except cmdexc.Error as err:
|
||||
message.error(str(err), stack=traceback.format_exc())
|
||||
|
||||
self.request_leave.emit(self._mode, "valid register key", True)
|
||||
|
||||
return True
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_keyconfig_changed(self, mode):
|
||||
"""RegisterKeyParser has no config section (no bindable keys)."""
|
||||
pass
|
||||
|
@ -24,14 +24,13 @@ import base64
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
import jinja2
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
|
||||
|
||||
from qutebrowser.commands import runners, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
||||
debug)
|
||||
jinja, debug)
|
||||
from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt
|
||||
from qutebrowser.mainwindow.statusbar import bar
|
||||
from qutebrowser.completion import completionwidget, completer
|
||||
@ -52,7 +51,7 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
||||
via_ipc: Whether the request was made via IPC.
|
||||
force_window: Whether to force opening in a window.
|
||||
force_tab: Whether to force opening in a tab.
|
||||
force_target: Override the new-instance-open-target config
|
||||
force_target: Override the new_instance_open_target config
|
||||
"""
|
||||
if force_window and force_tab:
|
||||
raise ValueError("force_window and force_tab are mutually exclusive!")
|
||||
@ -61,7 +60,7 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
||||
# Initial main window
|
||||
return 0
|
||||
|
||||
open_target = config.get('general', 'new-instance-open-target')
|
||||
open_target = config.val.new_instance_open_target
|
||||
|
||||
# Apply any target overrides, ordered by precedence
|
||||
if force_target is not None:
|
||||
@ -99,7 +98,7 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
||||
def get_target_window():
|
||||
"""Get the target window for new tabs, or None if none exist."""
|
||||
try:
|
||||
win_mode = config.get('general', 'new-instance-open-target.window')
|
||||
win_mode = config.val.new_instance_open_target_window
|
||||
if win_mode == 'last-focused':
|
||||
return objreg.last_focused_window()
|
||||
elif win_mode == 'first-opened':
|
||||
@ -165,7 +164,7 @@ class MainWindow(QWidget):
|
||||
self._init_downloadmanager()
|
||||
self._downloadview = downloadview.DownloadView(self.win_id)
|
||||
|
||||
if config.get('general', 'private-browsing'):
|
||||
if config.val.content.private_browsing:
|
||||
# This setting always trumps what's passed in.
|
||||
private = True
|
||||
else:
|
||||
@ -215,7 +214,7 @@ class MainWindow(QWidget):
|
||||
# resizing will fail. Therefore, we use singleShot QTimers to make sure
|
||||
# we defer this until everything else is initialized.
|
||||
QTimer.singleShot(0, self._connect_overlay_signals)
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
objreg.get("app").new_window.emit(self)
|
||||
|
||||
@ -254,7 +253,7 @@ class MainWindow(QWidget):
|
||||
left = (self.width() - width) / 2 if centered else 0
|
||||
|
||||
height_padding = 20
|
||||
status_position = config.get('ui', 'status-position')
|
||||
status_position = config.val.statusbar.position
|
||||
if status_position == 'bottom':
|
||||
if self.status.isVisible():
|
||||
status_height = self.status.height()
|
||||
@ -308,7 +307,7 @@ class MainWindow(QWidget):
|
||||
def _init_completion(self):
|
||||
self._completion = completionwidget.CompletionView(self.win_id, self)
|
||||
cmd = objreg.get('status-command', scope='window', window=self.win_id)
|
||||
completer_obj = completer.Completer(cmd, self.win_id, self._completion)
|
||||
completer_obj = completer.Completer(cmd, self._completion)
|
||||
self._completion.selection_changed.connect(
|
||||
completer_obj.on_selection_changed)
|
||||
objreg.register('completion', self._completion, scope='window',
|
||||
@ -327,16 +326,14 @@ class MainWindow(QWidget):
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def on_config_changed(self, section, option):
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
"""Resize the completion if related config options changed."""
|
||||
if section != 'ui':
|
||||
return
|
||||
if option == 'statusbar-padding':
|
||||
if option == 'statusbar.padding':
|
||||
self._update_overlay_geometries()
|
||||
elif option == 'downloads-position':
|
||||
elif option == 'downloads.position':
|
||||
self._add_widgets()
|
||||
elif option == 'status-position':
|
||||
elif option == 'statusbar.position':
|
||||
self._add_widgets()
|
||||
self._update_overlay_geometries()
|
||||
|
||||
@ -345,10 +342,9 @@ class MainWindow(QWidget):
|
||||
self._vbox.removeWidget(self.tabbed_browser)
|
||||
self._vbox.removeWidget(self._downloadview)
|
||||
self._vbox.removeWidget(self.status)
|
||||
downloads_position = config.get('ui', 'downloads-position')
|
||||
status_position = config.get('ui', 'status-position')
|
||||
widgets = [self.tabbed_browser]
|
||||
|
||||
downloads_position = config.val.downloads.position
|
||||
if downloads_position == 'top':
|
||||
widgets.insert(0, self._downloadview)
|
||||
elif downloads_position == 'bottom':
|
||||
@ -356,6 +352,7 @@ class MainWindow(QWidget):
|
||||
else:
|
||||
raise ValueError("Invalid position {}!".format(downloads_position))
|
||||
|
||||
status_position = config.val.statusbar.position
|
||||
if status_position == 'top':
|
||||
widgets.insert(0, self.status)
|
||||
elif status_position == 'bottom':
|
||||
@ -417,8 +414,6 @@ class MainWindow(QWidget):
|
||||
|
||||
def _connect_signals(self):
|
||||
"""Connect all mainwindow signals."""
|
||||
key_config = objreg.get('key-config')
|
||||
|
||||
status = self._get_object('statusbar')
|
||||
keyparsers = self._get_object('keyparsers')
|
||||
completion_obj = self._get_object('completion')
|
||||
@ -448,10 +443,6 @@ class MainWindow(QWidget):
|
||||
parser.keystring_updated.connect(functools.partial(
|
||||
self._keyhint.update_keyhint, mode.name))
|
||||
|
||||
# config
|
||||
for obj in keyparsers.values():
|
||||
key_config.changed.connect(obj.on_keyconfig_changed)
|
||||
|
||||
# messages
|
||||
message.global_bridge.show_message.connect(
|
||||
self._messageview.show_message)
|
||||
@ -545,24 +536,23 @@ class MainWindow(QWidget):
|
||||
if crashsignal.is_crashing:
|
||||
e.accept()
|
||||
return
|
||||
confirm_quit = config.get('ui', 'confirm-quit')
|
||||
tab_count = self.tabbed_browser.count()
|
||||
download_model = objreg.get('download-model', scope='window',
|
||||
window=self.win_id)
|
||||
download_count = download_model.running_downloads()
|
||||
quit_texts = []
|
||||
# Ask if multiple-tabs are open
|
||||
if 'multiple-tabs' in confirm_quit and tab_count > 1:
|
||||
if 'multiple-tabs' in config.val.confirm_quit and tab_count > 1:
|
||||
quit_texts.append("{} {} open.".format(
|
||||
tab_count, "tab is" if tab_count == 1 else "tabs are"))
|
||||
# Ask if multiple downloads running
|
||||
if 'downloads' in confirm_quit and download_count > 0:
|
||||
if 'downloads' in config.val.confirm_quit and download_count > 0:
|
||||
quit_texts.append("{} {} running.".format(
|
||||
download_count,
|
||||
"download is" if download_count == 1 else "downloads are"))
|
||||
# Process all quit messages that user must confirm
|
||||
if quit_texts or 'always' in confirm_quit:
|
||||
msg = jinja2.Template("""
|
||||
if quit_texts or 'always' in config.val.confirm_quit:
|
||||
msg = jinja.environment.from_string("""
|
||||
<ul>
|
||||
{% for text in quit_texts %}
|
||||
<li>{{text}}</li>
|
||||
|
@ -23,8 +23,8 @@
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, Qt, QSize
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy
|
||||
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.utils import usertypes, objreg
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes
|
||||
|
||||
|
||||
class Message(QLabel):
|
||||
@ -41,31 +41,32 @@ class Message(QLabel):
|
||||
"""
|
||||
if level == usertypes.MessageLevel.error:
|
||||
stylesheet += """
|
||||
background-color: {{ color['messages.bg.error'] }};
|
||||
color: {{ color['messages.fg.error'] }};
|
||||
font: {{ font['messages.error'] }};
|
||||
border-bottom: 1px solid {{ color['messages.border.error'] }};
|
||||
background-color: {{ conf.colors.messages.error.bg }};
|
||||
color: {{ conf.colors.messages.error.fg }};
|
||||
font: {{ conf.fonts.messages.error }};
|
||||
border-bottom: 1px solid {{ conf.colors.messages.error.border }};
|
||||
"""
|
||||
elif level == usertypes.MessageLevel.warning:
|
||||
stylesheet += """
|
||||
background-color: {{ color['messages.bg.warning'] }};
|
||||
color: {{ color['messages.fg.warning'] }};
|
||||
font: {{ font['messages.warning'] }};
|
||||
background-color: {{ conf.colors.messages.warning.bg }};
|
||||
color: {{ conf.colors.messages.warning.fg }};
|
||||
font: {{ conf.fonts.messages.warning }};
|
||||
border-bottom:
|
||||
1px solid {{ color['messages.border.warning'] }};
|
||||
1px solid {{ conf.colors.messages.warning.border }};
|
||||
"""
|
||||
elif level == usertypes.MessageLevel.info:
|
||||
stylesheet += """
|
||||
background-color: {{ color['messages.bg.info'] }};
|
||||
color: {{ color['messages.fg.info'] }};
|
||||
font: {{ font['messages.info'] }};
|
||||
border-bottom: 1px solid {{ color['messages.border.info'] }}
|
||||
background-color: {{ conf.colors.messages.info.bg }};
|
||||
color: {{ conf.colors.messages.info.fg }};
|
||||
font: {{ conf.fonts.messages.info }};
|
||||
border-bottom: 1px solid {{ conf.colors.messages.info.border }}
|
||||
"""
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Invalid level {!r}".format(level))
|
||||
# We don't bother with set_register_stylesheet here as it's short-lived
|
||||
# anyways.
|
||||
self.setStyleSheet(style.get_stylesheet(stylesheet))
|
||||
config.set_register_stylesheet(self, stylesheet=stylesheet,
|
||||
update=False)
|
||||
|
||||
|
||||
class MessageView(QWidget):
|
||||
@ -76,6 +77,7 @@ class MessageView(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._messages = []
|
||||
self._vbox = QVBoxLayout(self)
|
||||
self._vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._vbox.setSpacing(0)
|
||||
@ -83,10 +85,9 @@ class MessageView(QWidget):
|
||||
|
||||
self._clear_timer = QTimer()
|
||||
self._clear_timer.timeout.connect(self.clear_messages)
|
||||
objreg.get('config').changed.connect(self._set_clear_timer_interval)
|
||||
config.instance.changed.connect(self._set_clear_timer_interval)
|
||||
|
||||
self._last_text = None
|
||||
self._messages = []
|
||||
|
||||
def sizeHint(self):
|
||||
"""Get the proposed height for the view."""
|
||||
@ -94,10 +95,10 @@ class MessageView(QWidget):
|
||||
# The width isn't really relevant as we're expanding anyways.
|
||||
return QSize(-1, height)
|
||||
|
||||
@config.change_filter('ui', 'message-timeout')
|
||||
@config.change_filter('messages.timeout')
|
||||
def _set_clear_timer_interval(self):
|
||||
"""Configure self._clear_timer according to the config."""
|
||||
interval = config.get('ui', 'message-timeout')
|
||||
interval = config.val.messages.timeout
|
||||
if interval > 0:
|
||||
interval *= min(5, len(self._messages))
|
||||
self._clear_timer.setInterval(interval)
|
||||
@ -131,7 +132,7 @@ class MessageView(QWidget):
|
||||
self._last_text = text
|
||||
self.show()
|
||||
self.update_geometry.emit()
|
||||
if config.get('ui', 'message-timeout') != 0:
|
||||
if config.val.messages.timeout != 0:
|
||||
self._set_clear_timer_interval()
|
||||
self._clear_timer.start()
|
||||
|
||||
|
@ -30,7 +30,7 @@ from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit,
|
||||
QLabel, QFileSystemModel, QTreeView, QSizePolicy)
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import style, config
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
@ -233,29 +233,28 @@ class PromptContainer(QWidget):
|
||||
"""
|
||||
|
||||
STYLESHEET = """
|
||||
{% set prompt_radius = config.get('ui', 'prompt-radius') %}
|
||||
QWidget#PromptContainer {
|
||||
{% if config.get('ui', 'status-position') == 'top' %}
|
||||
border-bottom-left-radius: {{ prompt_radius }}px;
|
||||
border-bottom-right-radius: {{ prompt_radius }}px;
|
||||
{% if conf.statusbar.position == 'top' %}
|
||||
border-bottom-left-radius: {{ conf.prompt.radius }}px;
|
||||
border-bottom-right-radius: {{ conf.prompt.radius }}px;
|
||||
{% else %}
|
||||
border-top-left-radius: {{ prompt_radius }}px;
|
||||
border-top-right-radius: {{ prompt_radius }}px;
|
||||
border-top-left-radius: {{ conf.prompt.radius }}px;
|
||||
border-top-right-radius: {{ conf.prompt.radius }}px;
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
QWidget {
|
||||
font: {{ font['prompts'] }};
|
||||
color: {{ color['prompts.fg'] }};
|
||||
background-color: {{ color['prompts.bg'] }};
|
||||
font: {{ conf.fonts.prompts }};
|
||||
color: {{ conf.colors.prompts.fg }};
|
||||
background-color: {{ conf.colors.prompts.bg }};
|
||||
}
|
||||
|
||||
QTreeView {
|
||||
selection-background-color: {{ color['prompts.selected.bg'] }};
|
||||
selection-background-color: {{ conf.colors.prompts.selected.bg }};
|
||||
}
|
||||
|
||||
QTreeView::item:selected, QTreeView::item:selected:hover {
|
||||
background-color: {{ color['prompts.selected.bg'] }};
|
||||
background-color: {{ conf.colors.prompts.selected.bg }};
|
||||
}
|
||||
"""
|
||||
update_geometry = pyqtSignal()
|
||||
@ -269,7 +268,7 @@ class PromptContainer(QWidget):
|
||||
|
||||
self.setObjectName('PromptContainer')
|
||||
self.setAttribute(Qt.WA_StyledBackground, True)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
|
||||
message.global_bridge.prompt_done.connect(self._on_prompt_done)
|
||||
prompt_queue.show_prompts.connect(self._on_show_prompts)
|
||||
@ -483,9 +482,8 @@ class _BasePrompt(QWidget):
|
||||
self._key_grid = QGridLayout()
|
||||
self._key_grid.setVerticalSpacing(0)
|
||||
|
||||
key_config = objreg.get('key-config')
|
||||
# The bindings are all in the 'prompt' mode, even for yesno prompts
|
||||
all_bindings = key_config.get_reverse_bindings_for('prompt')
|
||||
all_bindings = config.key_instance.get_reverse_bindings_for('prompt')
|
||||
labels = []
|
||||
|
||||
for cmd, text in self._allowed_commands():
|
||||
@ -566,7 +564,7 @@ class FilenamePrompt(_BasePrompt):
|
||||
self.setFocusProxy(self._lineedit)
|
||||
self._init_key_label()
|
||||
|
||||
if config.get('ui', 'prompt-filebrowser'):
|
||||
if config.val.prompt.filebrowser:
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||
|
||||
@pyqtSlot(str)
|
||||
@ -628,7 +626,7 @@ class FilenamePrompt(_BasePrompt):
|
||||
self._file_view.setModel(self._file_model)
|
||||
self._file_view.clicked.connect(self._insert_path)
|
||||
|
||||
if config.get('ui', 'prompt-filebrowser'):
|
||||
if config.val.prompt.filebrowser:
|
||||
self._vbox.addWidget(self._file_view)
|
||||
else:
|
||||
self._file_view.hide()
|
||||
|
@ -23,7 +23,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
|
||||
|
||||
from qutebrowser.browser import browsertab
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
from qutebrowser.mainwindow.statusbar import (backforward, command, progress,
|
||||
keystring, percentage, url,
|
||||
@ -82,21 +82,21 @@ class ColorFlags:
|
||||
|
||||
def _generate_stylesheet():
|
||||
flags = [
|
||||
('private', 'statusbar.{}.private'),
|
||||
('caret', 'statusbar.{}.caret'),
|
||||
('caret-selection', 'statusbar.{}.caret-selection'),
|
||||
('prompt', 'prompts.{}'),
|
||||
('insert', 'statusbar.{}.insert'),
|
||||
('command', 'statusbar.{}.command'),
|
||||
('private-command', 'statusbar.{}.command.private'),
|
||||
('private', 'statusbar.private'),
|
||||
('caret', 'statusbar.caret'),
|
||||
('caret-selection', 'statusbar.caret.selection'),
|
||||
('prompt', 'prompts'),
|
||||
('insert', 'statusbar.insert'),
|
||||
('command', 'statusbar.command'),
|
||||
('private-command', 'statusbar.command.private'),
|
||||
]
|
||||
stylesheet = """
|
||||
QWidget#StatusBar,
|
||||
QWidget#StatusBar QLabel,
|
||||
QWidget#StatusBar QLineEdit {
|
||||
font: {{ font['statusbar'] }};
|
||||
background-color: {{ color['statusbar.bg'] }};
|
||||
color: {{ color['statusbar.fg'] }};
|
||||
font: {{ conf.fonts.statusbar }};
|
||||
background-color: {{ conf.colors.statusbar.normal.bg }};
|
||||
color: {{ conf.colors.statusbar.normal.fg }};
|
||||
}
|
||||
"""
|
||||
for flag, option in flags:
|
||||
@ -104,11 +104,11 @@ def _generate_stylesheet():
|
||||
QWidget#StatusBar[color_flags~="%s"],
|
||||
QWidget#StatusBar[color_flags~="%s"] QLabel,
|
||||
QWidget#StatusBar[color_flags~="%s"] QLineEdit {
|
||||
color: {{ color['%s'] }};
|
||||
background-color: {{ color['%s'] }};
|
||||
color: {{ conf.colors.%s }};
|
||||
background-color: {{ conf.colors.%s }};
|
||||
}
|
||||
""" % (flag, flag, flag, # flake8: disable=S001
|
||||
option.format('fg'), option.format('bg'))
|
||||
option + '.fg', option + '.bg')
|
||||
return stylesheet
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ class StatusBar(QWidget):
|
||||
objreg.register('statusbar', self, scope='window', window=win_id)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
self.setAttribute(Qt.WA_StyledBackground)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
|
||||
@ -197,33 +197,31 @@ class StatusBar(QWidget):
|
||||
self.prog = progress.Progress(self)
|
||||
self._hbox.addWidget(self.prog)
|
||||
|
||||
objreg.get('config').changed.connect(self._on_config_changed)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
QTimer.singleShot(0, self.maybe_hide)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def _on_config_changed(self, section, option):
|
||||
if section != 'ui':
|
||||
return
|
||||
if option == 'hide-statusbar':
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option == 'statusbar.hide':
|
||||
self.maybe_hide()
|
||||
elif option == 'statusbar-pdading':
|
||||
elif option == 'statusbar.padding':
|
||||
self._set_hbox_padding()
|
||||
|
||||
@pyqtSlot()
|
||||
def maybe_hide(self):
|
||||
"""Hide the statusbar if it's configured to do so."""
|
||||
hide = config.get('ui', 'hide-statusbar')
|
||||
tab = self._current_tab()
|
||||
hide = config.val.statusbar.hide
|
||||
if hide or (tab is not None and tab.data.fullscreen):
|
||||
self.hide()
|
||||
else:
|
||||
self.show()
|
||||
|
||||
def _set_hbox_padding(self):
|
||||
padding = config.get('ui', 'statusbar-padding')
|
||||
padding = config.val.statusbar.padding
|
||||
self._hbox.setContentsMargins(padding.left, 0, padding.right, 0)
|
||||
|
||||
@pyqtProperty('QStringList')
|
||||
@ -265,11 +263,21 @@ class StatusBar(QWidget):
|
||||
self._color_flags.caret = ColorFlags.CaretMode.on
|
||||
else:
|
||||
self._color_flags.caret = ColorFlags.CaretMode.off
|
||||
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
|
||||
config.set_register_stylesheet(self, update=False)
|
||||
|
||||
def _set_mode_text(self, mode):
|
||||
"""Set the mode text."""
|
||||
text = "-- {} MODE --".format(mode.upper())
|
||||
if mode == 'passthrough':
|
||||
key_instance = config.key_instance
|
||||
all_bindings = key_instance.get_reverse_bindings_for('passthrough')
|
||||
bindings = all_bindings.get('leave-mode')
|
||||
if bindings:
|
||||
suffix = ' ({} to leave)'.format(bindings[0])
|
||||
else:
|
||||
suffix = ''
|
||||
else:
|
||||
suffix = ''
|
||||
text = "-- {} MODE --{}".format(mode.upper(), suffix)
|
||||
self.txt.set_text(self.txt.Text.normal, text)
|
||||
|
||||
def _show_cmd_widget(self):
|
||||
@ -349,7 +357,7 @@ class StatusBar(QWidget):
|
||||
|
||||
def minimumSizeHint(self):
|
||||
"""Set the minimum height to the text height plus some padding."""
|
||||
padding = config.get('ui', 'statusbar-padding')
|
||||
padding = config.val.statusbar.padding
|
||||
width = super().minimumSizeHint().width()
|
||||
height = self.fontMetrics().height() + padding.top + padding.bottom
|
||||
return QSize(width, height)
|
||||
|
@ -22,7 +22,7 @@
|
||||
from PyQt5.QtCore import pyqtSlot, QSize
|
||||
from PyQt5.QtWidgets import QProgressBar, QSizePolicy
|
||||
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, usertypes
|
||||
|
||||
|
||||
@ -35,17 +35,17 @@ class Progress(QProgressBar):
|
||||
border-radius: 0px;
|
||||
border: 2px solid transparent;
|
||||
background-color: transparent;
|
||||
font: {{ font['statusbar'] }};
|
||||
font: {{ conf.fonts.statusbar }};
|
||||
}
|
||||
|
||||
QProgressBar::chunk {
|
||||
background-color: {{ color['statusbar.progress.bg'] }};
|
||||
background-color: {{ conf.colors.statusbar.progress.bg }};
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
self.setTextVisible(False)
|
||||
self.hide()
|
||||
|
@ -22,7 +22,7 @@
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, QUrl
|
||||
|
||||
from qutebrowser.mainwindow.statusbar import textbase
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, urlutils
|
||||
|
||||
|
||||
@ -53,27 +53,27 @@ class UrlText(textbase.TextBase):
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel#UrlText[urltype="normal"] {
|
||||
color: {{ color['statusbar.url.fg'] }};
|
||||
color: {{ conf.colors.statusbar.url.fg }};
|
||||
}
|
||||
|
||||
QLabel#UrlText[urltype="success"] {
|
||||
color: {{ color['statusbar.url.fg.success'] }};
|
||||
color: {{ conf.colors.statusbar.url.success.http.fg }};
|
||||
}
|
||||
|
||||
QLabel#UrlText[urltype="success_https"] {
|
||||
color: {{ color['statusbar.url.fg.success.https'] }};
|
||||
color: {{ conf.colors.statusbar.url.success.https.fg }};
|
||||
}
|
||||
|
||||
QLabel#UrlText[urltype="error"] {
|
||||
color: {{ color['statusbar.url.fg.error'] }};
|
||||
color: {{ conf.colors.statusbar.url.error.fg }};
|
||||
}
|
||||
|
||||
QLabel#UrlText[urltype="warn"] {
|
||||
color: {{ color['statusbar.url.fg.warn'] }};
|
||||
color: {{ conf.colors.statusbar.url.warn.fg }};
|
||||
}
|
||||
|
||||
QLabel#UrlText[urltype="hover"] {
|
||||
color: {{ color['statusbar.url.fg.hover'] }};
|
||||
color: {{ conf.colors.statusbar.url.hover.fg }};
|
||||
}
|
||||
"""
|
||||
|
||||
@ -81,7 +81,7 @@ class UrlText(textbase.TextBase):
|
||||
"""Override TextBase.__init__ to elide in the middle by default."""
|
||||
super().__init__(parent, Qt.ElideMiddle)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
self._hover_url = None
|
||||
self._normal_url = None
|
||||
self._normal_url_type = UrlType.normal
|
||||
@ -109,7 +109,7 @@ class UrlText(textbase.TextBase):
|
||||
else:
|
||||
self.setText('')
|
||||
self._urltype = UrlType.normal
|
||||
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
|
||||
config.set_register_stylesheet(self, update=False)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_load_status_changed(self, status_str):
|
||||
|
@ -62,7 +62,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
_filter: A SignalFilter instance.
|
||||
_now_focused: The tab which is focused now.
|
||||
_tab_insert_idx_left: Where to insert a new tab with
|
||||
tabbar -> new-tab-position set to 'prev'.
|
||||
tabs.new_tab_position set to 'prev'.
|
||||
_tab_insert_idx_right: Same as above, for 'next'.
|
||||
_undo_stack: List of UndoEntry namedtuples of closed tabs.
|
||||
shutting_down: Whether we're currently shutting down.
|
||||
@ -122,13 +122,20 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
self._global_marks = {}
|
||||
self.default_window_icon = self.window().windowIcon()
|
||||
self.private = private
|
||||
objreg.get('config').changed.connect(self.update_favicons)
|
||||
objreg.get('config').changed.connect(self.update_window_title)
|
||||
objreg.get('config').changed.connect(self.update_tab_titles)
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, count=self.count())
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option == 'tabs.favicons.show':
|
||||
self._update_favicons()
|
||||
elif option == 'window.title_format':
|
||||
self._update_window_title()
|
||||
elif option in ['tabs.title.format', 'tabs.title.format_pinned']:
|
||||
self._update_tab_titles()
|
||||
|
||||
def _tab_index(self, tab):
|
||||
"""Get the index of a given tab.
|
||||
|
||||
@ -159,8 +166,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
widgets.append(widget)
|
||||
return widgets
|
||||
|
||||
@config.change_filter('ui', 'window-title-format')
|
||||
def update_window_title(self):
|
||||
def _update_window_title(self):
|
||||
"""Change the window title to match the current tab."""
|
||||
idx = self.currentIndex()
|
||||
if idx == -1:
|
||||
@ -170,8 +176,9 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
fields = self.get_tab_fields(idx)
|
||||
fields['id'] = self._win_id
|
||||
|
||||
fmt = config.get('ui', 'window-title-format')
|
||||
self.window().setWindowTitle(fmt.format(**fields))
|
||||
title_format = config.val.window.title_format
|
||||
title = title_format.format(**fields)
|
||||
self.window().setWindowTitle(title)
|
||||
|
||||
def _connect_tab_signals(self, tab):
|
||||
"""Set up the needed signals for tab."""
|
||||
@ -252,7 +259,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
tab: The QWebView to be closed.
|
||||
add_undo: Whether the tab close can be undone.
|
||||
"""
|
||||
last_close = config.get('tabs', 'last-close')
|
||||
last_close = config.val.tabs.last_close
|
||||
count = self.count()
|
||||
|
||||
if last_close == 'ignore' and count == 1:
|
||||
@ -270,11 +277,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
elif last_close == 'blank':
|
||||
self.openurl(QUrl('about:blank'), newtab=True)
|
||||
elif last_close == 'startpage':
|
||||
url = QUrl(config.get('general', 'startpage')[0])
|
||||
self.openurl(url, newtab=True)
|
||||
for url in config.val.url.start_pages:
|
||||
self.openurl(url, newtab=True)
|
||||
elif last_close == 'default-page':
|
||||
url = config.get('general', 'default-page')
|
||||
self.openurl(url, newtab=True)
|
||||
self.openurl(config.val.url.default_page, newtab=True)
|
||||
|
||||
def _remove_tab(self, tab, *, add_undo=True, crashed=False):
|
||||
"""Remove a tab from the tab list and delete it properly.
|
||||
@ -329,15 +335,15 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
def undo(self):
|
||||
"""Undo removing of a tab."""
|
||||
# Remove unused tab which may be created after the last tab is closed
|
||||
last_close = config.get('tabs', 'last-close')
|
||||
last_close = config.val.tabs.last_close
|
||||
use_current_tab = False
|
||||
if last_close in ['blank', 'startpage', 'default-page']:
|
||||
only_one_tab_open = self.count() == 1
|
||||
no_history = len(self.widget(0).history) == 1
|
||||
urls = {
|
||||
'blank': QUrl('about:blank'),
|
||||
'startpage': QUrl(config.get('general', 'startpage')[0]),
|
||||
'default-page': config.get('general', 'default-page'),
|
||||
'startpage': config.val.url.start_pages[0],
|
||||
'default-page': config.val.url.default_page,
|
||||
}
|
||||
first_tab_url = self.widget(0).url()
|
||||
last_close_urlstr = urls[last_close].toString().rstrip('/')
|
||||
@ -393,7 +399,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
@pyqtSlot('QUrl', bool)
|
||||
def tabopen(self, url=None, background=None, explicit=False, idx=None, *,
|
||||
def tabopen(self, url=None, background=None, related=True, idx=None, *,
|
||||
ignore_tabs_are_windows=False):
|
||||
"""Open a new tab with a given URL.
|
||||
|
||||
@ -403,16 +409,17 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
Args:
|
||||
url: The URL to open as QUrl or None for an empty tab.
|
||||
background: Whether to open the tab in the background.
|
||||
if None, the background-tabs setting decides.
|
||||
explicit: Whether the tab was opened explicitly.
|
||||
If this is set, the new position might be different. With
|
||||
the default settings we handle it like Chromium does:
|
||||
- Tabs from clicked links etc. are to the right of
|
||||
the current.
|
||||
- Explicitly opened tabs are at the very right.
|
||||
if None, the `tabs.background_tabs`` setting decides.
|
||||
related: Whether the tab was opened from another existing tab.
|
||||
If this is set, the new position might be different. With
|
||||
the default settings we handle it like Chromium does:
|
||||
- Tabs from clicked links etc. are to the right of
|
||||
the current (related=True).
|
||||
- Explicitly opened tabs are at the very right
|
||||
(related=False)
|
||||
idx: The index where the new tab should be opened.
|
||||
ignore_tabs_are_windows: If given, never open a new window, even
|
||||
with tabs-are-windows set.
|
||||
with tabs.tabs_are_windows set.
|
||||
|
||||
Return:
|
||||
The opened WebView instance.
|
||||
@ -420,31 +427,32 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
if url is not None:
|
||||
qtutils.ensure_valid(url)
|
||||
log.webview.debug("Creating new tab with URL {}, background {}, "
|
||||
"explicit {}, idx {}".format(
|
||||
url, background, explicit, idx))
|
||||
"related {}, idx {}".format(
|
||||
url, background, related, idx))
|
||||
|
||||
if (config.get('tabs', 'tabs-are-windows') and self.count() > 0 and
|
||||
if (config.val.tabs.tabs_are_windows and self.count() > 0 and
|
||||
not ignore_tabs_are_windows):
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
window = mainwindow.MainWindow(private=self.private)
|
||||
window.show()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=window.win_id)
|
||||
return tabbed_browser.tabopen(url, background, explicit)
|
||||
return tabbed_browser.tabopen(url=url, background=background,
|
||||
related=related)
|
||||
|
||||
tab = browsertab.create(win_id=self._win_id, private=self.private,
|
||||
parent=self)
|
||||
self._connect_tab_signals(tab)
|
||||
|
||||
if idx is None:
|
||||
idx = self._get_new_tab_idx(explicit)
|
||||
idx = self._get_new_tab_idx(related)
|
||||
self.insertTab(idx, tab, "")
|
||||
|
||||
if url is not None:
|
||||
tab.openurl(url)
|
||||
|
||||
if background is None:
|
||||
background = config.get('tabs', 'background-tabs')
|
||||
background = config.val.tabs.background
|
||||
if background:
|
||||
# Make sure the background tab has the correct initial size.
|
||||
# With a foreground tab, it's going to be resized correctly by the
|
||||
@ -458,19 +466,19 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
self.new_tab.emit(tab, idx)
|
||||
return tab
|
||||
|
||||
def _get_new_tab_idx(self, explicit):
|
||||
def _get_new_tab_idx(self, related):
|
||||
"""Get the index of a tab to insert.
|
||||
|
||||
Args:
|
||||
explicit: Whether the tab was opened explicitly.
|
||||
related: Whether the tab was opened from another tab (as a "child")
|
||||
|
||||
Return:
|
||||
The index of the new tab.
|
||||
"""
|
||||
if explicit:
|
||||
pos = config.get('tabs', 'new-tab-position-explicit')
|
||||
if related:
|
||||
pos = config.val.tabs.new_position.related
|
||||
else:
|
||||
pos = config.get('tabs', 'new-tab-position')
|
||||
pos = config.val.tabs.new_position.unrelated
|
||||
if pos == 'prev':
|
||||
idx = self._tab_insert_idx_left
|
||||
# On first sight, we'd think we have to decrement
|
||||
@ -486,26 +494,23 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
elif pos == 'last':
|
||||
idx = -1
|
||||
else:
|
||||
raise ValueError("Invalid new-tab-position '{}'.".format(pos))
|
||||
log.webview.debug("new-tab-position {} -> opening new tab at {}, "
|
||||
raise ValueError("Invalid tabs.new_position '{}'.".format(pos))
|
||||
log.webview.debug("tabs.new_position {} -> opening new tab at {}, "
|
||||
"next left: {} / right: {}".format(
|
||||
pos, idx, self._tab_insert_idx_left,
|
||||
self._tab_insert_idx_right))
|
||||
return idx
|
||||
|
||||
@config.change_filter('tabs', 'show-favicons')
|
||||
def update_favicons(self):
|
||||
def _update_favicons(self):
|
||||
"""Update favicons when config was changed."""
|
||||
show = config.get('tabs', 'show-favicons')
|
||||
tabs_are_wins = config.get('tabs', 'tabs-are-windows')
|
||||
for i, tab in enumerate(self.widgets()):
|
||||
if show:
|
||||
if config.val.tabs.favicons.show:
|
||||
self.setTabIcon(i, tab.icon())
|
||||
if tabs_are_wins:
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.window().setWindowIcon(tab.icon())
|
||||
else:
|
||||
self.setTabIcon(i, QIcon())
|
||||
if tabs_are_wins:
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.window().setWindowIcon(self.default_window_icon)
|
||||
|
||||
@pyqtSlot()
|
||||
@ -520,16 +525,16 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
self.update_tab_title(idx)
|
||||
self._update_tab_title(idx)
|
||||
if tab.data.keep_icon:
|
||||
tab.data.keep_icon = False
|
||||
else:
|
||||
self.setTabIcon(idx, QIcon())
|
||||
if (config.get('tabs', 'tabs-are-windows') and
|
||||
config.get('tabs', 'show-favicons')):
|
||||
if (config.val.tabs.tabs_are_windows and
|
||||
config.val.tabs.favicons.show):
|
||||
self.window().setWindowIcon(self.default_window_icon)
|
||||
if idx == self.currentIndex():
|
||||
self.update_window_title()
|
||||
self._update_window_title()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_cur_load_started(self):
|
||||
@ -561,7 +566,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
idx, text))
|
||||
self.set_page_title(idx, text)
|
||||
if idx == self.currentIndex():
|
||||
self.update_window_title()
|
||||
self._update_window_title()
|
||||
|
||||
@pyqtSlot(browsertab.AbstractTab, QUrl)
|
||||
def on_url_changed(self, tab, url):
|
||||
@ -590,7 +595,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
tab: The WebView where the title was changed.
|
||||
icon: The new icon
|
||||
"""
|
||||
if not config.get('tabs', 'show-favicons'):
|
||||
if not config.val.tabs.favicons.show:
|
||||
return
|
||||
try:
|
||||
idx = self._tab_index(tab)
|
||||
@ -598,7 +603,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
self.setTabIcon(idx, icon)
|
||||
if config.get('tabs', 'tabs-are-windows'):
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.window().setWindowIcon(icon)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
@ -635,7 +640,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
scope='window', window=self._win_id)
|
||||
self._now_focused = tab
|
||||
self.current_tab_changed.emit(tab)
|
||||
QTimer.singleShot(0, self.update_window_title)
|
||||
QTimer.singleShot(0, self._update_window_title)
|
||||
self._tab_insert_idx_left = self.currentIndex()
|
||||
self._tab_insert_idx_right = self.currentIndex() + 1
|
||||
|
||||
@ -651,14 +656,14 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
start = config.get('colors', 'tabs.indicator.start')
|
||||
stop = config.get('colors', 'tabs.indicator.stop')
|
||||
system = config.get('colors', 'tabs.indicator.system')
|
||||
start = config.val.colors.tabs.indicator.start
|
||||
stop = config.val.colors.tabs.indicator.stop
|
||||
system = config.val.colors.tabs.indicator.system
|
||||
color = utils.interpolate_color(start, stop, perc, system)
|
||||
self.set_tab_indicator_color(idx, color)
|
||||
self.update_tab_title(idx)
|
||||
self._update_tab_title(idx)
|
||||
if idx == self.currentIndex():
|
||||
self.update_window_title()
|
||||
self._update_window_title()
|
||||
|
||||
def on_load_finished(self, tab, ok):
|
||||
"""Adjust tab indicator when loading finished."""
|
||||
@ -668,16 +673,16 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
if ok:
|
||||
start = config.get('colors', 'tabs.indicator.start')
|
||||
stop = config.get('colors', 'tabs.indicator.stop')
|
||||
system = config.get('colors', 'tabs.indicator.system')
|
||||
start = config.val.colors.tabs.indicator.start
|
||||
stop = config.val.colors.tabs.indicator.stop
|
||||
system = config.val.colors.tabs.indicator.system
|
||||
color = utils.interpolate_color(start, stop, 100, system)
|
||||
else:
|
||||
color = config.get('colors', 'tabs.indicator.error')
|
||||
color = config.val.colors.tabs.indicator.error
|
||||
self.set_tab_indicator_color(idx, color)
|
||||
self.update_tab_title(idx)
|
||||
self._update_tab_title(idx)
|
||||
if idx == self.currentIndex():
|
||||
self.update_window_title()
|
||||
self._update_window_title()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_scroll_pos_changed(self):
|
||||
@ -688,8 +693,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
log.webview.debug("Not updating scroll position because index is "
|
||||
"-1")
|
||||
return
|
||||
self.update_window_title()
|
||||
self.update_tab_title(idx)
|
||||
self._update_window_title()
|
||||
self._update_tab_title(idx)
|
||||
|
||||
def _on_renderer_process_terminated(self, tab, status, code):
|
||||
"""Show an error when a renderer process terminated."""
|
||||
@ -716,7 +721,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
url_string = tab.url(requested=True).toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading {}".format(url_string),
|
||||
url=url_string, error=msg, icon='')
|
||||
url=url_string, error=msg)
|
||||
QTimer.singleShot(100, lambda: show_error_page(error_page))
|
||||
else:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698
|
||||
|
@ -57,27 +57,27 @@ class TabWidget(QTabWidget):
|
||||
self.setTabBar(bar)
|
||||
bar.tabCloseRequested.connect(self.tabCloseRequested)
|
||||
bar.tabMoved.connect(functools.partial(
|
||||
QTimer.singleShot, 0, self.update_tab_titles))
|
||||
QTimer.singleShot, 0, self._update_tab_titles))
|
||||
bar.currentChanged.connect(self._on_current_changed)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self.setDocumentMode(True)
|
||||
self.setElideMode(Qt.ElideRight)
|
||||
self.setUsesScrollButtons(True)
|
||||
bar.setDrawBase(False)
|
||||
self.init_config()
|
||||
objreg.get('config').changed.connect(self.init_config)
|
||||
self._init_config()
|
||||
config.instance.changed.connect(self._init_config)
|
||||
|
||||
@config.change_filter('tabs')
|
||||
def init_config(self):
|
||||
def _init_config(self):
|
||||
"""Initialize attributes based on the config."""
|
||||
if self is None: # pragma: no cover
|
||||
# WORKAROUND for PyQt 5.2
|
||||
return
|
||||
tabbar = self.tabBar()
|
||||
self.setMovable(config.get('tabs', 'movable'))
|
||||
self.setMovable(True)
|
||||
self.setTabsClosable(False)
|
||||
position = config.get('tabs', 'position')
|
||||
selection_behavior = config.get('tabs', 'select-on-remove')
|
||||
position = config.val.tabs.position
|
||||
selection_behavior = config.val.tabs.select_on_remove
|
||||
self.setTabPosition(position)
|
||||
tabbar.vertical = position in [QTabWidget.West, QTabWidget.East]
|
||||
tabbar.setSelectionBehaviorOnRemove(selection_behavior)
|
||||
@ -117,7 +117,7 @@ class TabWidget(QTabWidget):
|
||||
|
||||
bar.set_tab_data(idx, 'pinned', pinned)
|
||||
tab.data.pinned = pinned
|
||||
self.update_tab_title(idx)
|
||||
self._update_tab_title(idx)
|
||||
|
||||
bar.refresh()
|
||||
|
||||
@ -128,21 +128,21 @@ class TabWidget(QTabWidget):
|
||||
def set_page_title(self, idx, title):
|
||||
"""Set the tab title user data."""
|
||||
self.tabBar().set_tab_data(idx, 'page-title', title)
|
||||
self.update_tab_title(idx)
|
||||
self._update_tab_title(idx)
|
||||
|
||||
def page_title(self, idx):
|
||||
"""Get the tab title user data."""
|
||||
return self.tabBar().page_title(idx)
|
||||
|
||||
def update_tab_title(self, idx):
|
||||
def _update_tab_title(self, idx):
|
||||
"""Update the tab text for the given tab."""
|
||||
tab = self.widget(idx)
|
||||
fields = self.get_tab_fields(idx)
|
||||
fields['title'] = fields['title'].replace('&', '&&')
|
||||
fields['index'] = idx + 1
|
||||
|
||||
fmt = config.get('tabs', 'title-format')
|
||||
fmt_pinned = config.get('tabs', 'title-format-pinned')
|
||||
fmt = config.val.tabs.title.format
|
||||
fmt_pinned = config.val.tabs.title.format_pinned
|
||||
|
||||
if tab.data.pinned:
|
||||
title = '' if fmt_pinned is None else fmt_pinned.format(**fields)
|
||||
@ -190,22 +190,20 @@ class TabWidget(QTabWidget):
|
||||
fields['scroll_pos'] = scroll_pos
|
||||
return fields
|
||||
|
||||
def update_tab_titles(self, section='tabs', option='title-format'):
|
||||
def _update_tab_titles(self):
|
||||
"""Update all texts."""
|
||||
if section == 'tabs' and option in ['title-format',
|
||||
'title-format-pinned']:
|
||||
for idx in range(self.count()):
|
||||
self.update_tab_title(idx)
|
||||
for idx in range(self.count()):
|
||||
self._update_tab_title(idx)
|
||||
|
||||
def tabInserted(self, idx):
|
||||
"""Update titles when a tab was inserted."""
|
||||
super().tabInserted(idx)
|
||||
self.update_tab_titles()
|
||||
self._update_tab_titles()
|
||||
|
||||
def tabRemoved(self, idx):
|
||||
"""Update titles when a tab was removed."""
|
||||
super().tabRemoved(idx)
|
||||
self.update_tab_titles()
|
||||
self._update_tab_titles()
|
||||
|
||||
def addTab(self, page, icon_or_text, text_or_empty=None):
|
||||
"""Override addTab to use our own text setting logic.
|
||||
@ -306,24 +304,17 @@ class TabBar(QTabBar):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self.setStyle(TabBarStyle())
|
||||
self.set_font()
|
||||
config_obj = objreg.get('config')
|
||||
config_obj.changed.connect(self.set_font)
|
||||
config_obj.changed.connect(self.set_icon_size)
|
||||
self._set_font()
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
self.vertical = False
|
||||
self._auto_hide_timer = QTimer()
|
||||
self._auto_hide_timer.setSingleShot(True)
|
||||
self._auto_hide_timer.setInterval(
|
||||
config.get('tabs', 'show-switching-delay'))
|
||||
self._auto_hide_timer.timeout.connect(self.maybe_hide)
|
||||
self._on_show_switching_delay_changed()
|
||||
self.setAutoFillBackground(True)
|
||||
self.set_colors()
|
||||
self._set_colors()
|
||||
self.pinned_count = 0
|
||||
config_obj.changed.connect(self.set_colors)
|
||||
QTimer.singleShot(0, self.maybe_hide)
|
||||
config_obj.changed.connect(self.on_tab_colors_changed)
|
||||
config_obj.changed.connect(self.on_show_switching_delay_changed)
|
||||
config_obj.changed.connect(self.tabs_show)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, count=self.count())
|
||||
@ -332,29 +323,37 @@ class TabBar(QTabBar):
|
||||
"""Get the current tab object."""
|
||||
return self.parent().currentWidget()
|
||||
|
||||
@config.change_filter('tabs', 'show')
|
||||
def tabs_show(self):
|
||||
"""Hide or show tab bar if needed when tabs->show got changed."""
|
||||
self.maybe_hide()
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
if option == 'fonts.tabs':
|
||||
self._set_font()
|
||||
elif option == 'tabs.favicons.scale':
|
||||
self._set_icon_size()
|
||||
elif option == 'colors.tabs.bar.bg':
|
||||
self._set_colors()
|
||||
elif option == 'tabs.show_switching_delay':
|
||||
self._on_show_switching_delay_changed()
|
||||
elif option == 'tabs.show':
|
||||
self.maybe_hide()
|
||||
|
||||
@config.change_filter('tabs', 'show-switching-delay')
|
||||
def on_show_switching_delay_changed(self):
|
||||
"""Set timer interval when tabs->show-switching-delay got changed."""
|
||||
self._auto_hide_timer.setInterval(
|
||||
config.get('tabs', 'show-switching-delay'))
|
||||
if option.startswith('colors.tabs.'):
|
||||
self.update()
|
||||
|
||||
def _on_show_switching_delay_changed(self):
|
||||
"""Set timer interval when tabs.show_switching_delay got changed."""
|
||||
self._auto_hide_timer.setInterval(config.val.tabs.show_switching_delay)
|
||||
|
||||
def on_current_changed(self):
|
||||
"""Show tab bar when current tab got changed."""
|
||||
self.maybe_hide() # for fullscreen tabs
|
||||
show = config.get('tabs', 'show')
|
||||
if show == 'switching':
|
||||
if config.val.tabs.show == 'switching':
|
||||
self.show()
|
||||
self._auto_hide_timer.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def maybe_hide(self):
|
||||
"""Hide the tab bar if needed."""
|
||||
show = config.get('tabs', 'show')
|
||||
show = config.val.tabs.show
|
||||
tab = self._current_tab()
|
||||
if (show in ['never', 'switching'] or
|
||||
(show == 'multiple' and self.count() == 1) or
|
||||
@ -409,35 +408,26 @@ class TabBar(QTabBar):
|
||||
# code sets layoutDirty so it actually relayouts the tabs.
|
||||
self.setIconSize(self.iconSize())
|
||||
|
||||
@config.change_filter('fonts', 'tabbar')
|
||||
def set_font(self):
|
||||
def _set_font(self):
|
||||
"""Set the tab bar font."""
|
||||
self.setFont(config.get('fonts', 'tabbar'))
|
||||
self.set_icon_size()
|
||||
self.setFont(config.val.fonts.tabs)
|
||||
self._set_icon_size()
|
||||
|
||||
@config.change_filter('tabs', 'favicon-scale')
|
||||
def set_icon_size(self):
|
||||
def _set_icon_size(self):
|
||||
"""Set the tab bar favicon size."""
|
||||
size = self.fontMetrics().height() - 2
|
||||
size *= config.get('tabs', 'favicon-scale')
|
||||
size *= config.val.tabs.favicons.scale
|
||||
self.setIconSize(QSize(size, size))
|
||||
|
||||
@config.change_filter('colors', 'tabs.bg.bar')
|
||||
def set_colors(self):
|
||||
def _set_colors(self):
|
||||
"""Set the tab bar colors."""
|
||||
p = self.palette()
|
||||
p.setColor(QPalette.Window, config.get('colors', 'tabs.bg.bar'))
|
||||
p.setColor(QPalette.Window, config.val.colors.tabs.bar.bg)
|
||||
self.setPalette(p)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def on_tab_colors_changed(self, section, option):
|
||||
"""Set the tab colors."""
|
||||
if section == 'colors' and option.startswith('tabs.'):
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
"""Override mousePressEvent to close tabs if configured."""
|
||||
button = config.get('tabs', 'close-mouse-button')
|
||||
button = config.val.tabs.close_mouse_button
|
||||
if (e.button() == Qt.RightButton and button == 'right' or
|
||||
e.button() == Qt.MiddleButton and button == 'middle'):
|
||||
e.accept()
|
||||
@ -458,7 +448,7 @@ class TabBar(QTabBar):
|
||||
A QSize.
|
||||
"""
|
||||
icon = self.tabIcon(index)
|
||||
padding = config.get('tabs', 'padding')
|
||||
padding = config.val.tabs.padding
|
||||
padding_h = padding.left + padding.right
|
||||
padding_v = padding.top + padding.bottom
|
||||
if icon.isNull():
|
||||
@ -469,10 +459,9 @@ class TabBar(QTabBar):
|
||||
icon_size = icon.actualSize(QSize(extent, extent))
|
||||
padding_h += self.style().pixelMetric(
|
||||
PixelMetrics.icon_padding, None, self)
|
||||
indicator_width = config.get('tabs', 'indicator-width')
|
||||
height = self.fontMetrics().height() + padding_v
|
||||
width = (self.fontMetrics().width('\u2026') + icon_size.width() +
|
||||
padding_h + indicator_width)
|
||||
padding_h + config.val.tabs.width.indicator)
|
||||
return QSize(width, height)
|
||||
|
||||
def tabSizeHint(self, index):
|
||||
@ -489,7 +478,7 @@ class TabBar(QTabBar):
|
||||
minimum_size = self.minimumTabSizeHint(index)
|
||||
height = minimum_size.height()
|
||||
if self.vertical:
|
||||
confwidth = str(config.get('tabs', 'width'))
|
||||
confwidth = str(config.val.tabs.width.bar)
|
||||
if confwidth.endswith('%'):
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
@ -504,19 +493,30 @@ class TabBar(QTabBar):
|
||||
# want to ensure it's valid in this special case.
|
||||
return QSize()
|
||||
else:
|
||||
tab_width_pinned_conf = config.get('tabs', 'pinned-width')
|
||||
|
||||
try:
|
||||
pinned = self.tab_data(index, 'pinned')
|
||||
except KeyError:
|
||||
pinned = False
|
||||
|
||||
no_pinned_count = self.count() - self.pinned_count
|
||||
pinned_width = tab_width_pinned_conf * self.pinned_count
|
||||
pinned_width = config.val.tabs.width.pinned * self.pinned_count
|
||||
no_pinned_width = self.width() - pinned_width
|
||||
|
||||
if pinned:
|
||||
width = tab_width_pinned_conf
|
||||
size = QSize(config.val.tabs.width.pinned, height)
|
||||
qtutils.ensure_valid(size)
|
||||
return size
|
||||
|
||||
# If we *do* have enough space, tabs should occupy the whole window
|
||||
# width. If there are pinned tabs their size will be subtracted
|
||||
# from the total window width.
|
||||
# During shutdown the self.count goes down,
|
||||
# but the self.pinned_count not - this generates some odd behavior.
|
||||
# To avoid this we compare self.count against self.pinned_count.
|
||||
if self.pinned_count > 0 and self.count() > self.pinned_count:
|
||||
pinned_width = config.val.tabs.width.pinned * self.pinned_count
|
||||
no_pinned_width = self.width() - pinned_width
|
||||
width = no_pinned_width / (self.count() - self.pinned_count)
|
||||
else:
|
||||
|
||||
# Tabs should attempt to occupy the whole window width. If
|
||||
@ -547,31 +547,21 @@ class TabBar(QTabBar):
|
||||
|
||||
def paintEvent(self, _e):
|
||||
"""Override paintEvent to draw the tabs like we want to."""
|
||||
# pylint: disable=bad-config-call
|
||||
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104
|
||||
p = QStylePainter(self)
|
||||
selected = self.currentIndex()
|
||||
for idx in range(self.count()):
|
||||
tab = QStyleOptionTab()
|
||||
self.initStyleOption(tab, idx)
|
||||
|
||||
bg_parts = ['tabs', 'bg']
|
||||
fg_parts = ['tabs', 'fg']
|
||||
# pylint: disable=bad-config-option
|
||||
setting = config.val.colors.tabs
|
||||
# pylint: enable=bad-config-option
|
||||
if idx == selected:
|
||||
bg_parts.append('selected')
|
||||
fg_parts.append('selected')
|
||||
setting = setting.selected
|
||||
setting = setting.odd if idx % 2 else setting.even
|
||||
|
||||
if idx % 2:
|
||||
bg_parts.append('odd')
|
||||
fg_parts.append('odd')
|
||||
else:
|
||||
bg_parts.append('even')
|
||||
fg_parts.append('even')
|
||||
|
||||
bg_color = config.get('colors', '.'.join(bg_parts))
|
||||
fg_color = config.get('colors', '.'.join(fg_parts))
|
||||
tab.palette.setColor(QPalette.Window, bg_color)
|
||||
tab.palette.setColor(QPalette.WindowText, fg_color)
|
||||
tab.palette.setColor(QPalette.Window, setting.bg)
|
||||
tab.palette.setColor(QPalette.WindowText, setting.fg)
|
||||
|
||||
indicator_color = self.tab_indicator_color(idx)
|
||||
tab.palette.setColor(QPalette.Base, indicator_color)
|
||||
@ -597,7 +587,7 @@ class TabBar(QTabBar):
|
||||
Args:
|
||||
e: The QWheelEvent
|
||||
"""
|
||||
if config.get('tabs', 'mousewheel-tab-switching'):
|
||||
if config.val.tabs.mousewheel_switching:
|
||||
super().wheelEvent(e)
|
||||
else:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
@ -707,7 +697,7 @@ class TabBarStyle(QCommonStyle):
|
||||
elif element == QStyle.CE_TabBarTabLabel:
|
||||
if not opt.icon.isNull() and layouts.icon.isValid():
|
||||
self._draw_icon(layouts, opt, p)
|
||||
alignment = (config.get('tabs', 'title-alignment') |
|
||||
alignment = (config.val.tabs.title.alignment |
|
||||
Qt.AlignVCenter | Qt.TextHideMnemonic)
|
||||
self._style.drawItemText(p, layouts.text, alignment, opt.palette,
|
||||
opt.state & QStyle.State_Enabled,
|
||||
@ -787,8 +777,8 @@ class TabBarStyle(QCommonStyle):
|
||||
Return:
|
||||
A Layout namedtuple with two QRects.
|
||||
"""
|
||||
padding = config.get('tabs', 'padding')
|
||||
indicator_padding = config.get('tabs', 'indicator-padding')
|
||||
padding = config.val.tabs.padding
|
||||
indicator_padding = config.val.tabs.indicator_padding
|
||||
|
||||
text_rect = QRect(opt.rect)
|
||||
if not text_rect.isValid():
|
||||
@ -799,7 +789,7 @@ class TabBarStyle(QCommonStyle):
|
||||
text_rect.adjust(padding.left, padding.top, -padding.right,
|
||||
-padding.bottom)
|
||||
|
||||
indicator_width = config.get('tabs', 'indicator-width')
|
||||
indicator_width = config.val.tabs.width.indicator
|
||||
if indicator_width == 0:
|
||||
indicator_rect = QRect()
|
||||
else:
|
||||
@ -842,10 +832,10 @@ class TabBarStyle(QCommonStyle):
|
||||
icon_state = (QIcon.On if opt.state & QStyle.State_Selected
|
||||
else QIcon.Off)
|
||||
# reserve space for favicon when tab bar is vertical (issue #1968)
|
||||
position = config.get('tabs', 'position')
|
||||
position = config.val.tabs.position
|
||||
if (opt.icon.isNull() and
|
||||
position in [QTabWidget.East, QTabWidget.West] and
|
||||
config.get('tabs', 'show-favicons')):
|
||||
config.val.tabs.favicons.show):
|
||||
tab_icon_size = icon_size
|
||||
else:
|
||||
actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state)
|
||||
|
@ -21,7 +21,8 @@
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
|
||||
|
||||
from qutebrowser.utils import usertypes, log
|
||||
from qutebrowser.utils import usertypes, log, standarddir, objreg
|
||||
from qutebrowser.misc import lineparser
|
||||
|
||||
|
||||
class HistoryEmptyError(Exception):
|
||||
@ -129,3 +130,14 @@ class History(QObject):
|
||||
if not self.history or text != self.history[-1]:
|
||||
self.history.append(text)
|
||||
self.changed.emit()
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the LimitLineParser storing the history."""
|
||||
save_manager = objreg.get('save-manager')
|
||||
command_history = lineparser.LimitLineParser(
|
||||
standarddir.data(), 'cmd-history',
|
||||
limit='completion.cmd_history_max_items')
|
||||
objreg.register('command-history', command_history)
|
||||
save_manager.add_saveable('command-history', command_history.save,
|
||||
command_history.changed)
|
||||
|
@ -51,8 +51,8 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
|
||||
_namespace: The local namespace of the interpreter.
|
||||
"""
|
||||
super().__init__(parent=parent)
|
||||
self.update_font()
|
||||
objreg.get('config').changed.connect(self.update_font)
|
||||
self._update_font()
|
||||
config.instance.changed.connect(self._update_font)
|
||||
self._history = cmdhistory.History(parent=self)
|
||||
self.returnPressed.connect(self.on_return_pressed)
|
||||
|
||||
@ -102,10 +102,10 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
|
||||
else:
|
||||
super().keyPressEvent(e)
|
||||
|
||||
@config.change_filter('fonts', 'debug-console')
|
||||
def update_font(self):
|
||||
@config.change_filter('fonts.debug_console')
|
||||
def _update_font(self):
|
||||
"""Set the correct font."""
|
||||
self.setFont(config.get('fonts', 'debug-console'))
|
||||
self.setFont(config.val.fonts.debug_console)
|
||||
|
||||
|
||||
class ConsoleTextEdit(QTextEdit):
|
||||
@ -116,17 +116,17 @@ class ConsoleTextEdit(QTextEdit):
|
||||
super().__init__(parent)
|
||||
self.setAcceptRichText(False)
|
||||
self.setReadOnly(True)
|
||||
objreg.get('config').changed.connect(self.update_font)
|
||||
self.update_font()
|
||||
config.instance.changed.connect(self._update_font)
|
||||
self._update_font()
|
||||
self.setFocusPolicy(Qt.ClickFocus)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@config.change_filter('fonts', 'debug-console')
|
||||
def update_font(self):
|
||||
@config.change_filter('fonts.debug_console')
|
||||
def _update_font(self):
|
||||
"""Update font when config changed."""
|
||||
self.setFont(config.get('fonts', 'debug-console'))
|
||||
self.setFont(config.val.fonts.debug_console)
|
||||
|
||||
def append_text(self, text):
|
||||
"""Append new text and scroll output to bottom.
|
||||
|
@ -255,8 +255,8 @@ class _CrashDialog(QDialog):
|
||||
except Exception:
|
||||
self._crash_info.append(("Version info", traceback.format_exc()))
|
||||
try:
|
||||
conf = objreg.get('config')
|
||||
self._crash_info.append(("Config", conf.dump_userconfig()))
|
||||
self._crash_info.append(("Config",
|
||||
config.instance.dump_userconfig()))
|
||||
except Exception:
|
||||
self._crash_info.append(("Config", traceback.format_exc()))
|
||||
try:
|
||||
@ -432,7 +432,7 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
self._chk_log = QCheckBox("Include a debug log in the report",
|
||||
checked=True)
|
||||
try:
|
||||
if config.get('general', 'private-browsing'):
|
||||
if config.val.content.private_browsing:
|
||||
self._chk_log.setChecked(False)
|
||||
except Exception:
|
||||
log.misc.exception("Error while checking private browsing mode")
|
||||
@ -524,7 +524,7 @@ class FatalCrashDialog(_CrashDialog):
|
||||
"accessed pages in the report.",
|
||||
checked=True)
|
||||
try:
|
||||
if config.get('general', 'private-browsing'):
|
||||
if config.val.content.private_browsing:
|
||||
self._chk_history.setChecked(False)
|
||||
except Exception:
|
||||
log.misc.exception("Error while checking private browsing mode")
|
||||
@ -635,8 +635,7 @@ def dump_exception_info(exc, pages, cmdhist, qobjects):
|
||||
traceback.print_exc()
|
||||
print("\n---- Config ----", file=sys.stderr)
|
||||
try:
|
||||
conf = objreg.get('config')
|
||||
print(conf.dump_userconfig(), file=sys.stderr)
|
||||
print(config.instance.dump_userconfig(), file=sys.stderr)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
print("\n---- Commandline args ----", file=sys.stderr)
|
||||
|
@ -75,7 +75,7 @@ class ExternalEditor(QObject):
|
||||
try:
|
||||
if exitcode != 0:
|
||||
return
|
||||
encoding = config.get('general', 'editor-encoding')
|
||||
encoding = config.val.editor.encoding
|
||||
try:
|
||||
with open(self._file.name, 'r', encoding=encoding) as f:
|
||||
text = f.read()
|
||||
@ -102,14 +102,14 @@ class ExternalEditor(QObject):
|
||||
if self._text is not None:
|
||||
raise ValueError("Already editing a file!")
|
||||
self._text = text
|
||||
encoding = config.get('general', 'editor-encoding')
|
||||
try:
|
||||
# Close while the external process is running, as otherwise systems
|
||||
# with exclusive write access (e.g. Windows) may fail to update
|
||||
# the file from the external editor, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/1767
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='w', prefix='qutebrowser-editor-', encoding=encoding,
|
||||
mode='w', prefix='qutebrowser-editor-',
|
||||
encoding=config.val.editor.encoding,
|
||||
delete=False) as fobj:
|
||||
if text:
|
||||
fobj.write(text)
|
||||
@ -120,7 +120,7 @@ class ExternalEditor(QObject):
|
||||
self._proc = guiprocess.GUIProcess(what='editor', parent=self)
|
||||
self._proc.finished.connect(self.on_proc_closed)
|
||||
self._proc.error.connect(self.on_proc_error)
|
||||
editor = config.get('general', 'editor')
|
||||
editor = config.val.editor.command
|
||||
executable = editor[0]
|
||||
args = [arg.replace('{}', self._file.name) for arg in editor[1:]]
|
||||
log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
|
||||
|
@ -30,8 +30,8 @@ import fnmatch
|
||||
from PyQt5.QtWidgets import QLabel, QSizePolicy
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt
|
||||
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.utils import objreg, utils, usertypes
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, usertypes
|
||||
|
||||
|
||||
class KeyHintView(QLabel):
|
||||
@ -47,11 +47,11 @@ class KeyHintView(QLabel):
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel {
|
||||
font: {{ font['keyhint'] }};
|
||||
color: {{ color['keyhint.fg'] }};
|
||||
background-color: {{ color['keyhint.bg'] }};
|
||||
font: {{ conf.fonts.keyhint }};
|
||||
color: {{ conf.colors.keyhint.fg }};
|
||||
background-color: {{ conf.colors.keyhint.bg }};
|
||||
padding: 6px;
|
||||
{% if config.get('ui', 'status-position') == 'top' %}
|
||||
{% if conf.statusbar.position == 'top' %}
|
||||
border-bottom-right-radius: 6px;
|
||||
{% else %}
|
||||
border-top-right-radius: 6px;
|
||||
@ -68,7 +68,7 @@ class KeyHintView(QLabel):
|
||||
self.hide()
|
||||
self._show_timer = usertypes.Timer(self, 'keyhint_show')
|
||||
self._show_timer.timeout.connect(self.show)
|
||||
style.set_register_stylesheet(self)
|
||||
config.set_register_stylesheet(self)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, win_id=self._win_id)
|
||||
@ -90,16 +90,14 @@ class KeyHintView(QLabel):
|
||||
self.hide()
|
||||
return
|
||||
|
||||
blacklist = config.get('ui', 'keyhint-blacklist') or []
|
||||
keyconf = objreg.get('key-config')
|
||||
|
||||
def blacklisted(keychain):
|
||||
return any(fnmatch.fnmatchcase(keychain, glob)
|
||||
for glob in blacklist)
|
||||
for glob in config.val.keyhint.blacklist)
|
||||
|
||||
bindings = [(k, v) for (k, v)
|
||||
in keyconf.get_bindings_for(modename).items()
|
||||
if k.startswith(prefix) and not utils.is_special_key(k) and
|
||||
bindings_dict = config.key_instance.get_bindings_for(modename)
|
||||
bindings = [(k, v) for (k, v) in sorted(bindings_dict.items())
|
||||
if k.startswith(prefix) and
|
||||
not utils.is_special_key(k) and
|
||||
not blacklisted(k)]
|
||||
|
||||
if not bindings:
|
||||
@ -107,9 +105,9 @@ class KeyHintView(QLabel):
|
||||
return
|
||||
|
||||
# delay so a quickly typed keychain doesn't display hints
|
||||
self._show_timer.setInterval(config.get('ui', 'keyhint-delay'))
|
||||
self._show_timer.setInterval(config.val.keyhint.delay)
|
||||
self._show_timer.start()
|
||||
suffix_color = html.escape(config.get('colors', 'keyhint.fg.suffix'))
|
||||
suffix_color = html.escape(config.val.colors.keyhint.suffix.fg)
|
||||
|
||||
text = ''
|
||||
for key, cmd in bindings:
|
||||
|
@ -25,7 +25,7 @@ import contextlib
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
|
||||
|
||||
from qutebrowser.utils import log, utils, objreg, qtutils
|
||||
from qutebrowser.utils import log, utils, qtutils
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
@ -195,8 +195,7 @@ class LimitLineParser(LineParser):
|
||||
"""A LineParser with a limited count of lines.
|
||||
|
||||
Attributes:
|
||||
_limit: The config section/option used to limit the maximum number of
|
||||
lines.
|
||||
_limit: The config option used to limit the maximum number of lines.
|
||||
"""
|
||||
|
||||
def __init__(self, configdir, fname, *, limit, binary=False, parent=None):
|
||||
@ -205,33 +204,33 @@ class LimitLineParser(LineParser):
|
||||
Args:
|
||||
configdir: Directory to read the config from, or None.
|
||||
fname: Filename of the config file.
|
||||
limit: Config tuple (section, option) which contains a limit.
|
||||
limit: Config option which contains a limit.
|
||||
binary: Whether to open the file in binary mode.
|
||||
"""
|
||||
super().__init__(configdir, fname, binary=binary, parent=parent)
|
||||
self._limit = limit
|
||||
if limit is not None and configdir is not None:
|
||||
objreg.get('config').changed.connect(self.cleanup_file)
|
||||
config.instance.changed.connect(self._cleanup_file)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True,
|
||||
configdir=self._configdir, fname=self._fname,
|
||||
limit=self._limit, binary=self._binary)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def cleanup_file(self, section, option):
|
||||
@pyqtSlot(str)
|
||||
def _cleanup_file(self, option):
|
||||
"""Delete the file if the limit was changed to 0."""
|
||||
assert self._configfile is not None
|
||||
if (section, option) != self._limit:
|
||||
if option != self._limit:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
value = config.instance.get(option)
|
||||
if value == 0:
|
||||
if os.path.exists(self._configfile):
|
||||
os.remove(self._configfile)
|
||||
|
||||
def save(self):
|
||||
"""Save the config file."""
|
||||
limit = config.get(*self._limit)
|
||||
limit = config.instance.get(self._limit)
|
||||
if limit == 0:
|
||||
return
|
||||
do_save = self._prepare_save()
|
||||
|
@ -24,7 +24,8 @@ from PyQt5.QtWidgets import (QLineEdit, QWidget, QHBoxLayout, QLabel,
|
||||
QStyleOption, QStyle, QLayout, QApplication)
|
||||
from PyQt5.QtGui import QValidator, QPainter
|
||||
|
||||
from qutebrowser.utils import utils, objreg, qtutils, log, usertypes
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, qtutils, log, usertypes
|
||||
from qutebrowser.misc import cmdhistory, objects
|
||||
|
||||
|
||||
@ -288,8 +289,7 @@ class FullscreenNotification(QLabel):
|
||||
padding: 30px;
|
||||
""")
|
||||
|
||||
key_config = objreg.get('key-config')
|
||||
all_bindings = key_config.get_reverse_bindings_for('normal')
|
||||
all_bindings = config.key_instance.get_reverse_bindings_for('normal')
|
||||
bindings = all_bindings.get('fullscreen --leave')
|
||||
if bindings:
|
||||
key = bindings[0]
|
||||
|
@ -41,6 +41,7 @@ def msgbox(parent, title, text, *, icon, buttons=QMessageBox.Ok,
|
||||
A new QMessageBox.
|
||||
"""
|
||||
box = QMessageBox(parent)
|
||||
box.setAttribute(Qt.WA_DeleteOnClose)
|
||||
box.setIcon(icon)
|
||||
box.setStandardButtons(buttons)
|
||||
if on_finished is not None:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user