Bulk renaming of selected files

This commit is contained in:
Kovid Goyal 2013-12-11 12:43:07 +05:30
parent 71aeed7cb5
commit 95ed2862a6
2 changed files with 62 additions and 10 deletions

View File

@ -88,6 +88,7 @@ class Boss(QObject):
fl.delete_requested.connect(self.delete_requested) fl.delete_requested.connect(self.delete_requested)
fl.reorder_spine.connect(self.reorder_spine) fl.reorder_spine.connect(self.reorder_spine)
fl.rename_requested.connect(self.rename_requested) fl.rename_requested.connect(self.rename_requested)
fl.bulk_rename_requested.connect(self.bulk_rename_requested)
fl.edit_file.connect(self.edit_file_requested) fl.edit_file.connect(self.edit_file_requested)
fl.merge_requested.connect(self.merge_requested) fl.merge_requested.connect(self.merge_requested)
fl.mark_requested.connect(self.mark_requested) fl.mark_requested.connect(self.mark_requested)
@ -338,21 +339,29 @@ class Boss(QObject):
'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs): 'confirm-urlunsafe-change', parent=self.gui, title=_('Are you sure?'), config_set=tprefs):
return return
self.add_savepoint(_('Rename %s') % oldname) self.add_savepoint(_('Rename %s') % oldname)
name_map = {oldname:newname}
self.gui.blocking_job( self.gui.blocking_job(
'rename_file', _('Renaming and updating links...'), partial(self.rename_done, oldname, newname), 'rename_file', _('Renaming and updating links...'), partial(self.rename_done, name_map),
rename_files, current_container(), {oldname: newname}) rename_files, current_container(), name_map)
def rename_done(self, oldname, newname, job): def bulk_rename_requested(self, name_map):
self.commit_all_editors_to_container()
self.add_savepoint(_('Bulk rename'))
self.gui.blocking_job(
'bulk_rename_files', _('Renaming and updating links...'), partial(self.rename_done, name_map),
rename_files, current_container(), name_map)
def rename_done(self, name_map, job):
if job.traceback is not None: if job.traceback is not None:
self.rewind_savepoint()
return error_dialog(self.gui, _('Failed to rename files'), return error_dialog(self.gui, _('Failed to rename files'),
_('Failed to rename files, click Show details for more information.'), _('Failed to rename files, click Show details for more information.'),
det_msg=job.traceback, show=True) det_msg=job.traceback, show=True)
self.gui.file_list.build(current_container()) self.gui.file_list.build(current_container())
self.set_modified() self.set_modified()
if oldname in editors: for oldname, newname in name_map.iteritems():
editors[newname] = editors.pop(oldname) if oldname in editors:
self.gui.central.rename_editor(editors[newname], newname) editors[newname] = editors.pop(oldname)
self.gui.central.rename_editor(editors[newname], newname)
self.apply_container_update_to_gui() self.apply_container_update_to_gui()
# }}} # }}}

View File

