When searching, allow use of un-accented characters to match accented characters. This is dont in a language dependent way. So if your calibre interface language is set to English, n will match ñ, but if it is set to Spanish, it will not. Tag browser: When grouping by first letter, handle languages that have 'letters' made of more than one character.

This commit is contained in:
Kovid Goyal 2012-07-06 16:49:08 +05:30
commit e1c7404e02
6 changed files with 62 additions and 84 deletions

View File

@ -515,3 +515,13 @@ compile_gpm_templates = True
# default_tweak_format = 'remember' # default_tweak_format = 'remember'
default_tweak_format = None default_tweak_format = None
#: Enable multi-character first-letters in the tag browser
# Some languages have letters that can be represented by multiple characters.
# For example, Czech has a 'character' "ch" that sorts between "h" and "i".
# If this tweak is True, then the tag browser will take these characters into
# consideration when partitioning by first letter.
# Examples:
# enable_multicharacters_in_tag_browser = True
# enable_multicharacters_in_tag_browser = True
enable_multicharacters_in_tag_browser = True

View File

@ -5,7 +5,9 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from functools import partial
import textwrap import textwrap
from collections import OrderedDict
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit
from calibre.gui2.preferences.tweaks_ui import Ui_Form from calibre.gui2.preferences.tweaks_ui import Ui_Form
@ -18,8 +20,8 @@ from calibre.utils.search_query_parser import (ParseException,
SearchQueryParser) SearchQueryParser)
from PyQt4.Qt import (QAbstractListModel, Qt, QStyledItemDelegate, QStyle, from PyQt4.Qt import (QAbstractListModel, Qt, QStyledItemDelegate, QStyle,
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, QApplication,
QVBoxLayout, QPlainTextEdit, QLabel, QModelIndex) QVBoxLayout, QPlainTextEdit, QLabel, QModelIndex, QMenu, QIcon)
ROOT = QModelIndex() ROOT = QModelIndex()
@ -46,10 +48,12 @@ class Tweak(object): # {{{
if self.doc: if self.doc:
self.doc = translate(self.doc) self.doc = translate(self.doc)
self.var_names = var_names self.var_names = var_names
self.default_values = {} if self.var_names:
self.doc = u"%s: %s\n\n%s"%(_('ID'), self.var_names[0], self.doc)
self.default_values = OrderedDict()
for x in var_names: for x in var_names:
self.default_values[x] = defaults[x] self.default_values[x] = defaults[x]
self.custom_values = {} self.custom_values = OrderedDict()
for x in var_names: for x in var_names:
if x in custom: if x in custom:
self.custom_values[x] = custom[x] self.custom_values[x] = custom[x]
@ -326,7 +330,27 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.search.initialize('tweaks_search_history', help_text= self.search.initialize('tweaks_search_history', help_text=
_('Search for tweak')) _('Search for tweak'))
self.search.search.connect(self.find) self.search.search.connect(self.find)
self.view.setContextMenuPolicy(Qt.CustomContextMenu)
self.view.customContextMenuRequested.connect(self.show_context_menu)
self.copy_icon = QIcon(I('edit-copy.png'))
def show_context_menu(self, point):
idx = self.tweaks_view.currentIndex()
if not idx.isValid():
return True
tweak = self.tweaks.data(idx, Qt.UserRole)
self.context_menu = QMenu(self)
self.context_menu.addAction(self.copy_icon,
_('Copy to clipboard'),
partial(self.copy_item_to_clipboard,
val=tweak.name))
self.context_menu.popup(self.mapToGlobal(point))
return True
def copy_item_to_clipboard(self, val):
cb = QApplication.clipboard();
cb.clear()
cb.setText(val)
def plugin_tweaks(self): def plugin_tweaks(self):
raw = self.tweaks.plugin_tweaks_string raw = self.tweaks.plugin_tweaks_string
@ -442,7 +466,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication
app = QApplication([]) app = QApplication([])
#Tweaks() #Tweaks()
#test_widget #test_widget

View File

