def do_test(self): selections = [] self.match_locs = [] class Pos: python: int = 0 qt: int = 0 if self.regex_valid(): text = str(self.preview.toPlainText()) regex = str(self.regex.text()) cursor = QTextCursor(self.preview.document()) extsel = QTextEdit.ExtraSelection() extsel.cursor = cursor extsel.format.setBackground(QBrush(Qt.GlobalColor.yellow)) with suppress(Exception): prev = Pos() for match in compile_regular_expression(regex).finditer(text): es = QTextEdit.ExtraSelection(extsel) qtchars_to_start = utf16_length( text[prev.python:match.start()]) qt_pos = prev.qt + qtchars_to_start prev.python = match.end() prev.qt = qt_pos + utf16_length(match.group()) es.cursor.setPosition(qt_pos, QTextCursor.MoveMode.MoveAnchor) es.cursor.setPosition(prev.qt, QTextCursor.MoveMode.KeepAnchor) selections.append(es) self.match_locs.append((qt_pos, prev.qt)) self.preview.setExtraSelections(selections) if self.match_locs: self.next.setEnabled(True) self.previous.setEnabled(True) self.occurrences.setText(str(len(self.match_locs)))
def do_test(self): selections = [] self.match_locs = [] if self.regex_valid(): text = unicode_type(self.preview.toPlainText()) regex = unicode_type(self.regex.text()) cursor = QTextCursor(self.preview.document()) extsel = QTextEdit.ExtraSelection() extsel.cursor = cursor extsel.format.setBackground(QBrush(Qt.GlobalColor.yellow)) try: for match in compile_regular_expression(regex).finditer(text): es = QTextEdit.ExtraSelection(extsel) es.cursor.setPosition(match.start(), QTextCursor.MoveMode.MoveAnchor) es.cursor.setPosition(match.end(), QTextCursor.MoveMode.KeepAnchor) selections.append(es) self.match_locs.append((match.start(), match.end())) except: pass self.preview.setExtraSelections(selections) if self.match_locs: self.next.setEnabled(True) self.previous.setEnabled(True) self.occurrences.setText(unicode_type(len(self.match_locs)))
def setup_ui(self): self.l = l = QVBoxLayout(self) self.text = t = QTextEdit(self) t.setReadOnly(True) l.addWidget(t), l.addWidget(self.bb) self.bb.clear(), self.bb.setStandardButtons( QDialogButtonBox.StandardButton.Close)
def __init__(self, parent): QDialog.__init__(self, parent) self.gui = parent self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False) self.setWindowIcon(QIcon(I('polish.png'))) self.reports = [] self.l = l = QGridLayout() self.setLayout(l) self.view = v = QTextEdit(self) v.setReadOnly(True) l.addWidget(self.view, 0, 0, 1, 2) self.backup_msg = la = QLabel('') l.addWidget(la, 1, 0, 1, 2) la.setVisible(False) la.setWordWrap(True) self.ign = QCheckBox(_('Ignore remaining reports'), self) l.addWidget(self.ign, 2, 0) bb = self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) b = self.log_button = bb.addButton( _('View full &log'), QDialogButtonBox.ButtonRole.ActionRole) b.clicked.connect(self.view_log) bb.button(QDialogButtonBox.StandardButton.Close).setDefault(True) l.addWidget(bb, 2, 1) self.finished.connect(self.show_next, type=Qt.ConnectionType.QueuedConnection) self.resize(QSize(800, 600))
def highlight_cursor_line(self): sel = QTextEdit.ExtraSelection() sel.format.setBackground(self.palette().alternateBase()) sel.format.setProperty(QTextFormat.Property.FullWidthSelection, True) sel.cursor = self.textCursor() sel.cursor.clearSelection() self.setExtraSelections([ sel, ])
def highlight_cursor_line(self): sel = QTextEdit.ExtraSelection() # Don't highlight if no text so that the placeholder text shows if not (self.blockCount() == 1 and len(self.toPlainText().strip()) == 0): sel.format.setBackground(self.palette().alternateBase()) sel.format.setProperty(QTextFormat.Property.FullWidthSelection, True) sel.cursor = self.textCursor() sel.cursor.clearSelection() self.setExtraSelections([sel,])
def setup_ui(self): l = QVBoxLayout(self) self.qte = qte = QTextEdit(self) qte.setMinimumHeight(400) qte.setMinimumWidth(600) if self.initial_notes: qte.setPlainText(self.initial_notes) l.addWidget(qte) l.addWidget(self.bb)
def setup_ui(self): l = QVBoxLayout(self) self.qte = qte = QTextEdit(self) qte.setMinimumHeight(400) qte.setMinimumWidth(600) if self.initial_notes: qte.setPlainText(self.initial_notes) qte.moveCursor(QTextCursor.MoveOperation.End) l.addWidget(qte) l.addWidget(self.bb)
def mark_selected_text(self): sel = QTextEdit.ExtraSelection() sel.format.setBackground(self.highlight_color) sel.cursor = self.textCursor() if sel.cursor.hasSelection(): self.current_search_mark = sel c = self.textCursor() c.clearSelection() self.setTextCursor(c) else: self.current_search_mark = None self.update_extra_selections()
def setup_ui(self): self.splitter = QSplitter(self) self.l = l = QVBoxLayout(self) l.addWidget(self.splitter) l.addWidget(self.bb) self.w = w = QGroupBox(_('Theme Metadata'), self) self.splitter.addWidget(w) l = w.l = QFormLayout(w) l.setFieldGrowthPolicy( QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) self.missing_icons_group = mg = QGroupBox(self) self.mising_icons = mi = QListWidget(mg) mi.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) mg.l = QVBoxLayout(mg) mg.l.addWidget(mi) self.splitter.addWidget(mg) self.title = QLineEdit(self) l.addRow(_('&Title:'), self.title) self.author = QLineEdit(self) l.addRow(_('&Author:'), self.author) self.version = v = QSpinBox(self) v.setMinimum(1), v.setMaximum(1000000) l.addRow(_('&Version:'), v) self.license = lc = QLineEdit(self) l.addRow(_('&License:'), lc) self.url = QLineEdit(self) l.addRow(_('&URL:'), self.url) lc.setText( _('The license for the icons in this theme. Common choices are' ' Creative Commons or Public Domain.')) self.description = QTextEdit(self) l.addRow(self.description) self.refresh_button = rb = self.bb.addButton( _('&Refresh'), QDialogButtonBox.ButtonRole.ActionRole) rb.setIcon(QIcon(I('view-refresh.png'))) rb.clicked.connect(self.refresh) self.apply_report()
def keyPressEvent(self, ev): if ev.matches(QKeySequence.StandardKey.Bold): ev.accept() self.action_bold.toggle(), self.action_bold.trigger() return if ev.matches(QKeySequence.StandardKey.Italic): ev.accept() self.action_italic.toggle(), self.action_italic.trigger() return if ev.matches(QKeySequence.StandardKey.Underline): ev.accept() self.action_underline.toggle(), self.action_underline.trigger() return return QTextEdit.keyPressEvent(self, ev)
def highlight_cursor_line(self): sel = QTextEdit.ExtraSelection() sel.format.setBackground(self.palette().alternateBase()) sel.format.setProperty(QTextFormat.Property.FullWidthSelection, True) sel.cursor = self.textCursor() sel.cursor.clearSelection() self.current_cursor_line = sel self.update_extra_selections(instant=False) # Update the cursor line's line number in the line number area try: self.line_number_area.update(0, self.last_current_lnum[0], self.line_number_area.width(), self.last_current_lnum[1]) except AttributeError: pass block = self.textCursor().block() top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) height = int(self.blockBoundingRect(block).height()) self.line_number_area.update(0, top, self.line_number_area.width(), height)
def add_tag(tag): a = QTextEdit.ExtraSelection() a.cursor, a.format = editor.textCursor(), editor.match_paren_format a.cursor.setPosition( tag.start_block.position()), a.cursor.movePosition( QTextCursor.MoveOperation.EndOfBlock, QTextCursor.MoveMode.KeepAnchor) text = unicode_type(a.cursor.selectedText()) start_pos = utf16_length(text[:tag.start_offset]) a.cursor.setPosition( tag.end_block.position()), a.cursor.movePosition( QTextCursor.MoveOperation.EndOfBlock, QTextCursor.MoveMode.KeepAnchor) text = unicode_type(a.cursor.selectedText()) end_pos = utf16_length(text[:tag.end_offset + 1]) a.cursor.setPosition(tag.start_block.position() + start_pos) a.cursor.setPosition(tag.end_block.position() + end_pos, QTextCursor.MoveMode.KeepAnchor) ans.append(a)
def __init__(self, parent=None): QTextEdit.__init__(self, parent) self.setTabChangesFocus(True) self.document().setDefaultStyleSheet(css() + '\n\nli { margin-top: 0.5ex; margin-bottom: 0.5ex; }') font = self.font() f = QFontInfo(font) delta = tweaks['change_book_details_font_size_by'] + 1 if delta: font.setPixelSize(f.pixelSize() + delta) self.setFont(font) f = QFontMetrics(self.font()) self.em_size = f.horizontalAdvance('m') self.base_url = None self._parent = weakref.ref(parent) self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL) for rec in ( ('bold', 'format-text-bold', _('Bold'), True), ('italic', 'format-text-italic', _('Italic'), True), ('underline', 'format-text-underline', _('Underline'), True), ('strikethrough', 'format-text-strikethrough', _('Strikethrough'), True), ('superscript', 'format-text-superscript', _('Superscript'), True), ('subscript', 'format-text-subscript', _('Subscript'), True), ('ordered_list', 'format-list-ordered', _('Ordered list'), True), ('unordered_list', 'format-list-unordered', _('Unordered list'), True), ('align_left', 'format-justify-left', _('Align left'), True), ('align_center', 'format-justify-center', _('Align center'), True), ('align_right', 'format-justify-right', _('Align right'), True), ('align_justified', 'format-justify-fill', _('Align justified'), True), ('undo', 'edit-undo', _('Undo'), ), ('redo', 'edit-redo', _('Redo'), ), ('remove_format', 'edit-clear', _('Remove formatting'), ), ('copy', 'edit-copy', _('Copy'), ), ('paste', 'edit-paste', _('Paste'), ), ('paste_and_match_style', 'edit-paste', _('Paste and match style'), ), ('cut', 'edit-cut', _('Cut'), ), ('indent', 'format-indent-more', _('Increase indentation'), ), ('outdent', 'format-indent-less', _('Decrease indentation'), ), ('select_all', 'edit-select-all', _('Select all'), ), ('color', 'format-text-color', _('Foreground color')), ('background', 'format-fill-color', _('Background color')), ('insert_link', 'insert-link', _('Insert link or image'),), ('insert_hr', 'format-text-hr', _('Insert separator'),), ('clear', 'trash', _('Clear')), ): name, icon, text = rec[:3] checkable = len(rec) == 4 ac = QAction(QIcon(I(icon + '.png')), text, self) if checkable: ac.setCheckable(checkable) setattr(self, 'action_'+name, ac) ac.triggered.connect(getattr(self, 'do_' + name)) self.action_block_style = QAction(QIcon(I('format-text-heading.png')), _('Style text block'), self) self.action_block_style.setToolTip( _('Style the selected text block')) self.block_style_menu = QMenu(self) self.action_block_style.setMenu(self.block_style_menu) self.block_style_actions = [] h = _('Heading {0}') for text, name in ( (_('Normal'), 'p'), (h.format(1), 'h1'), (h.format(2), 'h2'), (h.format(3), 'h3'), (h.format(4), 'h4'), (h.format(5), 'h5'), (h.format(6), 'h6'), (_('Blockquote'), 'blockquote'), ): ac = QAction(text, self) self.block_style_menu.addAction(ac) ac.block_name = name ac.setCheckable(True) self.block_style_actions.append(ac) ac.triggered.connect(self.do_format_block) self.setHtml('') self.copyAvailable.connect(self.update_clipboard_actions) self.update_clipboard_actions(False) self.selectionChanged.connect(self.update_selection_based_actions) self.update_selection_based_actions() connect_lambda(self.undoAvailable, self, lambda self, yes: self.action_undo.setEnabled(yes)) connect_lambda(self.redoAvailable, self, lambda self, yes: self.action_redo.setEnabled(yes)) self.action_undo.setEnabled(False), self.action_redo.setEnabled(False) self.textChanged.connect(self.update_cursor_position_actions) self.cursorPositionChanged.connect(self.update_cursor_position_actions) self.textChanged.connect(self.data_changed) self.update_cursor_position_actions()
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)
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)
class CheckLibraryDialog(QDialog): is_deletable = 1 is_fixable = 2 def __init__(self, parent, db): QDialog.__init__(self, parent) self.db = db self.setWindowTitle(_('Check library -- Problems found')) self.setWindowIcon(QIcon(I('debug.png'))) self._tl = QHBoxLayout() self.setLayout(self._tl) self.splitter = QSplitter(self) self.left = QWidget(self) self.splitter.addWidget(self.left) self.helpw = QTextEdit(self) self.splitter.addWidget(self.helpw) self._tl.addWidget(self.splitter) self._layout = QVBoxLayout() self.left.setLayout(self._layout) self.helpw.setReadOnly(True) self.helpw.setText( _('''\ <h1>Help</h1> <p>calibre stores the list of your books and their metadata in a database. The actual book files and covers are stored as normal files in the calibre library folder. The database contains a list of the files and covers belonging to each book entry. This tool checks that the actual files in the library folder on your computer match the information in the database.</p> <p>The result of each type of check is shown to the left. The various checks are: </p> <ul> <li><b>Invalid titles</b>: These are files and folders appearing in the library where books titles should, but that do not have the correct form to be a book title.</li> <li><b>Extra titles</b>: These are extra files in your calibre library that appear to be correctly-formed titles, but have no corresponding entries in the database.</li> <li><b>Invalid authors</b>: These are files appearing in the library where only author folders should be.</li> <li><b>Extra authors</b>: These are folders in the calibre library that appear to be authors but that do not have entries in the database.</li> <li><b>Missing book formats</b>: These are book formats that are in the database but have no corresponding format file in the book's folder. <li><b>Extra book formats</b>: These are book format files found in the book's folder but not in the database. <li><b>Unknown files in books</b>: These are extra files in the folder of each book that do not correspond to a known format or cover file.</li> <li><b>Missing cover files</b>: These represent books that are marked in the database as having covers but the actual cover files are missing.</li> <li><b>Cover files not in database</b>: These are books that have cover files but are marked as not having covers in the database.</li> <li><b>Folder raising exception</b>: These represent folders in the calibre library that could not be processed/understood by this tool.</li> </ul> <p>There are two kinds of automatic fixes possible: <i>Delete marked</i> and <i>Fix marked</i>.</p> <p><i>Delete marked</i> is used to remove extra files/folders/covers that have no entries in the database. Check the box next to the item you want to delete. Use with caution.</p> <p><i>Fix marked</i> is applicable only to covers and missing formats (the three lines marked 'fixable'). In the case of missing cover files, checking the fixable box and pushing this button will tell calibre that there is no cover for all of the books listed. Use this option if you are not going to restore the covers from a backup. In the case of extra cover files, checking the fixable box and pushing this button will tell calibre that the cover files it found are correct for all the books listed. Use this when you are not going to delete the file(s). In the case of missing formats, checking the fixable box and pushing this button will tell calibre that the formats are really gone. Use this if you are not going to restore the formats from a backup.</p> ''')) self.log = QTreeWidget(self) self.log.itemChanged.connect(self.item_changed) self.log.itemExpanded.connect(self.item_expanded_or_collapsed) self.log.itemCollapsed.connect(self.item_expanded_or_collapsed) self._layout.addWidget(self.log) self.check_button = QPushButton(_('&Run the check again')) self.check_button.setDefault(False) self.check_button.clicked.connect(self.run_the_check) self.copy_button = QPushButton(_('Copy &to clipboard')) self.copy_button.setDefault(False) self.copy_button.clicked.connect(self.copy_to_clipboard) self.ok_button = QPushButton(_('&Done')) self.ok_button.setDefault(True) self.ok_button.clicked.connect(self.accept) self.mark_delete_button = QPushButton(_('Mark &all for delete')) self.mark_delete_button.setToolTip(_('Mark all deletable subitems')) self.mark_delete_button.setDefault(False) self.mark_delete_button.clicked.connect(self.mark_for_delete) self.delete_button = QPushButton(_('Delete &marked')) self.delete_button.setToolTip( _('Delete marked files (checked subitems)')) self.delete_button.setDefault(False) self.delete_button.clicked.connect(self.delete_marked) self.mark_fix_button = QPushButton(_('Mar&k all for fix')) self.mark_fix_button.setToolTip(_('Mark all fixable items')) self.mark_fix_button.setDefault(False) self.mark_fix_button.clicked.connect(self.mark_for_fix) self.fix_button = QPushButton(_('&Fix marked')) self.fix_button.setDefault(False) self.fix_button.setEnabled(False) self.fix_button.setToolTip( _('Fix marked sections (checked fixable items)')) self.fix_button.clicked.connect(self.fix_items) self.bbox = QGridLayout() self.bbox.addWidget(self.check_button, 0, 0) self.bbox.addWidget(self.copy_button, 0, 1) self.bbox.addWidget(self.ok_button, 0, 2) self.bbox.addWidget(self.mark_delete_button, 1, 0) self.bbox.addWidget(self.delete_button, 1, 1) self.bbox.addWidget(self.mark_fix_button, 2, 0) self.bbox.addWidget(self.fix_button, 2, 1) h = QHBoxLayout() ln = QLabel(_('Names to ignore:')) h.addWidget(ln) self.name_ignores = QLineEdit() self.name_ignores.setText( db.new_api.pref('check_library_ignore_names', '')) self.name_ignores.setToolTip( _('Enter comma-separated standard file name wildcards, such as synctoy*.dat' )) ln.setBuddy(self.name_ignores) h.addWidget(self.name_ignores) le = QLabel(_('Extensions to ignore:')) h.addWidget(le) self.ext_ignores = QLineEdit() self.ext_ignores.setText( db.new_api.pref('check_library_ignore_extensions', '')) self.ext_ignores.setToolTip( _('Enter comma-separated extensions without a leading dot. Used only in book folders' )) le.setBuddy(self.ext_ignores) h.addWidget(self.ext_ignores) self._layout.addLayout(h) self._layout.addLayout(self.bbox) self.resize(950, 500) def do_exec(self): self.run_the_check() probs = 0 for c in self.problem_count: probs += self.problem_count[c] if probs == 0: return False self.exec_() return True def accept(self): self.db.new_api.set_pref('check_library_ignore_extensions', str(self.ext_ignores.text())) self.db.new_api.set_pref('check_library_ignore_names', str(self.name_ignores.text())) QDialog.accept(self) def box_to_list(self, txt): return [f.strip() for f in txt.split(',') if f.strip()] def run_the_check(self): checker = CheckLibrary(self.db.library_path, self.db) checker.scan_library(self.box_to_list(str(self.name_ignores.text())), self.box_to_list(str(self.ext_ignores.text()))) plaintext = [] def builder(tree, checker, check): attr, h, checkable, fixable = check list_ = getattr(checker, attr, None) if list_ is None: self.problem_count[attr] = 0 return else: self.problem_count[attr] = len(list_) tl = Item() tl.setText(0, h) if fixable and list: tl.setData(1, Qt.ItemDataRole.UserRole, self.is_fixable) tl.setText(1, _('(fixable)')) tl.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) tl.setCheckState(1, False) else: tl.setData(1, Qt.ItemDataRole.UserRole, self.is_deletable) tl.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable) tl.setText(1, _('(deletable)')) tl.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) tl.setCheckState(1, False) if attr == 'extra_covers': tl.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable) tl.setText(2, _('(deletable)')) tl.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) tl.setCheckState(2, False) self.top_level_items[attr] = tl for problem in list_: it = Item() if checkable: it.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) it.setCheckState(2, False) it.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable) else: it.setFlags(Qt.ItemFlag.ItemIsEnabled) it.setText(0, problem[0]) it.setData(0, Qt.ItemDataRole.UserRole, problem[2]) it.setText(2, problem[1]) tl.addChild(it) self.all_items.append(it) plaintext.append(','.join([h, problem[0], problem[1]])) tree.addTopLevelItem(tl) t = self.log t.clear() t.setColumnCount(3) t.setHeaderLabels([_('Name'), '', _('Path from library')]) self.all_items = [] self.top_level_items = {} self.problem_count = {} for check in CHECKS: builder(t, checker, check) t.resizeColumnToContents(0) t.resizeColumnToContents(1) self.delete_button.setEnabled(False) self.fix_button.setEnabled(False) self.text_results = '\n'.join(plaintext) def item_expanded_or_collapsed(self, item): self.log.resizeColumnToContents(0) self.log.resizeColumnToContents(1) def item_changed(self, item, column): def set_delete_boxes(node, col, to_what): self.log.blockSignals(True) if col: node.setCheckState(col, to_what) for i in range(0, node.childCount()): node.child(i).setCheckState(2, to_what) self.log.blockSignals(False) def is_child_delete_checked(node): checked = False all_checked = True for i in range(0, node.childCount()): c = node.child(i).checkState(2) checked = checked or c == Qt.CheckState.Checked all_checked = all_checked and c == Qt.CheckState.Checked return (checked, all_checked) def any_child_delete_checked(): for parent in self.top_level_items.values(): (c, _) = is_child_delete_checked(parent) if c: return True return False def any_fix_checked(): for parent in self.top_level_items.values(): if (parent.data(1, Qt.ItemDataRole.UserRole) == self.is_fixable and parent.checkState(1) == Qt.CheckState.Checked): return True return False if item in self.top_level_items.values(): if item.childCount() > 0: if item.data(1, Qt.ItemDataRole.UserRole ) == self.is_fixable and column == 1: if item.data( 2, Qt.ItemDataRole.UserRole) == self.is_deletable: set_delete_boxes(item, 2, False) else: set_delete_boxes(item, column, item.checkState(column)) if column == 2: self.log.blockSignals(True) item.setCheckState(1, False) self.log.blockSignals(False) else: item.setCheckState(column, Qt.CheckState.Unchecked) else: for parent in self.top_level_items.values(): if parent.data(2, Qt.ItemDataRole.UserRole) == self.is_deletable: (child_chkd, all_chkd) = is_child_delete_checked(parent) if all_chkd and child_chkd: check_state = Qt.CheckState.Checked elif child_chkd: check_state = Qt.CheckState.PartiallyChecked else: check_state = Qt.CheckState.Unchecked self.log.blockSignals(True) if parent.data( 1, Qt.ItemDataRole.UserRole) == self.is_fixable: parent.setCheckState(2, check_state) else: parent.setCheckState(1, check_state) if child_chkd and parent.data( 1, Qt.ItemDataRole.UserRole) == self.is_fixable: parent.setCheckState(1, Qt.CheckState.Unchecked) self.log.blockSignals(False) self.delete_button.setEnabled(any_child_delete_checked()) self.fix_button.setEnabled(any_fix_checked()) def mark_for_fix(self): for it in self.top_level_items.values(): if (it.flags() & Qt.ItemFlag.ItemIsUserCheckable and it.data(1, Qt.ItemDataRole.UserRole) == self.is_fixable and it.childCount() > 0): it.setCheckState(1, Qt.CheckState.Checked) def mark_for_delete(self): for it in self.all_items: if (it.flags() & Qt.ItemFlag.ItemIsUserCheckable and it.data( 2, Qt.ItemDataRole.UserRole) == self.is_deletable): it.setCheckState(2, Qt.CheckState.Checked) def delete_marked(self): if not confirm( '<p>' + _('The marked files and folders will be ' '<b>permanently deleted</b>. Are you sure?') + '</p>', 'check_library_editor_delete', self): return # Sort the paths in reverse length order so that we can be sure that # if an item is in another item, the sub-item will be deleted first. items = sorted(self.all_items, key=lambda x: len(x.text(1)), reverse=True) for it in items: if it.checkState(2) == Qt.CheckState.Checked: try: p = os.path.join(self.db.library_path, str(it.text(2))) if os.path.isdir(p): delete_tree(p) else: delete_file(p) except: prints('failed to delete', os.path.join(self.db.library_path, str(it.text(2)))) self.run_the_check() def fix_missing_formats(self): tl = self.top_level_items['missing_formats'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.ItemDataRole.UserRole)) all = self.db.formats(id, index_is_id=True, verify_formats=False) all = {f.strip() for f in all.split(',')} if all else set() valid = self.db.formats(id, index_is_id=True, verify_formats=True) valid = {f.strip() for f in valid.split(',')} if valid else set() for fmt in all - valid: self.db.remove_format(id, fmt, index_is_id=True, db_only=True) def fix_missing_covers(self): tl = self.top_level_items['missing_covers'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.ItemDataRole.UserRole)) self.db.set_has_cover(id, False) def fix_extra_covers(self): tl = self.top_level_items['extra_covers'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.ItemDataRole.UserRole)) self.db.set_has_cover(id, True) def fix_items(self): for check in CHECKS: attr = check[0] fixable = check[3] tl = self.top_level_items[attr] if fixable and tl.checkState(1): func = getattr(self, 'fix_' + attr, None) if func is not None and callable(func): func() self.run_the_check() def copy_to_clipboard(self): QApplication.clipboard().setText(self.text_results)
def __init__(self, parent, db): QDialog.__init__(self, parent) self.db = db self.setWindowTitle(_('Check library -- Problems found')) self.setWindowIcon(QIcon(I('debug.png'))) self._tl = QHBoxLayout() self.setLayout(self._tl) self.splitter = QSplitter(self) self.left = QWidget(self) self.splitter.addWidget(self.left) self.helpw = QTextEdit(self) self.splitter.addWidget(self.helpw) self._tl.addWidget(self.splitter) self._layout = QVBoxLayout() self.left.setLayout(self._layout) self.helpw.setReadOnly(True) self.helpw.setText( _('''\ <h1>Help</h1> <p>calibre stores the list of your books and their metadata in a database. The actual book files and covers are stored as normal files in the calibre library folder. The database contains a list of the files and covers belonging to each book entry. This tool checks that the actual files in the library folder on your computer match the information in the database.</p> <p>The result of each type of check is shown to the left. The various checks are: </p> <ul> <li><b>Invalid titles</b>: These are files and folders appearing in the library where books titles should, but that do not have the correct form to be a book title.</li> <li><b>Extra titles</b>: These are extra files in your calibre library that appear to be correctly-formed titles, but have no corresponding entries in the database.</li> <li><b>Invalid authors</b>: These are files appearing in the library where only author folders should be.</li> <li><b>Extra authors</b>: These are folders in the calibre library that appear to be authors but that do not have entries in the database.</li> <li><b>Missing book formats</b>: These are book formats that are in the database but have no corresponding format file in the book's folder. <li><b>Extra book formats</b>: These are book format files found in the book's folder but not in the database. <li><b>Unknown files in books</b>: These are extra files in the folder of each book that do not correspond to a known format or cover file.</li> <li><b>Missing cover files</b>: These represent books that are marked in the database as having covers but the actual cover files are missing.</li> <li><b>Cover files not in database</b>: These are books that have cover files but are marked as not having covers in the database.</li> <li><b>Folder raising exception</b>: These represent folders in the calibre library that could not be processed/understood by this tool.</li> </ul> <p>There are two kinds of automatic fixes possible: <i>Delete marked</i> and <i>Fix marked</i>.</p> <p><i>Delete marked</i> is used to remove extra files/folders/covers that have no entries in the database. Check the box next to the item you want to delete. Use with caution.</p> <p><i>Fix marked</i> is applicable only to covers and missing formats (the three lines marked 'fixable'). In the case of missing cover files, checking the fixable box and pushing this button will tell calibre that there is no cover for all of the books listed. Use this option if you are not going to restore the covers from a backup. In the case of extra cover files, checking the fixable box and pushing this button will tell calibre that the cover files it found are correct for all the books listed. Use this when you are not going to delete the file(s). In the case of missing formats, checking the fixable box and pushing this button will tell calibre that the formats are really gone. Use this if you are not going to restore the formats from a backup.</p> ''')) self.log = QTreeWidget(self) self.log.itemChanged.connect(self.item_changed) self.log.itemExpanded.connect(self.item_expanded_or_collapsed) self.log.itemCollapsed.connect(self.item_expanded_or_collapsed) self._layout.addWidget(self.log) self.check_button = QPushButton(_('&Run the check again')) self.check_button.setDefault(False) self.check_button.clicked.connect(self.run_the_check) self.copy_button = QPushButton(_('Copy &to clipboard')) self.copy_button.setDefault(False) self.copy_button.clicked.connect(self.copy_to_clipboard) self.ok_button = QPushButton(_('&Done')) self.ok_button.setDefault(True) self.ok_button.clicked.connect(self.accept) self.mark_delete_button = QPushButton(_('Mark &all for delete')) self.mark_delete_button.setToolTip(_('Mark all deletable subitems')) self.mark_delete_button.setDefault(False) self.mark_delete_button.clicked.connect(self.mark_for_delete) self.delete_button = QPushButton(_('Delete &marked')) self.delete_button.setToolTip( _('Delete marked files (checked subitems)')) self.delete_button.setDefault(False) self.delete_button.clicked.connect(self.delete_marked) self.mark_fix_button = QPushButton(_('Mar&k all for fix')) self.mark_fix_button.setToolTip(_('Mark all fixable items')) self.mark_fix_button.setDefault(False) self.mark_fix_button.clicked.connect(self.mark_for_fix) self.fix_button = QPushButton(_('&Fix marked')) self.fix_button.setDefault(False) self.fix_button.setEnabled(False) self.fix_button.setToolTip( _('Fix marked sections (checked fixable items)')) self.fix_button.clicked.connect(self.fix_items) self.bbox = QGridLayout() self.bbox.addWidget(self.check_button, 0, 0) self.bbox.addWidget(self.copy_button, 0, 1) self.bbox.addWidget(self.ok_button, 0, 2) self.bbox.addWidget(self.mark_delete_button, 1, 0) self.bbox.addWidget(self.delete_button, 1, 1) self.bbox.addWidget(self.mark_fix_button, 2, 0) self.bbox.addWidget(self.fix_button, 2, 1) h = QHBoxLayout() ln = QLabel(_('Names to ignore:')) h.addWidget(ln) self.name_ignores = QLineEdit() self.name_ignores.setText( db.new_api.pref('check_library_ignore_names', '')) self.name_ignores.setToolTip( _('Enter comma-separated standard file name wildcards, such as synctoy*.dat' )) ln.setBuddy(self.name_ignores) h.addWidget(self.name_ignores) le = QLabel(_('Extensions to ignore:')) h.addWidget(le) self.ext_ignores = QLineEdit() self.ext_ignores.setText( db.new_api.pref('check_library_ignore_extensions', '')) self.ext_ignores.setToolTip( _('Enter comma-separated extensions without a leading dot. Used only in book folders' )) le.setBuddy(self.ext_ignores) h.addWidget(self.ext_ignores) self._layout.addLayout(h) self._layout.addLayout(self.bbox) self.resize(950, 500)
unit = { QPageSize.Unit.Millimeter: 'mm', QPageSize.Unit.Inch: 'inch' }[QPageSize.definitionUnits(s)] name = f'{QPageSize.name(s)} ({sz.width():g} x {sz.height():g} {unit})' self.addItem(name, a) @property def get_value_for_config(self): return self.currentData() @get_value_for_config.setter def set_value_for_config(self, val): idx = self.findData(val or PaperSizes.system_default_paper_size) if idx == -1: idx = self.findData('a4') self.setCurrentIndex(idx) # }}} if __name__ == '__main__': from qt.core import QTextEdit app = QApplication([]) w = QTextEdit() s = PythonHighlighter(w) # w.setSyntaxHighlighter(s) w.setText(open(__file__, 'rb').read().decode('utf-8')) w.show() app.exec()
class ThemeCreateDialog(Dialog): def __init__(self, parent, report): self.report = report Dialog.__init__(self, _('Create an icon theme'), 'create-icon-theme', parent) def setup_ui(self): self.splitter = QSplitter(self) self.l = l = QVBoxLayout(self) l.addWidget(self.splitter) l.addWidget(self.bb) self.w = w = QGroupBox(_('Theme Metadata'), self) self.splitter.addWidget(w) l = w.l = QFormLayout(w) l.setFieldGrowthPolicy( QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) self.missing_icons_group = mg = QGroupBox(self) self.mising_icons = mi = QListWidget(mg) mi.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) mg.l = QVBoxLayout(mg) mg.l.addWidget(mi) self.splitter.addWidget(mg) self.title = QLineEdit(self) l.addRow(_('&Title:'), self.title) self.author = QLineEdit(self) l.addRow(_('&Author:'), self.author) self.version = v = QSpinBox(self) v.setMinimum(1), v.setMaximum(1000000) l.addRow(_('&Version:'), v) self.license = lc = QLineEdit(self) l.addRow(_('&License:'), lc) self.url = QLineEdit(self) l.addRow(_('&URL:'), self.url) lc.setText( _('The license for the icons in this theme. Common choices are' ' Creative Commons or Public Domain.')) self.description = QTextEdit(self) l.addRow(self.description) self.refresh_button = rb = self.bb.addButton( _('&Refresh'), QDialogButtonBox.ButtonRole.ActionRole) rb.setIcon(QIcon(I('view-refresh.png'))) rb.clicked.connect(self.refresh) self.apply_report() def sizeHint(self): return QSize(900, 670) @property def metadata(self): self.report.theme.title = self.title.text().strip( ) # Needed for report.name to work return { 'title': self.title.text().strip(), 'author': self.author.text().strip(), 'version': self.version.value(), 'description': self.description.toPlainText().strip(), 'number': len(self.report.name_map) - len(self.report.extra), 'date': utcnow().date().isoformat(), 'name': self.report.name, 'license': self.license.text().strip() or 'Unknown', 'url': self.url.text().strip() or None, } def save_metadata(self): data = json.dumps(self.metadata, indent=2) if not isinstance(data, bytes): data = data.encode('utf-8') with open(os.path.join(self.report.path, THEME_METADATA), 'wb') as f: f.write(data) def refresh(self): self.save_metadata() self.report = read_theme_from_folder(self.report.path) self.apply_report() def apply_report(self): theme = self.report.theme self.title.setText((theme.title or '').strip()) self.author.setText((theme.author or '').strip()) self.version.setValue(theme.version or 1) self.description.setText((theme.description or '').strip()) self.license.setText((theme.license or 'Unknown').strip()) self.url.setText((theme.url or '').strip()) if self.report.missing: title = _('%d icons missing in this theme') % len( self.report.missing) else: title = _('No missing icons') self.missing_icons_group.setTitle(title) mi = self.mising_icons mi.clear() for name in sorted(self.report.missing): QListWidgetItem(QIcon(I(name, allow_user_override=False)), name, mi) def accept(self): mi = self.metadata if not mi.get('title'): return error_dialog( self, _('No title specified'), _('You must specify a title for this icon theme'), show=True) if not mi.get('author'): return error_dialog( self, _('No author specified'), _('You must specify an author for this icon theme'), show=True) return Dialog.accept(self)