Merge branch 'caret_visual_mode' of https://github.com/artur-shaik/qutebrowser into visual

Conflicts:
      qutebrowser/browser/commands.py
      qutebrowser/browser/webview.py
      qutebrowser/config/configdata.py
This commit is contained in:
Florian Bruhin 2015-04-21 21:28:39 +02:00
commit 6c8e073dc8
8 changed files with 343 additions and 11 deletions

View File

@ -41,6 +41,7 @@ from qutebrowser.browser import webelem, inspector
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils)
from qutebrowser.misc import editor
from qutebrowser.keyinput import modeman
class CommandDispatcher:
@ -1150,3 +1151,193 @@ class CommandDispatcher:
flags |= QWebPage.FindBackward
for _ in range(count):
view.search(view.search_text, flags)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_next_line(self):
"""Move the cursor or select to the next line."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToNextLine
else:
act = QWebPage.SelectNextLine
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_prev_line(self):
"""Move the cursor or select to the prev line."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToPreviousLine
else:
act = QWebPage.SelectPreviousLine
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_next_char(self):
"""Move the cursor or select to the next char."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToNextChar
else:
act = QWebPage.SelectNextChar
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_prev_char(self):
"""Move the cursor or select to the prev char."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToPreviousChar
else:
act = QWebPage.SelectPreviousChar
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_end_of_word(self):
"""Move the cursor or select to the next word."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToNextWord
else:
act = QWebPage.SelectNextWord
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_next_word(self):
"""Move the cursor or select to the next word."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = [QWebPage.MoveToNextWord, QWebPage.MoveToNextChar]
else:
act = [QWebPage.SelectNextWord, QWebPage.SelectNextChar]
for a in act:
self._current_widget().triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_prev_word(self):
"""Move the cursor or select to the prev word."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToPreviousWord
else:
act = QWebPage.SelectPreviousWord
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_start_of_line(self):
"""Move the cursor or select to the start of line."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToStartOfLine
else:
act = QWebPage.SelectStartOfLine
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_end_of_line(self):
"""Move the cursor or select to the end of line."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToEndOfLine
else:
act = QWebPage.SelectEndOfLine
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_start_of_block(self):
"""Move the cursor or select to the start of block."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToStartOfBlock
else:
act = QWebPage.SelectStartOfBlock
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_end_of_block(self):
"""Move the cursor or select to the end of block."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToEndOfBlock
else:
act = QWebPage.SelectEndOfBlock
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_start_of_document(self):
"""Move the cursor or select to the start of document."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToStartOfDocument
else:
act = QWebPage.SelectStartOfDocument
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.caret, usertypes.KeyMode.visual],
hide=True, scope='window')
def move_to_end_of_document(self):
"""Move the cursor or select to the end of document."""
modemanager = modeman._get_modeman(self._win_id)
if modemanager.mode == usertypes.KeyMode.caret:
act = QWebPage.MoveToEndOfDocument
else:
act = QWebPage.SelectEndOfDocument
self._current_widget().triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.visual],
hide=True, scope='window')
def yank_selected(self, sel=False):
"""Yank selected text to the clipboard or primary selection.
Args:
sel: Use the primary selection instead of the clipboard.
"""
s = self._current_widget().selectedText()
if not self._current_widget().hasSelection() or len(s) == 0:
message.info(self._win_id, "Nothing to yank")
return
clipboard = QApplication.clipboard()
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection
target = "primary selection"
else:
mode = QClipboard.Clipboard
target = "clipboard"
log.misc.debug("Yanking to {}: '{}'".format(target, s))
clipboard.setText(s, mode)
message.info(self._win_id, "{} {} yanked to {}"
.format(len(s), "char" if len(s) == 1 else "chars", target))
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.visual],
hide=True, scope='window')
def drop_selection(self):
"""Drop selection and stay in visual mode."""
self._current_widget().triggerPageAction(QWebPage.MoveToNextChar)

View File