@ -17,7 +17,7 @@ from PyQt4.Qt import (QAbstractItemModel, QIcon, QVariant, QFont, Qt,
from calibre.gui2 import NONE, gprefs, config, error_dialog from calibre.gui2 import NONE, gprefs, config, error_dialog
from calibre.library.database2 import Tag from calibre.library.database2 import Tag
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key, lower, strcmp from calibre.utils.icu import sort_key, lower, strcmp, contractions
from calibre.library.field_metadata import TagsIcons, category_icon_map from calibre.library.field_metadata import TagsIcons, category_icon_map
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.utils.formatter import EvalFormatter from calibre.utils.formatter import EvalFormatter
@ -258,6 +258,16 @@ class TagsModel(QAbstractItemModel): # {{{
self.hidden_categories.add(cat) self.hidden_categories.add(cat)
db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories)) db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories))
conts = contractions()
if len(conts) == 0 or not tweaks['enable_multicharacters_in_tag_browser']:
self.do_contraction = False
else:
self.do_contraction = True
nconts = set()
for s in conts:
nconts.add(icu_upper(s))
self.contraction_set = frozenset(nconts)
self.db = db self.db = db
self._run_rebuild() self._run_rebuild()
self.endResetModel() self.endResetModel()
@ -416,7 +426,14 @@ class TagsModel(QAbstractItemModel): # {{{
if not tag.sort: if not tag.sort:
c = ' ' c = ' '
else: else:
c = icu_upper(tag.sort[0]) if not self.do_contraction:
c = icu_upper(tag.sort)[0]
else:
v = icu_upper(tag.sort)
c = v[0]
for s in self.contraction_set:
if len(s) > len(c) and v.startswith(s):
c = s
if c not in chardict: if c not in chardict:
chardict[c] = [idx, idx] chardict[c] = [idx, idx]
else: else:

View File

