mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Start work on completion popup for editor
This commit is contained in:
parent
f623249847
commit
1c369d3528
156
src/calibre/gui2/tweak_book/complete.py
Normal file
156
src/calibre/gui2/tweak_book/complete.py
Normal file
@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
from math import ceil
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QWidget, Qt, QStaticText, QTextOption, QSize, QPainter, QTimer)
|
||||
|
||||
from calibre.gui2.tweak_book.widgets import make_highlighted_text
|
||||
from calibre.utils.icu import string_length
|
||||
from calibre.utils.matcher import Matcher
|
||||
|
||||
|
||||
class CompletionPopup(QWidget):
|
||||
|
||||
TOP_MARGIN = BOTTOM_MARGIN = 2
|
||||
|
||||
def __init__(self, parent):
|
||||
QWidget.__init__(self, parent)
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
self.setFocusProxy(parent)
|
||||
self.setVisible(False)
|
||||
|
||||
self.matcher = None
|
||||
self.current_query = self.current_results = self.current_size_hint = None
|
||||
self.max_text_length = 0
|
||||
|
||||
self.text_option = to = QTextOption()
|
||||
to.setWrapMode(QTextOption.NoWrap)
|
||||
to.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||||
|
||||
self.rendered_text_cache = {}
|
||||
parent.installEventFilter(self)
|
||||
self.relayout_timer = t = QTimer(self)
|
||||
t.setSingleShot(True), t.setInterval(25), t.timeout.connect(self.layout)
|
||||
|
||||
def clear_caches(self):
|
||||
self.rendered_text_cache.clear()
|
||||
self.current_size_hint = None
|
||||
|
||||
def set_items(self, items, items_are_filenames=False):
|
||||
kw = {}
|
||||
if not items_are_filenames:
|
||||
kw['level1'] = ' '
|
||||
self.matcher = Matcher(tuple(items), **kw)
|
||||
self.descriptions = dict(items) if isinstance(items, dict) else {}
|
||||
self.clear_caches()
|
||||
self.set_query()
|
||||
|
||||
def set_query(self, query=''):
|
||||
self.current_size_hint = None
|
||||
self.current_query = query
|
||||
if self.matcher is None:
|
||||
self.current_results = ()
|
||||
else:
|
||||
if query:
|
||||
self.current_results = tuple(self.matcher(query).iteritems())
|
||||
else:
|
||||
self.current_results = tuple((text, ()) for text in self.matcher.items)
|
||||
self.max_text_length = 0
|
||||
if self.current_results:
|
||||
self.max_text_length = max(string_length(text) for text, pos in self.current_results)
|
||||
|
||||
def get_static_text(self, otext, positions):
|
||||
st = self.rendered_text_cache.get(otext)
|
||||
if st is None:
|
||||
text = (otext or '').ljust(self.max_text_length + 1, ' ')
|
||||
text = make_highlighted_text('color: magenta', text, positions)
|
||||
desc = self.descriptions.get(otext)
|
||||
if desc:
|
||||
text += ' ' + desc
|
||||
st = self.rendered_text_cache[otext] = QStaticText(text)
|
||||
st.setTextOption(self.text_option)
|
||||
st.setTextFormat(Qt.RichText)
|
||||
st.prepare(font=self.parent().font())
|
||||
return st
|
||||
|
||||
def sizeHint(self):
|
||||
if self.current_size_hint is None:
|
||||
max_width = 0
|
||||
height = 0
|
||||
for text, positions in self.current_results:
|
||||
sz = self.get_static_text(text, positions).size()
|
||||
height += int(ceil(sz.height())) + self.TOP_MARGIN + self.BOTTOM_MARGIN
|
||||
max_width = max(max_width, int(ceil(sz.width())))
|
||||
self.current_size_hint = QSize(max_width, height)
|
||||
return self.current_size_hint
|
||||
|
||||
def paintEvent(self, ev):
|
||||
painter = QPainter(self)
|
||||
painter.setClipRect(ev.rect())
|
||||
painter.setFont(self.parent().font())
|
||||
y = self.TOP_MARGIN
|
||||
top, bottom = ev.rect().top(), ev.rect().bottom()
|
||||
for text, positions in self.current_results:
|
||||
st = self.get_static_text(text, positions)
|
||||
height = self.BOTTOM_MARGIN + int(ceil(st.size().height()))
|
||||
if y + height < top:
|
||||
continue
|
||||
if y + height > bottom:
|
||||
break
|
||||
painter.drawStaticText(0, y, st)
|
||||
y += height
|
||||
painter.end()
|
||||
if self.current_size_hint is None:
|
||||
QTimer.singleShot(0, self.layout)
|
||||
|
||||
def layout(self, cursor_rect=None):
|
||||
p = self.parent()
|
||||
if cursor_rect is None:
|
||||
cursor_rect = p.cursorRect()
|
||||
gutter_width = p.gutter_width
|
||||
vp = p.viewport()
|
||||
above = cursor_rect.top() > vp.height() - cursor_rect.bottom()
|
||||
max_height = (cursor_rect.top() if above else vp.height() - cursor_rect.bottom()) - 15
|
||||
max_width = vp.width() - 25 - gutter_width
|
||||
sz = self.sizeHint()
|
||||
height = min(max_height, sz.height())
|
||||
width = min(max_width, sz.width())
|
||||
left = cursor_rect.left() + gutter_width
|
||||
extra = max_width - (width + left)
|
||||
if extra < 0:
|
||||
left += extra
|
||||
top = (cursor_rect.top() - height) if above else cursor_rect.bottom()
|
||||
self.resize(width, height)
|
||||
self.move(left, top)
|
||||
self.update()
|
||||
|
||||
def show(self):
|
||||
if self.current_results:
|
||||
self.layout()
|
||||
QWidget.show(self)
|
||||
self.raise_()
|
||||
|
||||
def hide(self):
|
||||
QWidget.hide(self)
|
||||
self.relayout_timer.stop()
|
||||
|
||||
def eventFilter(self, obj, ev):
|
||||
if obj is self.parent() and self.isVisible():
|
||||
if ev.type() == ev.Resize:
|
||||
self.relayout_timer.start()
|
||||
return False
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test(editor):
|
||||
c = editor.__c = CompletionPopup(editor.editor)
|
||||
c.set_items('one two three four five'.split())
|
||||
QTimer.singleShot(10, c.show)
|
||||
from calibre.gui2.tweak_book.editor.widget import launch_editor
|
||||
launch_editor('''<p>Something something</p>''', path_is_raw=True, callback=test)
|
@ -130,6 +130,7 @@ class TextEdit(PlainTextEdit):
|
||||
|
||||
def __init__(self, parent=None, expected_geometry=(100, 50)):
|
||||
PlainTextEdit.__init__(self, parent)
|
||||
self.gutter_width = 0
|
||||
self.expected_geometry = expected_geometry
|
||||
self.saved_matches = {}
|
||||
self.smarts = NullSmarts(self)
|
||||
@ -499,7 +500,8 @@ class TextEdit(PlainTextEdit):
|
||||
self.line_number_area.update(0, top, self.line_number_area.width(), height)
|
||||
|
||||
def update_line_number_area_width(self, block_count=0):
|
||||
self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)
|
||||
self.gutter_width = self.line_number_area_width()
|
||||
self.setViewportMargins(self.gutter_width, 0, 0, 0)
|
||||
|
||||
def line_number_area_width(self):
|
||||
digits = 1
|
||||
|
@ -533,7 +533,7 @@ class Editor(QMainWindow):
|
||||
dictionaries.add_to_user_dictionary(dic, word, locale)
|
||||
self.word_ignored.emit(word, locale)
|
||||
|
||||
def launch_editor(path_to_edit, path_is_raw=False, syntax='html'):
|
||||
def launch_editor(path_to_edit, path_is_raw=False, syntax='html', callback=None):
|
||||
from calibre.gui2.tweak_book import dictionaries
|
||||
from calibre.gui2.tweak_book.main import option_parser
|
||||
from calibre.gui2.tweak_book.ui import Main
|
||||
@ -556,6 +556,8 @@ def launch_editor(path_to_edit, path_is_raw=False, syntax='html'):
|
||||
syntax = 'css'
|
||||
t = Editor(syntax)
|
||||
t.data = raw
|
||||
if callback is not None:
|
||||
callback(t)
|
||||
t.show()
|
||||
app.exec_()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user