# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' __docformat__ = 'restructuredtext en' TEXT_DRM_FREE = ' (*: drm - free)' LAB_DRM_FREE = '* : drm - free' try: from PyQt5.Qt import (Qt, QVBoxLayout, QLabel, QApplication, QGroupBox, QDialogButtonBox, QHBoxLayout, QTextBrowser, QProgressDialog, QTimer, QSize, QDialog, QIcon, QTableWidget, QTableWidgetItem) except ImportError: from PyQt4.Qt import (Qt, QVBoxLayout, QLabel, QApplication, QGroupBox, QDialogButtonBox, QHBoxLayout, QTextBrowser, QProgressDialog, QTimer, QSize, QDialog, QIcon, QTableWidget, QTableWidgetItem) try: from PyQt5.QtWidgets import (QListWidget, QAbstractItemView) except ImportError: from PyQt4.QtGui import (QListWidget, QAbstractItemView) from calibre.gui2 import gprefs, warning_dialog, error_dialog from calibre.gui2.dialogs.message_box import MessageBox #from calibre.ptempfile import remove_dir from calibre_plugins.obok_dedrm.utilities import (SizePersistedDialog, ImageTitleLayout, showErrorDlg, get_icon, convert_qvariant, debug_print ) from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION) try: debug_print("obok::dialogs.py - loading translations") load_translations() except NameError: debug_print("obok::dialogs.py - exception when loading translations") pass # load_translations() added in calibre 1.9 class SelectionDialog(SizePersistedDialog): ''' Dialog to select the kobo books to decrypt ''' def __init__(self, gui, interface_action, books): ''' :param gui: Parent gui :param interface_action: InterfaceActionObject (InterfacePluginAction class from action.py) :param books: list of Kobo book ''' self.books = books self.gui = gui self.interface_action = interface_action self.books = books SizePersistedDialog.__init__(self, gui, PLUGIN_NAME + 'plugin:selections dialog') self.setWindowTitle(_(PLUGIN_NAME + ' v' + PLUGIN_VERSION)) self.setMinimumWidth(300) self.setMinimumHeight(300) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'images/obok.png', _('Obok DeDRM')) layout.addLayout(title_layout) help_label = QLabel(_('Help'), self) help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) help_label.setAlignment(Qt.AlignRight) help_label.linkActivated.connect(self._help_link_activated) title_layout.addWidget(help_label) title_layout.setAlignment(Qt.AlignTop) layout.addSpacing(5) main_layout = QHBoxLayout() layout.addLayout(main_layout) # self.listy = QListWidget() # self.listy.setSelectionMode(QAbstractItemView.ExtendedSelection) # main_layout.addWidget(self.listy) # self.listy.addItems(books) self.books_table = BookListTableWidget(self) main_layout.addWidget(self.books_table) layout.addSpacing(10) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self._ok_clicked) button_box.rejected.connect(self.reject) self.select_all_button = button_box.addButton(_("Select All"), QDialogButtonBox.ResetRole) self.select_all_button.setToolTip(_("Select all books to add them to the calibre library.")) self.select_all_button.clicked.connect(self._select_all_clicked) self.select_drm_button = button_box.addButton(_("All with DRM"), QDialogButtonBox.ResetRole) self.select_drm_button.setToolTip(_("Select all books with DRM.")) self.select_drm_button.clicked.connect(self._select_drm_clicked) self.select_free_button = button_box.addButton(_("All DRM free"), QDialogButtonBox.ResetRole) self.select_free_button.setToolTip(_("Select all books without DRM.")) self.select_free_button.clicked.connect(self._select_free_clicked) layout.addWidget(button_box) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() self.books_table.populate_table(self.books) def _select_all_clicked(self): self.books_table.select_all() def _select_drm_clicked(self): self.books_table.select_drm(True) def _select_free_clicked(self): self.books_table.select_drm(False) def _help_link_activated(self, url): ''' :param url: Dummy url to pass to the show_help method of the InterfacePluginAction class ''' self.interface_action.show_help() def _ok_clicked(self): ''' Build an index of the selected titles ''' if len(self.books_table.selectedItems()): self.accept() else: msg = 'You must make a selection!' showErrorDlg(msg, self) def getBooks(self): ''' Method to return the selected books ''' return self.books_table.get_books() class BookListTableWidget(QTableWidget): def __init__(self, parent): QTableWidget.__init__(self, parent) self.setSelectionBehavior(QAbstractItemView.SelectRows) def populate_table(self, books): self.clear() self.setAlternatingRowColors(True) self.setRowCount(len(books)) header_labels = ['DRM', _('Title'), _('Author'), _('Series'), 'book_id'] self.setColumnCount(len(header_labels)) self.setHorizontalHeaderLabels(header_labels) self.verticalHeader().setDefaultSectionSize(24) self.horizontalHeader().setStretchLastSection(True) self.books = {} for row, book in enumerate(books): self.populate_table_row(row, book) self.books[row] = book self.setSortingEnabled(False) self.resizeColumnsToContents() self.setMinimumColumnWidth(1, 100) self.setMinimumColumnWidth(2, 100) self.setMinimumSize(300, 0) if len(books) > 0: self.selectRow(0) self.hideColumn(4) self.setSortingEnabled(True) def setMinimumColumnWidth(self, col, minimum): if self.columnWidth(col) < minimum: self.setColumnWidth(col, minimum) def populate_table_row(self, row, book): if book.has_drm: icon = get_icon('drm-locked.png') val = 1 else: icon = get_icon('drm-unlocked.png') val = 0 status_cell = IconWidgetItem(None, icon, val) status_cell.setData(Qt.UserRole, val) self.setItem(row, 0, status_cell) self.setItem(row, 1, ReadOnlyTableWidgetItem(book.title)) self.setItem(row, 2, AuthorTableWidgetItem(book.author, book.author)) self.setItem(row, 3, SeriesTableWidgetItem(book.series, book.series_index)) self.setItem(row, 4, NumericTableWidgetItem(row)) def get_books(self): # debug_print("BookListTableWidget:get_books - self.books:", self.books) books = [] if len(self.selectedItems()): for row in range(self.rowCount()): # debug_print("BookListTableWidget:get_books - row:", row) if self.item(row, 0).isSelected(): book_num = convert_qvariant(self.item(row, 4).data(Qt.DisplayRole)) debug_print("BookListTableWidget:get_books - book_num:", book_num) book = self.books[book_num] debug_print("BookListTableWidget:get_books - book:", book.title) books.append(book) return books def select_all(self): self .selectAll() def select_drm(self, has_drm): self.clearSelection() current_selection_mode = self.selectionMode() self.setSelectionMode(QAbstractItemView.MultiSelection) for row in range(self.rowCount()): # debug_print("BookListTableWidget:select_drm - row:", row) if convert_qvariant(self.item(row, 0).data(Qt.UserRole)) == 1: # debug_print("BookListTableWidget:select_drm - has DRM:", row) if has_drm: self.selectRow(row) else: # debug_print("BookListTableWidget:select_drm - DRM free:", row) if not has_drm: self.selectRow(row) self.setSelectionMode(current_selection_mode) class DecryptAddProgressDialog(QProgressDialog): ''' Use the QTimer singleShot method to dole out books one at a time to the indicated callback function from action.py ''' def __init__(self, gui, indices, callback_fn, db, db_type='calibre', status_msg_type='books', action_type=('Decrypting','Decryption')): ''' :param gui: Parent gui :param indices: List of Kobo books or list calibre book maps (indicated by param db_type) :param callback_fn: the function from action.py that will do the heavy lifting (get_decrypted_kobo_books or add_new_books) :param db: kobo database object or calibre database cache (indicated by param db_type) :param db_type: string indicating what kind of database param db is :param status_msg_type: string to indicate what the ProgressDialog is operating on (cosmetic only) :param action_type: 2-Tuple of strings indicating what the ProgressDialog is doing to param status_msg_type (cosmetic only) ''' self.total_count = len(indices) QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_count, gui) self.setMinimumWidth(500) self.indices, self.callback_fn, self.db, self.db_type = indices, callback_fn, db, db_type self.action_type, self.status_msg_type = action_type, status_msg_type self.gui = gui self.setWindowTitle('{0} {1} {2}...'.format(self.action_type[0], self.total_count, self.status_msg_type)) self.i, self.successes, self.failures = 0, [], [] QTimer.singleShot(0, self.do_book_action) self.exec_() def do_book_action(self): if self.wasCanceled(): return self.do_close() if self.i >= self.total_count: return self.do_close() book = self.indices[self.i] self.i += 1 # Get the title and build the caption and label text from the string parameters provided if self.db_type == 'calibre': dtitle = book[0].title elif self.db_type == 'kobo': dtitle = book.title self.setWindowTitle('{0} {1} {2} ({3} {4} failures)...'.format(self.action_type[0], self.total_count, self.status_msg_type, len(self.failures), self.action_type[1])) self.setLabelText('{0}: {1}'.format(self.action_type[0], dtitle)) # If a calibre db, feed the calibre bookmap to action.py's add_new_books method if self.db_type == 'calibre': if self.callback_fn([book]): self.successes.append(book) else: self.failures.append(book) # If a kobo db, feed the index to the kobo book to action.py's get_decrypted_kobo_books method elif self.db_type == 'kobo': if self.callback_fn(book): debug_print("DecryptAddProgressDialog::do_book_action - decrypted book: '%s'" % dtitle) self.successes.append(book) else: debug_print("DecryptAddProgressDialog::do_book_action - book decryption failed: '%s'" % dtitle) self.failures.append(book) self.setValue(self.i) # Lather, rinse, repeat. QTimer.singleShot(0, self.do_book_action) def do_close(self): self.hide() self.gui = None class AddEpubFormatsProgressDialog(QProgressDialog): ''' Use the QTimer singleShot method to dole out epub formats one at a time to the indicated callback function from action.py ''' def __init__(self, gui, entries, callback_fn, status_msg_type='formats', action_type=('Adding','Added')): ''' :param gui: Parent gui :param entries: List of 3-tuples [(target calibre id, calibre metadata object, path to epub file)] :param callback_fn: the function from action.py that will do the heavy lifting (process_epub_formats) :param status_msg_type: string to indicate what the ProgressDialog is operating on (cosmetic only) :param action_type: 2-tuple of strings indicating what the ProgressDialog is doing to param status_msg_type (cosmetic only) ''' self.total_count = len(entries) QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_count, gui) self.setMinimumWidth(500) self.entries, self.callback_fn = entries, callback_fn self.action_type, self.status_msg_type = action_type, status_msg_type self.gui = gui self.setWindowTitle('{0} {1} {2}...'.format(self.action_type[0], self.total_count, self.status_msg_type)) self.i, self.successes, self.failures = 0, [], [] QTimer.singleShot(0, self.do_book_action) self.exec_() def do_book_action(self): if self.wasCanceled(): return self.do_close() if self.i >= self.total_count: return self.do_close() epub_format = self.entries[self.i] self.i += 1 # assign the elements of the 3-tuple details to legible variables book_id, mi, path = epub_format[0], epub_format[1], epub_format[2] # Get the title and build the caption and label text from the string parameters provided dtitle = mi.title self.setWindowTitle('{0} {1} {2} ({3} {4} failures)...'.format(self.action_type[0], self.total_count, self.status_msg_type, len(self.failures), self.action_type[1])) self.setLabelText('{0}: {1}'.format(self.action_type[0], dtitle)) # Send the necessary elements to the process_epub_formats callback function (action.py) # and record the results if self.callback_fn(book_id, mi, path): self.successes.append((book_id, mi, path)) else: self.failures.append((book_id, mi, path)) self.setValue(self.i) # Lather, rinse, repeat QTimer.singleShot(0, self.do_book_action) def do_close(self): self.hide() self.gui = None class ViewLog(QDialog): ''' Show a detailed summary of results as html. ''' def __init__(self, title, html, parent=None): ''' :param title: Caption for window title :param html: HTML string log/report ''' QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.tb = QTextBrowser(self) QApplication.setOverrideCursor(Qt.WaitCursor) # Rather than formatting the text in
blocks like the calibre # ViewLog does, instead just format it inside divs to keep style formatting html = html.replace('\t',' ')#.replace('\n', '
') html = html.replace('> ','> ') self.tb.setHtml('{0}'.format(html)) QApplication.restoreOverrideCursor() l.addWidget(self.tb) self.bb = QDialogButtonBox(QDialogButtonBox.Ok) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.copy_button = self.bb.addButton(_('Copy to clipboard'), self.bb.ActionRole) self.copy_button.setIcon(QIcon(I('edit-copy.png'))) self.copy_button.clicked.connect(self.copy_to_clipboard) l.addWidget(self.bb) self.setModal(False) self.resize(QSize(700, 500)) self.setWindowTitle(title) self.setWindowIcon(QIcon(I('dialog_information.png'))) self.show() def copy_to_clipboard(self): txt = self.tb.toPlainText() QApplication.clipboard().setText(txt) class ResultsSummaryDialog(MessageBox): def __init__(self, parent, title, msg, log='', det_msg=''): ''' :param log: An HTML log :param title: The title for this popup :param msg: The msg to display :param det_msg: Detailed message ''' MessageBox.__init__(self, MessageBox.INFO, title, msg, det_msg=det_msg, show_copy_button=False, parent=parent) self.log = log self.vlb = self.bb.addButton(_('View Report'), self.bb.ActionRole) self.vlb.setIcon(QIcon(I('dialog_information.png'))) self.vlb.clicked.connect(self.show_log) self.det_msg_toggle.setVisible(bool(det_msg)) self.vlb.setVisible(bool(log)) def show_log(self): self.log_viewer = ViewLog(PLUGIN_NAME + ' v' + PLUGIN_VERSION, self.log, parent=self) class ReadOnlyTableWidgetItem(QTableWidgetItem): def __init__(self, text): if text is None: text = '' QTableWidgetItem.__init__(self, text, QTableWidgetItem.UserType) self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) class AuthorTableWidgetItem(ReadOnlyTableWidgetItem): def __init__(self, text, sort_key): ReadOnlyTableWidgetItem.__init__(self, text) self.sort_key = sort_key #Qt uses a simple < check for sorting items, override this to use the sortKey def __lt__(self, other): return self.sort_key < other.sort_key class SeriesTableWidgetItem(ReadOnlyTableWidgetItem): def __init__(self, series, series_index=None): display = '' if series: if series_index: from calibre.ebooks.metadata import fmt_sidx display = '%s [%s]' % (series, fmt_sidx(series_index)) self.sortKey = '%s%04d' % (series, series_index) else: display = series self.sortKey = series ReadOnlyTableWidgetItem.__init__(self, display) class IconWidgetItem(ReadOnlyTableWidgetItem): def __init__(self, text, icon, sort_key): ReadOnlyTableWidgetItem.__init__(self, text) if icon: self.setIcon(icon) self.sort_key = sort_key #Qt uses a simple < check for sorting items, override this to use the sortKey def __lt__(self, other): return self.sort_key < other.sort_key class NumericTableWidgetItem(QTableWidgetItem): def __init__(self, number, is_read_only=False): QTableWidgetItem.__init__(self, '', QTableWidgetItem.UserType) self.setData(Qt.DisplayRole, number) if is_read_only: self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)