Split statusbar into multiple files
This commit is contained in:
parent
6020fa4b81
commit
55ea24e431
@ -24,7 +24,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$
|
||||
inlinevar-rgx=[a-z_][a-z0-9_]*$
|
||||
bad-names=foo,bar,baz,tmp
|
||||
bad-names=foo,tmp
|
||||
|
||||
[FORMAT]
|
||||
max-line-length=79
|
||||
|
@ -1,723 +0,0 @@
|
||||
# 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/>.
|
||||
|
||||
"""Widgets needed in the qutebrowser statusbar."""
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt
|
||||
from PyQt5.QtWidgets import (QWidget, QLineEdit, QProgressBar, QLabel,
|
||||
QHBoxLayout, QStackedLayout, QSizePolicy)
|
||||
from PyQt5.QtGui import QPainter, QValidator
|
||||
|
||||
import qutebrowser.keyinput.modeman as modeman
|
||||
import qutebrowser.commands.utils as cmdutils
|
||||
from qutebrowser.keyinput.modeparsers import STARTCHARS
|
||||
from qutebrowser.config.style import set_register_stylesheet, get_stylesheet
|
||||
from qutebrowser.utils.url import urlstring
|
||||
from qutebrowser.commands.managers import split_cmdline
|
||||
from qutebrowser.models.cmdhistory import (History, HistoryEmptyError,
|
||||
HistoryEndReachedError)
|
||||
|
||||
|
||||
class StatusBar(QWidget):
|
||||
|
||||
"""The statusbar at the bottom of the mainwindow.
|
||||
|
||||
Class attributes:
|
||||
STYLESHEET: The stylesheet template.
|
||||
|
||||
Attributes:
|
||||
cmd: The Command widget in the statusbar.
|
||||
txt: The Text widget in the statusbar.
|
||||
keystring: The KeyString widget in the statusbar.
|
||||
percentage: The Percentage widget in the statusbar.
|
||||
url: The Url widget in the statusbar.
|
||||
prog: The Progress widget in the statusbar.
|
||||
_hbox: The main QHBoxLayout.
|
||||
_stack: The QStackedLayout with cmd/txt widgets.
|
||||
|
||||
Class attributes:
|
||||
_error: If there currently is an error, accessed through the error
|
||||
property.
|
||||
|
||||
For some reason we need to have this as class attribute so
|
||||
pyqtProperty works correctly.
|
||||
|
||||
Signals:
|
||||
resized: Emitted when the statusbar has resized, so the completion
|
||||
widget can adjust its size to it.
|
||||
arg: The new size.
|
||||
moved: Emitted when the statusbar has moved, so the completion widget
|
||||
can move the the right position.
|
||||
arg: The new position.
|
||||
"""
|
||||
|
||||
resized = pyqtSignal('QRect')
|
||||
moved = pyqtSignal('QPoint')
|
||||
_error = False
|
||||
|
||||
STYLESHEET = """
|
||||
QWidget#StatusBar[error="false"] {{
|
||||
{color[statusbar.bg]}
|
||||
}}
|
||||
|
||||
QWidget#StatusBar[error="true"] {{
|
||||
{color[statusbar.bg.error]}
|
||||
}}
|
||||
|
||||
QWidget {{
|
||||
{color[statusbar.fg]}
|
||||
{font[statusbar]}
|
||||
}}
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
self.setAttribute(Qt.WA_StyledBackground)
|
||||
set_register_stylesheet(self)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
|
||||
self._option = None
|
||||
|
||||
self._hbox = QHBoxLayout(self)
|
||||
self._hbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._hbox.setSpacing(5)
|
||||
|
||||
self._stack = QStackedLayout()
|
||||
self._stack.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.cmd = _Command(self)
|
||||
self._stack.addWidget(self.cmd)
|
||||
|
||||
self.txt = _Text(self)
|
||||
self._stack.addWidget(self.txt)
|
||||
|
||||
self.cmd.show_cmd.connect(self._show_cmd_widget)
|
||||
self.cmd.hide_cmd.connect(self._hide_cmd_widget)
|
||||
self._hide_cmd_widget()
|
||||
|
||||
self._hbox.addLayout(self._stack)
|
||||
|
||||
self.keystring = _KeyString(self)
|
||||
self._hbox.addWidget(self.keystring)
|
||||
|
||||
self.url = _Url(self)
|
||||
self._hbox.addWidget(self.url)
|
||||
|
||||
self.percentage = _Percentage(self)
|
||||
self._hbox.addWidget(self.percentage)
|
||||
|
||||
self.prog = _Progress(self)
|
||||
self._hbox.addWidget(self.prog)
|
||||
|
||||
@pyqtProperty(bool)
|
||||
def error(self):
|
||||
"""Getter for self.error, so it can be used as Qt property."""
|
||||
# pylint: disable=method-hidden
|
||||
return self._error
|
||||
|
||||
@error.setter
|
||||
def error(self, val):
|
||||
"""Setter for self.error, so it can be used as Qt property.
|
||||
|
||||
Re-set the stylesheet after setting the value, so everything gets
|
||||
updated by Qt properly.
|
||||
"""
|
||||
self._error = val
|
||||
self.setStyleSheet(get_stylesheet(self.STYLESHEET))
|
||||
|
||||
def _show_cmd_widget(self):
|
||||
"""Show command widget instead of temporary text."""
|
||||
self._stack.setCurrentWidget(self.cmd)
|
||||
self.clear_error()
|
||||
|
||||
def _hide_cmd_widget(self):
|
||||
"""Show temporary text instead of command widget."""
|
||||
self._stack.setCurrentWidget(self.txt)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def disp_error(self, text):
|
||||
"""Display an error in the statusbar."""
|
||||
self.error = True
|
||||
self.txt.errortext = text
|
||||
|
||||
@pyqtSlot()
|
||||
def clear_error(self):
|
||||
"""Clear a displayed error from the status bar."""
|
||||
self.error = False
|
||||
self.txt.errortext = ''
|
||||
|
||||
@pyqtSlot('QKeyEvent')
|
||||
def on_key_pressed(self, e):
|
||||
"""Hide temporary error message if a key was pressed.
|
||||
|
||||
Args:
|
||||
e: The original QKeyEvent.
|
||||
"""
|
||||
if e.key() in [Qt.Key_Control, Qt.Key_Alt, Qt.Key_Shift, Qt.Key_Meta]:
|
||||
# Only modifier pressed, don't hide yet.
|
||||
return
|
||||
self.txt.set_temptext('')
|
||||
self.clear_error()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_mode_entered(self, mode):
|
||||
"""Mark certain modes in the commandline."""
|
||||
if mode in modeman.instance().passthrough:
|
||||
self.txt.normaltext = "-- {} MODE --".format(mode.upper())
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear marked mode."""
|
||||
if mode in modeman.instance().passthrough:
|
||||
self.txt.normaltext = ""
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.
|
||||
|
||||
Args:
|
||||
e: The QResizeEvent.
|
||||
|
||||
Emit:
|
||||
resized: Always emitted.
|
||||
"""
|
||||
super().resizeEvent(e)
|
||||
self.resized.emit(self.geometry())
|
||||
|
||||
def moveEvent(self, e):
|
||||
"""Extend moveEvent of QWidget to emit a moved signal afterwards.
|
||||
|
||||
Args:
|
||||
e: The QMoveEvent.
|
||||
|
||||
Emit:
|
||||
moved: Always emitted.
|
||||
"""
|
||||
super().moveEvent(e)
|
||||
self.moved.emit(e.pos())
|
||||
|
||||
|
||||
class _Command(QLineEdit):
|
||||
|
||||
"""The commandline part of the statusbar.
|
||||
|
||||
Attributes:
|
||||
history: The command history object.
|
||||
_statusbar: The statusbar (parent) QWidget.
|
||||
_validator: The current command validator.
|
||||
|
||||
Signals:
|
||||
got_cmd: Emitted when a command is triggered by the user.
|
||||
arg: The command string.
|
||||
got_search: Emitted when the user started a new search.
|
||||
arg: The search term.
|
||||
got_rev_search: Emitted when the user started a new reverse search.
|
||||
arg: The search term.
|
||||
clear_completion_selection: Emitted before the completion widget is
|
||||
hidden.
|
||||
hide_completion: Emitted when the completion widget should be hidden.
|
||||
show_cmd: Emitted when command input should be shown.
|
||||
hide_cmd: Emitted when command input can be hidden.
|
||||
"""
|
||||
|
||||
got_cmd = pyqtSignal(str)
|
||||
got_search = pyqtSignal(str)
|
||||
got_search_rev = pyqtSignal(str)
|
||||
clear_completion_selection = pyqtSignal()
|
||||
hide_completion = pyqtSignal()
|
||||
show_cmd = pyqtSignal()
|
||||
hide_cmd = pyqtSignal()
|
||||
|
||||
# FIXME won't the tab key switch to the next widget?
|
||||
# See http://www.saltycrane.com/blog/2008/01/how-to-capture-tab-key-press-event-with/
|
||||
# for a possible fix.
|
||||
|
||||
def __init__(self, statusbar):
|
||||
super().__init__(statusbar)
|
||||
self._statusbar = statusbar
|
||||
self.setStyleSheet("""
|
||||
QLineEdit {
|
||||
border: 0px;
|
||||
padding-left: 1px;
|
||||
background-color: transparent;
|
||||
}
|
||||
""")
|
||||
self.history = History()
|
||||
self._validator = _CommandValidator(self)
|
||||
self.setValidator(self._validator)
|
||||
self.textEdited.connect(self.history.stop)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_cmd_text(self, text):
|
||||
"""Preset the statusbar to some text.
|
||||
|
||||
Args:
|
||||
text: The text to set (string).
|
||||
|
||||
Emit:
|
||||
textEdited: Emitted if the text changed.
|
||||
"""
|
||||
old_text = self.text()
|
||||
self.setText(text)
|
||||
if old_text != text:
|
||||
# We want the completion to pop out here.
|
||||
self.textEdited.emit(text)
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_change_completed_part(self, newtext):
|
||||
"""Change the part we're currently completing in the commandline.
|
||||
|
||||
Args:
|
||||
text: The text to set (string).
|
||||
"""
|
||||
# FIXME we should consider the cursor position.
|
||||
text = self.text()
|
||||
if text[0] in STARTCHARS:
|
||||
prefix = text[0]
|
||||
text = text[1:]
|
||||
else:
|
||||
prefix = ''
|
||||
parts = split_cmdline(text)
|
||||
logging.debug("Old text: '{}' - parts: '{}', changing to '{}".format(
|
||||
text, parts, newtext))
|
||||
parts[-1] = newtext
|
||||
self.setText(prefix + ' '.join(parts))
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
|
||||
modes=['command'])
|
||||
def command_history_prev(self):
|
||||
"""Handle Up presses (go back in history)."""
|
||||
try:
|
||||
if not self.history.browsing:
|
||||
item = self.history.start(self.text().strip())
|
||||
else:
|
||||
item = self.history.previtem()
|
||||
except (HistoryEmptyError, HistoryEndReachedError):
|
||||
return
|
||||
if item:
|
||||
self.set_cmd_text(item)
|
||||
|
||||
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
|
||||
modes=['command'])
|
||||
def command_history_next(self):
|
||||
"""Handle Down presses (go forward in history)."""
|
||||
if not self.history.browsing:
|
||||
return
|
||||
try:
|
||||
item = self.history.nextitem()
|
||||
except HistoryEndReachedError:
|
||||
return
|
||||
if item:
|
||||
self.set_cmd_text(item)
|
||||
|
||||
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
|
||||
modes=['command'])
|
||||
def command_accept(self):
|
||||
"""Handle the command in the status bar.
|
||||
|
||||
Emit:
|
||||
got_cmd: If a new cmd was entered.
|
||||
got_search: If a new search was entered.
|
||||
got_search_rev: If a new reverse search was entered.
|
||||
"""
|
||||
signals = {
|
||||
':': self.got_cmd,
|
||||
'/': self.got_search,
|
||||
'?': self.got_search_rev,
|
||||
}
|
||||
text = self.text()
|
||||
self.history.append(text)
|
||||
modeman.leave('command', 'cmd accept')
|
||||
if text[0] in signals:
|
||||
signals[text[0]].emit(text.lstrip(text[0]))
|
||||
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear up when ommand mode was left.
|
||||
|
||||
- Clear the statusbar text if it's explicitely unfocused.
|
||||
- Clear completion selection
|
||||
- Hide completion
|
||||
|
||||
Args:
|
||||
mode: The mode which was left.
|
||||
|
||||
Emit:
|
||||
clear_completion_selection: Always emitted.
|
||||
hide_completion: Always emitted so the completion is hidden.
|
||||
"""
|
||||
if mode == "command":
|
||||
self.setText('')
|
||||
self.history.stop()
|
||||
self.hide_cmd.emit()
|
||||
self.clear_completion_selection.emit()
|
||||
self.hide_completion.emit()
|
||||
|
||||
def focusInEvent(self, e):
|
||||
"""Extend focusInEvent to enter command mode."""
|
||||
modeman.enter('command', 'cmd focus')
|
||||
super().focusInEvent(e)
|
||||
|
||||
|
||||
class _CommandValidator(QValidator):
|
||||
|
||||
"""Validator to prevent the : from getting deleted."""
|
||||
|
||||
def validate(self, string, pos):
|
||||
"""Override QValidator::validate.
|
||||
|
||||
Args:
|
||||
string: The string to validate.
|
||||
pos: The current curser position.
|
||||
|
||||
Return:
|
||||
A tuple (status, string, pos) as a QValidator should.
|
||||
"""
|
||||
if any(string.startswith(c) for c in STARTCHARS):
|
||||
return (QValidator.Acceptable, string, pos)
|
||||
else:
|
||||
return (QValidator.Invalid, string, pos)
|
||||
|
||||
|
||||
class _Progress(QProgressBar):
|
||||
|
||||
"""The progress bar part of the status bar.
|
||||
|
||||
Class attributes:
|
||||
STYLESHEET: The stylesheet template.
|
||||
"""
|
||||
|
||||
# FIXME for some reason, margin-left is not shown
|
||||
STYLESHEET = """
|
||||
QProgressBar {{
|
||||
border-radius: 0px;
|
||||
border: 2px solid transparent;
|
||||
margin-left: 1px;
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QProgressBar::chunk {{
|
||||
{color[statusbar.progress.bg]}
|
||||
}}
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
set_register_stylesheet(self)
|
||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored)
|
||||
self.setTextVisible(False)
|
||||
self.hide()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_load_started(self):
|
||||
"""Clear old error and show progress, used as slot to loadStarted."""
|
||||
self.setValue(0)
|
||||
self.show()
|
||||
|
||||
|
||||
class TextBase(QLabel):
|
||||
|
||||
"""A text in the statusbar.
|
||||
|
||||
Unlike QLabel, the text will get elided.
|
||||
|
||||
Eliding is loosly based on
|
||||
http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html
|
||||
|
||||
Attributes:
|
||||
_elidemode: Where to elide the text.
|
||||
_elided_text: The current elided text.
|
||||
"""
|
||||
|
||||
def __init__(self, bar, elidemode=Qt.ElideRight):
|
||||
super().__init__(bar)
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
||||
self._elidemode = elidemode
|
||||
self._elided_text = ''
|
||||
|
||||
def _update_elided_text(self, width):
|
||||
"""Update the elided text when necessary.
|
||||
|
||||
Args:
|
||||
width: The maximal width the text should take.
|
||||
"""
|
||||
self._elided_text = self.fontMetrics().elidedText(
|
||||
self.text(), self._elidemode, width, Qt.TextShowMnemonic)
|
||||
|
||||
def setText(self, txt):
|
||||
"""Extend QLabel::setText.
|
||||
|
||||
This update the elided text after setting the text, and also works
|
||||
around a weird QLabel redrawing bug where it doesn't redraw correctly
|
||||
when the text is empty -- we explicitely need to call repaint() to
|
||||
resolve this. See http://stackoverflow.com/q/21890462/2085149
|
||||
|
||||
Args:
|
||||
txt: The text to set (string).
|
||||
"""
|
||||
super().setText(txt)
|
||||
self._update_elided_text(self.geometry().width())
|
||||
if not txt:
|
||||
self.repaint()
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Extend QLabel::resizeEvent to update the elided text afterwards."""
|
||||
super().resizeEvent(e)
|
||||
self._update_elided_text(e.size().width())
|
||||
|
||||
def paintEvent(self, e):
|
||||
"""Override QLabel::paintEvent to draw elided text."""
|
||||
if self._elidemode == Qt.ElideNone:
|
||||
super().paintEvent(e)
|
||||
else:
|
||||
painter = QPainter(self)
|
||||
painter.drawText(0, 0, self.geometry().width(),
|
||||
self.geometry().height(), self.alignment(),
|
||||
self._elided_text)
|
||||
|
||||
|
||||
class _Text(TextBase):
|
||||
|
||||
"""Text displayed in the statusbar.
|
||||
|
||||
Attributes:
|
||||
normaltext: The "permanent" text. Never automatically cleared.
|
||||
temptext: The temporary text. Cleared on a keystroke.
|
||||
errortext: The error text. Cleared on a keystroke.
|
||||
_initializing: True if we're currently in __init__ and no text should
|
||||
be updated yet.
|
||||
|
||||
The errortext has the highest priority, i.e. it will always be shown
|
||||
when it is set. The temptext is shown when there is no error, and the
|
||||
(permanent) text is shown when there is neither a temporary text nor an
|
||||
error.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._initializing = True
|
||||
self.normaltext = ''
|
||||
self.temptext = ''
|
||||
self.errortext = ''
|
||||
self._initializing = False
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
"""Overwrite __setattr__ to call _update_text when needed."""
|
||||
super().__setattr__(name, val)
|
||||
if not name.startswith('_') and not self._initializing:
|
||||
self._update_text()
|
||||
|
||||
def _update_text(self):
|
||||
"""Update QLabel text when needed.
|
||||
|
||||
Called from __setattr__ if a text property changed.
|
||||
"""
|
||||
for text in [self.errortext, self.temptext, self.normaltext]:
|
||||
if text:
|
||||
self.setText(text)
|
||||
break
|
||||
else:
|
||||
self.setText('')
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_normaltext(self, val):
|
||||
"""Setter for normaltext, to be used as Qt slot."""
|
||||
self.normaltext = val
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_statusbar_message(self, val):
|
||||
"""Called when javascript tries to set a statusbar message.
|
||||
|
||||
For some reason, this is emitted a lot with an empty string during page
|
||||
load, so we currently ignore these and thus don't support clearing the
|
||||
message, which is a bit unfortunate...
|
||||
"""
|
||||
if val:
|
||||
self.temptext = val
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_temptext(self, val):
|
||||
"""Setter for temptext, to be used as Qt slot."""
|
||||
self.temptext = val
|
||||
|
||||
|
||||
class _KeyString(TextBase):
|
||||
|
||||
"""Keychain string displayed in the statusbar."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class _Percentage(TextBase):
|
||||
|
||||
"""Reading percentage displayed in the statusbar."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Constructor. Set percentage to 0%."""
|
||||
super().__init__(parent)
|
||||
self.set_perc(0, 0)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def set_perc(self, _, y):
|
||||
"""Setter to be used as a Qt slot.
|
||||
|
||||
Args:
|
||||
_: The x percentage (int), currently ignored.
|
||||
y: The y percentage (int)
|
||||
"""
|
||||
if y == 0:
|
||||
self.setText('[top]')
|
||||
elif y == 100:
|
||||
self.setText('[bot]')
|
||||
else:
|
||||
self.setText('[{:2}%]'.format(y))
|
||||
|
||||
|
||||
class _Url(TextBase):
|
||||
|
||||
"""URL displayed in the statusbar.
|
||||
|
||||
Class attributes:
|
||||
STYLESHEET: The stylesheet template.
|
||||
|
||||
Attributes:
|
||||
_old_url: The URL displayed before the hover URL.
|
||||
_old_urltype: The type of the URL displayed before the hover URL.
|
||||
_ssl_errors: Whether SSL errors occured while loading.
|
||||
|
||||
Class attributes:
|
||||
_urltype: The current URL type. One of normal/ok/error/warn/hover.
|
||||
Accessed via the urltype property.
|
||||
|
||||
For some reason we need to have this as class attribute so
|
||||
pyqtProperty works correctly.
|
||||
"""
|
||||
|
||||
_urltype = None
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel#_Url[urltype="normal"] {{
|
||||
{color[statusbar.url.fg]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="success"] {{
|
||||
{color[statusbar.url.fg.success]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="error"] {{
|
||||
{color[statusbar.url.fg.error]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="warn"] {{
|
||||
{color[statusbar.url.fg.warn]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="hover"] {{
|
||||
{color[statusbar.url.fg.hover]}
|
||||
}}
|
||||
"""
|
||||
|
||||
def __init__(self, bar, elidemode=Qt.ElideMiddle):
|
||||
"""Override TextBase::__init__ to elide in the middle by default.
|
||||
|
||||
Args:
|
||||
bar: The statusbar (parent) object.
|
||||
elidemode: How to elide the text.
|
||||
"""
|
||||
super().__init__(bar, elidemode)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
set_register_stylesheet(self)
|
||||
self._old_urltype = None
|
||||
self._old_url = None
|
||||
self._ssl_errors = False
|
||||
|
||||
@pyqtProperty(str)
|
||||
def urltype(self):
|
||||
"""Getter for self.urltype, so it can be used as Qt property."""
|
||||
# pylint: disable=method-hidden
|
||||
return self._urltype
|
||||
|
||||
@urltype.setter
|
||||
def urltype(self, val):
|
||||
"""Setter for self.urltype, so it can be used as Qt property."""
|
||||
self._urltype = val
|
||||
self.setStyleSheet(get_stylesheet(self.STYLESHEET))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_loading_started(self):
|
||||
"""Slot to clear SSL errors when loading started."""
|
||||
self._ssl_errors = False
|
||||
|
||||
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
|
||||
def on_ssl_errors(self, _reply, _errors):
|
||||
self._ssl_errors = True
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_loading_finished(self, ok):
|
||||
"""Slot for cur_loading_finished. Colors the URL according to ok.
|
||||
|
||||
Args:
|
||||
ok: Whether loading finished successfully (True) or not (False).
|
||||
"""
|
||||
if ok and not self._ssl_errors:
|
||||
self.urltype = 'success'
|
||||
elif ok:
|
||||
self.urltype = 'warn'
|
||||
else:
|
||||
self.urltype = 'error'
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_url(self, s):
|
||||
"""Setter to be used as a Qt slot.
|
||||
|
||||
Args:
|
||||
s: The URL to set.
|
||||
"""
|
||||
self.setText(urlstring(s))
|
||||
self.urltype = 'normal'
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def set_hover_url(self, link, _title, _text):
|
||||
"""Setter to be used as a Qt slot.
|
||||
|
||||
Saves old shown URL in self._old_url and restores it later if a link is
|
||||
"un-hovered" when it gets called with empty parameters.
|
||||
|
||||
Args:
|
||||
link: The link which was hovered (string)
|
||||
_title: The title of the hovered link (string)
|
||||
_text: The text of the hovered link (string)
|
||||
"""
|
||||
if link:
|
||||
if self._old_url is None:
|
||||
self._old_url = self.text()
|
||||
if self._old_urltype is None:
|
||||
self._old_urltype = self._urltype
|
||||
self.urltype = 'hover'
|
||||
self.setText(link)
|
||||
else:
|
||||
self.setText(self._old_url)
|
||||
self.urltype = self._old_urltype
|
||||
self._old_url = None
|
||||
self._old_urltype = None
|
@ -24,7 +24,7 @@ from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QCoreApplication
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||
from PyQt5.QtWebKitWidgets import QWebInspector
|
||||
|
||||
from qutebrowser.widgets._statusbar import StatusBar
|
||||
from qutebrowser.widgets.statusbar.bar import StatusBar
|
||||
from qutebrowser.widgets._tabbedbrowser import TabbedBrowser
|
||||
from qutebrowser.widgets._completion import CompletionView
|
||||
import qutebrowser.commands.utils as cmdutils
|
||||
|
18
qutebrowser/widgets/statusbar/__init__.py
Normal file
18
qutebrowser/widgets/statusbar/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
# 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/>.
|
||||
|
||||
"""The Qt widgets needed by qutebrowser."""
|
217
qutebrowser/widgets/statusbar/_command.py
Normal file
217
qutebrowser/widgets/statusbar/_command.py
Normal file
@ -0,0 +1,217 @@
|
||||
# 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/>.
|
||||
|
||||
"""The commandline in the statusbar."""
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QLineEdit, QSizePolicy
|
||||
from PyQt5.QtGui import QValidator
|
||||
|
||||
import qutebrowser.keyinput.modeman as modeman
|
||||
import qutebrowser.commands.utils as cmdutils
|
||||
from qutebrowser.commands.managers import split_cmdline
|
||||
from qutebrowser.keyinput.modeparsers import STARTCHARS
|
||||
from qutebrowser.models.cmdhistory import (History, HistoryEmptyError,
|
||||
HistoryEndReachedError)
|
||||
|
||||
|
||||
class Command(QLineEdit):
|
||||
|
||||
"""The commandline part of the statusbar.
|
||||
|
||||
Attributes:
|
||||
history: The command history object.
|
||||
_statusbar: The statusbar (parent) QWidget.
|
||||
_validator: The current command validator.
|
||||
|
||||
Signals:
|
||||
got_cmd: Emitted when a command is triggered by the user.
|
||||
arg: The command string.
|
||||
got_search: Emitted when the user started a new search.
|
||||
arg: The search term.
|
||||
got_rev_search: Emitted when the user started a new reverse search.
|
||||
arg: The search term.
|
||||
clear_completion_selection: Emitted before the completion widget is
|
||||
hidden.
|
||||
hide_completion: Emitted when the completion widget should be hidden.
|
||||
show_cmd: Emitted when command input should be shown.
|
||||
hide_cmd: Emitted when command input can be hidden.
|
||||
"""
|
||||
|
||||
got_cmd = pyqtSignal(str)
|
||||
got_search = pyqtSignal(str)
|
||||
got_search_rev = pyqtSignal(str)
|
||||
clear_completion_selection = pyqtSignal()
|
||||
hide_completion = pyqtSignal()
|
||||
show_cmd = pyqtSignal()
|
||||
hide_cmd = pyqtSignal()
|
||||
|
||||
# FIXME won't the tab key switch to the next widget?
|
||||
# See http://www.saltycrane.com/blog/2008/01/how-to-capture-tab-key-press-event-with/
|
||||
# for a possible fix.
|
||||
|
||||
def __init__(self, statusbar):
|
||||
super().__init__(statusbar)
|
||||
self._statusbar = statusbar
|
||||
self.setStyleSheet("""
|
||||
QLineEdit {
|
||||
border: 0px;
|
||||
padding-left: 1px;
|
||||
background-color: transparent;
|
||||
}
|
||||
""")
|
||||
self.history = History()
|
||||
self._validator = _CommandValidator(self)
|
||||
self.setValidator(self._validator)
|
||||
self.textEdited.connect(self.history.stop)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_cmd_text(self, text):
|
||||
"""Preset the statusbar to some text.
|
||||
|
||||
Args:
|
||||
text: The text to set (string).
|
||||
|
||||
Emit:
|
||||
textEdited: Emitted if the text changed.
|
||||
"""
|
||||
old_text = self.text()
|
||||
self.setText(text)
|
||||
if old_text != text:
|
||||
# We want the completion to pop out here.
|
||||
self.textEdited.emit(text)
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_change_completed_part(self, newtext):
|
||||
"""Change the part we're currently completing in the commandline.
|
||||
|
||||
Args:
|
||||
text: The text to set (string).
|
||||
"""
|
||||
# FIXME we should consider the cursor position.
|
||||
text = self.text()
|
||||
if text[0] in STARTCHARS:
|
||||
prefix = text[0]
|
||||
text = text[1:]
|
||||
else:
|
||||
prefix = ''
|
||||
parts = split_cmdline(text)
|
||||
logging.debug("Old text: '{}' - parts: '{}', changing to '{}".format(
|
||||
text, parts, newtext))
|
||||
parts[-1] = newtext
|
||||
self.setText(prefix + ' '.join(parts))
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
|
||||
modes=['command'])
|
||||
def command_history_prev(self):
|
||||
"""Handle Up presses (go back in history)."""
|
||||
try:
|
||||
if not self.history.browsing:
|
||||
item = self.history.start(self.text().strip())
|
||||
else:
|
||||
item = self.history.previtem()
|
||||
except (HistoryEmptyError, HistoryEndReachedError):
|
||||
return
|
||||
if item:
|
||||
self.set_cmd_text(item)
|
||||
|
||||
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
|
||||
modes=['command'])
|
||||
def command_history_next(self):
|
||||
"""Handle Down presses (go forward in history)."""
|
||||
if not self.history.browsing:
|
||||
return
|
||||
try:
|
||||
item = self.history.nextitem()
|
||||
except HistoryEndReachedError:
|
||||
return
|
||||
if item:
|
||||
self.set_cmd_text(item)
|
||||
|
||||
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
|
||||
modes=['command'])
|
||||
def command_accept(self):
|
||||
"""Handle the command in the status bar.
|
||||
|
||||
Emit:
|
||||
got_cmd: If a new cmd was entered.
|
||||
got_search: If a new search was entered.
|
||||
got_search_rev: If a new reverse search was entered.
|
||||
"""
|
||||
signals = {
|
||||
':': self.got_cmd,
|
||||
'/': self.got_search,
|
||||
'?': self.got_search_rev,
|
||||
}
|
||||
text = self.text()
|
||||
self.history.append(text)
|
||||
modeman.leave('command', 'cmd accept')
|
||||
if text[0] in signals:
|
||||
signals[text[0]].emit(text.lstrip(text[0]))
|
||||
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear up when ommand mode was left.
|
||||
|
||||
- Clear the statusbar text if it's explicitely unfocused.
|
||||
- Clear completion selection
|
||||
- Hide completion
|
||||
|
||||
Args:
|
||||
mode: The mode which was left.
|
||||
|
||||
Emit:
|
||||
clear_completion_selection: Always emitted.
|
||||
hide_completion: Always emitted so the completion is hidden.
|
||||
"""
|
||||
if mode == "command":
|
||||
self.setText('')
|
||||
self.history.stop()
|
||||
self.hide_cmd.emit()
|
||||
self.clear_completion_selection.emit()
|
||||
self.hide_completion.emit()
|
||||
|
||||
def focusInEvent(self, e):
|
||||
"""Extend focusInEvent to enter command mode."""
|
||||
modeman.enter('command', 'cmd focus')
|
||||
super().focusInEvent(e)
|
||||
|
||||
|
||||
class _CommandValidator(QValidator):
|
||||
|
||||
"""Validator to prevent the : from getting deleted."""
|
||||
|
||||
def validate(self, string, pos):
|
||||
"""Override QValidator::validate.
|
||||
|
||||
Args:
|
||||
string: The string to validate.
|
||||
pos: The current curser position.
|
||||
|
||||
Return:
|
||||
A tuple (status, string, pos) as a QValidator should.
|
||||
"""
|
||||
if any(string.startswith(c) for c in STARTCHARS):
|
||||
return (QValidator.Acceptable, string, pos)
|
||||
else:
|
||||
return (QValidator.Invalid, string, pos)
|
27
qutebrowser/widgets/statusbar/_keystring.py
Normal file
27
qutebrowser/widgets/statusbar/_keystring.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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/>.
|
||||
|
||||
"""Keychain string displayed in the statusbar."""
|
||||
|
||||
from qutebrowser.widgets.statusbar._textbase import TextBase
|
||||
|
||||
|
||||
class KeyString(TextBase):
|
||||
|
||||
"""Keychain string displayed in the statusbar."""
|
||||
|
||||
pass
|
47
qutebrowser/widgets/statusbar/_percentage.py
Normal file
47
qutebrowser/widgets/statusbar/_percentage.py
Normal file
@ -0,0 +1,47 @@
|
||||
# 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/>.
|
||||
|
||||
"""Scroll percentage displayed in the statusbar."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
|
||||
from qutebrowser.widgets.statusbar._textbase import TextBase
|
||||
|
||||
|
||||
class Percentage(TextBase):
|
||||
|
||||
"""Reading percentage displayed in the statusbar."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Constructor. Set percentage to 0%."""
|
||||
super().__init__(parent)
|
||||
self.set_perc(0, 0)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def set_perc(self, _, y):
|
||||
"""Setter to be used as a Qt slot.
|
||||
|
||||
Args:
|
||||
_: The x percentage (int), currently ignored.
|
||||
y: The y percentage (int)
|
||||
"""
|
||||
if y == 0:
|
||||
self.setText('[top]')
|
||||
elif y == 100:
|
||||
self.setText('[bot]')
|
||||
else:
|
||||
self.setText('[{:2}%]'.format(y))
|
59
qutebrowser/widgets/statusbar/_progress.py
Normal file
59
qutebrowser/widgets/statusbar/_progress.py
Normal file
@ -0,0 +1,59 @@
|
||||
# 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/>.
|
||||
|
||||
"""The progress bar in the statusbar."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QProgressBar, QSizePolicy
|
||||
|
||||
from qutebrowser.config.style import set_register_stylesheet
|
||||
|
||||
|
||||
class Progress(QProgressBar):
|
||||
|
||||
"""The progress bar part of the status bar.
|
||||
|
||||
Class attributes:
|
||||
STYLESHEET: The stylesheet template.
|
||||
"""
|
||||
|
||||
# FIXME for some reason, margin-left is not shown
|
||||
STYLESHEET = """
|
||||
QProgressBar {{
|
||||
border-radius: 0px;
|
||||
border: 2px solid transparent;
|
||||
margin-left: 1px;
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QProgressBar::chunk {{
|
||||
{color[statusbar.progress.bg]}
|
||||
}}
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
set_register_stylesheet(self)
|
||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored)
|
||||
self.setTextVisible(False)
|
||||
self.hide()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_load_started(self):
|
||||
"""Clear old error and show progress, used as slot to loadStarted."""
|
||||
self.setValue(0)
|
||||
self.show()
|
87
qutebrowser/widgets/statusbar/_text.py
Normal file
87
qutebrowser/widgets/statusbar/_text.py
Normal file
@ -0,0 +1,87 @@
|
||||
# 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/>.
|
||||
|
||||
"""Text displayed in the statusbar."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
|
||||
from qutebrowser.widgets.statusbar._textbase import TextBase
|
||||
|
||||
|
||||
class Text(TextBase):
|
||||
|
||||
"""Text displayed in the statusbar.
|
||||
|
||||
Attributes:
|
||||
normaltext: The "permanent" text. Never automatically cleared.
|
||||
temptext: The temporary text. Cleared on a keystroke.
|
||||
errortext: The error text. Cleared on a keystroke.
|
||||
_initializing: True if we're currently in __init__ and no text should
|
||||
be updated yet.
|
||||
|
||||
The errortext has the highest priority, i.e. it will always be shown
|
||||
when it is set. The temptext is shown when there is no error, and the
|
||||
(permanent) text is shown when there is neither a temporary text nor an
|
||||
error.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._initializing = True
|
||||
self.normaltext = ''
|
||||
self.temptext = ''
|
||||
self.errortext = ''
|
||||
self._initializing = False
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
"""Overwrite __setattr__ to call _update_text when needed."""
|
||||
super().__setattr__(name, val)
|
||||
if not name.startswith('_') and not self._initializing:
|
||||
self._update_text()
|
||||
|
||||
def _update_text(self):
|
||||
"""Update QLabel text when needed.
|
||||
|
||||
Called from __setattr__ if a text property changed.
|
||||
"""
|
||||
for text in [self.errortext, self.temptext, self.normaltext]:
|
||||
if text:
|
||||
self.setText(text)
|
||||
break
|
||||
else:
|
||||
self.setText('')
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_normaltext(self, val):
|
||||
"""Setter for normaltext, to be used as Qt slot."""
|
||||
self.normaltext = val
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_statusbar_message(self, val):
|
||||
"""Called when javascript tries to set a statusbar message.
|
||||
|
||||
For some reason, this is emitted a lot with an empty string during page
|
||||
load, so we currently ignore these and thus don't support clearing the
|
||||
message, which is a bit unfortunate...
|
||||
"""
|
||||
if val:
|
||||
self.temptext = val
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_temptext(self, val):
|
||||
"""Setter for temptext, to be used as Qt slot."""
|
||||
self.temptext = val
|
83
qutebrowser/widgets/statusbar/_textbase.py
Normal file
83
qutebrowser/widgets/statusbar/_textbase.py
Normal file
@ -0,0 +1,83 @@
|
||||
# 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/>.
|
||||
|
||||
"""Base text widgets for statusbar."""
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QLabel, QSizePolicy
|
||||
from PyQt5.QtGui import QPainter
|
||||
|
||||
|
||||
class TextBase(QLabel):
|
||||
|
||||
"""A text in the statusbar.
|
||||
|
||||
Unlike QLabel, the text will get elided.
|
||||
|
||||
Eliding is loosly based on
|
||||
http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html
|
||||
|
||||
Attributes:
|
||||
_elidemode: Where to elide the text.
|
||||
_elided_text: The current elided text.
|
||||
"""
|
||||
|
||||
def __init__(self, bar, elidemode=Qt.ElideRight):
|
||||
super().__init__(bar)
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
||||
self._elidemode = elidemode
|
||||
self._elided_text = ''
|
||||
|
||||
def _update_elided_text(self, width):
|
||||
"""Update the elided text when necessary.
|
||||
|
||||
Args:
|
||||
width: The maximal width the text should take.
|
||||
"""
|
||||
self._elided_text = self.fontMetrics().elidedText(
|
||||
self.text(), self._elidemode, width, Qt.TextShowMnemonic)
|
||||
|
||||
def setText(self, txt):
|
||||
"""Extend QLabel::setText.
|
||||
|
||||
This update the elided text after setting the text, and also works
|
||||
around a weird QLabel redrawing bug where it doesn't redraw correctly
|
||||
when the text is empty -- we explicitely need to call repaint() to
|
||||
resolve this. See http://stackoverflow.com/q/21890462/2085149
|
||||
|
||||
Args:
|
||||
txt: The text to set (string).
|
||||
"""
|
||||
super().setText(txt)
|
||||
self._update_elided_text(self.geometry().width())
|
||||
if not txt:
|
||||
self.repaint()
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Extend QLabel::resizeEvent to update the elided text afterwards."""
|
||||
super().resizeEvent(e)
|
||||
self._update_elided_text(e.size().width())
|
||||
|
||||
def paintEvent(self, e):
|
||||
"""Override QLabel::paintEvent to draw elided text."""
|
||||
if self._elidemode == Qt.ElideNone:
|
||||
super().paintEvent(e)
|
||||
else:
|
||||
painter = QPainter(self)
|
||||
painter.drawText(0, 0, self.geometry().width(),
|
||||
self.geometry().height(), self.alignment(),
|
||||
self._elided_text)
|
153
qutebrowser/widgets/statusbar/_url.py
Normal file
153
qutebrowser/widgets/statusbar/_url.py
Normal file
@ -0,0 +1,153 @@
|
||||
# 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/>.
|
||||
|
||||
"""URL displayed in the statusbar."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt
|
||||
|
||||
from qutebrowser.widgets.statusbar._textbase import TextBase
|
||||
from qutebrowser.config.style import set_register_stylesheet, get_stylesheet
|
||||
from qutebrowser.utils.url import urlstring
|
||||
|
||||
|
||||
class Url(TextBase):
|
||||
|
||||
"""URL displayed in the statusbar.
|
||||
|
||||
Class attributes:
|
||||
STYLESHEET: The stylesheet template.
|
||||
|
||||
Attributes:
|
||||
_old_url: The URL displayed before the hover URL.
|
||||
_old_urltype: The type of the URL displayed before the hover URL.
|
||||
_ssl_errors: Whether SSL errors occured while loading.
|
||||
|
||||
Class attributes:
|
||||
_urltype: The current URL type. One of normal/ok/error/warn/hover.
|
||||
Accessed via the urltype property.
|
||||
|
||||
For some reason we need to have this as class attribute so
|
||||
pyqtProperty works correctly.
|
||||
"""
|
||||
|
||||
_urltype = None
|
||||
|
||||
STYLESHEET = """
|
||||
QLabel#_Url[urltype="normal"] {{
|
||||
{color[statusbar.url.fg]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="success"] {{
|
||||
{color[statusbar.url.fg.success]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="error"] {{
|
||||
{color[statusbar.url.fg.error]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="warn"] {{
|
||||
{color[statusbar.url.fg.warn]}
|
||||
}}
|
||||
|
||||
QLabel#_Url[urltype="hover"] {{
|
||||
{color[statusbar.url.fg.hover]}
|
||||
}}
|
||||
"""
|
||||
|
||||
def __init__(self, bar, elidemode=Qt.ElideMiddle):
|
||||
"""Override TextBase::__init__ to elide in the middle by default.
|
||||
|
||||
Args:
|
||||
bar: The statusbar (parent) object.
|
||||
elidemode: How to elide the text.
|
||||
"""
|
||||
super().__init__(bar, elidemode)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
set_register_stylesheet(self)
|
||||
self._old_urltype = None
|
||||
self._old_url = None
|
||||
self._ssl_errors = False
|
||||
|
||||
@pyqtProperty(str)
|
||||
def urltype(self):
|
||||
"""Getter for self.urltype, so it can be used as Qt property."""
|
||||
# pylint: disable=method-hidden
|
||||
return self._urltype
|
||||
|
||||
@urltype.setter
|
||||
def urltype(self, val):
|
||||
"""Setter for self.urltype, so it can be used as Qt property."""
|
||||
self._urltype = val
|
||||
self.setStyleSheet(get_stylesheet(self.STYLESHEET))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_loading_started(self):
|
||||
"""Slot to clear SSL errors when loading started."""
|
||||
self._ssl_errors = False
|
||||
|
||||
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
|
||||
def on_ssl_errors(self, _reply, _errors):
|
||||
self._ssl_errors = True
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def on_loading_finished(self, ok):
|
||||
"""Slot for cur_loading_finished. Colors the URL according to ok.
|
||||
|
||||
Args:
|
||||
ok: Whether loading finished successfully (True) or not (False).
|
||||
"""
|
||||
if ok and not self._ssl_errors:
|
||||
self.urltype = 'success'
|
||||
elif ok:
|
||||
self.urltype = 'warn'
|
||||
else:
|
||||
self.urltype = 'error'
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_url(self, s):
|
||||
"""Setter to be used as a Qt slot.
|
||||
|
||||
Args:
|
||||
s: The URL to set.
|
||||
"""
|
||||
self.setText(urlstring(s))
|
||||
self.urltype = 'normal'
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def set_hover_url(self, link, _title, _text):
|
||||
"""Setter to be used as a Qt slot.
|
||||
|
||||
Saves old shown URL in self._old_url and restores it later if a link is
|
||||
"un-hovered" when it gets called with empty parameters.
|
||||
|
||||
Args:
|
||||
link: The link which was hovered (string)
|
||||
_title: The title of the hovered link (string)
|
||||
_text: The text of the hovered link (string)
|
||||
"""
|
||||
if link:
|
||||
if self._old_url is None:
|
||||
self._old_url = self.text()
|
||||
if self._old_urltype is None:
|
||||
self._old_urltype = self._urltype
|
||||
self.urltype = 'hover'
|
||||
self.setText(link)
|
||||
else:
|
||||
self.setText(self._old_url)
|
||||
self.urltype = self._old_urltype
|
||||
self._old_url = None
|
||||
self._old_urltype = None
|
210
qutebrowser/widgets/statusbar/bar.py
Normal file
210
qutebrowser/widgets/statusbar/bar.py
Normal file
@ -0,0 +1,210 @@
|
||||
# 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/>.
|
||||
|
||||
"""The main statusbar widget."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
|
||||
|
||||
import qutebrowser.keyinput.modeman as modeman
|
||||
from qutebrowser.widgets.statusbar._command import Command
|
||||
from qutebrowser.widgets.statusbar._progress import Progress
|
||||
from qutebrowser.widgets.statusbar._text import Text
|
||||
from qutebrowser.widgets.statusbar._keystring import KeyString
|
||||
from qutebrowser.widgets.statusbar._percentage import Percentage
|
||||
from qutebrowser.widgets.statusbar._url import Url
|
||||
from qutebrowser.config.style import set_register_stylesheet, get_stylesheet
|
||||
|
||||
|
||||
class StatusBar(QWidget):
|
||||
|
||||
"""The statusbar at the bottom of the mainwindow.
|
||||
|
||||
Class attributes:
|
||||
STYLESHEET: The stylesheet template.
|
||||
|
||||
Attributes:
|
||||
cmd: The Command widget in the statusbar.
|
||||
txt: The Text widget in the statusbar.
|
||||
keystring: The KeyString widget in the statusbar.
|
||||
percentage: The Percentage widget in the statusbar.
|
||||
url: The Url widget in the statusbar.
|
||||
prog: The Progress widget in the statusbar.
|
||||
_hbox: The main QHBoxLayout.
|
||||
_stack: The QStackedLayout with cmd/txt widgets.
|
||||
|
||||
Class attributes:
|
||||
_error: If there currently is an error, accessed through the error
|
||||
property.
|
||||
|
||||
For some reason we need to have this as class attribute so
|
||||
pyqtProperty works correctly.
|
||||
|
||||
Signals:
|
||||
resized: Emitted when the statusbar has resized, so the completion
|
||||
widget can adjust its size to it.
|
||||
arg: The new size.
|
||||
moved: Emitted when the statusbar has moved, so the completion widget
|
||||
can move the the right position.
|
||||
arg: The new position.
|
||||
"""
|
||||
|
||||
resized = pyqtSignal('QRect')
|
||||
moved = pyqtSignal('QPoint')
|
||||
_error = False
|
||||
|
||||
STYLESHEET = """
|
||||
QWidget#StatusBar[error="false"] {{
|
||||
{color[statusbar.bg]}
|
||||
}}
|
||||
|
||||
QWidget#StatusBar[error="true"] {{
|
||||
{color[statusbar.bg.error]}
|
||||
}}
|
||||
|
||||
QWidget {{
|
||||
{color[statusbar.fg]}
|
||||
{font[statusbar]}
|
||||
}}
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
self.setAttribute(Qt.WA_StyledBackground)
|
||||
set_register_stylesheet(self)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
|
||||
self._option = None
|
||||
|
||||
self._hbox = QHBoxLayout(self)
|
||||
self._hbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._hbox.setSpacing(5)
|
||||
|
||||
self._stack = QStackedLayout()
|
||||
self._stack.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.cmd = Command(self)
|
||||
self._stack.addWidget(self.cmd)
|
||||
|
||||
self.txt = Text(self)
|
||||
self._stack.addWidget(self.txt)
|
||||
|
||||
self.cmd.show_cmd.connect(self._show_cmd_widget)
|
||||
self.cmd.hide_cmd.connect(self._hide_cmd_widget)
|
||||
self._hide_cmd_widget()
|
||||
|
||||
self._hbox.addLayout(self._stack)
|
||||
|
||||
self.keystring = KeyString(self)
|
||||
self._hbox.addWidget(self.keystring)
|
||||
|
||||
self.url = Url(self)
|
||||
self._hbox.addWidget(self.url)
|
||||
|
||||
self.percentage = Percentage(self)
|
||||
self._hbox.addWidget(self.percentage)
|
||||
|
||||
self.prog = Progress(self)
|
||||
self._hbox.addWidget(self.prog)
|
||||
|
||||
@pyqtProperty(bool)
|
||||
def error(self):
|
||||
"""Getter for self.error, so it can be used as Qt property."""
|
||||
# pylint: disable=method-hidden
|
||||
return self._error
|
||||
|
||||
@error.setter
|
||||
def error(self, val):
|
||||
"""Setter for self.error, so it can be used as Qt property.
|
||||
|
||||
Re-set the stylesheet after setting the value, so everything gets
|
||||
updated by Qt properly.
|
||||
"""
|
||||
self._error = val
|
||||
self.setStyleSheet(get_stylesheet(self.STYLESHEET))
|
||||
|
||||
def _show_cmd_widget(self):
|
||||
"""Show command widget instead of temporary text."""
|
||||
self._stack.setCurrentWidget(self.cmd)
|
||||
self.clear_error()
|
||||
|
||||
def _hide_cmd_widget(self):
|
||||
"""Show temporary text instead of command widget."""
|
||||
self._stack.setCurrentWidget(self.txt)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def disp_error(self, text):
|
||||
"""Display an error in the statusbar."""
|
||||
self.error = True
|
||||
self.txt.errortext = text
|
||||
|
||||
@pyqtSlot()
|
||||
def clear_error(self):
|
||||
"""Clear a displayed error from the status bar."""
|
||||
self.error = False
|
||||
self.txt.errortext = ''
|
||||
|
||||
@pyqtSlot('QKeyEvent')
|
||||
def on_key_pressed(self, e):
|
||||
"""Hide temporary error message if a key was pressed.
|
||||
|
||||
Args:
|
||||
e: The original QKeyEvent.
|
||||
"""
|
||||
if e.key() in [Qt.Key_Control, Qt.Key_Alt, Qt.Key_Shift, Qt.Key_Meta]:
|
||||
# Only modifier pressed, don't hide yet.
|
||||
return
|
||||
self.txt.set_temptext('')
|
||||
self.clear_error()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_mode_entered(self, mode):
|
||||
"""Mark certain modes in the commandline."""
|
||||
if mode in modeman.instance().passthrough:
|
||||
self.txt.normaltext = "-- {} MODE --".format(mode.upper())
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear marked mode."""
|
||||
if mode in modeman.instance().passthrough:
|
||||
self.txt.normaltext = ""
|
||||
|
||||
def resizeEvent(self, e):
|
||||
"""Extend resizeEvent of QWidget to emit a resized signal afterwards.
|
||||
|
||||
Args:
|
||||
e: The QResizeEvent.
|
||||
|
||||
Emit:
|
||||
resized: Always emitted.
|
||||
"""
|
||||
super().resizeEvent(e)
|
||||
self.resized.emit(self.geometry())
|
||||
|
||||
def moveEvent(self, e):
|
||||
"""Extend moveEvent of QWidget to emit a moved signal afterwards.
|
||||
|
||||
Args:
|
||||
e: The QMoveEvent.
|
||||
|
||||
Emit:
|
||||
moved: Always emitted.
|
||||
"""
|
||||
super().moveEvent(e)
|
||||
self.moved.emit(e.pos())
|
Loading…
Reference in New Issue
Block a user