diff --git a/src/calibre/gui2/library/__init__.py b/src/calibre/gui2/library/__init__.py index 0080175bfa..8aa897b413 100644 --- a/src/calibre/gui2/library/__init__.py +++ b/src/calibre/gui2/library/__init__.py @@ -5,5 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from PyQt4.Qt import Qt - +DEFAULT_SORT = ('timestamp', Qt.AscendingOrder) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index abff227ae3..97e2317dce 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -22,6 +22,7 @@ from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.utils.search_query_parser import SearchQueryParser from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH from calibre import strftime +from calibre.gui2.library import DEFAULT_SORT def human_readable(size, precision=1): """ Convert a size in bytes into megabytes """ @@ -58,7 +59,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.editable_cols = ['title', 'authors', 'rating', 'publisher', 'tags', 'series', 'timestamp', 'pubdate'] self.default_image = QImage(I('book.svg')) - self.sorted_on = ('timestamp', Qt.AscendingOrder) + self.sorted_on = DEFAULT_SORT self.sort_history = [self.sorted_on] self.last_search = '' # The last search performed on this model self.column_map = [] @@ -217,7 +218,6 @@ class BooksModel(QAbstractTableModel): # {{{ self.reset() self.sorted_on = (self.column_map[col], order) self.sort_history.insert(0, self.sorted_on) - del self.sort_history[3:] # clean up older searches self.sorting_done.emit(self.db.index) def refresh(self, reset=True): @@ -776,7 +776,19 @@ class DeviceBooksModel(BooksModel): # {{{ self.db = [] self.map = [] self.sorted_map = [] + self.sorted_on = DEFAULT_SORT + self.sort_history = [self.sorted_on] self.unknown = _('Unknown') + self.column_map = ['inlibrary', 'title', 'authors', 'timestamp', 'size', + 'tags'] + self.headers = { + 'inlibrary' : _('In Library'), + 'title' : _('Title'), + 'authors' : _('Author(s)'), + 'timestamp' : _('Date'), + 'size' : _('Size'), + 'tags' : _('Tags') + } self.marked_for_deletion = {} self.search_engine = OnDeviceSearch(self) self.editable = True @@ -813,7 +825,8 @@ class DeviceBooksModel(BooksModel): # {{{ return Qt.ItemIsUserCheckable # Can't figure out how to get the disabled flag in python flags = QAbstractTableModel.flags(self, index) if index.isValid() and self.editable: - if index.column() in [0, 1] or (index.column() == 4 and self.db.supports_tags()): + cname = self.column_map[index.column()] + if cname in ('title', 'authors') or (cname == 'tags' and self.db.supports_tags()): flags |= Qt.ItemIsEditable return flags @@ -881,22 +894,30 @@ class DeviceBooksModel(BooksModel): # {{{ x, y = authors_to_string(self.db[x].authors), \ authors_to_string(self.db[y].authors) return cmp(x, y) - fcmp = strcmp('title_sorter') if col == 0 else authorcmp if col == 1 else \ - sizecmp if col == 2 else datecmp if col == 3 else tagscmp if col == 4 else libcmp + cname = self.column_map[col] + fcmp = { + 'title': strcmp('title_sorter'), + 'authors' : authorcmp, + 'size' : sizecmp, + 'timestamp': datecmp, + 'tags': tagscmp, + 'inlibrary': libcmp, + }[cname] self.map.sort(cmp=fcmp, reverse=descending) if len(self.map) == len(self.db): self.sorted_map = list(self.map) else: self.sorted_map = list(range(len(self.db))) self.sorted_map.sort(cmp=fcmp, reverse=descending) - self.sorted_on = (col, order) + self.sorted_on = (self.column_map[col], order) + self.sort_history.insert(0, self.sorted_on) if reset: self.reset() def columnCount(self, parent): if parent and parent.isValid(): return 0 - return 6 + return len(self.column_map) def rowCount(self, parent): if parent and parent.isValid(): @@ -942,39 +963,35 @@ class DeviceBooksModel(BooksModel): # {{{ def data(self, index, role): row, col = index.row(), index.column() + cname = self.column_map[col] if role == Qt.DisplayRole or role == Qt.EditRole: - if col == 0: + if cname == 'title': text = self.db[self.map[row]].title if not text: text = self.unknown return QVariant(text) - elif col == 1: + elif cname == 'authors': au = self.db[self.map[row]].authors if not au: au = self.unknown -# if role == Qt.EditRole: -# return QVariant(au) return QVariant(authors_to_string(au)) - elif col == 2: + elif cname == 'size': size = self.db[self.map[row]].size return QVariant(human_readable(size)) - elif col == 3: + elif cname == 'timestamp': dt = self.db[self.map[row]].datetime dt = dt_factory(dt, assume_utc=True, as_utc=False) return QVariant(strftime(TIME_FMT, dt.timetuple())) - elif col == 4: + elif cname == 'tags': tags = self.db[self.map[row]].tags if tags: return QVariant(', '.join(tags)) - elif role == Qt.TextAlignmentRole and index.column() in [2, 3]: - return QVariant(Qt.AlignRight | Qt.AlignVCenter) elif role == Qt.ToolTipRole and index.isValid(): - if self.map[index.row()] in self.indices_to_be_deleted(): - return QVariant('Marked for deletion') - col = index.column() - if col in [0, 1] or (col == 4 and self.db.supports_tags()): + if self.map[row] in self.indices_to_be_deleted(): + return QVariant(_('Marked for deletion')) + if cname in ['title', 'authors'] or (cname == 'tags' and self.db.supports_tags()): return QVariant(_("Double click to edit me

