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.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.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 EditNotes(Dialog): def __init__(self, notes, parent=None): self.initial_notes = notes Dialog.__init__(self, _('Edit notes for highlight'), 'library-annotations-browser-edit-notes', parent=parent) def setup_ui(self): self.notes_edit = QPlainTextEdit(self) if self.initial_notes: self.notes_edit.setPlainText(self.initial_notes) self.notes_edit.setMinimumWidth(400) self.notes_edit.setMinimumHeight(300) l = QVBoxLayout(self) l.addWidget(self.notes_edit) l.addWidget(self.bb) @property def notes(self): return self.notes_edit.toPlainText()
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( share_open(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( share_open(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.StandardButton.Ok) layout.addWidget(bx) bx.accepted.connect(d.accept) b = bx.addButton(_('&Clear logs'), QDialogButtonBox.ButtonRole.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 PlainTextDialog(Dialog): def __init__(self, parent, text, column_name=None): title = _('Edit "{0}"').format(column_name) if column_name else _( 'Edit text') Dialog.__init__(self, title, 'edit-plain-text-dialog', parent=parent) self.text = text def setup_ui(self): self.l = l = QVBoxLayout(self) self._text = QPlainTextEdit(self) l.addWidget(self._text) l.addWidget(self.bb) @property def text(self): return self._text.toPlainText() @text.setter def text(self, val): self._text.setPlainText(val or '') def sizeHint(self): return QSize(600, 400)
class PluginTweaks(QDialog): # {{{ def __init__(self, raw, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(_('Plugin tweaks')) self.edit = QPlainTextEdit(self) self.highlighter = PythonHighlighter(self.edit.document()) self.l = QVBoxLayout() self.setLayout(self.l) self.msg = QLabel( _('Add/edit tweaks for any custom plugins you have installed. ' 'Documentation for these tweaks should be available ' 'on the website from where you downloaded the plugins.')) self.msg.setWordWrap(True) self.l.addWidget(self.msg) self.l.addWidget(self.edit) self.edit.setPlainText(raw) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel, Qt.Orientation.Horizontal, self) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.l.addWidget(self.bb) self.resize(550, 300)
class TestEmail(QDialog): test_done = pyqtSignal(object) def __init__(self, pa, parent): QDialog.__init__(self, parent) self.test_func = parent.test_email_settings self.setWindowTitle(_("Test email settings")) self.setWindowIcon(QIcon(I('config.ui'))) l = QVBoxLayout(self) opts = smtp_prefs().parse() self.from_ = la = QLabel(_("Send test mail from %s to:") % opts.from_) l.addWidget(la) self.to = le = QLineEdit(self) if pa: self.to.setText(pa) self.test_button = b = QPushButton(_('&Test'), self) b.clicked.connect(self.start_test) self.test_done.connect(self.on_test_done, type=Qt.ConnectionType.QueuedConnection) self.h = h = QHBoxLayout() h.addWidget(le), h.addWidget(b) l.addLayout(h) if opts.relay_host: self.la = la = QLabel( _('Using: %(un)s:%(pw)s@%(host)s:%(port)s and %(enc)s encryption' ) % dict(un=opts.relay_username, pw=from_hex_unicode(opts.relay_password), host=opts.relay_host, port=opts.relay_port, enc=opts.encryption)) l.addWidget(la) self.log = QPlainTextEdit(self) l.addWidget(self.log) self.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) bb.rejected.connect(self.reject), bb.accepted.connect(self.accept) l.addWidget(bb) def start_test(self, *args): if not self.to.text().strip(): return error_dialog( self, _('No email address'), _('No email address to send mail to has been specified. You' ' must specify a To: address before running the test.'), show=True) self.log.setPlainText(_('Sending email, please wait...')) self.test_button.setEnabled(False) t = Thread(target=self.run_test, name='TestEmailSending') t.daemon = True t.start() def run_test(self): try: tb = self.test_func(unicode_type( self.to.text())) or _('Email successfully sent') except Exception: import traceback tb = traceback.format_exc() self.test_done.emit(tb) def on_test_done(self, txt): if self.isVisible(): self.test_button.setEnabled(True) self.log.setPlainText(txt)
class Editor(QWidget): # {{{ toolbar_prefs_name = None data_changed = pyqtSignal() def __init__(self, parent=None, one_line_toolbar=False, toolbar_prefs_name=None): QWidget.__init__(self, parent) self.toolbar_prefs_name = toolbar_prefs_name or self.toolbar_prefs_name self.toolbar1 = QToolBar(self) self.toolbar2 = QToolBar(self) self.toolbar3 = QToolBar(self) for i in range(1, 4): t = getattr(self, 'toolbar%d'%i) t.setIconSize(QSize(18, 18)) self.editor = EditorWidget(self) self.editor.data_changed.connect(self.data_changed) self.set_base_url = self.editor.set_base_url self.set_html = self.editor.set_html self.tabs = QTabWidget(self) self.tabs.setTabPosition(QTabWidget.TabPosition.South) self.wyswyg = QWidget(self.tabs) self.code_edit = QPlainTextEdit(self.tabs) self.source_dirty = False self.wyswyg_dirty = True self._layout = QVBoxLayout(self) self.wyswyg.layout = l = QVBoxLayout(self.wyswyg) self.setLayout(self._layout) l.setContentsMargins(0, 0, 0, 0) if one_line_toolbar: tb = QHBoxLayout() l.addLayout(tb) else: tb = l tb.addWidget(self.toolbar1) tb.addWidget(self.toolbar2) tb.addWidget(self.toolbar3) l.addWidget(self.editor) self._layout.addWidget(self.tabs) self.tabs.addTab(self.wyswyg, _('&Normal view')) self.tabs.addTab(self.code_edit, _('&HTML source')) self.tabs.currentChanged[int].connect(self.change_tab) self.highlighter = Highlighter(self.code_edit.document()) self.layout().setContentsMargins(0, 0, 0, 0) if self.toolbar_prefs_name is not None: hidden = gprefs.get(self.toolbar_prefs_name) if hidden: self.hide_toolbars() # toolbar1 {{{ self.toolbar1.addAction(self.editor.action_undo) self.toolbar1.addAction(self.editor.action_redo) self.toolbar1.addAction(self.editor.action_select_all) self.toolbar1.addAction(self.editor.action_remove_format) self.toolbar1.addAction(self.editor.action_clear) self.toolbar1.addSeparator() for x in ('copy', 'cut', 'paste'): ac = getattr(self.editor, 'action_'+x) self.toolbar1.addAction(ac) self.toolbar1.addSeparator() self.toolbar1.addAction(self.editor.action_background) # }}} # toolbar2 {{{ for x in ('', 'un'): ac = getattr(self.editor, 'action_%sordered_list'%x) self.toolbar2.addAction(ac) self.toolbar2.addSeparator() for x in ('superscript', 'subscript', 'indent', 'outdent'): self.toolbar2.addAction(getattr(self.editor, 'action_' + x)) if x in ('subscript', 'outdent'): self.toolbar2.addSeparator() self.toolbar2.addAction(self.editor.action_block_style) w = self.toolbar2.widgetForAction(self.editor.action_block_style) if hasattr(w, 'setPopupMode'): w.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) self.toolbar2.addAction(self.editor.action_insert_link) self.toolbar2.addAction(self.editor.action_insert_hr) # }}} # toolbar3 {{{ for x in ('bold', 'italic', 'underline', 'strikethrough'): ac = getattr(self.editor, 'action_'+x) self.toolbar3.addAction(ac) self.addAction(ac) self.toolbar3.addSeparator() for x in ('left', 'center', 'right', 'justified'): ac = getattr(self.editor, 'action_align_'+x) self.toolbar3.addAction(ac) self.toolbar3.addSeparator() self.toolbar3.addAction(self.editor.action_color) # }}} self.code_edit.textChanged.connect(self.code_dirtied) self.editor.data_changed.connect(self.wyswyg_dirtied) def set_minimum_height_for_editor(self, val): self.editor.setMinimumHeight(val) @property def html(self): self.tabs.setCurrentIndex(0) return self.editor.html @html.setter def html(self, v): self.editor.html = v def change_tab(self, index): # print 'reloading:', (index and self.wyswyg_dirty) or (not index and # self.source_dirty) if index == 1: # changing to code view if self.wyswyg_dirty: self.code_edit.setPlainText(self.editor.html) self.wyswyg_dirty = False elif index == 0: # changing to wyswyg if self.source_dirty: self.editor.html = to_plain_text(self.code_edit) self.source_dirty = False @property def tab(self): return 'code' if self.tabs.currentWidget() is self.code_edit else 'wyswyg' @tab.setter def tab(self, val): self.tabs.setCurrentWidget(self.code_edit if val == 'code' else self.wyswyg) def wyswyg_dirtied(self, *args): self.wyswyg_dirty = True def code_dirtied(self, *args): self.source_dirty = True def hide_toolbars(self): self.toolbar1.setVisible(False) self.toolbar2.setVisible(False) self.toolbar3.setVisible(False) def show_toolbars(self): self.toolbar1.setVisible(True) self.toolbar2.setVisible(True) self.toolbar3.setVisible(True) def toggle_toolbars(self): visible = self.toolbars_visible getattr(self, ('hide' if visible else 'show') + '_toolbars')() if self.toolbar_prefs_name is not None: gprefs.set(self.toolbar_prefs_name, visible) @property def toolbars_visible(self): return self.toolbar1.isVisible() or self.toolbar2.isVisible() or self.toolbar3.isVisible() @toolbars_visible.setter def toolbars_visible(self, val): getattr(self, ('show' if val else 'hide') + '_toolbars')() def set_readonly(self, what): self.editor.set_readonly(what) def hide_tabs(self): self.tabs.tabBar().setVisible(False) def smarten_punctuation(self): from calibre.ebooks.conversion.preprocess import smarten_punctuation html = self.html newhtml = smarten_punctuation(html) if html != newhtml: self.html = newhtml
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'), QDialogButtonBox.ButtonRole.ActionRole) self.ctc_button.clicked.connect(self.copy_to_clipboard) self.retry_button = self.bb.addButton( _('&Retry'), QDialogButtonBox.ButtonRole.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, QDialogButtonBox.ButtonRole.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, str(self.windowTitle()), str(d.toPlainText()), str(self.det_msg.toPlainText()))) if hasattr(self, 'ctc_button'): self.ctc_button.setText(_('Copied')) def toggle_det_msg(self, *args): vis = str(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(QDialogButtonBox.StandardButton.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 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())