diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 0a4b7a26ba..8d89ec76ed 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -310,7 +310,6 @@ class BooksModel(QAbstractTableModel): # {{{ def sort(self, col, order, reset=True): if not self.db: return - self.about_to_be_sorted.emit(self.db.id) if not isinstance(order, bool): order = order == Qt.AscendingOrder label = self.column_map[col] @@ -321,6 +320,7 @@ class BooksModel(QAbstractTableModel): # {{{ self._sort(field, order, reset) def _sort(self, label, order, reset): + self.about_to_be_sorted.emit(self.db.id) self.db.sort(label, order) if reset: self.reset() diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e87e7226e1..0b0cecc1ea 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -246,6 +246,36 @@ class BooksView(QTableView): # {{{ self.sortByColumn(idx, Qt.DescendingOrder) else: self._model.sort_by_named_field(field, order, reset) + + def multisort(self, fields, reset=True, only_if_different=False): + if len(fields) == 0: + return + sh = self.cleanup_sort_history(self._model.sort_history, + ignore_column_map=True) + if only_if_different and len(sh) >= len(fields): + ret=True + for i,t in enumerate(fields): + if t[0] != sh[i][0]: + ret = False + break + if ret: + return + + for n,d in reversed(fields): + if n in self._model.db.field_metadata.keys(): + sh.insert(0, (n, d)) + sh = self.cleanup_sort_history(sh, ignore_column_map=True) + self._model.sort_history = [tuple(x) for x in sh] + self._model.resort(reset=reset) + col = fields[0][0] + dir = Qt.AscendingOrder if fields[0][1] else Qt.DescendingOrder + if col in self.column_map: + col = self.column_map.index(col) + hdrs = self.horizontalHeader() + try: + hdrs.setSortIndicator(col, dir) + except: + pass # }}} # Ondevice column {{{ @@ -290,14 +320,14 @@ class BooksView(QTableView): # {{{ state = self.get_state() self.write_state(state) - def cleanup_sort_history(self, sort_history): + def cleanup_sort_history(self, sort_history, ignore_column_map=False): history = [] for col, order in sort_history: if not isinstance(order, bool): continue if col == 'date': col = 'timestamp' - if col in self.column_map: + if ignore_column_map or col in self.column_map: if (not history or history[-1][0] != col): history.append([col, order]) return history diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 4d696afe91..5b22ad6787 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -592,7 +592,8 @@ class ResultCache(SearchQueryParser): # {{{ candidates = self.universal_set() if len(candidates) == 0: return matches - self.test_location_is_valid(location, query) + if location not in self.all_search_locations: + return matches if len(location) > 2 and location.startswith('@') and \ location[1:] in self.db_prefs['grouped_search_terms']: diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index 10d8b64a0d..a50ca20fc1 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -19,8 +19,8 @@ If this module is run, it will perform a series of unit tests. import sys, string, operator from calibre.utils.pyparsing import CaselessKeyword, Group, Forward, \ - CharsNotIn, Suppress, OneOrMore, MatchFirst, alphas, alphanums, \ - Optional, ParseException, QuotedString, Word + CharsNotIn, Suppress, OneOrMore, MatchFirst, CaselessLiteral, \ + Optional, NoMatch, ParseException, QuotedString from calibre.constants import preferred_encoding from calibre.utils.icu import sort_key @@ -128,9 +128,12 @@ class SearchQueryParser(object): self._tests_failed = False self.optimize = optimize # Define a token - self.standard_locations = locations - location = Optional(Word(alphas+'#', bodyChars=alphanums+'_')+Suppress(':'), - default='all') + standard_locations = map(lambda x : CaselessLiteral(x)+Suppress(':'), + locations) + location = NoMatch() + for l in standard_locations: + location |= l + location = Optional(location, default='all') word_query = CharsNotIn(string.whitespace + '()') #quoted_query = Suppress('"')+CharsNotIn('"')+Suppress('"') quoted_query = QuotedString('"', escChar='\\') @@ -247,14 +250,7 @@ class SearchQueryParser(object): raise ParseException(query, len(query), 'undefined saved search', self) return self._get_matches(location, query, candidates) - def test_location_is_valid(self, location, query): - if location not in self.standard_locations: - raise ParseException(query, len(query), - _('No column exists with lookup name ') + location, self) - def _get_matches(self, location, query, candidates): - location = location.lower() - self.test_location_is_valid(location, query) if self.optimize: return self.get_matches(location, query, candidates=candidates) else: