diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 10ce9b5c2c..cc798a09ba 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -515,3 +515,13 @@ compile_gpm_templates = True # default_tweak_format = 'remember' 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 + diff --git a/src/calibre/gui2/preferences/tweaks.py b/src/calibre/gui2/preferences/tweaks.py index ebea8574a7..290fff37ba 100644 --- a/src/calibre/gui2/preferences/tweaks.py +++ b/src/calibre/gui2/preferences/tweaks.py @@ -5,7 +5,9 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from functools import partial import textwrap +from collections import OrderedDict from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit from calibre.gui2.preferences.tweaks_ui import Ui_Form @@ -18,8 +20,8 @@ from calibre.utils.search_query_parser import (ParseException, SearchQueryParser) from PyQt4.Qt import (QAbstractListModel, Qt, QStyledItemDelegate, QStyle, - QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, - QVBoxLayout, QPlainTextEdit, QLabel, QModelIndex) + QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, QApplication, + QVBoxLayout, QPlainTextEdit, QLabel, QModelIndex, QMenu, QIcon) ROOT = QModelIndex() @@ -46,10 +48,12 @@ class Tweak(object): # {{{ if self.doc: self.doc = translate(self.doc) 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: self.default_values[x] = defaults[x] - self.custom_values = {} + self.custom_values = OrderedDict() for x in var_names: if x in custom: self.custom_values[x] = custom[x] @@ -326,7 +330,27 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.search.initialize('tweaks_search_history', help_text= _('Search for tweak')) 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): raw = self.tweaks.plugin_tweaks_string @@ -442,7 +466,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): if __name__ == '__main__': - from PyQt4.Qt import QApplication app = QApplication([]) #Tweaks() #test_widget diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 8c6cb7d0eb..0b6a681a72 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -17,7 +17,7 @@ from PyQt4.Qt import (QAbstractItemModel, QIcon, QVariant, QFont, Qt, from calibre.gui2 import NONE, gprefs, config, error_dialog from calibre.library.database2 import Tag 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.gui2.dialogs.confirm_delete import confirm from calibre.utils.formatter import EvalFormatter @@ -258,6 +258,16 @@ class TagsModel(QAbstractItemModel): # {{{ self.hidden_categories.add(cat) 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._run_rebuild() self.endResetModel() @@ -416,7 +426,14 @@ class TagsModel(QAbstractItemModel): # {{{ if not tag.sort: c = ' ' 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: chardict[c] = [idx, idx] else: diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 191ac53f94..72e39e5651 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -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.opf2 import metadata_to_opf from calibre import prints +from calibre.utils.icu import primary_find class MetadataBackup(Thread): # {{{ ''' @@ -147,9 +148,10 @@ def _match(query, value, matchkind): return True elif query == t: return True - elif ((matchkind == REGEXP_MATCH and re.search(query, t, re.I|re.UNICODE)) or ### search unanchored - (matchkind == CONTAINS_MATCH and query in t)): - return True + elif matchkind == REGEXP_MATCH: + return re.search(query, t, re.I|re.UNICODE) + elif matchkind == CONTAINS_MATCH: + return primary_find(query, t)[0] != -1 except re.error: pass return False @@ -227,9 +229,6 @@ class ResultCache(SearchQueryParser): # {{{ def __init__(self, FIELD_MAP, field_metadata, db_prefs=None): self.FIELD_MAP = FIELD_MAP 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.composites = {} self.udc = get_udc() @@ -279,15 +278,6 @@ class ResultCache(SearchQueryParser): # {{{ # 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): 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 is_multiple_cols[loc] is not None: 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: vals = [item[loc]] ### make into list to make _match happy if _match(q, vals, matchkind): diff --git a/src/calibre/utils/icu.c b/src/calibre/utils/icu.c index 33c8b456f4..e936552128 100644 --- a/src/calibre/utils/icu.c +++ b/src/calibre/utils/icu.c @@ -272,42 +272,6 @@ icu_Collator_contractions(icu_Collator *self, PyObject *args, PyObject *kwargs) 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* 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." }, -#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() -> returns a clone of this collator." }, diff --git a/src/calibre/utils/icu.py b/src/calibre/utils/icu.py index 46d3c93206..76a374d085 100644 --- a/src/calibre/utils/icu.py +++ b/src/calibre/utils/icu.py @@ -110,20 +110,6 @@ def icu_contractions(collator): _cmap[collator] = 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_collator() _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, _collator))) -span_contractions = (py_span_contractions if _icu_not_ok else - icu_span_contractions) - def primary_strcmp(a, b): 'strcmp that ignores case and accents on letters' if _icu_not_ok: