Merge branch 'tabbar'

This commit is contained in:
Florian Bruhin 2014-07-16 17:57:15 +02:00
commit 86b7ea4dfc
10 changed files with 428 additions and 223 deletions

View File

@ -7,17 +7,6 @@ Bugs
e.g. loading a page and immediately yanking the (non-resolved) URL should
work.
- When setting tabbar -> expand to true on xmonad (?), the window expands
continously:
egan │ Sorry, what I mean is that the length of the tab starts at a specific
size and grows to the right continually.
egan │ In fact the web page rendered in the tab also scrolls in that
direction until it is no longer visible
egan │ This is accompanied by high CPU usage
- seir sometimes sees "-- COMMAND MODE --" even though that should never
happen.
@ -77,10 +66,6 @@ Bugs
- Shutdown is still flaky. (see notes)
- Eliding doesn't work correctly in tabs (cuts off start)
This especially happens when there's no favicon
(will be solved by tabbar reimplementation)
- Opening via commandline / startpage doesn't work with absolute file paths.
- Relative file paths and ~ don't work at all.
@ -98,10 +83,6 @@ Bugs
is loaded (probably *because* we're loading the error page and that
succeeds).
- When tabbar -> scroll-buttons is disabled and there are too many tabs, the
window has a bigger size hint instead of tabs getting smaller than the
minimum size (iggy)
- Opening editor is broken on http://p.cmpl.cc/
- Segfault on subsonic when clicking next track

View File

@ -76,7 +76,6 @@ Improvements / minor features
- Commandline argument to delete config
- Settings dialog
- Tab groups (tagging/filtering for displayed tabs)
- Reimplement tabbar to paint it by ourselves to look like dwb
- Save cookies in Netscape format so it can be used by wget. (see notes)
- Zoom with ctrl + mousewheel
- debug-Command for set loglevel/RAM capacity
@ -94,7 +93,6 @@ Improvements / minor features
- Ctrl+A/X to increase/decrease last number in URL
- Add more element-selection-detection code (with options?) (see notes)
- somehow unfocus elements (hide blinking cursor) when insert mode is left?
- tabs: some more padding?
- Copy link location on crash mail should not copy mailto:
- Drag&Drop of tabs to other windows
- Use QNetworkAccessManager per QWebPage again so we can set proxy per tab.

View File

@ -401,17 +401,9 @@ DATA = OrderedDict([
SettingValue(types.Bool(), 'true'),
"Whether tabs should be movable."),
('close-on-right-click',
SettingValue(types.Bool(), 'false'),
"Whether tabs should close when right-clicked."),
('close-buttons',
SettingValue(types.Bool(), 'false'),
"Whether tabs should have close-buttons."),
('scroll-buttons',
SettingValue(types.Bool(), 'true'),
"Whether there should be scroll buttons if there are too many tabs."),
('close-mouse-button',
SettingValue(types.CloseButton(), 'middle'),
"On which mouse button to close tabs."),
('position',
SettingValue(types.Position(), 'north'),
@ -429,22 +421,23 @@ DATA = OrderedDict([
SettingValue(types.Bool(), 'true'),
"Whether to wrap when changing tabs."),
('min-tab-width',
SettingValue(types.Int(minval=1), '100'),
"The minimum width of a tab."),
('max-tab-width',
SettingValue(types.Int(minval=1), '200'),
"The maximum width of a tab."),
('show-favicons',
SettingValue(types.Bool(), 'true'),
"Whether to show favicons in the tab bar."),
('expand',
SettingValue(types.Bool(), 'false'),
"Whether to expand tabs to use the full window width."),
('width',
SettingValue(types.PercOrInt(minperc=0, maxperc=100, minint=1),
'20%'),
"The width of the tab bar if it's vertical, in px or as percentage "
"of the window."),
('indicator-width',
SettingValue(types.Int(minval=0), '3'),
"Width of the progress indicator."),
('indicator-space',
SettingValue(types.Int(minval=0), '3'),
"Spacing between tab edge and indicator."),
)),
('storage', sect.KeyValue(
@ -873,9 +866,13 @@ DATA = OrderedDict([
SettingValue(types.Color(), 'white'),
"Foreground color of tabs."),
('tab.bg',
SettingValue(types.Color(), 'grey'),
"Background color of unselected tabs."),
('tab.bg.odd',
SettingValue(types.QtColor(), 'grey'),
"Background color of unselected odd tabs."),
('tab.bg.even',
SettingValue(types.QtColor(), 'darkgrey'),
"Background color of unselected even tabs."),
('tab.bg.selected',
SettingValue(types.Color(), 'black'),
@ -885,6 +882,22 @@ DATA = OrderedDict([
SettingValue(types.Color(), '#555555'),
"Background color of the tabbar."),
('tab.indicator.start',
SettingValue(types.QtColor(), '#0000aa'),
"Color gradient start for the tab indicator."),
('tab.indicator.stop',
SettingValue(types.QtColor(), '#00aa00'),
"Color gradient end for the tab indicator."),
('tab.indicator.error',
SettingValue(types.QtColor(), '#ff0000'),
"Color for the tab indicator on errors.."),
('tab.indicator.system',
SettingValue(types.ColorSystem(), 'rgb'),
"Color gradient interpolation system for the tab indicator."),
('tab.seperator',
SettingValue(types.Color(), '#555555'),
"Color for the tab seperator."),

View File

@ -935,3 +935,12 @@ class ForwardUnboundKeys(BaseType):
('auto', "Forward unbound non-alphanumeric "
"keys."),
('none', "Don't forward any keys."))
class CloseButton(BaseType):
"""Whether to forward unbound keys."""
valid_values = ValidValues(('right', "Close tabs on right-click."),
('middle', "Close tabs on middle-click."),
('none', "Don't close tabs using the mouse."))

View File

@ -32,6 +32,23 @@ from PyQt5.QtGui import QColor
import qutebrowser.utils.misc as utils
from qutebrowser.test.helpers import environ_set_temp, fake_keyevent
from qutebrowser.utils.qt import QtValueError
class Color(QColor):
"""A QColor with a nicer repr()."""
def __repr__(self):
return 'Color({}, {}, {}, {})'.format(
self.red(), self.green(), self.blue(), self.alpha())
def __eq__(self, other):
"""The default operator= of QColor seems to be rather strict."""
return (self.red() == other.red() and
self.green() == other.green() and
self.blue() == other.blue() and
self.alpha() == other.alpha())
class ElidingTests(unittest.TestCase):
@ -330,23 +347,23 @@ class InterpolateColorTests(unittest.TestCase):
"""Tests for interpolate_color.
Attributes:
white: The QColor white as a valid QColor for tests.
white: The QColor black as a valid QColor for tests.
white: The Color white as a valid Color for tests.
white: The Color black as a valid Color for tests.
"""
def setUp(self):
self.white = QColor('white')
self.black = QColor('black')
self.white = Color('white')
self.black = Color('black')
def test_invalid_start(self):
"""Test an invalid start color."""
with self.assertRaises(ValueError):
utils.interpolate_color(QColor(), self.white, 0)
with self.assertRaises(QtValueError):
utils.interpolate_color(Color(), self.white, 0)
def test_invalid_end(self):
"""Test an invalid end color."""
with self.assertRaises(ValueError):
utils.interpolate_color(self.white, QColor(), 0)
with self.assertRaises(QtValueError):
utils.interpolate_color(self.white, Color(), 0)
def test_invalid_percentage(self):
"""Test an invalid percentage."""
@ -365,52 +382,52 @@ class InterpolateColorTests(unittest.TestCase):
white = utils.interpolate_color(self.white, self.black, 0, QColor.Rgb)
black = utils.interpolate_color(self.white, self.black, 100,
QColor.Rgb)
self.assertEqual(white, self.white)
self.assertEqual(black, self.black)
self.assertEqual(Color(white), self.white)
self.assertEqual(Color(black), self.black)
def test_valid_percentages_hsv(self):
"""Test 0% and 100% in the HSV colorspace."""
white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsv)
black = utils.interpolate_color(self.white, self.black, 100,
QColor.Hsv)
self.assertEqual(white, self.white)
self.assertEqual(black, self.black)
self.assertEqual(Color(white), self.white)
self.assertEqual(Color(black), self.black)
def test_valid_percentages_hsl(self):
"""Test 0% and 100% in the HSL colorspace."""
white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsl)
black = utils.interpolate_color(self.white, self.black, 100,
QColor.Hsl)
self.assertEqual(white, self.white)
self.assertEqual(black, self.black)
self.assertEqual(Color(white), self.white)
self.assertEqual(Color(black), self.black)
def test_interpolation_rgb(self):
"""Test an interpolation in the RGB colorspace."""
color = utils.interpolate_color(QColor(0, 40, 100), QColor(0, 20, 200),
color = utils.interpolate_color(Color(0, 40, 100), Color(0, 20, 200),
50, QColor.Rgb)
self.assertEqual(color, QColor(0, 30, 150))
self.assertEqual(Color(color), Color(0, 30, 150))
def test_interpolation_hsv(self):
"""Test an interpolation in the HSV colorspace."""
start = QColor()
stop = QColor()
start = Color()
stop = Color()
start.setHsv(0, 40, 100)
stop.setHsv(0, 20, 200)
color = utils.interpolate_color(start, stop, 50, QColor.Hsv)
expected = QColor()
expected = Color()
expected.setHsv(0, 30, 150)
self.assertEqual(color, expected)
self.assertEqual(Color(color), expected)
def test_interpolation_hsl(self):
"""Test an interpolation in the HSL colorspace."""
start = QColor()
stop = QColor()
start = Color()
stop = Color()
start.setHsl(0, 40, 100)
stop.setHsl(0, 20, 200)
color = utils.interpolate_color(start, stop, 50, QColor.Hsl)
expected = QColor()
expected = Color()
expected.setHsl(0, 30, 150)
self.assertEqual(color, expected)
self.assertEqual(Color(color), expected)
class FormatSecondsTests(unittest.TestCase):

View File

@ -1,79 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 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/>.
"""Qt style to remove Ubuntu focus rectangle uglyness.
We might also use this to do more in the future.
"""
import functools
from PyQt5.QtWidgets import QCommonStyle, QStyle
class Style(QCommonStyle):
"""Qt style to remove Ubuntu focus rectangle uglyness.
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
Attributes:
_style: The base/"parent" style.
"""
def __init__(self, style):
"""Initialize all functions we're not overriding.
This simply calls the corresponding function in self._style.
Args:
style: The base/"parent" style.
"""
self._style = style
for method in ('drawComplexControl', 'drawControl', 'drawItemPixmap',
'drawItemText', 'generatedIconPixmap',
'hitTestComplexControl', 'itemPixmapRect',
'itemTextRect', 'pixelMetric', 'polish', 'styleHint',
'subControlRect', 'subElementRect', 'unpolish',
'sizeFromContents'):
target = getattr(self._style, method)
setattr(self, method, functools.partial(target))
super().__init__()
def drawPrimitive(self, element, option, painter, widget=None):
"""Override QCommonStyle.drawPrimitive.
Call the genuine drawPrimitive of self._style, except when a focus
rectangle should be drawn.
Args:
element: PrimitiveElement pe
option: const QStyleOption * opt
painter: QPainter * p
widget: const QWidget * widget
"""
if element == QStyle.PE_FrameFocusRect:
return
return self._style.drawPrimitive(element, option, painter, widget)

View File

@ -160,6 +160,7 @@ class MainWindow(QWidget):
super().resizeEvent(e)
self.resize_completion()
self.downloadview.updateGeometry()
self.tabs.tabBar().refresh()
def closeEvent(self, e):
"""Override closeEvent to display a confirmation if needed."""

View File

@ -23,12 +23,14 @@ from functools import partial
from PyQt5.QtWidgets import QSizePolicy
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSize
from PyQt5.QtGui import QIcon
import qutebrowser.config.config as config
import qutebrowser.commands.utils as cmdutils
import qutebrowser.keyinput.modeman as modeman
import qutebrowser.utils.log as log
from qutebrowser.widgets.tabwidget import TabWidget, EmptyTabIcon
import qutebrowser.utils.misc as utils
from qutebrowser.widgets.tabwidget import TabWidget
from qutebrowser.widgets.webview import WebView
from qutebrowser.browser.signalfilter import SignalFilter
from qutebrowser.browser.commands import CommandDispatcher
@ -102,7 +104,6 @@ class TabbedBrowser(TabWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.tabBar().tab_rightclicked.connect(self.on_tab_close_requested)
self.tabCloseRequested.connect(self.on_tab_close_requested)
self.currentChanged.connect(self.on_current_changed)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
@ -181,6 +182,8 @@ class TabbedBrowser(TabWidget):
# misc
tab.titleChanged.connect(partial(self.on_title_changed, tab))
tab.iconChanged.connect(partial(self.on_icon_changed, tab))
tab.loadProgress.connect(partial(self.on_load_progress, tab))
frame.loadFinished.connect(partial(self.on_load_finished, tab))
frame.loadStarted.connect(partial(self.on_load_started, tab))
page.windowCloseRequested.connect(
partial(self.on_window_close_requested, tab))
@ -347,7 +350,7 @@ class TabbedBrowser(TabWidget):
if show:
self.setTabIcon(i, tab.icon())
else:
self.setTabIcon(i, EmptyTabIcon())
self.setTabIcon(i, QIcon())
@pyqtSlot()
def on_load_started(self, tab):
@ -361,7 +364,6 @@ class TabbedBrowser(TabWidget):
# We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab))
return
self.setTabIcon(idx, EmptyTabIcon())
@pyqtSlot()
def on_cur_load_started(self):
@ -443,6 +445,27 @@ class TabbedBrowser(TabWidget):
self.current_tab_changed.emit(tab)
self.title_changed.emit('{} - qutebrowser'.format(self.tabText(idx)))
def on_load_progress(self, tab, perc):
"""Adjust tab indicator on load progress."""
idx = self.indexOf(tab)
start = config.get('colors', 'tab.indicator.start')
stop = config.get('colors', 'tab.indicator.stop')
system = config.get('colors', 'tab.indicator.system')
color = utils.interpolate_color(start, stop, perc, system)
self.tabBar().set_tab_indicator_color(idx, color)
def on_load_finished(self, tab, ok):
"""Adjust tab indicator when loading finished."""
idx = self.indexOf(tab)
if ok:
start = config.get('colors', 'tab.indicator.start')
stop = config.get('colors', 'tab.indicator.stop')
system = config.get('colors', 'tab.indicator.system')
color = utils.interpolate_color(start, stop, 100, system)
else:
color = config.get('colors', 'tab.indicator.error')
self.tabBar().set_tab_indicator_color(idx, color)
def resizeEvent(self, e):
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.

View File

@ -17,34 +17,27 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""The tab widget used for TabbedBrowser from browser.py."""
"""The tab widget used for TabbedBrowser from browser.py.
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize
from PyQt5.QtWidgets import QTabWidget, QTabBar, QSizePolicy
from PyQt5.QtGui import QIcon, QPixmap
Module attributes:
PM_TabBarPadding: The PixelMetric value for TabBarStyle to get the padding
between items.
"""
import functools
from PyQt5.QtCore import pyqtSlot, Qt, QSize, QRect, QPoint
from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle,
QStyle, QStylePainter, QStyleOptionTab,
QApplication)
from PyQt5.QtGui import QIcon, QPalette, QColor
import qutebrowser.config.config as config
from qutebrowser.config.style import set_register_stylesheet
from qutebrowser.utils.style import Style
from qutebrowser.utils.qt import qt_ensure_valid
import qutebrowser.config.config as config
class EmptyTabIcon(QIcon):
"""An empty icon for a tab.
Qt somehow cuts text off when padding is used for the tabbar, see
https://bugreports.qt-project.org/browse/QTBUG-15203
Until we find a better solution we use this hack of using a simple
transparent icon to get some padding, because when a real favicon is set,
the padding seems to be fine...
"""
def __init__(self):
pix = QPixmap(2, 16)
pix.fill(Qt.transparent)
super().__init__(pix)
PM_TabBarPadding = QStyle.PM_CustomBase
class TabWidget(QTabWidget):
@ -65,30 +58,19 @@ class TabWidget(QTabWidget):
{font[tabbar]}
{color[tab.bg.bar]}
}}
QTabBar::tab {{
{color[tab.bg]}
{color[tab.fg]}
border-right: 2px solid {color[tab.seperator]};
min-width: {config[tabbar][min-tab-width]}px;
max-width: {config[tabbar][max-tab-width]}px;
margin: 0px;
}}
QTabBar::tab:selected {{
{color[tab.bg.selected]}
}}
"""
def __init__(self, parent):
super().__init__(parent)
self.setTabBar(TabBar())
bar = TabBar()
self.setTabBar(bar)
bar.tabCloseRequested.connect(self.tabCloseRequested)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setStyle(Style(self.style()))
set_register_stylesheet(self)
self.setDocumentMode(True)
self.setElideMode(Qt.ElideRight)
self.tabBar().setDrawBase(False)
self.setUsesScrollButtons(True)
bar.setDrawBase(False)
self._init_config()
def _init_config(self):
@ -104,13 +86,16 @@ class TabWidget(QTabWidget):
'right': QTabBar.SelectRightTab,
'previous': QTabBar.SelectPreviousTab,
}
tabbar = self.tabBar()
self.setMovable(config.get('tabbar', 'movable'))
self.setTabsClosable(config.get('tabbar', 'close-buttons'))
self.setUsesScrollButtons(config.get('tabbar', 'scroll-buttons'))
self.setTabsClosable(False)
posstr = config.get('tabbar', 'position')
selstr = config.get('tabbar', 'select-on-remove')
self.setTabPosition(position_conv[posstr])
self.tabBar().setSelectionBehaviorOnRemove(select_conv[selstr])
position = position_conv[posstr]
self.setTabPosition(position)
tabbar.vertical = position in (QTabWidget.West, QTabWidget.East)
tabbar.setSelectionBehaviorOnRemove(select_conv[selstr])
tabbar.refresh()
@pyqtSlot(str, str)
def on_config_changed(self, section, _option):
@ -121,35 +106,52 @@ class TabWidget(QTabWidget):
class TabBar(QTabBar):
"""Custom tabbar to close tabs on right click.
"""Custom tabbar with our own style.
Signals:
tab_rightclicked: Emitted when a tab was right-clicked and should be
closed. We use this rather than tabCloseRequested
because tabCloseRequested is sometimes connected by
Qt to the tabwidget and sometimes not, depending on
if close buttons are enabled.
arg: The tab index to be closed.
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.
Attributes:
vertical: When the tab bar is currently vertical.
"""
tab_rightclicked = pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self.setStyle(TabBarStyle(self.style()))
self.vertical = False
def __repr__(self):
return '<{} with {} tabs>'.format(self.__class__.__name__,
self.count())
def refresh(self):
"""Properly repaint the tab bar and relayout tabs."""
# This is a horrible hack, but we need to do this so the underlaying Qt
# code sets layoutDirty so it actually relayouts the tabs.
self.setIconSize(self.iconSize())
def set_tab_indicator_color(self, idx, color):
"""Set the tab indicator color.
Args:
idx: The tab index.
color: A QColor.
"""
self.setTabData(idx, color)
self.update(self.tabRect(idx))
def mousePressEvent(self, e):
"""Override mousePressEvent to emit tabCloseRequested on rightclick."""
if e.button() != Qt.RightButton:
super().mousePressEvent(e)
return
idx = self.tabAt(e.pos())
if idx == -1:
super().mousePressEvent(e)
return
e.accept()
if config.get('tabbar', 'close-on-right-click'):
self.tab_rightclicked.emit(idx)
"""Override mousePressEvent to close tabs if configured."""
button = config.get('tabbar', 'close-mouse-button')
if (e.button() == Qt.RightButton and button == 'right' or
e.button() == Qt.MiddleButton and button == 'middle'):
idx = self.tabAt(e.pos())
if idx != -1:
e.accept()
self.tabCloseRequested.emit(idx)
return
super().mousePressEvent(e)
def minimumTabSizeHint(self, index):
"""Override minimumTabSizeHint because we want no hard minimum.
@ -167,18 +169,255 @@ class TabBar(QTabBar):
Return:
A QSize.
"""
height = super().tabSizeHint(index).height()
return QSize(1, height)
icon = self.tabIcon(index)
padding_count = 0
if not icon.isNull():
extent = self.style().pixelMetric(QStyle.PM_TabBarIconSize, None,
self)
icon_size = icon.actualSize(QSize(extent, extent))
padding_count += 1
else:
icon_size = QSize(0, 0)
padding_width = self.style().pixelMetric(PM_TabBarPadding, None, self)
height = self.fontMetrics().height()
width = (self.fontMetrics().size(0, '\u2026').width() +
icon_size.width() + padding_count * padding_width)
return QSize(width, height)
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.
"""
if config.get('tabbar', 'expand'):
height = super().tabSizeHint(index).height()
size = QSize(self.width() / self.count(), height)
minimum_size = self.minimumTabSizeHint(index)
height = self.fontMetrics().height()
if self.vertical:
confwidth = str(config.get('tabbar', 'width'))
if confwidth.endswith('%'):
perc = int(confwidth.rstrip('%'))
width = QApplication.instance().mainwindow.width() * perc / 100
else:
width = int(confwidth)
size = QSize(max(minimum_size.width(), width), height)
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:
size = super().tabSizeHint(index)
# If we *do* have enough space, tabs should occupy the whole window
# width.
size = QSize(self.width() / self.count(), height)
qt_ensure_valid(size)
return size
def paintEvent(self, _e):
"""Override paintEvent to draw the tabs like we want to."""
p = QStylePainter(self)
tab = QStyleOptionTab()
selected = self.currentIndex()
for idx in range(self.count()):
self.initStyleOption(tab, idx)
if idx == selected:
color = config.get('colors', 'tab.bg.selected')
elif idx % 2:
color = config.get('colors', 'tab.bg.odd')
else:
color = config.get('colors', 'tab.bg.even')
tab.palette.setColor(QPalette.Window, QColor(color))
tab.palette.setColor(QPalette.WindowText,
QColor(config.get('colors', 'tab.fg')))
indicator_color = self.tabData(idx)
if indicator_color is None:
indicator_color = QColor()
tab.palette.setColor(QPalette.Base, indicator_color)
if tab.rect.right() < 0 or tab.rect.left() > self.width():
# Don't bother drawing a tab if the entire tab is outside of
# the visible tab bar.
continue
p.drawControl(QStyle.CE_TabBarTab, tab)
class TabBarStyle(QCommonStyle):
"""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
Attributes:
_style: The base/"parent" style.
"""
def __init__(self, style):
"""Initialize all functions we're not overriding.
This simply calls the corresponding function in self._style.
Args:
style: The base/"parent" style.
"""
self._style = style
for method in ('drawComplexControl', 'drawItemPixmap',
'generatedIconPixmap', 'hitTestComplexControl',
'itemPixmapRect', 'itemTextRect',
'polish', 'styleHint', 'subControlRect', 'unpolish',
'drawItemText', 'sizeFromContents', 'drawPrimitive'):
target = getattr(self._style, method)
setattr(self, method, functools.partial(target))
super().__init__()
def drawControl(self, element, opt, p, widget=None):
"""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
option: const QStyleOption *
painter: QPainter *
widget: const QWidget *
"""
if element == QStyle.CE_TabBarTab:
# We override this so we can control TabBarTabShape/TabBarTabLabel.
self.drawControl(QStyle.CE_TabBarTabShape, opt, p, widget)
self.drawControl(QStyle.CE_TabBarTabLabel, opt, p, widget)
elif element == QStyle.CE_TabBarTabShape:
p.fillRect(opt.rect, opt.palette.window())
indicator_color = opt.palette.base().color()
indicator_width = config.get('tabbar', 'indicator-width')
if indicator_color.isValid() and indicator_width != 0:
topleft = opt.rect.topLeft()
topleft += QPoint(config.get('tabbar', 'indicator-space'), 2)
p.fillRect(topleft.x(), topleft.y(), indicator_width,
opt.rect.height() - 4, indicator_color)
# We use super() rather than self._style here because we don't want
# any sophisticated drawing.
super().drawControl(QStyle.CE_TabBarTabShape, opt, p, widget)
elif element == QStyle.CE_TabBarTabLabel:
text_rect, icon_rect = self._tab_layout(opt)
if not opt.icon.isNull():
qt_ensure_valid(icon_rect)
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)
p.drawPixmap(icon_rect.x(), icon_rect.y(), icon)
self._style.drawItemText(p, text_rect,
Qt.AlignLeft | Qt.AlignVCenter,
opt.palette,
opt.state & QStyle.State_Enabled,
opt.text, QPalette.WindowText)
else:
# For any other elements we just delegate the work to our real
# style.
self._style.drawControl(element, opt, p, widget)
def pixelMetric(self, metric, option=None, widget=None):
"""Override pixelMetric to not shift the selected tab.
Args:
metric: PixelMetric
option: const QStyleOption *
widget: const QWidget *
Return:
An int.
"""
if (metric == QStyle.PM_TabBarTabShiftHorizontal or
metric == QStyle.PM_TabBarTabShiftVertical or
metric == QStyle.PM_TabBarTabHSpace or
metric == QStyle.PM_TabBarTabVSpace):
return 0
elif metric == PM_TabBarPadding:
return 4
else:
return self._style.pixelMetric(metric, option, widget)
def subElementRect(self, sr, opt, widget=None):
"""Override subElementRect to use our own _tab_layout implementation.
Args:
sr: SubElement
opt: QStyleOption
widget: QWidget
Return:
A QRect.
"""
if sr == QStyle.SE_TabBarTabText:
text_rect, _icon_rect = self._tab_layout(opt)
return text_rect
else:
return self._style.subElementRect(sr, opt, widget)
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)
qt_ensure_valid(text_rect)
text_rect.adjust(padding, 0, 0, 0)
text_rect.adjust(config.get('tabbar', 'indicator-width') +
config.get('tabbar', '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 _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)
qt_ensure_valid(icon_rect)
return icon_rect

View File

@ -352,12 +352,15 @@ class WebView(QWebView):
callback: Function to call after shutting down.
"""
self._shutdown_callback = callback
# Avoid loading finished signal when stopping
try:
# Avoid loading finished signal when stopping
self.loadFinished.disconnect()
except TypeError:
pass
try:
self.page().mainFrame().loadFinished.disconnect()
except TypeError:
log.destroy.exception("This should never happen.")
pass
self.stop()
self.close()
self.settings().setAttribute(QWebSettings.JavascriptEnabled, False)