mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-04 03:27:00 -05: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)):
 | 
					    def __init__(self, parent=None, expected_geometry=(100, 50)):
 | 
				
			||||||
        PlainTextEdit.__init__(self, parent)
 | 
					        PlainTextEdit.__init__(self, parent)
 | 
				
			||||||
 | 
					        self.gutter_width = 0
 | 
				
			||||||
        self.expected_geometry = expected_geometry
 | 
					        self.expected_geometry = expected_geometry
 | 
				
			||||||
        self.saved_matches = {}
 | 
					        self.saved_matches = {}
 | 
				
			||||||
        self.smarts = NullSmarts(self)
 | 
					        self.smarts = NullSmarts(self)
 | 
				
			||||||
@ -499,7 +500,8 @@ class TextEdit(PlainTextEdit):
 | 
				
			|||||||
        self.line_number_area.update(0, top, self.line_number_area.width(), height)
 | 
					        self.line_number_area.update(0, top, self.line_number_area.width(), height)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_line_number_area_width(self, block_count=0):
 | 
					    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):
 | 
					    def line_number_area_width(self):
 | 
				
			||||||
        digits = 1
 | 
					        digits = 1
 | 
				
			||||||
 | 
				
			|||||||
@ -533,7 +533,7 @@ class Editor(QMainWindow):
 | 
				
			|||||||
            dictionaries.add_to_user_dictionary(dic, word, locale)
 | 
					            dictionaries.add_to_user_dictionary(dic, word, locale)
 | 
				
			||||||
        self.word_ignored.emit(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 import dictionaries
 | 
				
			||||||
    from calibre.gui2.tweak_book.main import option_parser
 | 
					    from calibre.gui2.tweak_book.main import option_parser
 | 
				
			||||||
    from calibre.gui2.tweak_book.ui import Main
 | 
					    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'
 | 
					            syntax = 'css'
 | 
				
			||||||
    t = Editor(syntax)
 | 
					    t = Editor(syntax)
 | 
				
			||||||
    t.data = raw
 | 
					    t.data = raw
 | 
				
			||||||
 | 
					    if callback is not None:
 | 
				
			||||||
 | 
					        callback(t)
 | 
				
			||||||
    t.show()
 | 
					    t.show()
 | 
				
			||||||
    app.exec_()
 | 
					    app.exec_()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user