@ -15,7 +15,8 @@ import sip
from PyQt4.Qt import ( from PyQt4.Qt import (
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QFont, QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QFont,
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer, QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer,
QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea, QRadioButton) QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea,
QRadioButton, QFormLayout, QSpinBox)
from calibre import human_readable, sanitize_file_name_unicode from calibre import human_readable, sanitize_file_name_unicode
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
@ -97,6 +98,7 @@ class FileList(QTreeWidget):
delete_requested = pyqtSignal(object, object) delete_requested = pyqtSignal(object, object)
reorder_spine = pyqtSignal(object) reorder_spine = pyqtSignal(object)
rename_requested = pyqtSignal(object, object) rename_requested = pyqtSignal(object, object)
bulk_rename_requested = pyqtSignal(object)
edit_file = pyqtSignal(object, object, object) edit_file = pyqtSignal(object, object, object)
merge_requested = pyqtSignal(object, object, object) merge_requested = pyqtSignal(object, object, object)
mark_requested = pyqtSignal(object, object) mark_requested = pyqtSignal(object, object)
@ -350,7 +352,9 @@ class FileList(QTreeWidget):
self.set_state(state) self.set_state(state)
if self.current_edited_name: if self.current_edited_name:
self.mark_item_as_current(self.current_edited_name) item = self.item_from_name(self.current_edited_name)
if item is not None:
self.mark_item_as_current(item)
def show_context_menu(self, point): def show_context_menu(self, point):
item = self.itemAt(point) item = self.itemAt(point)
@ -380,6 +384,8 @@ class FileList(QTreeWidget):
if num > 0: if num > 0:
m.addSeparator() m.addSeparator()
if num > 1:
m.addAction(QIcon(I('modified.png')), _('&Bulk rename selected files'), self.request_bulk_rename)
m.addAction(QIcon(I('trash.png')), _('&Delete selected files'), self.request_delete) m.addAction(QIcon(I('trash.png')), _('&Delete selected files'), self.request_delete)
m.addSeparator() m.addSeparator()
@ -435,6 +441,42 @@ class FileList(QTreeWidget):
else: else:
return QTreeWidget.keyPressEvent(self, ev) return QTreeWidget.keyPressEvent(self, ev)
def request_bulk_rename(self):
names = {unicode(item.data(0, NAME_ROLE).toString()) for item in self.selectedItems()}
bad = names & current_container().names_that_must_not_be_changed
if bad:
return error_dialog(self, _('Cannot rename'),
_('The file(s) %s cannot be renamed.') % ('<b>%s</b>' % ', '.join(bad)), show=True)
names = sorted(names, key=self.index_of_name)
d = QDialog(self)
d.l = l = QFormLayout(d)
d.setLayout(l)
d.prefix = p = QLineEdit(d)
p.setText(_('Chapter-'))
p.selectAll()
d.la = la = QLabel(_(
'All selected files will be renamed to the form prefix-number'))
l.addRow(la)
l.addRow(_('&Prefix:'), p)
d.num = num = QSpinBox(d)
num.setMinimum(0), num.setValue(1), num.setMaximum(1000)
l.addRow(_('Starting &number:'), num)
d.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
bb.accepted.connect(d.accept), bb.rejected.connect(d.reject)
l.addRow(bb)
if d.exec_() == d.Accepted:
prefix = sanitize_file_name_unicode(unicode(d.prefix.text()))
num = d.num.value()
largest = num + len(names) - 1
fmt = '%0{0}d'.format(len(str(largest)))
def change_name(name, num):
parts = name.split('/')
base, ext = parts[-1].rpartition('.')[0::2]
parts[-1] = prefix + (fmt % num) + '.' + ext
return '/'.join(parts)
name_map = {n:change_name(n, num + i) for i, n in enumerate(names)}
self.bulk_rename_requested.emit(name_map)
def request_delete(self): def request_delete(self):
names = {unicode(item.data(0, NAME_ROLE).toString()) for item in self.selectedItems()} names = {unicode(item.data(0, NAME_ROLE).toString()) for item in self.selectedItems()}
bad = names & current_container().names_that_must_not_be_removed bad = names & current_container().names_that_must_not_be_removed
@ -673,6 +715,7 @@ class FileListWidget(QWidget):
delete_requested = pyqtSignal(object, object) delete_requested = pyqtSignal(object, object)
reorder_spine = pyqtSignal(object) reorder_spine = pyqtSignal(object)
rename_requested = pyqtSignal(object, object) rename_requested = pyqtSignal(object, object)
bulk_rename_requested = pyqtSignal(object)
edit_file = pyqtSignal(object, object, object) edit_file = pyqtSignal(object, object, object)
merge_requested = pyqtSignal(object, object, object) merge_requested = pyqtSignal(object, object, object)
mark_requested = pyqtSignal(object, object) mark_requested = pyqtSignal(object, object)
@ -687,7 +730,7 @@ class FileListWidget(QWidget):
self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setContentsMargins(0, 0, 0, 0)
for x in ('delete_requested', 'reorder_spine', 'rename_requested', for x in ('delete_requested', 'reorder_spine', 'rename_requested',
'edit_file', 'merge_requested', 'mark_requested', 'edit_file', 'merge_requested', 'mark_requested',
'export_requested', 'replace_requested'): 'export_requested', 'replace_requested', 'bulk_rename_requested'):
getattr(self.file_list, x).connect(getattr(self, x)) getattr(self.file_list, x).connect(getattr(self, x))
for x in ('delete_done', 'select_name', 'request_edit', 'mark_name_as_current', 'clear_currently_edited_name'): for x in ('delete_done', 'select_name', 'request_edit', 'mark_name_as_current', 'clear_currently_edited_name'):
setattr(self, x, getattr(self.file_list, x)) setattr(self, x, getattr(self.file_list, x))