Make it possible to configure tab titles.

This commit is contained in:
Florian Bruhin 2015-02-13 17:30:36 +01:00
parent 1cf34e7984
commit 66d3ec1c08
4 changed files with 239 additions and 33 deletions

View File

@ -378,11 +378,12 @@ class CommandDispatcher:
"""
if bg and window:
raise cmdexc.CommandError("Only one of -b/-w can be given!")
curtab = self._current_widget()
tabbed_browser = self._tabbed_browser(window)
curtab = self._current_widget()
cur_title = tabbed_browser.page_title(self._current_index())
newtab = tabbed_browser.tabopen(background=bg, explicit=True)
idx = tabbed_browser.indexOf(newtab)
tabbed_browser.setTabText(idx, curtab.title().replace('&', '&&'))
tabbed_browser.set_page_title(idx, cur_title)
tabbed_browser.setTabIcon(idx, curtab.icon())
newtab.keep_icon = True
newtab.setZoomFactor(curtab.zoomFactor())
@ -594,7 +595,7 @@ class CommandDispatcher:
"""
clipboard = QApplication.clipboard()
if title:
s = self._tabbed_browser().tabText(self._current_index())
s = self._tabbed_browser().page_title(self._current_index())
else:
s = self._current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
@ -788,7 +789,7 @@ class CommandDispatcher:
tab = self._current_widget()
cur_idx = self._current_index()
icon = tabbed_browser.tabIcon(cur_idx)
label = tabbed_browser.tabText(cur_idx)
label = tabbed_browser.page_title(cur_idx)
cmdutils.check_overflow(cur_idx, 'int')
cmdutils.check_overflow(new_idx, 'int')
tabbed_browser.setUpdatesEnabled(False)
@ -850,7 +851,7 @@ class CommandDispatcher:
idx = self._current_index()
tabbed_browser = self._tabbed_browser()
if idx != -1:
env['QUTE_TITLE'] = tabbed_browser.tabText(idx)
env['QUTE_TITLE'] = tabbed_browser.page_title(idx)
webview = tabbed_browser.currentWidget()
if webview is not None and webview.hasSelection():

View File