@ -20,6 +20,7 @@ from calibre.utils.localization import (canonicalize_lang, lang_map, get_udc,
from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre import prints from calibre import prints
from calibre.utils.icu import primary_find
class MetadataBackup(Thread): # {{{ class MetadataBackup(Thread): # {{{
''' '''
@ -147,9 +148,10 @@ def _match(query, value, matchkind):
return True return True
elif query == t: elif query == t:
return True return True
elif ((matchkind == REGEXP_MATCH and re.search(query, t, re.I|re.UNICODE)) or ### search unanchored elif matchkind == REGEXP_MATCH:
(matchkind == CONTAINS_MATCH and query in t)): return re.search(query, t, re.I|re.UNICODE)
return True elif matchkind == CONTAINS_MATCH:
return primary_find(query, t)[0] != -1
except re.error: except re.error:
pass pass
return False return False
@ -227,9 +229,6 @@ class ResultCache(SearchQueryParser): # {{{
def __init__(self, FIELD_MAP, field_metadata, db_prefs=None): def __init__(self, FIELD_MAP, field_metadata, db_prefs=None):
self.FIELD_MAP = FIELD_MAP self.FIELD_MAP = FIELD_MAP
l = get_lang() l = get_lang()
asciize_author_names = l and l.lower() in ('en', 'eng')
if not asciize_author_names:
self.ascii_name = lambda x: False
self.db_prefs = db_prefs self.db_prefs = db_prefs
self.composites = {} self.composites = {}
self.udc = get_udc() self.udc = get_udc()
@ -279,15 +278,6 @@ class ResultCache(SearchQueryParser): # {{{
# Search functions {{{ # Search functions {{{
def ascii_name(self, name):
try:
ans = self.udc.decode(name)
if ans == name:
ans = False
except:
ans = False
return ans
def universal_set(self): def universal_set(self):
return set([i[0] for i in self._data if i is not None]) return set([i[0] for i in self._data if i is not None])
@ -805,9 +795,6 @@ class ResultCache(SearchQueryParser): # {{{
if loc not in exclude_fields: # time for text matching if loc not in exclude_fields: # time for text matching
if is_multiple_cols[loc] is not None: if is_multiple_cols[loc] is not None:
vals = [v.strip() for v in item[loc].split(is_multiple_cols[loc])] vals = [v.strip() for v in item[loc].split(is_multiple_cols[loc])]
if loc == au_loc:
vals += filter(None, map(self.ascii_name,
vals))
else: else:
vals = [item[loc]] ### make into list to make _match happy vals = [item[loc]] ### make into list to make _match happy
if _match(q, vals, matchkind): if _match(q, vals, matchkind):

View File

@ -272,42 +272,6 @@ icu_Collator_contractions(icu_Collator *self, PyObject *args, PyObject *kwargs)
return Py_BuildValue("O", ans); return Py_BuildValue("O", ans);
} // }}} } // }}}
// Collator.span_contractions {{{
#ifndef __APPLE__
// uset_span is not available in the version of ICU on Apple's idiotic OS
static PyObject *
icu_Collator_span_contractions(icu_Collator *self, PyObject *args, PyObject *kwargs) {
int span_type;
UErrorCode status = U_ZERO_ERROR;
PyObject *str;
size_t slen = 0;
wchar_t *buf;
UChar *s;
int32_t ret;
if (!PyArg_ParseTuple(args, "Ui", &str, &span_type)) return NULL;
if (self->contractions == NULL) {
self->contractions = uset_open(1, 0);
if (self->contractions == NULL) return PyErr_NoMemory();
self->contractions = ucol_getTailoredSet(self->collator, &status);
}
status = U_ZERO_ERROR;
slen = PyUnicode_GetSize(str);
buf = (wchar_t*)calloc(slen*4 + 2, sizeof(wchar_t));
s = (UChar*)calloc(slen*4 + 2, sizeof(UChar));
if (buf == NULL || s == NULL) return PyErr_NoMemory();
PyUnicode_AsWideChar((PyUnicodeObject*)str, buf, slen);
u_strFromWCS(s, slen*4+1, NULL, buf, slen, &status);
ret = uset_span(self->contractions, s, slen, span_type);
free(s); free(buf);
return Py_BuildValue("i", ret);
}
#endif
// }}}
static PyObject* static PyObject*
icu_Collator_clone(icu_Collator *self, PyObject *args, PyObject *kwargs); icu_Collator_clone(icu_Collator *self, PyObject *args, PyObject *kwargs);
@ -328,12 +292,6 @@ static PyMethodDef icu_Collator_methods[] = {
"contractions() -> returns the contractions defined for this collator." "contractions() -> returns the contractions defined for this collator."
}, },
#ifndef __APPLE__
{"span_contractions", (PyCFunction)icu_Collator_span_contractions, METH_VARARGS,
"span_contractions(src, span_condition) -> returns the length of the initial substring according to span_condition in the set of contractions for this collator. Returns 0 if src does not fit the span_condition. The span_condition can be one of USET_SPAN_NOT_CONTAINED, USET_SPAN_CONTAINED, USET_SPAN_SIMPLE."
},
#endif
{"clone", (PyCFunction)icu_Collator_clone, METH_VARARGS, {"clone", (PyCFunction)icu_Collator_clone, METH_VARARGS,
"clone() -> returns a clone of this collator." "clone() -> returns a clone of this collator."
}, },

View File

@ -110,20 +110,6 @@ def icu_contractions(collator):
_cmap[collator] = ans _cmap[collator] = ans
return ans return ans
def py_span_contractions(*args, **kwargs):
return 0
def icu_span_contractions(src, span_type=None, collator=None):
global _collator
if collator is None:
collator = _collator
if span_type is None:
span_type = _icu.USET_SPAN_SIMPLE
try:
return collator.span_contractions(src, span_type)
except TypeError:
return collator.span_contractions(unicode(src), span_type)
load_icu() load_icu()
load_collator() load_collator()
_icu_not_ok = _icu is None or _collator is None _icu_not_ok = _icu is None or _collator is None
@ -164,9 +150,6 @@ find = (py_find if _icu_not_ok else partial(icu_find, _collator))
contractions = ((lambda : {}) if _icu_not_ok else (partial(icu_contractions, contractions = ((lambda : {}) if _icu_not_ok else (partial(icu_contractions,
_collator))) _collator)))
span_contractions = (py_span_contractions if _icu_not_ok else
icu_span_contractions)
def primary_strcmp(a, b): def primary_strcmp(a, b):
'strcmp that ignores case and accents on letters' 'strcmp that ignores case and accents on letters'
if _icu_not_ok: if _icu_not_ok: