mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add searching to the charmap dialog
This commit is contained in:
parent
305379f27f
commit
db71b29026
@ -6,17 +6,20 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import unicodedata, re
|
import unicodedata, re, os, cPickle
|
||||||
from bisect import bisect
|
from bisect import bisect
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QAbstractItemModel, QModelIndex, Qt, QVariant, pyqtSignal, QApplication,
|
QAbstractItemModel, QModelIndex, Qt, QVariant, pyqtSignal, QApplication,
|
||||||
QTreeView, QSize, QGridLayout, QAbstractListModel, QListView, QPen, QMenu,
|
QTreeView, QSize, QGridLayout, QAbstractListModel, QListView, QPen, QMenu,
|
||||||
QStyledItemDelegate, QSplitter, QLabel, QSizePolicy, QIcon, QMimeData)
|
QStyledItemDelegate, QSplitter, QLabel, QSizePolicy, QIcon, QMimeData,
|
||||||
|
QPushButton, QToolButton)
|
||||||
|
|
||||||
from calibre.constants import ispy3, plugins
|
from calibre.constants import ispy3, plugins, cache_dir
|
||||||
from calibre.gui2 import NONE
|
from calibre.gui2 import NONE
|
||||||
|
from calibre.gui2.widgets2 import HistoryLineEdit2
|
||||||
from calibre.gui2.tweak_book import tprefs
|
from calibre.gui2.tweak_book import tprefs
|
||||||
from calibre.gui2.tweak_book.editor.insert_resource import Dialog
|
from calibre.gui2.tweak_book.editor.insert_resource import Dialog
|
||||||
|
|
||||||
@ -32,6 +35,58 @@ non_printing = {
|
|||||||
0x206e: 'nads', 0x206f: 'nods', 0x20: 'sp', 0x7f: 'del', 0x2e3a: '2m', 0x2e3b: '3m', 0xad: 'shy',
|
0x206e: 'nads', 0x206f: 'nods', 0x20: 'sp', 0x7f: 'del', 0x2e3a: '2m', 0x2e3b: '3m', 0xad: 'shy',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Searching {{{
|
||||||
|
def load_search_index():
|
||||||
|
ver = 1 # Increment this when you make any changes to the index
|
||||||
|
name_map = {}
|
||||||
|
path = os.path.join(cache_dir(), 'unicode-name-index.pickle')
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
name_map = cPickle.load(f)
|
||||||
|
if name_map.pop('calibre-nm-version:', -1) != ver:
|
||||||
|
name_map = {}
|
||||||
|
if not name_map:
|
||||||
|
name_map = defaultdict(set)
|
||||||
|
from calibre.constants import ispy3
|
||||||
|
if not ispy3:
|
||||||
|
chr = unichr
|
||||||
|
for x in xrange(1, 0x10FFFF + 1):
|
||||||
|
for word in unicodedata.name(chr(x), '').split():
|
||||||
|
name_map[word.lower()].add(x)
|
||||||
|
from calibre.ebooks.html_entities import html5_entities
|
||||||
|
for name, char in html5_entities.iteritems():
|
||||||
|
try:
|
||||||
|
name_map[name.lower()].add(ord(char))
|
||||||
|
except TypeError:
|
||||||
|
continue
|
||||||
|
name_map['nnbsp'].add(0x202F)
|
||||||
|
name_map['calibre-nm-version:'] = ver
|
||||||
|
cPickle.dump(dict(name_map), open(path, 'wb'), -1)
|
||||||
|
del name_map['calibre-nm-version:']
|
||||||
|
return name_map
|
||||||
|
|
||||||
|
_index = None
|
||||||
|
|
||||||
|
def search_for_chars(query, and_tokens=False):
|
||||||
|
global _index
|
||||||
|
if _index is None:
|
||||||
|
_index = load_search_index()
|
||||||
|
ans = set()
|
||||||
|
for token in query.split():
|
||||||
|
token = token.lower()
|
||||||
|
m = re.match(r'(?:[u]\+)([a-f0-9]+)', token)
|
||||||
|
if m is not None:
|
||||||
|
chars = {int(m.group(1), 16)}
|
||||||
|
else:
|
||||||
|
chars = _index.get(token, None)
|
||||||
|
if chars is not None:
|
||||||
|
if and_tokens:
|
||||||
|
ans &= chars
|
||||||
|
else:
|
||||||
|
ans |= chars
|
||||||
|
return sorted(ans)
|
||||||
|
# }}}
|
||||||
|
|
||||||
class CategoryModel(QAbstractItemModel):
|
class CategoryModel(QAbstractItemModel):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -452,10 +507,16 @@ class CategoryView(QTreeView):
|
|||||||
else:
|
else:
|
||||||
self.expand(index)
|
self.expand(index)
|
||||||
|
|
||||||
|
def get_chars(self):
|
||||||
|
ans = self._model.get_range(self.currentIndex())
|
||||||
|
if ans is not None:
|
||||||
|
self.category_selected.emit(*ans)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
if not self.initialized:
|
if not self.initialized:
|
||||||
self._model = m = CategoryModel(self)
|
self._model = m = CategoryModel(self)
|
||||||
self.setModel(m)
|
self.setModel(m)
|
||||||
|
self.setCurrentIndex(m.index(0, 0))
|
||||||
self.item_activated(m.index(0, 0))
|
self.item_activated(m.index(0, 0))
|
||||||
self._delegate = CategoryDelegate(self)
|
self._delegate = CategoryDelegate(self)
|
||||||
self.setItemDelegate(self._delegate)
|
self.setItemDelegate(self._delegate)
|
||||||
@ -668,8 +729,24 @@ class CharSelect(Dialog):
|
|||||||
self.splitter = s = QSplitter(self)
|
self.splitter = s = QSplitter(self)
|
||||||
s.setChildrenCollapsible(False)
|
s.setChildrenCollapsible(False)
|
||||||
|
|
||||||
|
self.search = h = HistoryLineEdit2(self)
|
||||||
|
h.setToolTip(_(
|
||||||
|
'Search for unicode characters by using the English names or nicknames.'
|
||||||
|
' You can also search directly using a character code. For example, the following'
|
||||||
|
' searches will all yield the no-break space character: U+A0, nbsp, no-break'))
|
||||||
|
h.initialize('charmap_search')
|
||||||
|
h.setPlaceholderText(_('Search by name, nickname or character code'))
|
||||||
|
self.search_button = b = QPushButton(_('&Search'))
|
||||||
|
h.returnPressed.connect(self.do_search)
|
||||||
|
b.clicked.connect(self.do_search)
|
||||||
|
self.clear_button = cb = QToolButton(self)
|
||||||
|
cb.setIcon(QIcon(I('clear_left.png')))
|
||||||
|
cb.setText(_('Clear search'))
|
||||||
|
cb.clicked.connect(self.clear_search)
|
||||||
|
l.addWidget(h), l.addWidget(b, 0, 1), l.addWidget(cb, 0, 2)
|
||||||
|
|
||||||
self.category_view = CategoryView(self)
|
self.category_view = CategoryView(self)
|
||||||
l.addWidget(s)
|
l.addWidget(s, 1, 0, 1, 3)
|
||||||
self.char_view = CharView(self)
|
self.char_view = CharView(self)
|
||||||
self.rearrange_button.toggled[bool].connect(self.set_allow_drag_and_drop)
|
self.rearrange_button.toggled[bool].connect(self.set_allow_drag_and_drop)
|
||||||
self.category_view.category_selected.connect(self.show_chars)
|
self.category_view.category_selected.connect(self.show_chars)
|
||||||
@ -679,14 +756,26 @@ class CharSelect(Dialog):
|
|||||||
|
|
||||||
self.char_info = la = QLabel('\xa0')
|
self.char_info = la = QLabel('\xa0')
|
||||||
la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
l.addWidget(la)
|
l.addWidget(la, 2, 0, 1, 3)
|
||||||
|
|
||||||
self.rearrange_msg = la = QLabel(_(
|
self.rearrange_msg = la = QLabel(_(
|
||||||
'Drag and drop characters to re-arrange them. Click the re-arrange button again when you are done.'))
|
'Drag and drop characters to re-arrange them. Click the re-arrange button again when you are done.'))
|
||||||
la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
la.setVisible(False)
|
la.setVisible(False)
|
||||||
l.addWidget(la)
|
l.addWidget(la, 3, 0, 1, 3)
|
||||||
l.addWidget(self.bb)
|
l.addWidget(self.bb, 4, 0, 1, 3)
|
||||||
|
self.char_view.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
def do_search(self):
|
||||||
|
text = unicode(self.search.text()).strip()
|
||||||
|
if not text:
|
||||||
|
return self.clear_search()
|
||||||
|
chars = search_for_chars(text)
|
||||||
|
self.show_chars(_('Search'), chars)
|
||||||
|
|
||||||
|
def clear_search(self):
|
||||||
|
self.search.clear()
|
||||||
|
self.category_view.get_chars()
|
||||||
|
|
||||||
def set_allow_drag_and_drop(self, on):
|
def set_allow_drag_and_drop(self, on):
|
||||||
self.char_view.set_allow_drag_and_drop(on)
|
self.char_view.set_allow_drag_and_drop(on)
|
||||||
@ -711,7 +800,7 @@ class CharSelect(Dialog):
|
|||||||
def show_char_info(self, char_code):
|
def show_char_info(self, char_code):
|
||||||
if char_code > 0:
|
if char_code > 0:
|
||||||
category_name, subcategory_name, character_name = self.category_view.model().get_char_info(char_code)
|
category_name, subcategory_name, character_name = self.category_view.model().get_char_info(char_code)
|
||||||
self.char_info.setText('%s - %s - %s (%04X)' % (category_name, subcategory_name, character_name, char_code))
|
self.char_info.setText('%s - %s - %s (U+%04X)' % (category_name, subcategory_name, character_name, char_code))
|
||||||
else:
|
else:
|
||||||
self.char_info.clear()
|
self.char_info.clear()
|
||||||
|
|
||||||
|
@ -270,6 +270,7 @@ class TextEdit(QPlainTextEdit):
|
|||||||
c.setPosition(start)
|
c.setPosition(start)
|
||||||
c.setPosition(end, c.KeepAnchor)
|
c.setPosition(end, c.KeepAnchor)
|
||||||
self.setTextCursor(c)
|
self.setTextCursor(c)
|
||||||
|
self.centerCursor()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def all_in_marked(self, pat, template=None):
|
def all_in_marked(self, pat, template=None):
|
||||||
@ -323,6 +324,7 @@ class TextEdit(QPlainTextEdit):
|
|||||||
c.setPosition(start)
|
c.setPosition(start)
|
||||||
c.setPosition(end, c.KeepAnchor)
|
c.setPosition(end, c.KeepAnchor)
|
||||||
self.setTextCursor(c)
|
self.setTextCursor(c)
|
||||||
|
self.centerCursor()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def replace(self, pat, template):
|
def replace(self, pat, template):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user