mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-31 14:33:54 -04:00
Framework for saving/restoring state in the table views. Needs to be linked up fully.
This commit is contained in:
parent
61e78b6a17
commit
428cebd365
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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_()
|
||||
|
@ -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()
|
||||
|
||||
############################################################################
|
||||
|
Loading…
x
Reference in New Issue
Block a user