def _tab_layout(self, opt): """Compute the text/icon rect from the opt rect. This is based on Qt's QCommonStylePrivate::tabLayout (qtbase/src/widgets/styles/qcommonstyle.cpp) as we can't use the private implementation. Args: opt: QStyleOptionTab Return: A (text_rect, icon_rect) tuple (both QRects). """ padding = self.pixelMetric(PM_TabBarPadding, opt) icon_rect = QRect() text_rect = QRect(opt.rect) qtutils.ensure_valid(text_rect) indicator_width = config.get('tabs', 'indicator-width') text_rect.adjust(padding, 0, 0, 0) if indicator_width != 0: text_rect.adjust(indicator_width + config.get('tabs', 'indicator-space'), 0, 0, 0) if not opt.icon.isNull(): icon_rect = self._get_icon_rect(opt, text_rect) text_rect.adjust(icon_rect.width() + padding, 0, 0, 0) text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect) return (text_rect, icon_rect)
def createRequest(self, _op, request, _outgoing_data): """Create a new request. Args: request: const QNetworkRequest & req _op: Operation op _outgoing_data: QIODevice * outgoingData Return: A QNetworkReply. """ try: mimetype, data = qutescheme.data_for_url(request.url()) except qutescheme.NoHandlerFound: errorstr = "No handler found for {}!".format( request.url().toDisplayString()) return networkreply.ErrorNetworkReply( request, errorstr, QNetworkReply.ContentNotFoundError, self.parent()) except qutescheme.QuteSchemeOSError as e: return networkreply.ErrorNetworkReply( request, str(e), QNetworkReply.ContentNotFoundError, self.parent()) except qutescheme.QuteSchemeError as e: return networkreply.ErrorNetworkReply(request, e.errorstring, e.error, self.parent()) except qutescheme.Redirect as e: qtutils.ensure_valid(e.url) return networkreply.RedirectNetworkReply(e.url, self.parent()) return networkreply.FixedDataNetworkReply(request, data, mimetype, self.parent())
def resolve_url(self, baseurl): """Resolve the URL in the element's src/href attribute. Args: baseurl: The URL to base relative URLs on as QUrl. Return: A QUrl with the absolute URL, or None. """ if baseurl.isRelative(): raise ValueError("Need an absolute base URL!") for attr in ['href', 'src']: if attr in self: text = self[attr].strip() break else: return None url = QUrl(text) if not url.isValid(): return None if url.isRelative(): url = baseurl.resolved(url) qtutils.ensure_valid(url) return url
def _get_icon_rect(self, opt, text_rect): """Get a QRect for the icon to draw. Args: opt: QStyleOptionTab text_rect: The QRect for the text. Return: A QRect. """ icon_size = opt.iconSize if not icon_size.isValid(): icon_extent = self.pixelMetric(QStyle.PM_SmallIconSize) icon_size = QSize(icon_extent, icon_extent) icon_mode = (QIcon.Normal if opt.state & QStyle.State_Enabled else QIcon.Disabled) icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) tab_icon_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) tab_icon_size = QSize(min(tab_icon_size.width(), icon_size.width()), min(tab_icon_size.height(), icon_size.height())) icon_rect = QRect(text_rect.left(), text_rect.center().y() - tab_icon_size.height() / 2, tab_icon_size.width(), tab_icon_size.height()) icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect) qtutils.ensure_valid(icon_rect) return icon_rect
def handler(request): """Scheme handler for qute:// URLs. Args: request: QNetworkRequest to answer to. Return: A QNetworkReply. """ try: mimetype, data = qutescheme.data_for_url(request.url()) except qutescheme.NoHandlerFound: errorstr = "No handler found for {}!".format( request.url().toDisplayString()) return networkreply.ErrorNetworkReply( request, errorstr, QNetworkReply.ContentNotFoundError) except qutescheme.QuteSchemeOSError as e: return networkreply.ErrorNetworkReply( request, str(e), QNetworkReply.ContentNotFoundError) except qutescheme.QuteSchemeError as e: return networkreply.ErrorNetworkReply(request, e.errorstring, e.error) except qutescheme.Redirect as e: qtutils.ensure_valid(e.url) return networkreply.RedirectNetworkReply(e.url) return networkreply.FixedDataNetworkReply(request, data, mimetype)
def resolve(self, query, from_file=False): """Resolve a proxy via PAC. Args: query: QNetworkProxyQuery. from_file: Whether the proxy info is coming from a file. Return: A list of QNetworkProxy objects in order of preference. """ qtutils.ensure_valid(query.url()) if from_file: string_flags = QUrl.PrettyDecoded else: string_flags = QUrl.RemoveUserInfo if query.url().scheme() == 'https': string_flags |= QUrl.RemovePath | QUrl.RemoveQuery result = self._resolver.call([query.url().toString(string_flags), query.peerHostName()]) result_str = result.toString() if not result.isString(): err = "Got strange value from FindProxyForURL: '{}'" raise EvalProxyError(err.format(result_str)) return self._parse_proxy_string(result_str)
def requestStarted(self, job): """Handle a request for a qute: scheme. This method must be reimplemented by all custom URL scheme handlers. The request is asynchronous and does not need to be handled right away. Args: job: QWebEngineUrlRequestJob """ url = job.requestUrl() if url.scheme() in ['chrome-error', 'chrome-extension']: # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378 job.fail(QWebEngineUrlRequestJob.UrlInvalid) return if not self._check_initiator(job): return if job.requestMethod() != b'GET': job.fail(QWebEngineUrlRequestJob.RequestDenied) return assert url.scheme() == 'qute' log.misc.debug("Got request for {}".format(url.toDisplayString())) try: mimetype, data = qutescheme.data_for_url(url) except qutescheme.Error as e: errors = { qutescheme.NotFoundError: QWebEngineUrlRequestJob.UrlNotFound, qutescheme.UrlInvalidError: QWebEngineUrlRequestJob.UrlInvalid, qutescheme.RequestDeniedError: QWebEngineUrlRequestJob.RequestDenied, qutescheme.SchemeOSError: QWebEngineUrlRequestJob.UrlNotFound, qutescheme.Error: QWebEngineUrlRequestJob.RequestFailed, } exctype = type(e) log.misc.error("{} while handling qute://* URL".format( exctype.__name__)) job.fail(errors[exctype]) except qutescheme.Redirect as e: qtutils.ensure_valid(e.url) job.redirect(e.url) else: log.misc.debug("Returning {} data".format(mimetype)) # We can't just use the QBuffer constructor taking a QByteArray, # because that somehow segfaults... # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html buf = QBuffer(parent=self) buf.open(QIODevice.WriteOnly) buf.write(data) buf.seek(0) buf.close() job.reply(mimetype.encode('ascii'), buf)
def sizeHint(self, option, index): """Override sizeHint of QStyledItemDelegate. Return the cell size based on the QTextDocument size, but might not work correctly yet. Args: option: const QStyleOptionViewItem & option index: const QModelIndex & index Return: A QSize with the recommended size. """ 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._get_textdoc(index) docsize = self._doc.size().toSize() size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt, docsize, self._opt.widget) qtutils.ensure_valid(size) return size + QSize(10, 3)
def follow_prevnext(self, frame, baseurl, prev=False, tab=False, background=False, window=False): """Click a "previous"/"next" element on the page. Args: frame: The frame where the element is in. baseurl: The base URL of the current tab. prev: True to open a "previous" link, False to open a "next" link. tab: True to open in a new tab, False for the current tab. background: True to open in a background tab. window: True to open in a new window, False for the current one. """ from qutebrowser.mainwindow import mainwindow elem = self._find_prevnext(frame, prev) if elem is None: raise cmdexc.CommandError("No {} links found!".format("prev" if prev else "forward")) url = self._resolve_url(elem, baseurl) if url is None: raise cmdexc.CommandError("No {} links found!".format("prev" if prev else "forward")) qtutils.ensure_valid(url) if window: new_window = mainwindow.MainWindow() new_window.show() tabbed_browser = objreg.get("tabbed-browser", scope="window", window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: tabbed_browser = objreg.get("tabbed-browser", scope="window", window=self._win_id) tabbed_browser.tabopen(url, background=background) else: webview = objreg.get("webview", scope="tab", window=self._win_id, tab=self._tab_id) webview.openurl(url)
def _remove_tab(self, tab): """Remove a tab from the tab list and delete it properly. Args: tab: The QWebView to be closed. Raise: ValueError if the tab is not in the QTabWidget. """ idx = self.indexOf(tab) if idx == -1: raise ValueError("tab {} is not contained in TabbedWidget!".format( tab)) if tab is self._now_focused: self._now_focused = None if tab is objreg.get('last-focused-tab', None): objreg.delete('last-focused-tab') if not tab.cur_url.isEmpty(): qtutils.ensure_valid(tab.cur_url) history_data = qtutils.serialize(tab.history()) entry = UndoEntry(tab.cur_url, history_data) self._undo_stack.append(entry) tab.shutdown() self._tabs.remove(tab) self.removeTab(idx) tab.deleteLater()
def current_url(self): """Get the URL of the current tab. Intended to be used from command handlers. Return: The current URL as QUrl. Raise: CommandError if the current URL is invalid. """ widget = self.currentWidget() if widget is None: url = QUrl() else: url = widget.cur_url try: qtutils.ensure_valid(url) except qtutils.QtValueError as e: msg = "Current URL is invalid" if e.reason: msg += " ({})".format(e.reason) msg += "!" raise cmdexc.CommandError(msg) return url
def _prevnext_cb(elems): elem = _find_prevnext(prev, elems) word = 'prev' if prev else 'forward' if elem is None: message.error(win_id, "No {} links found!".format(word)) return url = elem.resolve_url(baseurl) if url is None: message.error(win_id, "No {} links found!".format(word)) return qtutils.ensure_valid(url) if window: from qutebrowser.mainwindow import mainwindow new_window = mainwindow.MainWindow() new_window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) tabbed_browser.tabopen(url, background=background) else: browsertab.openurl(url)
def _get_search_url(txt): """Get a search engine URL for a text. Args: txt: Text to search for. Return: The search URL as a QUrl. """ log.url.debug("Finding search engine for {!r}".format(txt)) engine, term = _parse_search_term(txt) assert term if engine is None: engine = 'DEFAULT' template = config.val.url.searchengines[engine] quoted_term = urllib.parse.quote(term, safe='') url = qurl_from_user_input(template.format(quoted_term)) if config.val.url.open_base_url and term in config.val.url.searchengines: url = qurl_from_user_input(config.val.url.searchengines[term]) url.setPath(None) url.setFragment(None) url.setQuery(None) qtutils.ensure_valid(url) return url
def fuzzy_url(urlstr, cwd=None): """Get a QUrl based on an user input which is URL or search term. Args: urlstr: URL to load as a string. cwd: The current working directory, or None. Return: A target QUrl to a searchpage or the original URL. """ if cwd: path = os.path.join(cwd, os.path.expanduser(urlstr)) else: try: path = os.path.abspath(os.path.expanduser(urlstr)) except OSError: path = None stripped = urlstr.strip() if path is not None and os.path.exists(path): log.url.debug("URL is a local file") url = QUrl.fromLocalFile(path) elif is_url(stripped): # probably an address log.url.debug("URL is a fuzzy address") url = qurl_from_user_input(urlstr) else: # probably a search term log.url.debug("URL is a fuzzy search term") try: url = _get_search_url(urlstr) except ValueError: # invalid search engine url = qurl_from_user_input(stripped) log.url.debug("Converting fuzzy term {} to URL -> {}".format( urlstr, url.toDisplayString())) qtutils.ensure_valid(url) return url
def fuzzy_url(urlstr): """Get a QUrl based on an user input which is URL or search term. Args: urlstr: URL to load as a string. Return: A target QUrl to a searchpage or the original URL. """ path = os.path.abspath(os.path.expanduser(urlstr)) stripped = urlstr.strip() if os.path.exists(path): log.url.debug("URL is a local file") url = QUrl.fromLocalFile(path) elif (not _has_explicit_scheme(QUrl(urlstr)) and os.path.exists(os.path.abspath(path))): # We do this here rather than in the first block because we first want # to make sure it's not an URL like http://, because os.path.abspath # would mangle that. log.url.debug("URL is a relative local file") url = QUrl.fromLocalFile(os.path.abspath(path)) elif is_url(stripped): # probably an address log.url.debug("URL is a fuzzy address") url = qurl_from_user_input(urlstr) else: # probably a search term log.url.debug("URL is a fuzzy search term") try: url = _get_search_url(urlstr) except ValueError: # invalid search engine url = qurl_from_user_input(stripped) log.url.debug("Converting fuzzy term {} to URL -> {}".format( urlstr, url.toDisplayString())) qtutils.ensure_valid(url) return url
def _get_search_url(txt): """Get a search engine URL for a text. Args: txt: Text to search for. Return: The search URL as a QUrl. """ log.url.debug("Finding search engine for '{}'".format(txt)) r = re.compile(r'(^\w+)\s+(.+)($|\s+)') m = r.search(txt) if m: engine = m.group(1) try: template = config.get('searchengines', engine) except configexc.NoOptionError: template = config.get('searchengines', 'DEFAULT') term = txt else: term = m.group(2).rstrip() log.url.debug("engine {}, term '{}'".format(engine, term)) else: template = config.get('searchengines', 'DEFAULT') term = txt log.url.debug("engine: default, term '{}'".format(txt)) if not term: raise FuzzyUrlError("No search term given") url = qurl_from_user_input(template.format(urllib.parse.quote(term))) qtutils.ensure_valid(url) return url
def lessThan(self, lindex, rindex): """Custom sorting implementation. Prefers all items which start with self._pattern. Other than that, uses normal Python string sorting. Args: lindex: The QModelIndex of the left item (*left* < right) rindex: The QModelIndex of the right item (left < *right*) Return: True if left < right, else False """ qtutils.ensure_valid(lindex) qtutils.ensure_valid(rindex) left = self.srcmodel.data(lindex) right = self.srcmodel.data(rindex) leftstart = left.startswith(self._pattern) rightstart = right.startswith(self._pattern) if leftstart and not rightstart: return True elif rightstart and not leftstart: return False elif self._sort: return left < right else: return False
def _prevnext_cb(elems): if elems is None: message.error("There was an error while getting hint elements") return elem = _find_prevnext(prev, elems) word = 'prev' if prev else 'forward' if elem is None: message.error("No {} links found!".format(word)) return url = elem.resolve_url(baseurl) if url is None: message.error("No {} links found!".format(word)) return qtutils.ensure_valid(url) cur_tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) if window: new_window = mainwindow.MainWindow( private=cur_tabbed_browser.private) new_window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: cur_tabbed_browser.tabopen(url, background=background) else: browsertab.openurl(url)
def tabopen(self, url=None, background=None, explicit=False): """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. Also connect all the signals we need to _filter_signals. Args: url: The URL to open as QUrl or None for an empty tab. background: Whether to open the tab in the background. if None, the background-tabs setting decides. explicit: Whether the tab was opened explicitely. If this is set, the new position might be different. With the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current. - Explicitely opened tabs are at the very right. Return: The opened WebView instance. """ if url is not None: qtutils.ensure_valid(url) log.webview.debug("Creating new tab with URL {}".format(url)) tab = webview.WebView(self) self._connect_tab_signals(tab) self._tabs.append(tab) if explicit: pos = config.get('tabs', 'new-tab-position-explicit') else: pos = config.get('tabs', 'new-tab-position') if pos == 'left': idx = self._tab_insert_idx_left # On first sight, we'd think we have to decrement # self._tab_insert_idx_left here, as we want the next tab to be # *before* the one we just opened. However, since we opened a tab # *to the left* of the currently focused tab, indices will shift by # 1 automatically. elif pos == 'right': idx = self._tab_insert_idx_right self._tab_insert_idx_right += 1 elif pos == 'first': idx = 0 elif pos == 'last': idx = -1 else: raise ValueError("Invalid new-tab-position '{}'.".format(pos)) log.webview.debug("new-tab-position {} -> opening new tab at {}, " "next left: {} / right: {}".format( pos, idx, self._tab_insert_idx_left, self._tab_insert_idx_right)) self.insertTab(idx, tab, "") if url is not None: tab.openurl(url) if background is None: background = config.get('tabs', 'background-tabs') if not background: self.setCurrentWidget(tab) tab.show() return tab
def tabopen(self, url=None, background=None, explicit=False, idx=None, *, ignore_tabs_are_windows=False): """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. Also connect all the signals we need to _filter_signals. Args: url: The URL to open as QUrl or None for an empty tab. background: Whether to open the tab in the background. if None, the background-tabs setting decides. explicit: Whether the tab was opened explicitly. If this is set, the new position might be different. With the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current. - Explicitly opened tabs are at the very right. idx: The index where the new tab should be opened. ignore_tabs_are_windows: If given, never open a new window, even with tabs-are-windows set. Return: The opened WebView instance. """ if url is not None: qtutils.ensure_valid(url) log.webview.debug("Creating new tab with URL {}, background {}, " "explicit {}, idx {}".format( url, background, explicit, idx)) if (config.get('tabs', 'tabs-are-windows') and self.count() > 0 and not ignore_tabs_are_windows): from qutebrowser.mainwindow import mainwindow window = mainwindow.MainWindow(private=self.private) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) return tabbed_browser.tabopen(url, background, explicit) tab = browsertab.create(win_id=self._win_id, private=self.private, parent=self) self._connect_tab_signals(tab) if idx is None: idx = self._get_new_tab_idx(explicit) self.insertTab(idx, tab, "") if url is not None: tab.openurl(url) if background is None: background = config.get('tabs', 'background-tabs') if background: self.tab_index_changed.emit(self.currentIndex(), self.count()) else: self.setCurrentWidget(tab) tab.show() self.new_tab.emit(tab, idx) return tab
def first_item(self): """Return the index of the first child (non-category) in the model.""" for row, cat in enumerate(self._categories): if cat.rowCount() > 0: parent = self.index(row, 0) index = self.index(0, 0, parent) qtutils.ensure_valid(index) return index return QModelIndex()
def first_item(self): """Return the first item in the model.""" for i in range(self.rowCount()): cat = self.index(i, 0) qtutils.ensure_valid(cat) if cat.model().hasChildren(cat): index = self.index(0, 0, cat) qtutils.ensure_valid(index) return index return QModelIndex()
def connect_log_slot(obj): """Helper function to connect all signals to a logging slot.""" metaobj = obj.metaObject() for i in range(metaobj.methodCount()): meta_method = metaobj.method(i) qtutils.ensure_valid(meta_method) if meta_method.methodType() == QMetaMethod.Signal: name = bytes(meta_method.name()).decode('ascii') signal = getattr(obj, name) signal.connect(functools.partial(log_slot, obj, signal))
def last_item(self): """Return the index of the last child (non-category) in the model.""" for row, cat in reversed(list(enumerate(self._categories))): childcount = cat.rowCount() if childcount > 0: parent = self.index(row, 0) index = self.index(childcount - 1, 0, parent) qtutils.ensure_valid(index) return index return QModelIndex()
def _preset_cmd_text(self, url): """Preset a commandline text based on a hint URL. Args: url: The URL to open as a QUrl. """ qtutils.ensure_valid(url) urlstr = url.toDisplayString(QUrl.FullyEncoded) args = self._context.get_args(urlstr) message.set_cmd_text(' '.join(args))
def sizeHint(self): """Return sizeHint based on the view contents.""" idx = self.model().last_index() height = self.visualRect(idx).bottom() if height != -1: size = QSize(0, height + 2) else: size = QSize(0, 0) qtutils.ensure_valid(size) return size
def on_data_changed(self, download): """Emit data_changed signal when download data changed.""" try: idx = self.downloads.index(download) except ValueError: # download has been deleted in the meantime return model_idx = self.index(idx, 0) qtutils.ensure_valid(model_idx) self.dataChanged.emit(model_idx, model_idx)
def prompt_save(url): """Prompt for a new quickmark name to be added and add it. Args: url: The quickmark url as a QUrl. """ qtutils.ensure_valid(url) urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) message.ask_async("Add quickmark:", usertypes.PromptMode.text, functools.partial(quickmark_add, urlstr))
def delete_url(self, url): """Remove all history entries with the given url. Args: url: URL string to delete. """ qurl = QUrl(url) qtutils.ensure_valid(qurl) self.delete('url', self._format_url(qurl)) self.completion.delete('url', self._format_completion_url(qurl))
def tabSizeHint(self, index: int): """Override tabSizeHint to customize qb's tab size. https://wiki.python.org/moin/PyQt/Customising%20tab%20bars Args: index: The index of the tab. Return: A QSize. """ minimum_size = self.minimumTabSizeHint(index) height = minimum_size.height() if self.vertical: confwidth = str(config.val.tabs.width.bar) if confwidth.endswith('%'): main_window = objreg.get('main-window', scope='window', window=self._win_id) perc = int(confwidth.rstrip('%')) width = main_window.width() * perc / 100 else: width = int(confwidth) size = QSize(max(minimum_size.width(), width), height) elif self.count() == 0: # This happens on startup on macOS. # We return it directly rather than setting `size' because we don't # want to ensure it's valid in this special case. return QSize() else: pinned = self._tab_pinned(index) pinned_count, pinned_width = self._pinned_statistics() no_pinned_count = self.count() - pinned_count no_pinned_width = self.width() - pinned_width if pinned: # Give pinned tabs the minimum size they need to display their # titles, let Qt handle scaling it down if we get too small. width = self.minimumTabSizeHint(index, ellipsis=False).width() else: width = no_pinned_width / no_pinned_count # If no_pinned_width is not divisible by no_pinned_count, add a # pixel to some tabs so that there is no ugly leftover space. if (no_pinned_count > 0 and index < no_pinned_width % no_pinned_count): width += 1 # If we don't have enough space, we return the minimum size so we # get scroll buttons as soon as needed. width = max(width, minimum_size.width()) size = QSize(width, height) qtutils.ensure_valid(size) return size
def _get_search_url(txt): """Get a search engine URL for a text. Args: txt: Text to search for. Return: The search URL as a QUrl. """ log.url.debug("Finding search engine for {!r}".format(txt)) engine, term = _parse_search_term(txt) assert term if engine is None: engine = 'DEFAULT' template = config.val.url.searchengines[engine] url = qurl_from_user_input(template.format(urllib.parse.quote(term))) if config.val.url.open_base_url and term in config.val.url.searchengines: url = qurl_from_user_input(config.val.url.searchengines[term]) url.setPath(None) url.setFragment(None) url.setQuery(None) qtutils.ensure_valid(url) return url
def incdec(url, count, inc_or_dec): """Helper method for :navigate when `where' is increment/decrement. Args: url: The current url. count: How much to increment or decrement by. inc_or_dec: Either 'increment' or 'decrement'. tab: Whether to open the link in a new tab. background: Open the link in a new background tab. window: Open the link in a new window. """ urlutils.ensure_valid(url) segments: Optional[Set[str]] = (set(config.val.url.incdec_segments)) if segments is None: segments = {'path', 'query'} # Make a copy of the QUrl so we don't modify the original url = QUrl(url) # We're searching the last number so we walk the url segments backwards for segment, getter, setter in reversed(_URL_SEGMENTS): if segment not in segments: continue # Get the last number in a string not preceded by regex '%' or '%.' match = re.fullmatch(r'(.*\D|^)(?<!%)(?<!%.)(0*)(\d+)(.*)', getter(url)) if not match: continue setter(url, _get_incdec_value(match, inc_or_dec, count)) qtutils.ensure_valid(url) return url raise Error("No number found in URL!")
def _save_tab(self, tab, active): """Get a dict with data for a single tab. Args: tab: The WebView to save. active: Whether the tab is currently active. """ data = {'history': []} if active: data['active'] = True history = tab.page().history() for idx, item in enumerate(history.items()): qtutils.ensure_valid(item) item_data = { 'url': bytes(item.url().toEncoded()).decode('ascii'), 'title': item.title(), } if item.originalUrl() != item.url(): encoded = item.originalUrl().toEncoded() item_data['original-url'] = bytes(encoded).decode('ascii') user_data = item.userData() if history.currentItemIndex() == idx: item_data['active'] = True if user_data is None: pos = tab.page().mainFrame().scrollPosition() data['zoom'] = tab.zoomFactor() data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()} data['history'].append(item_data) if user_data is not None: if 'zoom' in user_data: data['zoom'] = user_data['zoom'] if 'scroll-pos' in user_data: pos = user_data['scroll-pos'] data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()} return data
def fuzzy_url(urlstr, cwd=None, relative=False, do_search=True): """Get a QUrl based on a user input which is URL or search term. Args: urlstr: URL to load as a string. cwd: The current working directory, or None. relative: Whether to resolve relative files. do_search: Whether to perform a search on non-URLs. Return: A target QUrl to a search page or the original URL. """ urlstr = urlstr.strip() path = get_path_if_valid(urlstr, cwd=cwd, relative=relative, check_exists=True) if path is not None: url = QUrl.fromLocalFile(path) elif (not do_search) or is_url(urlstr): # probably an address log.url.debug("URL is a fuzzy address") url = qurl_from_user_input(urlstr) else: # probably a search term log.url.debug("URL is a fuzzy search term") try: url = _get_search_url(urlstr) except ValueError: # invalid search engine url = qurl_from_user_input(urlstr) log.url.debug("Converting fuzzy term {!r} to URL -> {}".format( urlstr, url.toDisplayString())) if do_search and config.get('general', 'auto-search') and urlstr: qtutils.ensure_valid(url) else: if not url.isValid(): raise InvalidUrlError(url) return url
def _get_search_url(txt: str) -> QUrl: """Get a search engine URL for a text. Args: txt: Text to search for. Return: The search URL as a QUrl. """ log.url.debug("Finding search engine for {!r}".format(txt)) engine, term = _parse_search_term(txt) if not engine: engine = 'DEFAULT' if term: template = config.val.url.searchengines[engine] quoted_term = urllib.parse.quote(term, safe='') url = qurl_from_user_input(template.format(quoted_term)) else: url = qurl_from_user_input(config.val.url.searchengines[engine]) url.setPath(None) # type: ignore url.setFragment(None) # type: ignore url.setQuery(None) # type: ignore qtutils.ensure_valid(url) return url
def present(self, qt_notification: "QWebEngineNotification") -> None: """Show a notification using the configured adapter. Lazily initializes a suitable adapter if none exists yet. This should *not* be directly passed to setNotificationPresenter on PyQtWebEngine < 5.15 because of a bug in the PyQtWebEngine bindings. """ if self._adapter is None: self._init_adapter() assert self._adapter is not None replaces_id = self._find_replaces_id(qt_notification) qtutils.ensure_valid(qt_notification.origin()) notification_id = self._adapter.present( qt_notification, replaces_id=replaces_id) log.misc.debug(f"New notification ID from adapter: {notification_id}") if self._adapter is None: # If a fatal error occurred, we replace the adapter via its "error" signal. log.misc.debug("Adapter vanished, bailing out") # type: ignore[unreachable] return if notification_id <= 0: raise Error(f"Got invalid notification id {notification_id}") if replaces_id is None: if notification_id in self._active_notifications: raise Error(f"Got duplicate id {notification_id}") qt_notification.show() self._active_notifications[notification_id] = qt_notification qt_notification.closed.connect( # type: ignore[attr-defined] functools.partial(self._adapter.on_web_closed, notification_id))
def _resolve_url(self, elem, baseurl=None): """Resolve a URL and check if we want to keep it. Args: elem: The QWebElement to get the URL of. baseurl: The baseurl of the current tab (overrides baseurl from self._context). Return: A QUrl with the absolute URL, or None. """ try: text = elem['href'] except KeyError: return None url = QUrl(text) if not url.isValid(): return None if url.isRelative(): if baseurl is None: baseurl = self._context.baseurl url = baseurl.resolved(url) qtutils.ensure_valid(url) return url
def _resolve_url(self, elem, baseurl): """Resolve a URL and check if we want to keep it. Args: elem: The QWebElement to get the URL of. baseurl: The baseurl of the current tab. Return: A QUrl with the absolute URL, or None. """ for attr in ('href', 'src'): if attr in elem: text = elem[attr].strip() break else: return None url = QUrl(text) if not url.isValid(): return None if url.isRelative(): url = baseurl.resolved(url) qtutils.ensure_valid(url) return url
def _prevnext_cb(elems): if elems is None: message.error("Unknown error while getting hint elements") return elif isinstance(elems, webelem.Error): message.error(str(elems)) return elem = _find_prevnext(prev, elems) word = 'prev' if prev else 'forward' if elem is None: message.error("No {} links found!".format(word)) return url = elem.resolve_url(baseurl) if url is None: message.error("No {} links found!".format(word)) return qtutils.ensure_valid(url) cur_tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) if window: new_window = mainwindow.MainWindow( private=cur_tabbed_browser.is_private) new_window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: cur_tabbed_browser.tabopen(url, background=background) else: browsertab.openurl(url)
def _save_tab(self, tab, active): """Get a dict with data for a single tab. Args: tab: The WebView to save. active: Whether the tab is currently active. """ # FIXME understand why this happens if tab is None: return {} data = {'history': []} if active: data['active'] = True for idx, item in enumerate(tab.history): qtutils.ensure_valid(item) item_data = self._save_tab_item(tab, idx, item) if item.url().scheme() == 'qute' and item.url().host() == 'back': # don't add qute://back to the session file if item_data.get('active', False) and data['history']: # mark entry before qute://back as active data['history'][-1]['active'] = True else: data['history'].append(item_data) return data
def lessThan(self, lindex, rindex): """Custom sorting implementation. Prefers all items which start with self.pattern. Other than that, uses normal Python string sorting. Args: lindex: The QModelIndex of the left item (*left* < right) rindex: The QModelIndex of the right item (left < *right*) Return: True if left < right, else False """ qtutils.ensure_valid(lindex) qtutils.ensure_valid(rindex) left_sort = self.srcmodel.data(lindex, role=completion.Role.sort) right_sort = self.srcmodel.data(rindex, role=completion.Role.sort) if left_sort is not None and right_sort is not None: return left_sort < right_sort left = self.srcmodel.data(lindex) right = self.srcmodel.data(rindex) leftstart = left.startswith(self.pattern) rightstart = right.startswith(self.pattern) if leftstart and rightstart: return left < right elif leftstart: return True elif rightstart: return False else: return left < right
def follow_prevnext(self, frame, baseurl, prev=False, tab=False, background=False, window=False): """Click a "previous"/"next" element on the page. Args: frame: The frame where the element is in. baseurl: The base URL of the current tab. prev: True to open a "previous" link, False to open a "next" link. tab: True to open in a new tab, False for the current tab. background: True to open in a background tab. window: True to open in a new window, False for the current one. """ from qutebrowser.mainwindow import mainwindow elem = self._find_prevnext(frame, prev) if elem is None: raise cmdexc.CommandError("No {} links found!".format( "prev" if prev else "forward")) url = self._resolve_url(elem, baseurl) if url is None: raise cmdexc.CommandError("No {} links found!".format( "prev" if prev else "forward")) qtutils.ensure_valid(url) if window: new_window = mainwindow.MainWindow() new_window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=new_window.win_id) tabbed_browser.tabopen(url, background=False) elif tab: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) tabbed_browser.tabopen(url, background=background) else: webview = objreg.get('webview', scope='tab', window=self._win_id, tab=self._tab_id) webview.openurl(url)
def interpolate_color( start: QColor, end: QColor, percent: int, colorspace: typing.Optional[QColor.Spec] = QColor.Rgb ) -> QColor: """Get an interpolated color value. Args: start: The start color. end: The end color. percent: Which value to get (0 - 100) colorspace: The desired interpolation color system, QColor::{Rgb,Hsv,Hsl} (from QColor::Spec enum) If None, start is used except when percent is 100. Return: The interpolated QColor, with the same spec as the given start color. """ qtutils.ensure_valid(start) qtutils.ensure_valid(end) if colorspace is None: if percent == 100: return QColor(*end.getRgb()) else: return QColor(*start.getRgb()) out = QColor() if colorspace == QColor.Rgb: a_c1, a_c2, a_c3, _alpha = start.getRgb() b_c1, b_c2, b_c3, _alpha = end.getRgb() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setRgb(*components) elif colorspace == QColor.Hsv: a_c1, a_c2, a_c3, _alpha = start.getHsv() b_c1, b_c2, b_c3, _alpha = end.getHsv() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setHsv(*components) elif colorspace == QColor.Hsl: a_c1, a_c2, a_c3, _alpha = start.getHsl() b_c1, b_c2, b_c3, _alpha = end.getHsl() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setHsl(*components) else: raise ValueError("Invalid colorspace!") out = out.convertTo(start.spec()) qtutils.ensure_valid(out) return out
def delete_cur_item(self, completion): """Delete the selected item. Args: completion: The Completion object to use. """ index = completion.currentIndex() qtutils.ensure_valid(index) url = index.data() category = index.parent() qtutils.ensure_valid(category) if category.data() == 'Bookmarks': bookmark_manager = objreg.get('bookmark-manager') bookmark_manager.delete(url) elif category.data() == 'Quickmarks': quickmark_manager = objreg.get('quickmark-manager') sibling = index.sibling(index.row(), self.TEXT_COLUMN) qtutils.ensure_valid(sibling) name = sibling.data() quickmark_manager.quickmark_del(name)
def interpolate_color(start, end, percent, colorspace=QColor.Rgb): """Get an interpolated color value. Args: start: The start color. end: The end color. percent: Which value to get (0 - 100) colorspace: The desired interpolation colorsystem, QColor::{Rgb,Hsv,Hsl} (from QColor::Spec enum) Return: The interpolated QColor, with the same spec as the given start color. """ qtutils.ensure_valid(start) qtutils.ensure_valid(end) out = QColor() if colorspace == QColor.Rgb: a_c1, a_c2, a_c3, _alpha = start.getRgb() b_c1, b_c2, b_c3, _alpha = end.getRgb() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setRgb(*components) elif colorspace == QColor.Hsv: a_c1, a_c2, a_c3, _alpha = start.getHsv() b_c1, b_c2, b_c3, _alpha = end.getHsv() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setHsv(*components) elif colorspace == QColor.Hsl: a_c1, a_c2, a_c3, _alpha = start.getHsl() b_c1, b_c2, b_c3, _alpha = end.getHsl() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setHsl(*components) else: raise ValueError("Invalid colorspace!") out = out.convertTo(start.spec()) qtutils.ensure_valid(out) return out
def _on_before_load_started(self, url: QUrl) -> None: """Adjust the title if we are going to visit a URL soon.""" qtutils.ensure_valid(url) url_string = url.toDisplayString() log.webview.debug("Going to start loading: {}".format(url_string)) self.title_changed.emit(url_string)
def tabopen( self, url: QUrl = None, background: bool = None, related: bool = True, idx: int = None, ) -> browsertab.AbstractTab: """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. Also connect all the signals we need to _filter_signals. Args: url: The URL to open as QUrl or None for an empty tab. background: Whether to open the tab in the background. if None, the `tabs.background` setting decides. related: Whether the tab was opened from another existing tab. If this is set, the new position might be different. With the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current (related=True). - Explicitly opened tabs are at the very right (related=False) idx: The index where the new tab should be opened. Return: The opened WebView instance. """ if url is not None: qtutils.ensure_valid(url) log.webview.debug("Creating new tab with URL {}, background {}, " "related {}, idx {}".format(url, background, related, idx)) prev_focus = QApplication.focusWidget() if config.val.tabs.tabs_are_windows and self.widget.count() > 0: window = mainwindow.MainWindow(private=self.is_private) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) return tabbed_browser.tabopen(url=url, background=background, related=related) tab = browsertab.create(win_id=self._win_id, private=self.is_private, parent=self.widget) self._connect_tab_signals(tab) if idx is None: idx = self._get_new_tab_idx(related) self.widget.insertTab(idx, tab, "") if url is not None: tab.load_url(url) if background is None: background = config.val.tabs.background if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the # layout anyways. tab.resize(self.widget.currentWidget().size()) self.widget.tab_index_changed.emit(self.widget.currentIndex(), self.widget.count()) # Refocus webview in case we lost it by spawning a bg tab self.widget.currentWidget().setFocus() else: self.widget.setCurrentWidget(tab) mode = modeman.instance(self._win_id).mode if mode in [ usertypes.KeyMode.command, usertypes.KeyMode.prompt, usertypes.KeyMode.yesno ]: # If we were in a command prompt, restore old focus # The above commands need to be run to switch tabs if prev_focus is not None: prev_focus.setFocus() tab.show() self.new_tab.emit(tab, idx) return tab
def _load_url_prepare(self, url: QUrl) -> None: qtutils.ensure_valid(url) self.before_load_started.emit(url)
def tabSizeHint(self, index): """Override tabSizeHint so all tabs are the same size. https://wiki.python.org/moin/PyQt/Customising%20tab%20bars Args: index: The index of the tab. Return: A QSize. """ minimum_size = self.minimumTabSizeHint(index) height = minimum_size.height() if self.vertical: confwidth = str(config.get('tabs', 'width')) if confwidth.endswith('%'): main_window = objreg.get('main-window', scope='window', window=self._win_id) perc = int(confwidth.rstrip('%')) width = main_window.width() * perc / 100 else: width = int(confwidth) size = QSize(max(minimum_size.width(), width), height) elif self.count() == 0: # This happens on startup on OS X. # We return it directly rather than setting `size' because we don't # want to ensure it's valid in this special case. return QSize() elif self.count() * minimum_size.width() > self.width(): # If we don't have enough space, we return the minimum size so we # get scroll buttons as soon as needed. size = minimum_size else: tab_width_pinned_conf = config.get('tabs', 'pinned-width') try: pinned = self.tab_data(index, 'pinned') except KeyError: pinned = False no_pinned_count = self.count() - self.pinned_count pinned_width = tab_width_pinned_conf * self.pinned_count no_pinned_width = self.width() - pinned_width if pinned: width = tab_width_pinned_conf else: # If we *do* have enough space, tabs should occupy the whole # window width. If there are pinned tabs their size will be # subtracted from the total window width. # During shutdown the self.count goes down, # but the self.pinned_count not - this generates some odd # behavior. To avoid this we compare self.count against # self.pinned_count. if self.pinned_count > 0 and no_pinned_count > 0: width = no_pinned_width / no_pinned_count else: width = self.width() / self.count() # If no_pinned_width is not divisible by no_pinned_count, add a # pixel to some tabs so that there is no ugly leftover space. if (no_pinned_count > 0 and index < no_pinned_width % no_pinned_count): width += 1 size = QSize(width, height) qtutils.ensure_valid(size) return size
def data_url(mimetype: str, data: bytes) -> QUrl: """Get a data: QUrl for the given data.""" b64 = base64.b64encode(data).decode('ascii') url = QUrl('data:{};base64,{}'.format(mimetype, b64)) qtutils.ensure_valid(url) return url
def _load_url_prepare(self, url: QUrl, *, emit_before_load_started: bool = True) -> None: qtutils.ensure_valid(url) if emit_before_load_started: self.before_load_started.emit(url)
def on_data_changed(self, download): """Emit data_changed signal when download data changed.""" idx = self.downloads.index(download) model_idx = self.index(idx, 0) qtutils.ensure_valid(model_idx) self.dataChanged.emit(model_idx, model_idx)
def _on_predicted_navigation(self, url): """Adjust the title if we are going to visit an URL soon.""" qtutils.ensure_valid(url) url_string = url.toDisplayString() log.webview.debug("Predicted navigation: {}".format(url_string)) self.title_changed.emit(url_string)
def _openurl_prepare(self, url, *, predict=True): qtutils.ensure_valid(url) if predict: self.predicted_navigation.emit(url)
def tabopen(self, url=None, background=None, explicit=False, idx=None, *, ignore_tabs_are_windows=False): """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. Also connect all the signals we need to _filter_signals. Args: url: The URL to open as QUrl or None for an empty tab. background: Whether to open the tab in the background. if None, the background-tabs setting decides. explicit: Whether the tab was opened explicitly. If this is set, the new position might be different. With the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current. - Explicitly opened tabs are at the very right. idx: The index where the new tab should be opened. ignore_tabs_are_windows: If given, never open a new window, even with tabs-are-windows set. Return: The opened WebView instance. """ if url is not None: qtutils.ensure_valid(url) log.webview.debug("Creating new tab with URL {}, background {}, " "explicit {}, idx {}".format(url, background, explicit, idx)) if (config.get('tabs', 'tabs-are-windows') and self.count() > 0 and not ignore_tabs_are_windows): from qutebrowser.mainwindow import mainwindow window = mainwindow.MainWindow(private=self.private) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) return tabbed_browser.tabopen(url, background, explicit) tab = browsertab.create(win_id=self._win_id, private=self.private, parent=self) self._connect_tab_signals(tab) if idx is None: idx = self._get_new_tab_idx(explicit) self.insertTab(idx, tab, "") if url is not None: tab.openurl(url) if background is None: background = config.get('tabs', 'background-tabs') if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the # layout anyways. if self.tabBar().vertical: tab_size = QSize(self.width() - self.tabBar().width(), self.height()) else: tab_size = QSize(self.width(), self.height() - self.tabBar().height()) tab.resize(tab_size) self.tab_index_changed.emit(self.currentIndex(), self.count()) else: self.setCurrentWidget(tab) tab.show() self.new_tab.emit(tab, idx) return tab
def resizeEvent(self, e): """Extend QLabel::resizeEvent to update the elided text afterwards.""" super().resizeEvent(e) size = e.size() qtutils.ensure_valid(size) self._update_elided_text(size.width())
def tabopen(self, url=None, background=None, related=True, idx=None, *, ignore_tabs_are_windows=False): """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. Also connect all the signals we need to _filter_signals. Args: url: The URL to open as QUrl or None for an empty tab. background: Whether to open the tab in the background. if None, the `tabs.background_tabs`` setting decides. related: Whether the tab was opened from another existing tab. If this is set, the new position might be different. With the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current (related=True). - Explicitly opened tabs are at the very right (related=False) idx: The index where the new tab should be opened. ignore_tabs_are_windows: If given, never open a new window, even with tabs.tabs_are_windows set. Return: The opened WebView instance. """ if url is not None: qtutils.ensure_valid(url) log.webview.debug("Creating new tab with URL {}, background {}, " "related {}, idx {}".format(url, background, related, idx)) if (config.val.tabs.tabs_are_windows and self.count() > 0 and not ignore_tabs_are_windows): window = mainwindow.MainWindow(private=self.private) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) return tabbed_browser.tabopen(url=url, background=background, related=related) tab = browsertab.create(win_id=self._win_id, private=self.private, parent=self) self._connect_tab_signals(tab) # this was necessary to remove otherwise it was making a hell of a mess in tree structure # if idx is None: # idx = self._get_new_tab_idx(related) idx = self._get_new_tab_idx(related) self.insertTab(idx, tab, "") if url is not None: tab.openurl(url) if background is None: background = config.val.tabs.background if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the # layout anyways. tab.resize(self.currentWidget().size()) self.tab_index_changed.emit(self.currentIndex(), self.count()) else: self.setCurrentWidget(tab) tab.show() self.new_tab.emit(tab, idx) return tab
def __init__(self, atime, url, title, redirect=False): self.atime = float(atime) self.url = url self.title = title self.redirect = redirect qtutils.ensure_valid(url)
def _openurl_prepare(self, url: QUrl, *, predict: bool = True) -> None: qtutils.ensure_valid(url) if predict: self.predicted_navigation.emit(url)
def _openurl_prepare(self, url): qtutils.ensure_valid(url) self.title_changed.emit(url.toDisplayString())