diff --git a/imgsrc/merge.svg b/imgsrc/merge.svg new file mode 100644 index 0000000000..a5a77f2bfb --- /dev/null +++ b/imgsrc/merge.svg @@ -0,0 +1,10259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/merge.png b/resources/images/merge.png new file mode 100644 index 0000000000..ddf9cda594 Binary files /dev/null and b/resources/images/merge.png differ diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 71ac9651bd..d9066285ec 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -8,11 +8,12 @@ __copyright__ = '2013, Kovid Goyal ' import os from binascii import hexlify -from collections import OrderedDict +from collections import OrderedDict, defaultdict +from functools import partial from PyQt4.Qt import ( QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, - QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout) + QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea, QRadioButton) from calibre import human_readable, sanitize_file_name_unicode from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS @@ -81,6 +82,7 @@ class FileList(QTreeWidget): reorder_spine = pyqtSignal(object) rename_requested = pyqtSignal(object, object) edit_file = pyqtSignal(object, object, object) + merge_requested = pyqtSignal(object, object, object) def __init__(self, parent=None): QTreeWidget.__init__(self, parent) @@ -295,7 +297,8 @@ class FileList(QTreeWidget): if item is None or item in set(self.categories.itervalues()): return m = QMenu(self) - num = len(self.selectedItems()) + sel = self.selectedItems() + num = len(sel) if num > 0: m.addAction(QIcon(I('trash.png')), _('&Delete selected files'), self.request_delete) ci = self.currentItem() @@ -303,9 +306,34 @@ class FileList(QTreeWidget): cn = unicode(ci.data(0, NAME_ROLE).toString()) m.addAction(QIcon(I('modified.png')), _('&Rename %s') % (elided_text(self.font(), cn)), self.edit_current_item) + selected_map = defaultdict(list) + for item in sel: + selected_map[unicode(item.data(0, CATEGORY_ROLE).toString())].append(unicode(item.data(0, NAME_ROLE).toString())) + + for items in selected_map.itervalues(): + items.sort(key=self.index_of_name) + + if len(selected_map['text']) > 1: + m.addAction(QIcon(I('merge.png')), _('&Merge selected text files'), partial(self.start_merge, 'text', selected_map['text'])) + if len(selected_map['styles']) > 1: + m.addAction(QIcon(I('merge.png')), _('&Merge selected style files'), partial(self.start_merge, 'styles', selected_map['styles'])) + if len(list(m.actions())) > 0: m.popup(self.mapToGlobal(point)) + def index_of_name(self, name): + for category, parent in self.categories.iteritems(): + for i in xrange(parent.childCount()): + item = parent.child(i) + if unicode(item.data(0, NAME_ROLE).toString()) == name: + return (category, i) + return (None, -1) + + def start_merge(self, category, names): + d = MergeDialog(names, self) + if d.exec_() == d.Accepted and d.ans: + self.merge_requested.emit(category, names, d.ans) + def edit_current_item(self): if self.currentItem() is not None: self.editItem(self.currentItem()) @@ -391,7 +419,7 @@ class FileList(QTreeWidget): ans['selected'][name] = syntax_from_mime(mime) return ans -class NewFileDialog(QDialog): +class NewFileDialog(QDialog): # {{{ def __init__(self, initial_choice='html', parent=None): QDialog.__init__(self, parent) @@ -470,6 +498,42 @@ class NewFileDialog(QDialog): self.using_template = True self.file_name = name QDialog.accept(self) +# }}} + +class MergeDialog(QDialog): # {{{ + + def __init__(self, names, parent=None): + QDialog.__init__(self, parent) + self.setWindowTitle(_('Choose master file')) + self.l = l = QVBoxLayout() + self.setLayout(l) + self.la = la = QLabel(_('Choose the master file. All selected files will be merged into the master file:')) + la.setWordWrap(True) + l.addWidget(la) + self.sa = sa = QScrollArea(self) + l.addWidget(sa) + self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + l.addWidget(bb) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + self.w = w = QWidget(self) + w.l = QVBoxLayout() + w.setLayout(w.l) + + buttons = self.buttons = [QRadioButton(n) for n in names] + buttons[0].setChecked(True) + map(w.l.addWidget, buttons) + sa.setWidget(w) + + self.resize(self.sizeHint() + QSize(150, 20)) + + @property + def ans(self): + for b in self.buttons: + if b.isChecked(): + return unicode(b.text()) + +# }}} class FileListWidget(QWidget): @@ -477,6 +541,7 @@ class FileListWidget(QWidget): reorder_spine = pyqtSignal(object) rename_requested = pyqtSignal(object, object) edit_file = pyqtSignal(object, object, object) + merge_requested = pyqtSignal(object, object, object) def __init__(self, parent=None): QWidget.__init__(self, parent) @@ -484,7 +549,7 @@ class FileListWidget(QWidget): self.file_list = FileList(self) self.layout().addWidget(self.file_list) self.layout().setContentsMargins(0, 0, 0, 0) - for x in ('delete_requested', 'reorder_spine', 'rename_requested', 'edit_file'): + for x in ('delete_requested', 'reorder_spine', 'rename_requested', 'edit_file', 'merge_requested'): getattr(self.file_list, x).connect(getattr(self, x)) for x in ('delete_done', 'select_name'): setattr(self, x, getattr(self.file_list, x))