@ -247,7 +247,7 @@ DATA = collections.OrderedDict([
('window-title-format',
SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title',
'title_sep']),
'title_sep', 'id']),
'{perc}{title}{title_sep}qutebrowser'),
"The format to use for the window title. The following placeholders "
"are defined:\n\n"
@ -255,7 +255,8 @@ DATA = collections.OrderedDict([
"* `{perc_raw}`: The raw percentage, e.g. `10`\n"
"* `{title}`: The title of the current webpage\n"
"* `{title_sep}`: The string ` - ` if a title is set, empty "
"otherwise.")
"otherwise.\n"
"* `{id}`: The internal window ID of this window."),
)),
('network', sect.KeyValue(
@ -419,6 +420,20 @@ DATA = collections.OrderedDict([
('tabs-are-windows',
SettingValue(typ.Bool(), 'false'),
"Whether to open windows instead of tabs."),
('title-format',
SettingValue(typ.FormatString(fields=['perc', 'perc_raw', 'title',
'title_sep', 'index', 'id']),
'{index}: {title}'),
"The format to use for the tab title. The following placeholders "
"are defined:\n\n"
"* `{perc}`: The percentage as a string like `[10%]`.\n"
"* `{perc_raw}`: The raw percentage, e.g. `10`\n"
"* `{title}`: The title of the current webpage\n"
"* `{title_sep}`: The string ` - ` if a title is set, empty "
"otherwise.\n"
"* `{index}`: The index of this tab.\n"
"* `{id}`: The internal tab ID of this tab."),
)),
('storage', sect.KeyValue(

View File

@ -114,6 +114,7 @@ class TabbedBrowser(tabwidget.TabWidget):
self.setIconSize(QSize(12, 12))
objreg.get('config').changed.connect(self.update_favicons)
objreg.get('config').changed.connect(self.update_window_title)
objreg.get('config').changed.connect(self.update_tab_titles)
def __repr__(self):
return utils.get_repr(self, count=self.count())
@ -133,7 +134,11 @@ class TabbedBrowser(tabwidget.TabWidget):
def update_window_title(self):
"""Change the window title to match the current tab."""
idx = self.currentIndex()
tabtitle = self.tabText(idx)
if idx == -1:
# (e.g. last tab removed)
log.webview.debug("Not updating window title because index is -1")
return
tabtitle = self.page_title(idx)
widget = self.widget(idx)
fields = {}
@ -144,6 +149,7 @@ class TabbedBrowser(tabwidget.TabWidget):
fields['perc_raw'] = widget.progress
fields['title'] = tabtitle
fields['title_sep'] = ' - ' if tabtitle else ''
fields['id'] = self._win_id
fmt = config.get('ui', 'window-title-format')
self.window().setWindowTitle(fmt.format(**fields))
@ -429,6 +435,7 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab))
return
self.update_tab_title(idx)
if tab.keep_icon:
tab.keep_icon = False
else:
@ -468,7 +475,7 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab))
return
self.setTabText(idx, text.replace('&', '&&'))
self.set_page_title(idx, text)
if idx == self.currentIndex():
self.update_window_title()
@ -489,8 +496,8 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab))
return
if not self.tabText(idx):
self.setTabText(idx, url)
if not self.page_title(idx):
self.set_page_title(idx, url)
@pyqtSlot(webview.WebView)
def on_icon_changed(self, tab):
@ -542,7 +549,7 @@ class TabbedBrowser(tabwidget.TabWidget):
scope='window', window=self._win_id)
self._now_focused = tab
self.current_tab_changed.emit(tab)
self.update_window_title()
QTimer.singleShot(0, self.update_window_title)
self._tab_insert_idx_left = self.currentIndex()
self._tab_insert_idx_right = self.currentIndex() + 1
@ -562,7 +569,8 @@ class TabbedBrowser(tabwidget.TabWidget):
stop = config.get('colors', 'tabs.indicator.stop')
system = config.get('colors', 'tabs.indicator.system')
color = utils.interpolate_color(start, stop, perc, system)
self.tabBar().set_tab_indicator_color(idx, color)
self.set_tab_indicator_color(idx, color)
self.update_tab_title(idx)
if idx == self.currentIndex():
self.update_window_title()
@ -585,7 +593,8 @@ class TabbedBrowser(tabwidget.TabWidget):
stop = config.get('colors', 'tabs.indicator.stop')
system = config.get('colors', 'tabs.indicator.system')
color = utils.interpolate_color(start, stop, 100, system)
self.tabBar().set_tab_indicator_color(idx, color)
self.set_tab_indicator_color(idx, color)
self.update_tab_title(idx)
if idx == self.currentIndex():
self.update_window_title()

View File