@ -23,10 +23,11 @@ import sys
import itertools
import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QPoint
from PyQt5.QtWidgets import QApplication, QStyleFactory
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from PyQt5.QtGui import QMouseEvent
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
@ -71,6 +72,7 @@ class WebView(QWebView):
_check_insertmode: If True, in mouseReleaseEvent we should check if we
need to enter/leave insert mode.
_default_zoom_changed: Whether the zoom was changed from the default.
_caret_exist: Whether caret already has focus element
Signals:
scroll_pos_changed: Scroll percentage of current tab changed.
@ -136,6 +138,7 @@ class WebView(QWebView):
self.viewing_source = False
self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100)
self._default_zoom_changed = False
self._caret_exist = False
if config.get('input', 'rocker-gestures'):
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.urlChanged.connect(self.on_url_changed)
@ -436,6 +439,37 @@ class WebView(QWebView):
"entered.".format(mode))
self.setFocusPolicy(Qt.NoFocus)
self._caret_exist = False
elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual):
self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, True)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self.win_id)
if self.tab_id == tabbed_browser._now_focused.tab_id and not self._caret_exist:
"""
Here is a workaround for auto position enabled caret.
Unfortunatly, caret doesn't appear until you click
mouse button on element. I have such behavior in dwb,
so I decided to implement this workaround.
May be should be reworked.
"""
frame = self.page().currentFrame()
halfWidth = frame.scrollBarGeometry(Qt.Horizontal).width() / 2
point = QPoint(halfWidth,10)
event = QMouseEvent(QMouseEvent.MouseButtonPress, point, point,
point, Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QApplication.sendEvent(self, event)
#frame.setFocus()
#frame.documentElement().setFocus()
#frame.documentElement().firstChild().setFocus()
#self.page().focusNextPrevChild(True)
#self.page().setContentEditable(True)
#self.triggerPageAction(QWebPage.MoveToNextChar)
self._caret_exist = True
else:
self._caret_exist = False
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Restore focus policy if status-input modes were left."""
@ -443,6 +477,13 @@ class WebView(QWebView):
usertypes.KeyMode.yesno):
log.webview.debug("Restoring focus policy because mode {} was "
"left.".format(mode))
elif mode in (usertypes.KeyMode.caret, usertypes.KeyMode.visual):
if self.settings().testAttribute(QWebSettings.CaretBrowsingEnabled):
if mode == usertypes.KeyMode.visual and self.hasSelection():
# Remove selection if exist
self.triggerPageAction(QWebPage.MoveToNextChar)
self.settings().setAttribute(QWebSettings.CaretBrowsingEnabled, False)
self.setFocusPolicy(Qt.WheelFocus)
def search(self, text, flags):

View File

@ -816,6 +816,14 @@ def data(readonly=False):
SettingValue(typ.QssColor(), 'darkgreen'),
"Background color of the statusbar in insert mode."),
('statusbar.bg.caret',
SettingValue(typ.QssColor(), 'purple'),
"Background color of the statusbar in caret mode."),
('statusbar.bg.visual',
SettingValue(typ.QssColor(), '#a12dff'),
"Background color of the statusbar in visual mode."),
('statusbar.progress.bg',
SettingValue(typ.QssColor(), 'white'),
"Background color of the progress bar."),
@ -1088,6 +1096,8 @@ KEY_SECTION_DESC = {
" * `prompt-accept`: Confirm the entered value.\n"
" * `prompt-yes`: Answer yes to a yes/no question.\n"
" * `prompt-no`: Answer no to a yes/no question."),
'caret': (
""),
}
@ -1152,6 +1162,7 @@ KEY_DATA = collections.OrderedDict([
('search-next', ['n']),
('search-prev', ['N']),
('enter-mode insert', ['i']),
('enter-mode caret', ['v']),
('yank', ['yy']),
('yank -s', ['yY']),
('yank -t', ['yt']),
@ -1247,6 +1258,36 @@ KEY_DATA = collections.OrderedDict([
('rl-delete-char', ['<Ctrl-?>']),
('rl-backward-delete-char', ['<Ctrl-H>']),
])),
('visual', collections.OrderedDict([
('yank-selected', ['y']),
('yank-selected -s', ['Y']),
('drop-selection', ['v']),
('enter-mode caret', ['c']),
])),
('caret', collections.OrderedDict([
('enter-mode visual', ['v']),
('enter-mode normal', ['c']),
])),
('caret,visual', collections.OrderedDict([
('move-to-next-line', ['j']),
('move-to-prev-line', ['k']),
('move-to-next-char', ['l']),
('move-to-prev-char', ['h']),
('move-to-end-of-word', ['e']),
('move-to-next-word', ['w']),
('move-to-prev-word', ['b']),
('move-to-start-of-line', ['0']),
('move-to-end-of-line', ['$']),
('move-to-start-of-document', ['gg']),
('move-to-end-of-document', ['G']),
('scroll -50 0', ['H']),
('scroll 0 50', ['J']),
('scroll 0 -50', ['K']),
('scroll 50 0', ['L']),
])),
])

View File

@ -24,6 +24,7 @@ import functools
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKitWidgets import QWebView
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.keyinput import modeparsers, keyparser
from qutebrowser.config import config
@ -78,6 +79,8 @@ def init(win_id, parent):
KM.prompt: keyparser.PassthroughKeyParser(win_id, 'prompt', modeman,
warn=False),
KM.yesno: modeparsers.PromptKeyParser(win_id, modeman),
KM.caret: modeparsers.CaretKeyParser(win_id, modeman),
KM.visual: modeparsers.VisualKeyParser(win_id, modeman),
}
objreg.register('keyparsers', keyparsers, scope='window', window=win_id)
modeman.destroyed.connect(
@ -92,6 +95,8 @@ def init(win_id, parent):
passthrough=True)
modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True)
modeman.register(KM.yesno, keyparsers[KM.yesno].handle)
modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True)
modeman.register(KM.visual, keyparsers[KM.visual].handle, passthrough=True)
return modeman

View File

@ -218,3 +218,27 @@ class HintKeyParser(keyparser.CommandKeyParser):
hintmanager = objreg.get('hintmanager', scope='tab',
window=self._win_id, tab='current')
hintmanager.handle_partial_key(keystr)
class CaretKeyParser(keyparser.CommandKeyParser):
"""KeyParser for Caret mode."""
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent, supports_count=True,
supports_chains=True)
self.read_config('caret')
def __repr__(self):
return utils.get_repr(self)
class VisualKeyParser(keyparser.CommandKeyParser):
"""KeyParser for Visual mode."""
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent, supports_count=True,
supports_chains=True)
self.read_config('visual')
def __repr__(self):
return utils.get_repr(self)

View File

@ -91,6 +91,8 @@ class StatusBar(QWidget):
_severity = None
_prompt_active = False
_insert_active = False
_caret_active = False
_visual_active = False
STYLESHEET = """
QWidget#StatusBar {
@ -101,6 +103,14 @@ class StatusBar(QWidget):
{{ color['statusbar.bg.insert'] }}
}
QWidget#StatusBar[caret_active="true"] {
{{ color['statusbar.bg.caret'] }}
}
QWidget#StatusBar[visual_active="true"] {
{{ color['statusbar.bg.visual'] }}
}
QWidget#StatusBar[prompt_active="true"] {
{{ color['statusbar.bg.prompt'] }}
}
@ -253,14 +263,31 @@ class StatusBar(QWidget):
"""Getter for self.insert_active, so it can be used as Qt property."""
return self._insert_active
def _set_insert_active(self, val):
"""Setter for self.insert_active.
@pyqtProperty(bool)
def caret_active(self):
"""Getter for self.caret_active, so it can be used as Qt property."""
return self._caret_active
@pyqtProperty(bool)
def visual_active(self):
"""Getter for self.visual_active, so it can be used as Qt property."""
return self._visual_active
def _set_mode_active(self, mode, val):
"""Setter for self.insert_active, self.caret_active or self.visual_active.
Re-set the stylesheet after setting the value, so everything gets
updated by Qt properly.
"""
log.statusbar.debug("Setting insert_active to {}".format(val))
self._insert_active = val
if mode == usertypes.KeyMode.insert:
log.statusbar.debug("Setting insert_active to {}".format(val))
self._insert_active = val
elif mode == usertypes.KeyMode.caret:
log.statusbar.debug("Setting caret_active to {}".format(val))
self._caret_active = val
elif mode == usertypes.KeyMode.visual:
log.statusbar.debug("Setting visual_active to {}".format(val))
self._visual_active = val
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
def _set_mode_text(self, mode):
@ -438,8 +465,9 @@ class StatusBar(QWidget):
window=self._win_id)
if mode in mode_manager.passthrough:
self._set_mode_text(mode.name)
if mode == usertypes.KeyMode.insert:
self._set_insert_active(True)
if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret,
usertypes.KeyMode.visual):
self._set_mode_active(mode, True)
@pyqtSlot(usertypes.KeyMode, usertypes.KeyMode)
def on_mode_left(self, old_mode, new_mode):
@ -451,8 +479,9 @@ class StatusBar(QWidget):
self._set_mode_text(new_mode.name)
else:
self.txt.set_text(self.txt.Text.normal, '')
if old_mode == usertypes.KeyMode.insert:
self._set_insert_active(False)
if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret,
usertypes.KeyMode.visual):
self._set_mode_active(old_mode, False)
@config.change_filter('ui', 'message-timeout')
def set_pop_timer_interval(self):

View File

@ -518,7 +518,8 @@ class TabbedBrowser(tabwidget.TabWidget):
tab = self.widget(idx)
log.modes.debug("Current tab changed, focusing {!r}".format(tab))
tab.setFocus()
for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert):
for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert,
usertypes.KeyMode.caret, usertypes.KeyMode.visual):
modeman.maybe_leave(self._win_id, mode, 'tab changed')
if self._now_focused is not None:
objreg.register('last-focused-tab', self._now_focused, update=True,

View File

@ -231,7 +231,7 @@ ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window'])
# Key input modes
KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
'insert', 'passthrough'])
'insert', 'passthrough', 'caret', 'visual'])
# Available command completions