Coverage for qutebrowser/mainwindow/tabwidget.py : 71%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # # This file is part of qutebrowser. # # qutebrowser is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qutebrowser is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
QTimer, QUrl) QStyle, QStylePainter, QStyleOptionTab, QStyleFactory, QWidget)
start=QStyle.PM_CustomBase)
"""The tab widget used for TabbedBrowser.
Signals: tab_index_changed: Emitted when the current tab was changed. arg 0: The index of the tab which is now focused. arg 1: The total count of tabs. new_tab_requested: Emitted when a new tab is requested. """
QTimer.singleShot, 0, self._update_tab_titles))
def _init_config(self): """Initialize attributes based on the config."""
"""Set the tab indicator color.
Args: idx: The tab index. color: A QColor. """ bar = self.tabBar() bar.set_tab_data(idx, 'indicator-color', color) bar.update(bar.tabRect(idx))
pinned: bool) -> None: """Set the tab status as pinned.
Args: tab: The tab to pin pinned: Pinned tab state to set. """ bar = self.tabBar() idx = self.indexOf(tab)
bar.set_tab_data(idx, 'pinned', pinned) tab.data.pinned = pinned self._update_tab_title(idx)
"""Get the tab indicator color for the given index.""" return self.tabBar().tab_indicator_color(idx)
"""Set the tab title user data."""
"""Get the tab title user data."""
"""Update the tab text for the given tab.
Args: idx: The tab index to update. field: A field name which was updated. If given, the title is only set if the given field is in the template. """ fmt = config.val.tabs.title.format_pinned else:
(fmt is None or ('{' + field + '}') not in fmt)): return
"""Get the tab field data.""" log.misc.debug("Got None-tab in get_tab_fields!")
fields['perc'] = '[{}%] '.format(tab.progress()) else:
else: fields['host'] = url.host() fields['current_url'] = url.toDisplayString() fields['protocol'] = url.scheme()
scroll_pos = '???' elif y >= 100: scroll_pos = 'bot' else: scroll_pos = '{:2}%'.format(y)
"""Update all texts."""
"""Update titles when a tab was inserted."""
"""Update titles when a tab was removed."""
"""Override addTab to use our own text setting logic.
Unfortunately QTabWidget::addTab has these two overloads: - QWidget * page, const QIcon & icon, const QString & label - QWidget * page, const QString & label
This means we'll get different arguments based on the chosen overload.
Args: page: The QWidget to add. icon_or_text: Either the QIcon to add or the label. text_or_empty: Either the label or None.
Return: The index of the newly added tab. """ else:
"""Override insertTab to use our own text setting logic.
Unfortunately QTabWidget::insertTab has these two overloads: - int index, QWidget * page, const QIcon & icon, const QString & label - int index, QWidget * page, const QString & label
This means we'll get different arguments based on the chosen overload.
Args: idx: Where to insert the widget. page: The QWidget to add. icon_or_text: Either the QIcon to add or the label. text_or_empty: Either the label or None.
Return: The index of the newly added tab. """ if text_or_empty is None: icon = None text = icon_or_text new_idx = super().insertTab(idx, page, '') else: icon = icon_or_text text = text_or_empty new_idx = super().insertTab(idx, page, icon, '') self.set_page_title(new_idx, text) return new_idx
def _on_current_changed(self, index): """Emit the tab_index_changed signal if the current tab changed."""
def _on_new_tab_requested(self): """Open a new tab.""" self.new_tab_requested.emit(config.val.url.default_page, False, False)
"""Get the URL of the tab at the given index.
Return: The tab URL as QUrl. """ url = QUrl() else: # It's possible for url to be invalid, but the caller will handle that. return url
"""Custom tab bar with our own style.
FIXME: Dragging tabs doesn't look as nice as it does in QTabBar. However, fixing this would be a lot of effort, so we'll postpone it until we're reimplementing drag&drop for other reasons.
https://github.com/qutebrowser/qutebrowser/issues/126
Attributes: vertical: When the tab bar is currently vertical. win_id: The window ID this TabBar belongs to.
Signals: new_tab_requested: Emitted when a new tab is requested. """
def __repr__(self): return utils.get_repr(self, count=self.count())
"""Get the current tab object."""
if option == 'fonts.tabs': self._set_font() elif option == 'tabs.favicons.scale': self._set_icon_size() elif option == 'colors.tabs.bar.bg': self._set_colors() elif option == 'tabs.show_switching_delay': self._on_show_switching_delay_changed() elif option == 'tabs.show': self.maybe_hide()
if option.startswith('colors.tabs.'): self.update()
# Clear _minimum_tab_size_hint_helper cache when appropriate if option in ["tabs.indicator.padding", "tabs.padding", "tabs.indicator.width"]: self._minimum_tab_size_hint_helper.cache_clear()
"""Set timer interval when tabs.show_switching_delay got changed."""
"""Show tab bar when current tab got changed.""" self.show() self._auto_hide_timer.start()
def maybe_hide(self): """Hide the tab bar if needed.""" (show == 'multiple' and self.count() == 1) or (tab and tab.data.fullscreen)): self.hide() else:
"""Set tab data as a dictionary.""" raise IndexError("Tab index ({}) out of range ({})!".format( idx, self.count()))
"""Get tab data for a given key.""" raise IndexError("Tab index ({}) out of range ({})!".format( idx, self.count()))
"""Get the tab indicator color for the given index."""
"""Get the tab title user data.
Args: idx: The tab index to get the title for. handle_unset: Whether to return an empty string on KeyError. """
"""Properly repaint the tab bar and relayout tabs.""" # This is a horrible hack, but we need to do this so the underlying Qt # code sets layoutDirty so it actually relayouts the tabs.
"""Set the tab bar font.""" # clear tab size cache
"""Set the tab bar favicon size."""
"""Set the tab bar colors."""
"""Override mousePressEvent to close tabs if configured.""" button = config.val.tabs.close_mouse_button if (e.button() == Qt.RightButton and button == 'right' or e.button() == Qt.MiddleButton and button == 'middle'): e.accept() idx = self.tabAt(e.pos()) if idx == -1: action = config.val.tabs.close_mouse_button_on_bar if action == 'ignore': return elif action == 'new-tab': self.new_tab_requested.emit() return elif action == 'close-current': idx = self.currentIndex() elif action == 'close-last': idx = self.count() - 1 self.tabCloseRequested.emit(idx) return super().mousePressEvent(e)
"""Set the minimum tab size to indicator/icon/... text.
Args: index: The index of the tab to get a size hint for. ellipsis: Whether to use ellipsis to calculate width instead of the tab's text. Return: A QSize of the smallest tab size we can make. """ None, self) else: self.iconSize().width()) + icon_padding icon_width, ellipsis)
icon_width: int, ellipsis: bool) -> QSize: """Helper function to cache tab results.
Config values accessed in here should be added to _on_config_changed to ensure cache is flushed when needed. """ # Don't ever shorten if text is shorter than the ellipsis
# Calculate text width taking into account qt mnemonics _text_to_width(tab_text)) # Only add padding if indicator exists padding_h + indicator_width)
"""Get the number of pinned tabs and the total width of pinned tabs.""" if self._tab_pinned(idx)] for idx in pinned_list)
"""Return True if tab is pinned."""
"""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. """ confwidth = str(config.val.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) # 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. else: else: pinned = False pinned_count, pinned_width = 0, 0
# 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:
# 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. index < no_pinned_width % no_pinned_count):
# If we don't have enough space, we return the minimum size so we # get scroll buttons as soon as needed.
"""Override paintEvent to draw the tabs like we want to."""
# pylint: disable=bad-config-option # pylint: enable=bad-config-option
# Don't bother drawing a tab if the entire tab is outside of # the visible tab bar.
"""Update visibility when a tab was inserted."""
"""Update visibility when a tab was removed."""
"""Override wheelEvent to make the action configurable.
Args: e: The QWheelEvent """ if config.val.tabs.mousewheel_switching: super().wheelEvent(e) else: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) tabbed_browser.wheelEvent(e)
class Layouts:
"""Layout information for tab.
Used by TabBarStyle._tab_layout(). """
"""Qt style used by TabBar to fix some issues with the default one.
This fixes the following things: - Remove the focus rectangle Ubuntu draws on tabs. - Force text to be left-aligned even though Qt has "centered" hardcoded.
Unfortunately PyQt doesn't support QProxyStyle, so we need to do this the hard way...
Based on:
http://stackoverflow.com/a/17294081 https://code.google.com/p/makehuman/source/browse/trunk/makehuman/lib/qtgui.py """
"""Initialize all functions we're not overriding.
This simply calls the corresponding function in self._style. """ 'generatedIconPixmap', 'hitTestComplexControl', 'itemPixmapRect', 'itemTextRect', 'polish', 'styleHint', 'subControlRect', 'unpolish', 'drawItemText', 'sizeFromContents', 'drawPrimitive']:
"""Draw the tab indicator.
Args: layouts: The layouts from _tab_layout. opt: QStyleOption from drawControl. p: QPainter from drawControl. """ p.fillRect(rect, color)
"""Draw the tab icon.
Args: layouts: The layouts from _tab_layout. opt: QStyleOption p: QPainter """ qtutils.ensure_valid(layouts.icon) 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) icon = opt.icon.pixmap(opt.iconSize, icon_mode, icon_state) self._style.drawItemPixmap(p, layouts.icon, Qt.AlignCenter, icon)
"""Override drawControl to draw odd tabs in a different color.
Draws the given element with the provided painter with the style options specified by option.
Args: element: ControlElement opt: QStyleOption p: QPainter widget: QWidget """ QStyle.CE_TabBarTabLabel]: # Let the real style draw it. self._style.drawControl(element, opt, p, widget) return
log.misc.warning("Could not get layouts for tab!") return
# We override this so we can control TabBarTabShape/TabBarTabLabel. # We use super() rather than self._style here because we don't want # any sophisticated drawing. self._draw_icon(layouts, opt, p) Qt.AlignVCenter | Qt.TextHideMnemonic) opt.state & QStyle.State_Enabled, opt.text, QPalette.WindowText) else: raise ValueError("Invalid element {!r}".format(element))
"""Override pixelMetric to not shift the selected tab.
Args: metric: PixelMetric option: const QStyleOption * widget: const QWidget *
Return: An int. """ QStyle.PM_TabBarTabShiftVertical, QStyle.PM_TabBarTabHSpace, QStyle.PM_TabBarTabVSpace, QStyle.PM_TabBarScrollButtonWidth]: else:
"""Override subElementRect to use our own _tab_layout implementation.
Args: sr: SubElement opt: QStyleOption widget: QWidget
Return: A QRect. """ log.misc.warning("Could not get layouts for tab!") return QRect() QStyle.SE_TabBarScrollLeftButton]: # Handling SE_TabBarScrollLeftButton so the left scroll button is # aligned properly. Otherwise, empty space will be shown after the # last tab even though the button width is set to 0 # # Need to use super() because we also use super() to render # element in drawControl(); otherwise, we may get bit by # style differences... else:
"""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 Layout object with two QRects. """
# This happens sometimes according to crash reports, but no idea # why... return None
-padding.bottom)
indicator_rect = QRect() else: padding.top + indicator_padding.top, 0, -(padding.bottom + indicator_padding.bottom))
indicator_padding.right, 0, 0, 0)
icon_padding = self.pixelMetric(PixelMetrics.icon_padding, opt) text_rect.adjust(icon_rect.width() + icon_padding, 0, 0, 0)
indicator=indicator_rect)
"""Get a QRect for the icon to draw.
Args: opt: QStyleOptionTab text_rect: The QRect for the text.
Return: A QRect. """ icon_extent = self.pixelMetric(QStyle.PM_SmallIconSize) icon_size = QSize(icon_extent, icon_extent) else QIcon.Disabled) else QIcon.Off) # reserve space for favicon when tab bar is vertical (issue #1968) config.val.tabs.favicons.show): tab_icon_size = icon_size else: min(actual_size.width(), icon_size.width()), min(actual_size.height(), icon_size.height()))
|