")) - elif role == Qt.DecorationRole and col == 5: + elif role == Qt.DecorationRole and cname == 'inlibrary': if self.db[self.map[row]].in_library: return QVariant(self.bool_yes_icon) @@ -983,14 +1000,9 @@ class DeviceBooksModel(BooksModel): # {{{ def headerData(self, section, orientation, role): if role != Qt.DisplayRole: return NONE - text = "" if orientation == Qt.Horizontal: - if section == 0: text = _("Title") - elif section == 1: text = _("Author(s)") - elif section == 2: text = _("Size (MB)") - elif section == 3: text = _("Date") - elif section == 4: text = _("Tags") - elif section == 5: text = _("In Library") + cname = self.column_map[section] + text = self.headers[cname] return QVariant(text) else: return QVariant(section+1) @@ -999,23 +1011,22 @@ class DeviceBooksModel(BooksModel): # {{{ done = False if role == Qt.EditRole: row, col = index.row(), index.column() - if col in [2, 3]: + cname = self.column_map[col] + if cname in ('size', 'timestamp', 'inlibrary'): return False val = unicode(value.toString()).strip() idx = self.map[row] - if col == 0: + if cname == 'title' : self.db[idx].title = val self.db[idx].title_sorter = val - elif col == 1: + elif cname == 'authors': self.db[idx].authors = string_to_authors(val) - elif col == 4: + elif cname == 'tags': tags = [i.strip() for i in val.split(',')] tags = [t for t in tags if t] self.db.set_tags(self.db[idx], tags) self.dataChanged.emit(index, index) self.booklist_dirtied.emit() - if col == self.sorted_on[0]: - self.sort(col, self.sorted_on[1]) done = True return done diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 8734e7582a..ee7ab5e838 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -15,7 +15,8 @@ from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \ CcBoolDelegate, CcCommentsDelegate, CcDateDelegate from calibre.gui2.library.models import BooksModel, DeviceBooksModel from calibre.utils.config import tweaks -from calibre.gui2 import error_dialog +from calibre.gui2 import error_dialog, gprefs +from calibre.gui2.library import DEFAULT_SORT class BooksView(QTableView): # {{{ @@ -72,11 +73,9 @@ class BooksView(QTableView): # {{{ elif action == 'show': h.setSectionHidden(idx, False) elif action == 'ascending': - self._model.sort(idx, Qt.AscendingOrder) - h.setSortIndicator(idx, Qt.AscendingOrder) + self.sortByColumn(idx, Qt.AscendingOrder) elif action == 'descending': - self._model.sort(idx, Qt.DescendingOrder) - h.setSortIndicator(idx, Qt.DescendingOrder) + self.sortByColumn(idx, Qt.DescendingOrder) self.save_state() @@ -143,12 +142,15 @@ class BooksView(QTableView): # {{{ self._model.set_device_connected(is_connected) self.set_ondevice_column_visibility() + # Save/Restore State {{{ def get_state(self): h = self.column_header cm = self.column_map state = {} state['hidden_columns'] = [cm[i] for i in range(h.count()) if h.isSectionHidden(i) and cm[i] != 'ondevice'] + state['sort_history'] = \ + self.cleanup_sort_history(self.model().sort_history) state['column_positions'] = {} state['column_sizes'] = {} for i in range(h.count()): @@ -156,22 +158,83 @@ class BooksView(QTableView): # {{{ state['column_positions'][name] = h.visualIndex(i) if name != 'ondevice': state['column_sizes'][name] = h.sectionSize(i) - import pprint - pprint.pprint(state) return state def save_state(self): # Only save if we have been initialized (set_database called) if len(self.column_map) > 0: state = self.get_state() - state + name = unicode(self.objectName()) + if name: + gprefs.set(name + ' books view state', state) + + def cleanup_sort_history(self, sort_history): + history = [] + for col, order in sort_history: + if col in self.column_map and (not history or history[0][0] != col): + history.append([col, order]) + return history + + def apply_sort_history(self, saved_history): + if not saved_history: + return + for col, order in reversed(self.cleanup_sort_history(saved_history)[:3]): + self.sortByColumn(self.column_map.index(col), order) + #self.model().sort_history = saved_history def apply_state(self, state): - pass + h = self.column_header + cmap = {} + hidden = state.get('hidden_columns', []) + for i, c in enumerate(self.column_map): + cmap[c] = i + if c != 'ondevice': + h.setSectionHidden(i, c in hidden) + + positions = state.get('column_positions', {}) + pmap = {} + for col, pos in positions.items(): + if col in cmap: + pmap[pos] = col + for pos in sorted(pmap.keys(), reverse=True): + col = pmap[pos] + idx = cmap[col] + current_pos = h.visualIndex(idx) + if current_pos != pos: + h.moveSection(current_pos, pos) + + sizes = state.get('column_sizes', {}) + for col, size in sizes.items(): + if col in cmap: + h.resizeSection(cmap[col], sizes[col]) + self.apply_sort_history(state.get('sort_history', None)) def restore_state(self): - pass + name = unicode(self.objectName()) + old_state = None + if name: + old_state = gprefs.get(name + ' books view state', None) + if old_state is None: + # Default layout + old_state = {'hidden_columns': [], + 'sort_history':[DEFAULT_SORT], + 'column_positions': {}, + 'column_sizes': {}} + h = self.column_header + cm = self.column_map + for i in range(h.count()): + name = cm[i] + old_state['column_positions'][name] = h.logicalIndex(i) + if name != 'ondevice': + old_state['column_sizes'][name] = \ + max(self.sizeHintForColumn(i), h.sectionSizeHint(i)) + if tweaks['sort_columns_at_startup'] is not None: + old_state['sort_history'] = tweaks['sort_columns_at_startup'] + + self.apply_state(old_state) + + # }}} @property def column_map(self): @@ -239,22 +302,6 @@ class BooksView(QTableView): # {{{ self.context_menu.popup(event.globalPos()) event.accept() - def restore_sort_at_startup(self, saved_history): - if tweaks['sort_columns_at_startup'] is not None: - saved_history = tweaks['sort_columns_at_startup'] - - if saved_history is None: - return - for col,order in reversed(saved_history): - self.sortByColumn(col, order) - self.model().sort_history = saved_history - - def sortByColumn(self, colname, order): - try: - idx = self._model.column_map.index(colname) - except ValueError: - idx = 0 - QTableView.sortByColumn(self, idx, order) @classmethod def paths_from_event(cls, event): @@ -340,9 +387,6 @@ class DeviceBooksView(BooksView): # {{{ def connect_dirtied_signal(self, slot): self._model.booklist_dirtied.connect(slot) - def sortByColumn(self, col, order): - QTableView.sortByColumn(self, col, order) - def dropEvent(self, *args): error_dialog(self, _('Not allowed'), _('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index ff063800d5..536c68f77d 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -532,7 +532,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.library_view.set_database(db) self.library_view.model().set_book_on_device_func(self.book_on_device) prefs['library_path'] = self.library_path - self.library_view.restore_sort_at_startup(dynamic.get('sort_history', [('timestamp', Qt.DescendingOrder)])) self.search.setFocus(Qt.OtherFocusReason) self.cover_cache = CoverCache(self.library_path) self.cover_cache.start() @@ -1017,12 +1016,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA) self.card_b_view.set_database(cardblist) self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA) - for view in (self.memory_view, self.card_a_view, self.card_b_view): - view.sortByColumn(3, Qt.DescendingOrder) - if view.model().rowCount(None) > 1: - view.resizeRowToContents(0) - height = view.rowHeight(0) - view.verticalHeader().setDefaultSectionSize(height) self.sync_news() self.sync_catalogs() self.refresh_ondevice_info(device_connected = True) @@ -2284,7 +2277,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.status_bar.clearMessage() self.search.clear_to_help() self.status_bar.reset_info() - self.library_view.sortByColumn(3, Qt.DescendingOrder) self.library_view.model().count_changed() ############################################################################