Framework for saving/restoring state in the table views. Needs to be linked up fully.

This commit is contained in:
Kovid Goyal 2010-05-17 22:41:53 -06:00
parent 61e78b6a17
commit 428cebd365
4 changed files with 120 additions and 72 deletions

View File

@ -5,5 +5,6 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import Qt
DEFAULT_SORT = ('timestamp', Qt.AscendingOrder)

View File

@ -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 <b>edit</b> me<br><br>"))
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

View File

@ -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_()

View File

@ -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()
############################################################################