def create_filterable_names_list(names, filter_text=None, parent=None, model=NamesModel): nl = QListView(parent) nl.m = m = model(names, parent=nl) connect_lambda(m.filtered, nl, lambda nl, all_items: nl.scrollTo(m.index(0))) nl.setModel(m) if model is NamesModel: nl.d = NamesDelegate(nl) nl.setItemDelegate(nl.d) f = QLineEdit(parent) f.setPlaceholderText(filter_text or '') f.textEdited.connect(m.filter) return nl, f
class SelectFormats(QDialog): def __init__(self, fmt_count, msg, single=False, parent=None, exclude=False): QDialog.__init__(self, parent) self._l = QVBoxLayout(self) self.single_fmt = single self.setLayout(self._l) self.setWindowTitle(_('Choose formats')) self._m = QLabel(msg) self._m.setWordWrap(True) self._l.addWidget(self._m) self.formats = Formats(fmt_count) self.fview = QListView(self) self.fview.doubleClicked.connect( self.double_clicked, type=Qt.ConnectionType.QueuedConnection) if exclude: if QApplication.instance().is_dark_theme: sheet = 'background-color: #DAA520; color: black' else: sheet = 'background-color: #fae7b5' self.fview.setStyleSheet('QListView { %s }' % sheet) self._l.addWidget(self.fview) self.fview.setModel(self.formats) self.fview.setSelectionMode( QAbstractItemView.SelectionMode.SingleSelection if single else QAbstractItemView.SelectionMode.MultiSelection) self.bbox = \ QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel, Qt.Orientation.Horizontal, self) self._l.addWidget(self.bbox) self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.reject) self.fview.setIconSize(QSize(48, 48)) self.fview.setSpacing(2) self.resize(350, 500) self.selected_formats = set() def accept(self, *args): for idx in self.fview.selectedIndexes(): self.selected_formats.add(self.formats.fmt(idx)) QDialog.accept(self, *args) def double_clicked(self, index): if self.single_fmt: self.accept()
class SelectNames(QDialog): # {{{ def __init__(self, names, txt, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout(self) self.setLayout(l) self.la = la = QLabel(_('Create a Virtual library based on %s') % txt) l.addWidget(la) self.filter = f = QLineEdit(self) f.setPlaceholderText(_('Filter {}').format(txt)) f.setClearButtonEnabled(True) l.addWidget(f) self.model = QStringListModel(sorted(names, key=sort_key)) self.pmodel = QSortFilterProxyModel(self) self.pmodel.setFilterCaseSensitivity( Qt.CaseSensitivity.CaseInsensitive) f.textChanged.connect(self.pmodel.setFilterFixedString) self.pmodel.setSourceModel(self.model) self._names = QListView(self) self._names.setModel(self.pmodel) self._names.setSelectionMode( QAbstractItemView.SelectionMode.MultiSelection) l.addWidget(self._names) self._or = QRadioButton(_('Match any of the selected %s') % txt) self._and = QRadioButton(_('Match all of the selected %s') % txt) self._or.setChecked(True) l.addWidget(self._or) l.addWidget(self._and) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb) self.resize(self.sizeHint()) @property def names(self): for index in self._names.selectedIndexes(): yield index.data(Qt.ItemDataRole.DisplayRole) or '' @property def match_type(self): return ' and ' if self._and.isChecked() else ' or '
class ShortcutConfig(QWidget): def __init__(self, model, parent=None): QWidget.__init__(self, parent) self._layout = QHBoxLayout() self.setLayout(self._layout) self.view = QListView(self) self._layout.addWidget(self.view) self.view.setModel(model) self.delegate = Delegate() self.view.setItemDelegate(self.delegate) self.delegate.sizeHintChanged.connect( self.scrollTo, type=Qt.ConnectionType.QueuedConnection) def scrollTo(self, index): self.view.scrollTo(index, QAbstractItemView.ScrollHint.EnsureVisible) @property def is_editing(self): return self.view.state() == QAbstractItemView.State.EditingState
class Config(QDialog): ''' Configuration dialog for single book conversion. If accepted, has the following important attributes output_format - Output format (without a leading .) input_format - Input format (without a leading .) opf_path - Path to OPF file with user specified metadata cover_path - Path to user specified cover (can be None) recommendations - A pickled list of 3 tuples in the same format as the recommendations member of the Input/Output plugins. ''' def __init__(self, parent, db, book_id, preferred_input_format=None, preferred_output_format=None): QDialog.__init__(self, parent) self.widgets = [] self.setupUi() self.opt_individual_saved_settings.setVisible(False) self.db, self.book_id = db, book_id self.setup_input_output_formats(self.db, self.book_id, preferred_input_format, preferred_output_format) self.setup_pipeline() self.input_formats.currentIndexChanged[native_string_type].connect( self.setup_pipeline) self.output_formats.currentIndexChanged[native_string_type].connect( self.setup_pipeline) self.groups.setSpacing(5) self.groups.entered[(QModelIndex)].connect(self.show_group_help) rb = self.buttonBox.button( QDialogButtonBox.StandardButton.RestoreDefaults) rb.setText(_('Restore &defaults')) rb.setIcon(QIcon(I('clear_left.png'))) rb.clicked.connect(self.restore_defaults) self.groups.setMouseTracking(True) geom = gprefs.get('convert_single_dialog_geom', None) if geom: QApplication.instance().safe_restore_geometry(self, geom) else: self.resize(self.sizeHint()) def current_group_changed(self, cur, prev): self.show_pane(cur) def setupUi(self): self.setObjectName("Dialog") self.resize(1024, 700) self.setWindowIcon(QIcon(I('convert.png'))) self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.input_label = QLabel(self) self.input_label.setObjectName("input_label") self.horizontalLayout.addWidget(self.input_label) self.input_formats = QComboBox(self) self.input_formats.setSizeAdjustPolicy( QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) self.input_formats.setMinimumContentsLength(5) self.input_formats.setObjectName("input_formats") self.horizontalLayout.addWidget(self.input_formats) self.opt_individual_saved_settings = QCheckBox(self) self.opt_individual_saved_settings.setObjectName( "opt_individual_saved_settings") self.horizontalLayout.addWidget(self.opt_individual_saved_settings) spacerItem = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(spacerItem) self.label_2 = QLabel(self) self.label_2.setObjectName("label_2") self.horizontalLayout.addWidget(self.label_2) self.output_formats = QComboBox(self) self.output_formats.setSizeAdjustPolicy( QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) self.output_formats.setMinimumContentsLength(5) self.output_formats.setObjectName("output_formats") self.horizontalLayout.addWidget(self.output_formats) self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 2) self.groups = QListView(self) sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.groups.sizePolicy().hasHeightForWidth()) self.groups.setSizePolicy(sizePolicy) self.groups.setTabKeyNavigation(True) self.groups.setIconSize(QSize(48, 48)) self.groups.setWordWrap(True) self.groups.setObjectName("groups") self.gridLayout.addWidget(self.groups, 1, 0, 3, 1) self.scrollArea = QScrollArea(self) sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(4) sizePolicy.setVerticalStretch(10) sizePolicy.setHeightForWidth( self.scrollArea.sizePolicy().hasHeightForWidth()) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setFrameShape(QFrame.Shape.NoFrame) self.scrollArea.setLineWidth(0) self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.page = QWidget() self.page.setObjectName("page") self.gridLayout.addWidget(self.scrollArea, 1, 1, 1, 1) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setOrientation(Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons( QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.RestoreDefaults) self.buttonBox.setObjectName("buttonBox") self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1) self.help = QTextEdit(self) self.help.setReadOnly(True) sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.help.sizePolicy().hasHeightForWidth()) self.help.setSizePolicy(sizePolicy) self.help.setMaximumHeight(80) self.help.setObjectName("help") self.gridLayout.addWidget(self.help, 2, 1, 1, 1) self.input_label.setBuddy(self.input_formats) self.label_2.setBuddy(self.output_formats) self.input_label.setText(_("&Input format:")) self.opt_individual_saved_settings.setText( _("Use &saved conversion settings for individual books")) self.label_2.setText(_("&Output format:")) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) def sizeHint(self): geom = self.screen().availableSize() nh, nw = max(300, geom.height() - 100), max(400, geom.width() - 70) return QSize(nw, nh) def restore_defaults(self): delete_specifics(self.db, self.book_id) self.setup_pipeline() @property def input_format(self): return str(self.input_formats.currentText()).lower() @property def output_format(self): return str(self.output_formats.currentText()).lower() @property def manually_fine_tune_toc(self): for w in self.widgets: if hasattr(w, 'manually_fine_tune_toc'): return w.manually_fine_tune_toc.isChecked() def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() input_format = self.input_format output_format = self.output_format self.plumber = create_dummy_plumber(input_format, output_format) def widget_factory(cls): return cls(self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) self.mw = widget_factory(MetadataWidget) self.setWindowTitle(_('Convert') + ' ' + str(self.mw.title.text())) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) from calibre.gui2.actions.toc_edit import SUPPORTED toc.manually_fine_tune_toc.setVisible( output_format.upper() in SUPPORTED) debug = widget_factory(DebugWidget) output_widget = self.plumber.output_plugin.gui_configuration_widget( self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) input_widget = self.plumber.input_plugin.gui_configuration_widget( self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) self.break_cycles() self.widgets = widgets = [self.mw, lf, hw, ps, sd, toc, sr] if input_widget is not None: widgets.append(input_widget) if output_widget is not None: widgets.append(output_widget) widgets.append(debug) for w in widgets: w.set_help_signal.connect(self.help.setPlainText) w.setVisible(False) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.show_pane(idx) self.groups.selectionModel().currentChanged.connect( self.current_group_changed) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except Exception: pass def setup_input_output_formats(self, db, book_id, preferred_input_format, preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.upper() output_formats = get_output_formats(preferred_output_format) input_format, input_formats = get_input_format_for_book( db, book_id, preferred_input_format) preferred_output_format = preferred_output_format if \ preferred_output_format in output_formats else \ sort_formats_by_preference(output_formats, [prefs['output_format']])[0] self.input_formats.addItems(str(x.upper()) for x in input_formats) self.output_formats.addItems(str(x.upper()) for x in output_formats) self.input_formats.setCurrentIndex(input_formats.index(input_format)) self.output_formats.setCurrentIndex( output_formats.index(preferred_output_format)) def show_pane(self, index): if hasattr(index, 'row'): index = index.row() ow = self.scrollArea.takeWidget() if ow: ow.setParent(self) for i, w in enumerate(self.widgets): if i == index: self.scrollArea.setWidget(w) w.show() else: w.setVisible(False) def accept(self): recs = GuiRecommendations() for w in self._groups_model.widgets: if not w.pre_commit_check(): return x = w.commit(save_defaults=False) recs.update(x) self.opf_file, self.cover_file = self.mw.opf_file, self.mw.cover_file self._recommendations = recs if self.db is not None: recs['gui_preferred_input_format'] = self.input_format save_specifics(self.db, self.book_id, recs) self.break_cycles() QDialog.accept(self) def reject(self): self.break_cycles() QDialog.reject(self) def done(self, r): if self.isVisible(): gprefs['convert_single_dialog_geom'] = \ bytearray(self.saveGeometry()) return QDialog.done(self, r) def break_cycles(self): for w in self.widgets: w.break_cycles() @property def recommendations(self): recs = [(k, v, OptionRecommendation.HIGH) for k, v in self._recommendations.items()] return recs def show_group_help(self, index): widget = self._groups_model.widgets[index.row()] self.help.setPlainText(widget.HELP)