From 8d9280d1d88fe15f3bf1c0f3a13e318be4630c91 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 08:37:05 +0100 Subject: [PATCH 01/26] Set more parents --- qutebrowser/widgets/browser.py | 2 +- qutebrowser/widgets/completion.py | 2 +- qutebrowser/widgets/crash.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index fc9e07256..9c90b62d8 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -205,7 +205,7 @@ class TabbedBrowser(TabWidget): # FIXME that does not what I expect tab = self._widget(count) if tab is not None: - preview = QPrintPreviewDialog() + preview = QPrintPreviewDialog(self) preview.paintRequested.connect(tab.print) preview.exec_() diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index 951d378ad..35799a9a1 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -364,7 +364,7 @@ class CompletionItemDelegate(QStyledItemDelegate): text_option.setAlignment(QStyle.visualAlignment( self.opt.direction, self.opt.displayAlignment)) - self.doc = QTextDocument() + self.doc = QTextDocument(self) if index.parent().isValid(): self.doc.setPlainText(self.opt.text) else: diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index 450505d0c..eab895c0f 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -44,8 +44,8 @@ class CrashDialog(QDialog): self.setWindowTitle('Whoops!') self.setModal(True) - self.vbox = QVBoxLayout() - self.lbl = QLabel(self) + self.vbox = QVBoxLayout(self) + self.lbl = QLabel() text = ('Argh! qutebrowser crashed unexpectedly.
' 'Please review the info below to remove sensitive data and ' 'then submit it to ' @@ -57,19 +57,19 @@ class CrashDialog(QDialog): self.lbl.setWordWrap(True) self.vbox.addWidget(self.lbl) - self.txt = QTextEdit(self) + self.txt = QTextEdit() self.txt.setReadOnly(True) self.txt.setText(self._crash_info(pages, cmdhist, exc)) self.vbox.addWidget(self.txt) self.setLayout(self.vbox) - self.hbox = QHBoxLayout() - self.btn_quit = QPushButton(self) + self.hbox = QHBoxLayout(self) + self.btn_quit = QPushButton() self.btn_quit.setText('Quit') self.btn_quit.clicked.connect(self.reject) self.hbox.addWidget(self.btn_quit) if pages: - self.btn_restore = QPushButton(self) + self.btn_restore = QPushButton() self.btn_restore.setText('Restore tabs') self.btn_restore.clicked.connect(self.accept) self.btn_restore.setDefault(True) From 8c1619236b1e1e1115482c53bf046c7897e7fc8b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 08:52:25 +0100 Subject: [PATCH 02/26] CrashDialog: fix layout setting issue --- qutebrowser/widgets/crash.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index eab895c0f..1f9ee581a 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -61,9 +61,8 @@ class CrashDialog(QDialog): self.txt.setReadOnly(True) self.txt.setText(self._crash_info(pages, cmdhist, exc)) self.vbox.addWidget(self.txt) - self.setLayout(self.vbox) - self.hbox = QHBoxLayout(self) + self.hbox = QHBoxLayout() self.btn_quit = QPushButton() self.btn_quit.setText('Quit') self.btn_quit.clicked.connect(self.reject) From d349a987c728eea6a91825cca3219898e956d783 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 08:52:37 +0100 Subject: [PATCH 03/26] CrashDialog: add Stretch before buttons. --- qutebrowser/widgets/crash.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index 1f9ee581a..43a45c661 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -63,6 +63,7 @@ class CrashDialog(QDialog): self.vbox.addWidget(self.txt) self.hbox = QHBoxLayout() + self.hbox.addStretch() self.btn_quit = QPushButton() self.btn_quit.setText('Quit') self.btn_quit.clicked.connect(self.reject) From 99aba6d9c0d2445dfe1521e59efb82ae94d23317 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 10:07:52 +0100 Subject: [PATCH 04/26] Move default config to file --- qutebrowser/qutebrowser.conf | 187 +++++++++++++++++++++++++++++++++++ qutebrowser/utils/config.py | 106 +------------------- 2 files changed, 190 insertions(+), 103 deletions(-) create mode 100644 qutebrowser/qutebrowser.conf diff --git a/qutebrowser/qutebrowser.conf b/qutebrowser/qutebrowser.conf new file mode 100644 index 000000000..77782b908 --- /dev/null +++ b/qutebrowser/qutebrowser.conf @@ -0,0 +1,187 @@ +# vim: ft=dosini + +# Configfile for qutebrowser. +# +# This configfile is parsed by python's configparser in extended interpolation +# mode. The format is very INI-like, so there are categories like [general] +# with "key = value"-pairs. +# +# Comments start with ; or # and may only start at the beginning of a line. +# +# Interpolation looks like ${value} or ${section:value} and will be replaced +# by the respective value. +# +# This is the default config, so if you want to remove anything from here (as +# opposed to change/add), for example a keybinding, set it to an empty value. + +[general] +# show_completion: bool, whether to show the autocompletion window or not. +# ignorecase: bool, whether to do case-insensitive searching. +# wrapsearch: bool, whether to wrap search to the top when arriving at the end. +# startpage: The default pages to open at the start, multiple pages can be +# separated with commas. +# addressbar_dns_lookup: bool, whether to do a lookup in the DNS to figure out +# if something is a search term or not. Might be slow. +# auto_search: bool, whether to start a search automatically when something +# which is not an url is entered. +show_completion = true +ignorecase = true +wrapsearch = true +startpage = http://www.duckduckgo.com/ +addressbar_dns_lookup = false +auto_search = true + +[tabbar] +# movable: bool, whether tabs should be movable +# closebuttons: bool, whether tabs should have a close button +# scrollbuttons: bool, whether there should be scroll buttons if there are too +# many tabs open. +# position: Position of the tab bar, either north, south, east or west. +# select_on_remove: Which tab to select when the focused tab is removed. Either +# 'previous', 'left' or 'right'. +# last_close; Behavour when the last tab is closed - 'ignore' (don't do +# anything), 'blank' (load about:blank) or 'quit' (quit +# qutebrowser). +movable = true +closebuttons = false +scrollbuttons = false +position = north +select_on_remove = previous +last_close = quit + +[searchengines] +# Definitions of search engines which can be used via the address bar. +# The searchengine named DEFAULT is used when general.auto_search is true and +# something else than an URL was entered to be opened. +# Other search engines can be used via the bang-syntax, e.g. +# "qutebrowser !google". The string "{}" will be replaced by the search term, +# use "{{" and "}}" for literal {/} signs. +DEFAULT = ${duckduckgo} +duckduckgo = https://duckduckgo.com/?q={} +ddg = ${duckduckgo} +google = https://encrypted.google.com/search?q={} +g = ${google} +wikipedia = http://en.wikipedia.org/w/index.php?title=Special:Search&search={} +wiki = ${wikipedia} + +[keybind] +# Bindings from a key(chain) to a command. For special keys (can't be part of a +# keychain), enclose them in @-signs. For modifiers, you can use either - or + +# as delimiters, and these names: +# Control: Control, Ctrl +# Meta: Meta, Windows, Mod4 +# Alt: Alt, Mod1 +# Shift: Shift +# For simple keys (no @ signs), a capital letter means the key is pressed with +# Shift. For modifier keys (with @ signs), you need to explicitely add "Shift-" +# to match a key pressed with shift. +o = open +go = opencur +O = tabopen +gO = tabopencur +ga = tabopen about:blank +d = tabclose +J = tabnext +K = tabprev +r = reload +H = back +L = forward +h = scroll -50 0 +j = scroll 0 50 +k = scroll 0 -50 +l = scroll 50 0 +u = undo +gg = scroll_perc_y 0 +G = scroll_perc_y +n = nextsearch +yy = yank +yY = yank sel +yt = yanktitle +yT = yanktitle sel +pp = paste +pP = paste sel +Pp = tabpaste +PP = tabpaste sel +@Ctrl-Q@ = quit +@Ctrl-Shift-T@ = undo +@Ctrl-W@ = tabclose +@Ctrl-T@ = tabopen about:blank +@Ctrl-F@ = scroll_page 0 1 +@Ctrl-B@ = scroll_page 0 -1 +@Ctrl-D@ = scroll_page 0 0.5 +@Ctrl-U@ = scroll_page 0 -0.5 + +[colors] +# Colors used in the UI. A value can be in one of the following format: +# - #RGB/#RRGGBB/#RRRGGGBBB/#RRRRGGGGBBBB +# - A SVG color name as specified in [1]. +# - transparent (no color) +# - rgb(r, g, b) / rgba(r, g, b, a) (values 0-255 or percentages) +# - hsv(h, s, v) / hsva(h, s, v, a) (values 0-255, hue 0-359) +# - A gradient as explained at [2] under "Gradient" +# [1] http://www.w3.org/TR/SVG/types.html#ColorKeywords +# [2] http://qt-project.org/doc/qt-4.8/stylesheet-reference.html#list-of-property-types +# +## Completion widget +# Text color +completion.fg = #333333 +# Background of a normal item +completion.item.bg = white +# Background of a category header +completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #e4e4e4, stop:1 #dbdbdb) +# Borders of a category header +completion.category.border.top = #808080 +completion.category.border.bottom = #bbbbbb +# Text/background color for the currently selected item +completion.item.selected.fg = #333333 +completion.item.selected.bg = #ffec8b +# Borders for the selected item +completion.item.selected.border.top = #f2f2c0 +completion.item.selected.border.bottom = #e6e680 +# Matched text in a completion +completion.match.fg = red +## Statusbar widget +# Normal colors for the statusbar. +statusbar.bg = black +statusbar.fg = white +# Statusbar colors when there is an error +statusbar.bg.error = red +# Colors of the progress bar +statusbar.progress.bg = white +# Colors of the URL shown in the statusbar +# Unknown/default status +statusbar.url.fg = ${statusbar.fg} +# Page loaded successfully +statusbar.url.fg.success = lime +# Error while loading page +statusbar.url.fg.error = orange +# Warning while loading page +statusbar.url.fg.warn = yellow +# Link under the mouse cursor +statusbar.url.fg.hover = aqua +## Tabbar +# Tabbar colors +tab.fg = white +tab.bg = grey +# Color of the selected tab +tab.bg.selected = black +# Seperator between tabs +tab.seperator = white + +[fonts] +# Fonts used for the UI, with optional style/weight/size. +# Style: normal/italic/oblique +# Weight: normal, bold, 100..900 +# Size: Number + px/pt +# +# Default monospace fonts +_monospace = Monospace, "DejaVu Sans Mono", Consolas, Monaco, + "Bitstream Vera Sans Mono", "Andale Mono", "Liberation Mono", + "Courier New", Courier, monospace, Fixed, Terminal +# Font used in the completion widget. +completion = 8pt ${_monospace} +# Font used in the tabbar +tabbar = 8pt ${_monospace} +# Font used in the statusbar. +statusbar = 8pt ${_monospace} diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index 2c2e1346d..4caa4bbb2 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -19,7 +19,6 @@ config -- The main Config object. colordict -- All configured colors. -default_config -- The default config as dict. MONOSPACE -- A list of suitable monospace fonts. """ @@ -30,111 +29,12 @@ import os.path import logging from configparser import ConfigParser, ExtendedInterpolation +from qutebrowser.utils.misc import read_file + config = None colordict = {} fontdict = {} -default_config = """ -[general] -show_completion = true -ignorecase = true -wrapsearch = true -startpage = http://www.duckduckgo.com/ -addressbar_dns_lookup = false -auto_search = true - -[tabbar] -movable = true -closebuttons = false -scrollbuttons = false -# north, south, east, west -position = north -# previous, left, right -select_on_remove = previous -# ignore, blank, quit -last_close = quit - -[searchengines] -DEFAULT = ${duckduckgo} -duckduckgo = https://duckduckgo.com/?q={} -ddg = ${duckduckgo} -google = https://encrypted.google.com/search?q={} -g = ${google} -wikipedia = http://en.wikipedia.org/w/index.php?title=Special:Search&search={} -wiki = ${wikipedia} - -[keybind] -o = open -go = opencur -O = tabopen -gO = tabopencur -ga = tabopen about:blank -d = tabclose -J = tabnext -K = tabprev -r = reload -H = back -L = forward -h = scroll -50 0 -j = scroll 0 50 -k = scroll 0 -50 -l = scroll 50 0 -u = undo -gg = scroll_perc_y 0 -G = scroll_perc_y -n = nextsearch -yy = yank -yY = yank sel -yt = yanktitle -yT = yanktitle sel -pp = paste -pP = paste sel -Pp = tabpaste -PP = tabpaste sel -@Ctrl-Q@ = quit -@Ctrl-Shift-T@ = undo -@Ctrl-W@ = tabclose -@Ctrl-T@ = tabopen about:blank -@Ctrl-F@ = scroll_page 0 1 -@Ctrl-B@ = scroll_page 0 -1 -@Ctrl-D@ = scroll_page 0 0.5 -@Ctrl-U@ = scroll_page 0 -0.5 - -[colors] -completion.fg = #333333 -completion.item.bg = white -completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 #e4e4e4, stop:1 #dbdbdb) -completion.category.border.top = #808080 -completion.category.border.bottom = #bbbbbb -completion.item.selected.fg = #333333 -completion.item.selected.bg = #ffec8b -completion.item.selected.border.top = #f2f2c0 -completion.item.selected.border.bottom = #e6e680 -completion.match.fg = red -statusbar.progress.bg = white -statusbar.bg = black -statusbar.fg = white -statusbar.bg.error = red -statusbar.url.fg = ${statusbar.fg} -statusbar.url.fg.success = lime -statusbar.url.fg.error = orange -statusbar.url.fg.warn = yellow -statusbar.url.fg.hover = aqua -tab.bg = grey -tab.bg.selected = black -tab.fg = white -tab.seperator = white - -[fonts] -_monospace = Monospace, "DejaVu Sans Mono", Consolas, Monaco, - "Bitstream Vera Sans Mono", "Andale Mono", "Liberation Mono", - "Courier New", Courier, monospace, Fixed, Terminal -completion = 8pt ${_monospace} -tabbar = 8pt ${_monospace} -statusbar = 8pt ${_monospace} -""" - def init(confdir): """Initialize the global objects based on the config in configdir.""" @@ -245,7 +145,7 @@ class Config(ConfigParser): super().__init__(interpolation=ExtendedInterpolation()) self.default_cp = ConfigParser(interpolation=ExtendedInterpolation()) self.default_cp.optionxform = lambda opt: opt # be case-insensitive - self.default_cp.read_string(default_config) + self.default_cp.read_string(read_file('qutebrowser.conf')) if not self.configdir: return self.optionxform = lambda opt: opt # be case-insensitive From 833da4a8e5aa03784aa0edb4220f57d5fab2dad3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 10:16:42 +0100 Subject: [PATCH 05/26] Update TODO --- TODO | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index cdbab95a7..aeeeae355 100644 --- a/TODO +++ b/TODO @@ -37,7 +37,6 @@ Internationalization Marks show infos in statusline, temporary/longer set settings/colors/bindings via commandline -write default config with comments more completions (URLs, ...) @@ -47,6 +46,9 @@ Minor features Hiding scrollbars :bind Ctrl+A/X to increase/decrease last number in URL +unify dns/autosearch +position remembering +multiple commands with ; Other stuff =========== From 88ea6e62b1054099b2ae4cb903cd0386c33df86f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 10:28:34 +0100 Subject: [PATCH 06/26] Unify auto_search/addressbar_dns_lookup --- TODO | 1 - qutebrowser/qutebrowser.conf | 12 ++++++------ qutebrowser/utils/url.py | 21 ++++++++++++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/TODO b/TODO index aeeeae355..8a46c419e 100644 --- a/TODO +++ b/TODO @@ -46,7 +46,6 @@ Minor features Hiding scrollbars :bind Ctrl+A/X to increase/decrease last number in URL -unify dns/autosearch position remembering multiple commands with ; diff --git a/qutebrowser/qutebrowser.conf b/qutebrowser/qutebrowser.conf index 77782b908..97b5d190c 100644 --- a/qutebrowser/qutebrowser.conf +++ b/qutebrowser/qutebrowser.conf @@ -20,16 +20,16 @@ # wrapsearch: bool, whether to wrap search to the top when arriving at the end. # startpage: The default pages to open at the start, multiple pages can be # separated with commas. -# addressbar_dns_lookup: bool, whether to do a lookup in the DNS to figure out -# if something is a search term or not. Might be slow. -# auto_search: bool, whether to start a search automatically when something -# which is not an url is entered. +# auto_search: Whether to start a search automatically when something +# which is not an url is entered. +# true/naive: Use simple/naive check +# dns: Use DNS matching (might be slow) +# false: Never search automatically show_completion = true ignorecase = true wrapsearch = true startpage = http://www.duckduckgo.com/ -addressbar_dns_lookup = false -auto_search = true +auto_search = fuzzy [tabbar] # movable: bool, whether tabs should be movable diff --git a/qutebrowser/utils/url.py b/qutebrowser/utils/url.py index b48a97ece..618601d32 100644 --- a/qutebrowser/utils/url.py +++ b/qutebrowser/utils/url.py @@ -49,7 +49,7 @@ def fuzzy_url(url): """ u = qurl(url) urlstr = urlstring(url) - if (not config.config.getboolean('general', 'auto_search')) or is_url(u): + if is_url(u): # probably an address logging.debug("url is a fuzzy address") newurl = QUrl.fromUserInput(urlstr) @@ -94,14 +94,29 @@ def is_url(url): """Return True if url (QUrl) seems to be a valid URL.""" urlstr = urlstring(url) logging.debug('Checking if "{}" is an URL'.format(urlstr)) + + try: + autosearch = config.config.getboolean('general', 'auto_search') + except ValueError: + autosearch = config.config.get('general', 'auto_search') + else: + if autosearch: + autosearch = 'naive' + else: + autosearch = None + + if autosearch is None: + # no autosearch, so everything is an URL. + return True + if ' ' in urlstr: # An URL will never contain a space logging.debug('Contains space -> no url') return False - elif config.config.getboolean('general', 'addressbar_dns_lookup'): + elif autosearch == 'dns': logging.debug('Checking via DNS') return _is_url_dns(QUrl.fromUserInput(urlstr)) - else: + elif autosearch == 'naive': logging.debug('Checking via naive check') return _is_url_naive(url) From 8fee32e6c4e88a59c79199d7bff0383009012603 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 10:41:00 +0100 Subject: [PATCH 07/26] Support multiple commands --- TODO | 1 - qutebrowser/commands/utils.py | 4 ++++ qutebrowser/qutebrowser.conf | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 8a46c419e..8c032699e 100644 --- a/TODO +++ b/TODO @@ -47,7 +47,6 @@ Hiding scrollbars :bind Ctrl+A/X to increase/decrease last number in URL position remembering -multiple commands with ; Other stuff =========== diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index 0aeb8a000..7de54da6a 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -155,6 +155,10 @@ class CommandParser(QObject): arguments. """ + if ';;' in text: + for sub in text.split(';;'): + self.run(sub, count, ignore_exc) + return try: self._parse(text) self._check() diff --git a/qutebrowser/qutebrowser.conf b/qutebrowser/qutebrowser.conf index 97b5d190c..0c3ec474e 100644 --- a/qutebrowser/qutebrowser.conf +++ b/qutebrowser/qutebrowser.conf @@ -75,6 +75,7 @@ wiki = ${wikipedia} # For simple keys (no @ signs), a capital letter means the key is pressed with # Shift. For modifier keys (with @ signs), you need to explicitely add "Shift-" # to match a key pressed with shift. +# You can bind multiple commands by separating them with ";;". o = open go = opencur O = tabopen From df0e534a0b2853e3e0d09decbf49d01e2d7e5443 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 10:45:34 +0100 Subject: [PATCH 08/26] bugfix: Always handle about-URLs as URL. --- qutebrowser/utils/url.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/url.py b/qutebrowser/utils/url.py index 618601d32..156c57170 100644 --- a/qutebrowser/utils/url.py +++ b/qutebrowser/utils/url.py @@ -113,6 +113,10 @@ def is_url(url): # An URL will never contain a space logging.debug('Contains space -> no url') return False + elif is_about_url(url): + # About URLs are always URLs, even with autosearch=False + logging.debug('Is an about URL.') + return True elif autosearch == 'dns': logging.debug('Checking via DNS') return _is_url_dns(QUrl.fromUserInput(urlstr)) @@ -126,7 +130,7 @@ def _is_url_naive(url): PROTOCOLS = ['http://', 'https://'] u = urlstring(url) return (any(u.startswith(proto) for proto in PROTOCOLS) or '.' in u or - is_about_url(url) or u == 'localhost') + u == 'localhost') def _is_url_dns(url): From 8ba46618385509bd1cd51f3f3ab50b63abffd7f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 10:50:41 +0100 Subject: [PATCH 09/26] bugfix: Fix command history --- qutebrowser/app.py | 4 ++-- qutebrowser/widgets/statusbar.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index bd0663eed..dbd54c831 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -102,9 +102,9 @@ class QuteBrowser(QApplication): self.lastWindowClosed.connect(self.shutdown) self.mainwindow.tabs.keypress.connect(self.keyparser.handle) self.keyparser.set_cmd_text.connect( - self.mainwindow.status.cmd.on_set_cmd_text) + self.mainwindow.status.cmd.set_cmd_text) self.mainwindow.tabs.set_cmd_text.connect( - self.mainwindow.status.cmd.on_set_cmd_text) + self.mainwindow.status.cmd.set_cmd_text) self.mainwindow.tabs.quit.connect(self.shutdown) self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.run) self.mainwindow.status.cmd.got_search.connect(self.searchparser.search) diff --git a/qutebrowser/widgets/statusbar.py b/qutebrowser/widgets/statusbar.py index 3eb02ef9d..16862eb71 100644 --- a/qutebrowser/widgets/statusbar.py +++ b/qutebrowser/widgets/statusbar.py @@ -217,7 +217,7 @@ class Command(QLineEdit): signals[text[0]].emit(text.lstrip(text[0])) @pyqtSlot(str) - def on_set_cmd_text(self, text): + def set_cmd_text(self, text): """Preset the statusbar to some text.""" self.setText(text) self.setFocus() @@ -277,7 +277,7 @@ class Command(QLineEdit): return logging.debug("history up: {} / len {} / pos {}".format( self._tmphist, len(self._tmphist), self._histpos)) - self.set_cmd(self._tmphist[self._histpos]) + self.set_cmd_text(self._tmphist[self._histpos]) @pyqtSlot() def on_key_down_pressed(self): @@ -291,7 +291,7 @@ class Command(QLineEdit): self._histpos += 1 logging.debug("history up: {} / len {} / pos {}".format( self._tmphist, len(self._tmphist), self._histpos)) - self.set_cmd(self._tmphist[self._histpos]) + self.set_cmd_text(self._tmphist[self._histpos]) class CommandValidator(QValidator): From 306371aa313932277a5306866c78e746101f4862 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 11:56:40 +0100 Subject: [PATCH 10/26] Better logging when shutdown fails. --- qutebrowser/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index dbd54c831..5bc0039f9 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -326,7 +326,7 @@ class QuteBrowser(QApplication): functools.partial(self._maybe_quit, 'shutdown')) self.mainwindow.tabs.shutdown() except AttributeError: # mainwindow or tabs could still be None - logging.debug("No mainwindow/tabs to shut down.") + logging.exception("No mainwindow/tabs to shut down.") if do_quit: self.quit() From 96d01f875596261ebb8a6ff5c8d91d9d5b8cc24a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 11:57:19 +0100 Subject: [PATCH 11/26] Improve fallback handling for Config. --- qutebrowser/utils/config.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index 4caa4bbb2..120ceae65 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -173,15 +173,30 @@ class Config(ConfigParser): Extend ConfigParser's get(). + This is a bit of a hack, but it (hopefully) works like this: + - Get value from original configparser. + - If that's not available, try the default_cp configparser + - If that's not available, try the fallback given as kwarg + - If that's not available, we're doomed. + """ if 'fallback' in kwargs: + orig_fallback = kwargs['fallback'] del kwargs['fallback'] - fallback = self.default_cp.get(*args, **kwargs) + else: + orig_fallback = configparser._UNSET + try: + fallback = self.default_cp.get(*args, **kwargs) + except configparser.NoSectionError: # FIXME other errors? + fallback = orig_fallback return super().get(*args, fallback=fallback, **kwargs) def save(self): """Save the config file.""" - if self.configdir is None or not self.config_loaded: + if self.configdir is None or (not self.config_loaded and + not self.always_save): + logging.error("Not saving config (dir {}, loaded {})".format( + self.configdir, self.config_loaded)) return if not os.path.exists(self.configdir): os.makedirs(self.configdir, 0o755) From 27d354633ca3973d0d5451394b06d3e59c7d3e06 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 11:57:35 +0100 Subject: [PATCH 12/26] Save/restore window geometry. --- qutebrowser/app.py | 22 +++++++++++++++++++++- qutebrowser/utils/config.py | 24 +++++++++++++++++------- qutebrowser/widgets/mainwindow.py | 19 ++++++++++++++++--- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 5bc0039f9..c9b002260 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -22,6 +22,7 @@ import sys import logging import functools import subprocess +import configparser from signal import signal, SIGINT from argparse import ArgumentParser @@ -316,8 +317,15 @@ class QuteBrowser(QApplication): return self.shutting_down = True logging.debug("Shutting down... (do_quit={})".format(do_quit)) - if config.config is not None: + try: config.config.save() + except AttributeError: + logging.exception("Could not save config.") + try: + self._save_geometry() + config.state.save() + except AttributeError: + logging.exception("Could not save window geometry.") try: if do_quit: self.mainwindow.tabs.shutdown_complete.connect(self.quit) @@ -330,6 +338,18 @@ class QuteBrowser(QApplication): if do_quit: self.quit() + def _save_geometry(self): + """Save the window geometry to the state config.""" + rect = self.mainwindow.geometry() + try: + config.state.add_section('mainwindow') + except configparser.DuplicateSectionError: + pass + config.state['mainwindow']['x'] = str(rect.x()) + config.state['mainwindow']['y'] = str(rect.y()) + config.state['mainwindow']['w'] = str(rect.width()) + config.state['mainwindow']['h'] = str(rect.height()) + @pyqtSlot(tuple) def cmd_handler(self, tpl): """Handle commands and delegate the specific actions. diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index 120ceae65..7f46eca71 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -27,19 +27,23 @@ import os import io import os.path import logging +import configparser from configparser import ConfigParser, ExtendedInterpolation from qutebrowser.utils.misc import read_file config = None +state = None colordict = {} fontdict = {} def init(confdir): """Initialize the global objects based on the config in configdir.""" - global config, colordict, fontdict - config = Config(confdir) + global config, state, colordict, fontdict + logging.debug("Config init, confdir {}".format(confdir)) + config = Config(confdir, 'qutebrowser.conf', read_file('qutebrowser.conf')) + state = Config(confdir, 'state', always_save=True) try: colordict = ColorDict(config['colors']) except KeyError: @@ -126,31 +130,37 @@ class FontDict(dict): except KeyError: return None - class Config(ConfigParser): """Our own ConfigParser subclass.""" configdir = None - FNAME = 'config' default_cp = None config_loaded = False - def __init__(self, configdir): + def __init__(self, configdir, fname, default_config=None, + always_save=False): """Config constructor. configdir -- directory to store the config in. + fname -- Filename of the config file. + default_config -- Default config as string. + always_save -- Whether to always save the config, even when it wasn't + loaded. """ super().__init__(interpolation=ExtendedInterpolation()) + self.always_save = always_save + self.configdir = configdir self.default_cp = ConfigParser(interpolation=ExtendedInterpolation()) self.default_cp.optionxform = lambda opt: opt # be case-insensitive - self.default_cp.read_string(read_file('qutebrowser.conf')) + if default_config is not None: + self.default_cp.read_string(default_config) if not self.configdir: return self.optionxform = lambda opt: opt # be case-insensitive self.configdir = configdir - self.configfile = os.path.join(self.configdir, self.FNAME) + self.configfile = os.path.join(self.configdir, fname) if not os.path.isfile(self.configfile): return logging.debug("Reading config from {}".format(self.configfile)) diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 5674775ab..3f5c7ccb6 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -17,12 +17,13 @@ """The main window of QuteBrowser.""" +from PyQt5.QtCore import QRect from PyQt5.QtWidgets import QWidget, QVBoxLayout from qutebrowser.widgets.statusbar import StatusBar from qutebrowser.widgets.browser import TabbedBrowser from qutebrowser.widgets.completion import CompletionView - +import qutebrowser.utils.config as config class MainWindow(QWidget): @@ -41,8 +42,20 @@ class MainWindow(QWidget): super().__init__() self.setWindowTitle('qutebrowser') - # FIXME maybe store window position/size on exit - self.resize(800, 600) + try: + x = int(config.state['mainwindow']['x']) + y = int(config.state['mainwindow']['y']) + w = int(config.state['mainwindow']['w']) + h = int(config.state['mainwindow']['h']) + except KeyError: + rect = QRect() + else: + rect = QRect(x, y, w, h) + if not rect.isValid(): + rect = QRect(50, 50, 800, 600) + # FIXME there is no setFrameGeometry, but this seems to do the wrong + # thing. + self.setGeometry(rect) self.vbox = QVBoxLayout(self) self.vbox.setContentsMargins(0, 0, 0, 0) From bbab0e74305f1c4195f87b8abbc2e99f4424d093 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 12:02:07 +0100 Subject: [PATCH 13/26] autosearch bugfix and debug output --- qutebrowser/qutebrowser.conf | 2 +- qutebrowser/utils/url.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qutebrowser/qutebrowser.conf b/qutebrowser/qutebrowser.conf index 0c3ec474e..43e30ba5c 100644 --- a/qutebrowser/qutebrowser.conf +++ b/qutebrowser/qutebrowser.conf @@ -29,7 +29,7 @@ show_completion = true ignorecase = true wrapsearch = true startpage = http://www.duckduckgo.com/ -auto_search = fuzzy +auto_search = naive [tabbar] # movable: bool, whether tabs should be movable diff --git a/qutebrowser/utils/url.py b/qutebrowser/utils/url.py index 156c57170..7866e4409 100644 --- a/qutebrowser/utils/url.py +++ b/qutebrowser/utils/url.py @@ -93,7 +93,6 @@ def is_about_url(url): def is_url(url): """Return True if url (QUrl) seems to be a valid URL.""" urlstr = urlstring(url) - logging.debug('Checking if "{}" is an URL'.format(urlstr)) try: autosearch = config.config.getboolean('general', 'auto_search') @@ -105,6 +104,9 @@ def is_url(url): else: autosearch = None + logging.debug('Checking if "{}" is an URL (autosearch={}).'.format( + urlstr, autosearch)) + if autosearch is None: # no autosearch, so everything is an URL. return True @@ -123,6 +125,8 @@ def is_url(url): elif autosearch == 'naive': logging.debug('Checking via naive check') return _is_url_naive(url) + else: + raise ValueError("Invalid autosearch value") def _is_url_naive(url): From b43a4311619a7440fb76a7c50b87f906ee6c8238 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 12:03:12 +0100 Subject: [PATCH 14/26] Update TODO --- TODO | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 8c032699e..68e220ec5 100644 --- a/TODO +++ b/TODO @@ -46,7 +46,8 @@ Minor features Hiding scrollbars :bind Ctrl+A/X to increase/decrease last number in URL -position remembering +remove unneeded class attributes +shutdown timeout Other stuff =========== From 109906043e0b95d49ea2d029bf9f14607eaeddf9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 12:10:36 +0100 Subject: [PATCH 15/26] Add a trace_lines util function --- qutebrowser/utils/misc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index a7420bf35..3fdeec3b2 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -17,6 +17,7 @@ """Other utilities which don't fit anywhere else.""" +import sys import os.path from PyQt5.QtCore import pyqtRemoveInputHook @@ -50,3 +51,14 @@ def read_file(filename): fn = os.path.join(qutebrowser.basedir, filename) with open(fn, 'r', encoding='UTF-8') as f: return f.read() + + +def trace_lines(do_trace): + def trace(frame, event, arg): + print("{}, {}:{}".format(event, frame.f_code.co_filename, + frame.f_lineno)) + return trace + if do_trace: + sys.settrace(trace) + else: + sys.settrace(None) From c9e4f2417d64e9e74150e612c513afe4c9295936 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 13:05:42 +0100 Subject: [PATCH 16/26] Add more shutdown debugging --- qutebrowser/app.py | 14 +++++++++++++- qutebrowser/widgets/browser.py | 16 +++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index c9b002260..3e3dd6cf1 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -238,6 +238,7 @@ class QuteBrowser(QApplication): logging.debug("maybe_quit called from {}, quit status {}".format( sender, self._quit_status)) if all(self._quit_status.values()): + logging.debug("maybe_quit quitting.") self.quit() def _python_hacks(self): @@ -328,7 +329,8 @@ class QuteBrowser(QApplication): logging.exception("Could not save window geometry.") try: if do_quit: - self.mainwindow.tabs.shutdown_complete.connect(self.quit) + self.mainwindow.tabs.shutdown_complete.connect( + self._on_tab_shutdown_complete) else: self.mainwindow.tabs.shutdown_complete.connect( functools.partial(self._maybe_quit, 'shutdown')) @@ -350,6 +352,16 @@ class QuteBrowser(QApplication): config.state['mainwindow']['w'] = str(rect.width()) config.state['mainwindow']['h'] = str(rect.height()) + @pyqtSlot() + def _on_tab_shutdown_complete(self): + """Quit application after a shutdown. + + Gets called when all tabs finished shutting down after shutdown(). + + """ + logging.debug("Shutdown complete, quitting.") + self.quit() + @pyqtSlot(tuple) def cmd_handler(self, tpl): """Handle commands and delegate the specific actions. diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index 9c90b62d8..14d2e9d9f 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -171,9 +171,10 @@ class TabbedBrowser(TabWidget): try: self._tabs.remove(tab) except ValueError: - logging.error("tab {} could not be removed from tabs {}.".format( - tab, self._tabs)) + logging.exception("tab {} could not be removed".format(tab)) + logging.debug("Tabs after removing: {}".format(self._tabs)) if not self._tabs: # all tabs shut down + logging.debug("Tab shutdown complete.") self.shutdown_complete.emit() def cur_reload(self, count=None): @@ -451,12 +452,12 @@ class TabbedBrowser(TabWidget): except TypeError: pass tabcount = self.count() - logging.debug("Shutting down {} tabs...".format(tabcount)) if tabcount == 0: + logging.debug("No tabs -> shutdown complete") self.shutdown_complete.emit() return for tabidx in range(tabcount): - logging.debug("shutdown {}".format(tabidx)) + logging.debug("Shutting down tab {}/{}".format(tabidx, tabcount)) tab = self.widget(tabidx) tab.shutdown(callback=functools.partial(self._cb_tab_shutdown, tab)) @@ -574,13 +575,18 @@ class BrowserTab(QWebView): netman.abort_requests() netman.destroyed.connect(functools.partial(self.on_destroyed, netman)) netman.deleteLater() - logging.debug("Shutdown scheduled") + logging.debug("Tab shutdown scheduled") def on_destroyed(self, sender): """Called when a subsystem has been destroyed during shutdown.""" self._destroyed[sender] = True + dbgout = '\n'.join(['{}: {}'.format(k.__class__.__name__, v) + for (k, v) in self._destroyed.items()]) + logging.debug("{} has been destroyed, new status:\n{}".format( + sender.__class__.__name__, dbgout)) if all(self._destroyed.values()): if self._shutdown_callback is not None: + logging.debug("Everything destroyed, calling callback") self._shutdown_callback() def eventFilter(self, watched, e): From 234d17148350155ddd121c6291ba6f5cf8367118 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 14:06:45 +0100 Subject: [PATCH 17/26] Use Qt saveGeometry and restoreGeometry --- qutebrowser/app.py | 10 ++++------ qutebrowser/widgets/mainwindow.py | 29 +++++++++++++++++------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3e3dd6cf1..f84eafd27 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -25,6 +25,7 @@ import subprocess import configparser from signal import signal, SIGINT from argparse import ArgumentParser +from base64 import b64encode # Print a nice traceback on segfault -- only available on Python 3.3+, but if # it's unavailable, it doesn't matter much. @@ -342,15 +343,12 @@ class QuteBrowser(QApplication): def _save_geometry(self): """Save the window geometry to the state config.""" - rect = self.mainwindow.geometry() + geom = b64encode(bytes(self.mainwindow.saveGeometry())).decode('ASCII') try: - config.state.add_section('mainwindow') + config.state.add_section('geometry') except configparser.DuplicateSectionError: pass - config.state['mainwindow']['x'] = str(rect.x()) - config.state['mainwindow']['y'] = str(rect.y()) - config.state['mainwindow']['w'] = str(rect.width()) - config.state['mainwindow']['h'] = str(rect.height()) + config.state['geometry']['mainwindow'] = geom @pyqtSlot() def _on_tab_shutdown_complete(self): diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 3f5c7ccb6..56f1a91d7 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -17,6 +17,9 @@ """The main window of QuteBrowser.""" +import binascii +from base64 import b64decode + from PyQt5.QtCore import QRect from PyQt5.QtWidgets import QWidget, QVBoxLayout @@ -43,19 +46,17 @@ class MainWindow(QWidget): self.setWindowTitle('qutebrowser') try: - x = int(config.state['mainwindow']['x']) - y = int(config.state['mainwindow']['y']) - w = int(config.state['mainwindow']['w']) - h = int(config.state['mainwindow']['h']) - except KeyError: - rect = QRect() + geom = b64decode(config.state['geometry']['mainwindow'], + validate=True) + except binascii.Error: + self._set_default_geometry() else: - rect = QRect(x, y, w, h) - if not rect.isValid(): - rect = QRect(50, 50, 800, 600) - # FIXME there is no setFrameGeometry, but this seems to do the wrong - # thing. - self.setGeometry(rect) + try: + ok = self.restoreGeometry(geom) + except KeyError: + self._set_default_geometry() + if not ok: + self._set_default_geometry() self.vbox = QVBoxLayout(self) self.vbox.setContentsMargins(0, 0, 0, 0) @@ -93,3 +94,7 @@ class MainWindow(QWidget): #self.retranslateUi(MainWindow) #self.tabWidget.setCurrentIndex(0) #QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def _set_default_geometry(self): + """Set some sensible default geometry.""" + self.setGeometry(QRect(50, 50, 800, 600)) From 3d37b22fb0b67c2d6ef3c4d5ffcf2feb6c2e4b6e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 14:21:39 +0100 Subject: [PATCH 18/26] Cleanup --- qutebrowser/utils/config.py | 1 + qutebrowser/utils/misc.py | 4 +++- qutebrowser/widgets/mainwindow.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index 7f46eca71..91f0a3020 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -130,6 +130,7 @@ class FontDict(dict): except KeyError: return None + class Config(ConfigParser): """Our own ConfigParser subclass.""" diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 3fdeec3b2..829cd4c11 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -54,7 +54,9 @@ def read_file(filename): def trace_lines(do_trace): - def trace(frame, event, arg): + """Turn on/off printing each executed line.""" + def trace(frame, event, _): + """Trace function passed to sys.settrace.""" print("{}, {}:{}".format(event, frame.f_code.co_filename, frame.f_lineno)) return trace diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 56f1a91d7..a465209ce 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -28,6 +28,7 @@ from qutebrowser.widgets.browser import TabbedBrowser from qutebrowser.widgets.completion import CompletionView import qutebrowser.utils.config as config + class MainWindow(QWidget): """The main window of QuteBrowser. From 2fcda0e67beadc049df317718805826966ae0230 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 14:34:46 +0100 Subject: [PATCH 19/26] Refactor Config.get() --- qutebrowser/utils/config.py | 29 +++++++++++++++++------------ qutebrowser/utils/url.py | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index 91f0a3020..1927b86e1 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -27,8 +27,8 @@ import os import io import os.path import logging -import configparser -from configparser import ConfigParser, ExtendedInterpolation +from configparser import (ConfigParser, ExtendedInterpolation, NoSectionError, + NoOptionError) from qutebrowser.utils.misc import read_file @@ -37,6 +37,9 @@ state = None colordict = {} fontdict = {} +# Special value for an unset fallback, so None can be passed as fallback. +_UNSET = object() + def init(confdir): """Initialize the global objects based on the config in configdir.""" @@ -179,7 +182,7 @@ class Config(ConfigParser): except KeyError: return self.default_cp[key] - def get(self, *args, **kwargs): + def get(self, *args, raw=False, vars=None, fallback=_UNSET): """Get an item from the configparser or default dict. Extend ConfigParser's get(). @@ -191,16 +194,18 @@ class Config(ConfigParser): - If that's not available, we're doomed. """ - if 'fallback' in kwargs: - orig_fallback = kwargs['fallback'] - del kwargs['fallback'] - else: - orig_fallback = configparser._UNSET + # pylint: disable=redefined-builtin try: - fallback = self.default_cp.get(*args, **kwargs) - except configparser.NoSectionError: # FIXME other errors? - fallback = orig_fallback - return super().get(*args, fallback=fallback, **kwargs) + return super().get(*args, raw=raw, vars=vars) + except (NoSectionError, NoOptionError): + pass + try: + return self.default_cp.get(*args, raw=raw, vars=vars) + except (NoSectionError, NoOptionError): + if fallback is _UNSET: + raise + else: + return fallback def save(self): """Save the config file.""" diff --git a/qutebrowser/utils/url.py b/qutebrowser/utils/url.py index 7866e4409..68069bc93 100644 --- a/qutebrowser/utils/url.py +++ b/qutebrowser/utils/url.py @@ -82,6 +82,7 @@ def _get_search_url(txt): logging.debug('engine: default, term "{}"'.format(txt)) if template is None or not term: raise ValueError + # pylint: disable=maybe-no-member return QUrl.fromUserInput(template.format(urllib.parse.quote(term))) From 6cd02ca368b5fd54af210de04abf55f6a3afa01a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 14:37:49 +0100 Subject: [PATCH 20/26] utils.config docstring update --- qutebrowser/utils/config.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index 1927b86e1..d96cfb189 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -15,13 +15,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Configuration storage and config-related utilities. - -config -- The main Config object. -colordict -- All configured colors. -MONOSPACE -- A list of suitable monospace fonts. - -""" +"""Configuration storage and config-related utilities.""" import os import io From 911d5d3b4a150e1019db8fb5b1ea38de8a87dbd8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 16:38:13 +0100 Subject: [PATCH 21/26] Refactor ALL the things --- qutebrowser/app.py | 102 ++++++----- qutebrowser/commands/keys.py | 58 +++--- qutebrowser/commands/template.py | 4 + qutebrowser/commands/utils.py | 75 +++++--- qutebrowser/models/commandcompletion.py | 7 +- qutebrowser/models/completion.py | 84 ++++----- qutebrowser/models/completionfilter.py | 30 ++-- qutebrowser/utils/config.py | 42 +++-- qutebrowser/utils/signals.py | 27 +-- qutebrowser/utils/style.py | 3 + qutebrowser/widgets/browser.py | 106 ++++++----- qutebrowser/widgets/completion.py | 181 ++++++++++--------- qutebrowser/widgets/crash.py | 60 ++++--- qutebrowser/widgets/mainwindow.py | 19 +- qutebrowser/widgets/statusbar.py | 228 +++++++++++++++--------- qutebrowser/widgets/tabbar.py | 11 +- 16 files changed, 602 insertions(+), 435 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index f84eafd27..2fc0e88f1 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -64,39 +64,43 @@ class QuteBrowser(QApplication): >>> app = QuteBrowser() >>> sys.exit(app.exec_()) - """ + Attributes: + mainwindow: The MainWindow QWidget. + commandparser: The main CommandParser instance. + keyparser: The main KeyParser instance. + searchparser: The main SearchParser instance. + _dirs: AppDirs instance for config/cache directories. + _args: ArgumentParser instance. + _timers: List of used QTimers so they don't get GCed. + _shutting_down: True if we're currently shutting down. + _quit_status: The current quitting status. - dirs = None # AppDirs - config/cache directories - config = None # Config(Parser) object - mainwindow = None - commandparser = None - keyparser = None - args = None # ArgumentParser - timers = None - shutting_down = False - _quit_status = None + """ def __init__(self): super().__init__(sys.argv) self._quit_status = {} + self._timers = [] + self._shutting_down = False + sys.excepthook = self._exception_hook - self._parseopts() + self._args = self._parseopts() self._initlog() self._initmisc() - self.dirs = AppDirs('qutebrowser') - if self.args.confdir is None: - confdir = self.dirs.user_config_dir - elif self.args.confdir == '': + self._dirs = AppDirs('qutebrowser') + if self._args.confdir is None: + confdir = self._dirs.user_config_dir + elif self._args.confdir == '': confdir = None else: - confdir = self.args.confdir + confdir = self._args.confdir config.init(confdir) self.commandparser = cmdutils.CommandParser() self.searchparser = cmdutils.SearchParser() - self.keyparser = KeyParser(self.mainwindow) + self.keyparser = KeyParser(self) self._init_cmds() self.mainwindow = MainWindow() @@ -125,7 +129,7 @@ class QuteBrowser(QApplication): self.mainwindow.show() self._python_hacks() timer = QTimer.singleShot(0, self._process_init_args) - self.timers.append(timer) + self._timers.append(timer) def _process_init_args(self): """Process initial positional args. @@ -139,7 +143,7 @@ class QuteBrowser(QApplication): QEventLoop.ExcludeSocketNotifiers) opened_urls = False - for e in self.args.command: + for e in self._args.command: if e.startswith(':'): logging.debug('Startup cmd {}'.format(e)) self.commandparser.run(e.lstrip(':')) @@ -254,7 +258,7 @@ class QuteBrowser(QApplication): timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) - self.timers.append(timer) + self._timers.append(timer) def _parseopts(self): """Parse command line options.""" @@ -269,11 +273,11 @@ class QuteBrowser(QApplication): 'on startup.', metavar=':command') # URLs will actually be in command parser.add_argument('url', nargs='*', help='URLs to open on startup.') - self.args = parser.parse_args() + return parser.parse_args() def _initlog(self): """Initialisation of the logging output.""" - loglevel = 'debug' if self.args.debug else self.args.loglevel + loglevel = 'debug' if self._args.debug else self._args.loglevel numeric_level = getattr(logging, loglevel.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: {}'.format(loglevel)) @@ -285,11 +289,10 @@ class QuteBrowser(QApplication): def _initmisc(self): """Initialize misc things.""" - if self.args.debug: + if self._args.debug: os.environ['QT_FATAL_WARNINGS'] = '1' self.setApplicationName("qutebrowser") self.setApplicationVersion(qutebrowser.__version__) - self.timers = [] def _init_cmds(self): """Initialisation of the qutebrowser commands. @@ -315,9 +318,9 @@ class QuteBrowser(QApplication): quit -- Whether to quit after shutting down. """ - if self.shutting_down: + if self._shutting_down: return - self.shutting_down = True + self._shutting_down = True logging.debug("Shutting down... (do_quit={})".format(do_quit)) try: config.config.save() @@ -331,7 +334,7 @@ class QuteBrowser(QApplication): try: if do_quit: self.mainwindow.tabs.shutdown_complete.connect( - self._on_tab_shutdown_complete) + self.on_tab_shutdown_complete) else: self.mainwindow.tabs.shutdown_complete.connect( functools.partial(self._maybe_quit, 'shutdown')) @@ -351,7 +354,7 @@ class QuteBrowser(QApplication): config.state['geometry']['mainwindow'] = geom @pyqtSlot() - def _on_tab_shutdown_complete(self): + def on_tab_shutdown_complete(self): """Quit application after a shutdown. Gets called when all tabs finished shutting down after shutdown(). @@ -375,32 +378,33 @@ class QuteBrowser(QApplication): (count, argv) = tpl cmd = argv[0] args = argv[1:] + browser = self.mainwindow.tabs handlers = { - 'open': self.mainwindow.tabs.openurl, - 'opencur': self.mainwindow.tabs.opencur, - 'tabopen': self.mainwindow.tabs.tabopen, - 'tabopencur': self.mainwindow.tabs.tabopencur, + 'open': browser.openurl, + 'opencur': browser.opencur, + 'tabopen': browser.tabopen, + 'tabopencur': browser.tabopencur, 'quit': self.shutdown, - 'tabclose': self.mainwindow.tabs.cur_close, - 'tabprev': self.mainwindow.tabs.switch_prev, - 'tabnext': self.mainwindow.tabs.switch_next, - 'reload': self.mainwindow.tabs.cur_reload, - 'stop': self.mainwindow.tabs.cur_stop, - 'back': self.mainwindow.tabs.cur_back, - 'forward': self.mainwindow.tabs.cur_forward, - 'print': self.mainwindow.tabs.cur_print, - 'scroll': self.mainwindow.tabs.cur_scroll, - 'scroll_page': self.mainwindow.tabs.cur_scroll_page, - 'scroll_perc_x': self.mainwindow.tabs.cur_scroll_percent_x, - 'scroll_perc_y': self.mainwindow.tabs.cur_scroll_percent_y, - 'undo': self.mainwindow.tabs.undo_close, + 'tabclose': browser.cur_close, + 'tabprev': browser.switch_prev, + 'tabnext': browser.switch_next, + 'reload': browser.cur_reload, + 'stop': browser.cur_stop, + 'back': browser.cur_back, + 'forward': browser.cur_forward, + 'print': browser.cur_print, + 'scroll': browser.cur_scroll, + 'scroll_page': browser.cur_scroll_page, + 'scroll_perc_x': browser.cur_scroll_percent_x, + 'scroll_perc_y': browser.cur_scroll_percent_y, + 'undo': browser.undo_close, 'pyeval': self.pyeval, 'nextsearch': self.searchparser.nextsearch, - 'yank': self.mainwindow.tabs.cur_yank, - 'yanktitle': self.mainwindow.tabs.cur_yank_title, - 'paste': self.mainwindow.tabs.paste, - 'tabpaste': self.mainwindow.tabs.tabpaste, + 'yank': browser.cur_yank, + 'yanktitle': browser.cur_yank_title, + 'paste': browser.paste, + 'tabpaste': browser.tabpaste, 'crash': self.crash, } diff --git a/qutebrowser/commands/keys.py b/qutebrowser/commands/keys.py index 7ba79c3a5..5fcc18b2e 100644 --- a/qutebrowser/commands/keys.py +++ b/qutebrowser/commands/keys.py @@ -32,17 +32,24 @@ startchars = ":/?" class KeyParser(QObject): - """Parser for vim-like key sequences.""" + """Parser for vim-like key sequences. + + Attributes: + commandparser: Commandparser instance. + _keystring: The currently entered key sequence + _bindings: Bound keybindings + _modifier_bindings: Bound modifier bindings. + + Signals: + set_cmd_text: Emitted when the statusbar should set a partial command. + arg: Text to set. + keystring_updated: Emitted when the keystring is updated. + arg: New keystring. + + """ - keystring = '' # The currently entered key sequence - # Signal emitted when the statusbar should set a partial command set_cmd_text = pyqtSignal(str) - # Signal emitted when the keystring is updated keystring_updated = pyqtSignal(str) - # Keybindings - bindings = {} - modifier_bindings = {} - commandparser = None MATCH_PARTIAL = 0 MATCH_DEFINITIVE = 1 @@ -51,6 +58,9 @@ class KeyParser(QObject): def __init__(self, mainwindow): super().__init__(mainwindow) self.commandparser = CommandParser() + self._keystring = '' + self._bindings = {} + self._modifier_bindings = {} def from_config_sect(self, sect): """Load keybindings from a ConfigParser section. @@ -65,10 +75,10 @@ class KeyParser(QObject): keystr = self._normalize_keystr(key.strip('@')) logging.debug('registered mod key: {} -> {}'.format(keystr, cmd)) - self.modifier_bindings[keystr] = cmd + self._modifier_bindings[keystr] = cmd else: logging.debug('registered key: {} -> {}'.format(key, cmd)) - self.bindings[key] = cmd + self._bindings[key] = cmd def handle(self, e): """Handle a new keypress and call the respective handlers. @@ -79,7 +89,7 @@ class KeyParser(QObject): handled = self._handle_modifier_key(e) if not handled: self._handle_single_key(e) - self.keystring_updated.emit(self.keystring) + self.keystring_updated.emit(self._keystring) def _handle_modifier_key(self, e): """Handle a new keypress with modifiers. @@ -108,7 +118,7 @@ class KeyParser(QObject): modstr += s + '+' keystr = QKeySequence(e.key()).toString() try: - cmdstr = self.modifier_bindings[modstr + keystr] + cmdstr = self._modifier_bindings[modstr + keystr] except KeyError: logging.debug('No binding found for {}.'.format(modstr + keystr)) return True @@ -133,15 +143,15 @@ class KeyParser(QObject): logging.debug('Ignoring, no text') return - self.keystring += txt + self._keystring += txt - if any(self.keystring == c for c in startchars): - self.set_cmd_text.emit(self.keystring) - self.keystring = '' + if any(self._keystring == c for c in startchars): + self.set_cmd_text.emit(self._keystring) + self._keystring = '' return (countstr, cmdstr_needle) = re.match(r'^(\d*)(.*)', - self.keystring).groups() + self._keystring).groups() if not cmdstr_needle: return @@ -157,16 +167,16 @@ class KeyParser(QObject): if match == self.MATCH_DEFINITIVE: pass elif match == self.MATCH_PARTIAL: - logging.debug('No match for "{}" (added {})'.format(self.keystring, - txt)) + logging.debug('No match for "{}" (added {})'.format( + self._keystring, txt)) return elif match == self.MATCH_NONE: logging.debug('Giving up with "{}", no matches'.format( - self.keystring)) - self.keystring = '' + self._keystring)) + self._keystring = '' return - self.keystring = '' + self._keystring = '' count = int(countstr) if countstr else None self._run_or_fill(cmdstr_hay, count=count, ignore_exc=False) return @@ -178,11 +188,11 @@ class KeyParser(QObject): """ try: - cmdstr_hay = self.bindings[cmdstr_needle] + cmdstr_hay = self._bindings[cmdstr_needle] return (self.MATCH_DEFINITIVE, cmdstr_hay) except KeyError: # No definitive match, check if there's a chance of a partial match - for hay in self.bindings: + for hay in self._bindings: try: if cmdstr_needle[-1] == hay[len(cmdstr_needle) - 1]: return (self.MATCH_PARTIAL, None) diff --git a/qutebrowser/commands/template.py b/qutebrowser/commands/template.py index b99c79a9c..7126fe017 100644 --- a/qutebrowser/commands/template.py +++ b/qutebrowser/commands/template.py @@ -30,6 +30,10 @@ class Command(QObject): See the module documentation for qutebrowser.commands.commands for details. + Signals: + signal: Emitted when the command was executed. + arg: A tuple (command, [args]) + """ # FIXME: diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index 7de54da6a..4e20bfeec 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -49,12 +49,26 @@ def register_all(): class SearchParser(QObject): - """Parse qutebrowser searches.""" + """Parse qutebrowser searches. + + Attributes: + _text: The text from the last search. + _flags: The flags from the last search. + + Signals: + do_search: Emitted when a search should be started. + arg 1: Search string. + arg 2: Flags to use. + + """ - text = None - flags = 0 do_search = pyqtSignal(str, 'QWebPage::FindFlags') + def __init__(self, parent=None): + self._text = None + self._flags = 0 + super().__init__(parent) + @pyqtSlot(str) def search(self, text): """Search for a text on a website. @@ -80,33 +94,45 @@ class SearchParser(QObject): rev -- Search direction. """ - if self.text != text: + if self._text is not None and self._text != text: self.do_search.emit('', 0) - self.text = text - self.flags = 0 + self._text = text + self._flags = 0 if config.config.getboolean('general', 'ignorecase', fallback=True): - self.flags |= QWebPage.FindCaseSensitively + self._flags |= QWebPage.FindCaseSensitively if config.config.getboolean('general', 'wrapsearch', fallback=True): - self.flags |= QWebPage.FindWrapsAroundDocument + self._flags |= QWebPage.FindWrapsAroundDocument if rev: - self.flags |= QWebPage.FindBackward - self.do_search.emit(self.text, self.flags) + self._flags |= QWebPage.FindBackward + self.do_search.emit(self._text, self._flags) def nextsearch(self, count=1): """Continue the search to the ([count]th) next term.""" - if self.text is not None: + if self._text is not None: for i in range(count): # pylint: disable=unused-variable - self.do_search.emit(self.text, self.flags) + self.do_search.emit(self._text, self._flags) class CommandParser(QObject): - """Parse qutebrowser commandline commands.""" + """Parse qutebrowser commandline commands. - text = '' - cmd = '' - args = [] - error = pyqtSignal(str) # Emitted if there's an error + Attributes: + _cmd: The command which was parsed. + _args: The arguments which were parsed. + + Signals: + error: Emitted if there was an error. + arg: The error message. + + """ + + error = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self._cmd = None + self._args = [] def _parse(self, text): """Split the commandline text into command and arguments. @@ -114,8 +140,7 @@ class CommandParser(QObject): Raise NoSuchCommandError if a command wasn't found. """ - self.text = text - parts = self.text.strip().split(maxsplit=1) + parts = text.strip().split(maxsplit=1) if not parts: raise NoSuchCommandError cmdstr = parts[0] @@ -130,19 +155,19 @@ class CommandParser(QObject): args = shlex.split(parts[1]) else: args = [parts[1]] - self.cmd = cmd - self.args = args + self._cmd = cmd + self._args = args def _check(self): """Check if the argument count for the command is correct.""" - self.cmd.check(self.args) + self._cmd.check(self._args) def _run(self, count=None): """Run a command with an optional count.""" if count is not None: - self.cmd.run(self.args, count=count) + self._cmd.run(self._args, count=count) else: - self.cmd.run(self.args) + self._cmd.run(self._args) @pyqtSlot(str, int, bool) def run(self, text, count=None, ignore_exc=True): @@ -165,7 +190,7 @@ class CommandParser(QObject): except ArgumentCountError: if ignore_exc: self.error.emit("{}: invalid argument count".format( - self.cmd.mainname)) + self._cmd.mainname)) return False else: raise diff --git a/qutebrowser/models/commandcompletion.py b/qutebrowser/models/commandcompletion.py index 25bb6e819..d8e4f7d72 100644 --- a/qutebrowser/models/commandcompletion.py +++ b/qutebrowser/models/commandcompletion.py @@ -17,6 +17,8 @@ """A CompletionModel filled with all commands and descriptions.""" +from collections import OrderedDict + from qutebrowser.commands.utils import cmd_dict from qutebrowser.models.completion import CompletionModel @@ -35,5 +37,6 @@ class CommandCompletionModel(CompletionModel): if not obj.hide: doc = obj.__doc__.splitlines()[0].strip().rstrip('.') cmdlist.append([obj.mainname, doc]) - self._data['Commands'] = sorted(cmdlist) - self.init_data() + data = OrderedDict() + data['Commands'] = sorted(cmdlist) + self.init_data(data) diff --git a/qutebrowser/models/completion.py b/qutebrowser/models/completion.py index 1e4ed488b..7d2ab64a4 100644 --- a/qutebrowser/models/completion.py +++ b/qutebrowser/models/completion.py @@ -15,15 +15,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""The base completion model for completion in the command line. - -Contains: - CompletionModel -- A simple tree model based on Python data. - CompletionItem -- One item in the CompletionModel. - -""" - -from collections import OrderedDict +"""The base completion model for completion in the command line.""" from PyQt5.QtCore import Qt, QVariant, QAbstractItemModel, QModelIndex @@ -34,15 +26,18 @@ class CompletionModel(QAbstractItemModel): Used for showing completions later in the CompletionView. + Attributes: + _id_map: A mapping from Python object IDs (from id()) to objects, to be + used as internalIndex for the model. + _root: The root item. + """ def __init__(self, parent=None): super().__init__(parent) - self._data = OrderedDict() - self.parents = [] - self.id_map = {} - self.root = CompletionItem([""] * 2) - self.id_map[id(self.root)] = self.root + self._id_map = {} + self._root = CompletionItem([""] * 2) + self._id_map[id(self._root)] = self._root def removeRows(self, position=0, count=1, parent=QModelIndex()): """Remove rows from the model. @@ -63,9 +58,9 @@ class CompletionModel(QAbstractItemModel): """ if index.isValid(): - return self.id_map[index.internalId()] + return self._id_map[index.internalId()] else: - return self.root + return self._root def columnCount(self, parent=QModelIndex()): """Return the column count in the model. @@ -74,7 +69,7 @@ class CompletionModel(QAbstractItemModel): """ # pylint: disable=unused-argument - return self.root.column_count() + return self._root.column_count() def data(self, index, role=Qt.DisplayRole): """Return the data for role/index as QVariant. @@ -86,7 +81,7 @@ class CompletionModel(QAbstractItemModel): if not index.isValid(): return QVariant() try: - item = self.id_map[index.internalId()] + item = self._id_map[index.internalId()] except KeyError: return QVariant() try: @@ -106,7 +101,7 @@ class CompletionModel(QAbstractItemModel): if not index.isValid(): return Qt.NoItemFlags flags = Qt.ItemIsEnabled - if len(self.id_map[index.internalId()].children) > 0: + if len(self._id_map[index.internalId()].children) > 0: return flags else: return flags | Qt.ItemIsSelectable @@ -119,7 +114,7 @@ class CompletionModel(QAbstractItemModel): """ if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return QVariant(self.root.data(section)) + return QVariant(self._root.data(section)) return QVariant() def setData(self, index, value, role=Qt.EditRole): @@ -131,7 +126,7 @@ class CompletionModel(QAbstractItemModel): """ if not index.isValid(): return False - item = self.id_map[index.internalId()] + item = self._id_map[index.internalId()] try: item.setdata(index.column(), value, role) except (IndexError, ValueError): @@ -153,14 +148,14 @@ class CompletionModel(QAbstractItemModel): return QModelIndex() if not parent.isValid(): - parent_item = self.root + parent_item = self._root else: - parent_item = self.id_map[parent.internalId()] + parent_item = self._id_map[parent.internalId()] child_item = parent_item.children[row] if child_item: index = self.createIndex(row, column, id(child_item)) - self.id_map.setdefault(index.internalId(), child_item) + self._id_map.setdefault(index.internalId(), child_item) return index else: return QModelIndex() @@ -174,8 +169,8 @@ class CompletionModel(QAbstractItemModel): """ if not index.isValid(): return QModelIndex() - item = self.id_map[index.internalId()].parent - if item == self.root or item is None: + item = self._id_map[index.internalId()].parent + if item == self._root or item is None: return QModelIndex() return self.createIndex(item.row(), 0, id(item)) @@ -190,9 +185,9 @@ class CompletionModel(QAbstractItemModel): return 0 if not parent.isValid(): - pitem = self.root + pitem = self._root else: - pitem = self.id_map[parent.internalId()] + pitem = self._id_map[parent.internalId()] return len(pitem.children) @@ -205,15 +200,19 @@ class CompletionModel(QAbstractItemModel): """ raise NotImplementedError - def init_data(self): - """Initialize the Qt model based on the data in self._data.""" - for (cat, items) in self._data.items(): - newcat = CompletionItem([cat], self.root) - self.id_map[id(newcat)] = newcat - self.root.children.append(newcat) + def init_data(self, data): + """Initialize the Qt model based on the data given. + + data -- dict of data to process. + + """ + for (cat, items) in data.items(): + newcat = CompletionItem([cat], self._root) + self._id_map[id(newcat)] = newcat + self._root.children.append(newcat) for item in items: newitem = CompletionItem(item, newcat) - self.id_map[id(newitem)] = newitem + self._id_map[id(newitem)] = newitem newcat.children.append(newitem) def mark_all_items(self, needle): @@ -247,12 +246,15 @@ class CompletionModel(QAbstractItemModel): class CompletionItem(): - """An item (row) in a CompletionModel.""" + """An item (row) in a CompletionModel. - parent = None - children = None - _data = None - _marks = None + Attributes: + parent: The parent of this item. + children: The children of this item. + _data: The data of this item. + _marks: The marks of this item. + + """ def __init__(self, data, parent=None): """Constructor for CompletionItem. @@ -262,8 +264,8 @@ class CompletionItem(): """ self.parent = parent - self._data = data self.children = [] + self._data = data self._marks = [] def data(self, column, role=Qt.DisplayRole): diff --git a/qutebrowser/models/completionfilter.py b/qutebrowser/models/completionfilter.py index 8d2c50bb8..371784e68 100644 --- a/qutebrowser/models/completionfilter.py +++ b/qutebrowser/models/completionfilter.py @@ -27,13 +27,17 @@ from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex class CompletionFilterModel(QSortFilterProxyModel): - """Subclass of QSortFilterProxyModel with custom sorting/filtering.""" + """Subclass of QSortFilterProxyModel with custom sorting/filtering. - _pattern = None - srcmodel = None + Attributes: + _pattern: The pattern to filter with, used in pattern property. + srcmodel: The source model. + + """ def __init__(self, parent=None): super().__init__(parent) + self.srcmodel = None self._pattern = '' @property @@ -41,16 +45,6 @@ class CompletionFilterModel(QSortFilterProxyModel): """Getter for pattern.""" return self._pattern - def setsrc(self, model): - """Set a new source model and clear the pattern. - - model -- The new source model. - - """ - self.setSourceModel(model) - self.srcmodel = model - self.pattern = '' - @pattern.setter def pattern(self, val): """Setter for pattern. @@ -71,6 +65,16 @@ class CompletionFilterModel(QSortFilterProxyModel): self.sort(sortcol) self.invalidate() + def setsrc(self, model): + """Set a new source model and clear the pattern. + + model -- The new source model. + + """ + self.setSourceModel(model) + self.srcmodel = model + self.pattern = '' + def filterAcceptsRow(self, row, parent): """Custom filter implementation. diff --git a/qutebrowser/utils/config.py b/qutebrowser/utils/config.py index d96cfb189..c286e56b4 100644 --- a/qutebrowser/utils/config.py +++ b/qutebrowser/utils/config.py @@ -130,11 +130,14 @@ class FontDict(dict): class Config(ConfigParser): - """Our own ConfigParser subclass.""" + """Our own ConfigParser subclass. - configdir = None - default_cp = None - config_loaded = False + Attributes: + _configdir: The dictionary to save the config in. + _default_cp: The ConfigParser instance supplying the default values. + _config_loaded: Whether the config was loaded successfully. + + """ def __init__(self, configdir, fname, default_config=None, always_save=False): @@ -148,22 +151,23 @@ class Config(ConfigParser): """ super().__init__(interpolation=ExtendedInterpolation()) + self._config_loaded = False self.always_save = always_save - self.configdir = configdir - self.default_cp = ConfigParser(interpolation=ExtendedInterpolation()) - self.default_cp.optionxform = lambda opt: opt # be case-insensitive + self._configdir = configdir + self._default_cp = ConfigParser(interpolation=ExtendedInterpolation()) + self._default_cp.optionxform = lambda opt: opt # be case-insensitive if default_config is not None: - self.default_cp.read_string(default_config) - if not self.configdir: + self._default_cp.read_string(default_config) + if not self._configdir: return self.optionxform = lambda opt: opt # be case-insensitive - self.configdir = configdir - self.configfile = os.path.join(self.configdir, fname) + self._configdir = configdir + self.configfile = os.path.join(self._configdir, fname) if not os.path.isfile(self.configfile): return logging.debug("Reading config from {}".format(self.configfile)) self.read(self.configfile) - self.config_loaded = True + self._config_loaded = True def __getitem__(self, key): """Get an item from the configparser or default dict. @@ -174,7 +178,7 @@ class Config(ConfigParser): try: return super().__getitem__(key) except KeyError: - return self.default_cp[key] + return self._default_cp[key] def get(self, *args, raw=False, vars=None, fallback=_UNSET): """Get an item from the configparser or default dict. @@ -194,7 +198,7 @@ class Config(ConfigParser): except (NoSectionError, NoOptionError): pass try: - return self.default_cp.get(*args, raw=raw, vars=vars) + return self._default_cp.get(*args, raw=raw, vars=vars) except (NoSectionError, NoOptionError): if fallback is _UNSET: raise @@ -203,13 +207,13 @@ class Config(ConfigParser): def save(self): """Save the config file.""" - if self.configdir is None or (not self.config_loaded and - not self.always_save): + if self._configdir is None or (not self._config_loaded and + not self.always_save): logging.error("Not saving config (dir {}, loaded {})".format( - self.configdir, self.config_loaded)) + self._configdir, self._config_loaded)) return - if not os.path.exists(self.configdir): - os.makedirs(self.configdir, 0o755) + if not os.path.exists(self._configdir): + os.makedirs(self._configdir, 0o755) logging.debug("Saving config to {}".format(self.configfile)) with open(self.configfile, 'w') as f: self.write(f) diff --git a/qutebrowser/utils/signals.py b/qutebrowser/utils/signals.py index a124f3cae..b933c57ad 100644 --- a/qutebrowser/utils/signals.py +++ b/qutebrowser/utils/signals.py @@ -42,10 +42,13 @@ def dbg_signal(sig, args): class SignalCache(QObject): - """Cache signals emitted by an object, and re-emit them later.""" + """Cache signals emitted by an object, and re-emit them later. - uncached = None - signal_dict = None + Attributes: + _uncached: A list of signals which should not be cached. + _signal_dict: The internal mapping of signals we got. + + """ def __init__(self, uncached=None): """Create a new SignalCache. @@ -56,10 +59,10 @@ class SignalCache(QObject): """ super().__init__() if uncached is None: - self.uncached = [] + self._uncached = [] else: - self.uncached = uncached - self.signal_dict = OrderedDict() + self._uncached = uncached + self._signal_dict = OrderedDict() def add(self, sig, args): """Add a new signal to the signal cache. @@ -71,21 +74,21 @@ class SignalCache(QObject): """ if not self._signal_needs_caching(sig): return - had_signal = sig.signal in self.signal_dict - self.signal_dict[sig.signal] = (sig, args) + had_signal = sig.signal in self._signal_dict + self._signal_dict[sig.signal] = (sig, args) if had_signal: - self.signal_dict.move_to_end(sig.signal) + self._signal_dict.move_to_end(sig.signal) def clear(self): """Clear/purge the signal cache.""" - self.signal_dict.clear() + self._signal_dict.clear() def replay(self): """Replay all cached signals.""" - for (signal, args) in self.signal_dict.values(): + for (signal, args) in self._signal_dict.values(): logging.debug('emitting {}'.format(dbg_signal(signal, args))) signal.emit(*args) def _signal_needs_caching(self, signal): """Return True if a signal should be cached, false otherwise.""" - return not signal_name(signal) in self.uncached + return not signal_name(signal) in self._uncached diff --git a/qutebrowser/utils/style.py b/qutebrowser/utils/style.py index 344ab959c..4b7a654b4 100644 --- a/qutebrowser/utils/style.py +++ b/qutebrowser/utils/style.py @@ -38,6 +38,9 @@ class Style(QCommonStyle): http://stackoverflow.com/a/17294081 https://code.google.com/p/makehuman/source/browse/trunk/makehuman/lib/qtgui.py # noqa # pylint: disable=line-too-long + Attributes: + _style: The base/"parent" style. + """ def __init__(self, style): diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index 14d2e9d9f..491346b0b 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -56,23 +56,40 @@ class TabbedBrowser(TabWidget): - the signal gets filtered with _filter_signals and self.cur_* gets emitted if the signal occured in the current tab. + Attributes: + _url_stack: Stack of URLs of closed tabs. + _space: Space QShortcut to avoid garbage collection + _tabs: A list of open tabs. + + Signals: + cur_progress: Progress of the current tab changed (loadProgress). + cur_load_started: Current tab started loading (loadStarted) + cur_load_finished: Current tab finished loading (loadFinished) + cur_statusbar_message: Current tab got a statusbar message + (statusBarMessage) + cur_url_changed: Current URL changed (urlChanged) + cur_link_hovered: Link hovered in current tab (linkHovered) + cur_scroll_perc_changed: Scroll percentage of current tab changed. + arg 1: x-position in %. + arg 2: y-position in %. + keypress: A key was pressed. + arg: The QKeyEvent leading to the keypress. + shutdown_complete: The shuttdown is completed. + quit: The last tab was closed, quit application. + """ - cur_progress = pyqtSignal(int) # Progress of the current tab changed - cur_load_started = pyqtSignal() # Current tab started loading - cur_load_finished = pyqtSignal(bool) # Current tab finished loading - cur_statusbar_message = pyqtSignal(str) # Status bar message - cur_url_changed = pyqtSignal('QUrl') # Current URL changed - cur_link_hovered = pyqtSignal(str, str, str) # Link hovered in cur tab - # Current tab changed scroll position + cur_progress = pyqtSignal(int) + cur_load_started = pyqtSignal() + cur_load_finished = pyqtSignal(bool) + cur_statusbar_message = pyqtSignal(str) + cur_url_changed = pyqtSignal('QUrl') + cur_link_hovered = pyqtSignal(str, str, str) cur_scroll_perc_changed = pyqtSignal(int, int) - set_cmd_text = pyqtSignal(str) # Set commandline to a given text + set_cmd_text = pyqtSignal(str) keypress = pyqtSignal('QKeyEvent') - shutdown_complete = pyqtSignal() # All tabs have been shut down. - quit = pyqtSignal() # Last tab closed, quit application. - _url_stack = [] # Stack of URLs of closed tabs - _space = None # Space QShortcut - _tabs = None + shutdown_complete = pyqtSignal() + quit = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) @@ -469,27 +486,37 @@ class BrowserTab(QWebView): Our own subclass of a QWebView with some added bells and whistles. + Attributes: + page_: The QWebPage behind the view + signal_cache: The signal cache associated with the view. + _scroll_pos: The old scroll position. + _shutdown_callback: Callback to be called after shutdown. + _open_new_tab: Whether to open a new tab for the next action. + _shutdown_callback: The callback to call after shutting down. + _destroyed: Dict of all items to be destroyed on shtudown. + Signals: + scroll_pos_changed: Scroll percentage of current tab changed. + arg 1: x-position in %. + arg 2: y-position in %. + open_tab: A new tab should be opened. + arg: The address to open + linkHovered: QWebPages linkHovered signal exposed. + """ - progress = 0 scroll_pos_changed = pyqtSignal(int, int) open_tab = pyqtSignal('QUrl') linkHovered = pyqtSignal(str, str, str) - _scroll_pos = (-1, -1) - _shutdown_callback = None # callback to be called after shutdown - _open_new_tab = False # open new tab for the next action - _destroyed = None # Dict of all items to be destroyed. - page_ = None # QWebPage - # dict of tab specific signals, and the values we last got from them. - signal_cache = None def __init__(self, parent=None): super().__init__(parent) + self._scroll_pos = (-1, -1) + self._shutdown_callback = None + self._open_new_tab = False self._destroyed = {} self.page_ = BrowserPage(self) self.setPage(self.page_) self.signal_cache = SignalCache(uncached=['linkHovered']) - self.loadProgress.connect(self.on_load_progress) self.page_.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page_.linkHovered.connect(self.linkHovered) self.installEventFilter(self) @@ -532,17 +559,6 @@ class BrowserTab(QWebView): else: self.openurl(url) - @pyqtSlot(int) - def on_load_progress(self, prog): - """Update the progress property if the loading progress changed. - - Slot for the loadProgress signal. - - prog -- New progress. - - """ - self.progress = prog - def shutdown(self, callback=None): """Shut down the tab cleanly and remove it. @@ -562,22 +578,22 @@ class BrowserTab(QWebView): self.settings().setAttribute(QWebSettings.JavascriptEnabled, False) self._destroyed[self.page_] = False - self.page_.destroyed.connect(functools.partial(self.on_destroyed, + self.page_.destroyed.connect(functools.partial(self._on_destroyed, self.page_)) self.page_.deleteLater() self._destroyed[self] = False - self.destroyed.connect(functools.partial(self.on_destroyed, self)) + self.destroyed.connect(functools.partial(self._on_destroyed, self)) self.deleteLater() netman = self.page_.network_access_manager self._destroyed[netman] = False netman.abort_requests() - netman.destroyed.connect(functools.partial(self.on_destroyed, netman)) + netman.destroyed.connect(functools.partial(self._on_destroyed, netman)) netman.deleteLater() logging.debug("Tab shutdown scheduled") - def on_destroyed(self, sender): + def _on_destroyed(self, sender): """Called when a subsystem has been destroyed during shutdown.""" self._destroyed[sender] = True dbgout = '\n'.join(['{}: {}'.format(k.__class__.__name__, v) @@ -636,10 +652,13 @@ class BrowserTab(QWebView): class BrowserPage(QWebPage): - """Our own QWebPage with advanced features.""" + """Our own QWebPage with advanced features. - _extension_handlers = None - network_access_manager = None + Attributes: + _extension_handlers: Mapping of QWebPage extensions to their handlers. + network_access_manager: The QNetworkAccessManager used. + + """ def __init__(self, parent=None): super().__init__(parent) @@ -683,9 +702,12 @@ class BrowserPage(QWebPage): class NetworkManager(QNetworkAccessManager): - """Our own QNetworkAccessManager.""" + """Our own QNetworkAccessManager. - _requests = None + Attributes: + _requests: Pending requests. + + """ def __init__(self, parent=None): self._requests = {} diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index 35799a9a1..e0cd87c58 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -45,9 +45,22 @@ class CompletionView(QTreeView): Highlights completions based on marks in the UserRole. + Attributes: + _STYLESHEET: The stylesheet template for the CompletionView. + _completion_models: dict of available completion models. + _ignore_next: Whether to ignore the next cmd_text_changed signal. + _enabled: Whether showing the CompletionView is enabled. + _completing: Whether we're currently completing something. + _height: The height to use for the CompletionView. + _delegate: The item delegate used. + + Signals: + append_cmd_text: Command text which should be appended to the + statusbar. + """ - _stylesheet = """ + _STYLESHEET = """ QTreeView {{ {font[completion]} {color[completion.fg]} @@ -77,25 +90,24 @@ class CompletionView(QTreeView): # like one anymore # FIXME somehow only the first column is yellow, even with # setAllColumnsShowFocus - completion_models = {} append_cmd_text = pyqtSignal(str) - ignore_next = False - enabled = True - completing = False - height = QPoint(0, 200) - _delegate = None def __init__(self, parent=None): super().__init__(parent) - self.enabled = config.config.getboolean('general', 'show_completion') - self.completion_models[''] = None - self.completion_models['command'] = CommandCompletionModel() + self._height = QPoint(0, 200) # FIXME make that configurable + self._enabled = config.config.getboolean('general', 'show_completion') + self._completion_models = {} + self._completion_models[''] = None + self._completion_models['command'] = CommandCompletionModel() + self._ignore_next = False + self._completing = False + self.model = CompletionFilterModel() self.setModel(self.model) self.setmodel('command') - self._delegate = CompletionItemDelegate(self) + self._delegate = _CompletionItemDelegate(self) self.setItemDelegate(self._delegate) - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) self.setHeaderHidden(True) self.setIndentation(0) @@ -117,7 +129,7 @@ class CompletionView(QTreeView): model -- A QAbstractItemModel with available completions. """ - self.model.setsrc(self.completion_models[model]) + self.model.setsrc(self._completion_models[model]) self.expandAll() self.resizeColumnToContents(0) @@ -131,7 +143,7 @@ class CompletionView(QTreeView): """ bottomleft = geom.topLeft() bottomright = geom.topRight() - topleft = bottomleft - self.height + topleft = bottomleft - self._height assert topleft.x() < bottomright.x() assert topleft.y() < bottomright.y() self.setGeometry(QRect(topleft, bottomright)) @@ -144,7 +156,7 @@ class CompletionView(QTreeView): pos -- A QPoint containing the statusbar position. """ - self.move(pos - self.height) + self.move(pos - self._height) @pyqtSlot(str) def on_cmd_text_changed(self, text): @@ -154,22 +166,22 @@ class CompletionView(QTreeView): text -- The new text """ - if self.ignore_next: + if self._ignore_next: # Text changed by a completion, so we don't have to complete again. - self.ignore_next = False + self._ignore_next = False return # FIXME more sophisticated completions if ' ' in text or not text.startswith(':'): self.hide() - self.completing = False + self._completing = False return - self.completing = True + self._completing = True self.setmodel('command') text = text.lstrip(':') self.model.pattern = text self.model.srcmodel.mark_all_items(text) - if self.enabled: + if self._enabled: self.show() @pyqtSlot(bool) @@ -182,7 +194,7 @@ class CompletionView(QTreeView): shift -- Whether shift is pressed or not. """ - if not self.completing: + if not self._completing: # No completion running at the moment, ignore keypress return idx = self._next_idx(shift) @@ -190,7 +202,7 @@ class CompletionView(QTreeView): idx, QItemSelectionModel.ClearAndSelect) data = self.model.data(idx) if data is not None: - self.ignore_next = True + self._ignore_next = True self.append_cmd_text.emit(self.model.data(idx) + ' ') def _next_idx(self, upwards): @@ -217,7 +229,7 @@ class CompletionView(QTreeView): return idx -class CompletionItemDelegate(QStyledItemDelegate): +class _CompletionItemDelegate(QStyledItemDelegate): """Delegate used by CompletionView to draw individual items. @@ -227,12 +239,20 @@ class CompletionItemDelegate(QStyledItemDelegate): Original implementation: qt/src/gui/styles/qcommonstyle.cpp:drawControl:2153 + Attributes: + _opt: The QStyleOptionViewItem which is used. + _style: The style to be used. + _painter: The QPainter to be used. + _doc: The QTextDocument to be used. + """ - opt = None - style = None - painter = None - doc = None + def __init__(self, parent=None): + self._painter = None + self._opt = None + self._doc = None + self._style = None + super().__init__(parent) def sizeHint(self, option, index): """Override sizeHint of QStyledItemDelegate. @@ -244,49 +264,48 @@ class CompletionItemDelegate(QStyledItemDelegate): value = index.data(Qt.SizeHintRole) if value is not None: return value - self.opt = QStyleOptionViewItem(option) - self.initStyleOption(self.opt, index) - self.style = self.opt.widget.style() + self._opt = QStyleOptionViewItem(option) + self.initStyleOption(self._opt, index) + self._style = self._opt.widget.style() self._get_textdoc(index) - docsize = self.doc.size().toSize() - size = self.style.sizeFromContents(QStyle.CT_ItemViewItem, self.opt, - docsize, self.opt.widget) + docsize = self._doc.size().toSize() + size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt, + docsize, self._opt.widget) return size + QSize(10, 1) def paint(self, painter, option, index): """Override the QStyledItemDelegate paint function.""" - painter.save() - - self.painter = painter - self.opt = QStyleOptionViewItem(option) - self.initStyleOption(self.opt, index) - self.style = self.opt.widget.style() + self._painter = painter + self._painter.save() + self._opt = QStyleOptionViewItem(option) + self.initStyleOption(self._opt, index) + self._style = self._opt.widget.style() self._draw_background() self._draw_icon() self._draw_text(index) self._draw_focus_rect() - painter.restore() + self._painter.restore() def _draw_background(self): """Draw the background of an ItemViewItem.""" - self.style.drawPrimitive(self.style.PE_PanelItemViewItem, self.opt, - self.painter, self.opt.widget) + self._style.drawPrimitive(self._style.PE_PanelItemViewItem, self._opt, + self._painter, self._opt.widget) def _draw_icon(self): """Draw the icon of an ItemViewItem.""" - icon_rect = self.style.subElementRect( - self.style.SE_ItemViewItemDecoration, self.opt, self.opt.widget) + icon_rect = self._style.subElementRect( + self._style.SE_ItemViewItemDecoration, self._opt, self._opt.widget) mode = QIcon.Normal - if not self.opt.state & QStyle.State_Enabled: + if not self._opt.state & QStyle.State_Enabled: mode = QIcon.Disabled - elif self.opt.state & QStyle.State_Selected: + elif self._opt.state & QStyle.State_Selected: mode = QIcon.Selected - state = QIcon.On if self.opt.state & QStyle.State_Open else QIcon.Off - self.opt.icon.paint(self.painter, icon_rect, - self.opt.decorationAlignment, mode, state) + state = QIcon.On if self._opt.state & QStyle.State_Open else QIcon.Off + self._opt.icon.paint(self._painter, icon_rect, + self._opt.decorationAlignment, mode, state) def _draw_text(self, index): """Draw the text of an ItemViewItem. @@ -297,13 +316,13 @@ class CompletionItemDelegate(QStyledItemDelegate): index of the item of the item -- The QModelIndex of the item to draw. """ - if not self.opt.text: + if not self._opt.text: return - text_rect_ = self.style.subElementRect(self.style.SE_ItemViewItemText, - self.opt, self.opt.widget) - margin = self.style.pixelMetric(QStyle.PM_FocusFrameHMargin, self.opt, - self.opt.widget) + 1 + text_rect_ = self._style.subElementRect( + self._style.SE_ItemViewItemText, self._opt, self._opt.widget) + margin = self._style.pixelMetric(QStyle.PM_FocusFrameHMargin, + self._opt, self._opt.widget) + 1 # remove width padding text_rect = text_rect_.adjusted(margin, 0, -margin, 0) # move text upwards a bit @@ -311,8 +330,8 @@ class CompletionItemDelegate(QStyledItemDelegate): text_rect.adjust(0, -1, 0, -1) else: text_rect.adjust(0, -2, 0, -2) - self.painter.save() - state = self.opt.state + self._painter.save() + state = self._opt.state if state & QStyle.State_Enabled and state & QStyle.State_Active: cg = QPalette.Normal elif state & QStyle.State_Enabled: @@ -321,22 +340,22 @@ class CompletionItemDelegate(QStyledItemDelegate): cg = QPalette.Disabled if state & QStyle.State_Selected: - self.painter.setPen(self.opt.palette.color( + self._painter.setPen(self._opt.palette.color( cg, QPalette.HighlightedText)) # FIXME this is a dirty fix for the text jumping by one pixel... # we really should do this properly somehow text_rect.adjust(0, -1, 0, 0) else: - self.painter.setPen(self.opt.palette.color(cg, QPalette.Text)) + self._painter.setPen(self._opt.palette.color(cg, QPalette.Text)) if state & QStyle.State_Editing: - self.painter.setPen(self.opt.palette.color(cg, QPalette.Text)) - self.painter.drawRect(text_rect_.adjusted(0, 0, -1, -1)) + self._painter.setPen(self._opt.palette.color(cg, QPalette.Text)) + self._painter.drawRect(text_rect_.adjusted(0, 0, -1, -1)) - self.painter.translate(text_rect.left(), text_rect.top()) + self._painter.translate(text_rect.left(), text_rect.top()) self._get_textdoc(index) self._draw_textdoc(text_rect) - self.painter.restore() + self._painter.restore() def _draw_textdoc(self, text_rect): """Draw the QTextDocument of an item. @@ -345,7 +364,7 @@ class CompletionItemDelegate(QStyledItemDelegate): """ clip = QRectF(0, 0, text_rect.width(), text_rect.height()) - self.doc.drawContents(self.painter, clip) + self._doc.drawContents(self._painter, clip) def _get_textdoc(self, index): """Create the QTextDocument of an item. @@ -356,32 +375,32 @@ class CompletionItemDelegate(QStyledItemDelegate): # FIXME we probably should do eliding here. See # qcommonstyle.cpp:viewItemDrawText text_option = QTextOption() - if self.opt.features & QStyleOptionViewItem.WrapText: + if self._opt.features & QStyleOptionViewItem.WrapText: text_option.setWrapMode(QTextOption.WordWrap) else: text_option.setWrapMode(QTextOption.ManualWrap) - text_option.setTextDirection(self.opt.direction) + text_option.setTextDirection(self._opt.direction) text_option.setAlignment(QStyle.visualAlignment( - self.opt.direction, self.opt.displayAlignment)) + self._opt.direction, self._opt.displayAlignment)) - self.doc = QTextDocument(self) + self._doc = QTextDocument(self) if index.parent().isValid(): - self.doc.setPlainText(self.opt.text) + self._doc.setPlainText(self._opt.text) else: - self.doc.setHtml('{}'.format(html.escape(self.opt.text))) - self.doc.setDefaultFont(self.opt.font) - self.doc.setDefaultTextOption(text_option) - self.doc.setDefaultStyleSheet(config.get_stylesheet(""" + self._doc.setHtml('{}'.format(html.escape(self._opt.text))) + self._doc.setDefaultFont(self._opt.font) + self._doc.setDefaultTextOption(text_option) + self._doc.setDefaultStyleSheet(config.get_stylesheet(""" .highlight {{ {color[completion.match.fg]} }} """)) - self.doc.setDocumentMargin(2) + self._doc.setDocumentMargin(2) if index.column() == 0: marks = index.data(Qt.UserRole) for mark in marks: - cur = QTextCursor(self.doc) + cur = QTextCursor(self._doc) cur.setPosition(mark[0]) cur.setPosition(mark[1], QTextCursor.KeepAnchor) txt = cur.selectedText() @@ -391,12 +410,12 @@ class CompletionItemDelegate(QStyledItemDelegate): def _draw_focus_rect(self): """Draw the focus rectangle of an ItemViewItem.""" - state = self.opt.state + state = self._opt.state if not state & QStyle.State_HasFocus: return - o = self.opt - o.rect = self.style.subElementRect(self.style.SE_ItemViewItemFocusRect, - self.opt, self.opt.widget) + o = self._opt + o.rect = self._style.subElementRect( + self._style.SE_ItemViewItemFocusRect, self._opt, self._opt.widget) o.state |= QStyle.State_KeyboardFocusChange | QStyle.State_Item if state & QStyle.State_Enabled: cg = QPalette.Normal @@ -406,6 +425,6 @@ class CompletionItemDelegate(QStyledItemDelegate): role = QPalette.Highlight else: role = QPalette.Window - o.backgroundColor = self.opt.palette.color(cg, role) - self.style.drawPrimitive(QStyle.PE_FrameFocusRect, o, self.painter, - self.opt.widget) + o.backgroundColor = self._opt.palette.color(cg, role) + self._style.drawPrimitive(QStyle.PE_FrameFocusRect, o, self._painter, + self._opt.widget) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index 43a45c661..b5da98dbf 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -29,14 +29,18 @@ from qutebrowser.utils.version import version class CrashDialog(QDialog): - """Dialog which gets shown after there was a crash.""" + """Dialog which gets shown after there was a crash. - vbox = None - lbl = None - txt = None - hbox = None - btn_quit = None - btn_restore = None + Attributes: + These are just here to have a static reference to avoid GCing. + _vbox: The main QVBoxLayout + _lbl: The QLabel with the static text + _txt: The QTextEdit with the crash information + _hbox: The QHboxLayout containing the buttons + _btn_quit: The quit button + _btn_restore: the restore button + + """ def __init__(self, pages, cmdhist, exc): super().__init__() @@ -44,8 +48,8 @@ class CrashDialog(QDialog): self.setWindowTitle('Whoops!') self.setModal(True) - self.vbox = QVBoxLayout(self) - self.lbl = QLabel() + self._vbox = QVBoxLayout(self) + self._lbl = QLabel() text = ('Argh! qutebrowser crashed unexpectedly.
' 'Please review the info below to remove sensitive data and ' 'then submit it to
' @@ -53,29 +57,29 @@ class CrashDialog(QDialog): if pages: text += ('You can click "Restore tabs" to attempt to reopen your ' 'open tabs.') - self.lbl.setText(text) - self.lbl.setWordWrap(True) - self.vbox.addWidget(self.lbl) + self._lbl.setText(text) + self._lbl.setWordWrap(True) + self._vbox.addWidget(self._lbl) - self.txt = QTextEdit() - self.txt.setReadOnly(True) - self.txt.setText(self._crash_info(pages, cmdhist, exc)) - self.vbox.addWidget(self.txt) + self._txt = QTextEdit() + self._txt.setReadOnly(True) + self._txt.setText(self._crash_info(pages, cmdhist, exc)) + self._vbox.addWidget(self._txt) - self.hbox = QHBoxLayout() - self.hbox.addStretch() - self.btn_quit = QPushButton() - self.btn_quit.setText('Quit') - self.btn_quit.clicked.connect(self.reject) - self.hbox.addWidget(self.btn_quit) + self._hbox = QHBoxLayout() + self._hbox.addStretch() + self._btn_quit = QPushButton() + self._btn_quit.setText('Quit') + self._btn_quit.clicked.connect(self.reject) + self._hbox.addWidget(self._btn_quit) if pages: - self.btn_restore = QPushButton() - self.btn_restore.setText('Restore tabs') - self.btn_restore.clicked.connect(self.accept) - self.btn_restore.setDefault(True) - self.hbox.addWidget(self.btn_restore) + self._btn_restore = QPushButton() + self._btn_restore.setText('Restore tabs') + self._btn_restore.clicked.connect(self.accept) + self._btn_restore.setDefault(True) + self._hbox.addWidget(self._btn_restore) - self.vbox.addLayout(self.hbox) + self._vbox.addLayout(self._hbox) def _crash_info(self, pages, cmdhist, exc): """Gather crash information to display.""" diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index a465209ce..72da4f74e 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -36,11 +36,12 @@ class MainWindow(QWidget): Adds all needed components to a vbox, initializes subwidgets and connects signals. - """ + Attributes: + tabs: The TabbedBrowser widget. + status: The StatusBar widget. + _vbox: The main QVBoxLayout. - vbox = None - tabs = None - status = None + """ def __init__(self): super().__init__() @@ -59,17 +60,17 @@ class MainWindow(QWidget): if not ok: self._set_default_geometry() - self.vbox = QVBoxLayout(self) - self.vbox.setContentsMargins(0, 0, 0, 0) - self.vbox.setSpacing(0) + self._vbox = QVBoxLayout(self) + self._vbox.setContentsMargins(0, 0, 0, 0) + self._vbox.setSpacing(0) self.tabs = TabbedBrowser() - self.vbox.addWidget(self.tabs) + self._vbox.addWidget(self.tabs) self.completion = CompletionView(self) self.status = StatusBar() - self.vbox.addWidget(self.status) + self._vbox.addWidget(self.status) self.status.resized.connect(self.completion.resize_to_bar) self.status.moved.connect(self.completion.move_to_bar) diff --git a/qutebrowser/widgets/statusbar.py b/qutebrowser/widgets/statusbar.py index 16862eb71..0a5fd59f2 100644 --- a/qutebrowser/widgets/statusbar.py +++ b/qutebrowser/widgets/statusbar.py @@ -32,20 +32,33 @@ from qutebrowser.utils.url import urlstring class StatusBar(QWidget): - """The statusbar at the bottom of the mainwindow.""" + """The statusbar at the bottom of the mainwindow. + + Attributes: + cmd: The Command widget in the statusbar. + txt: The Text widget in the statusbar. + keystring: The KeyString widget in the statusbar. + percentage: The Percentage widget in the statusbar. + url: The Url widget in the statusbar. + prog: The Progress widget in the statusbar. + _hbox: The main QHBoxLayout. + _error: If there currently is an error, accessed through the error + property. + _STYLESHEET: The stylesheet template. + + Signals: + resized: Emitted when the statusbar has resized, so the completion + widget can adjust its size to it. + arg: The new size. + moved: Emitted when the statusbar has moved, so the completion widget + can move the the right position. + arg: The new position. + + """ - hbox = None - cmd = None - txt = None - keystring = None - percentage = None - url = None - prog = None resized = pyqtSignal('QRect') moved = pyqtSignal('QPoint') - _error = False - _option = None - _stylesheet = """ + _STYLESHEET = """ QWidget#StatusBar[error="false"] {{ {color[statusbar.bg]} }} @@ -60,36 +73,38 @@ class StatusBar(QWidget): }} """ - # TODO: the statusbar should be a bit smaller def __init__(self, parent=None): super().__init__(parent) self.setObjectName(self.__class__.__name__) - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) - self.hbox = QHBoxLayout(self) - self.hbox.setContentsMargins(0, 0, 0, 0) - self.hbox.setSpacing(5) + self._error = False + self._option = None - self.cmd = Command(self) - self.hbox.addWidget(self.cmd) + self._hbox = QHBoxLayout(self) + self._hbox.setContentsMargins(0, 0, 0, 0) + self._hbox.setSpacing(5) - self.txt = Text(self) - self.hbox.addWidget(self.txt) - self.hbox.addStretch() + self.cmd = _Command(self) + self._hbox.addWidget(self.cmd) - self.keystring = KeyString(self) - self.hbox.addWidget(self.keystring) + self.txt = _Text(self) + self._hbox.addWidget(self.txt) + self._hbox.addStretch() - self.url = Url(self) - self.hbox.addWidget(self.url) + self.keystring = _KeyString(self) + self._hbox.addWidget(self.keystring) - self.percentage = Percentage(self) - self.hbox.addWidget(self.percentage) + self.url = _Url(self) + self._hbox.addWidget(self.url) - self.prog = Progress(self) - self.hbox.addWidget(self.prog) + self.percentage = _Percentage(self) + self._hbox.addWidget(self.percentage) + + self.prog = _Progress(self) + self._hbox.addWidget(self.prog) @pyqtProperty(bool) def error(self): @@ -106,20 +121,19 @@ class StatusBar(QWidget): """ self._error = val - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) def paintEvent(self, e): """Override QWIidget.paintEvent to handle stylesheets.""" # pylint: disable=unused-argument - self._option = QStyleOption() - self._option.initFrom(self) + option = QStyleOption() + option.initFrom(self) painter = QPainter(self) - self.style().drawPrimitive(QStyle.PE_Widget, self._option, - painter, self) + self.style().drawPrimitive(QStyle.PE_Widget, option, painter, self) @pyqtSlot(str) def disp_error(self, text): - """Displaysan error in the statusbar.""" + """Display an error in the statusbar.""" self.error = True self.txt.set_error(text) @@ -147,24 +161,40 @@ class StatusBar(QWidget): self.moved.emit(e.pos()) -class Command(QLineEdit): +class _Command(QLineEdit): - """The commandline part of the statusbar.""" + """The commandline part of the statusbar. + + Attributes: + history: The command history, with newer commands at the bottom. + _statusbar: The statusbar (parent) QWidget. + _shortcuts: Defined QShortcuts to prevent GCing. + _tmphist: The temporary history for history browsing + _histpos: The current position inside _tmphist + _validator: The current command validator. + + Signals: + got_cmd: Emitted when a command is triggered by the user. + arg: The command string. + got_search: Emitted when the user started a new search. + arg: The search term. + got_rev_search: Emitted when the user started a new reverse search. + arg: The search term. + esc_pressed: Emitted when the escape key was pressed. + tab_pressed: Emitted when the tab key was pressed. + arg: Whether shift has been pressed. + hide_completion: Emitted when the completion widget should be hidden. + + """ + + # FIXME we should probably use a proper model for the command history. - # Emitted when a command is triggered by the user got_cmd = pyqtSignal(str) - # Emitted for searches triggered by the user got_search = pyqtSignal(str) got_search_rev = pyqtSignal(str) - statusbar = None # The status bar object - esc_pressed = pyqtSignal() # Emitted when escape is pressed - tab_pressed = pyqtSignal(bool) # Emitted when tab is pressed (arg: shift) - hide_completion = pyqtSignal() # Hide completion window - history = [] # The command history, with newer commands at the bottom - _shortcuts = [] - _tmphist = [] - _histpos = None - _validator = None # CommandValidator + esc_pressed = pyqtSignal() + tab_pressed = pyqtSignal(bool) + hide_completion = pyqtSignal() # FIXME won't the tab key switch to the next widget? # See [0] for a possible fix. @@ -173,7 +203,9 @@ class Command(QLineEdit): def __init__(self, statusbar): super().__init__(statusbar) # FIXME - self.statusbar = statusbar + self._statusbar = statusbar + self._histpos = None + self._tmphist = [] self.setStyleSheet(""" QLineEdit { border: 0px; @@ -181,16 +213,18 @@ class Command(QLineEdit): background-color: transparent; } """) - self._validator = CommandValidator(self) + self._validator = _CommandValidator(self) self.setValidator(self._validator) - self.returnPressed.connect(self.on_return_pressed) + self.returnPressed.connect(self._on_return_pressed) self.textEdited.connect(self._histbrowse_stop) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) + self.history = [] + self._shortcuts = [] for (key, handler) in [ (Qt.Key_Escape, self.esc_pressed), - (Qt.Key_Up, self.on_key_up_pressed), - (Qt.Key_Down, self.on_key_down_pressed), + (Qt.Key_Up, self._on_key_up_pressed), + (Qt.Key_Down, self._on_key_down_pressed), (Qt.Key_Tab | Qt.SHIFT, lambda: self.tab_pressed.emit(True)), (Qt.Key_Tab, lambda: self.tab_pressed.emit(False)) ]: @@ -201,7 +235,7 @@ class Command(QLineEdit): self._shortcuts.append(sc) @pyqtSlot() - def on_return_pressed(self): + def _on_return_pressed(self): """Handle the command in the status bar.""" signals = { ':': self.got_cmd, @@ -240,7 +274,7 @@ class Command(QLineEdit): def focusInEvent(self, e): """Clear error message when the statusbar is focused.""" - self.statusbar.clear_error() + self._statusbar.clear_error() super().focusInEvent(e) def _histbrowse_start(self): @@ -264,7 +298,7 @@ class Command(QLineEdit): self._histpos = None @pyqtSlot() - def on_key_up_pressed(self): + def _on_key_up_pressed(self): """Handle Up presses (go back in history).""" logging.debug("history up [pre]: pos {}".format(self._histpos)) if self._histpos is None: @@ -280,7 +314,7 @@ class Command(QLineEdit): self.set_cmd_text(self._tmphist[self._histpos]) @pyqtSlot() - def on_key_down_pressed(self): + def _on_key_down_pressed(self): """Handle Down presses (go forward in history).""" logging.debug("history up [pre]: pos {}".format(self._histpos, self._tmphist, len(self._tmphist), self._histpos)) @@ -294,7 +328,7 @@ class Command(QLineEdit): self.set_cmd_text(self._tmphist[self._histpos]) -class CommandValidator(QValidator): +class _CommandValidator(QValidator): """Validator to prevent the : from getting deleted.""" @@ -313,13 +347,17 @@ class CommandValidator(QValidator): return (QValidator.Invalid, string, pos) -class Progress(QProgressBar): +class _Progress(QProgressBar): - """The progress bar part of the status bar.""" + """The progress bar part of the status bar. + + Attributes: + _STYLESHEET: The stylesheet template. + + """ - statusbar = None # FIXME for some reason, margin-left is not shown - _stylesheet = """ + _STYLESHEET = """ QProgressBar {{ border-radius: 0px; border: 2px solid transparent; @@ -332,10 +370,9 @@ class Progress(QProgressBar): }} """ - def __init__(self, statusbar): - super().__init__(statusbar) - self.statusbar = statusbar - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + def __init__(self, parent): + super().__init__(parent) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored) self.setTextVisible(False) self.hide() @@ -356,15 +393,17 @@ class TextBase(QLabel): Eliding is loosly based on http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html - """ + Attributes: + _elidemode: Where to elide the text. + _elided_text: The current elided text. - elidemode = None - _elided_text = None + """ def __init__(self, bar, elidemode=Qt.ElideRight): super().__init__(bar) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) - self.elidemode = elidemode + self._elidemode = elidemode + self._elided_text = '' def setText(self, txt): """Extend QLabel::setText to update the elided text afterwards.""" @@ -383,11 +422,11 @@ class TextBase(QLabel): """ self._elided_text = self.fontMetrics().elidedText( - self.text(), self.elidemode, width, Qt.TextShowMnemonic) + self.text(), self._elidemode, width, Qt.TextShowMnemonic) def paintEvent(self, e): """Override QLabel::paintEvent to draw elided text.""" - if self.elidemode == Qt.ElideNone: + if self._elidemode == Qt.ElideNone: super().paintEvent(e) else: painter = QPainter(self) @@ -396,30 +435,37 @@ class TextBase(QLabel): self._elided_text) -class Text(TextBase): +class _Text(TextBase): - """Text displayed in the statusbar.""" + """Text displayed in the statusbar. - old_text = '' + Attributes: + _old_text: The text displayed before the temporary error message. + + """ + + def __init__(self, parent=None): + super().__init__(parent) + self._old_text = '' def set_error(self, text): """Display an error message and save current text in old_text.""" - self.old_text = self.text() + self._old_text = self.text() self.setText(text) def clear_error(self): """Clear a displayed error message.""" - self.setText(self.old_text) + self.setText(self._old_text) -class KeyString(TextBase): +class _KeyString(TextBase): """Keychain string displayed in the statusbar.""" pass -class Percentage(TextBase): +class _Percentage(TextBase): """Reading percentage displayed in the statusbar.""" @@ -434,15 +480,20 @@ class Percentage(TextBase): self.setText('[{:2}%]'.format(y)) -class Url(TextBase): +class _Url(TextBase): - """URL displayed in the statusbar.""" + """URL displayed in the statusbar. - _old_url = None - _old_urltype = None - _urltype = None # 'normal', 'ok', 'error', 'warn, 'hover' + Attributes: + _old_url: The URL displayed before the hover URL. + _old_urltype: The type of the URL displayed before the hover URL. + _urltype: The current URL type. One of normal/ok/error/warn/hover. + Accessed via the urltype property. + _STYLESHEET: The stylesheet template. - _stylesheet = """ + """ + + _STYLESHEET = """ QLabel#Url[urltype="normal"] {{ {color[statusbar.url.fg]} }} @@ -468,7 +519,10 @@ class Url(TextBase): """Override TextBase::__init__ to elide in the middle by default.""" super().__init__(bar, elidemode) self.setObjectName(self.__class__.__name__) - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) + self._urltype = None + self._old_urltype = None + self._old_url = None @pyqtProperty(str) def urltype(self): @@ -480,7 +534,7 @@ class Url(TextBase): def urltype(self, val): """Setter for self.urltype, so it can be used as Qt property.""" self._urltype = val - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) @pyqtSlot(bool) def on_loading_finished(self, ok): diff --git a/qutebrowser/widgets/tabbar.py b/qutebrowser/widgets/tabbar.py index 9a5c75a62..3aa50c1b0 100644 --- a/qutebrowser/widgets/tabbar.py +++ b/qutebrowser/widgets/tabbar.py @@ -26,12 +26,17 @@ from qutebrowser.utils.style import Style class TabWidget(QTabWidget): - """The tabwidget used for TabbedBrowser.""" + """The tabwidget used for TabbedBrowser. + + Attributes: + _STYLESHEET: The stylesheet template to be used. + + """ # FIXME there is still some ugly 1px white stripe from somewhere if we do # background-color: grey for QTabBar... - _stylesheet = """ + _STYLESHEET = """ QTabWidget::pane {{ position: absolute; top: 0px; @@ -63,7 +68,7 @@ class TabWidget(QTabWidget): super().__init__(parent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setStyle(Style(self.style())) - self.setStyleSheet(config.get_stylesheet(self._stylesheet)) + self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) self.setDocumentMode(True) self.setElideMode(Qt.ElideRight) self._init_config() From 2fe2ca85c2be0d554890aee32ca30def29719f80 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 16:57:10 +0100 Subject: [PATCH 22/26] Set WA_StyledBackground for statusbar instead of overriding paintEvent --- qutebrowser/widgets/statusbar.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/qutebrowser/widgets/statusbar.py b/qutebrowser/widgets/statusbar.py index 0a5fd59f2..d3b6fdb1e 100644 --- a/qutebrowser/widgets/statusbar.py +++ b/qutebrowser/widgets/statusbar.py @@ -21,8 +21,7 @@ import logging from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt from PyQt5.QtWidgets import (QWidget, QLineEdit, QProgressBar, QLabel, - QHBoxLayout, QSizePolicy, QShortcut, QStyle, - QStyleOption) + QHBoxLayout, QSizePolicy, QShortcut) from PyQt5.QtGui import QPainter, QKeySequence, QValidator import qutebrowser.utils.config as config @@ -76,6 +75,7 @@ class StatusBar(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setObjectName(self.__class__.__name__) + self.setAttribute(Qt.WA_StyledBackground) self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) @@ -123,14 +123,6 @@ class StatusBar(QWidget): self._error = val self.setStyleSheet(config.get_stylesheet(self._STYLESHEET)) - def paintEvent(self, e): - """Override QWIidget.paintEvent to handle stylesheets.""" - # pylint: disable=unused-argument - option = QStyleOption() - option.initFrom(self) - painter = QPainter(self) - self.style().drawPrimitive(QStyle.PE_Widget, option, painter, self) - @pyqtSlot(str) def disp_error(self, text): """Display an error in the statusbar.""" From bc589c341740afc339a8f12ff08592661b159f47 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 17:09:07 +0100 Subject: [PATCH 23/26] Update TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 68e220ec5..ec2f6e59d 100644 --- a/TODO +++ b/TODO @@ -46,8 +46,6 @@ Minor features Hiding scrollbars :bind Ctrl+A/X to increase/decrease last number in URL -remove unneeded class attributes -shutdown timeout Other stuff =========== From 0425ce7b4879df1043ea09e731a443723dcd2ba6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 17:26:26 +0100 Subject: [PATCH 24/26] Extend paintEvent instead of using an eventFilter --- qutebrowser/widgets/browser.py | 42 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index 491346b0b..0d90abf34 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -519,7 +519,6 @@ class BrowserTab(QWebView): self.signal_cache = SignalCache(uncached=['linkHovered']) self.page_.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page_.linkHovered.connect(self.linkHovered) - self.installEventFilter(self) self.linkClicked.connect(self.on_link_clicked) # FIXME find some way to hide scrollbars without setScrollBarPolicy @@ -605,33 +604,30 @@ class BrowserTab(QWebView): logging.debug("Everything destroyed, calling callback") self._shutdown_callback() - def eventFilter(self, watched, e): - """Dirty hack to emit a signal if the scroll position changed. + def paintEvent(self, e): + """Extend paintEvent to emit a signal if the scroll position changed. - We listen to repaint requests here, in the hope a repaint will always - be requested when scrolling, and if the scroll position actually - changed, we emit a signal. + This is a bit of a hack: We listen to repaint requests here, in the + hope a repaint will always be requested when scrolling, and if the + scroll position actually changed, we emit a signal. - watched -- The watched Qt object. - e -- The new event. + e -- The QPaintEvent. """ - if watched == self and e.type() == QEvent.Paint: + frame = self.page_.mainFrame() + new_pos = (frame.scrollBarValue(Qt.Horizontal), + frame.scrollBarValue(Qt.Vertical)) + if self._scroll_pos != new_pos: + self._scroll_pos = new_pos + logging.debug("Updating scroll position") frame = self.page_.mainFrame() - new_pos = (frame.scrollBarValue(Qt.Horizontal), - frame.scrollBarValue(Qt.Vertical)) - if self._scroll_pos != new_pos: - self._scroll_pos = new_pos - logging.debug("Updating scroll position") - frame = self.page_.mainFrame() - m = (frame.scrollBarMaximum(Qt.Horizontal), - frame.scrollBarMaximum(Qt.Vertical)) - perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0, - round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0) - self.scroll_pos_changed.emit(*perc) - # we're not actually filtering something, let superclass handle the - # event - return super().eventFilter(watched, e) + m = (frame.scrollBarMaximum(Qt.Horizontal), + frame.scrollBarMaximum(Qt.Vertical)) + perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0, + round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0) + self.scroll_pos_changed.emit(*perc) + # Let superclass handle the event + return super().paintEvent(e) def event(self, e): """Check if a link was clicked with the middle button or Ctrl. From 5f83228c75e18393cf4ef83f13a372c8287d20c9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 17:54:17 +0100 Subject: [PATCH 25/26] Reorder some methods, widgets not done yet --- qutebrowser/app.py | 234 ++++++++++++------------- qutebrowser/commands/keys.py | 58 +++--- qutebrowser/commands/utils.py | 36 ++-- qutebrowser/models/completion.py | 176 +++++++++---------- qutebrowser/models/completionfilter.py | 21 +-- qutebrowser/utils/signals.py | 8 +- qutebrowser/utils/url.py | 92 +++++----- qutebrowser/utils/version.py | 46 ++--- 8 files changed, 336 insertions(+), 335 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 2fc0e88f1..150a24a9f 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -131,6 +131,54 @@ class QuteBrowser(QApplication): timer = QTimer.singleShot(0, self._process_init_args) self._timers.append(timer) + def _parseopts(self): + """Parse command line options.""" + parser = ArgumentParser("usage: %(prog)s [options]") + parser.add_argument('-l', '--log', dest='loglevel', + help='Set loglevel', default='info') + parser.add_argument('-c', '--confdir', help='Set config directory ' + '(empty for no config storage)') + parser.add_argument('-d', '--debug', help='Turn on debugging options.', + action='store_true') + parser.add_argument('command', nargs='*', help='Commands to execute ' + 'on startup.', metavar=':command') + # URLs will actually be in command + parser.add_argument('url', nargs='*', help='URLs to open on startup.') + return parser.parse_args() + + def _initlog(self): + """Initialisation of the logging output.""" + loglevel = 'debug' if self._args.debug else self._args.loglevel + numeric_level = getattr(logging, loglevel.upper(), None) + if not isinstance(numeric_level, int): + raise ValueError('Invalid log level: {}'.format(loglevel)) + logging.basicConfig( + level=numeric_level, + format='%(asctime)s [%(levelname)s] ' + '[%(module)s:%(funcName)s:%(lineno)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + + def _initmisc(self): + """Initialize misc things.""" + if self._args.debug: + os.environ['QT_FATAL_WARNINGS'] = '1' + self.setApplicationName("qutebrowser") + self.setApplicationVersion(qutebrowser.__version__) + + def _init_cmds(self): + """Initialisation of the qutebrowser commands. + + Registers all commands, connects its signals, and sets up keyparser. + + """ + cmdutils.register_all() + for cmd in cmdutils.cmd_dict.values(): + cmd.signal.connect(self.cmd_handler) + try: + self.keyparser.from_config_sect(config.config['keybind']) + except KeyError: + pass + def _process_init_args(self): """Process initial positional args. @@ -158,6 +206,20 @@ class QuteBrowser(QApplication): for url in config.config.get('general', 'startpage').split(','): self.mainwindow.tabs.tabopen(url) + def _python_hacks(self): + """Get around some PyQt-oddities by evil hacks. + + This sets up the uncaught exception hook, quits with an appropriate + exit status, and handles Ctrl+C properly by passing control to the + Python interpreter once all 500ms. + + """ + signal(SIGINT, lambda *args: self.exit(128 + SIGINT)) + timer = QTimer() + timer.start(500) + timer.timeout.connect(lambda: None) + self._timers.append(timer) + def _recover_pages(self): """Try to recover all open pages. @@ -179,6 +241,15 @@ class QuteBrowser(QApplication): pass return pages + def _save_geometry(self): + """Save the window geometry to the state config.""" + geom = b64encode(bytes(self.mainwindow.saveGeometry())).decode('ASCII') + try: + config.state.add_section('geometry') + except configparser.DuplicateSectionError: + pass + config.state['geometry']['mainwindow'] = geom + def _exception_hook(self, exctype, excvalue, tb): """Handle uncaught python exceptions. @@ -246,123 +317,6 @@ class QuteBrowser(QApplication): logging.debug("maybe_quit quitting.") self.quit() - def _python_hacks(self): - """Get around some PyQt-oddities by evil hacks. - - This sets up the uncaught exception hook, quits with an appropriate - exit status, and handles Ctrl+C properly by passing control to the - Python interpreter once all 500ms. - - """ - signal(SIGINT, lambda *args: self.exit(128 + SIGINT)) - timer = QTimer() - timer.start(500) - timer.timeout.connect(lambda: None) - self._timers.append(timer) - - def _parseopts(self): - """Parse command line options.""" - parser = ArgumentParser("usage: %(prog)s [options]") - parser.add_argument('-l', '--log', dest='loglevel', - help='Set loglevel', default='info') - parser.add_argument('-c', '--confdir', help='Set config directory ' - '(empty for no config storage)') - parser.add_argument('-d', '--debug', help='Turn on debugging options.', - action='store_true') - parser.add_argument('command', nargs='*', help='Commands to execute ' - 'on startup.', metavar=':command') - # URLs will actually be in command - parser.add_argument('url', nargs='*', help='URLs to open on startup.') - return parser.parse_args() - - def _initlog(self): - """Initialisation of the logging output.""" - loglevel = 'debug' if self._args.debug else self._args.loglevel - numeric_level = getattr(logging, loglevel.upper(), None) - if not isinstance(numeric_level, int): - raise ValueError('Invalid log level: {}'.format(loglevel)) - logging.basicConfig( - level=numeric_level, - format='%(asctime)s [%(levelname)s] ' - '[%(module)s:%(funcName)s:%(lineno)s] %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') - - def _initmisc(self): - """Initialize misc things.""" - if self._args.debug: - os.environ['QT_FATAL_WARNINGS'] = '1' - self.setApplicationName("qutebrowser") - self.setApplicationVersion(qutebrowser.__version__) - - def _init_cmds(self): - """Initialisation of the qutebrowser commands. - - Registers all commands, connects its signals, and sets up keyparser. - - """ - cmdutils.register_all() - for cmd in cmdutils.cmd_dict.values(): - cmd.signal.connect(self.cmd_handler) - try: - self.keyparser.from_config_sect(config.config['keybind']) - except KeyError: - pass - - @pyqtSlot() - def shutdown(self, do_quit=True): - """Try to shutdown everything cleanly. - - For some reason lastWindowClosing sometimes seem to get emitted twice, - so we make sure we only run once here. - - quit -- Whether to quit after shutting down. - - """ - if self._shutting_down: - return - self._shutting_down = True - logging.debug("Shutting down... (do_quit={})".format(do_quit)) - try: - config.config.save() - except AttributeError: - logging.exception("Could not save config.") - try: - self._save_geometry() - config.state.save() - except AttributeError: - logging.exception("Could not save window geometry.") - try: - if do_quit: - self.mainwindow.tabs.shutdown_complete.connect( - self.on_tab_shutdown_complete) - else: - self.mainwindow.tabs.shutdown_complete.connect( - functools.partial(self._maybe_quit, 'shutdown')) - self.mainwindow.tabs.shutdown() - except AttributeError: # mainwindow or tabs could still be None - logging.exception("No mainwindow/tabs to shut down.") - if do_quit: - self.quit() - - def _save_geometry(self): - """Save the window geometry to the state config.""" - geom = b64encode(bytes(self.mainwindow.saveGeometry())).decode('ASCII') - try: - config.state.add_section('geometry') - except configparser.DuplicateSectionError: - pass - config.state['geometry']['mainwindow'] = geom - - @pyqtSlot() - def on_tab_shutdown_complete(self): - """Quit application after a shutdown. - - Gets called when all tabs finished shutting down after shutdown(). - - """ - logging.debug("Shutdown complete, quitting.") - self.quit() - @pyqtSlot(tuple) def cmd_handler(self, tpl): """Handle commands and delegate the specific actions. @@ -438,3 +392,49 @@ class QuteBrowser(QApplication): """ raise Exception + + @pyqtSlot() + def shutdown(self, do_quit=True): + """Try to shutdown everything cleanly. + + For some reason lastWindowClosing sometimes seem to get emitted twice, + so we make sure we only run once here. + + quit -- Whether to quit after shutting down. + + """ + if self._shutting_down: + return + self._shutting_down = True + logging.debug("Shutting down... (do_quit={})".format(do_quit)) + try: + config.config.save() + except AttributeError: + logging.exception("Could not save config.") + try: + self._save_geometry() + config.state.save() + except AttributeError: + logging.exception("Could not save window geometry.") + try: + if do_quit: + self.mainwindow.tabs.shutdown_complete.connect( + self.on_tab_shutdown_complete) + else: + self.mainwindow.tabs.shutdown_complete.connect( + functools.partial(self._maybe_quit, 'shutdown')) + self.mainwindow.tabs.shutdown() + except AttributeError: # mainwindow or tabs could still be None + logging.exception("No mainwindow/tabs to shut down.") + if do_quit: + self.quit() + + @pyqtSlot() + def on_tab_shutdown_complete(self): + """Quit application after a shutdown. + + Gets called when all tabs finished shutting down after shutdown(). + + """ + logging.debug("Shutdown complete, quitting.") + self.quit() diff --git a/qutebrowser/commands/keys.py b/qutebrowser/commands/keys.py index 5fcc18b2e..0ed29624e 100644 --- a/qutebrowser/commands/keys.py +++ b/qutebrowser/commands/keys.py @@ -62,35 +62,6 @@ class KeyParser(QObject): self._bindings = {} self._modifier_bindings = {} - def from_config_sect(self, sect): - """Load keybindings from a ConfigParser section. - - Config format: key = command, e.g.: - gg = scrollstart - - """ - for (key, cmd) in sect.items(): - if key.startswith('@') and key.endswith('@'): - # normalize keystring - keystr = self._normalize_keystr(key.strip('@')) - logging.debug('registered mod key: {} -> {}'.format(keystr, - cmd)) - self._modifier_bindings[keystr] = cmd - else: - logging.debug('registered key: {} -> {}'.format(key, cmd)) - self._bindings[key] = cmd - - def handle(self, e): - """Handle a new keypress and call the respective handlers. - - e -- the KeyPressEvent from Qt - - """ - handled = self._handle_modifier_key(e) - if not handled: - self._handle_single_key(e) - self.keystring_updated.emit(self._keystring) - def _handle_modifier_key(self, e): """Handle a new keypress with modifiers. @@ -239,3 +210,32 @@ class KeyParser(QObject): cmdstr)) self.set_cmd_text.emit(':{} '.format(cmdstr)) return + + def from_config_sect(self, sect): + """Load keybindings from a ConfigParser section. + + Config format: key = command, e.g.: + gg = scrollstart + + """ + for (key, cmd) in sect.items(): + if key.startswith('@') and key.endswith('@'): + # normalize keystring + keystr = self._normalize_keystr(key.strip('@')) + logging.debug('registered mod key: {} -> {}'.format(keystr, + cmd)) + self._modifier_bindings[keystr] = cmd + else: + logging.debug('registered key: {} -> {}'.format(key, cmd)) + self._bindings[key] = cmd + + def handle(self, e): + """Handle a new keypress and call the respective handlers. + + e -- the KeyPressEvent from Qt + + """ + handled = self._handle_modifier_key(e) + if not handled: + self._handle_single_key(e) + self.keystring_updated.emit(self._keystring) diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index 4e20bfeec..f63c3f11d 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -69,24 +69,6 @@ class SearchParser(QObject): self._flags = 0 super().__init__(parent) - @pyqtSlot(str) - def search(self, text): - """Search for a text on a website. - - text -- The text to search for. - - """ - self._search(text) - - @pyqtSlot(str) - def search_rev(self, text): - """Search for a text on a website in reverse direction. - - text -- The text to search for. - - """ - self._search(text, rev=True) - def _search(self, text, rev=False): """Search for a text on the current page. @@ -106,6 +88,24 @@ class SearchParser(QObject): self._flags |= QWebPage.FindBackward self.do_search.emit(self._text, self._flags) + @pyqtSlot(str) + def search(self, text): + """Search for a text on a website. + + text -- The text to search for. + + """ + self._search(text) + + @pyqtSlot(str) + def search_rev(self, text): + """Search for a text on a website in reverse direction. + + text -- The text to search for. + + """ + self._search(text, rev=True) + def nextsearch(self, count=1): """Continue the search to the ([count]th) next term.""" if self._text is not None: diff --git a/qutebrowser/models/completion.py b/qutebrowser/models/completion.py index 7d2ab64a4..695e980a7 100644 --- a/qutebrowser/models/completion.py +++ b/qutebrowser/models/completion.py @@ -39,17 +39,6 @@ class CompletionModel(QAbstractItemModel): self._root = CompletionItem([""] * 2) self._id_map[id(self._root)] = self._root - def removeRows(self, position=0, count=1, parent=QModelIndex()): - """Remove rows from the model. - - Override QAbstractItemModel::removeRows. - - """ - node = self._node(parent) - self.beginRemoveRows(parent, position, position + count - 1) - node.children.pop(position) - self.endRemoveRows() - def _node(self, index): """Return the interal data representation for index. @@ -62,6 +51,60 @@ class CompletionModel(QAbstractItemModel): else: return self._root + def _get_marks(self, needle, haystack): + """Return the marks for needle in haystack.""" + pos1 = pos2 = 0 + marks = [] + if not needle: + return marks + while True: + pos1 = haystack.find(needle, pos2) + if pos1 == -1: + break + pos2 = pos1 + len(needle) + marks.append((pos1, pos2)) + return marks + + def mark_all_items(self, needle): + """Mark a string in all items (children of root-children). + + needle -- The string to mark. + + """ + for i in range(self.rowCount()): + cat = self.index(i, 0) + for k in range(self.rowCount(cat)): + idx = self.index(k, 0, cat) + old = self.data(idx).value() + marks = self._get_marks(needle, old) + self.setData(idx, marks, Qt.UserRole) + + def init_data(self, data): + """Initialize the Qt model based on the data given. + + data -- dict of data to process. + + """ + for (cat, items) in data.items(): + newcat = CompletionItem([cat], self._root) + self._id_map[id(newcat)] = newcat + self._root.children.append(newcat) + for item in items: + newitem = CompletionItem(item, newcat) + self._id_map[id(newitem)] = newitem + newcat.children.append(newitem) + + def removeRows(self, position=0, count=1, parent=QModelIndex()): + """Remove rows from the model. + + Override QAbstractItemModel::removeRows. + + """ + node = self._node(parent) + self.beginRemoveRows(parent, position, position + count - 1) + node.children.pop(position) + self.endRemoveRows() + def columnCount(self, parent=QModelIndex()): """Return the column count in the model. @@ -71,6 +114,23 @@ class CompletionModel(QAbstractItemModel): # pylint: disable=unused-argument return self._root.column_count() + def rowCount(self, parent=QModelIndex()): + """Return the children count of an item. + + Use the root frame if parent is invalid. + Override QAbstractItemModel::rowCount. + + """ + if parent.column() > 0: + return 0 + + if not parent.isValid(): + pitem = self._root + else: + pitem = self._id_map[parent.internalId()] + + return len(pitem.children) + def data(self, index, role=Qt.DisplayRole): """Return the data for role/index as QVariant. @@ -89,23 +149,6 @@ class CompletionModel(QAbstractItemModel): except (IndexError, ValueError): return QVariant() - def flags(self, index): - """Return the item flags for index. - - Return Qt.NoItemFlags on error. - Override QAbstractItemModel::flags. - - """ - # FIXME categories are not selectable, but moving via arrow keys still - # tries to select them - if not index.isValid(): - return Qt.NoItemFlags - flags = Qt.ItemIsEnabled - if len(self._id_map[index.internalId()].children) > 0: - return flags - else: - return flags | Qt.ItemIsSelectable - def headerData(self, section, orientation, role=Qt.DisplayRole): """Return the header data for role/index as QVariant. @@ -134,6 +177,23 @@ class CompletionModel(QAbstractItemModel): self.dataChanged.emit(index, index) return True + def flags(self, index): + """Return the item flags for index. + + Return Qt.NoItemFlags on error. + Override QAbstractItemModel::flags. + + """ + # FIXME categories are not selectable, but moving via arrow keys still + # tries to select them + if not index.isValid(): + return Qt.NoItemFlags + flags = Qt.ItemIsEnabled + if len(self._id_map[index.internalId()].children) > 0: + return flags + else: + return flags | Qt.ItemIsSelectable + def index(self, row, column, parent=QModelIndex()): """Return the QModelIndex for row/column/parent. @@ -174,23 +234,6 @@ class CompletionModel(QAbstractItemModel): return QModelIndex() return self.createIndex(item.row(), 0, id(item)) - def rowCount(self, parent=QModelIndex()): - """Return the children count of an item. - - Use the root frame if parent is invalid. - Override QAbstractItemModel::rowCount. - - """ - if parent.column() > 0: - return 0 - - if not parent.isValid(): - pitem = self._root - else: - pitem = self._id_map[parent.internalId()] - - return len(pitem.children) - def sort(self, column, order=Qt.AscendingOrder): """Sort the data in column according to order. @@ -200,49 +243,6 @@ class CompletionModel(QAbstractItemModel): """ raise NotImplementedError - def init_data(self, data): - """Initialize the Qt model based on the data given. - - data -- dict of data to process. - - """ - for (cat, items) in data.items(): - newcat = CompletionItem([cat], self._root) - self._id_map[id(newcat)] = newcat - self._root.children.append(newcat) - for item in items: - newitem = CompletionItem(item, newcat) - self._id_map[id(newitem)] = newitem - newcat.children.append(newitem) - - def mark_all_items(self, needle): - """Mark a string in all items (children of root-children). - - needle -- The string to mark. - - """ - for i in range(self.rowCount()): - cat = self.index(i, 0) - for k in range(self.rowCount(cat)): - idx = self.index(k, 0, cat) - old = self.data(idx).value() - marks = self._get_marks(needle, old) - self.setData(idx, marks, Qt.UserRole) - - def _get_marks(self, needle, haystack): - """Return the marks for needle in haystack.""" - pos1 = pos2 = 0 - marks = [] - if not needle: - return marks - while True: - pos1 = haystack.find(needle, pos2) - if pos1 == -1: - break - pos2 = pos1 + len(needle) - marks.append((pos1, pos2)) - return marks - class CompletionItem(): diff --git a/qutebrowser/models/completionfilter.py b/qutebrowser/models/completionfilter.py index 371784e68..c5a7bd954 100644 --- a/qutebrowser/models/completionfilter.py +++ b/qutebrowser/models/completionfilter.py @@ -71,10 +71,21 @@ class CompletionFilterModel(QSortFilterProxyModel): model -- The new source model. """ + # FIXME change this to a property self.setSourceModel(model) self.srcmodel = model self.pattern = '' + def first_item(self): + """Return the first item in the model.""" + cat = self.index(0, 0) + return self.index(0, 0, cat) + + def last_item(self): + """Return the last item in the model.""" + cat = self.index(self.rowCount() - 1, 0) + return self.index(self.rowCount(cat) - 1, 0, cat) + def filterAcceptsRow(self, row, parent): """Custom filter implementation. @@ -120,13 +131,3 @@ class CompletionFilterModel(QSortFilterProxyModel): return False else: return left < right - - def first_item(self): - """Return the first item in the model.""" - cat = self.index(0, 0) - return self.index(0, 0, cat) - - def last_item(self): - """Return the last item in the model.""" - cat = self.index(self.rowCount() - 1, 0) - return self.index(self.rowCount(cat) - 1, 0, cat) diff --git a/qutebrowser/utils/signals.py b/qutebrowser/utils/signals.py index b933c57ad..69ca4ab24 100644 --- a/qutebrowser/utils/signals.py +++ b/qutebrowser/utils/signals.py @@ -64,6 +64,10 @@ class SignalCache(QObject): self._uncached = uncached self._signal_dict = OrderedDict() + def _signal_needs_caching(self, signal): + """Return True if a signal should be cached, false otherwise.""" + return not signal_name(signal) in self._uncached + def add(self, sig, args): """Add a new signal to the signal cache. @@ -88,7 +92,3 @@ class SignalCache(QObject): for (signal, args) in self._signal_dict.values(): logging.debug('emitting {}'.format(dbg_signal(signal, args))) signal.emit(*args) - - def _signal_needs_caching(self, signal): - """Return True if a signal should be cached, false otherwise.""" - return not signal_name(signal) in self._uncached diff --git a/qutebrowser/utils/url.py b/qutebrowser/utils/url.py index 68069bc93..acc5f154c 100644 --- a/qutebrowser/utils/url.py +++ b/qutebrowser/utils/url.py @@ -27,6 +27,52 @@ from PyQt5.QtCore import QUrl import qutebrowser.utils.config as config +def _get_search_url(txt): + """Return a search engine URL (QUrl) for a text.""" + logging.debug('Finding search engine for "{}"'.format(txt)) + r = re.compile(r'(^|\s+)!(\w+)($|\s+)') + m = r.search(txt) + if m: + engine = m.group(2) + # FIXME why doesn't fallback work?! + template = config.config.get('searchengines', engine, fallback=None) + term = r.sub('', txt) + logging.debug('engine {}, term "{}"'.format(engine, term)) + else: + template = config.config.get('searchengines', 'DEFAULT', + fallback=None) + term = txt + logging.debug('engine: default, term "{}"'.format(txt)) + if template is None or not term: + raise ValueError + # pylint: disable=maybe-no-member + return QUrl.fromUserInput(template.format(urllib.parse.quote(term))) + + +def _is_url_naive(url): + """Naive check if given url (QUrl) is really an url.""" + PROTOCOLS = ['http://', 'https://'] + u = urlstring(url) + return (any(u.startswith(proto) for proto in PROTOCOLS) or '.' in u or + u == 'localhost') + + +def _is_url_dns(url): + """Check if an url (QUrl) is really an url via DNS.""" + # FIXME we could probably solve this in a nicer way by attempting to open + # the page in the webview, and then open the search if that fails. + host = url.host() + logging.debug("DNS request for {}".format(host)) + if not host: + return False + try: + socket.gethostbyname(host) + except socket.gaierror: + return False + else: + return True + + def qurl(url): """Get a QUrl from an url string.""" return url if isinstance(url, QUrl) else QUrl(url) @@ -64,28 +110,6 @@ def fuzzy_url(url): return newurl -def _get_search_url(txt): - """Return a search engine URL (QUrl) for a text.""" - logging.debug('Finding search engine for "{}"'.format(txt)) - r = re.compile(r'(^|\s+)!(\w+)($|\s+)') - m = r.search(txt) - if m: - engine = m.group(2) - # FIXME why doesn't fallback work?! - template = config.config.get('searchengines', engine, fallback=None) - term = r.sub('', txt) - logging.debug('engine {}, term "{}"'.format(engine, term)) - else: - template = config.config.get('searchengines', 'DEFAULT', - fallback=None) - term = txt - logging.debug('engine: default, term "{}"'.format(txt)) - if template is None or not term: - raise ValueError - # pylint: disable=maybe-no-member - return QUrl.fromUserInput(template.format(urllib.parse.quote(term))) - - def is_about_url(url): """Return True if url (QUrl) is an about:... or other special URL.""" return urlstring(url).replace('http://', '').startswith('about:') @@ -128,27 +152,3 @@ def is_url(url): return _is_url_naive(url) else: raise ValueError("Invalid autosearch value") - - -def _is_url_naive(url): - """Naive check if given url (QUrl) is really an url.""" - PROTOCOLS = ['http://', 'https://'] - u = urlstring(url) - return (any(u.startswith(proto) for proto in PROTOCOLS) or '.' in u or - u == 'localhost') - - -def _is_url_dns(url): - """Check if an url (QUrl) is really an url via DNS.""" - # FIXME we could probably solve this in a nicer way by attempting to open - # the page in the webview, and then open the search if that fails. - host = url.host() - logging.debug("DNS request for {}".format(host)) - if not host: - return False - try: - socket.gethostbyname(host) - except socket.gaierror: - return False - else: - return True diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 93a5fd147..3b518205d 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -28,6 +28,29 @@ from PyQt5.QtWebKit import qWebKitVersion import qutebrowser +def _git_str(): + """Try to find out git version and return a string if possible. + + Return None if there was an error or we're not in a git repo. + + """ + if hasattr(sys, "frozen"): + return None + try: + gitpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), + os.path.pardir, os.path.pardir) + except NameError: + return None + if not os.path.isdir(os.path.join(gitpath, ".git")): + return None + try: + return subprocess.check_output( + ['git', 'describe', '--tags', '--dirty', '--always'], + cwd=gitpath).decode('UTF-8').strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return None + + def version(): """Return a string with various version informations.""" if sys.platform == 'linux': @@ -56,26 +79,3 @@ def version(): lines.append('\nGit commit: {}'.format(gitver)) return ''.join(lines) - - -def _git_str(): - """Try to find out git version and return a string if possible. - - Return None if there was an error or we're not in a git repo. - - """ - if hasattr(sys, "frozen"): - return None - try: - gitpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), - os.path.pardir, os.path.pardir) - except NameError: - return None - if not os.path.isdir(os.path.join(gitpath, ".git")): - return None - try: - return subprocess.check_output( - ['git', 'describe', '--tags', '--dirty', '--always'], - cwd=gitpath).decode('UTF-8').strip() - except (subprocess.CalledProcessError, FileNotFoundError): - return None From 034f85b1d478c10a07d71294f8d60cbdbfafc447 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Feb 2014 17:57:58 +0100 Subject: [PATCH 26/26] Use property for CompletionFilterModel.srcmodel --- qutebrowser/models/completionfilter.py | 14 ++++++++++---- qutebrowser/widgets/completion.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/qutebrowser/models/completionfilter.py b/qutebrowser/models/completionfilter.py index c5a7bd954..6052bb045 100644 --- a/qutebrowser/models/completionfilter.py +++ b/qutebrowser/models/completionfilter.py @@ -31,13 +31,13 @@ class CompletionFilterModel(QSortFilterProxyModel): Attributes: _pattern: The pattern to filter with, used in pattern property. - srcmodel: The source model. + _srcmodel: The source model, accessed via the srcmodel property. """ def __init__(self, parent=None): super().__init__(parent) - self.srcmodel = None + self._srcmodel = None self._pattern = '' @property @@ -65,7 +65,13 @@ class CompletionFilterModel(QSortFilterProxyModel): self.sort(sortcol) self.invalidate() - def setsrc(self, model): + @property + def srcmodel(self): + """Getter for srcmodel.""" + return self._srcmodel + + @srcmodel.setter + def srcmodel(self, model): """Set a new source model and clear the pattern. model -- The new source model. @@ -73,7 +79,7 @@ class CompletionFilterModel(QSortFilterProxyModel): """ # FIXME change this to a property self.setSourceModel(model) - self.srcmodel = model + self._srcmodel = model self.pattern = '' def first_item(self): diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index e0cd87c58..9035f7569 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -129,7 +129,7 @@ class CompletionView(QTreeView): model -- A QAbstractItemModel with available completions. """ - self.model.setsrc(self._completion_models[model]) + self.model.srcmodel = self._completion_models[model] self.expandAll() self.resizeColumnToContents(0)