Redo delete from device with a dialog

This commit is contained in:
Charles Haley 2010-06-16 20:56:49 +01:00
parent 388bc7e7f8
commit e4660d788c
5 changed files with 208 additions and 19 deletions

View File

@ -28,6 +28,7 @@ from calibre.constants import preferred_encoding, filesystem_encoding, \
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.delete_matching_from_device import DeleteMatchingFromDeviceDialog
class AnnotationsAction(object): # {{{
@ -471,21 +472,45 @@ class DeleteAction(object): # {{{
if ids:
self.tags_view.recount()
def mark_matching_for_removal(self, *args):
def remove_matching_books_from_device(self, *args):
if not self.device_manager.is_device_connected:
d = error_dialog(self, _('Cannot delete books'),
_('No device is connected'))
d.exec_()
return
ids = self._get_selected_ids()
if not ids:
#For some reason the delete dialog reports no selection, so
#we don't need to do it here
return
db = self.library_view.model().db
for model in (self.memory_view.model(), self.card_a_view.model(),
self.card_b_view.model()):
ids_to_mark = []
for id in ids:
uuid = db.uuid(id, index_is_id=True)
for book in model.db:
if getattr(book, 'uuid', None) == uuid:
ids_to_mark.append(id)
break
model.clear_ondevice(ids_to_mark, to_what=False)
to_delete = {}
some_to_delete = False
for model,name in ((self.memory_view.model(), _('Main memory')),
(self.card_a_view.model(), _('Storage card A')),
(self.card_b_view.model(), _('Storage card A'))):
to_delete[name] = (model, model.paths_for_db_ids(ids))
if len(to_delete[name][1]) > 0:
some_to_delete = True
if not some_to_delete:
d = error_dialog(self, _('No books to delete'),
_('None of the selected books are on the device'))
d.exec_()
return
d = DeleteMatchingFromDeviceDialog(self, to_delete)
if d.exec_():
paths = {}
ids = {}
for (model, id, path) in d.result:
if model not in paths:
paths[model] = []
ids[model] = []
paths[model].append(path)
ids[model].append(id)
for model in paths:
job = self.remove_paths(paths[model])
self.delete_memory[job] = (paths[model], model)
model.mark_for_deletion(job, ids[model], rows_are_ids=True)
self.status_bar.show_message(_('Deleting books from device.'), 1000)
def delete_covers(self, *args):
ids = self._get_selected_ids()

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__license__ = 'GPL v3'
from PyQt4.Qt import Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon
from calibre.ebooks.metadata import authors_to_string
from calibre.gui2.dialogs.delete_matching_from_device_ui import Ui_DeleteMatchingFromDeviceDialog
class tableItem(QTableWidgetItem):
def __init__(self, text):
QTableWidgetItem.__init__(self, text)
self.setFlags(Qt.ItemIsEnabled)
def __ge__(self, other):
return unicode(self.text()).lower() >= unicode(other.text()).lower()
def __lt__(self, other):
return unicode(self.text()).lower() < unicode(other.text()).lower()
class DeleteMatchingFromDeviceDialog(QDialog, Ui_DeleteMatchingFromDeviceDialog):
def __init__(self, parent, items):
QDialog.__init__(self, parent)
Ui_DeleteMatchingFromDeviceDialog.__init__(self)
self.setupUi(self)
self.buttonBox.accepted.connect(self.accepted)
self.table.cellClicked.connect(self.cell_clicked)
self.table.setSelectionMode(QAbstractItemView.NoSelection)
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels(['', _('Location'), _('Title'),
_('Author'), _('Format')])
del_icon = QIcon(I('list_remove.svg'))
rows = 0
for card in items:
rows += len(items[card][1])
self.table.setRowCount(rows)
row = 0
for card in items:
(model,books) = items[card]
for (id,book) in books:
item = QTableWidgetItem(del_icon, '')
item.setData(Qt.UserRole, (model, id, book.path))
self.table.setItem(row, 0, item)
self.table.setItem(row, 1, tableItem(card))
self.table.setItem(row, 2, tableItem(book.title))
self.table.setItem(row, 3, tableItem(authors_to_string(book.authors)))
self.table.setItem(row, 4, tableItem(book.path.rpartition('.')[2]))
row += 1
self.table.resizeColumnsToContents()
self.table.setSortingEnabled(True)
self.table.sortByColumn(2, Qt.AscendingOrder)
def accepted(self):
self.result = []
for row in range(self.table.rowCount()):
(model, id, path) = self.table.item(row, 0).data(Qt.UserRole).toPyObject()
path = unicode(path)
self.result.append((model, id, path))
return
def cell_clicked(self, row, col):
if col == 0:
self.table.removeRow(row)

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DeleteMatchingFromDeviceDialog</class>
<widget class="QDialog" name="DeleteMatchingFromDeviceDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>730</width>
<height>342</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Delete from device</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="table">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="columnCount">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DeleteMatchingFromDeviceDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>229</x>
<y>211</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>234</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DeleteMatchingFromDeviceDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>297</x>
<y>217</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>234</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -132,8 +132,8 @@ class ToolbarMixin(object): # {{{
self.delete_menu.addAction(
_('Remove covers from selected books'), self.delete_covers)
self.delete_menu.addAction(
_('Mark matching books on device for removal'),
self.mark_matching_for_removal)
_('Remove matching books from device'),
self.remove_matching_books_from_device)
self.action_del.setMenu(self.delete_menu)
self.action_open_containing_folder.setShortcut(Qt.Key_O)

View File

@ -872,11 +872,15 @@ class DeviceBooksModel(BooksModel): # {{{
self.editable = True
self.book_in_library = None
def mark_for_deletion(self, job, rows):
self.marked_for_deletion[job] = self.indices(rows)
for row in rows:
indices = self.row_indices(row)
self.dataChanged.emit(indices[0], indices[-1])
def mark_for_deletion(self, job, rows, rows_are_ids=False):
if rows_are_ids:
self.marked_for_deletion[job] = rows
self.reset()
else:
self.marked_for_deletion[job] = self.indices(rows)
for row in rows:
indices = self.row_indices(row)
self.dataChanged.emit(indices[0], indices[-1])
def deletion_done(self, job, succeeded=True):
if not self.marked_for_deletion.has_key(job):
@ -1059,6 +1063,13 @@ class DeviceBooksModel(BooksModel): # {{{
def paths(self, rows):
return [self.db[self.map[r.row()]].path for r in rows ]
def paths_for_db_ids(self, db_ids):
res = []
for r,b in enumerate(self.db):
if b.application_id in db_ids:
res.append((r,b))
return res
def indices(self, rows):
'''
Return indices into underlying database from rows