qutebrowser/qutebrowser/widgets/tabwidget.py

278 lines
9.2 KiB
Python
Raw Normal View History

2014-06-19 09:04:37 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2014-02-06 14:01:23 +01:00
# 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/>.
2014-02-17 12:23:52 +01:00
"""The tab widget used for TabbedBrowser from browser.py."""
2014-07-13 22:10:15 +02:00
import functools
2014-07-11 07:01:08 +02:00
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize
2014-07-13 22:10:15 +02:00
from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle,
QStyle)
from PyQt5.QtGui import QIcon, QPixmap, QPalette
2013-12-15 20:29:39 +01:00
2014-02-23 18:07:17 +01:00
import qutebrowser.config.config as config
from qutebrowser.config.style import set_register_stylesheet
2014-06-23 07:45:04 +02:00
from qutebrowser.utils.qt import qt_ensure_valid
2014-01-28 12:21:00 +01:00
2014-01-28 23:04:02 +01:00
2014-05-12 21:23:16 +02:00
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)
2013-12-15 20:29:39 +01:00
class TabWidget(QTabWidget):
2014-02-07 20:21:50 +01:00
2014-02-18 16:38:13 +01:00
"""The tabwidget used for TabbedBrowser.
2014-04-17 17:44:27 +02:00
Class attributes:
STYLESHEET: The stylesheet template to be used.
2014-02-18 16:38:13 +01:00
"""
2013-12-15 20:29:39 +01:00
STYLESHEET = """
2014-01-28 12:21:00 +01:00
QTabWidget::pane {{
position: absolute;
top: 0px;
2014-01-28 12:21:00 +01:00
}}
2014-01-28 12:21:00 +01:00
QTabBar {{
2014-02-10 07:03:51 +01:00
{font[tabbar]}
2014-04-22 20:49:16 +02:00
{color[tab.bg.bar]}
2014-01-28 12:21:00 +01:00
}}
2013-12-15 20:29:39 +01:00
2014-01-28 12:21:00 +01:00
QTabBar::tab {{
{color[tab.bg]}
{color[tab.fg]}
2014-06-24 11:03:44 +02:00
margin: 0px;
2014-01-28 12:21:00 +01:00
}}
2014-01-27 23:12:43 +01:00
QTabBar::tab:first, QTabBar::tab:middle {{
border-right: 2px solid {color[tab.seperator]};
}}
QTabBar::tab:only-one {{
border-right: 0px;
}}
2014-01-28 12:21:00 +01:00
QTabBar::tab:selected {{
{color[tab.bg.selected]}
}}
"""
def __init__(self, parent):
super().__init__(parent)
2014-05-23 04:12:18 +02:00
self.setTabBar(TabBar())
2014-01-30 22:29:01 +01:00
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
2014-07-13 22:10:15 +02:00
self.setStyle(TabWidgetStyle(self.style()))
set_register_stylesheet(self)
2013-12-15 20:29:39 +01:00
self.setDocumentMode(True)
2014-01-19 16:56:33 +01:00
self.setElideMode(Qt.ElideRight)
2014-04-22 20:49:16 +02:00
self.tabBar().setDrawBase(False)
2014-02-13 08:56:01 +01:00
self._init_config()
def _init_config(self):
"""Initialize attributes based on the config."""
position_conv = {
'north': QTabWidget.North,
'south': QTabWidget.South,
'west': QTabWidget.West,
'east': QTabWidget.East,
}
select_conv = {
'left': QTabBar.SelectLeftTab,
'right': QTabBar.SelectRightTab,
'previous': QTabBar.SelectPreviousTab,
}
self.setMovable(config.get('tabbar', 'movable'))
2014-04-27 21:21:14 +02:00
self.setTabsClosable(config.get('tabbar', 'close-buttons'))
posstr = config.get('tabbar', 'position')
2014-04-27 21:21:14 +02:00
selstr = config.get('tabbar', 'select-on-remove')
2014-06-21 22:40:31 +02:00
self.setTabPosition(position_conv[posstr])
self.tabBar().setSelectionBehaviorOnRemove(select_conv[selstr])
@pyqtSlot(str, str)
2014-04-22 17:53:27 +02:00
def on_config_changed(self, section, _option):
"""Update attributes when config changed."""
if section == 'tabbar':
self._init_config()
2014-05-23 04:12:18 +02:00
class TabBar(QTabBar):
2014-07-11 07:01:08 +02:00
"""Custom tabbar to close tabs on right click.
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.
"""
tab_rightclicked = pyqtSignal(int)
2014-05-23 04:12:18 +02:00
def __repr__(self):
return '<{} with {} tabs>'.format(self.__class__.__name__,
self.count())
2014-05-23 04:12:18 +02:00
def mousePressEvent(self, e):
"""Override mousePressEvent to emit tabCloseRequested on rightclick."""
2014-07-11 07:01:08 +02:00
if e.button() != Qt.RightButton:
2014-06-22 23:37:16 +02:00
super().mousePressEvent(e)
return
2014-05-23 04:12:18 +02:00
idx = self.tabAt(e.pos())
if idx == -1:
2014-06-22 23:37:16 +02:00
super().mousePressEvent(e)
return
2014-06-23 18:21:12 +02:00
e.accept()
2014-07-11 07:01:08 +02:00
if config.get('tabbar', 'close-on-right-click'):
self.tab_rightclicked.emit(idx)
2014-06-10 14:30:31 +02:00
2014-07-11 16:54:54 +02:00
def minimumTabSizeHint(self, index):
"""Override minimumTabSizeHint because we want no hard minimum.
There are two problems with having a hard minimum tab size:
- When expanding is True, the window will expand without stopping
on some window managers.
- We don't want the main window to get bigger with many tabs. If
nothing else helps, we *do* want the tabs to get smaller instead
of enforcing a minimum window size.
Args:
index: The index of the tab to get a sizehint for.
Return:
A QSize.
"""
height = super().tabSizeHint(index).height()
return QSize(1, height)
2014-06-10 14:30:31 +02:00
def tabSizeHint(self, index):
"""Override tabSizeHint so all tabs are the same size.
https://wiki.python.org/moin/PyQt/Customising%20tab%20bars
"""
2014-07-13 22:38:44 +02:00
height = super().tabSizeHint(index).height()
size = QSize(self.width() / self.count(), height)
2014-06-22 23:32:49 +02:00
qt_ensure_valid(size)
return size
2014-07-13 22:10:15 +02:00
class TabWidgetStyle(QCommonStyle):
"""Qt style used by TabWidget 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', 'drawControl', 'drawItemPixmap',
'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)
def drawItemText(self, painter, rectangle, alignment, palette, enabled,
text, textRole=QPalette.NoRole):
"""Extend QCommonStyle::drawItemText to not center-align text.
Since Qt hardcodes the text alignment for tabbar tabs in QCommonStyle,
we need to undo this here by deleting the flag again, and align left
instead.
Draws the given text in the specified rectangle using the provided
painter and palette.
The text is drawn using the painter's pen, and aligned and wrapped
according to the specified alignment. If an explicit textRole is
specified, the text is drawn using the palette's color for the given
role. The enabled parameter indicates whether or not the item is
enabled; when reimplementing this function, the enabled parameter
should influence how the item is drawn.
Args:
painter: QPainter *
rectangle: const QRect &
alignment int (Qt::Alignment)
palette: const QPalette &
enabled: bool
text: const QString &
textRole: QPalette::ColorRole textRole
"""
# pylint: disable=invalid-name
alignment &= ~Qt.AlignHCenter
alignment |= Qt.AlignLeft
super().drawItemText(painter, rectangle, alignment, palette, enabled,
text, textRole)