Implement #3509 (Ability to delete books by file format)

This commit is contained in:
Kovid Goyal 2009-12-24 21:37:45 -07:00
parent 12871ae8ce
commit 4d7697817f
4 changed files with 170 additions and 1 deletions

View File

@ -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):

View 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

View File

@ -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):

View File

@ -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.