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

View File

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

View File

@ -114,6 +114,7 @@ class TabbedBrowser(tabwidget.TabWidget):
self.setIconSize(QSize(12, 12)) self.setIconSize(QSize(12, 12))
objreg.get('config').changed.connect(self.update_favicons) objreg.get('config').changed.connect(self.update_favicons)
objreg.get('config').changed.connect(self.update_window_title) objreg.get('config').changed.connect(self.update_window_title)
objreg.get('config').changed.connect(self.update_tab_titles)
def __repr__(self): def __repr__(self):
return utils.get_repr(self, count=self.count()) return utils.get_repr(self, count=self.count())
@ -133,7 +134,11 @@ class TabbedBrowser(tabwidget.TabWidget):
def update_window_title(self): def update_window_title(self):
"""Change the window title to match the current tab.""" """Change the window title to match the current tab."""
idx = self.currentIndex() 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) widget = self.widget(idx)
fields = {} fields = {}
@ -144,6 +149,7 @@ class TabbedBrowser(tabwidget.TabWidget):
fields['perc_raw'] = widget.progress fields['perc_raw'] = widget.progress
fields['title'] = tabtitle fields['title'] = tabtitle
fields['title_sep'] = ' - ' if tabtitle else '' fields['title_sep'] = ' - ' if tabtitle else ''
fields['id'] = self._win_id
fmt = config.get('ui', 'window-title-format') fmt = config.get('ui', 'window-title-format')
self.window().setWindowTitle(fmt.format(**fields)) self.window().setWindowTitle(fmt.format(**fields))
@ -429,6 +435,7 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted... # We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab)) log.webview.debug("Got invalid tab {}!".format(tab))
return return
self.update_tab_title(idx)
if tab.keep_icon: if tab.keep_icon:
tab.keep_icon = False tab.keep_icon = False
else: else:
@ -468,7 +475,7 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted... # We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab)) log.webview.debug("Got invalid tab {}!".format(tab))
return return
self.setTabText(idx, text.replace('&', '&&')) self.set_page_title(idx, text)
if idx == self.currentIndex(): if idx == self.currentIndex():
self.update_window_title() self.update_window_title()
@ -489,8 +496,8 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted... # We can get signals for tabs we already deleted...
log.webview.debug("Got invalid tab {}!".format(tab)) log.webview.debug("Got invalid tab {}!".format(tab))
return return
if not self.tabText(idx): if not self.page_title(idx):
self.setTabText(idx, url) self.set_page_title(idx, url)
@pyqtSlot(webview.WebView) @pyqtSlot(webview.WebView)
def on_icon_changed(self, tab): def on_icon_changed(self, tab):
@ -542,7 +549,7 @@ class TabbedBrowser(tabwidget.TabWidget):
scope='window', window=self._win_id) scope='window', window=self._win_id)
self._now_focused = tab self._now_focused = tab
self.current_tab_changed.emit(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_left = self.currentIndex()
self._tab_insert_idx_right = self.currentIndex() + 1 self._tab_insert_idx_right = self.currentIndex() + 1
@ -562,7 +569,8 @@ class TabbedBrowser(tabwidget.TabWidget):
stop = config.get('colors', 'tabs.indicator.stop') stop = config.get('colors', 'tabs.indicator.stop')
system = config.get('colors', 'tabs.indicator.system') system = config.get('colors', 'tabs.indicator.system')
color = utils.interpolate_color(start, stop, perc, 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(): if idx == self.currentIndex():
self.update_window_title() self.update_window_title()
@ -585,7 +593,8 @@ class TabbedBrowser(tabwidget.TabWidget):
stop = config.get('colors', 'tabs.indicator.stop') stop = config.get('colors', 'tabs.indicator.stop')
system = config.get('colors', 'tabs.indicator.system') system = config.get('colors', 'tabs.indicator.system')
color = utils.interpolate_color(start, stop, 100, 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(): if idx == self.currentIndex():
self.update_window_title() 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.utils import qtutils, objreg, utils
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.browser import webview
PM_TabBarPadding = QStyle.PM_CustomBase PM_TabBarPadding = QStyle.PM_CustomBase
@ -47,6 +48,8 @@ class TabWidget(QTabWidget):
bar = TabBar(win_id) bar = TabBar(win_id)
self.setTabBar(bar) self.setTabBar(bar)
bar.tabCloseRequested.connect(self.tabCloseRequested) bar.tabCloseRequested.connect(self.tabCloseRequested)
bar.tabMoved.connect(functools.partial(
QTimer.singleShot, 0, self.update_tab_titles))
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setDocumentMode(True) self.setDocumentMode(True)
self.setElideMode(Qt.ElideRight) self.setElideMode(Qt.ElideRight)
@ -68,6 +71,119 @@ class TabWidget(QTabWidget):
tabbar.setSelectionBehaviorOnRemove(selection_behaviour) tabbar.setSelectionBehaviorOnRemove(selection_behaviour)
tabbar.refresh() 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): class TabBar(QTabBar):
@ -122,15 +238,38 @@ class TabBar(QTabBar):
else: else:
self.show() self.show()
def _set_tab_data(self, idx, key, value): def set_tab_data(self, idx, key, value):
"""Set tab data as a dictionary.""" """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) data = self.tabData(idx)
if data is None:
data = {}
data[key] = value data[key] = value
self.setTabData(idx, data) self.setTabData(idx, data)
def _tab_data(self, idx, key): def tab_data(self, idx, key):
"""Get tab data for a given 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): def refresh(self):
"""Properly repaint the tab bar and relayout tabs.""" """Properly repaint the tab bar and relayout tabs."""
@ -138,16 +277,6 @@ class TabBar(QTabBar):
# code sets layoutDirty so it actually relayouts the tabs. # code sets layoutDirty so it actually relayouts the tabs.
self.setIconSize(self.iconSize()) 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') @config.change_filter('fonts', 'tabbar')
def set_font(self): def set_font(self):
"""Set the tabbar font.""" """Set the tabbar font."""
@ -264,7 +393,7 @@ class TabBar(QTabBar):
tab.palette.setColor(QPalette.Window, bg_color) tab.palette.setColor(QPalette.Window, bg_color)
tab.palette.setColor(QPalette.WindowText, fg_color) tab.palette.setColor(QPalette.WindowText, fg_color)
try: try:
indicator_color = self._tab_data(idx, 'indicator-color') indicator_color = self.tab_data(idx, 'indicator-color')
except KeyError: except KeyError:
indicator_color = QColor() indicator_color = QColor()
tab.palette.setColor(QPalette.Base, indicator_color) tab.palette.setColor(QPalette.Base, indicator_color)
@ -275,15 +404,67 @@ class TabBar(QTabBar):
p.drawControl(QStyle.CE_TabBarTab, tab) p.drawControl(QStyle.CE_TabBarTab, tab)
def tabInserted(self, idx): def tabInserted(self, idx):
"""Show the tabbar if configured to hide and >1 tab is open.""" """Update visibility when a tab was inserted."""
self._tabhide()
self.setTabData(idx, {})
super().tabInserted(idx) super().tabInserted(idx)
self._tabhide()
def tabRemoved(self, idx): def tabRemoved(self, idx):
"""Hide the tabbar if configured when only one tab is open.""" """Update visibility when a tab was removed."""
self._tabhide()
super().tabRemoved(idx) 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): class TabBarStyle(QCommonStyle):