From 0012f5cc4ba97eeee7a4c397ec091b0fff942d47 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 6 Jan 2014 17:00:21 +0530 Subject: [PATCH] Edit book: Add a tool to insert arbitrary unicode characters, such as special spaces or punctuation or characters from non-English languages into the text --- imgsrc/character-set.svg | 201 +++++++++++++++++++++ resources/images/character-set.png | Bin 0 -> 4794 bytes src/calibre/gui2/tweak_book/boss.py | 3 + src/calibre/gui2/tweak_book/char_select.py | 36 +++- src/calibre/gui2/tweak_book/ui.py | 6 + 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 imgsrc/character-set.svg create mode 100644 resources/images/character-set.png diff --git a/imgsrc/character-set.svg b/imgsrc/character-set.svg new file mode 100644 index 0000000000..e513ec0451 --- /dev/null +++ b/imgsrc/character-set.svg @@ -0,0 +1,201 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/resources/images/character-set.png b/resources/images/character-set.png new file mode 100644 index 0000000000000000000000000000000000000000..5831b73882635cca37a8e59b74b937b9cae7d559 GIT binary patch literal 4794 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 R&;FSJT`ePAwT4~P{{iCJvw{Es literal 0 HcmV?d00001 diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 506eaa9599..c05864725d 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -1068,6 +1068,9 @@ class Boss(QObject): if not editors: self.gui.preview.clear() + def insert_character(self): + self.gui.insert_char.show() + # Shutdown {{{ def quit(self): if not self.confirm_quit(): diff --git a/src/calibre/gui2/tweak_book/char_select.py b/src/calibre/gui2/tweak_book/char_select.py index 3338e46d4f..53da28314e 100644 --- a/src/calibre/gui2/tweak_book/char_select.py +++ b/src/calibre/gui2/tweak_book/char_select.py @@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import unicodedata, re +import unicodedata, re, weakref from bisect import bisect from functools import partial @@ -548,6 +548,7 @@ class CharDelegate(QStyledItemDelegate): class CharView(QListView): show_name = pyqtSignal(object) + char_selected = pyqtSignal(object) def __init__(self, parent=None): self.last_mouse_idx = -1 @@ -564,6 +565,16 @@ class CharView(QListView): self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.context_menu) self.showing_favorites = False + pi = plugins['progress_indicator'][0] + if hasattr(pi, 'set_no_activate_on_click'): + pi.set_no_activate_on_click(self) + self.activated.connect(self.item_activated) + self.clicked.connect(self.item_activated) + + def item_activated(self, index): + char_code, ok = self.model().data(index, Qt.UserRole).toInt() + if ok: + self.char_selected.emit(chr(char_code)) def set_allow_drag_and_drop(self, enabled): if not enabled: @@ -640,6 +651,8 @@ class CharSelect(Dialog): def __init__(self, parent=None): self.initialized = False Dialog.__init__(self, _('Insert character'), 'charmap_dialog', parent) + self.setWindowIcon(QIcon(I('character-set.png'))) + self.focus_widget = None def setup_ui(self): self.l = l = QGridLayout(self) @@ -650,6 +663,7 @@ class CharSelect(Dialog): b.setCheckable(True) b.setChecked(False) b.setVisible(False) + b.setDefault(True) self.splitter = s = QSplitter(self) s.setChildrenCollapsible(False) @@ -660,6 +674,7 @@ class CharSelect(Dialog): self.rearrange_button.toggled[bool].connect(self.set_allow_drag_and_drop) self.category_view.category_selected.connect(self.show_chars) self.char_view.show_name.connect(self.show_char_info) + self.char_view.char_selected.connect(self.char_selected) s.addWidget(self.category_view), s.addWidget(self.char_view) self.char_info = la = QLabel('\xa0') @@ -701,10 +716,29 @@ class CharSelect(Dialog): self.char_info.clear() def show(self): + try: + self.focus_widget = weakref.ref(QApplication.focusWidget()) + except TypeError: + self.focus_widget = None self.initialize() Dialog.show(self) self.raise_() + def char_selected(self, c): + if QApplication.keyboardModifiers() & Qt.CTRL: + self.hide() + if self.focus_widget is None or self.focus_widget() is None: + QApplication.clipboard().setText(c) + return + w = self.focus_widget() + if hasattr(w, 'textCursor'): + cr = w.textCursor() + cr.insertText(c) + w.setTextCursor(cr) + elif hasattr(w, 'insert'): + w.insert(c) + + if __name__ == '__main__': app = QApplication([]) w = CharSelect() diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 8282c9eae7..81f0134c3c 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -29,6 +29,7 @@ from calibre.gui2.tweak_book.preview import Preview from calibre.gui2.tweak_book.search import SearchPanel from calibre.gui2.tweak_book.check import Check from calibre.gui2.tweak_book.toc import TOCViewer +from calibre.gui2.tweak_book.char_select import CharSelect from calibre.gui2.tweak_book.editor.widget import register_text_editor_actions from calibre.gui2.tweak_book.editor.insert_resource import InsertImage @@ -217,6 +218,7 @@ class Main(MainWindow): self.check_book = Check(self) self.toc_view = TOCViewer(self) self.image_browser = InsertImage(self, for_browsing=True) + self.insert_char = CharSelect(self) self.create_actions() self.create_toolbars() @@ -321,6 +323,8 @@ class Main(MainWindow): _('Beautify current file')) self.action_pretty_all = reg('format-justify-fill.png', _('&Beautify all files'), partial(self.boss.pretty_print, False), 'pretty-all', (), _('Beautify all files')) + self.action_insert_char = reg('character-set.png', _('&Insert special character'), self.boss.insert_character, 'insert-character', (), + _('Insert special character')) # Polish actions group = _('Polish Book') @@ -423,6 +427,7 @@ class Main(MainWindow): e.addAction(self.action_editor_cut) e.addAction(self.action_editor_copy) e.addAction(self.action_editor_paste) + e.addAction(self.action_insert_char) e.addSeparator() e.addAction(self.action_preferences) @@ -505,6 +510,7 @@ class Main(MainWindow): b.setToolTip(_('Donate to support calibre development')) QTimer.singleShot(10, b.start_animation) self.global_bar.addWidget(w) + self.global_bar.addAction(self.action_insert_char) a(self.action_help) a = create(_('Polish book tool bar'), 'polish').addAction