mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement #3509 (Ability to delete books by file format)
This commit is contained in:
parent
12871ae8ce
commit
4d7697817f
@ -385,6 +385,7 @@ def initialize_file_icon_provider():
|
||||
|
||||
def file_icon_provider():
|
||||
global _file_icon_provider
|
||||
initialize_file_icon_provider()
|
||||
return _file_icon_provider
|
||||
|
||||
class FileDialog(QObject):
|
||||
|
77
src/calibre/gui2/dialogs/select_formats.py
Normal file
77
src/calibre/gui2/dialogs/select_formats.py
Normal file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from PyQt4.Qt import QVBoxLayout, QDialog, QLabel, QDialogButtonBox, Qt, \
|
||||
QAbstractListModel, QVariant, QListView, QSize
|
||||
|
||||
from calibre.gui2 import NONE, file_icon_provider
|
||||
|
||||
class Formats(QAbstractListModel):
|
||||
|
||||
def __init__(self, fmts):
|
||||
QAbstractListModel.__init__(self)
|
||||
self.fmts = sorted(fmts)
|
||||
self.fi = file_icon_provider()
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self.fmts)
|
||||
|
||||
def data(self, index, role):
|
||||
row = index.row()
|
||||
if role == Qt.DisplayRole:
|
||||
return QVariant(self.fmts[row].upper())
|
||||
if role == Qt.DecorationRole:
|
||||
return QVariant(self.fi.icon_from_ext(self.fmts[row].lower()))
|
||||
return NONE
|
||||
|
||||
def flags(self, index):
|
||||
return Qt.ItemIsSelectable|Qt.ItemIsEnabled
|
||||
|
||||
def fmt(self, idx):
|
||||
return self.fmts[idx.row()]
|
||||
|
||||
class SelectFormats(QDialog):
|
||||
|
||||
def __init__(self, fmt_list, msg, single=False, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self._l = QVBoxLayout(self)
|
||||
self.setLayout(self._l)
|
||||
self.setWindowTitle(_('Choose formats'))
|
||||
self._m = QLabel(msg)
|
||||
self._m.setWordWrap = True
|
||||
self._l.addWidget(self._m)
|
||||
self.formats = Formats(fmt_list)
|
||||
self.fview = QListView(self)
|
||||
self._l.addWidget(self.fview)
|
||||
self.fview.setModel(self.formats)
|
||||
self.fview.setSelectionMode(self.fview.SingleSelection if single else
|
||||
self.fview.MultiSelection)
|
||||
self.bbox = \
|
||||
QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
|
||||
Qt.Horizontal, self)
|
||||
self._l.addWidget(self.bbox)
|
||||
self.bbox.accepted.connect(self.accept)
|
||||
self.bbox.rejected.connect(self.reject)
|
||||
self.fview.setIconSize(QSize(48, 48))
|
||||
self.fview.setSpacing(2)
|
||||
|
||||
self.resize(350, 500)
|
||||
self.selected_formats = set([])
|
||||
|
||||
def accept(self, *args):
|
||||
for idx in self.fview.selectedIndexes():
|
||||
self.selected_formats.add(self.formats.fmt(idx))
|
||||
QDialog.accept(self, *args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication
|
||||
app = QApplication([])
|
||||
d = SelectFormats(['epub', 'lrf', 'lit', 'mobi'], 'Choose a format')
|
||||
d.exec_()
|
||||
print d.selected_formats
|
@ -314,6 +314,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.view_menu.addAction(_('View'))
|
||||
self.view_menu.addAction(_('View specific format'))
|
||||
self.action_view.setMenu(self.view_menu)
|
||||
|
||||
self.delete_menu = QMenu()
|
||||
self.delete_menu.addAction(_('Remove selected books'))
|
||||
self.delete_menu.addAction(
|
||||
_('Remove files of a specific format from selected books..'))
|
||||
self.delete_menu.addAction(
|
||||
_('Remove all formats from selected books, except...'))
|
||||
self.delete_menu.addAction(
|
||||
_('Remove covers from selected books'))
|
||||
self.action_del.setMenu(self.delete_menu)
|
||||
QObject.connect(self.action_save, SIGNAL("triggered(bool)"),
|
||||
self.save_to_disk)
|
||||
QObject.connect(self.save_menu.actions()[0], SIGNAL("triggered(bool)"),
|
||||
@ -330,6 +340,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
SIGNAL("triggered(bool)"), self.view_specific_format)
|
||||
self.connect(self.action_open_containing_folder,
|
||||
SIGNAL('triggered(bool)'), self.view_folder)
|
||||
self.delete_menu.actions()[0].triggered.connect(self.delete_books)
|
||||
self.delete_menu.actions()[1].triggered.connect(self.delete_selected_formats)
|
||||
self.delete_menu.actions()[2].triggered.connect(self.delete_all_but_selected_formats)
|
||||
self.delete_menu.actions()[3].triggered.connect(self.delete_covers)
|
||||
|
||||
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
||||
self.addAction(self.action_open_containing_folder)
|
||||
self.action_sync.setShortcut(Qt.Key_D)
|
||||
@ -376,6 +391,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
setPopupMode(QToolButton.MenuButtonPopup)
|
||||
self.tool_bar.widgetForAction(self.action_view).\
|
||||
setPopupMode(QToolButton.MenuButtonPopup)
|
||||
self.tool_bar.widgetForAction(self.action_del).\
|
||||
setPopupMode(QToolButton.MenuButtonPopup)
|
||||
self.tool_bar.widgetForAction(self.action_preferences).\
|
||||
setPopupMode(QToolButton.MenuButtonPopup)
|
||||
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
@ -987,7 +1004,72 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
############################################################################
|
||||
|
||||
############################### Delete books ###############################
|
||||
def delete_books(self, checked):
|
||||
|
||||
def _get_selected_formats(self, msg):
|
||||
from calibre.gui2.dialogs.select_formats import SelectFormats
|
||||
fmts = self.library_view.model().db.all_formats()
|
||||
d = SelectFormats([x.lower() for x in fmts], msg, parent=self)
|
||||
if d.exec_() != d.Accepted:
|
||||
return None
|
||||
return d.selected_formats
|
||||
|
||||
def _get_selected_ids(self, err_title=_('Cannot delete')):
|
||||
rows = self.library_view.selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
d = error_dialog(self, err_title, _('No book selected'))
|
||||
d.exec_()
|
||||
return set([])
|
||||
return set(map(self.library_view.model().id, rows))
|
||||
|
||||
def delete_selected_formats(self, *args):
|
||||
ids = self._get_selected_ids()
|
||||
if not ids:
|
||||
return
|
||||
fmts = self._get_selected_formats(
|
||||
_('Choose formats to be deleted'))
|
||||
if not fmts:
|
||||
return
|
||||
for id in ids:
|
||||
for fmt in fmts:
|
||||
self.library_view.model().db.remove_format(id, fmt,
|
||||
index_is_id=True, notify=False)
|
||||
self.library_view.model().refresh_ids(ids)
|
||||
self.library_view.model().current_changed(self.library_view.currentIndex(),
|
||||
self.library_view.currentIndex())
|
||||
|
||||
def delete_all_but_selected_formats(self, *args):
|
||||
ids = self._get_selected_ids()
|
||||
if not ids:
|
||||
return
|
||||
fmts = self._get_selected_formats(
|
||||
'<p>'+_('Choose formats <b>not</b> to be deleted'))
|
||||
if fmts is None:
|
||||
return
|
||||
for id in ids:
|
||||
bfmts = self.library_view.model().db.formats(id, index_is_id=True)
|
||||
if bfmts is None:
|
||||
continue
|
||||
bfmts = set([x.lower() for x in bfmts.split(',')])
|
||||
rfmts = bfmts - set(fmts)
|
||||
for fmt in rfmts:
|
||||
self.library_view.model().db.remove_format(id, fmt,
|
||||
index_is_id=True, notify=False)
|
||||
self.library_view.model().refresh_ids(ids)
|
||||
self.library_view.model().current_changed(self.library_view.currentIndex(),
|
||||
self.library_view.currentIndex())
|
||||
|
||||
|
||||
def delete_covers(self, *args):
|
||||
ids = self._get_selected_ids()
|
||||
if not ids:
|
||||
return
|
||||
for id in ids:
|
||||
self.library_view.model().db.remove_cover(id)
|
||||
self.library_view.model().refresh_ids(ids)
|
||||
self.library_view.model().current_changed(self.library_view.currentIndex(),
|
||||
self.library_view.currentIndex())
|
||||
|
||||
def delete_books(self, *args):
|
||||
'''
|
||||
Delete selected books from device or library.
|
||||
'''
|
||||
@ -1599,6 +1681,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.action_sync.setEnabled(True)
|
||||
self.status_bar.tag_view_button.setEnabled(True)
|
||||
self.status_bar.cover_flow_button.setEnabled(True)
|
||||
for action in list(self.delete_menu.actions())[1:]:
|
||||
action.setEnabled(True)
|
||||
else:
|
||||
self.action_edit.setEnabled(False)
|
||||
self.action_convert.setEnabled(False)
|
||||
@ -1607,6 +1691,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.action_sync.setEnabled(False)
|
||||
self.status_bar.tag_view_button.setEnabled(False)
|
||||
self.status_bar.cover_flow_button.setEnabled(False)
|
||||
for action in list(self.delete_menu.actions())[1:]:
|
||||
action.setEnabled(False)
|
||||
|
||||
|
||||
def device_job_exception(self, job):
|
||||
|
@ -865,6 +865,11 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
|
||||
return os.access(path, os.R_OK)
|
||||
|
||||
def remove_cover(self, id):
|
||||
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
def set_cover(self, id, data):
|
||||
'''
|
||||
Set the cover for this book.
|
||||
|
Loading…
x
Reference in New Issue
Block a user