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(_('<a href="http://www.foo.com/">Help</a>'), 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 __init__(self, parent, icon, text, over=True): QDialog.__init__(self, parent) self.state=None layout = QVBoxLayout(self) self.setLayout(layout) self.setWindowTitle('UnMerge Epub') label = QLabel(text) label.setOpenExternalLinks(True) label.setWordWrap(True) layout.addWidget(label) button_box = QDialogButtonBox(self) button = button_box.addButton(_("Add"), button_box.AcceptRole) button.clicked.connect(self.add) if over: button = button_box.addButton(_("Overwrite"), button_box.AcceptRole) button.clicked.connect(self.over) button = button_box.addButton(_("Discard"), button_box.AcceptRole) button.clicked.connect(self.discard) button_box.accepted.connect(self.accept) layout.addWidget(button_box)
def __init__(self, parent, icon, text, over=True): QDialog.__init__(self, parent) self.state = None layout = QVBoxLayout(self) self.setLayout(layout) self.setWindowTitle(_('UnMerge Epub')) label = QLabel(text) label.setOpenExternalLinks(True) label.setWordWrap(True) layout.addWidget(label) self.applyall = QCheckBox(_('Apply to all EPUBs?'), self) self.applyall.setToolTip( _('Apply the same action to the rest of the EPUBs after this.')) layout.addWidget(self.applyall) button_box = QDialogButtonBox(self) button = button_box.addButton(_("Add"), button_box.AcceptRole) button.clicked.connect(self.add) if over: button = button_box.addButton(_("Overwrite"), button_box.AcceptRole) button.clicked.connect(self.over) button = button_box.addButton(_("Discard"), button_box.AcceptRole) button.clicked.connect(self.discard) button_box.accepted.connect(self.accept) layout.addWidget(button_box)
class ConfirmDialog(QDialog): def __init__(self, ids, parent): QDialog.__init__(self, parent) self.setWindowTitle(_('Schedule download?')) self.setWindowIcon(QIcon(I('dialog_question.png'))) l = self.l = QGridLayout() self.setLayout(l) i = QLabel(self) i.setPixmap(QPixmap(I('dialog_question.png'))) l.addWidget(i, 0, 0) t = QLabel( '<p>'+_('The download of metadata for the <b>%d selected book(s)</b> will' ' run in the background. Proceed?')%len(ids) + '<p>'+_('You can monitor the progress of the download ' 'by clicking the rotating spinner in the bottom right ' 'corner.') + '<p>'+_('When the download completes you will be asked for' ' confirmation before calibre applies the downloaded metadata.') ) t.setWordWrap(True) l.addWidget(t, 0, 1) l.setColumnStretch(0, 1) l.setColumnStretch(1, 100) self.identify = self.covers = True self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) self.bb.rejected.connect(self.reject) b = self.bb.addButton(_('Download only &metadata'), self.bb.AcceptRole) b.clicked.connect(self.only_metadata) b.setIcon(QIcon(I('edit_input.png'))) b = self.bb.addButton(_('Download only &covers'), self.bb.AcceptRole) b.clicked.connect(self.only_covers) b.setIcon(QIcon(I('default_cover.png'))) b = self.b = self.bb.addButton(_('&Configure download'), self.bb.ActionRole) b.setIcon(QIcon(I('config.png'))) b.clicked.connect(partial(show_config, parent, self)) l.addWidget(self.bb, 1, 0, 1, 2) b = self.bb.addButton(_('Download &both'), self.bb.AcceptRole) b.clicked.connect(self.accept) b.setDefault(True) b.setAutoDefault(True) b.setIcon(QIcon(I('ok.png'))) self.resize(self.sizeHint()) b.setFocus(Qt.OtherFocusReason) def only_metadata(self): self.covers = False self.accept() def only_covers(self): self.identify = False self.accept()
def __init__(self, parent, icon, text, over=True): QDialog.__init__(self, parent) self.state=None layout = QVBoxLayout(self) self.setLayout(layout) self.setWindowTitle(_('UnMerge Epub')) label = QLabel(text) label.setOpenExternalLinks(True) label.setWordWrap(True) layout.addWidget(label) self.applyall = QCheckBox(_('Apply to all EPUBs?'),self) self.applyall.setToolTip(_('Apply the same action to the rest of the EPUBs after this.')) layout.addWidget(self.applyall) button_box = QDialogButtonBox(self) button = button_box.addButton(_("Add"), button_box.AcceptRole) button.clicked.connect(self.add) if over: button = button_box.addButton(_("Overwrite"), button_box.AcceptRole) button.clicked.connect(self.over) button = button_box.addButton(_("Discard"), button_box.AcceptRole) button.clicked.connect(self.discard) button_box.accepted.connect(self.accept) layout.addWidget(button_box)
class UpdateNotification(QDialog): def __init__(self, calibre_version, plugin_updates, parent=None): QDialog.__init__(self, parent) self.setAttribute(Qt.WA_QuitOnClose, False) self.resize(400, 250) self.l = QGridLayout() self.setLayout(self.l) self.logo = QLabel() self.logo.setMaximumWidth(110) self.logo.setPixmap(QIcon(I('lt.png')).pixmap(100, 100)) ver = calibre_version if ver.endswith('.0'): ver = ver[:-2] self.label = QLabel( ('<p>' + _('New version <b>{ver}</b> of {app} is available for download. ' 'See the <a href="{url}">new features</a>.').format( url=localize_website_link( 'https://calibre-ebook.com/whats-new'), app=__appname__, ver=ver))) self.label.setOpenExternalLinks(True) self.label.setWordWrap(True) self.setWindowTitle(_('Update available!')) self.setWindowIcon(QIcon(I('lt.png'))) self.l.addWidget(self.logo, 0, 0) self.l.addWidget(self.label, 0, 1) self.cb = QCheckBox(_('Show this notification for future updates'), self) self.l.addWidget(self.cb, 1, 0, 1, -1) self.cb.setChecked(config.get('new_version_notification')) self.cb.stateChanged.connect(self.show_future) self.bb = QDialogButtonBox(self) b = self.bb.addButton(_('&Get update'), self.bb.AcceptRole) b.setDefault(True) b.setIcon(QIcon(I('arrow-down.png'))) if plugin_updates > 0: b = self.bb.addButton(_('Update &plugins'), self.bb.ActionRole) b.setIcon(QIcon(I('plugins/plugin_updater.png'))) b.clicked.connect(self.get_plugins, type=Qt.QueuedConnection) self.bb.addButton(self.bb.Cancel) self.l.addWidget(self.bb, 2, 0, 1, -1) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) dynamic.set('update to version %s' % calibre_version, False) def get_plugins(self): from calibre.gui2.dialogs.plugin_updater import ( PluginUpdaterDialog, FILTER_UPDATE_AVAILABLE) d = PluginUpdaterDialog(self.parent(), initial_filter=FILTER_UPDATE_AVAILABLE) d.exec_() def show_future(self, *args): config.set('new_version_notification', bool(self.cb.isChecked())) def accept(self): open_url(QUrl(get_download_url())) QDialog.accept(self)
def __init__(self, parent, icon, text, over=True): QDialog.__init__(self, parent) self.state = None layout = QVBoxLayout(self) self.setLayout(layout) self.setWindowTitle('UnMerge Epub') label = QLabel(text) label.setOpenExternalLinks(True) label.setWordWrap(True) layout.addWidget(label) button_box = QDialogButtonBox(self) button = button_box.addButton(_("Add"), button_box.AcceptRole) button.clicked.connect(self.add) if over: button = button_box.addButton(_("Overwrite"), button_box.AcceptRole) button.clicked.connect(self.over) button = button_box.addButton(_("Discard"), button_box.AcceptRole) button.clicked.connect(self.discard) button_box.accepted.connect(self.accept) layout.addWidget(button_box)
class UpdateNotification(QDialog): def __init__(self, calibre_version, plugin_updates, parent=None): QDialog.__init__(self, parent) self.setAttribute(Qt.WA_QuitOnClose, False) self.resize(400, 250) self.l = QGridLayout() self.setLayout(self.l) self.logo = QLabel() self.logo.setMaximumWidth(110) self.logo.setPixmap(QPixmap(I('lt.png')).scaled(100, 100, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)) ver = calibre_version if ver.endswith('.0'): ver = ver[:-2] self.label = QLabel(('<p>'+ _('New version <b>%(ver)s</b> of %(app)s is available for download. ' 'See the <a href="http://calibre-ebook.com/whats-new' '">new features</a>.'))%dict( app=__appname__, ver=ver)) self.label.setOpenExternalLinks(True) self.label.setWordWrap(True) self.setWindowTitle(_('Update available!')) self.setWindowIcon(QIcon(I('lt.png'))) self.l.addWidget(self.logo, 0, 0) self.l.addWidget(self.label, 0, 1) self.cb = QCheckBox( _('Show this notification for future updates'), self) self.l.addWidget(self.cb, 1, 0, 1, -1) self.cb.setChecked(config.get('new_version_notification')) self.cb.stateChanged.connect(self.show_future) self.bb = QDialogButtonBox(self) b = self.bb.addButton(_('&Get update'), self.bb.AcceptRole) b.setDefault(True) b.setIcon(QIcon(I('arrow-down.png'))) if plugin_updates > 0: b = self.bb.addButton(_('Update &plugins'), self.bb.ActionRole) b.setIcon(QIcon(I('plugins/plugin_updater.png'))) b.clicked.connect(self.get_plugins, type=Qt.QueuedConnection) self.bb.addButton(self.bb.Cancel) self.l.addWidget(self.bb, 2, 0, 1, -1) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) dynamic.set('update to version %s'%calibre_version, False) def get_plugins(self): from calibre.gui2.dialogs.plugin_updater import (PluginUpdaterDialog, FILTER_UPDATE_AVAILABLE) d = PluginUpdaterDialog(self.parent(), initial_filter=FILTER_UPDATE_AVAILABLE) d.exec_() def show_future(self, *args): config.set('new_version_notification', bool(self.cb.isChecked())) def accept(self): open_url(QUrl(get_download_url())) QDialog.accept(self)
class ConfirmDialog(QDialog): def __init__(self, ids, parent): QDialog.__init__(self, parent) self.setWindowTitle(_('Schedule download?')) self.setWindowIcon(QIcon(I('download-metadata.png'))) l = self.l = QGridLayout() self.setLayout(l) i = QLabel(self) i.setPixmap(QIcon(I('download-metadata.png')).pixmap(128, 128)) l.addWidget(i, 0, 0) t = ngettext( 'The download of metadata for the <b>selected book</b> will run in the background. Proceed?', 'The download of metadata for the <b>{} selected books</b> will run in the background. Proceed?', len(ids)).format(len(ids)) t = QLabel( '<p>' + t + '<p>' + _('You can monitor the progress of the download ' 'by clicking the rotating spinner in the bottom right ' 'corner.') + '<p>' + _('When the download completes you will be asked for' ' confirmation before calibre applies the downloaded metadata.')) t.setWordWrap(True) l.addWidget(t, 0, 1) l.setColumnStretch(0, 1) l.setColumnStretch(1, 100) self.identify = self.covers = True self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) self.bb.rejected.connect(self.reject) b = self.bb.addButton(_('Download only &metadata'), self.bb.AcceptRole) b.clicked.connect(self.only_metadata) b.setIcon(QIcon(I('edit_input.png'))) b = self.bb.addButton(_('Download only &covers'), self.bb.AcceptRole) b.clicked.connect(self.only_covers) b.setIcon(QIcon(I('default_cover.png'))) b = self.b = self.bb.addButton(_('&Configure download'), self.bb.ActionRole) b.setIcon(QIcon(I('config.png'))) connect_lambda(b.clicked, self, lambda self: show_config(self)) l.addWidget(self.bb, 1, 0, 1, 2) b = self.bb.addButton(_('Download &both'), self.bb.AcceptRole) b.clicked.connect(self.accept) b.setDefault(True) b.setAutoDefault(True) b.setIcon(QIcon(I('ok.png'))) self.resize(self.sizeHint()) b.setFocus(Qt.OtherFocusReason) def only_metadata(self): self.covers = False self.accept() def only_covers(self): self.identify = False self.accept()
class MovedDialog(QDialog): # {{{ def __init__(self, stats, location, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(_('No library found')) self._l = l = QGridLayout(self) self.setLayout(l) self.stats, self.location = stats, location loc = self.oldloc = location.replace('/', os.sep) self.header = QLabel( _('No existing calibre library was found at %s. ' 'If the library was moved, select its new location below. ' 'Otherwise calibre will forget this library.') % loc) self.header.setWordWrap(True) ncols = 2 l.addWidget(self.header, 0, 0, 1, ncols) self.cl = QLabel('<b>' + _('New location of this library:')) l.addWidget(self.cl, l.rowCount(), 0, 1, ncols) self.loc = QLineEdit(loc, self) l.addWidget(self.loc, l.rowCount(), 0, 1, 1) self.cd = QToolButton(self) self.cd.setIcon(QIcon(I('document_open.png'))) self.cd.clicked.connect(self.choose_dir) l.addWidget(self.cd, l.rowCount() - 1, 1, 1, 1) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Abort) b = self.bb.addButton(_('Library moved'), QDialogButtonBox.ButtonRole.AcceptRole) b.setIcon(QIcon(I('ok.png'))) b = self.bb.addButton(_('Forget library'), QDialogButtonBox.ButtonRole.RejectRole) b.setIcon(QIcon(I('edit-clear.png'))) b.clicked.connect(self.forget_library) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb, 3, 0, 1, ncols) self.resize(self.sizeHint() + QSize(120, 0)) def choose_dir(self): d = choose_dir(self, 'library moved choose new loc', _('New library location'), default_dir=self.oldloc) if d is not None: self.loc.setText(d) def forget_library(self): self.stats.remove(self.location) def accept(self): newloc = unicode_type(self.loc.text()) if not db_class().exists_at(newloc): error_dialog(self, _('No library found'), _('No existing calibre library found at %s') % newloc, show=True) return self.stats.rename(self.location, newloc) self.newloc = newloc QDialog.accept(self)
def __init__(self, gui, header, prefs, icon, lines, do_split_fn, do_splits_fn, get_split_size_fn, do_user_config, save_size_name='epubsplit:update list dialog'): SizePersistedDialog.__init__(self, gui, save_size_name) self.gui = gui self.do_split_fn = do_split_fn self.do_splits_fn = do_splits_fn self.get_split_size_fn = get_split_size_fn self.do_user_config = do_user_config self.prefs = prefs self.setWindowTitle(header) self.setWindowIcon(icon) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'images/icon.png', header) layout.addLayout(title_layout) lines_layout = QHBoxLayout() layout.addLayout(lines_layout) self.lines_table = LinesTableWidget(self) lines_layout.addWidget(self.lines_table) options_layout = QHBoxLayout() # Button to search the document for something config_button = QtGui.QPushButton(_('Configure'),self) config_button.clicked.connect(self.user_config) config_button.setToolTip(_('Configure Plugin')) options_layout.addWidget(config_button) button_box = QDialogButtonBox(self) new_book = button_box.addButton(_("New Book"), button_box.ActionRole) new_book.setToolTip(_("Make <i>one</i> new book containing the sections selected above and then edit its Metadata.")) new_book.clicked.connect(self.new_book) new_books = button_box.addButton(_("New Book per Section"), button_box.ActionRole) new_books.setToolTip(_("Make a new book for <i>each</i> of the sections selected above. Title for each will be the Table of Contents, which you can edit here first.")) new_books.clicked.connect(self.new_books) get_split_size = button_box.addButton(_("Get Size"), button_box.ActionRole) get_split_size.setToolTip("<i></i>" + _("Calculate the size of the new book from the currently selected sections.")) get_split_size.clicked.connect(self.get_split_size) button_box.addButton(_("Done"), button_box.RejectRole) button_box.rejected.connect(self.reject) options_layout.addWidget(button_box) layout.addLayout(options_layout) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() self.lines_table.populate_table(lines) self.lines_table.show_checkedalways(self.prefs['show_checkedalways'])
class DebugDevice(QDialog): def __init__(self, gui, parent=None): QDialog.__init__(self, parent) self.gui = gui self._layout = QVBoxLayout(self) self.setLayout(self._layout) self.log = QPlainTextEdit(self) self._layout.addWidget(self.log) self.log.setPlainText( _('Getting debug information, please wait') + '...') self.copy = QPushButton(_('Copy to &clipboard')) self.copy.setDefault(True) self.setWindowTitle(_('Debug device detection')) self.setWindowIcon(QIcon(I('debug.png'))) self.copy.clicked.connect(self.copy_to_clipboard) self.ok = QPushButton('&OK') self.ok.setAutoDefault(False) self.ok.clicked.connect(self.accept) self.bbox = QDialogButtonBox(self) self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole) self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole) self._layout.addWidget(self.bbox) self.resize(750, 500) self.bbox.setEnabled(False) QTimer.singleShot(1000, self.debug) def debug(self): if self.gui.device_manager.is_device_connected: error_dialog( self, _('Device already detected'), _('A device (%s) is already detected by calibre.' ' If you wish to debug the detection of another device' ', first disconnect this device.') % self.gui.device_manager.connected_device.get_gui_name(), show=True) self.bbox.setEnabled(True) return self.gui.debug_detection(self) def __call__(self, job): if not self.isVisible(): return self.bbox.setEnabled(True) if job.failed: return error_dialog( self, _('Debugging failed'), _('Running debug device detection failed. Click Show ' 'Details for more information.'), det_msg=job.details, show=True) self.log.setPlainText(job.result) def copy_to_clipboard(self): QApplication.clipboard().setText(self.log.toPlainText())
class MovedDialog(QDialog): # {{{ def __init__(self, stats, location, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(_("No library found")) self._l = l = QGridLayout(self) self.setLayout(l) self.stats, self.location = stats, location loc = self.oldloc = location.replace("/", os.sep) self.header = QLabel( _( "No existing calibre library was found at %s. " "If the library was moved, select its new location below. " "Otherwise calibre will forget this library." ) % loc ) self.header.setWordWrap(True) ncols = 2 l.addWidget(self.header, 0, 0, 1, ncols) self.cl = QLabel("<br><b>" + _("New location of this library:")) l.addWidget(self.cl, 1, 0, 1, ncols) self.loc = QLineEdit(loc, self) l.addWidget(self.loc, 2, 0, 1, 1) self.cd = QToolButton(self) self.cd.setIcon(QIcon(I("document_open.png"))) self.cd.clicked.connect(self.choose_dir) l.addWidget(self.cd, 2, 1, 1, 1) self.bb = QDialogButtonBox(QDialogButtonBox.Abort) b = self.bb.addButton(_("Library moved"), self.bb.AcceptRole) b.setIcon(QIcon(I("ok.png"))) b = self.bb.addButton(_("Forget library"), self.bb.RejectRole) b.setIcon(QIcon(I("edit-clear.png"))) b.clicked.connect(self.forget_library) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb, 3, 0, 1, ncols) self.resize(self.sizeHint() + QSize(100, 50)) def choose_dir(self): d = choose_dir(self, "library moved choose new loc", _("New library location"), default_dir=self.oldloc) if d is not None: self.loc.setText(d) def forget_library(self): self.stats.remove(self.location) def accept(self): newloc = unicode(self.loc.text()) if not db_class().exists_at(newloc): error_dialog(self, _("No library found"), _("No existing calibre library found at %s") % newloc, show=True) return self.stats.rename(self.location, newloc) self.newloc = newloc QDialog.accept(self)
class DebugDevice(QDialog): def __init__(self, gui, parent=None): QDialog.__init__(self, parent) self.gui = gui self._layout = QVBoxLayout(self) self.setLayout(self._layout) self.log = QPlainTextEdit(self) self._layout.addWidget(self.log) self.log.setPlainText(_('Getting debug information, please wait')+'...') self.copy = QPushButton(_('Copy to &clipboard')) self.copy.setDefault(True) self.setWindowTitle(_('Debug device detection')) self.setWindowIcon(QIcon(I('debug.png'))) self.copy.clicked.connect(self.copy_to_clipboard) self.ok = QPushButton('&OK') self.ok.setAutoDefault(False) self.ok.clicked.connect(self.accept) self.bbox = QDialogButtonBox(self) self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole) self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole) self._layout.addWidget(self.bbox) self.resize(750, 500) self.bbox.setEnabled(False) QTimer.singleShot(1000, self.debug) def debug(self): if self.gui.device_manager.is_device_connected: error_dialog(self, _('Device already detected'), _('A device (%s) is already detected by calibre.' ' If you wish to debug the detection of another device' ', first disconnect this device.')% self.gui.device_manager.connected_device.get_gui_name(), show=True) self.bbox.setEnabled(True) return self.gui.debug_detection(self) def __call__(self, job): if not self.isVisible(): return self.bbox.setEnabled(True) if job.failed: return error_dialog(self, _('Debugging failed'), _('Running debug device detection failed. Click Show ' 'Details for more information.'), det_msg=job.details, show=True) self.log.setPlainText(job.result) def copy_to_clipboard(self): QApplication.clipboard().setText(self.log.toPlainText())
def __init__(self, gui, header, prefs, icon, lines, do_split_fn, do_splits_fn, save_size_name='epubsplit:update list dialog'): SizePersistedDialog.__init__(self, gui, save_size_name) self.gui = gui self.do_split_fn = do_split_fn self.do_splits_fn = do_splits_fn self.setWindowTitle(header) self.setWindowIcon(icon) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'images/icon.png', header) layout.addLayout(title_layout) lines_layout = QHBoxLayout() layout.addLayout(lines_layout) self.lines_table = LinesTableWidget(self) lines_layout.addWidget(self.lines_table) options_layout = QHBoxLayout() button_box = QDialogButtonBox(self) new_book = button_box.addButton(_("ساختن کتاب نمونه"), button_box.ActionRole) new_book.setToolTip( _("Make <i>one</i> new book containing the sections selected above and then edit its Metadata." )) new_book.clicked.connect(self.new_book) # new_books = button_box.addButton(_("New Book per Section"), button_box.ActionRole) # new_books.setToolTip(_("Make a new book for <i>each</i> of the sections selected above. Title for each will be the Table of Contents, which you can edit here first.")) # new_books.clicked.connect(self.new_books) button_box.addButton(_("بستن"), button_box.RejectRole) button_box.rejected.connect(self.reject) options_layout.addWidget(button_box) layout.addLayout(options_layout) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() self.lines_table.populate_table(lines)
class ViewLog(QDialog): def __init__(self, title, html, parent=None): 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 <pre> blocks like the calibre # ViewLog does, instead just format it inside divs to keep style formatting html = html.replace('\t',' ') html = html.replace('> ','> ') self.tb.setHtml('<div>{0}</div>'.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('debug.png'))) self.show() def copy_to_clipboard(self): txt = self.tb.toPlainText() QApplication.clipboard().setText(txt)
def _init_controls(self): layout = QVBoxLayout(self) self.setLayout(layout) ml = QHBoxLayout() layout.addLayout(ml, 1) self.keys_list = QListWidget(self) self.keys_list.setSelectionMode(QAbstractItemView.SingleSelection) self.keys_list.setFixedWidth(150) self.keys_list.setAlternatingRowColors(True) ml.addWidget(self.keys_list) self.value_text = QTextEdit(self) self.value_text.setTabStopWidth(24) self.value_text.setReadOnly(False) ml.addWidget(self.value_text, 1) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self._apply_changes) button_box.rejected.connect(self.reject) self.clear_button = button_box.addButton('Clear', QDialogButtonBox.ResetRole) self.clear_button.setIcon(get_icon('trash.png')) self.clear_button.setToolTip('Clear all settings for this plugin') self.clear_button.clicked.connect(self._clear_settings) layout.addWidget(button_box)
class CoverFetch(QDialog): # {{{ def __init__(self, current_cover=None, parent=None): QDialog.__init__(self, parent) self.current_cover = current_cover self.log = Log() self.cover_pixmap = None self.setWindowTitle(_('Downloading cover...')) self.setWindowIcon(QIcon(I('default_cover.png'))) self.l = l = QVBoxLayout() self.setLayout(l) self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) self.covers_widget.chosen.connect(self.accept) l.addWidget(self.covers_widget) self.resize(850, 600) self.finished.connect(self.cleanup) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok) l.addWidget(self.bb) self.log_button = self.bb.addButton( _('&View log'), QDialogButtonBox.ButtonRole.ActionRole) self.log_button.clicked.connect(self.view_log) self.log_button.setIcon(QIcon(I('debug.png'))) self.bb.rejected.connect(self.reject) self.bb.accepted.connect(self.accept) geom = gprefs.get('single-cover-fetch-dialog-geometry', None) if geom is not None: QApplication.instance().safe_restore_geometry(self, geom) def cleanup(self): self.covers_widget.cleanup() def reject(self): gprefs.set('single-cover-fetch-dialog-geometry', bytearray(self.saveGeometry())) self.covers_widget.cancel() return QDialog.reject(self) def accept(self, *args): gprefs.set('single-cover-fetch-dialog-geometry', bytearray(self.saveGeometry())) self.cover_pixmap = self.covers_widget.cover_pixmap() QDialog.accept(self) def start(self, title, authors, identifiers): book = Metadata(title, authors) book.identifiers = identifiers self.covers_widget.start(book, self.current_cover, title, authors, {}) return self.exec_() def view_log(self): self._lv = LogViewer(self.log, self)
class ViewLog(QDialog): # {{{ def __init__(self, title, html, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.tb = QTextBrowser(self) self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html) 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('debug.png'))) self.show() def copy_to_clipboard(self): txt = self.tb.toPlainText() QApplication.clipboard().setText(txt)
def _init_controls(self): layout = QVBoxLayout(self) self.setLayout(layout) ml = QHBoxLayout() layout.addLayout(ml, 1) self.keys_list = QListWidget(self) self.keys_list.setSelectionMode(QAbstractItemView.SingleSelection) self.keys_list.setFixedWidth(150) self.keys_list.setAlternatingRowColors(True) ml.addWidget(self.keys_list) self.value_text = QTextEdit(self) self.value_text.setTabStopWidth(24) self.value_text.setReadOnly(False) ml.addWidget(self.value_text, 1) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self._apply_changes) button_box.rejected.connect(self.reject) self.clear_button = button_box.addButton(_('Clear'), QDialogButtonBox.ResetRole) self.clear_button.setIcon(get_icon('trash.png')) self.clear_button.setToolTip(_('Clear all settings for this plugin')) self.clear_button.clicked.connect(self._clear_settings) layout.addWidget(button_box)
class ViewLog(QDialog): def __init__(self, title, html, parent=None): 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 <pre> blocks like the calibre # ViewLog does, instead just format it inside divs to keep style formatting html = html.replace('\t', ' ') html = html.replace('> ', '> ') self.tb.setHtml('<div>{0}</div>'.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('debug.png'))) self.show() def copy_to_clipboard(self): txt = self.tb.toPlainText() QApplication.clipboard().setText(txt)
class CoverFetch(QDialog): # {{{ def __init__(self, current_cover=None, parent=None): QDialog.__init__(self, parent) self.current_cover = current_cover self.log = Log() self.cover_pixmap = None self.setWindowTitle(_('Downloading cover...')) self.setWindowIcon(QIcon(I('default_cover.png'))) self.l = l = QVBoxLayout() self.setLayout(l) self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) self.covers_widget.chosen.connect(self.accept) l.addWidget(self.covers_widget) self.resize(850, 600) self.finished.connect(self.cleanup) self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) l.addWidget(self.bb) self.log_button = self.bb.addButton(_('&View log'), self.bb.ActionRole) self.log_button.clicked.connect(self.view_log) self.log_button.setIcon(QIcon(I('debug.png'))) self.bb.rejected.connect(self.reject) self.bb.accepted.connect(self.accept) geom = gprefs.get('single-cover-fetch-dialog-geometry', None) if geom is not None: self.restoreGeometry(geom) def cleanup(self): self.covers_widget.cleanup() def reject(self): gprefs.set('single-cover-fetch-dialog-geometry', bytearray(self.saveGeometry())) self.covers_widget.cancel() return QDialog.reject(self) def accept(self, *args): gprefs.set('single-cover-fetch-dialog-geometry', bytearray(self.saveGeometry())) self.cover_pixmap = self.covers_widget.cover_pixmap() QDialog.accept(self) def start(self, title, authors, identifiers): book = Metadata(title, authors) book.identifiers = identifiers self.covers_widget.start(book, self.current_cover, title, authors, {}) return self.exec_() def view_log(self): self._lv = LogViewer(self.log, self)
def view_server_logs(self): from calibre.srv.embedded import log_paths log_error_file, log_access_file = log_paths() d = QDialog(self) d.resize(QSize(800, 600)) layout = QVBoxLayout() d.setLayout(layout) layout.addWidget(QLabel(_('Error log:'))) el = QPlainTextEdit(d) layout.addWidget(el) try: el.setPlainText( lopen(log_error_file, 'rb').read().decode('utf8', 'replace')) except EnvironmentError: el.setPlainText(_('No error log found')) layout.addWidget(QLabel(_('Access log:'))) al = QPlainTextEdit(d) layout.addWidget(al) try: al.setPlainText( lopen(log_access_file, 'rb').read().decode('utf8', 'replace')) except EnvironmentError: al.setPlainText(_('No access log found')) loc = QLabel( _('The server log files are in: {}').format( os.path.dirname(log_error_file))) loc.setWordWrap(True) layout.addWidget(loc) bx = QDialogButtonBox(QDialogButtonBox.Ok) layout.addWidget(bx) bx.accepted.connect(d.accept) b = bx.addButton(_('&Clear logs'), bx.ActionRole) def clear_logs(): if getattr(self.server, 'is_running', False): return error_dialog( d, _('Server running'), _('Cannot clear logs while the server is running. First stop the server.' ), show=True) if self.server: self.server.access_log.clear() self.server.log.clear() else: for x in (log_error_file, log_access_file): try: os.remove(x) except EnvironmentError as err: if err.errno != errno.ENOENT: raise el.setPlainText(''), al.setPlainText('') b.clicked.connect(clear_logs) d.show()
def setup_ui(self): self.setMinimumWidth(300) self.setMinimumHeight(300) layout = QVBoxLayout(self) self.setLayout(layout) main_layout = QHBoxLayout() layout.addLayout(main_layout) self.listy = QListWidget() # self.listy.setSelectionMode(QAbstractItemView.ExtendedSelection) main_layout.addWidget(self.listy) self.listy.addItems(self.files) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box = QDialogButtonBox() ok_button = button_box.addButton(_("See what changed"), QDialogButtonBox.AcceptRole) cancel_button = button_box.addButton(_("Close"), QDialogButtonBox.RejectRole) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) layout.addWidget(button_box)
class ImportAnnotationsDialog(QDialog): def __init__(self, parent, friendly_name, rac): #self.dialog = QDialog(parent.gui) QDialog.__init__(self, parent.gui) self.parent = parent self.opts = parent.opts self.rac = rac parent_loc = self.parent.gui.pos() self.move(parent_loc.x(), parent_loc.y()) self.setWindowTitle(rac.import_dialog_title) self.setWindowIcon(self.opts.icon) l = QVBoxLayout() self.setLayout(l) self.pte = PlainTextEdit(self.parent) self.pte.setPlainText(rac.initial_dialog_text) self.pte.setMinimumWidth(400) l.addWidget(self.pte) self.dialogButtonBox = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Help) self.import_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.import_button.setText('Import') self.dialogButtonBox.clicked.connect(self.import_annotations_dialog_clicked) l.addWidget(self.dialogButtonBox) self.rejected.connect(self.close) self.exec_() self.text = str(self.pte.toPlainText()) def close(self): # Catch ESC and close button self.pte.setPlainText('') self.accept() def import_annotations_dialog_clicked(self, button): BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: # Remove initial_dialog_text if user clicks OK without dropping file if self.text() == self.rac.initial_dialog_text: self.pte.clear() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.HelpRole: hv = HelpView(self, self.opts.icon, self.opts.prefs, html=self.rac.import_help_text) hv.show() else: self.close() def text(self): return unicode(self.pte.toPlainText())
def _init_controls(self): layout = QVBoxLayout(self) self.setLayout(layout) ml = QHBoxLayout() layout.addLayout(ml, 1) self.keys_list = QListWidget(self) self.keys_list.setSelectionMode(QAbstractItemView.SingleSelection) self.keys_list.setFixedWidth(150) self.keys_list.setAlternatingRowColors(True) ml.addWidget(self.keys_list) self.value_text = QTextEdit(self) self.value_text.setTabStopWidth(24) self.value_text.setReadOnly(True) ml.addWidget(self.value_text, 1) button_box = QDialogButtonBox(QDialogButtonBox.Ok) button_box.accepted.connect(self.accept) self.clear_button = button_box.addButton(_('Clear'), QDialogButtonBox.ResetRole) self.clear_button.setIcon(get_icon('trash.png')) self.clear_button.setToolTip(_('Clear all settings for this plugin')) self.clear_button.clicked.connect(self._clear_settings) if DEBUG: self.edit_button = button_box.addButton(_('Edit'), QDialogButtonBox.ResetRole) self.edit_button.setIcon(get_icon('edit_input.png')) self.edit_button.setToolTip(_('Edit settings.')) self.edit_button.clicked.connect(self._edit_settings) self.save_button = button_box.addButton(_('Save'), QDialogButtonBox.ResetRole) self.save_button.setIcon(get_icon('save.png')) self.save_button.setToolTip(_('Save setting for this plugin')) self.save_button.clicked.connect(self._save_settings) self.save_button.setEnabled(False) layout.addWidget(button_box)
def view_server_logs(self): from calibre.srv.embedded import log_paths log_error_file, log_access_file = log_paths() d = QDialog(self) d.resize(QSize(800, 600)) layout = QVBoxLayout() d.setLayout(layout) layout.addWidget(QLabel(_('Error log:'))) el = QPlainTextEdit(d) layout.addWidget(el) try: el.setPlainText( lopen(log_error_file, 'rb').read().decode('utf8', 'replace') ) except EnvironmentError: el.setPlainText(_('No error log found')) layout.addWidget(QLabel(_('Access log:'))) al = QPlainTextEdit(d) layout.addWidget(al) try: al.setPlainText( lopen(log_access_file, 'rb').read().decode('utf8', 'replace') ) except EnvironmentError: al.setPlainText(_('No access log found')) loc = QLabel(_('The server log files are in: {}').format(os.path.dirname(log_error_file))) loc.setWordWrap(True) layout.addWidget(loc) bx = QDialogButtonBox(QDialogButtonBox.Ok) layout.addWidget(bx) bx.accepted.connect(d.accept) b = bx.addButton(_('&Clear logs'), bx.ActionRole) def clear_logs(): if getattr(self.server, 'is_running', False): return error_dialog(d, _('Server running'), _( 'Cannot clear logs while the server is running. First stop the server.'), show=True) if self.server: self.server.access_log.clear() self.server.log.clear() else: for x in (log_error_file, log_access_file): try: os.remove(x) except EnvironmentError as err: if err.errno != errno.ENOENT: raise el.setPlainText(''), al.setPlainText('') b.clicked.connect(clear_logs) d.show()
class LogViewer(QDialog): # {{{ def __init__(self, log, parent=None): QDialog.__init__(self, parent) self.log = log self.l = l = QVBoxLayout() self.setLayout(l) self.tb = QTextBrowser(self) l.addWidget(self.tb) self.bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(self.bb) self.copy_button = self.bb.addButton(_('Copy to clipboard'), self.bb.ActionRole) self.copy_button.clicked.connect(self.copy_to_clipboard) self.copy_button.setIcon(QIcon(I('edit-copy.png'))) self.bb.rejected.connect(self.reject) self.bb.accepted.connect(self.accept) self.setWindowTitle(_('Download log')) self.setWindowIcon(QIcon(I('debug.png'))) self.resize(QSize(800, 400)) self.keep_updating = True self.last_html = None self.finished.connect(self.stop) QTimer.singleShot(100, self.update_log) self.show() def copy_to_clipboard(self): QApplication.clipboard().setText(''.join(self.log.plain_text)) def stop(self, *args): self.keep_updating = False def update_log(self): if not self.keep_updating: return html = self.log.html if html != self.last_html: self.last_html = html self.tb.setHtml('<pre style="font-family:monospace">%s</pre>'%html) QTimer.singleShot(1000, self.update_log)
class LogViewer(QDialog): # {{{ def __init__(self, log, parent=None): QDialog.__init__(self, parent) self.log = log self.l = l = QVBoxLayout() self.setLayout(l) self.tb = QTextBrowser(self) l.addWidget(self.tb) self.bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(self.bb) self.copy_button = self.bb.addButton(_('Copy to clipboard'), self.bb.ActionRole) self.copy_button.clicked.connect(self.copy_to_clipboard) self.copy_button.setIcon(QIcon(I('edit-copy.png'))) self.bb.rejected.connect(self.reject) self.bb.accepted.connect(self.accept) self.setWindowTitle(_('Download log')) self.setWindowIcon(QIcon(I('debug.png'))) self.resize(QSize(800, 400)) self.keep_updating = True self.last_html = None self.finished.connect(self.stop) QTimer.singleShot(100, self.update_log) self.show() def copy_to_clipboard(self): QApplication.clipboard().setText(''.join(self.log.plain_text)) def stop(self, *args): self.keep_updating = False def update_log(self): if not self.keep_updating: return html = self.log.html if html != self.last_html: self.last_html = html self.tb.setHtml('<pre style="font-family:monospace">%s</pre>' % html) QTimer.singleShot(1000, self.update_log)
class ViewLog(QDialog): # {{{ def __init__(self, title, html, parent=None, unique_name=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.tb = QTextBrowser(self) self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html) 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.unique_name = unique_name or 'view-log-dialog' self.finished.connect(self.dialog_closing) self.resize(QSize(700, 500)) geom = gprefs.get(self.unique_name, None) if geom is not None: self.restoreGeometry(geom) self.setModal(False) self.setWindowTitle(title) self.setWindowIcon(QIcon(I('debug.png'))) self.show() def copy_to_clipboard(self): txt = self.tb.toPlainText() QApplication.clipboard().setText(txt) def dialog_closing(self, result): self.geom = bytearray(self.saveGeometry()) gprefs[self.unique_name] = self.geom
class ViewLog(QDialog): # {{{ def __init__(self, title, html, parent=None, unique_name=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.tb = QTextBrowser(self) self.tb.setHtml('<pre style="font-family: monospace">%s</pre>' % html) 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.unique_name = unique_name or 'view-log-dialog' self.finished.connect(self.dialog_closing) self.resize(QSize(700, 500)) geom = gprefs.get(self.unique_name, None) if geom is not None: self.restoreGeometry(geom) self.setModal(False) self.setWindowTitle(title) self.setWindowIcon(QIcon(I('debug.png'))) self.show() def copy_to_clipboard(self): txt = self.tb.toPlainText() QApplication.clipboard().setText(txt) def dialog_closing(self, result): gprefs[self.unique_name] = bytearray(self.saveGeometry())
class Preferences(QDialog): """docstring for Preferences.""" def __init__(self, parent): super(Preferences, self).__init__(parent) self.layout = QVBoxLayout(self) self.tab = QTabWidget(self) self.themeChooser = ThemeChooser(self.tab) self.tab.addTab(self.themeChooser, 'Theme') self.saveOption = SaveOption(self.tab, self.parent().saveThreading) self.tab.addTab(self.saveOption, 'Save') self.layout.addWidget(self.tab) self.buttonBox = QDialogButtonBox(self) self.buttonBox.addButton('Ok', QDialogButtonBox.AcceptRole) self.buttonBox.addButton('Cancel', QDialogButtonBox.RejectRole) self.buttonBox.addButton( 'Apply', QDialogButtonBox.ApplyRole).clicked.connect(self.commit) self.buttonBox.accepted.connect(self.acceptButtonTriggered) self.buttonBox.rejected.connect(self.rejectButtonTriggered) self.layout.addWidget(self.buttonBox) self.setMinimumWidth(self.tab.widget(0).width()) self.setMinimumHeight(self.tab.widget(0).height()) def acceptButtonTriggered(self): self.accept() def rejectButtonTriggered(self): self.reject() def commit(self): for i in range(0, self.tab.count()): self.tab.widget(i).commit() def rollback(self): for i in range(0, self.tab.count()): self.tab.widget(i).rollback()
class Preferences(QMainWindow): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QMainWindow.__init__(self, gui) self.gui = gui self.must_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height() - 25, available_width() - 10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) self.esc_action = QAction(self) self.addAction(self.esc_action) self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape)) self.esc_action.triggered.connect(self.esc) geom = gprefs.get('preferences_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.status_bar = StatusBar(self) self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) self.cw = QWidget(self) self.cw.setLayout(QVBoxLayout()) self.cw.layout().addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Close) self.wizard_button = self.bb.addButton(_('Run welcome wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.cw.layout().addWidget(self.bb) self.bb.button(self.bb.Close).setDefault(True) self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) self.setCentralWidget(self.cw) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.apply_action = self.bar.addAction(QIcon(I('ok.png')), _('&Apply'), self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) self.bar_title = BarTitle(self.bar) self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), ('cancel', _('Cancel and return to overview'))]: ac = getattr(self, ac + '_action') ac.setToolTip(tt) ac.setWhatsThis(tt) ac.setStatusTip(tt) for ch in self.bar.children(): if isinstance(ch, QToolButton): ch.setCursor(Qt.PointingHandCursor) ch.setAutoRaise(True) self.stack.setCurrentIndex(0) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) def event(self, ev): if ev.type() == ev.StatusTip: msg = re.sub(r'</?[a-z1-6]+>', ' ', ev.tip()) ev = QStatusTipEvent(msg) return QMainWindow.event(self, ev) def run_wizard(self): self.close() self.run_wizard_requested.emit() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode(buddy.toolTip()).strip() etext = unicode(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.apply_action.setEnabled(False) self.showing_widget.changed_signal.connect( lambda: self.apply_action.setEnabled(True)) self.showing_widget.restart_now.connect(self.restart_now) self.restore_action.setEnabled( self.showing_widget.supports_restoring_to_defaults) tt = self.showing_widget.restore_defaults_desc if not self.restore_action.isEnabled(): tt = _('Restoring to defaults not supported for') + ' ' + \ plugin.gui_name self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setStatusTip(tt) self.bar_title.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bar.setVisible(True) self.bb.setVisible(False) def hide_plugin(self): self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.bar.setVisible(False) self.stack.setCurrentIndex(0) self.setWindowIcon(QIcon(I('config.png'))) self.bb.setVisible(True) def esc(self, *args): if self.stack.currentIndex() == 1: self.cancel() elif self.stack.currentIndex() == 0: self.close() def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.hide_plugin() self.close() self.gui.quit(restart=True) def commit(self, *args): try: must_restart = self.showing_widget.commit() except AbortCommit: return rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) self.hide_plugin() if self.close_after_initial or (must_restart and rc) or do_restart: self.close() if do_restart: self.gui.quit(restart=True) def cancel(self, *args): if self.close_after_initial: self.close() else: self.hide_plugin() def restore_defaults(self, *args): self.showing_widget.restore_defaults() def closeEvent(self, *args): gprefs.set('preferences_window_geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state( bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() return QMainWindow.closeEvent(self, *args)
class ProceedQuestion(QDialog): ask_question = pyqtSignal(object, object, object) def __init__(self, parent): QDialog.__init__(self, parent) self.setAttribute(Qt.WA_DeleteOnClose, False) self.setWindowIcon(QIcon(I('dialog_question.png'))) self.questions = [] self._l = l = QGridLayout(self) self.setLayout(l) self.icon_label = ic = QLabel(self) ic.setPixmap(QPixmap(I('dialog_question.png'))) self.msg_label = msg = QLabel('some random filler text') msg.setWordWrap(True) ic.setMaximumWidth(110) ic.setMaximumHeight(100) ic.setScaledContents(True) ic.setStyleSheet('QLabel { margin-right: 10px }') self.bb = QDialogButtonBox() self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole) self.log_button.setIcon(QIcon(I('debug.png'))) self.log_button.clicked.connect(self.show_log) self.copy_button = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole) self.copy_button.clicked.connect(self.copy_to_clipboard) self.action_button = self.bb.addButton('', self.bb.ActionRole) self.action_button.clicked.connect(self.action_clicked) self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.det_msg = QPlainTextEdit(self) self.det_msg.setReadOnly(True) self.bb.setStandardButtons(self.bb.Yes|self.bb.No|self.bb.Ok) self.bb.button(self.bb.Yes).setDefault(True) self.checkbox = QCheckBox('', self) l.addWidget(ic, 0, 0, 1, 1) l.addWidget(msg, 0, 1, 1, 1) l.addWidget(self.checkbox, 1, 0, 1, 2) l.addWidget(self.det_msg, 2, 0, 1, 2) l.addWidget(self.bb, 3, 0, 1, 2) self.ask_question.connect(self.do_ask_question, type=Qt.QueuedConnection) def copy_to_clipboard(self, *args): QApplication.clipboard().setText( 'calibre, version %s\n%s: %s\n\n%s' % (__version__, unicode(self.windowTitle()), unicode(self.msg_label.text()), unicode(self.det_msg.toPlainText()))) self.copy_button.setText(_('Copied')) def action_clicked(self): if self.questions: q = self.questions[0] self.questions[0] = q._replace(callback=q.action_callback) self.accept() def accept(self): if self.geom_pref: gprefs[self.geom_pref] = bytearray(self.saveGeometry()) if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(callback, payload, cb) self.hide() def reject(self): if self.geom_pref: gprefs[self.geom_pref] = bytearray(self.saveGeometry()) if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(cancel_callback, payload, cb) self.hide() def do_ask_question(self, callback, payload, checkbox_checked): if callable(callback): args = [payload] if checkbox_checked is not None: args.append(checkbox_checked) callback(*args) self.show_question() def toggle_det_msg(self, *args): vis = unicode(self.det_msg_toggle.text()) == self.hide_det_msg self.det_msg_toggle.setText(self.show_det_msg if vis else self.hide_det_msg) self.det_msg.setVisible(not vis) self.do_resize() def do_resize(self): if self.geom_pref: geom = gprefs.get(self.geom_pref, None) if geom: self.restoreGeometry(geom) return sz = self.sizeHint() + QSize(100, 0) sz.setWidth(min(500, sz.width())) sz.setHeight(min(500, sz.height())) self.resize(sz) def show_question(self): if self.isVisible(): return if self.questions: question = self.questions[0] self.msg_label.setText(question.msg) self.setWindowTitle(question.title) self.log_button.setVisible(bool(question.html_log)) self.copy_button.setText(_('&Copy to clipboard')) self.copy_button.setVisible(bool(question.show_copy_button)) self.action_button.setVisible(question.action_callback is not None) if question.action_callback is not None: self.action_button.setText(question.action_label or '') self.action_button.setIcon( QIcon() if question.action_icon is None else question.action_icon) self.det_msg.setPlainText(question.det_msg or '') self.det_msg.setVisible(False) self.det_msg_toggle.setVisible(bool(question.det_msg)) self.det_msg_toggle.setText(self.show_det_msg) self.checkbox.setVisible(question.checkbox_msg is not None) if question.checkbox_msg is not None: self.checkbox.setText(question.checkbox_msg) self.checkbox.setChecked(question.checkbox_checked) self.bb.button(self.bb.Ok).setVisible(question.show_ok) self.bb.button(self.bb.Yes).setVisible(not question.show_ok) self.bb.button(self.bb.No).setVisible(not question.show_ok) self.geom_pref = ('proceed question dialog:' + question.geom_pref) if question.geom_pref else None if question.show_det: self.toggle_det_msg() else: self.do_resize() self.show() button = self.action_button if question.focus_action and question.action_callback is not None else \ (self.bb.button(self.bb.Ok) if question.show_ok else self.bb.button(self.bb.Yes)) button.setDefault(True) button.setFocus(Qt.OtherFocusReason) def __call__(self, callback, payload, html_log, log_viewer_title, title, msg, det_msg='', show_copy_button=False, cancel_callback=None, log_is_file=False, checkbox_msg=None, checkbox_checked=False, action_callback=None, action_label=None, action_icon=None, focus_action=False, show_det=False, show_ok=False, geom_pref=None): ''' A non modal popup that notifies the user that a background task has been completed. This class guarantees that only a single popup is visible at any one time. Other requests are queued and displayed after the user dismisses the current popup. :param callback: A callable that is called with payload if the user asks to proceed. Note that this is always called in the GUI thread. :param cancel_callback: A callable that is called with the payload if the users asks not to proceed. :param payload: Arbitrary object, passed to callback :param html_log: An HTML or plain text log :param log_viewer_title: The title for the log viewer window :param title: The title for this popup :param msg: The msg to display :param det_msg: Detailed message :param log_is_file: If True the html_log parameter is interpreted as the path to a file on disk containing the log encoded with utf-8 :param checkbox_msg: If not None, a checkbox is displayed in the dialog, showing this message. The callback is called with both the payload and the state of the checkbox as arguments. :param checkbox_checked: If True the checkbox is checked by default. :param action_callback: If not None, an extra button is added, which when clicked will cause action_callback to be called instead of callback. action_callback is called in exactly the same way as callback. :param action_label: The text on the action button :param action_icon: The icon for the action button, must be a QIcon object or None :param focus_action: If True, the action button will be focused instead of the Yes button :param show_det: If True, the Detailed message will be shown initially :param show_ok: If True, OK will be shown instead of YES/NO :param geom_pref: String for preference name to preserve dialog box geometry ''' question = Question( payload, callback, cancel_callback, title, msg, html_log, log_viewer_title, log_is_file, det_msg, show_copy_button, checkbox_msg, checkbox_checked, action_callback, action_label, action_icon, focus_action, show_det, show_ok, geom_pref) self.questions.append(question) self.show_question() def show_log(self): if self.questions: q = self.questions[0] log = q.html_log if q.log_is_file: with open(log, 'rb') as f: log = f.read().decode('utf-8') self.log_viewer = ViewLog(q.log_viewer_title, log, parent=self)
class FontFamilyDialog(QDialog): def __init__(self, current_family, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(_('Choose font family')) self.setWindowIcon(QIcon(I('font.png'))) from calibre.utils.fonts.scanner import font_scanner self.font_scanner = font_scanner self.m = QStringListModel(self) self.build_font_list() self.l = l = QGridLayout() self.setLayout(l) self.view = FontsView(self) self.view.setModel(self.m) self.view.setCurrentIndex(self.m.index(0)) if current_family: for i, val in enumerate(self.families): if icu_lower(val) == icu_lower(current_family): self.view.setCurrentIndex(self.m.index(i)) break self.view.doubleClicked.connect(self.accept, type=Qt.QueuedConnection) self.view.changed.connect(self.current_changed, type=Qt.QueuedConnection) self.faces = Typefaces(self) self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.add_fonts_button = afb = self.bb.addButton( _('Add &fonts'), self.bb.ActionRole) afb.setIcon(QIcon(I('plus.png'))) afb.clicked.connect(self.add_fonts) self.ml = QLabel(_('Choose a font family from the list below:')) self.search = QLineEdit(self) self.search.setPlaceholderText(_('Search')) self.search.returnPressed.connect(self.find) self.nb = QToolButton(self) self.nb.setIcon(QIcon(I('arrow-down.png'))) self.nb.setToolTip(_('Find Next')) self.pb = QToolButton(self) self.pb.setIcon(QIcon(I('arrow-up.png'))) self.pb.setToolTip(_('Find Previous')) self.nb.clicked.connect(self.find_next) self.pb.clicked.connect(self.find_previous) l.addWidget(self.ml, 0, 0, 1, 4) l.addWidget(self.search, 1, 0, 1, 1) l.addWidget(self.nb, 1, 1, 1, 1) l.addWidget(self.pb, 1, 2, 1, 1) l.addWidget(self.view, 2, 0, 1, 3) l.addWidget(self.faces, 1, 3, 2, 1) l.addWidget(self.bb, 3, 0, 1, 4) l.setAlignment(self.faces, Qt.AlignTop) self.resize(800, 600) def set_current(self, i): self.view.setCurrentIndex(self.m.index(i)) def keyPressEvent(self, e): if e.key() == Qt.Key_Return: return return QDialog.keyPressEvent(self, e) def find(self, backwards=False): i = self.view.currentIndex().row() if i < 0: i = 0 q = icu_lower(unicode(self.search.text())).strip() if not q: return r = (xrange(i - 1, -1, -1) if backwards else xrange( i + 1, len(self.families))) for j in r: f = self.families[j] if q in icu_lower(f): self.set_current(j) return def find_next(self): self.find() def find_previous(self): self.find(backwards=True) def build_font_list(self): try: self.families = list(self.font_scanner.find_font_families()) except: self.families = [] print('WARNING: Could not load fonts') import traceback traceback.print_exc() self.families.insert(0, _('None')) self.m.setStringList(self.families) def add_fonts(self): families = add_fonts(self) if not families: return self.font_scanner.do_scan() self.m.beginResetModel() self.build_font_list() self.m.endResetModel() self.view.setCurrentIndex(self.m.index(0)) if families: for i, val in enumerate(self.families): if icu_lower(val) == icu_lower(families[0]): self.view.setCurrentIndex(self.m.index(i)) break info_dialog(self, _('Added fonts'), _('Added font families: %s') % (', '.join(families)), show=True) @property def font_family(self): idx = self.view.currentIndex().row() if idx == 0: return None return self.families[idx] def current_changed(self): fam = self.font_family self.faces.show_family( fam, self.font_scanner.fonts_for_family(fam) if fam else None)
class ProceedQuestion(QWidget): ask_question = pyqtSignal(object, object, object) @pyqtProperty(float) def show_fraction(self): return self._show_fraction @show_fraction.setter def show_fraction(self, val): self._show_fraction = max(0, min(1, float(val))) self.update() def __init__(self, parent): QWidget.__init__(self, parent) self.setVisible(False) parent.installEventFilter(self) self._show_fraction = 0.0 self.show_animation = a = QPropertyAnimation(self, b"show_fraction", self) a.setDuration(1000), a.setEasingCurve(QEasingCurve.OutQuad) a.setStartValue(0.0), a.setEndValue(1.0) a.finished.connect(self.stop_show_animation) self.rendered_pixmap = None self.questions = [] self.icon = ic = Icon(self) self.msg_label = msg = QLabel('some random filler text') msg.setWordWrap(True) self.bb = QDialogButtonBox() self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole) self.log_button.setIcon(QIcon(I('debug.png'))) self.log_button.clicked.connect(self.show_log) self.copy_button = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole) self.copy_button.clicked.connect(self.copy_to_clipboard) self.action_button = self.bb.addButton('', self.bb.ActionRole) self.action_button.clicked.connect(self.action_clicked) self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.det_msg = PlainTextEdit(self) self.det_msg.setReadOnly(True) self.bb.setStandardButtons(self.bb.Yes|self.bb.No|self.bb.Ok) self.bb.button(self.bb.Yes).setDefault(True) self.title_label = title = QLabel('A dummy title') f = title.font() f.setBold(True) title.setFont(f) self.checkbox = QCheckBox('', self) self._l = l = QVBoxLayout(self) self._h = h = QHBoxLayout() self._v = v = QVBoxLayout() v.addWidget(title), v.addWidget(msg) h.addWidget(ic), h.addSpacing(10), h.addLayout(v), l.addLayout(h) l.addSpacing(5) l.addWidget(self.checkbox) l.addWidget(self.det_msg) l.addWidget(self.bb) self.ask_question.connect(self.do_ask_question, type=Qt.QueuedConnection) self.setFocusPolicy(Qt.NoFocus) for child in self.findChildren(QWidget): child.setFocusPolicy(Qt.NoFocus) self.setFocusProxy(self.parent()) self.resize_timer = t = QTimer(self) t.setSingleShot(True), t.setInterval(100), t.timeout.connect(self.parent_resized) def eventFilter(self, obj, ev): if ev.type() == ev.Resize and self.isVisible(): self.resize_timer.start() return False def parent_resized(self): if self.isVisible(): self.do_resize() def copy_to_clipboard(self, *args): QApplication.clipboard().setText( 'calibre, version %s\n%s: %s\n\n%s' % (__version__, unicode_type(self.windowTitle()), unicode_type(self.msg_label.text()), unicode_type(self.det_msg.toPlainText()))) self.copy_button.setText(_('Copied')) def action_clicked(self): if self.questions: q = self.questions[0] self.questions[0] = q._replace(callback=q.action_callback) self.accept() def accept(self): if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(callback, payload, cb) self.hide() def reject(self): if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(cancel_callback, payload, cb) self.hide() def do_ask_question(self, callback, payload, checkbox_checked): if callable(callback): args = [payload] if checkbox_checked is not None: args.append(checkbox_checked) callback(*args) self.show_question() def toggle_det_msg(self, *args): vis = unicode_type(self.det_msg_toggle.text()) == self.hide_det_msg self.det_msg_toggle.setText(self.show_det_msg if vis else self.hide_det_msg) self.det_msg.setVisible(not vis) self.do_resize() def do_resize(self): sz = self.sizeHint() sz.setWidth(min(self.parent().width(), sz.width())) sb = self.parent().statusBar().height() + 10 sz.setHeight(min(self.parent().height() - sb, sz.height())) self.resize(sz) self.position_widget() def show_question(self): if not self.questions: return if not self.isVisible(): question = self.questions[0] self.msg_label.setText(question.msg) self.icon.set_icon(question.icon) self.title_label.setText(question.title) self.log_button.setVisible(bool(question.html_log)) self.copy_button.setText(_('&Copy to clipboard')) if question.action_callback is not None: self.action_button.setText(question.action_label or '') self.action_button.setIcon( QIcon() if question.action_icon is None else question.action_icon) # Force the button box to relayout its buttons, as button text # might have changed self.bb.setOrientation(Qt.Vertical), self.bb.setOrientation(Qt.Horizontal) self.det_msg.setPlainText(question.det_msg or '') self.det_msg.setVisible(False) self.det_msg_toggle.setVisible(bool(question.det_msg)) self.det_msg_toggle.setText(self.show_det_msg) self.checkbox.setVisible(question.checkbox_msg is not None) if question.checkbox_msg is not None: self.checkbox.setText(question.checkbox_msg) self.checkbox.setChecked(question.checkbox_checked) self.bb.button(self.bb.Ok).setVisible(question.show_ok) self.bb.button(self.bb.Yes).setVisible(not question.show_ok) self.bb.button(self.bb.No).setVisible(not question.show_ok) self.copy_button.setVisible(bool(question.show_copy_button)) self.action_button.setVisible(question.action_callback is not None) self.toggle_det_msg() if question.show_det else self.do_resize() self.show_widget() button = self.action_button if question.focus_action and question.action_callback is not None else \ (self.bb.button(self.bb.Ok) if question.show_ok else self.bb.button(self.bb.Yes)) button.setDefault(True) self.raise_() self.start_show_animation() def start_show_animation(self): if self.rendered_pixmap is not None: return dpr = getattr(self, 'devicePixelRatioF', self.devicePixelRatio)() p = QImage(dpr * self.size(), QImage.Format_ARGB32_Premultiplied) p.setDevicePixelRatio(dpr) self.render(p) self.rendered_pixmap = QPixmap.fromImage(p) self.original_visibility = v = [] for child in self.findChildren(QWidget): if child.isVisible(): child.setVisible(False) v.append(child) self.show_animation.start() def stop_show_animation(self): self.rendered_pixmap = None [c.setVisible(True) for c in getattr(self, 'original_visibility', ())] self.update() for child in self.findChildren(QWidget): child.update() if hasattr(child, 'viewport'): child.viewport().update() def position_widget(self): geom = self.parent().geometry() x = geom.width() - self.width() - 5 sb = self.parent().statusBar() if sb is None: y = geom.height() - self.height() else: y = sb.geometry().top() - self.height() self.move(x, y) def show_widget(self): self.show() self.position_widget() def dummy_question(self, action_label=None): self(lambda *args:args, (), 'dummy log', 'Log Viewer', 'A Dummy Popup', 'This is a dummy popup to easily test things, with a long line of text that should wrap. ' 'This is a dummy popup to easily test things, with a long line of text that should wrap', checkbox_msg='A dummy checkbox', action_callback=lambda *args: args, action_label=action_label or 'An action') def __call__(self, callback, payload, html_log, log_viewer_title, title, msg, det_msg='', show_copy_button=False, cancel_callback=None, log_is_file=False, checkbox_msg=None, checkbox_checked=False, action_callback=None, action_label=None, action_icon=None, focus_action=False, show_det=False, show_ok=False, icon=None, log_viewer_unique_name=None, **kw): ''' A non modal popup that notifies the user that a background task has been completed. This class guarantees that only a single popup is visible at any one time. Other requests are queued and displayed after the user dismisses the current popup. :param callback: A callable that is called with payload if the user asks to proceed. Note that this is always called in the GUI thread. :param cancel_callback: A callable that is called with the payload if the users asks not to proceed. :param payload: Arbitrary object, passed to callback :param html_log: An HTML or plain text log :param log_viewer_title: The title for the log viewer window :param title: The title for this popup :param msg: The msg to display :param det_msg: Detailed message :param log_is_file: If True the html_log parameter is interpreted as the path to a file on disk containing the log encoded with utf-8 :param checkbox_msg: If not None, a checkbox is displayed in the dialog, showing this message. The callback is called with both the payload and the state of the checkbox as arguments. :param checkbox_checked: If True the checkbox is checked by default. :param action_callback: If not None, an extra button is added, which when clicked will cause action_callback to be called instead of callback. action_callback is called in exactly the same way as callback. :param action_label: The text on the action button :param action_icon: The icon for the action button, must be a QIcon object or None :param focus_action: If True, the action button will be focused instead of the Yes button :param show_det: If True, the Detailed message will be shown initially :param show_ok: If True, OK will be shown instead of YES/NO :param icon: The icon to be used for this popop (defaults to question mark). Can be either a QIcon or a string to be used with I() :log_viewer_unique_name: If set, ViewLog will remember/reuse its size for this name in calibre.gui2.gprefs ''' question = Question( payload, callback, cancel_callback, title, msg, html_log, log_viewer_title, log_is_file, det_msg, show_copy_button, checkbox_msg, checkbox_checked, action_callback, action_label, action_icon, focus_action, show_det, show_ok, icon, log_viewer_unique_name) self.questions.append(question) self.show_question() def show_log(self): if self.questions: q = self.questions[0] log = q.html_log if q.log_is_file: with open(log, 'rb') as f: log = f.read().decode('utf-8') self.log_viewer = ViewLog(q.log_viewer_title, log, parent=self, unique_name=q.log_viewer_unique_name) def paintEvent(self, ev): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) try: if self.rendered_pixmap is None: self.paint_background(painter) else: self.animated_paint(painter) finally: painter.end() def animated_paint(self, painter): top = (1 - self._show_fraction) * self.height() painter.drawPixmap(0, top, self.rendered_pixmap) def paint_background(self, painter): br = 12 # border_radius bw = 1 # border_width pal = self.palette() c = pal.color(pal.Window) c.setAlphaF(0.9) p = QPainterPath() p.addRoundedRect(QRectF(self.rect()), br, br) painter.fillPath(p, c) p.addRoundedRect(QRectF(self.rect()).adjusted(bw, bw, -bw, -bw), br, br) painter.fillPath(p, pal.color(pal.WindowText))
class Preferences(QDialog): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QDialog.__init__(self, gui) self.gui = gui self.must_restart = False self.do_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height() - 25, available_width() - 10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) geom = gprefs.get('preferences dialog geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.ApplicationModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.l = l = QVBoxLayout(self) self.stack = QStackedWidget(self) self.bb = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Apply | QDialogButtonBox.Discard | QDialogButtonBox.RestoreDefaults) self.bb.button(self.bb.Apply).clicked.connect(self.accept) self.bb.button(self.bb.Discard).clicked.connect(self.reject) self.bb.button(self.bb.RestoreDefaults).setIcon( QIcon(I('clear_left.png'))) self.bb.button(self.bb.RestoreDefaults).clicked.connect( self.restore_defaults) self.wizard_button = self.bb.addButton(_('Run Welcome &wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.wizard_button.setAutoDefault(False) self.bb.rejected.connect(self.reject) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.title_bar = TitleBar(self) for ac, tt in [(self.bb.Apply, _('Save changes')), (self.bb.Discard, _('Cancel and return to overview'))]: self.bb.button(ac).setToolTip(tt) l.addWidget(self.title_bar), l.addWidget(self.stack), l.addWidget( self.bb) if initial_plugin is not None: category, name = initial_plugin[:2] plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) if len(initial_plugin) > 2: w = self.findChild(QWidget, initial_plugin[2]) if w is not None: for c in self.showing_widget.children(): if isinstance(c, QTabWidget): idx = c.indexOf(w) if idx > -1: c.setCurrentIndex(idx) break else: self.hide_plugin() def event(self, ev): if isinstance(ev, QStatusTipEvent): msg = re.sub(r'</?[a-z1-6]+>', ' ', ev.tip()) self.title_bar.show_msg(msg) return QDialog.event(self, ev) def run_wizard(self): self.run_wizard_requested.emit() self.accept() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode_type(buddy.toolTip()).strip() etext = unicode_type(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.showing_widget.restart_now.connect(self.restart_now) self.title_bar.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bb.button(self.bb.Close).setVisible(False) self.wizard_button.setVisible(False) for button in (self.bb.Apply, self.bb.RestoreDefaults, self.bb.Discard): button = self.bb.button(button) button.setVisible(True) self.bb.button(self.bb.Apply).setEnabled(False) self.bb.button(self.bb.Apply).setDefault(False), self.bb.button( self.bb.Apply).setDefault(True) self.bb.button(self.bb.RestoreDefaults).setEnabled( self.showing_widget.supports_restoring_to_defaults) self.bb.button(self.bb.RestoreDefaults).setToolTip( self.showing_widget.restore_defaults_desc if self.showing_widget. supports_restoring_to_defaults else ( _('Restoring to defaults not supported for') + ' ' + plugin.gui_name)) self.bb.button(self.bb.RestoreDefaults).setText(_('Restore &defaults')) self.showing_widget.changed_signal.connect(self.changed_signal) def changed_signal(self): b = self.bb.button(self.bb.Apply) b.setEnabled(True) def hide_plugin(self): for sig in 'changed_signal restart_now'.split(): try: getattr(self.showing_widget, sig).disconnect(getattr(self, sig)) except Exception: pass self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.stack.setCurrentIndex(0) self.title_bar.show_plugin() self.setWindowIcon(QIcon(I('config.png'))) for button in (self.bb.Apply, self.bb.RestoreDefaults, self.bb.Discard): button = self.bb.button(button) button.setVisible(False) self.bb.button(self.bb.Close).setVisible(True) self.bb.button(self.bb.Close).setDefault(False), self.bb.button( self.bb.Close).setDefault(True) self.wizard_button.setVisible(True) def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.do_restart = True self.hide_plugin() self.accept() def commit(self, *args): must_restart = self.showing_widget.commit() rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) if do_restart: self.do_restart = True return self.close_after_initial or (must_restart and rc) or do_restart def restore_defaults(self, *args): self.showing_widget.restore_defaults() def on_shutdown(self): gprefs.set('preferences dialog geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state( bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() def accept(self): if self.stack.currentIndex() == 0: self.on_shutdown() return QDialog.accept(self) try: close = self.commit() except AbortCommit: return if close: self.on_shutdown() return QDialog.accept(self) self.hide_plugin() def reject(self): if self.stack.currentIndex() == 0 or self.close_after_initial: self.on_shutdown() return QDialog.reject(self) self.hide_plugin()
class IgnoredFolders(QDialog): def __init__(self, dev, ignored_folders=None, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel('<p>'+ _('<b>Scanned folders:</b>') + ' ' + _('You can select which folders calibre will ' 'scan when searching this device for books.')) la.setWordWrap(True) l.addWidget(la) self.tabs = QTabWidget(self) l.addWidget(self.tabs) self.widgets = [] for storage in dev.filesystem_cache.entries: self.dev = dev w = Storage(storage, item_func=self.create_item) del self.dev self.tabs.addTab(w, storage.name) self.widgets.append(w) w.itemChanged.connect(self.item_changed) self.la2 = la = QLabel(_( 'If you a select a previously unselected folder, any sub-folders' ' will not be visible until you restart calibre.')) l.addWidget(la) la.setWordWrap(True) self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.sab = self.bb.addButton(_('Select &All'), self.bb.ActionRole) self.sab.clicked.connect(self.select_all) self.snb = self.bb.addButton(_('Select &None'), self.bb.ActionRole) self.snb.clicked.connect(self.select_none) l.addWidget(self.bb) self.setWindowTitle(_('Choose folders to scan')) self.setWindowIcon(QIcon(I('devices/tablet.png'))) self.resize(600, 500) def item_changed(self, item, column): w = item.treeWidget() root = w.invisibleRootItem() w.itemChanged.disconnect(self.item_changed) try: if item.checkState(0) == Qt.Checked: # Ensure that the parents of this item are checked p = item.parent() while p is not None and p is not root: p.setCheckState(0, Qt.Checked) p = p.parent() # Set the state of all descendants to the same state as this item for child in self.iterchildren(item): child.setCheckState(0, item.checkState(0)) finally: w.itemChanged.connect(self.item_changed) def iterchildren(self, node): ' Iterate over all descendants of node ' for i in xrange(node.childCount()): child = node.child(i) yield child for gc in self.iterchildren(child): yield gc def create_item(self, f, parent): name = f.name ans = QTreeWidgetItem(parent, [name]) ans.setData(0, Qt.UserRole, '/'.join(f.full_path[1:])) ans.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) ans.setCheckState(0, Qt.Unchecked if self.dev.is_folder_ignored(f.storage_id, f.full_path[1:]) else Qt.Checked) ans.setData(0, Qt.DecorationRole, file_icon_provider().icon_from_ext('dir')) return ans def select_all(self): w = self.tabs.currentWidget() for i in xrange(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Checked) def select_none(self): w = self.tabs.currentWidget() for i in xrange(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Unchecked) @property def ignored_folders(self): ans = {} for w in self.widgets: folders = set() for node in self.iterchildren(w.invisibleRootItem()): if node.checkState(0) == Qt.Checked: continue path = unicode(node.data(0, Qt.UserRole) or '') parent = path.rpartition('/')[0] if '/' not in path or icu_lower(parent) not in folders: folders.add(icu_lower(path)) ans[unicode(w.storage.storage_id)] = list(folders) return ans
class ProceedQuestion(QDialog): ask_question = pyqtSignal(object, object, object) def __init__(self, parent): QDialog.__init__(self, parent) self.setAttribute(Qt.WA_DeleteOnClose, False) self.setWindowIcon(QIcon(I('dialog_question.png'))) self.questions = [] self._l = l = QGridLayout(self) self.setLayout(l) self.icon_label = ic = QLabel(self) ic.setPixmap(QPixmap(I('dialog_question.png'))) self.msg_label = msg = QLabel('some random filler text') msg.setWordWrap(True) ic.setMaximumWidth(110) ic.setMaximumHeight(100) ic.setScaledContents(True) ic.setStyleSheet('QLabel { margin-right: 10px }') self.bb = QDialogButtonBox() self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole) self.log_button.setIcon(QIcon(I('debug.png'))) self.log_button.clicked.connect(self.show_log) self.copy_button = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole) self.copy_button.clicked.connect(self.copy_to_clipboard) self.action_button = self.bb.addButton('', self.bb.ActionRole) self.action_button.clicked.connect(self.action_clicked) self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.det_msg = QPlainTextEdit(self) self.det_msg.setReadOnly(True) self.bb.setStandardButtons(self.bb.Yes | self.bb.No | self.bb.Ok) self.bb.button(self.bb.Yes).setDefault(True) self.checkbox = QCheckBox('', self) l.addWidget(ic, 0, 0, 1, 1) l.addWidget(msg, 0, 1, 1, 1) l.addWidget(self.checkbox, 1, 0, 1, 2) l.addWidget(self.det_msg, 2, 0, 1, 2) l.addWidget(self.bb, 3, 0, 1, 2) self.ask_question.connect(self.do_ask_question, type=Qt.QueuedConnection) def copy_to_clipboard(self, *args): QApplication.clipboard().setText( 'calibre, version %s\n%s: %s\n\n%s' % (__version__, unicode(self.windowTitle()), unicode(self.msg_label.text()), unicode( self.det_msg.toPlainText()))) self.copy_button.setText(_('Copied')) def action_clicked(self): if self.questions: q = self.questions[0] self.questions[0] = q._replace(callback=q.action_callback) self.accept() def accept(self): if self.geom_pref: gprefs[self.geom_pref] = bytearray(self.saveGeometry()) if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(callback, payload, cb) self.hide() def reject(self): if self.geom_pref: gprefs[self.geom_pref] = bytearray(self.saveGeometry()) if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(cancel_callback, payload, cb) self.hide() def do_ask_question(self, callback, payload, checkbox_checked): if callable(callback): args = [payload] if checkbox_checked is not None: args.append(checkbox_checked) callback(*args) self.show_question() def toggle_det_msg(self, *args): vis = unicode(self.det_msg_toggle.text()) == self.hide_det_msg self.det_msg_toggle.setText( self.show_det_msg if vis else self.hide_det_msg) self.det_msg.setVisible(not vis) self.do_resize() def do_resize(self): if self.geom_pref: geom = gprefs.get(self.geom_pref, None) if geom: self.restoreGeometry(geom) return sz = self.sizeHint() + QSize(100, 0) sz.setWidth(min(500, sz.width())) sz.setHeight(min(500, sz.height())) self.resize(sz) def show_question(self): if self.isVisible(): return if self.questions: question = self.questions[0] self.msg_label.setText(question.msg) self.setWindowTitle(question.title) self.log_button.setVisible(bool(question.html_log)) self.copy_button.setText(_('&Copy to clipboard')) self.copy_button.setVisible(bool(question.show_copy_button)) self.action_button.setVisible(question.action_callback is not None) if question.action_callback is not None: self.action_button.setText(question.action_label or '') self.action_button.setIcon(QIcon( ) if question.action_icon is None else question.action_icon) self.det_msg.setPlainText(question.det_msg or '') self.det_msg.setVisible(False) self.det_msg_toggle.setVisible(bool(question.det_msg)) self.det_msg_toggle.setText(self.show_det_msg) self.checkbox.setVisible(question.checkbox_msg is not None) if question.checkbox_msg is not None: self.checkbox.setText(question.checkbox_msg) self.checkbox.setChecked(question.checkbox_checked) self.bb.button(self.bb.Ok).setVisible(question.show_ok) self.bb.button(self.bb.Yes).setVisible(not question.show_ok) self.bb.button(self.bb.No).setVisible(not question.show_ok) self.geom_pref = ( 'proceed question dialog:' + question.geom_pref) if question.geom_pref else None if question.show_det: self.toggle_det_msg() else: self.do_resize() self.show() button = self.action_button if question.focus_action and question.action_callback is not None else \ (self.bb.button(self.bb.Ok) if question.show_ok else self.bb.button(self.bb.Yes)) button.setDefault(True) button.setFocus(Qt.OtherFocusReason) def __call__(self, callback, payload, html_log, log_viewer_title, title, msg, det_msg='', show_copy_button=False, cancel_callback=None, log_is_file=False, checkbox_msg=None, checkbox_checked=False, action_callback=None, action_label=None, action_icon=None, focus_action=False, show_det=False, show_ok=False, geom_pref=None): ''' A non modal popup that notifies the user that a background task has been completed. This class guarantees that only a single popup is visible at any one time. Other requests are queued and displayed after the user dismisses the current popup. :param callback: A callable that is called with payload if the user asks to proceed. Note that this is always called in the GUI thread. :param cancel_callback: A callable that is called with the payload if the users asks not to proceed. :param payload: Arbitrary object, passed to callback :param html_log: An HTML or plain text log :param log_viewer_title: The title for the log viewer window :param title: The title for this popup :param msg: The msg to display :param det_msg: Detailed message :param log_is_file: If True the html_log parameter is interpreted as the path to a file on disk containing the log encoded with utf-8 :param checkbox_msg: If not None, a checkbox is displayed in the dialog, showing this message. The callback is called with both the payload and the state of the checkbox as arguments. :param checkbox_checked: If True the checkbox is checked by default. :param action_callback: If not None, an extra button is added, which when clicked will cause action_callback to be called instead of callback. action_callback is called in exactly the same way as callback. :param action_label: The text on the action button :param action_icon: The icon for the action button, must be a QIcon object or None :param focus_action: If True, the action button will be focused instead of the Yes button :param show_det: If True, the Detailed message will be shown initially :param show_ok: If True, OK will be shown instead of YES/NO :param geom_pref: String for preference name to preserve dialog box geometry ''' question = Question(payload, callback, cancel_callback, title, msg, html_log, log_viewer_title, log_is_file, det_msg, show_copy_button, checkbox_msg, checkbox_checked, action_callback, action_label, action_icon, focus_action, show_det, show_ok, geom_pref) self.questions.append(question) self.show_question() def show_log(self): if self.questions: q = self.questions[0] log = q.html_log if q.log_is_file: with open(log, 'rb') as f: log = f.read().decode('utf-8') self.log_viewer = ViewLog(q.log_viewer_title, log, parent=self)
class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) forum_label_text = _('Plugin homepage') def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE): SizePersistedDialog.__init__( self, gui, 'Plugin Updater plugin:plugin updater dialog') self.gui = gui self.forum_link = None self.zip_url = None self.model = None self.do_restart = False self._initialize_controls() self._create_context_menu() display_plugins = read_available_plugins() if display_plugins: self.model = DisplayPluginModel(display_plugins) self.proxy_model = DisplayPluginSortFilterModel(self) self.proxy_model.setSourceModel(self.model) self.plugin_view.setModel(self.proxy_model) self.plugin_view.resizeColumnsToContents() self.plugin_view.selectionModel().currentRowChanged.connect( self._plugin_current_changed) self.plugin_view.doubleClicked.connect(self.install_button.click) self.filter_combo.setCurrentIndex(initial_filter) self._select_and_focus_view() else: error_dialog(self.gui, _('Update Check Failed'), _('Unable to reach the plugin index page.'), det_msg=INDEX_URL, show=True) self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def _initialize_controls(self): self.setWindowTitle(_('User plugins')) self.setWindowIcon(QIcon(I('plugins/plugin_updater.png'))) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png', _('User Plugins')) layout.addLayout(title_layout) header_layout = QHBoxLayout() layout.addLayout(header_layout) self.filter_combo = PluginFilterComboBox(self) self.filter_combo.setMinimumContentsLength(20) self.filter_combo.currentIndexChanged[int].connect( self._filter_combo_changed) header_layout.addWidget(QLabel( _('Filter list of plugins') + ':', self)) header_layout.addWidget(self.filter_combo) header_layout.addStretch(10) # filter plugins by name header_layout.addWidget(QLabel(_('Filter by name') + ':', self)) self.filter_by_name_lineedit = QLineEdit(self) self.filter_by_name_lineedit.setText("") self.filter_by_name_lineedit.textChanged.connect( self._filter_name_lineedit_changed) header_layout.addWidget(self.filter_by_name_lineedit) self.plugin_view = QTableView(self) self.plugin_view.horizontalHeader().setStretchLastSection(True) self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection) self.plugin_view.setAlternatingRowColors(True) self.plugin_view.setSortingEnabled(True) self.plugin_view.setIconSize(QSize(28, 28)) layout.addWidget(self.plugin_view) details_layout = QHBoxLayout() layout.addLayout(details_layout) forum_label = self.forum_label = QLabel('') forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) forum_label.linkActivated.connect(self._forum_label_activated) details_layout.addWidget(QLabel(_('Description') + ':', self), 0, Qt.AlignLeft) details_layout.addWidget(forum_label, 1, Qt.AlignRight) self.description = QLabel(self) self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) self.description.setWordWrap(True) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) self.button_box.rejected.connect(self.reject) self.finished.connect(self._finished) self.install_button = self.button_box.addButton( _('&Install'), QDialogButtonBox.AcceptRole) self.install_button.setToolTip(_('Install the selected plugin')) self.install_button.clicked.connect(self._install_clicked) self.install_button.setEnabled(False) self.configure_button = self.button_box.addButton( ' ' + _('&Customize plugin ') + ' ', QDialogButtonBox.ResetRole) self.configure_button.setToolTip( _('Customize the options for this plugin')) self.configure_button.clicked.connect(self._configure_clicked) self.configure_button.setEnabled(False) layout.addWidget(self.button_box) def update_forum_label(self): txt = '' if self.forum_link: txt = '<a href="%s">%s</a>' % (self.forum_link, self.forum_label_text) self.forum_label.setText(txt) def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction( QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I('chapters.png')), _('Version &History'), self) self.history_action.setToolTip( _('Show history of changes to this plugin')) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &Forum Thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&Disable plugin'), self) self.toggle_enabled_action.setToolTip( _('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect( self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip( _('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip( _('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action) def _finished(self, *args): if self.model: update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins) self.gui.recalc_update_label(len(update_plugins)) def _plugin_current_changed(self, current, previous): if current.isValid(): actual_idx = self.proxy_model.mapToSource(current) display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link self.zip_url = display_plugin.zip_url self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled( display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) self.uninstall_action.setEnabled(display_plugin.is_installed()) self.history_action.setEnabled(display_plugin.has_changelog) self.configure_button.setEnabled(display_plugin.is_installed()) self.configure_action.setEnabled(self.configure_button.isEnabled()) self.toggle_enabled_action.setEnabled( display_plugin.is_installed()) self.donate_enabled_action.setEnabled( bool(display_plugin.donation_link)) else: self.description.setText('') self.forum_link = None self.zip_url = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) self.uninstall_action.setEnabled(False) self.history_action.setEnabled(False) self.configure_button.setEnabled(False) self.configure_action.setEnabled(False) self.toggle_enabled_action.setEnabled(False) self.donate_enabled_action.setEnabled(False) self.update_forum_label() def _donate_clicked(self): plugin = self._selected_display_plugin() if plugin and plugin.donation_link: open_url(QUrl(plugin.donation_link)) def _select_and_focus_view(self, change_selection=True): if change_selection and self.plugin_view.model().rowCount() > 0: self.plugin_view.selectRow(0) else: idx = self.plugin_view.selectionModel().currentIndex() self._plugin_current_changed(idx, 0) self.plugin_view.setFocus() def _filter_combo_changed(self, idx): self.filter_by_name_lineedit.setText( "" ) # clear the name filter text when a different group was selected self.proxy_model.set_filter_criteria(idx) if idx == FILTER_NOT_INSTALLED: self.plugin_view.sortByColumn(5, Qt.DescendingOrder) else: self.plugin_view.sortByColumn(0, Qt.AscendingOrder) self._select_and_focus_view() def _filter_name_lineedit_changed(self, text): self.proxy_model.set_filter_text( text) # set the filter text for filterAcceptsRow def _forum_label_activated(self): if self.forum_link: open_url(QUrl(self.forum_link)) def _selected_display_plugin(self): idx = self.plugin_view.selectionModel().currentIndex() actual_idx = self.proxy_model.mapToSource(idx) return self.model.display_plugins[actual_idx.row()] def _uninstall_plugin(self, name_to_remove): if DEBUG: prints('Removing plugin: ', name_to_remove) remove_plugin(name_to_remove) # Make sure that any other plugins that required this plugin # to be uninstalled first have the requirement removed for display_plugin in self.model.display_plugins: # Make sure we update the status and display of the # plugin we just uninstalled if name_to_remove in display_plugin.uninstall_plugins: if DEBUG: prints('Removing uninstall dependency for: ', display_plugin.name) display_plugin.uninstall_plugins.remove(name_to_remove) if display_plugin.name == name_to_remove: if DEBUG: prints('Resetting plugin to uninstalled status: ', display_plugin.name) display_plugin.installed_version = None display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria not in [ FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE ]: self.model.refresh_plugin(display_plugin) def _uninstall_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog( self, _('Are you sure?'), '<p>' + _('Are you sure you want to uninstall the <b>%s</b> plugin?') % display_plugin.name, show_copy_button=False): return self._uninstall_plugin(display_plugin.name) if self.proxy_model.filter_criteria in [ FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE ]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self._select_and_focus_view(change_selection=False) def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog( self, _('Install %s') % display_plugin.name, '<p>' + _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints('Uninstalling plugin: ', ', '.join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) plugin_zip_url = display_plugin.zip_url if DEBUG: prints('Downloading plugin zip attachment: ', plugin_zip_url) self.gui.status_bar.showMessage( _('Downloading plugin zip attachment: %s') % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) do_restart = False try: from calibre.customize.ui import config installed_plugins = frozenset(config['plugins']) try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _('Already exists'), unicode(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) self.gui.status_bar.showMessage( _('Plugin installed: %s') % display_plugin.name) d = info_dialog( self.gui, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format( plugin.name, plugin.type), show_copy_button=False) b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints('ERROR occurred while installing plugin: %s' % display_plugin.name) traceback.print_exc() error_dialog( self.gui, _('Install Plugin Failed'), _('A problem occurred while installing this plugin.' ' This plugin will now be uninstalled.' ' Please post the error message in details below into' ' the forum thread for this plugin and restart Calibre.'), det_msg=traceback.format_exc(), show=True) if DEBUG: prints('Due to error now uninstalling plugin: %s' % display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [ FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE ]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) if do_restart: self.do_restart = True self.accept() def _history_clicked(self): display_plugin = self._selected_display_plugin() text = self._read_version_history_html(display_plugin.forum_link) if text: dlg = VersionHistoryDialog(self, display_plugin.name, text) dlg.exec_() else: return error_dialog( self, _('Version history missing'), _('Unable to find the version history for %s') % display_plugin.name, show=True) def _configure_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.is_customizable(): return info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization') % plugin.name, show=True) from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr( plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin') % plugin.name, show=True) plugin.do_user_config(self.parent()) def _toggle_enabled_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.can_be_disabled: return error_dialog(self, _('Plugin cannot be disabled'), _('The plugin: %s cannot be disabled') % plugin.name, show=True) if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self.model.refresh_plugin(display_plugin) def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) spoiler_nodes = root.xpath( '//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find( 'version history') != -1: div_node = spoiler_node.xpath('div')[0] text = html.tostring(div_node, method='html', encoding=unicode) return re.sub('<div\s.*?>', '<div>', text) except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(spoiler_node)) return None def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile br = browser(user_agent='%s %s' % (__appname__, __version__)) raw = br.open_novisit(plugin_zip_url).read() with PersistentTemporaryFile('.zip') as pt: pt.write(raw) return pt.name
class Preferences(QDialog): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QDialog.__init__(self, gui) self.gui = gui self.must_restart = False self.do_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) geom = gprefs.get('preferences dialog geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.ApplicationModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.l = l = QVBoxLayout(self) self.stack = QStackedWidget(self) self.bb = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Apply | QDialogButtonBox.Discard | QDialogButtonBox.RestoreDefaults) self.bb.button(self.bb.Apply).clicked.connect(self.accept) self.bb.button(self.bb.Discard).clicked.connect(self.reject) self.bb.button(self.bb.RestoreDefaults).setIcon(QIcon(I('clear_left.png'))) self.bb.button(self.bb.RestoreDefaults).clicked.connect(self.restore_defaults) self.wizard_button = self.bb.addButton(_('Run Welcome &wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.wizard_button.setAutoDefault(False) self.bb.rejected.connect(self.reject) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.title_bar = TitleBar(self) for ac, tt in [(self.bb.Apply, _('Save changes')), (self.bb.Discard, _('Cancel and return to overview'))]: self.bb.button(ac).setToolTip(tt) l.addWidget(self.title_bar), l.addWidget(self.stack), l.addWidget(self.bb) if initial_plugin is not None: category, name = initial_plugin[:2] plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) if len(initial_plugin) > 2: w = self.findChild(QWidget, initial_plugin[2]) if w is not None: for c in self.showing_widget.children(): if isinstance(c, QTabWidget): idx = c.indexOf(w) if idx > -1: c.setCurrentIndex(idx) break else: self.hide_plugin() def event(self, ev): if isinstance(ev, QStatusTipEvent): msg = re.sub(r'</?[a-z1-6]+>', ' ', ev.tip()) self.title_bar.show_msg(msg) return QDialog.event(self, ev) def run_wizard(self): self.run_wizard_requested.emit() self.accept() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode_type(buddy.toolTip()).strip() etext = unicode_type(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.showing_widget.restart_now.connect(self.restart_now) self.title_bar.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bb.button(self.bb.Close).setVisible(False) self.wizard_button.setVisible(False) for button in (self.bb.Apply, self.bb.RestoreDefaults, self.bb.Discard): button = self.bb.button(button) button.setVisible(True) self.bb.button(self.bb.Apply).setEnabled(False) self.bb.button(self.bb.Apply).setDefault(False), self.bb.button(self.bb.Apply).setDefault(True) self.bb.button(self.bb.RestoreDefaults).setEnabled(self.showing_widget.supports_restoring_to_defaults) self.bb.button(self.bb.RestoreDefaults).setToolTip( self.showing_widget.restore_defaults_desc if self.showing_widget.supports_restoring_to_defaults else (_('Restoring to defaults not supported for') + ' ' + plugin.gui_name)) self.bb.button(self.bb.RestoreDefaults).setText(_('Restore &defaults')) self.showing_widget.changed_signal.connect(self.changed_signal) def changed_signal(self): b = self.bb.button(self.bb.Apply) b.setEnabled(True) def hide_plugin(self): for sig in 'changed_signal restart_now'.split(): try: getattr(self.showing_widget, sig).disconnect(getattr(self, sig)) except Exception: pass self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.stack.setCurrentIndex(0) self.title_bar.show_plugin() self.setWindowIcon(QIcon(I('config.png'))) for button in (self.bb.Apply, self.bb.RestoreDefaults, self.bb.Discard): button = self.bb.button(button) button.setVisible(False) self.bb.button(self.bb.Close).setVisible(True) self.bb.button(self.bb.Close).setDefault(False), self.bb.button(self.bb.Close).setDefault(True) self.wizard_button.setVisible(True) def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.do_restart = True self.hide_plugin() self.accept() def commit(self, *args): must_restart = self.showing_widget.commit() rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) if do_restart: self.do_restart = True return self.close_after_initial or (must_restart and rc) or do_restart def restore_defaults(self, *args): self.showing_widget.restore_defaults() def on_shutdown(self): gprefs.set('preferences dialog geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() def accept(self): if self.stack.currentIndex() == 0: self.on_shutdown() return QDialog.accept(self) try: close = self.commit() except AbortCommit: return if close: self.on_shutdown() return QDialog.accept(self) self.hide_plugin() def reject(self): if self.stack.currentIndex() == 0 or self.close_after_initial: self.on_shutdown() return QDialog.reject(self) self.hide_plugin()
class FullFetch(QDialog): # {{{ def __init__(self, current_cover=None, parent=None): QDialog.__init__(self, parent) self.current_cover = current_cover self.log = Log() self.book = self.cover_pixmap = None self.setWindowTitle(_('Downloading metadata...')) self.setWindowIcon(QIcon(I('download-metadata.png'))) self.stack = QStackedWidget() self.l = l = QVBoxLayout() self.setLayout(l) l.addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) self.h = h = QHBoxLayout() l.addLayout(h) self.bb.rejected.connect(self.reject) self.bb.accepted.connect(self.accept) self.ok_button = self.bb.button(self.bb.Ok) self.ok_button.setEnabled(False) self.ok_button.clicked.connect(self.ok_clicked) self.prev_button = pb = QPushButton(QIcon(I('back.png')), _('&Back'), self) pb.clicked.connect(self.back_clicked) pb.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.log_button = self.bb.addButton(_('&View log'), self.bb.ActionRole) self.log_button.clicked.connect(self.view_log) self.log_button.setIcon(QIcon(I('debug.png'))) self.prev_button.setVisible(False) h.addWidget(self.prev_button), h.addWidget(self.bb) self.identify_widget = IdentifyWidget(self.log, self) self.identify_widget.rejected.connect(self.reject) self.identify_widget.results_found.connect(self.identify_results_found) self.identify_widget.book_selected.connect(self.book_selected) self.stack.addWidget(self.identify_widget) self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) self.covers_widget.chosen.connect(self.ok_clicked) self.stack.addWidget(self.covers_widget) self.resize(850, 600) geom = gprefs.get('metadata_single_gui_geom', None) if geom is not None and geom: self.restoreGeometry(geom) self.finished.connect(self.cleanup) def view_log(self): self._lv = LogViewer(self.log, self) def book_selected(self, book, caches): self.prev_button.setVisible(True) self.book = book self.stack.setCurrentIndex(1) self.log('\n\n') self.covers_widget.start(book, self.current_cover, self.title, self.authors, caches) self.ok_button.setFocus() def back_clicked(self): self.prev_button.setVisible(False) self.stack.setCurrentIndex(0) self.covers_widget.cancel() self.covers_widget.reset_covers() def accept(self): # Prevent the usual dialog accept mechanisms from working gprefs['metadata_single_gui_geom'] = bytearray(self.saveGeometry()) if DEBUG_DIALOG: if self.stack.currentIndex() == 2: return QDialog.accept(self) else: if self.stack.currentIndex() == 1: return QDialog.accept(self) def reject(self): gprefs['metadata_single_gui_geom'] = bytearray(self.saveGeometry()) self.identify_widget.cancel() self.covers_widget.cancel() return QDialog.reject(self) def cleanup(self): self.covers_widget.cleanup() def identify_results_found(self): self.ok_button.setEnabled(True) def next_clicked(self, *args): gprefs['metadata_single_gui_geom'] = bytearray(self.saveGeometry()) self.identify_widget.get_result() def ok_clicked(self, *args): self.cover_pixmap = self.covers_widget.cover_pixmap() if self.stack.currentIndex() == 0: self.next_clicked() return if DEBUG_DIALOG: if self.cover_pixmap is not None: self.w = QLabel() self.w.setPixmap(self.cover_pixmap) self.stack.addWidget(self.w) self.stack.setCurrentIndex(2) else: QDialog.accept(self) def start(self, title=None, authors=None, identifiers={}): self.title, self.authors = title, authors self.identify_widget.start(title=title, authors=authors, identifiers=identifiers) return self.exec_()
class ProceedQuestion(QWidget): ask_question = pyqtSignal(object, object, object) @pyqtProperty(float) def show_fraction(self): return self._show_fraction @show_fraction.setter def show_fraction(self, val): self._show_fraction = max(0, min(1, float(val))) self.update() def __init__(self, parent): QWidget.__init__(self, parent) self.setVisible(False) parent.installEventFilter(self) self._show_fraction = 0.0 self.show_animation = a = QPropertyAnimation(self, b"show_fraction", self) a.setDuration(1000), a.setEasingCurve(QEasingCurve.OutQuad) a.setStartValue(0.0), a.setEndValue(1.0) a.finished.connect(self.stop_show_animation) self.rendered_pixmap = None self.questions = [] self.icon = ic = Icon(self) self.msg_label = msg = QLabel('some random filler text') msg.setWordWrap(True) self.bb = QDialogButtonBox() self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole) self.log_button.setIcon(QIcon(I('debug.png'))) self.log_button.clicked.connect(self.show_log) self.copy_button = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole) self.copy_button.clicked.connect(self.copy_to_clipboard) self.action_button = self.bb.addButton('', self.bb.ActionRole) self.action_button.clicked.connect(self.action_clicked) self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.det_msg = PlainTextEdit(self) self.det_msg.setReadOnly(True) self.bb.setStandardButtons(self.bb.Yes | self.bb.No | self.bb.Ok) self.bb.button(self.bb.Yes).setDefault(True) self.title_label = title = QLabel('A dummy title') f = title.font() f.setBold(True) title.setFont(f) self.checkbox = QCheckBox('', self) self._l = l = QVBoxLayout(self) self._h = h = QHBoxLayout() self._v = v = QVBoxLayout() v.addWidget(title), v.addWidget(msg) h.addWidget(ic), h.addSpacing(10), h.addLayout(v), l.addLayout(h) l.addSpacing(5) l.addWidget(self.checkbox) l.addWidget(self.det_msg) l.addWidget(self.bb) self.ask_question.connect(self.do_ask_question, type=Qt.QueuedConnection) self.setFocusPolicy(Qt.NoFocus) for child in self.findChildren(QWidget): child.setFocusPolicy(Qt.NoFocus) self.setFocusProxy(self.parent()) self.resize_timer = t = QTimer(self) t.setSingleShot(True), t.setInterval(100), t.timeout.connect( self.parent_resized) def eventFilter(self, obj, ev): if ev.type() == ev.Resize and self.isVisible(): self.resize_timer.start() return False def parent_resized(self): if self.isVisible(): self.do_resize() def copy_to_clipboard(self, *args): QApplication.clipboard().setText( 'calibre, version %s\n%s: %s\n\n%s' % (__version__, unicode_type( self.windowTitle()), unicode_type(self.msg_label.text()), unicode_type(self.det_msg.toPlainText()))) self.copy_button.setText(_('Copied')) def action_clicked(self): if self.questions: q = self.questions[0] self.questions[0] = q._replace(callback=q.action_callback) self.accept() def accept(self): if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(callback, payload, cb) self.hide() def reject(self): if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] cb = None if self.checkbox.isVisible(): cb = bool(self.checkbox.isChecked()) self.ask_question.emit(cancel_callback, payload, cb) self.hide() def do_ask_question(self, callback, payload, checkbox_checked): if callable(callback): args = [payload] if checkbox_checked is not None: args.append(checkbox_checked) callback(*args) self.show_question() def toggle_det_msg(self, *args): vis = unicode_type(self.det_msg_toggle.text()) == self.hide_det_msg self.det_msg_toggle.setText( self.show_det_msg if vis else self.hide_det_msg) self.det_msg.setVisible(not vis) self.do_resize() def do_resize(self): sz = self.sizeHint() sz.setWidth(min(self.parent().width(), sz.width())) sb = self.parent().statusBar().height() + 10 sz.setHeight(min(self.parent().height() - sb, sz.height())) self.resize(sz) self.position_widget() def show_question(self): if not self.questions: return if not self.isVisible(): question = self.questions[0] self.msg_label.setText(question.msg) self.icon.set_icon(question.icon) self.title_label.setText(question.title) self.log_button.setVisible(bool(question.html_log)) self.copy_button.setText(_('&Copy to clipboard')) if question.action_callback is not None: self.action_button.setText(question.action_label or '') self.action_button.setIcon(QIcon( ) if question.action_icon is None else question.action_icon) # Force the button box to relayout its buttons, as button text # might have changed self.bb.setOrientation(Qt.Vertical), self.bb.setOrientation( Qt.Horizontal) self.det_msg.setPlainText(question.det_msg or '') self.det_msg.setVisible(False) self.det_msg_toggle.setVisible(bool(question.det_msg)) self.det_msg_toggle.setText(self.show_det_msg) self.checkbox.setVisible(question.checkbox_msg is not None) if question.checkbox_msg is not None: self.checkbox.setText(question.checkbox_msg) self.checkbox.setChecked(question.checkbox_checked) self.bb.button(self.bb.Ok).setVisible(question.show_ok) self.bb.button(self.bb.Yes).setVisible(not question.show_ok) self.bb.button(self.bb.No).setVisible(not question.show_ok) self.copy_button.setVisible(bool(question.show_copy_button)) self.action_button.setVisible(question.action_callback is not None) self.toggle_det_msg() if question.show_det else self.do_resize() self.show_widget() button = self.action_button if question.focus_action and question.action_callback is not None else \ (self.bb.button(self.bb.Ok) if question.show_ok else self.bb.button(self.bb.Yes)) button.setDefault(True) self.raise_() self.start_show_animation() def start_show_animation(self): if self.rendered_pixmap is not None: return dpr = getattr(self, 'devicePixelRatioF', self.devicePixelRatio)() p = QImage(dpr * self.size(), QImage.Format_ARGB32_Premultiplied) p.setDevicePixelRatio(dpr) # For some reason, Qt scrolls the book view when rendering this widget, # for the very first time, so manually preserve its position pr = getattr(self.parent(), 'library_view', None) if not hasattr(pr, 'preserve_state'): self.render(p) else: with pr.preserve_state(): self.render(p) self.rendered_pixmap = QPixmap.fromImage(p) self.original_visibility = v = [] for child in self.findChildren(QWidget): if child.isVisible(): child.setVisible(False) v.append(child) self.show_animation.start() def stop_show_animation(self): self.rendered_pixmap = None [c.setVisible(True) for c in getattr(self, 'original_visibility', ())] self.update() for child in self.findChildren(QWidget): child.update() if hasattr(child, 'viewport'): child.viewport().update() def position_widget(self): geom = self.parent().geometry() x = geom.width() - self.width() - 5 sb = self.parent().statusBar() if sb is None: y = geom.height() - self.height() else: y = sb.geometry().top() - self.height() self.move(x, y) def show_widget(self): self.show() self.position_widget() def dummy_question(self, action_label=None): self( lambda *args: args, (), 'dummy log', 'Log Viewer', 'A Dummy Popup', 'This is a dummy popup to easily test things, with a long line of text that should wrap. ' 'This is a dummy popup to easily test things, with a long line of text that should wrap', checkbox_msg='A dummy checkbox', action_callback=lambda *args: args, action_label=action_label or 'An action') def __call__(self, callback, payload, html_log, log_viewer_title, title, msg, det_msg='', show_copy_button=False, cancel_callback=None, log_is_file=False, checkbox_msg=None, checkbox_checked=False, action_callback=None, action_label=None, action_icon=None, focus_action=False, show_det=False, show_ok=False, icon=None, log_viewer_unique_name=None, **kw): ''' A non modal popup that notifies the user that a background task has been completed. This class guarantees that only a single popup is visible at any one time. Other requests are queued and displayed after the user dismisses the current popup. :param callback: A callable that is called with payload if the user asks to proceed. Note that this is always called in the GUI thread. :param cancel_callback: A callable that is called with the payload if the users asks not to proceed. :param payload: Arbitrary object, passed to callback :param html_log: An HTML or plain text log :param log_viewer_title: The title for the log viewer window :param title: The title for this popup :param msg: The msg to display :param det_msg: Detailed message :param log_is_file: If True the html_log parameter is interpreted as the path to a file on disk containing the log encoded with utf-8 :param checkbox_msg: If not None, a checkbox is displayed in the dialog, showing this message. The callback is called with both the payload and the state of the checkbox as arguments. :param checkbox_checked: If True the checkbox is checked by default. :param action_callback: If not None, an extra button is added, which when clicked will cause action_callback to be called instead of callback. action_callback is called in exactly the same way as callback. :param action_label: The text on the action button :param action_icon: The icon for the action button, must be a QIcon object or None :param focus_action: If True, the action button will be focused instead of the Yes button :param show_det: If True, the Detailed message will be shown initially :param show_ok: If True, OK will be shown instead of YES/NO :param icon: The icon to be used for this popop (defaults to question mark). Can be either a QIcon or a string to be used with I() :log_viewer_unique_name: If set, ViewLog will remember/reuse its size for this name in calibre.gui2.gprefs ''' question = Question(payload, callback, cancel_callback, title, msg, html_log, log_viewer_title, log_is_file, det_msg, show_copy_button, checkbox_msg, checkbox_checked, action_callback, action_label, action_icon, focus_action, show_det, show_ok, icon, log_viewer_unique_name) self.questions.append(question) self.show_question() def show_log(self): if self.questions: q = self.questions[0] log = q.html_log if q.log_is_file: with open(log, 'rb') as f: log = f.read().decode('utf-8') self.log_viewer = ViewLog(q.log_viewer_title, log, parent=self, unique_name=q.log_viewer_unique_name) def paintEvent(self, ev): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) try: if self.rendered_pixmap is None: self.paint_background(painter) else: self.animated_paint(painter) finally: painter.end() def animated_paint(self, painter): top = (1 - self._show_fraction) * self.height() painter.drawPixmap(0, top, self.rendered_pixmap) def paint_background(self, painter): br = 12 # border_radius bw = 1 # border_width pal = self.palette() c = pal.color(pal.Window) c.setAlphaF(0.9) p = QPainterPath() p.addRoundedRect(QRectF(self.rect()), br, br) painter.fillPath(p, c) p.addRoundedRect( QRectF(self.rect()).adjusted(bw, bw, -bw, -bw), br, br) painter.fillPath(p, pal.color(pal.WindowText))
class JobError(QDialog): # {{{ WIDTH = 600 do_pop = pyqtSignal() def __init__(self, parent): QDialog.__init__(self, parent) self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False) self.queue = [] self.do_pop.connect(self.pop, type=Qt.ConnectionType.QueuedConnection) self._layout = l = QGridLayout() self.setLayout(l) self.icon = QIcon(I('dialog_error.png')) self.setWindowIcon(self.icon) self.icon_widget = Icon(self) self.icon_widget.set_icon(self.icon) self.msg_label = QLabel('<p> ') self.msg_label.setStyleSheet('QLabel { margin-top: 1ex; }') self.msg_label.setWordWrap(True) self.msg_label.setTextFormat(Qt.TextFormat.RichText) self.det_msg = QPlainTextEdit(self) self.det_msg.setVisible(False) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, parent=self) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.ctc_button = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole) self.ctc_button.clicked.connect(self.copy_to_clipboard) self.retry_button = self.bb.addButton(_('&Retry'), self.bb.ActionRole) self.retry_button.clicked.connect(self.retry) self.retry_func = None self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.suppress = QCheckBox(self) l.addWidget(self.icon_widget, 0, 0, 1, 1) l.addWidget(self.msg_label, 0, 1, 1, 1) l.addWidget(self.det_msg, 1, 0, 1, 2) l.addWidget(self.suppress, 2, 0, 1, 2, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom) l.addWidget(self.bb, 3, 0, 1, 2, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignBottom) l.setColumnStretch(1, 100) self.setModal(False) self.suppress.setVisible(False) self.do_resize() def retry(self): if self.retry_func is not None: self.accept() self.retry_func() def update_suppress_state(self): self.suppress.setText( ngettext('Hide the remaining error message', 'Hide the {} remaining error messages', len(self.queue)).format(len(self.queue))) self.suppress.setVisible(len(self.queue) > 3) self.do_resize() def copy_to_clipboard(self, *args): d = QTextDocument() d.setHtml(self.msg_label.text()) QApplication.clipboard().setText( 'calibre, version %s (%s, embedded-python: %s)\n%s: %s\n\n%s' % (__version__, sys.platform, isfrozen, unicode_type(self.windowTitle()), unicode_type( d.toPlainText()), unicode_type(self.det_msg.toPlainText()))) if hasattr(self, 'ctc_button'): self.ctc_button.setText(_('Copied')) def toggle_det_msg(self, *args): vis = unicode_type(self.det_msg_toggle.text()) == self.hide_det_msg self.det_msg_toggle.setText( self.show_det_msg if vis else self.hide_det_msg) self.det_msg.setVisible(not vis) self.do_resize() def do_resize(self): h = self.sizeHint().height() self.setMinimumHeight(0) # Needed as this gets set if det_msg is shown # Needed otherwise re-showing the box after showing det_msg causes the box # to not reduce in height self.setMaximumHeight(h) self.resize(QSize(self.WIDTH, h)) def showEvent(self, ev): ret = QDialog.showEvent(self, ev) self.bb.button(self.bb.Close).setFocus(Qt.FocusReason.OtherFocusReason) return ret def show_error(self, title, msg, det_msg='', retry_func=None): self.queue.append((title, msg, det_msg, retry_func)) self.update_suppress_state() self.pop() def pop(self): if not self.queue or self.isVisible(): return title, msg, det_msg, retry_func = self.queue.pop(0) self.setWindowTitle(title) self.msg_label.setText(msg) self.det_msg.setPlainText(det_msg) self.det_msg.setVisible(False) self.det_msg_toggle.setText(self.show_det_msg) self.det_msg_toggle.setVisible(True) self.suppress.setChecked(False) self.update_suppress_state() if not det_msg: self.det_msg_toggle.setVisible(False) self.retry_button.setVisible(retry_func is not None) self.retry_func = retry_func self.do_resize() self.show() def done(self, r): if self.suppress.isChecked(): self.queue = [] QDialog.done(self, r) self.do_pop.emit()
class Preferences(QMainWindow): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QMainWindow.__init__(self, gui) self.gui = gui self.must_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) self.esc_action = QAction(self) self.addAction(self.esc_action) self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape)) self.esc_action.triggered.connect(self.esc) geom = gprefs.get('preferences_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.status_bar = StatusBar(self) self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) self.cw = QWidget(self) self.cw.setLayout(QVBoxLayout()) self.cw.layout().addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Close) self.wizard_button = self.bb.addButton(_('Run welcome wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.cw.layout().addWidget(self.bb) self.bb.button(self.bb.Close).setDefault(True) self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) self.setCentralWidget(self.cw) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.apply_action = self.bar.addAction(QIcon(I('ok.png')), _('&Apply'), self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) self.bar_title = BarTitle(self.bar) self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), ('cancel', _('Cancel and return to overview'))]: ac = getattr(self, ac+'_action') ac.setToolTip(tt) ac.setWhatsThis(tt) ac.setStatusTip(tt) for ch in self.bar.children(): if isinstance(ch, QToolButton): ch.setCursor(Qt.PointingHandCursor) ch.setAutoRaise(True) self.stack.setCurrentIndex(0) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) def run_wizard(self): self.close() self.run_wizard_requested.emit() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode(buddy.toolTip()).strip() etext = unicode(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.apply_action.setEnabled(False) self.showing_widget.changed_signal.connect(lambda : self.apply_action.setEnabled(True)) self.showing_widget.restart_now.connect(self.restart_now) self.restore_action.setEnabled(self.showing_widget.supports_restoring_to_defaults) tt = self.showing_widget.restore_defaults_desc if not self.restore_action.isEnabled(): tt = _('Restoring to defaults not supported for') + ' ' + \ plugin.gui_name self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setStatusTip(tt) self.bar_title.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bar.setVisible(True) self.bb.setVisible(False) def hide_plugin(self): self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.bar.setVisible(False) self.stack.setCurrentIndex(0) self.setWindowIcon(QIcon(I('config.png'))) self.bb.setVisible(True) def esc(self, *args): if self.stack.currentIndex() == 1: self.cancel() elif self.stack.currentIndex() == 0: self.close() def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.hide_plugin() self.close() self.gui.quit(restart=True) def commit(self, *args): try: must_restart = self.showing_widget.commit() except AbortCommit: return rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) self.hide_plugin() if self.close_after_initial or (must_restart and rc) or do_restart: self.close() if do_restart: self.gui.quit(restart=True) def cancel(self, *args): if self.close_after_initial: self.close() else: self.hide_plugin() def restore_defaults(self, *args): self.showing_widget.restore_defaults() def closeEvent(self, *args): gprefs.set('preferences_window_geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() return QMainWindow.closeEvent(self, *args)
class DemoDialog(QDialog): def __init__(self, gui, icon, do_user_config): QDialog.__init__(self, gui) self.gui = gui self.do_user_config = do_user_config self.db = gui.current_db self.l = QVBoxLayout() self.setLayout(self.l) self.setWindowTitle('Wiki Reader') self.setWindowIcon(icon) self.helpl = QLabel( 'Enter the URL of a wikipedia article below. ' 'It will be downloaded and converted into an ebook.') self.helpl.setWordWrap(True) self.l.addWidget(self.helpl) self.w = QWidget(self) self.sa = QScrollArea(self) self.l.addWidget(self.sa) self.w.l = QVBoxLayout() self.w.setLayout(self.w.l) self.sa.setWidget(self.w) self.sa.setWidgetResizable(True) self.title = Title(self) self.w.l.addWidget(self.title) self.urls = [URL(self)] self.w.l.addWidget(self.urls[0]) self.add_more_button = QPushButton( QIcon(I('plus.png')), 'Add another URL') self.l.addWidget(self.add_more_button) self.bb = QDialogButtonBox(self) self.bb.setStandardButtons(self.bb.Ok | self.bb.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.l.addWidget(self.bb) self.book_button = b = self.bb.addButton( 'Convert a Wiki&Book', self.bb.ActionRole) b.clicked.connect(self.wiki_book) self.add_more_button.clicked.connect(self.add_more) self.finished.connect(self.download) self.setMinimumWidth(500) self.resize(self.sizeHint()) self.single_url = None def wiki_book(self): d = QDialog(self) d.l = l = QFormLayout(d) l.setFieldGrowthPolicy(l.ExpandingFieldsGrow) d.setWindowTitle('Enter WikiBook URL') d.la = la = QLabel( '<p>You can convert a pre-made WikiBook into a book here. ' 'Simply enter the URL to the WikiBook page. For a list of ' 'WikiBooks, see ' '<a href="https://en.wikipedia.org/wiki/Special:PrefixIndex/Book:' '">here</a>.' ) la.setMinimumWidth(400) la.setWordWrap(True) la.setOpenExternalLinks(True) l.addRow(la) d.le = le = QLineEdit(self) l.addRow('WikiBook &URL:', le) le.setText('https://') le.selectAll() d.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, d) l.addRow(bb) bb.accepted.connect(d.accept), bb.rejected.connect(d.reject) if d.exec_() == d.Accepted: self.single_url = le.text() self.accept() def do_resize(self): a = self.sizeHint() b = self.w.sizeHint() h = min(400, b.height()) self.resize(QSize(a.width(), h + 200)) def scroll_to_bottom(self): v = self.sa.verticalScrollBar() v.setValue(v.maximum()) def add_more(self): url = URL(self) self.urls.append(url) self.w.l.addWidget(url) QTimer.singleShot(0, self.do_resize) QTimer.singleShot(10, self.scroll_to_bottom) def about(self): # Get the about text from a file inside the plugin zip file # The get_resources function is a builtin function defined for all your # plugin code. It loads files from the plugin zip file. It returns # the bytes from the specified file. # # Note that if you are loading more than one file, for performance, you # should pass a list of names to get_resources. In this case, # get_resources will return a dictionary mapping names to bytes. Names # that are not found in the zip file will not be in the returned # dictionary. text = get_resources('about.txt') QMessageBox.about(self, 'About the Wiki Reader', text.decode('utf-8')) # def config(self): # self.do_user_config(parent=self) # Apply the changes # self.label.setText(prefs['hello_world_msg']) def download(self, retcode): if retcode != self.Accepted: return if self.single_url is None: urls = [x.url for x in self.urls] urls = [x.strip() for x in urls if x.strip()] urls = [ ('http://' + x) if not urlparse(x).scheme else x for x in urls] else: urls = self.single_url self.single_url = None args, fmt, temp_files = get_recipe(urls, self.title.title) job = self.gui.job_manager.run_job( Dispatcher(self.fetched), 'gui_convert', args=args, description='Fetch article from Wikipedia') job.extra_conversion_args = (temp_files, fmt) # don't prompt if all OK # if isinstance(urls, list): # info_dialog( # self, 'Downloading', # 'Downloading %d article(s) from Wikipedia. When the download' # ' completes the book will be added to your calibre library.' # % len(urls), show=True, show_copy_button=False) # else: # info_dialog( # self, 'Downloading', # 'Downloading book from Wikipedia. When the download' # ' completes the book will be added to your calibre library.', # show=True, show_copy_button=False) def fetched(self, job): if job.failed: return self.gui.job_exception(job) temp_files, fmt = job.extra_conversion_args fname = temp_files[0].name self.gui.iactions['Add Books']._add_books([fname], False) for f in temp_files[1:]: try: os.remove(f.name) except: pass
def exec_(self): dialog = QDialog(self.parent) textEdit = QTextEdit(dialog) buttonBox = QDialogButtonBox(dialog) layout = QVBoxLayout(dialog) labels = [] html = '' width = 400 height = 25 def triggeredCredit(): if textEdit.isVisible(): for i in range(2 if self.logo else 1, len(labels)): labels[i].show() textEdit.hide() else: for i in range(2 if self.logo else 1, len(labels)): labels[i].hide() textEdit.show() def triggeredClose(arg): dialog.close() dialog.setWindowTitle('About {}'.format(self.programName)) for key in self.__labelsKeys: value = self.__labels.get(key) if value: label = QLabel(dialog) label.setAlignment(Qt.AlignCenter) if key == 'website' or key == 'license': label.setText('<a href="{}">{}</a>'.format( value['url'], value['label'] if value['label'] else value['url'])) label.setTextFormat(Qt.RichText) label.setTextInteractionFlags(Qt.TextBrowserInteraction) label.setOpenExternalLinks(True) elif key == 'logo': image = QPixmap(self.logo) label.setPixmap(image.scaled(128, 128)) else: label.setText(value) labels.append(label) layout.addWidget(labels[len(labels) - 1]) for key in self.__creditsKeys: value = self.__credits.get(key) if len(value.get('contributors')) > 0: html += '<p><strong>{}</strong><br />{}</p>'.format( value.get('label'), '<br />'.join(value.get('contributors'))) if html: textEdit.setHtml('<center>{}</center>'.format(html)) layout.addWidget(textEdit) buttonBox.addButton( 'Credits', QDialogButtonBox.YesRole).clicked.connect(triggeredCredit) textEdit.close() textEdit.setReadOnly(True) buttonBox.addButton( 'Close', QDialogButtonBox.NoRole).clicked.connect(triggeredClose) layout.addWidget(buttonBox) dialog.setLayout(layout) height *= len(dialog.children()) if self.logo: height += 128 dialog.setFixedSize(width, height) return dialog.exec_()
class FontFamilyDialog(QDialog): def __init__(self, current_family, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(_('Choose font family')) self.setWindowIcon(QIcon(I('font.png'))) from calibre.utils.fonts.scanner import font_scanner self.font_scanner = font_scanner self.m = QStringListModel(self) self.build_font_list() self.l = l = QGridLayout() self.setLayout(l) self.view = FontsView(self) self.view.setModel(self.m) self.view.setCurrentIndex(self.m.index(0)) if current_family: for i, val in enumerate(self.families): if icu_lower(val) == icu_lower(current_family): self.view.setCurrentIndex(self.m.index(i)) break self.view.doubleClicked.connect(self.accept, type=Qt.QueuedConnection) self.view.changed.connect(self.current_changed, type=Qt.QueuedConnection) self.faces = Typefaces(self) self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.add_fonts_button = afb = self.bb.addButton(_('Add &fonts'), self.bb.ActionRole) afb.setIcon(QIcon(I('plus.png'))) afb.clicked.connect(self.add_fonts) self.ml = QLabel(_('Choose a font family from the list below:')) self.search = QLineEdit(self) self.search.setPlaceholderText(_('Search')) self.search.returnPressed.connect(self.find) self.nb = QToolButton(self) self.nb.setIcon(QIcon(I('arrow-down.png'))) self.nb.setToolTip(_('Find next')) self.pb = QToolButton(self) self.pb.setIcon(QIcon(I('arrow-up.png'))) self.pb.setToolTip(_('Find previous')) self.nb.clicked.connect(self.find_next) self.pb.clicked.connect(self.find_previous) l.addWidget(self.ml, 0, 0, 1, 4) l.addWidget(self.search, 1, 0, 1, 1) l.addWidget(self.nb, 1, 1, 1, 1) l.addWidget(self.pb, 1, 2, 1, 1) l.addWidget(self.view, 2, 0, 1, 3) l.addWidget(self.faces, 1, 3, 2, 1) l.addWidget(self.bb, 3, 0, 1, 4) l.setAlignment(self.faces, Qt.AlignTop) self.resize(800, 600) def set_current(self, i): self.view.setCurrentIndex(self.m.index(i)) def keyPressEvent(self, e): if e.key() == Qt.Key_Return: return return QDialog.keyPressEvent(self, e) def find(self, backwards=False): i = self.view.currentIndex().row() if i < 0: i = 0 q = icu_lower(unicode(self.search.text())).strip() if not q: return r = (xrange(i-1, -1, -1) if backwards else xrange(i+1, len(self.families))) for j in r: f = self.families[j] if q in icu_lower(f): self.set_current(j) return def find_next(self): self.find() def find_previous(self): self.find(backwards=True) def build_font_list(self): try: self.families = list(self.font_scanner.find_font_families()) except: self.families = [] print ('WARNING: Could not load fonts') import traceback traceback.print_exc() self.families.insert(0, _('None')) self.m.setStringList(self.families) def add_fonts(self): families = add_fonts(self) if not families: return self.font_scanner.do_scan() self.m.beginResetModel() self.build_font_list() self.m.endResetModel() self.view.setCurrentIndex(self.m.index(0)) if families: for i, val in enumerate(self.families): if icu_lower(val) == icu_lower(families[0]): self.view.setCurrentIndex(self.m.index(i)) break info_dialog(self, _('Added fonts'), _('Added font families: %s')%( ', '.join(families)), show=True) @property def font_family(self): idx = self.view.currentIndex().row() if idx == 0: return None return self.families[idx] def current_changed(self): fam = self.font_family self.faces.show_family(fam, self.font_scanner.fonts_for_family(fam) if fam else None)
class FindAnnotationsDialog(SizePersistedDialog, Logger): GENERIC_STYLE = 'Any style' GENERIC_READER = 'Any reader' def __init__(self, opts): self.matched_ids = set() self.opts = opts self.prefs = opts.prefs super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog') self.setWindowTitle('Find Annotations') self.setWindowIcon(self.opts.icon) self.l = QVBoxLayout(self) self.setLayout(self.l) self.search_criteria_gb = QGroupBox(self) self.search_criteria_gb.setTitle("Search criteria") self.scgl = QGridLayout(self.search_criteria_gb) self.l.addWidget(self.search_criteria_gb) # addWidget(widget, row, col, rowspan, colspan) row = 0 # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~ self.reader_label = QLabel('Reader') self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.reader_label, row, 0, 1, 1) self.find_annotations_reader_comboBox = QComboBox() self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox') self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for') self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER) racs = ReaderApp.get_reader_app_classes() for ra in sorted(racs.keys()): self.find_annotations_reader_comboBox.addItem(ra) self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~ self.style_label = QLabel('Style') self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.style_label, row, 0, 1, 1) self.find_annotations_color_comboBox = QComboBox() self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox') self.find_annotations_color_comboBox.setToolTip('Annotation style to search for') self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE) all_colors = COLOR_MAP.keys() all_colors.remove('Default') for color in sorted(all_colors): self.find_annotations_color_comboBox.addItem(color) self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4) row += 1 # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~ self.text_label = QLabel('Text') self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.text_label, row, 0, 1, 1) self.find_annotations_text_lineEdit = MyLineEdit() self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit') self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3) self.reset_text_tb = QToolButton() self.reset_text_tb.setObjectName('reset_text_tb') self.reset_text_tb.setToolTip('Clear search criteria') self.reset_text_tb.setIcon(QIcon(I('trash.png'))) self.reset_text_tb.clicked.connect(self.clear_text_field) self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~ self.note_label = QLabel('Note') self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.note_label, row, 0, 1, 1) self.find_annotations_note_lineEdit = MyLineEdit() self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit') self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3) self.reset_note_tb = QToolButton() self.reset_note_tb.setObjectName('reset_note_tb') self.reset_note_tb.setToolTip('Clear search criteria') self.reset_note_tb.setIcon(QIcon(I('trash.png'))) self.reset_note_tb.clicked.connect(self.clear_note_field) self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create the Date range controls ~~~~~~~~ self.date_range_label = QLabel('Date range') self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter) self.scgl.addWidget(self.date_range_label, row, 0, 1, 1) # Date 'From' self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit') #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1) self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date) self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1) self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1) # Date 'To' self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today()) self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit') #self.find_annotations_date_to_dateEdit.current_val = datetime.today() self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date) self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1) self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1) row += 1 # ~~~~~~~~ Create a horizontal line ~~~~~~~~ self.hl = QFrame(self) self.hl.setGeometry(QRect(0, 0, 1, 3)) self.hl.setFrameShape(QFrame.HLine) self.hl.setFrameShadow(QFrame.Raised) self.scgl.addWidget(self.hl, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the results label field ~~~~~~~~ self.result_label = QLabel('<p style="color:red">scanning…</p>') self.result_label.setAlignment(Qt.AlignCenter) self.result_label.setWordWrap(False) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth()) self.result_label.setSizePolicy(sizePolicy) self.result_label.setMinimumSize(QtCore.QSize(250, 0)) self.scgl.addWidget(self.result_label, row, 0, 1, 5) row += 1 # ~~~~~~~~ Create the ButtonBox ~~~~~~~~ self.dialogButtonBox = QDialogButtonBox(self) self.dialogButtonBox.setOrientation(Qt.Horizontal) if False: self.update_button = QPushButton('Update results') self.update_button.setDefault(True) self.update_button.setVisible(False) self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole) self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel) self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok) self.find_button.setText('Find Matching Books') self.l.addWidget(self.dialogButtonBox) self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked) # ~~~~~~~~ Add a spacer ~~~~~~~~ self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.l.addItem(self.spacerItem) # ~~~~~~~~ Restore previously saved settings ~~~~~~~~ self.restore_settings() # ~~~~~~~~ Declare sizing ~~~~~~~~ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) self.resize_dialog() # ~~~~~~~~ Connect all signals ~~~~~~~~ self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader')) self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color')) self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text')) self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note')) # self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed) # self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed) self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed) # Date range signals connected in inventory_available() # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~ #field = self.prefs.get('cfg_annotations_destination_field', None) field = get_cc_mapping('annotations', 'field', None) self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True) self.annotated_books_scanner.signal.connect(self.inventory_available) QTimer.singleShot(1, self.start_inventory_scan) def clear_note_field(self): if str(self.find_annotations_note_lineEdit.text()) > '': self.find_annotations_note_lineEdit.setText('') self.update_results('clear_note_field') def clear_text_field(self): if str(self.find_annotations_text_lineEdit.text()) > '': self.find_annotations_text_lineEdit.setText('') self.update_results('clear_text_field') def find_annotations_dialog_clicked(self, button): if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole: self.save_settings() self.accept() elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def inventory_available(self): ''' Update the Date range widgets with the rounded oldest, newest dates Don't connect date signals until date range available ''' self._log_location() # Reset the date range based on available annotations oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation)) oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0)) newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation)) newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59)) # Set 'From' date limits to inventory values self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_from_dateEdit.current_val = oldest self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day) # Set 'To' date limits to inventory values self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day) self.find_annotations_date_to_dateEdit.current_val = newest_day self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day) # Connect the signals for date range changes self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date')) self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date')) self.update_results('inventory_available') def restore_settings(self): self.blockSignals(True) ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER) ra_index = self.find_annotations_reader_comboBox.findText(ra) self.find_annotations_reader_comboBox.setCurrentIndex(ra_index) color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE) color_index = self.find_annotations_color_comboBox.findText(color) self.find_annotations_color_comboBox.setCurrentIndex(color_index) text = self.prefs.get('find_annotations_text_lineEdit', '') self.find_annotations_text_lineEdit.setText(text) note = self.prefs.get('find_annotations_note_lineEdit', '') self.find_annotations_note_lineEdit.setText(note) if False: from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1)) self.find_annotations_date_from_dateEdit.current_val = from_date to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today()) self.find_annotations_date_to_dateEdit.current_val = to_date self.blockSignals(False) def return_pressed(self): self.update_results("return_pressed") def save_settings(self): ra = str(self.find_annotations_reader_comboBox.currentText()) self.prefs.set('find_annotations_reader_comboBox', ra) color = str(self.find_annotations_color_comboBox.currentText()) self.prefs.set('find_annotations_color_comboBox', color) text = str(self.find_annotations_text_lineEdit.text()) self.prefs.set('find_annotations_text_lineEdit', text) note = str(self.find_annotations_note_lineEdit.text()) self.prefs.set('find_annotations_note_lineEdit', note) if False: from_date = self.find_annotations_date_from_dateEdit.current_val self.prefs.set('find_annotations_date_from_dateEdit', from_date) to_date = self.find_annotations_date_to_dateEdit.current_val self.prefs.set('find_annotations_date_to_dateEdit', to_date) def start_inventory_scan(self): self._log_location() self.annotated_books_scanner.start() def update_results(self, trigger): #self._log_location(trigger) reader_to_match = str(self.find_annotations_reader_comboBox.currentText()) color_to_match = str(self.find_annotations_color_comboBox.currentText()) text_to_match = str(self.find_annotations_text_lineEdit.text()) note_to_match = str(self.find_annotations_note_lineEdit.text()) from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t() to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t() annotation_map = self.annotated_books_scanner.annotation_map #field = self.prefs.get("cfg_annotations_destination_field", None) field = get_cc_mapping('annotations', 'field', None) db = self.opts.gui.current_db matched_titles = [] self.matched_ids = set() for cid in annotation_map: mi = db.get_metadata(cid, index_is_id=True) soup = None if field == 'Comments': if mi.comments: soup = BeautifulSoup(mi.comments) else: if mi.get_user_metadata(field, False)['#value#'] is not None: soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#']) if soup: uas = soup.findAll('div', 'annotation') for ua in uas: # Are we already logged? if cid in self.matched_ids: continue # Check reader if reader_to_match != self.GENERIC_READER: this_reader = ua['reader'] if this_reader != reader_to_match: continue # Check color if color_to_match != self.GENERIC_STYLE: this_color = ua.find('table')['color'] if this_color != color_to_match: continue # Check date range, allow for mangled timestamp try: timestamp = float(ua.find('td', 'timestamp')['uts']) if timestamp < from_date or timestamp > to_date: continue except: continue highlight_text = '' try: pels = ua.findAll('p', 'highlight') for pel in pels: highlight_text += pel.string + '\n' except: pass if text_to_match > '': if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE): continue note_text = '' try: nels = ua.findAll('p', 'note') for nel in nels: note_text += nel.string + '\n' except: pass if note_to_match > '': if not re.search(note_to_match, note_text, flags=re.IGNORECASE): continue # If we made it this far, add the id to matched_ids self.matched_ids.add(cid) matched_titles.append(mi.title) # Update the results box matched_titles.sort() if len(annotation_map): if len(matched_titles): first_match = ("<i>%s</i>" % matched_titles[0]) if len(matched_titles) == 1: results = first_match else: results = first_match + (" and %d more." % (len(matched_titles) - 1)) self.result_label.setText('<p style="color:blue">{0}</p>'.format(results)) else: self.result_label.setText('<p style="color:red">no matches</p>') else: self.result_label.setText('<p style="color:red">no annotated books in library</p>') self.resize_dialog()
class CustomColumnWizard(SizePersistedDialog, Logger): STEP_ONE = _("Name your '{0}' column:") YELLOW_BG = '<font style="background:#FDFF99">{0}</font>' def __init__(self, parent, column_type, profile, verbose=True): super(CustomColumnWizard, self).__init__(parent, 'annotations plugin:custom column wizard') self.column_type = column_type self.db = parent.gui.current_db self.gui = parent.gui self.modified_column = None self.previous_name = None self.profile = profile self.verbose = verbose self._log_location() self.setWindowTitle(_('Custom column wizard')) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, '../images/wizard.png', _('Custom column wizard'), has_help=False) layout.addLayout(title_layout) grid_layout = QGridLayout() layout.addLayout(grid_layout) self.calibre_destination_le = QLineEdit() grid_layout.addWidget(self.calibre_destination_le, 3, 1) self.step_1 = QLabel("1. Step 1", self) grid_layout.addWidget(self.step_1, 2, 1) self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) layout.addWidget(self.bb) # Add the Accept button self.accept_button = self.bb.addButton('Button', QDialogButtonBox.AcceptRole) self.accept_button.setDefault(True) # Hook the QLineEdit box self.calibre_destination_le.textChanged.connect( self.validate_destination) self.populate_editor() self.highlight_step(1) # Hook the button events self.bb.clicked.connect(self.dispatch_button_click) def accept(self): self._log_location() super(CustomColumnWizard, self).accept() def close(self): self._log_location() super(CustomColumnWizard, self).close() def custom_column_add(self, requested_name, profile): ''' Add the requested custom column with profile ''' self._log_location(requested_name) self._log(profile) self.db.create_custom_column(profile['label'], requested_name, profile['datatype'], profile['is_multiple'], display=profile['display']) self.modified_column = { 'destination': requested_name, 'label': "#%s" % profile['label'], 'previous': self.previous_name, 'source': profile['source'] } def custom_column_rename(self, requested_name, profile): ''' The name already exists for label, update it ''' self._log_location(requested_name) self._log(profile) # Find the existing for cf in self.db.custom_field_keys(): #self._log(self.db.metadata_for_field(cf)) mi = self.db.metadata_for_field(cf) if mi['label'] == profile['label']: self.db.set_custom_column_metadata(mi['colnum'], name=requested_name, label=mi['label'], display=mi['display']) self.modified_column = { 'destination': requested_name, 'label': "#%s" % profile['label'], 'previous': self.previous_name, 'source': profile['source'] } break def dispatch_button_click(self, button): ''' BUTTON_ROLES = ['AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole'] ''' self._log_location() if self.bb.buttonRole(button) == QDialogButtonBox.AcceptRole: requested_name = str(self.calibre_destination_le.text()) if requested_name in self.get_custom_column_names(): self._log("'%s' already in use" % requested_name) warning_dialog( self.gui, _("Already in use"), _("<p>'{0}' is an existing custom column.</p><p>Pick a different name.</p>" ).format(requested_name), show=True, show_copy_button=False) self.calibre_destination_le.selectAll() self.calibre_destination_le.setFocus() else: source = self.column_type #profile = self.FIELDS[source] self.profile['source'] = source if button.objectName() == 'add_button': self.custom_column_add(requested_name, self.profile) elif button.objectName() == 'rename_button': self.custom_column_rename(requested_name, self.profile) self.accept() elif self.bb.buttonRole(button) == QDialogButtonBox.RejectRole: self.close() def esc(self, *args): self.close() def highlight_step(self, step): ''' ''' self._log_location(step) if step == 1: #self.step_1.setText(self.YELLOW_BG.format(self.STEP_ONE.format(self.column_type))) self.step_1.setText(self.STEP_ONE.format(self.column_type)) def get_custom_column_names(self): ''' ''' self._log_location() existing_custom_names = [] for cf in self.db.custom_field_keys(): #self._log(self.db.metadata_for_field(cf)) existing_custom_names.append( self.db.metadata_for_field(cf)['name']) return existing_custom_names def reset_accept_button(self, action="add_button", enabled=False): ''' ''' self.accept_button.setObjectName(action) if action == "add_button": self.accept_button.setText(_('Add custom column')) self.accept_button.setIcon(QIcon(I('plus.png'))) elif action == "rename_button": self.accept_button.setText(_("Rename custom column")) self.accept_button.setIcon(QIcon(I('edit_input.png'))) self.accept_button.setEnabled(enabled) def populate_editor(self): ''' ''' self._log_location() selected = self.column_type existing = None #label = self.FIELDS[selected]['label'] label = self.profile['label'] for cf in self.db.custom_field_keys(): #self._log(self.db.metadata_for_field(cf)) cfd = self.db.metadata_for_field(cf) if cfd['label'] == label: existing = cfd['name'] break # Does label already exist? if existing: self.previous_name = existing self.calibre_destination_le.setText(existing) self.reset_accept_button(action="rename_button", enabled=True) else: # Populate the edit box with the default Column name self.calibre_destination_le.setText(selected) self.reset_accept_button(action="add_button", enabled=True) # Select the text self.calibre_destination_le.selectAll() self.calibre_destination_le.setFocus() def validate_destination(self, destination): ''' Confirm length of column name > 0 ''' enabled = len(str(destination)) self.accept_button.setEnabled(enabled)
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip(_('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>')) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip(_('<qt>Specify additional command-line arguments for the conversion</qt>')) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80,20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][prefs['default_CSS']]),'css') self.css1.setToolTip(_('<qt>This stylesheet can be modified<br/>The default can be configured</qt>')) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip(i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip(_('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip(i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>')) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip(_('<qt>Show/hide the additional styles used for the conversion</qt>')) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][style]),'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@',text,re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print(_('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile() custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile() for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0,0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels); process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog(self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()
class JobError(QDialog): # {{{ WIDTH = 600 do_pop = pyqtSignal() def __init__(self, parent): QDialog.__init__(self, parent) self.setAttribute(Qt.WA_DeleteOnClose, False) self.queue = [] self.do_pop.connect(self.pop, type=Qt.QueuedConnection) self._layout = l = QGridLayout() self.setLayout(l) self.icon = QIcon(I('dialog_error.png')) self.setWindowIcon(self.icon) self.icon_label = QLabel() self.icon_label.setPixmap(self.icon.pixmap(68, 68)) self.icon_label.setMaximumSize(QSize(68, 68)) self.msg_label = QLabel('<p> ') self.msg_label.setStyleSheet('QLabel { margin-top: 1ex; }') self.msg_label.setWordWrap(True) self.msg_label.setTextFormat(Qt.RichText) self.det_msg = QPlainTextEdit(self) self.det_msg.setVisible(False) self.bb = QDialogButtonBox(QDialogButtonBox.Close, parent=self) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.ctc_button = self.bb.addButton(_('&Copy to clipboard'), self.bb.ActionRole) self.ctc_button.clicked.connect(self.copy_to_clipboard) self.retry_button = self.bb.addButton(_('&Retry'), self.bb.ActionRole) self.retry_button.clicked.connect(self.retry) self.retry_func = None self.show_det_msg = _('Show &details') self.hide_det_msg = _('Hide &details') self.det_msg_toggle = self.bb.addButton(self.show_det_msg, self.bb.ActionRole) self.det_msg_toggle.clicked.connect(self.toggle_det_msg) self.det_msg_toggle.setToolTip( _('Show detailed information about this error')) self.suppress = QCheckBox(self) l.addWidget(self.icon_label, 0, 0, 1, 1) l.addWidget(self.msg_label, 0, 1, 1, 1) l.addWidget(self.det_msg, 1, 0, 1, 2) l.addWidget(self.suppress, 2, 0, 1, 2, Qt.AlignLeft|Qt.AlignBottom) l.addWidget(self.bb, 3, 0, 1, 2, Qt.AlignRight|Qt.AlignBottom) l.setColumnStretch(1, 100) self.setModal(False) self.suppress.setVisible(False) self.do_resize() def retry(self): if self.retry_func is not None: self.accept() self.retry_func() def update_suppress_state(self): self.suppress.setText(_( 'Hide the remaining %d error messages'%len(self.queue))) self.suppress.setVisible(len(self.queue) > 3) self.do_resize() def copy_to_clipboard(self, *args): d = QTextDocument() d.setHtml(self.msg_label.text()) QApplication.clipboard().setText( u'calibre, version %s (%s, embedded-python: %s)\n%s: %s\n\n%s' % (__version__, sys.platform, isfrozen, unicode(self.windowTitle()), unicode(d.toPlainText()), unicode(self.det_msg.toPlainText()))) if hasattr(self, 'ctc_button'): self.ctc_button.setText(_('Copied')) def toggle_det_msg(self, *args): vis = unicode(self.det_msg_toggle.text()) == self.hide_det_msg self.det_msg_toggle.setText(self.show_det_msg if vis else self.hide_det_msg) self.det_msg.setVisible(not vis) self.do_resize() def do_resize(self): h = self.sizeHint().height() self.setMinimumHeight(0) # Needed as this gets set if det_msg is shown # Needed otherwise re-showing the box after showing det_msg causes the box # to not reduce in height self.setMaximumHeight(h) self.resize(QSize(self.WIDTH, h)) def showEvent(self, ev): ret = QDialog.showEvent(self, ev) self.bb.button(self.bb.Close).setFocus(Qt.OtherFocusReason) return ret def show_error(self, title, msg, det_msg=u'', retry_func=None): self.queue.append((title, msg, det_msg, retry_func)) self.update_suppress_state() self.pop() def pop(self): if not self.queue or self.isVisible(): return title, msg, det_msg, retry_func = self.queue.pop(0) self.setWindowTitle(title) self.msg_label.setText(msg) self.det_msg.setPlainText(det_msg) self.det_msg.setVisible(False) self.det_msg_toggle.setText(self.show_det_msg) self.det_msg_toggle.setVisible(True) self.suppress.setChecked(False) self.update_suppress_state() if not det_msg: self.det_msg_toggle.setVisible(False) self.retry_button.setVisible(retry_func is not None) self.retry_func = retry_func self.do_resize() self.show() def done(self, r): if self.suppress.isChecked(): self.queue = [] QDialog.done(self, r) self.do_pop.emit()
class IgnoredFolders(QDialog): def __init__(self, dev, ignored_folders=None, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel('<p>' + _('<b>Scanned folders:</b>') + ' ' + _('You can select which folders calibre will ' 'scan when searching this device for books.')) la.setWordWrap(True) l.addWidget(la) self.tabs = QTabWidget(self) l.addWidget(self.tabs) self.widgets = [] for storage in dev.filesystem_cache.entries: self.dev = dev w = Storage(storage, item_func=self.create_item) del self.dev self.tabs.addTab(w, storage.name) self.widgets.append(w) w.itemChanged.connect(self.item_changed) self.la2 = la = QLabel( _('If you a select a previously unselected folder, any sub-folders' ' will not be visible until you restart calibre.')) l.addWidget(la) la.setWordWrap(True) self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.sab = self.bb.addButton(_('Select &all'), self.bb.ActionRole) self.sab.clicked.connect(self.select_all) self.snb = self.bb.addButton(_('Select &none'), self.bb.ActionRole) self.snb.clicked.connect(self.select_none) l.addWidget(self.bb) self.setWindowTitle(_('Choose folders to scan')) self.setWindowIcon(QIcon(I('devices/tablet.png'))) self.resize(600, 500) def item_changed(self, item, column): w = item.treeWidget() root = w.invisibleRootItem() w.itemChanged.disconnect(self.item_changed) try: if item.checkState(0) == Qt.Checked: # Ensure that the parents of this item are checked p = item.parent() while p is not None and p is not root: p.setCheckState(0, Qt.Checked) p = p.parent() # Set the state of all descendants to the same state as this item for child in self.iterchildren(item): child.setCheckState(0, item.checkState(0)) finally: w.itemChanged.connect(self.item_changed) def iterchildren(self, node): ' Iterate over all descendants of node ' for i in range(node.childCount()): child = node.child(i) yield child for gc in self.iterchildren(child): yield gc def create_item(self, f, parent): name = f.name ans = QTreeWidgetItem(parent, [name]) ans.setData(0, Qt.UserRole, '/'.join(f.full_path[1:])) ans.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) ans.setCheckState( 0, Qt.Unchecked if self.dev.is_folder_ignored( f.storage_id, f.full_path[1:]) else Qt.Checked) ans.setData(0, Qt.DecorationRole, file_icon_provider().icon_from_ext('dir')) return ans def select_all(self): w = self.tabs.currentWidget() for i in range(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Checked) def select_none(self): w = self.tabs.currentWidget() for i in range(w.invisibleRootItem().childCount()): c = w.invisibleRootItem().child(i) c.setCheckState(0, Qt.Unchecked) @property def ignored_folders(self): ans = {} for w in self.widgets: folders = set() for node in self.iterchildren(w.invisibleRootItem()): if node.checkState(0) == Qt.Checked: continue path = unicode_type(node.data(0, Qt.UserRole) or '') parent = path.rpartition('/')[0] if '/' not in path or icu_lower(parent) not in folders: folders.add(icu_lower(path)) ans[unicode_type(w.storage.storage_id)] = list(folders) return ans
class ImportAnnotationsDialog(QDialog): def __init__(self, parent, friendly_name, rac): #self.dialog = QDialog(parent.gui) QDialog.__init__(self, parent.gui) self.parent = parent self.opts = parent.opts self.rac = rac parent_loc = self.parent.gui.pos() self.move(parent_loc.x(), parent_loc.y()) self.setWindowTitle(rac.import_dialog_title) self.setWindowIcon(self.opts.icon) l = QVBoxLayout() self.setLayout(l) self.pte = PlainTextEdit(self.parent) self.pte.setPlainText(rac.initial_dialog_text) self.pte.setMinimumWidth(400) l.addWidget(self.pte) self.dialogButtonBox = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Help) self.import_button = self.dialogButtonBox.addButton( self.dialogButtonBox.Ok) self.import_button.setText(_('Import')) self.dialogButtonBox.clicked.connect( self.import_annotations_dialog_clicked) l.addWidget(self.dialogButtonBox) self.rejected.connect(self.close) self.exec_() self.text = str(self.pte.toPlainText()) def close(self): # Catch ESC and close button self.pte.setPlainText('') self.accept() def import_annotations_dialog_clicked(self, button): BUTTON_ROLES = [ 'AcceptRole', 'RejectRole', 'DestructiveRole', 'ActionRole', 'HelpRole', 'YesRole', 'NoRole', 'ApplyRole', 'ResetRole' ] if self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.AcceptRole: # Remove initial_dialog_text if user clicks OK without dropping file if self.text() == self.rac.initial_dialog_text: self.pte.clear() self.accept() elif self.dialogButtonBox.buttonRole( button) == QDialogButtonBox.HelpRole: hv = HelpView(self, self.opts.icon, self.opts.prefs, html=self.rac.import_help_text) hv.show() else: self.close() def text(self): return unicode(self.pte.toPlainText())
class UserDefinedDevice(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self._layout = QVBoxLayout(self) self.setLayout(self._layout) self.log = QPlainTextEdit(self) self._layout.addWidget(self.log) self.log.setPlainText(_('Getting device information') + '...') self.copy = QPushButton(_('Copy to &clipboard')) self.copy.setDefault(True) self.setWindowTitle(_('User-defined device information')) self.setWindowIcon(QIcon(I('debug.png'))) self.copy.clicked.connect(self.copy_to_clipboard) self.ok = QPushButton('&OK') self.ok.setAutoDefault(False) self.ok.clicked.connect(self.accept) self.bbox = QDialogButtonBox(self) self.bbox.addButton(self.copy, QDialogButtonBox.ButtonRole.ActionRole) self.bbox.addButton(self.ok, QDialogButtonBox.ButtonRole.AcceptRole) self._layout.addWidget(self.bbox) self.resize(750, 500) self.bbox.setEnabled(False) QTimer.singleShot(1000, self.device_info) def device_info(self): try: from calibre.devices import device_info r = step_dialog( self.parent(), _('Device Detection'), _('Ensure your device is disconnected, then press OK')) if r: self.close() return before = device_info() r = step_dialog( self.parent(), _('Device Detection'), _('Ensure your device is connected, then press OK')) if r: self.close() return after = device_info() new_devices = after['device_set'] - before['device_set'] res = '' if len(new_devices) == 1: def fmtid(x): x = x or 0 if isinstance(x, numbers.Integral): x = hex(x) if not x.startswith('0x'): x = '0x' + x return x for d in new_devices: res = _('USB Vendor ID (in hex)') + ': ' + \ fmtid(after['device_details'][d][0]) + '\n' res += _('USB Product ID (in hex)') + ': ' + \ fmtid(after['device_details'][d][1]) + '\n' res += _('USB Revision ID (in hex)') + ': ' + \ fmtid(after['device_details'][d][2]) + '\n' trailer = _( 'Copy these values to the clipboard, paste them into an ' 'editor, then enter them into the USER_DEVICE by ' 'customizing the device plugin in Preferences->Advanced->Plugins. ' 'Remember to also enter the folders where you want the books to ' 'be put. You must restart calibre for your changes ' 'to take effect.\n') self.log.setPlainText(res + '\n\n' + trailer) finally: self.bbox.setEnabled(True) def copy_to_clipboard(self): QApplication.clipboard().setText(self.log.toPlainText())
class FullFetch(QDialog): # {{{ def __init__(self, current_cover=None, parent=None): QDialog.__init__(self, parent) self.current_cover = current_cover self.log = Log() self.book = self.cover_pixmap = None self.setWindowTitle(_('Downloading metadata...')) self.setWindowIcon(QIcon(I('download-metadata.png'))) self.stack = QStackedWidget() self.l = l = QVBoxLayout() self.setLayout(l) l.addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) self.h = h = QHBoxLayout() l.addLayout(h) self.bb.rejected.connect(self.reject) self.bb.accepted.connect(self.accept) self.ok_button = self.bb.button(self.bb.Ok) self.ok_button.setEnabled(False) self.ok_button.clicked.connect(self.ok_clicked) self.prev_button = pb = QPushButton(QIcon(I('back.png')), _('&Back'), self) pb.clicked.connect(self.back_clicked) pb.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.log_button = self.bb.addButton(_('&View log'), self.bb.ActionRole) self.log_button.clicked.connect(self.view_log) self.log_button.setIcon(QIcon(I('debug.png'))) self.prev_button.setVisible(False) h.addWidget(self.prev_button), h.addWidget(self.bb) self.identify_widget = IdentifyWidget(self.log, self) self.identify_widget.rejected.connect(self.reject) self.identify_widget.results_found.connect(self.identify_results_found) self.identify_widget.book_selected.connect(self.book_selected) self.stack.addWidget(self.identify_widget) self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) self.covers_widget.chosen.connect(self.ok_clicked) self.stack.addWidget(self.covers_widget) self.resize(850, 600) geom = gprefs.get('metadata_single_gui_geom', None) if geom is not None and geom: self.restoreGeometry(geom) self.finished.connect(self.cleanup) def view_log(self): self._lv = LogViewer(self.log, self) def book_selected(self, book, caches): self.prev_button.setVisible(True) self.book = book self.stack.setCurrentIndex(1) self.log('\n\n') self.covers_widget.start(book, self.current_cover, self.title, self.authors, caches) self.ok_button.setFocus() def back_clicked(self): self.prev_button.setVisible(False) self.stack.setCurrentIndex(0) self.covers_widget.cancel() self.covers_widget.reset_covers() def accept(self): # Prevent the usual dialog accept mechanisms from working gprefs['metadata_single_gui_geom'] = bytearray(self.saveGeometry()) if DEBUG_DIALOG: if self.stack.currentIndex() == 2: return QDialog.accept(self) else: if self.stack.currentIndex() == 1: return QDialog.accept(self) def reject(self): gprefs['metadata_single_gui_geom'] = bytearray(self.saveGeometry()) self.identify_widget.cancel() self.covers_widget.cancel() return QDialog.reject(self) def cleanup(self): self.covers_widget.cleanup() def identify_results_found(self): self.ok_button.setEnabled(True) def next_clicked(self, *args): gprefs['metadata_single_gui_geom'] = bytearray(self.saveGeometry()) self.identify_widget.get_result() def ok_clicked(self, *args): self.cover_pixmap = self.covers_widget.cover_pixmap() if self.stack.currentIndex() == 0: self.next_clicked() return if DEBUG_DIALOG: if self.cover_pixmap is not None: self.w = QLabel() self.w.setPixmap(self.cover_pixmap) self.stack.addWidget(self.w) self.stack.setCurrentIndex(2) else: QDialog.accept(self) def start(self, title=None, authors=None, identifiers={}): self.title, self.authors = title, authors self.identify_widget.start(title=title, authors=authors, identifiers=identifiers) return self.exec_()
class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) forum_label_text = _('Plugin homepage') def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE): SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog') self.gui = gui self.forum_link = None self.zip_url = None self.model = None self.do_restart = False self._initialize_controls() self._create_context_menu() try: display_plugins = read_available_plugins(raise_error=True) except Exception: display_plugins = [] import traceback error_dialog(self.gui, _('Update Check Failed'), _('Unable to reach the plugin index page.'), det_msg=traceback.format_exc(), show=True) if display_plugins: self.model = DisplayPluginModel(display_plugins) self.proxy_model = DisplayPluginSortFilterModel(self) self.proxy_model.setSourceModel(self.model) self.plugin_view.setModel(self.proxy_model) self.plugin_view.resizeColumnsToContents() self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed) self.plugin_view.doubleClicked.connect(self.install_button.click) self.filter_combo.setCurrentIndex(initial_filter) self._select_and_focus_view() else: self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def _initialize_controls(self): self.setWindowTitle(_('User plugins')) self.setWindowIcon(QIcon(I('plugins/plugin_updater.png'))) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png', _('User plugins')) layout.addLayout(title_layout) header_layout = QHBoxLayout() layout.addLayout(header_layout) self.filter_combo = PluginFilterComboBox(self) self.filter_combo.setMinimumContentsLength(20) self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed) la = QLabel(_('Filter list of &plugins')+':', self) la.setBuddy(self.filter_combo) header_layout.addWidget(la) header_layout.addWidget(self.filter_combo) header_layout.addStretch(10) # filter plugins by name la = QLabel(_('Filter by &name')+':', self) header_layout.addWidget(la) self.filter_by_name_lineedit = QLineEdit(self) la.setBuddy(self.filter_by_name_lineedit) self.filter_by_name_lineedit.setText("") self.filter_by_name_lineedit.textChanged.connect(self._filter_name_lineedit_changed) header_layout.addWidget(self.filter_by_name_lineedit) self.plugin_view = QTableView(self) self.plugin_view.horizontalHeader().setStretchLastSection(True) self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection) self.plugin_view.setAlternatingRowColors(True) self.plugin_view.setSortingEnabled(True) self.plugin_view.setIconSize(QSize(28, 28)) layout.addWidget(self.plugin_view) details_layout = QHBoxLayout() layout.addLayout(details_layout) forum_label = self.forum_label = QLabel('') forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) forum_label.linkActivated.connect(self._forum_label_activated) details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft) details_layout.addWidget(forum_label, 1, Qt.AlignRight) self.description = QLabel(self) self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) self.description.setWordWrap(True) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) self.button_box.rejected.connect(self.reject) self.finished.connect(self._finished) self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole) self.install_button.setToolTip(_('Install the selected plugin')) self.install_button.clicked.connect(self._install_clicked) self.install_button.setEnabled(False) self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole) self.configure_button.setToolTip(_('Customize the options for this plugin')) self.configure_button.clicked.connect(self._configure_clicked) self.configure_button.setEnabled(False) layout.addWidget(self.button_box) def update_forum_label(self): txt = '' if self.forum_link: txt = '<a href="%s">%s</a>' % (self.forum_link, self.forum_label_text) self.forum_label.setText(txt) def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I('chapters.png')), _('Version &history'), self) self.history_action.setToolTip(_('Show history of changes to this plugin')) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &forum thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&disable plugin'), self) self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip(_('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action) def _finished(self, *args): if self.model: update_plugins = list(filter(filter_upgradeable_plugins, self.model.display_plugins)) self.gui.recalc_update_label(len(update_plugins)) def _plugin_current_changed(self, current, previous): if current.isValid(): actual_idx = self.proxy_model.mapToSource(current) display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link self.zip_url = display_plugin.zip_url self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled(display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) self.uninstall_action.setEnabled(display_plugin.is_installed()) self.history_action.setEnabled(display_plugin.has_changelog) self.configure_button.setEnabled(display_plugin.is_installed()) self.configure_action.setEnabled(self.configure_button.isEnabled()) self.toggle_enabled_action.setEnabled(display_plugin.is_installed()) self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link)) else: self.description.setText('') self.forum_link = None self.zip_url = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) self.uninstall_action.setEnabled(False) self.history_action.setEnabled(False) self.configure_button.setEnabled(False) self.configure_action.setEnabled(False) self.toggle_enabled_action.setEnabled(False) self.donate_enabled_action.setEnabled(False) self.update_forum_label() def _donate_clicked(self): plugin = self._selected_display_plugin() if plugin and plugin.donation_link: open_url(QUrl(plugin.donation_link)) def _select_and_focus_view(self, change_selection=True): if change_selection and self.plugin_view.model().rowCount() > 0: self.plugin_view.selectRow(0) else: idx = self.plugin_view.selectionModel().currentIndex() self._plugin_current_changed(idx, 0) self.plugin_view.setFocus() def _filter_combo_changed(self, idx): self.filter_by_name_lineedit.setText("") # clear the name filter text when a different group was selected self.proxy_model.set_filter_criteria(idx) if idx == FILTER_NOT_INSTALLED: self.plugin_view.sortByColumn(5, Qt.DescendingOrder) else: self.plugin_view.sortByColumn(0, Qt.AscendingOrder) self._select_and_focus_view() def _filter_name_lineedit_changed(self, text): self.proxy_model.set_filter_text(text) # set the filter text for filterAcceptsRow def _forum_label_activated(self): if self.forum_link: open_url(QUrl(self.forum_link)) def _selected_display_plugin(self): idx = self.plugin_view.selectionModel().currentIndex() actual_idx = self.proxy_model.mapToSource(idx) return self.model.display_plugins[actual_idx.row()] def _uninstall_plugin(self, name_to_remove): if DEBUG: prints('Removing plugin: ', name_to_remove) remove_plugin(name_to_remove) # Make sure that any other plugins that required this plugin # to be uninstalled first have the requirement removed for display_plugin in self.model.display_plugins: # Make sure we update the status and display of the # plugin we just uninstalled if name_to_remove in display_plugin.uninstall_plugins: if DEBUG: prints('Removing uninstall dependency for: ', display_plugin.name) display_plugin.uninstall_plugins.remove(name_to_remove) if display_plugin.qname == name_to_remove: if DEBUG: prints('Resetting plugin to uninstalled status: ', display_plugin.name) display_plugin.installed_version = None display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.refresh_plugin(display_plugin) def _uninstall_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Are you sure?'), '<p>'+ _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name, show_copy_button=False): return self._uninstall_plugin(display_plugin.qname) if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self._select_and_focus_view(change_selection=False) def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' + _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints('Uninstalling plugin: ', ', '.join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) plugin_zip_url = display_plugin.zip_url if DEBUG: prints('Downloading plugin ZIP attachment: ', plugin_zip_url) self.gui.status_bar.showMessage(_('Downloading plugin ZIP attachment: %s') % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) do_restart = False try: from calibre.customize.ui import config installed_plugins = frozenset(config['plugins']) try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _('Already exists'), unicode_type(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) d = info_dialog(self.gui, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format(plugin.name, plugin.type), show_copy_button=False) b = d.bb.addButton(_('&Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints('ERROR occurred while installing plugin: %s'%display_plugin.name) traceback.print_exc() error_dialog(self.gui, _('Install plugin failed'), _('A problem occurred while installing this plugin.' ' This plugin will now be uninstalled.' ' Please post the error message in details below into' ' the forum thread for this plugin and restart calibre.'), det_msg=traceback.format_exc(), show=True) if DEBUG: prints('Due to error now uninstalling plugin: %s'%display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.beginResetModel(), self.model.endResetModel() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) if do_restart: self.do_restart = True self.accept() def _history_clicked(self): display_plugin = self._selected_display_plugin() text = self._read_version_history_html(display_plugin.forum_link) if text: dlg = VersionHistoryDialog(self, display_plugin.name, text) dlg.exec_() else: return error_dialog(self, _('Version history missing'), _('Unable to find the version history for %s')%display_plugin.name, show=True) def _configure_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.is_customizable(): return info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization')%plugin.name, show=True) from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin')%plugin.name, show=True) plugin.do_user_config(self.parent()) def _toggle_enabled_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.can_be_disabled: return error_dialog(self,_('Plugin cannot be disabled'), _('The plugin: %s cannot be disabled')%plugin.name, show=True) if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self.model.refresh_plugin(display_plugin) def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find('version history') != -1: div_node = spoiler_node.xpath('div')[0] text = html.tostring(div_node, method='html', encoding='unicode') return re.sub(r'<div\s.*?>', '<div>', text) except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(spoiler_node)) return None def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile raw = get_https_resource_securely(plugin_zip_url, headers={'User-Agent':'%s %s' % (__appname__, __version__)}) with PersistentTemporaryFile('.zip') as pt: pt.write(raw) return pt.name
class UserDefinedDevice(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self._layout = QVBoxLayout(self) self.setLayout(self._layout) self.log = QPlainTextEdit(self) self._layout.addWidget(self.log) self.log.setPlainText(_('Getting device information')+'...') self.copy = QPushButton(_('Copy to &clipboard')) self.copy.setDefault(True) self.setWindowTitle(_('User-defined device information')) self.setWindowIcon(QIcon(I('debug.png'))) self.copy.clicked.connect(self.copy_to_clipboard) self.ok = QPushButton('&OK') self.ok.setAutoDefault(False) self.ok.clicked.connect(self.accept) self.bbox = QDialogButtonBox(self) self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole) self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole) self._layout.addWidget(self.bbox) self.resize(750, 500) self.bbox.setEnabled(False) QTimer.singleShot(1000, self.device_info) def device_info(self): try: from calibre.devices import device_info r = step_dialog(self.parent(), _('Device Detection'), _('Ensure your device is disconnected, then press OK')) if r: self.close() return before = device_info() r = step_dialog(self.parent(), _('Device Detection'), _('Ensure your device is connected, then press OK')) if r: self.close() return after = device_info() new_devices = after['device_set'] - before['device_set'] res = '' if len(new_devices) == 1: def fmtid(x): if isinstance(x, (int, long)): x = hex(x) if not x.startswith('0x'): x = '0x' + x return x for d in new_devices: res = _('USB Vendor ID (in hex)') + ': ' + \ fmtid(after['device_details'][d][0]) + '\n' res += _('USB Product ID (in hex)') + ': ' + \ fmtid(after['device_details'][d][1]) + '\n' res += _('USB Revision ID (in hex)') + ': ' + \ fmtid(after['device_details'][d][2]) + '\n' trailer = _( 'Copy these values to the clipboard, paste them into an ' 'editor, then enter them into the USER_DEVICE by ' 'customizing the device plugin in Preferences->Plugins. ' 'Remember to also enter the folders where you want the books to ' 'be put. You must restart calibre for your changes ' 'to take effect.\n') self.log.setPlainText(res + '\n\n' + trailer) finally: self.bbox.setEnabled(True) def copy_to_clipboard(self): QApplication.clipboard().setText(self.log.toPlainText())