mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Device collections editor, new rename collections job, changes to tag_list_edit so it could be reused.
This commit is contained in:
parent
b50024f6c3
commit
55b9a96fd8
@ -99,7 +99,7 @@ class PRS505(USBMS):
|
|||||||
if self._card_b_prefix is not None:
|
if self._card_b_prefix is not None:
|
||||||
if not write_cache(self._card_b_prefix):
|
if not write_cache(self._card_b_prefix):
|
||||||
self._card_b_prefix = None
|
self._card_b_prefix = None
|
||||||
|
self.booklist_class.rebuild_collections = self.rebuild_collections
|
||||||
|
|
||||||
def get_device_information(self, end_session=True):
|
def get_device_information(self, end_session=True):
|
||||||
return (self.gui_name, '', '', '')
|
return (self.gui_name, '', '', '')
|
||||||
@ -156,4 +156,10 @@ class PRS505(USBMS):
|
|||||||
USBMS.sync_booklists(self, booklists, end_session=end_session)
|
USBMS.sync_booklists(self, booklists, end_session=end_session)
|
||||||
debug_print('PRS505: finished sync_booklists')
|
debug_print('PRS505: finished sync_booklists')
|
||||||
|
|
||||||
|
def rebuild_collections(self, booklist, oncard):
|
||||||
|
debug_print('PRS505: started rebuild_collections')
|
||||||
|
c = self.initialize_XML_cache()
|
||||||
|
c.rebuild_collections(booklist, {'carda':1, 'cardb':2}.get(oncard, 0))
|
||||||
|
c.write()
|
||||||
|
debug_print('PRS505: finished rebuild_collections')
|
||||||
|
|
||||||
|
@ -61,8 +61,7 @@ class XMLCache(object):
|
|||||||
|
|
||||||
def __init__(self, paths, prefixes, use_author_sort):
|
def __init__(self, paths, prefixes, use_author_sort):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
debug_print('Building XMLCache...')
|
debug_print('Building XMLCache...', paths)
|
||||||
pprint(paths)
|
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
self.prefixes = prefixes
|
self.prefixes = prefixes
|
||||||
self.use_author_sort = use_author_sort
|
self.use_author_sort = use_author_sort
|
||||||
@ -347,6 +346,12 @@ class XMLCache(object):
|
|||||||
self.fix_ids()
|
self.fix_ids()
|
||||||
debug_print('Finished update XML from JSON')
|
debug_print('Finished update XML from JSON')
|
||||||
|
|
||||||
|
def rebuild_collections(self, booklist, bl_index):
|
||||||
|
if bl_index not in self.record_roots:
|
||||||
|
return
|
||||||
|
root = self.record_roots[bl_index]
|
||||||
|
self.update_playlists(bl_index, root, booklist, [])
|
||||||
|
|
||||||
def update_playlists(self, bl_index, root, booklist, collections_attributes):
|
def update_playlists(self, bl_index, root, booklist, collections_attributes):
|
||||||
debug_print('Starting update_playlists', collections_attributes)
|
debug_print('Starting update_playlists', collections_attributes)
|
||||||
collections = booklist.get_collections(collections_attributes)
|
collections = booklist.get_collections(collections_attributes)
|
||||||
|
@ -167,3 +167,10 @@ class CollectionsBookList(BookList):
|
|||||||
books.sort(cmp=lambda x,y:cmp(getter(x), getter(y)))
|
books.sort(cmp=lambda x,y:cmp(getter(x), getter(y)))
|
||||||
return collections
|
return collections
|
||||||
|
|
||||||
|
def rebuild_collections(self, booklist, oncard):
|
||||||
|
'''
|
||||||
|
For each book in the booklist for the card oncard, remove it from all
|
||||||
|
its current collections, then add it to the collections specified in
|
||||||
|
device_collections.
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
@ -21,6 +21,7 @@ from calibre.utils.filenames import ascii_filename
|
|||||||
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
||||||
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||||
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
||||||
|
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
||||||
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \
|
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \
|
||||||
fetch_scheduled_recipe, generate_catalog
|
fetch_scheduled_recipe, generate_catalog
|
||||||
from calibre.constants import preferred_encoding, filesystem_encoding, \
|
from calibre.constants import preferred_encoding, filesystem_encoding, \
|
||||||
@ -831,6 +832,21 @@ class EditMetadataAction(object): # {{{
|
|||||||
db.set_metadata(dest_id, dest_mi, ignore_errors=False)
|
db.set_metadata(dest_id, dest_mi, ignore_errors=False)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
def edit_device_collections(self, view):
|
||||||
|
model = view.model()
|
||||||
|
result = model.get_collections_with_ids()
|
||||||
|
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
||||||
|
d = TagListEditor(self, tag_to_match=None, data=result, compare=compare)
|
||||||
|
d.exec_()
|
||||||
|
if d.result() == d.Accepted:
|
||||||
|
to_rename = d.to_rename # dict of new text to old id
|
||||||
|
to_delete = d.to_delete # list of ids
|
||||||
|
for text in to_rename:
|
||||||
|
model.rename_collection(old_id=to_rename[text], new_name=unicode(text))
|
||||||
|
for item in to_delete:
|
||||||
|
model.delete_collection_using_id(item)
|
||||||
|
self.upload_collections(model.db, view=view)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class SaveToDiskAction(object): # {{{
|
class SaveToDiskAction(object): # {{{
|
||||||
|
@ -294,6 +294,11 @@ class DeviceManager(Thread): # {{{
|
|||||||
return self.create_job(self._sync_booklists, done, args=[booklists],
|
return self.create_job(self._sync_booklists, done, args=[booklists],
|
||||||
description=_('Send metadata to device'))
|
description=_('Send metadata to device'))
|
||||||
|
|
||||||
|
def upload_collections(self, done, booklist, on_card):
|
||||||
|
return self.create_job(booklist.rebuild_collections, done,
|
||||||
|
args=[booklist, on_card],
|
||||||
|
description=_('Send collections to device'))
|
||||||
|
|
||||||
def _upload_books(self, files, names, on_card=None, metadata=None):
|
def _upload_books(self, files, names, on_card=None, metadata=None):
|
||||||
'''Upload books to device: '''
|
'''Upload books to device: '''
|
||||||
return self.device.upload_books(files, names, on_card,
|
return self.device.upload_books(files, names, on_card,
|
||||||
@ -1234,6 +1239,16 @@ class DeviceMixin(object): # {{{
|
|||||||
self.card_a_view.reset()
|
self.card_a_view.reset()
|
||||||
self.card_b_view.reset()
|
self.card_b_view.reset()
|
||||||
|
|
||||||
|
def _upload_collections(self, job, view):
|
||||||
|
view.reset()
|
||||||
|
|
||||||
|
def upload_collections(self, booklist, view):
|
||||||
|
on_card = 'carda' if self.stack.currentIndex() == 2 else \
|
||||||
|
'cardb' if self.stack.currentIndex() == 3 else \
|
||||||
|
None
|
||||||
|
done = partial(self._upload_collections, view=view)
|
||||||
|
return self.device_manager.upload_collections(done, booklist, on_card)
|
||||||
|
|
||||||
def upload_books(self, files, names, metadata, on_card=None, memory=None):
|
def upload_books(self, files, names, metadata, on_card=None, memory=None):
|
||||||
'''
|
'''
|
||||||
Upload books to device.
|
Upload books to device.
|
||||||
|
@ -1,54 +1,34 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
from PyQt4.QtCore import SIGNAL, Qt
|
from PyQt4.QtCore import SIGNAL, Qt
|
||||||
from PyQt4.QtGui import QDialog, QListWidgetItem
|
from PyQt4.QtGui import QDialog, QListWidgetItem
|
||||||
|
|
||||||
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
||||||
from calibre.gui2 import question_dialog, error_dialog
|
from calibre.gui2 import question_dialog, error_dialog
|
||||||
from calibre.ebooks.metadata import title_sort
|
|
||||||
|
|
||||||
class TagListEditor(QDialog, Ui_TagListEditor):
|
class TagListEditor(QDialog, Ui_TagListEditor):
|
||||||
|
|
||||||
def __init__(self, window, db, tag_to_match, category, data, compare):
|
def __init__(self, window, tag_to_match, data, compare):
|
||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_TagListEditor.__init__(self)
|
Ui_TagListEditor.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.to_rename = {}
|
self.to_rename = {}
|
||||||
self.to_delete = []
|
self.to_delete = []
|
||||||
self.db = db
|
|
||||||
self.all_tags = {}
|
self.all_tags = {}
|
||||||
self.category = category
|
|
||||||
if category == 'tags':
|
|
||||||
result = db.get_tags_with_ids()
|
|
||||||
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
|
||||||
elif category == 'series':
|
|
||||||
result = db.get_series_with_ids()
|
|
||||||
compare = (lambda x,y:cmp(title_sort(x).lower(), title_sort(y).lower()))
|
|
||||||
elif category == 'publisher':
|
|
||||||
result = db.get_publishers_with_ids()
|
|
||||||
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
|
||||||
else: # should be a custom field
|
|
||||||
self.cc_label = None
|
|
||||||
if category in db.field_metadata:
|
|
||||||
self.cc_label = db.field_metadata[category]['label']
|
|
||||||
result = self.db.get_custom_items_with_ids(label=self.cc_label)
|
|
||||||
else:
|
|
||||||
result = []
|
|
||||||
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
|
||||||
|
|
||||||
for k,v in result:
|
for k,v in data:
|
||||||
self.all_tags[v] = k
|
self.all_tags[v] = k
|
||||||
for tag in sorted(self.all_tags.keys(), cmp=compare):
|
for tag in sorted(self.all_tags.keys(), cmp=compare):
|
||||||
item = QListWidgetItem(tag)
|
item = QListWidgetItem(tag)
|
||||||
item.setData(Qt.UserRole, self.all_tags[tag])
|
item.setData(Qt.UserRole, self.all_tags[tag])
|
||||||
self.available_tags.addItem(item)
|
self.available_tags.addItem(item)
|
||||||
|
|
||||||
items = self.available_tags.findItems(tag_to_match, Qt.MatchExactly)
|
if tag_to_match is not None:
|
||||||
if len(items) == 1:
|
items = self.available_tags.findItems(tag_to_match, Qt.MatchExactly)
|
||||||
self.available_tags.setCurrentItem(items[0])
|
if len(items) == 1:
|
||||||
|
self.available_tags.setCurrentItem(items[0])
|
||||||
|
|
||||||
self.connect(self.delete_button, SIGNAL('clicked()'), self.delete_tags)
|
self.connect(self.delete_button, SIGNAL('clicked()'), self.delete_tags)
|
||||||
self.connect(self.rename_button, SIGNAL('clicked()'), self.rename_tag)
|
self.connect(self.rename_button, SIGNAL('clicked()'), self.rename_tag)
|
||||||
@ -62,11 +42,6 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
|||||||
item.setText(self.item_before_editing.text())
|
item.setText(self.item_before_editing.text())
|
||||||
return
|
return
|
||||||
if item.text() != self.item_before_editing.text():
|
if item.text() != self.item_before_editing.text():
|
||||||
if item.text() in self.all_tags.keys() or item.text() in self.to_rename.keys():
|
|
||||||
error_dialog(self, _('Item already used'),
|
|
||||||
_('The item %s is already used.')%(item.text())).exec_()
|
|
||||||
item.setText(self.item_before_editing.text())
|
|
||||||
return
|
|
||||||
(id,ign) = self.item_before_editing.data(Qt.UserRole).toInt()
|
(id,ign) = self.item_before_editing.data(Qt.UserRole).toInt()
|
||||||
self.to_rename[item.text()] = id
|
self.to_rename[item.text()] = id
|
||||||
|
|
||||||
@ -100,29 +75,4 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
|||||||
self.available_tags.takeItem(self.available_tags.row(item))
|
self.available_tags.takeItem(self.available_tags.row(item))
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
rename_func = None
|
QDialog.accept(self)
|
||||||
if self.category == 'tags':
|
|
||||||
rename_func = self.db.rename_tag
|
|
||||||
delete_func = self.db.delete_tag_using_id
|
|
||||||
elif self.category == 'series':
|
|
||||||
rename_func = self.db.rename_series
|
|
||||||
delete_func = self.db.delete_series_using_id
|
|
||||||
elif self.category == 'publisher':
|
|
||||||
rename_func = self.db.rename_publisher
|
|
||||||
delete_func = self.db.delete_publisher_using_id
|
|
||||||
else:
|
|
||||||
rename_func = partial(self.db.rename_custom_item, label=self.cc_label)
|
|
||||||
delete_func = partial(self.db.delete_custom_item_using_id, label=self.cc_label)
|
|
||||||
|
|
||||||
work_done = False
|
|
||||||
if rename_func:
|
|
||||||
for text in self.to_rename:
|
|
||||||
work_done = True
|
|
||||||
rename_func(id=self.to_rename[text], new_name=unicode(text))
|
|
||||||
for item in self.to_delete:
|
|
||||||
work_done = True
|
|
||||||
delete_func(item)
|
|
||||||
if not work_done:
|
|
||||||
QDialog.reject(self)
|
|
||||||
else:
|
|
||||||
QDialog.accept(self)
|
|
||||||
|
@ -226,17 +226,22 @@ class LibraryViewMixin(object): # {{{
|
|||||||
self.action_show_book_details,
|
self.action_show_book_details,
|
||||||
self.action_del,
|
self.action_del,
|
||||||
add_to_library = None,
|
add_to_library = None,
|
||||||
|
edit_device_collections=None,
|
||||||
similar_menu=similar_menu)
|
similar_menu=similar_menu)
|
||||||
add_to_library = (_('Add books to library'), self.add_books_from_device)
|
add_to_library = (_('Add books to library'), self.add_books_from_device)
|
||||||
|
edit_device_collections = (_('Manage collections'), self.edit_device_collections)
|
||||||
self.memory_view.set_context_menu(None, None, None,
|
self.memory_view.set_context_menu(None, None, None,
|
||||||
self.action_view, self.action_save, None, None, self.action_del,
|
self.action_view, self.action_save, None, None, self.action_del,
|
||||||
add_to_library=add_to_library)
|
add_to_library=add_to_library,
|
||||||
|
edit_device_collections=edit_device_collections)
|
||||||
self.card_a_view.set_context_menu(None, None, None,
|
self.card_a_view.set_context_menu(None, None, None,
|
||||||
self.action_view, self.action_save, None, None, self.action_del,
|
self.action_view, self.action_save, None, None, self.action_del,
|
||||||
add_to_library=add_to_library)
|
add_to_library=add_to_library,
|
||||||
|
edit_device_collections=edit_device_collections)
|
||||||
self.card_b_view.set_context_menu(None, None, None,
|
self.card_b_view.set_context_menu(None, None, None,
|
||||||
self.action_view, self.action_save, None, None, self.action_del,
|
self.action_view, self.action_save, None, None, self.action_del,
|
||||||
add_to_library=add_to_library)
|
add_to_library=add_to_library,
|
||||||
|
edit_device_collections=edit_device_collections)
|
||||||
|
|
||||||
self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
|
self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
|
||||||
for func, args in [
|
for func, args in [
|
||||||
@ -249,8 +254,11 @@ class LibraryViewMixin(object): # {{{
|
|||||||
getattr(view, func)(*args)
|
getattr(view, func)(*args)
|
||||||
|
|
||||||
self.memory_view.connect_dirtied_signal(self.upload_booklists)
|
self.memory_view.connect_dirtied_signal(self.upload_booklists)
|
||||||
|
self.memory_view.connect_upload_collections_signal(self.upload_collections)
|
||||||
self.card_a_view.connect_dirtied_signal(self.upload_booklists)
|
self.card_a_view.connect_dirtied_signal(self.upload_booklists)
|
||||||
|
self.card_a_view.connect_upload_collections_signal(self.upload_collections)
|
||||||
self.card_b_view.connect_dirtied_signal(self.upload_booklists)
|
self.card_b_view.connect_dirtied_signal(self.upload_booklists)
|
||||||
|
self.card_b_view.connect_upload_collections_signal(self.upload_collections)
|
||||||
|
|
||||||
self.book_on_device(None, reset=True)
|
self.book_on_device(None, reset=True)
|
||||||
db.set_book_on_device_func(self.book_on_device)
|
db.set_book_on_device_func(self.book_on_device)
|
||||||
|
@ -857,6 +857,7 @@ class OnDeviceSearch(SearchQueryParser): # {{{
|
|||||||
class DeviceBooksModel(BooksModel): # {{{
|
class DeviceBooksModel(BooksModel): # {{{
|
||||||
|
|
||||||
booklist_dirtied = pyqtSignal()
|
booklist_dirtied = pyqtSignal()
|
||||||
|
upload_collections = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
BooksModel.__init__(self, parent)
|
BooksModel.__init__(self, parent)
|
||||||
@ -977,8 +978,8 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
x, y = int(self.db[x].size), int(self.db[y].size)
|
x, y = int(self.db[x].size), int(self.db[y].size)
|
||||||
return cmp(x, y)
|
return cmp(x, y)
|
||||||
def tagscmp(x, y):
|
def tagscmp(x, y):
|
||||||
x = ','.join(self.db[x].device_collections)
|
x = ','.join(getattr(self.db[x], 'device_collections', [])).lower()
|
||||||
y = ','.join(self.db[y].device_collections)
|
y = ','.join(getattr(self.db[y], 'device_collections', [])).lower()
|
||||||
return cmp(x, y)
|
return cmp(x, y)
|
||||||
def libcmp(x, y):
|
def libcmp(x, y):
|
||||||
x, y = self.db[x].in_library, self.db[y].in_library
|
x, y = self.db[x].in_library, self.db[y].in_library
|
||||||
@ -1026,6 +1027,9 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self.custom_columns = {}
|
self.custom_columns = {}
|
||||||
self.db = db
|
self.db = db
|
||||||
|
for book in db:
|
||||||
|
if book.device_collections is not None:
|
||||||
|
book.device_collections.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
||||||
self.map = list(range(0, len(db)))
|
self.map = list(range(0, len(db)))
|
||||||
|
|
||||||
def current_changed(self, current, previous):
|
def current_changed(self, current, previous):
|
||||||
@ -1079,6 +1083,36 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
res.append((r,b))
|
res.append((r,b))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def get_collections_with_ids(self):
|
||||||
|
collections = set()
|
||||||
|
for book in self.db:
|
||||||
|
if book.device_collections is not None:
|
||||||
|
collections.update(set(book.device_collections))
|
||||||
|
self.collections = []
|
||||||
|
result = []
|
||||||
|
for i,collection in enumerate(collections):
|
||||||
|
result.append((i, collection))
|
||||||
|
self.collections.append(collection)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def rename_collection(self, old_id, new_name):
|
||||||
|
old_name = self.collections[old_id]
|
||||||
|
for book in self.db:
|
||||||
|
if book.device_collections is None:
|
||||||
|
continue
|
||||||
|
if old_name in book.device_collections:
|
||||||
|
book.device_collections.remove(old_name)
|
||||||
|
if new_name not in book.device_collections:
|
||||||
|
book.device_collections.append(new_name)
|
||||||
|
|
||||||
|
def delete_collection_using_id(self, old_id):
|
||||||
|
old_name = self.collections[old_id]
|
||||||
|
for book in self.db:
|
||||||
|
if book.device_collections is None:
|
||||||
|
continue
|
||||||
|
if old_name in book.device_collections:
|
||||||
|
book.device_collections.remove(old_name)
|
||||||
|
|
||||||
def indices(self, rows):
|
def indices(self, rows):
|
||||||
'''
|
'''
|
||||||
Return indices into underlying database from rows
|
Return indices into underlying database from rows
|
||||||
@ -1109,7 +1143,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
elif cname == 'collections':
|
elif cname == 'collections':
|
||||||
tags = self.db[self.map[row]].device_collections
|
tags = self.db[self.map[row]].device_collections
|
||||||
if tags:
|
if tags:
|
||||||
return QVariant(', '.join(sorted(tags, key=str.lower)))
|
return QVariant(', '.join(tags))
|
||||||
elif role == Qt.ToolTipRole and index.isValid():
|
elif role == Qt.ToolTipRole and index.isValid():
|
||||||
if self.map[row] in self.indices_to_be_deleted():
|
if self.map[row] in self.indices_to_be_deleted():
|
||||||
return QVariant(_('Marked for deletion'))
|
return QVariant(_('Marked for deletion'))
|
||||||
@ -1151,14 +1185,17 @@ class DeviceBooksModel(BooksModel): # {{{
|
|||||||
return False
|
return False
|
||||||
val = unicode(value.toString()).strip()
|
val = unicode(value.toString()).strip()
|
||||||
idx = self.map[row]
|
idx = self.map[row]
|
||||||
|
if cname == 'collections':
|
||||||
|
tags = [i.strip() for i in val.split(',')]
|
||||||
|
tags = [t for t in tags if t]
|
||||||
|
self.db[idx].device_collections = tags
|
||||||
|
self.upload_collections.emit(self.db)
|
||||||
|
return True
|
||||||
|
|
||||||
if cname == 'title' :
|
if cname == 'title' :
|
||||||
self.db[idx].title = val
|
self.db[idx].title = val
|
||||||
elif cname == 'authors':
|
elif cname == 'authors':
|
||||||
self.db[idx].authors = string_to_authors(val)
|
self.db[idx].authors = string_to_authors(val)
|
||||||
elif cname == 'collections':
|
|
||||||
tags = [i.strip() for i in val.split(',')]
|
|
||||||
tags = [t for t in tags if t]
|
|
||||||
self.db[idx].device_collections = tags
|
|
||||||
self.dataChanged.emit(index, index)
|
self.dataChanged.emit(index, index)
|
||||||
self.booklist_dirtied.emit()
|
self.booklist_dirtied.emit()
|
||||||
done = True
|
done = True
|
||||||
|
@ -371,7 +371,8 @@ class BooksView(QTableView): # {{{
|
|||||||
# Context Menu {{{
|
# Context Menu {{{
|
||||||
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
|
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
|
||||||
save, open_folder, book_details, delete,
|
save, open_folder, book_details, delete,
|
||||||
similar_menu=None, add_to_library=None):
|
similar_menu=None, add_to_library=None,
|
||||||
|
edit_device_collections=None):
|
||||||
self.setContextMenuPolicy(Qt.DefaultContextMenu)
|
self.setContextMenuPolicy(Qt.DefaultContextMenu)
|
||||||
self.context_menu = QMenu(self)
|
self.context_menu = QMenu(self)
|
||||||
if edit_metadata is not None:
|
if edit_metadata is not None:
|
||||||
@ -393,6 +394,9 @@ class BooksView(QTableView): # {{{
|
|||||||
if add_to_library is not None:
|
if add_to_library is not None:
|
||||||
func = partial(add_to_library[1], view=self)
|
func = partial(add_to_library[1], view=self)
|
||||||
self.context_menu.addAction(add_to_library[0], func)
|
self.context_menu.addAction(add_to_library[0], func)
|
||||||
|
if edit_device_collections is not None:
|
||||||
|
func = partial(edit_device_collections[1], view=self)
|
||||||
|
self.context_menu.addAction(edit_device_collections[0], func)
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
self.context_menu.popup(event.globalPos())
|
self.context_menu.popup(event.globalPos())
|
||||||
@ -505,6 +509,9 @@ class DeviceBooksView(BooksView): # {{{
|
|||||||
def connect_dirtied_signal(self, slot):
|
def connect_dirtied_signal(self, slot):
|
||||||
self._model.booklist_dirtied.connect(slot)
|
self._model.booklist_dirtied.connect(slot)
|
||||||
|
|
||||||
|
def connect_upload_collections_signal(self, func):
|
||||||
|
self._model.upload_collections.connect(partial(func, view=self))
|
||||||
|
|
||||||
def dropEvent(self, *args):
|
def dropEvent(self, *args):
|
||||||
error_dialog(self, _('Not allowed'),
|
error_dialog(self, _('Not allowed'),
|
||||||
_('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_()
|
_('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_()
|
||||||
|
@ -692,18 +692,38 @@ class TagBrowserMixin(object): # {{{
|
|||||||
result = db.get_publishers_with_ids()
|
result = db.get_publishers_with_ids()
|
||||||
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
||||||
else: # should be a custom field
|
else: # should be a custom field
|
||||||
self.cc_label = None
|
cc_label = None
|
||||||
if category in db.field_metadata:
|
if category in db.field_metadata:
|
||||||
self.cc_label = db.field_metadata[category]['label']
|
cc_label = db.field_metadata[category]['label']
|
||||||
result = self.db.get_custom_items_with_ids(label=self.cc_label)
|
result = self.db.get_custom_items_with_ids(label=cc_label)
|
||||||
else:
|
else:
|
||||||
result = []
|
result = []
|
||||||
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
compare = (lambda x,y:cmp(x.lower(), y.lower()))
|
||||||
|
|
||||||
d = TagListEditor(self, db=db, tag_to_match=tag, category=category,
|
d = TagListEditor(self, tag_to_match=tag, data=result, compare=compare)
|
||||||
data=result, compare=compare)
|
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
|
to_rename = d.to_rename # dict of new text to old id
|
||||||
|
to_delete = d.to_delete # list of ids
|
||||||
|
rename_func = None
|
||||||
|
if category == 'tags':
|
||||||
|
rename_func = db.rename_tag
|
||||||
|
delete_func = db.delete_tag_using_id
|
||||||
|
elif category == 'series':
|
||||||
|
rename_func = db.rename_series
|
||||||
|
delete_func = db.delete_series_using_id
|
||||||
|
elif category == 'publisher':
|
||||||
|
rename_func = db.rename_publisher
|
||||||
|
delete_func = db.delete_publisher_using_id
|
||||||
|
else:
|
||||||
|
rename_func = partial(db.rename_custom_item, label=cc_label)
|
||||||
|
delete_func = partial(db.delete_custom_item_using_id, label=cc_label)
|
||||||
|
if rename_func:
|
||||||
|
for text in to_rename:
|
||||||
|
rename_func(old_id=to_rename[text], new_name=unicode(text))
|
||||||
|
for item in to_delete:
|
||||||
|
delete_func(item)
|
||||||
|
|
||||||
# Clean up everything, as information could have changed for many books.
|
# Clean up everything, as information could have changed for many books.
|
||||||
self.library_view.model().refresh()
|
self.library_view.model().refresh()
|
||||||
self.tags_view.set_new_model()
|
self.tags_view.set_new_model()
|
||||||
|
@ -104,14 +104,20 @@ will appear in the next release of |app|.
|
|||||||
How does |app| manage collections on my SONY reader?
|
How does |app| manage collections on my SONY reader?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
When you send a book to the device, |app| will create collections based on the metadata for that book. By default, collections are created
|
When |app| connects with the device, it retrieves all collections for the books on the device. The collections
|
||||||
from tags and series. You can control what metadata is used by going to Preferences->Plugins->Device Interface plugins and customizing
|
of which books are members are shown on the device view.
|
||||||
|
|
||||||
|
When you send a book to the device, |app| will if necessary create new collections based on the metadata for
|
||||||
|
that book, then add the book to the collections. By default, collections are created from tags and series. You
|
||||||
|
can control what metadata is used by going to Preferences->Plugins->Device Interface plugins and customizing
|
||||||
the SONY device interface plugin.
|
the SONY device interface plugin.
|
||||||
|
|
||||||
You can edit collections on the device in the device view in |app| by double clicking or right clicking on the collections field.
|
|app| will not delete already existing collections for a book on your device when you resend the book to the
|
||||||
|
device. To ensure that the collections are based only on current |app| metadata, first delete the books from
|
||||||
|
the device, and then resend the books.
|
||||||
|
|
||||||
|app| will not delete already existing collections on your device. To ensure that the collections are based only on current |app| metadata,
|
You can edit collections on the device in the device view in |app| by double clicking or right clicking on the
|
||||||
delete and resend the books to the device.
|
collections field. This is the only way to remove a book from a collection.
|
||||||
|
|
||||||
Can I use both |app| and the SONY software to manage my reader?
|
Can I use both |app| and the SONY software to manage my reader?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Loading…
x
Reference in New Issue
Block a user