@ -33,6 +33,7 @@ from PyQt5.QtGui import QIcon, QPalette, QColor
from qutebrowser.utils import qtutils, objreg, utils
from qutebrowser.config import config
from qutebrowser.browser import webview
PM_TabBarPadding = QStyle.PM_CustomBase
@ -47,6 +48,8 @@ class TabWidget(QTabWidget):
bar = TabBar(win_id)
self.setTabBar(bar)
bar.tabCloseRequested.connect(self.tabCloseRequested)
bar.tabMoved.connect(functools.partial(
QTimer.singleShot, 0, self.update_tab_titles))
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setDocumentMode(True)
self.setElideMode(Qt.ElideRight)
@ -68,6 +71,119 @@ class TabWidget(QTabWidget):
tabbar.setSelectionBehaviorOnRemove(selection_behaviour)
tabbar.refresh()
def set_tab_indicator_color(self, idx, color):
"""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))
def set_page_title(self, idx, title):
"""Set the tab title user data."""
self.tabBar().set_tab_data(idx, 'page-title', title.replace('&', '&&'))
self.update_tab_title(idx)
def page_title(self, idx):
"""Get the tab title user data."""
return self.tabBar().page_title(idx)
def update_tab_title(self, idx):
"""Update the tab text for the given tab."""
widget = self.widget(idx)
page_title = self.page_title(idx)
fields = {}
if widget.load_status == webview.LoadStatus.loading:
fields['perc'] = '[{}%] '.format(widget.progress)
else:
fields['perc'] = ''
fields['perc_raw'] = widget.progress
fields['title'] = page_title
fields['index'] = idx + 1
fields['id'] = widget.tab_id
fields['title_sep'] = ' - ' if page_title else ''
fmt = config.get('tabs', 'title-format')
self.tabBar().setTabText(idx, fmt.format(**fields))
@config.change_filter('tabs', 'title-format')
def update_tab_titles(self):
"""Update all texts."""
for idx in range(self.count()):
self.update_tab_title(idx)
def tabInserted(self, idx):
"""Update titles when a tab was inserted."""
super().tabInserted(idx)
self.update_tab_titles()
def tabRemoved(self, idx):
"""Update titles when a tab was removed."""
super().tabRemoved(idx)
self.update_tab_titles()
def addTab(self, page, icon_or_text, text_or_empty=None):
"""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.
"""
if text_or_empty is None:
icon = None
text = icon_or_text
new_idx = super().addTab(page, '')
else:
icon = icon_or_text
text = text_or_empty
new_idx = super().addTab(page, icon, '')
self.set_page_title(new_idx, text)
return new_idx
def insertTab(self, idx, page, icon_or_text, text_or_empty=None):
"""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
class TabBar(QTabBar):
@ -122,15 +238,38 @@ class TabBar(QTabBar):
else:
self.show()
def _set_tab_data(self, idx, key, value):
def set_tab_data(self, idx, key, value):
"""Set tab data as a dictionary."""
if not 0 <= idx < self.count():
raise IndexError("Tab index ({}) out of range ({})!".format(
idx, self.count()))
data = self.tabData(idx)
if data is None:
data = {}
data[key] = value
self.setTabData(idx, data)
def _tab_data(self, idx, key):
def tab_data(self, idx, key):
"""Get tab data for a given key."""
return self.tabData(idx)[key]
if not 0 <= idx < self.count():
raise IndexError("Tab index ({}) out of range ({})!".format(
idx, self.count()))
data = self.tabData(idx)
if data is None:
data = {}
return data[key]
def page_title(self, idx):
"""Get the tab title user data.
Args:
idx: The tab index to get the title for.
handle_unset: Whether to return an emtpy string on KeyError.
"""
try:
return self.tab_data(idx, 'page-title')
except KeyError:
return ''
def refresh(self):
"""Properly repaint the tab bar and relayout tabs."""
@ -138,16 +277,6 @@ class TabBar(QTabBar):
# 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._set_tab_data(idx, 'indicator-color', color)
self.update(self.tabRect(idx))
@config.change_filter('fonts', 'tabbar')
def set_font(self):
"""Set the tabbar font."""
@ -264,7 +393,7 @@ class TabBar(QTabBar):
tab.palette.setColor(QPalette.Window, bg_color)
tab.palette.setColor(QPalette.WindowText, fg_color)
try:
indicator_color = self._tab_data(idx, 'indicator-color')
indicator_color = self.tab_data(idx, 'indicator-color')
except KeyError:
indicator_color = QColor()
tab.palette.setColor(QPalette.Base, indicator_color)
@ -275,15 +404,67 @@ class TabBar(QTabBar):
p.drawControl(QStyle.CE_TabBarTab, tab)
def tabInserted(self, idx):
"""Show the tabbar if configured to hide and >1 tab is open."""
self._tabhide()
self.setTabData(idx, {})
"""Update visibility when a tab was inserted."""
super().tabInserted(idx)
self._tabhide()
def tabRemoved(self, idx):
"""Hide the tabbar if configured when only one tab is open."""
self._tabhide()
"""Update visibility when a tab was removed."""
super().tabRemoved(idx)
self._tabhide()
def addTab(self, icon_or_text, text_or_empty=None):
"""Override addTab to use our own text setting logic.
Unfortunately QTabBar::addTab has these two overloads:
- const QIcon & icon, const QString & label
- const QString & label
This means we'll get different arguments based on the chosen overload.
Args:
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().addTab('')
else:
icon = icon_or_text
text = text_or_empty
new_idx = super().addTab(icon, '')
self.set_page_title(new_idx, text)
def insertTab(self, idx, icon_or_text, text_or_empty=None):
"""Override insertTab to use our own text setting logic.
Unfortunately QTabBar::insertTab has these two overloads:
- int index, const QIcon & icon, const QString & label
- int index, const QString & label
This means we'll get different arguments based on the chosen overload.
Args:
idx: Where to insert the widget.
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, '')
else:
icon = icon_or_text
text = text_or_empty
new_idx = super().insertTab(idx, icon, '')
self.set_page_title(new_idx, text)
class TabBarStyle(QCommonStyle):