class EditRules(QWidget): # {{{ changed = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) self.enabled = c = QCheckBox(self) l.addWidget(c, l.rowCount(), 0, 1, 2) c.setVisible(False) c.stateChanged.connect(self.changed) self.l1 = l1 = QLabel('') l1.setWordWrap(True) l.addWidget(l1, l.rowCount(), 0, 1, 2) self.add_button = QPushButton(QIcon(I('plus.png')), _('&Add rule'), self) self.remove_button = QPushButton(QIcon(I('minus.png')), _('&Remove rule(s)'), self) self.add_button.clicked.connect(self.add_rule) self.remove_button.clicked.connect(self.remove_rule) l.addWidget(self.add_button, l.rowCount(), 0) l.addWidget(self.remove_button, l.rowCount() - 1, 1) self.g = g = QGridLayout() self.rules_view = QListView(self) self.rules_view.doubleClicked.connect(self.edit_rule) self.rules_view.setSelectionMode(self.rules_view.ExtendedSelection) self.rules_view.setAlternatingRowColors(True) self.rtfd = RichTextDelegate(parent=self.rules_view, max_width=400) self.rules_view.setItemDelegate(self.rtfd) g.addWidget(self.rules_view, 0, 0, 2, 1) self.up_button = b = QToolButton(self) b.setIcon(QIcon(I('arrow-up.png'))) b.setToolTip(_('Move the selected rule up')) b.clicked.connect(self.move_up) g.addWidget(b, 0, 1, 1, 1, Qt.AlignTop) self.down_button = b = QToolButton(self) b.setIcon(QIcon(I('arrow-down.png'))) b.setToolTip(_('Move the selected rule down')) b.clicked.connect(self.move_down) g.addWidget(b, 1, 1, 1, 1, Qt.AlignBottom) l.addLayout(g, l.rowCount(), 0, 1, 2) l.setRowStretch(l.rowCount() - 1, 10) self.add_advanced_button = b = QPushButton(QIcon(I('plus.png')), _('Add ad&vanced rule'), self) b.clicked.connect(self.add_advanced) self.hb = hb = QHBoxLayout() l.addLayout(hb, l.rowCount(), 0, 1, 2) hb.addWidget(b) hb.addStretch(10) self.export_button = b = QPushButton(_('E&xport'), self) b.clicked.connect(self.export_rules) b.setToolTip(_('Export these rules to a file')) hb.addWidget(b) self.import_button = b = QPushButton(_('&Import'), self) b.setToolTip(_('Import rules from a file')) b.clicked.connect(self.import_rules) hb.addWidget(b) def initialize(self, fm, prefs, mi, pref_name): self.pref_name = pref_name self.model = RulesModel(prefs, fm, self.pref_name) self.rules_view.setModel(self.model) self.fm = fm self.mi = mi if pref_name == 'column_color_rules': text = _( 'You can control the color of columns in the' ' book list by creating "rules" that tell calibre' ' what color to use. Click the "Add rule" button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') elif pref_name == 'column_icon_rules': text = _( 'You can add icons to columns in the' ' book list by creating "rules" that tell calibre' ' what icon to use. Click the "Add rule" button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') elif pref_name == 'cover_grid_icon_rules': text = _('You can add emblems (small icons) that are displayed on the side of covers' ' in the Cover grid by creating "rules" that tell calibre' ' what image to use. Click the "Add rule" button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') self.enabled.setVisible(True) self.enabled.setChecked(gprefs['show_emblems']) self.enabled.setText(_('Show &emblems next to the covers')) self.enabled.stateChanged.connect(self.enabled_toggled) self.enabled.setToolTip(_( 'If checked, you can tell calibre to display icons of your choosing' ' next to the covers shown in the Cover grid, controlled by the' ' metadata of the book.')) self.enabled_toggled() self.l1.setText('<p>'+ text) def enabled_toggled(self): enabled = self.enabled.isChecked() for x in ('add_advanced_button', 'rules_view', 'up_button', 'down_button', 'add_button', 'remove_button'): getattr(self, x).setEnabled(enabled) def add_rule(self): d = RuleEditor(self.model.fm, self.pref_name) d.add_blank_condition() if d.exec_() == d.Accepted: kind, col, r = d.rule if kind and r and col: idx = self.model.add_rule(kind, col, r) self.rules_view.scrollTo(idx) self.changed.emit() def add_advanced(self): if self.pref_name == 'column_color_rules': td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='') if td.exec_() == td.Accepted: col, r = td.rule if r and col: idx = self.model.add_rule('color', col, r) self.rules_view.scrollTo(idx) self.changed.emit() else: if self.pref_name == 'cover_grid_icon_rules': td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, doing_emblem=True) else: td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, icon_field_key='') if td.exec_() == td.Accepted: typ, col, r = td.rule if typ and r and col: idx = self.model.add_rule(typ, col, r) self.rules_view.scrollTo(idx) self.changed.emit() def edit_rule(self, index): try: kind, col, rule = self.model.data(index, Qt.UserRole) except: return if isinstance(rule, Rule): d = RuleEditor(self.model.fm, self.pref_name) d.apply_rule(kind, col, rule) elif self.pref_name == 'column_color_rules': d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, color_field=col) elif self.pref_name == 'cover_grid_icon_rules': d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, doing_emblem=True) else: d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, icon_field_key=col, icon_rule_kind=kind) if d.exec_() == d.Accepted: if len(d.rule) == 2: # Convert template dialog rules to a triple d.rule = ('color', d.rule[0], d.rule[1]) kind, col, r = d.rule if kind and r is not None and col: self.model.replace_rule(index, kind, col, r) self.rules_view.scrollTo(index) self.changed.emit() def get_selected_row(self, txt): sm = self.rules_view.selectionModel() rows = list(sm.selectedRows()) if not rows: error_dialog(self, _('No rule selected'), _('No rule selected for %s.')%txt, show=True) return None return sorted(rows, reverse=True) def remove_rule(self): rows = self.get_selected_row(_('removal')) if rows is not None: for row in rows: self.model.remove_rule(row) self.changed.emit() def move_up(self): idx = self.rules_view.currentIndex() if idx.isValid(): idx = self.model.move(idx, -1) if idx is not None: sm = self.rules_view.selectionModel() sm.select(idx, sm.ClearAndSelect) self.rules_view.setCurrentIndex(idx) self.changed.emit() def move_down(self): idx = self.rules_view.currentIndex() if idx.isValid(): idx = self.model.move(idx, 1) if idx is not None: sm = self.rules_view.selectionModel() sm.select(idx, sm.ClearAndSelect) self.rules_view.setCurrentIndex(idx) self.changed.emit() def clear(self): self.model.clear() self.changed.emit() def commit(self, prefs): self.model.commit(prefs) if self.pref_name == 'cover_grid_icon_rules': gprefs['show_emblems'] = self.enabled.isChecked() def export_rules(self): path = choose_save_file(self, 'export-coloring-rules', _('Choose file to export to'), filters=[(_('Rules'), ['rules'])], all_files=False, initial_filename=self.pref_name + '.rules') if path: rules = { 'version': self.model.EXIM_VERSION, 'type': self.model.pref_name, 'rules': self.model.rules_as_list(for_export=True) } with lopen(path, 'wb') as f: f.write(json.dumps(rules, indent=2)) def import_rules(self): files = choose_files(self, 'import-coloring-rules', _('Choose file to import from'), filters=[(_('Rules'), ['rules'])], all_files=False, select_only_single_file=True) if files: with lopen(files[0], 'rb') as f: raw = f.read() try: rules = json.loads(raw) if rules['version'] != self.model.EXIM_VERSION: raise ValueError('Unsupported rules version: {}'.format(rules['version'])) if rules['type'] != self.pref_name: raise ValueError('Rules are not of the correct type') rules = list(rules['rules']) except Exception as e: return error_dialog(self, _('No valid rules found'), _( 'No valid rules were found in {}.').format(files[0]), det_msg=as_unicode(e), show=True) self.model.import_rules(rules) self.changed.emit()
class GameListWidget(QWidget): def __init__(self, main): super(GameListWidget, self).__init__() self.main_win = main self.keystore_exchanged = False self.setObjectName("GameListWidget") v_layout = QVBoxLayout() h_layout1 = QHBoxLayout() self.game_list_view = QListView() self.game_list_view.setViewMode(QListView.ListMode) self.game_list_view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.game_list_model = GameListModel(self.main_win.games) self.game_list_view.setModel(self.game_list_model) self.game_list_view.clicked.connect(self.list_item_onclick) h_layout1.addWidget(self.game_list_view, 1) self.game_list_view.setCurrentIndex( self.game_list_model.index(self.main_win.game_index)) self.game = self.main_win.games[self.main_win.game_index] form_layout = QFormLayout() form_layout.setContentsMargins(20, 50, 20, 50) self.game_name_value = QLineEdit() form_layout.addRow("游戏名称:", self.game_name_value) self.game_desc_value = QTextEdit() form_layout.addRow("游戏简介:", self.game_desc_value) self.game_appid_value = QLabel() self.game_appid_value.setTextInteractionFlags(Qt.TextSelectableByMouse) form_layout.addRow("游戏ID:", self.game_appid_value) self.game_appkey_value = QLabel() self.game_appkey_value.setTextInteractionFlags( Qt.TextSelectableByMouse) form_layout.addRow("客户端Key:", self.game_appkey_value) h_layout = QHBoxLayout() self.keystore_path = QLineEdit() select_key_btn = QPushButton("浏览") select_key_btn.setStyleSheet('QPushButton{border-radius: 0px;}') select_key_btn.clicked.connect(self.select_ketstore) h_layout.addWidget(self.keystore_path) h_layout.addWidget(select_key_btn) form_layout.addRow("KeyStore:", h_layout) self.keystore_pwd_value = QLineEdit() form_layout.addRow("KeyPass:"******"Alias:", self.keystore_alias_value) self.keystore_aliaspwd_value = QLineEdit() form_layout.addRow("AliasPass:"******"更换Icon") icon_exchange_btn.setFixedWidth(100) icon_exchange_btn.clicked.connect(self.exchange_icon) v_layout1.addWidget(icon_exchange_btn, alignment=Qt.AlignHCenter) v_layout1.addStretch(2) h_layout1.addLayout(v_layout1, 1) v_layout.addLayout(h_layout1) h_layout2 = QHBoxLayout() create_game_btn = QPushButton("返 回") create_game_btn.setFixedWidth(100) create_game_btn.clicked.connect(self.back) h_layout2.addWidget(create_game_btn, alignment=Qt.AlignLeft | Qt.AlignBottom) back_btn = QPushButton("创建游戏") back_btn.setFixedWidth(100) back_btn.clicked.connect(self.add_game) h_layout2.addWidget(back_btn, alignment=Qt.AlignHCenter) next_btn = QPushButton("下一步") next_btn.setFixedWidth(100) next_btn.clicked.connect(self.next) h_layout2.addWidget(next_btn, alignment=Qt.AlignRight | Qt.AlignBottom) v_layout.addLayout(h_layout2) self.setLayout(v_layout) self.set_game_info() def set_game_info(self): self.game_name_value.setText(self.game['name']) self.game_desc_value.setText(self.game['desc']) self.game_appid_value.setText(self.game['id']) self.game_appkey_value.setText(self.game['key']) self.keystore_path.setText(self.game['keystore']) self.keystore_pwd_value.setText(self.game['keypwd']) self.keystore_alias_value.setText(self.game['alias']) self.keystore_aliaspwd_value.setText(self.game['aliaspwd']) icon = Utils.get_full_path('games/' + self.game['id'] + '/icon/icon.png') if not os.path.exists(icon): icon = 'icon.png' self.icon_img.setPixmap(QPixmap(icon)) def list_item_onclick(self): self.main_win.game_index = self.game_list_view.currentIndex().row() self.game = self.main_win.games[self.main_win.game_index] self.set_game_info() def back(self): self.main_win.set_main_widget() def add_game(self): if not self.save_data(): return self.main_win.set_create_game_widget(self.main_win.games) def select_ketstore(self): fname = QFileDialog.getOpenFileName( self, '选择签名文件', Utils.get_full_path('games/' + self.game['id'] + '/keystore/')) if fname[0]: if os.path.samefile( fname[0], Utils.get_full_path('games/' + self.game['id'] + '/keystore/' + self.game['keystore'])): self.keystore_path.setText(self.game['keystore']) self.keystore_pwd_value.setText(self.game['keypwd']) self.keystore_alias_value.setText(self.game['alias']) self.keystore_aliaspwd_value.setText(self.game['aliaspwd']) self.keystore_exchanged = False else: self.keystore_path.setText(fname[0]) self.keystore_pwd_value.clear() self.keystore_alias_value.clear() self.keystore_aliaspwd_value.clear() self.keystore_exchanged = True def exchange_icon(self): fname = QFileDialog.getOpenFileName( self, '选择icon', Utils.get_full_path('games/' + self.game['id'] + '/icon/'), ("Images (*.png)")) if fname[0]: pix = QPixmap(fname[0]) if pix.width() != 512 or pix.height() != 512: QMessageBox.warning(self, "警告", "必须上传512*512.png图片") return self.icon_img.setPixmap(pix) current_icon = Utils.get_full_path('games/' + self.game['id'] + '/icon/icon.png') if os.path.exists(current_icon): if os.path.samefile(os.path.dirname(fname[0]), os.path.dirname(current_icon)): if not os.path.samefile( fname[0], current_icon ): # 如果选中的,在game的icon目录下,但不是当前icon,则进行重命名 count = 0 temp = 'icon0.png' while os.path.exists( Utils.get_full_path('games/' + self.game['id'] + '/icon/' + temp)): count += 1 temp = 'icon' + str(count) + '.png' os.renames( current_icon, Utils.get_full_path('games/' + self.game['id'] + '/icon/' + temp)) os.renames(fname[0], current_icon) else: # 如果所选的是当前icon,不做处理 return else: # 如果选中的不在game的icon目录下,则重命名当前icon,并将选中的icon复制到目录下作为当前icon count = 0 temp = 'icon0.png' while os.path.exists( Utils.get_full_path('games/' + self.game['id'] + '/icon/' + temp)): count += 1 temp = 'icon' + str(count) + '.png' os.renames( current_icon, Utils.get_full_path('games/' + self.game['id'] + '/icon/' + temp)) Utils.copy_file(fname[0], current_icon) else: Utils.copy_file(fname[0], current_icon) def save_data(self): if self.game_name_value.text().strip() == "": QMessageBox.warning(self, "警告", "游戏名称不能为空!") return False if self.keystore_path.text().strip() == "": QMessageBox.warning(self, "警告", "必须上传keystore签名文件!") return False if self.keystore_pwd_value.text().strip() == "": QMessageBox.warning(self, "警告", "keystore密码不能为空!") return False if self.keystore_alias_value.text().strip() == "": QMessageBox.warning(self, "警告", "alias不能为空!") return False if self.keystore_aliaspwd_value.text().strip() == "": QMessageBox.warning(self, "警告", "alias密码不能为空!") return False self.game['name'] = self.game_name_value.text().strip() self.game['desc'] = self.game_desc_value.toPlainText().strip() if self.keystore_exchanged: keystore = os.path.basename(self.keystore_path.text().strip()) self.game['keystore'] = keystore self.game['keypwd'] = self.keystore_pwd_value.text().strip() self.game['alias'] = self.keystore_alias_value.text().strip() self.game['aliaspwd'] = self.keystore_aliaspwd_value.text().strip() keystore_path = Utils.get_full_path('games/' + self.game['id'] + '/keystore/' + keystore) if not os.path.exists(keystore_path): Utils.copy_file(self.keystore_path.text().strip(), keystore_path) self.main_win.games[self.main_win.game_index] = self.game self.game_list_model.update_item(self.main_win.game_index, self.game) return Utils.update_games(Utils.get_full_path('games/games.xml'), self.game, self.main_win.game_index) def next(self): if not self.save_data(): return channels = Utils.get_channels( Utils.get_full_path('games/' + self.game['id'] + '/config.xml')) if channels is None: if not os.path.exists(Utils.get_full_path('channelsdk')): QMessageBox.warning( self, "警告", os.path.dirname(os.getcwd()) + " 没有channelsdk文件夹") return elif len(os.listdir(Utils.get_full_path('channelsdk'))) <= 0: QMessageBox.warning( self, "警告", "本地没有渠道sdk,请手动添加sdk文件夹到" + Utils.get_full_path('channelsdk')) return channels = [] self.main_win.set_add_channel_widget(channels) else: self.main_win.set_channel_list_widget(channels)
class EditRules(QWidget): # {{{ changed = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) self.enabled = c = QCheckBox(self) l.addWidget(c, l.rowCount(), 0, 1, 2) c.setVisible(False) c.stateChanged.connect(self.changed) self.l1 = l1 = QLabel('') l1.setWordWrap(True) l.addWidget(l1, l.rowCount(), 0, 1, 2) self.add_button = QPushButton(QIcon(I('plus.png')), _('Add Rule'), self) self.remove_button = QPushButton(QIcon(I('minus.png')), _('Remove Rule(s)'), self) self.add_button.clicked.connect(self.add_rule) self.remove_button.clicked.connect(self.remove_rule) l.addWidget(self.add_button, l.rowCount(), 0) l.addWidget(self.remove_button, l.rowCount() - 1, 1) self.g = g = QGridLayout() self.rules_view = QListView(self) self.rules_view.doubleClicked.connect(self.edit_rule) self.rules_view.setSelectionMode(self.rules_view.ExtendedSelection) self.rules_view.setAlternatingRowColors(True) self.rtfd = RichTextDelegate(parent=self.rules_view, max_width=400) self.rules_view.setItemDelegate(self.rtfd) g.addWidget(self.rules_view, 0, 0, 2, 1) self.up_button = b = QToolButton(self) b.setIcon(QIcon(I('arrow-up.png'))) b.setToolTip(_('Move the selected rule up')) b.clicked.connect(self.move_up) g.addWidget(b, 0, 1, 1, 1, Qt.AlignTop) self.down_button = b = QToolButton(self) b.setIcon(QIcon(I('arrow-down.png'))) b.setToolTip(_('Move the selected rule down')) b.clicked.connect(self.move_down) g.addWidget(b, 1, 1, 1, 1, Qt.AlignBottom) l.addLayout(g, l.rowCount(), 0, 1, 2) l.setRowStretch(l.rowCount() - 1, 10) self.add_advanced_button = b = QPushButton(QIcon(I('plus.png')), _('Add Advanced Rule'), self) b.clicked.connect(self.add_advanced) l.addWidget(b, l.rowCount(), 0, 1, 2) def initialize(self, fm, prefs, mi, pref_name): self.pref_name = pref_name self.model = RulesModel(prefs, fm, self.pref_name) self.rules_view.setModel(self.model) self.fm = fm self.mi = mi if pref_name == 'column_color_rules': text = _( 'You can control the color of columns in the' ' book list by creating "rules" that tell calibre' ' what color to use. Click the Add Rule button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') elif pref_name == 'column_icon_rules': text = _( 'You can add icons to columns in the' ' book list by creating "rules" that tell calibre' ' what icon to use. Click the Add Rule button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') elif pref_name == 'cover_grid_icon_rules': text = _( 'You can add emblems (small icons) that are displayed on the side of covers' ' in the cover grid by creating "rules" that tell calibre' ' what image to use. Click the Add Rule button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') self.enabled.setVisible(True) self.enabled.setChecked(gprefs['show_emblems']) self.enabled.setText(_('Show &emblems next to the covers')) self.enabled.stateChanged.connect(self.enabled_toggled) self.enabled.setToolTip( _('If checked, you can tell calibre to display icons of your choosing' ' next to the covers shown in the cover grid, controlled by the' ' metadata of the book.')) self.enabled_toggled() self.l1.setText('<p>' + text) def enabled_toggled(self): enabled = self.enabled.isChecked() for x in ('add_advanced_button', 'rules_view', 'up_button', 'down_button', 'add_button', 'remove_button'): getattr(self, x).setEnabled(enabled) def add_rule(self): d = RuleEditor(self.model.fm, self.pref_name) d.add_blank_condition() if d.exec_() == d.Accepted: kind, col, r = d.rule if kind and r and col: idx = self.model.add_rule(kind, col, r) self.rules_view.scrollTo(idx) self.changed.emit() def add_advanced(self): if self.pref_name == 'column_color_rules': td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='') if td.exec_() == td.Accepted: col, r = td.rule if r and col: idx = self.model.add_rule('color', col, r) self.rules_view.scrollTo(idx) self.changed.emit() else: if self.pref_name == 'cover_grid_icon_rules': td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, doing_emblem=True) else: td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, icon_field_key='') if td.exec_() == td.Accepted: typ, col, r = td.rule if typ and r and col: idx = self.model.add_rule(typ, col, r) self.rules_view.scrollTo(idx) self.changed.emit() def edit_rule(self, index): try: kind, col, rule = self.model.data(index, Qt.UserRole) except: return if isinstance(rule, Rule): d = RuleEditor(self.model.fm, self.pref_name) d.apply_rule(kind, col, rule) elif self.pref_name == 'column_color_rules': d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, color_field=col) elif self.pref_name == 'cover_grid_icon_rules': d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, doing_emblem=True) else: d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, icon_field_key=col, icon_rule_kind=kind) if d.exec_() == d.Accepted: if len(d.rule) == 2: # Convert template dialog rules to a triple d.rule = ('color', d.rule[0], d.rule[1]) kind, col, r = d.rule if kind and r is not None and col: self.model.replace_rule(index, kind, col, r) self.rules_view.scrollTo(index) self.changed.emit() def get_selected_row(self, txt): sm = self.rules_view.selectionModel() rows = list(sm.selectedRows()) if not rows: error_dialog(self, _('No rule selected'), _('No rule selected for %s.') % txt, show=True) return None return sorted(rows, reverse=True) def remove_rule(self): rows = self.get_selected_row(_('removal')) if rows is not None: for row in rows: self.model.remove_rule(row) self.changed.emit() def move_up(self): idx = self.rules_view.currentIndex() if idx.isValid(): idx = self.model.move(idx, -1) if idx is not None: sm = self.rules_view.selectionModel() sm.select(idx, sm.ClearAndSelect) self.rules_view.setCurrentIndex(idx) self.changed.emit() def move_down(self): idx = self.rules_view.currentIndex() if idx.isValid(): idx = self.model.move(idx, 1) if idx is not None: sm = self.rules_view.selectionModel() sm.select(idx, sm.ClearAndSelect) self.rules_view.setCurrentIndex(idx) self.changed.emit() def clear(self): self.model.clear() self.changed.emit() def commit(self, prefs): self.model.commit(prefs) if self.pref_name == 'cover_grid_icon_rules': gprefs['show_emblems'] = self.enabled.isChecked()
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.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.activated[(QModelIndex)].connect(self.show_pane) self.groups.clicked[(QModelIndex)].connect(self.show_pane) self.groups.entered[(QModelIndex)].connect(self.show_group_help) rb = self.buttonBox.button(self.buttonBox.RestoreDefaults) rb.setText(_('Restore &defaults')) rb.clicked.connect(self.restore_defaults) self.groups.setMouseTracking(True) geom = gprefs.get('convert_single_dialog_geom', None) if geom: self.restoreGeometry(geom) else: self.resize(self.sizeHint()) 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.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.Expanding, QSizePolicy.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.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.Expanding, QSizePolicy.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.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(4) sizePolicy.setVerticalStretch(10) sizePolicy.setHeightForWidth( self.scrollArea.sizePolicy().hasHeightForWidth()) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setFrameShape(QFrame.NoFrame) self.scrollArea.setLineWidth(0) self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("scrollArea") self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 810, 494)) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents) self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.verticalLayout_3.setObjectName("verticalLayout_3") self.stack = QStackedWidget(self.scrollAreaWidgetContents) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.stack.sizePolicy().hasHeightForWidth()) self.stack.setSizePolicy(sizePolicy) self.stack.setObjectName("stack") self.page = QWidget() self.page.setObjectName("page") self.stack.addWidget(self.page) self.page_2 = QWidget() self.page_2.setObjectName("page_2") self.stack.addWidget(self.page_2) self.verticalLayout_3.addWidget(self.stack) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.gridLayout.addWidget(self.scrollArea, 1, 1, 1, 1) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.RestoreDefaults) self.buttonBox.setObjectName("buttonBox") self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1) self.help = QTextEdit(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.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): desktop = QCoreApplication.instance().desktop() geom = desktop.availableGeometry(self) 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 unicode_type(self.input_formats.currentText()).lower() @property def output_format(self): return unicode_type(self.output_formats.currentText()).lower() @property def manually_fine_tune_toc(self): for i in range(self.stack.count()): w = self.stack.widget(i) 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.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) self.mw = widget_factory(MetadataWidget) self.setWindowTitle( _('Convert') + ' ' + unicode_type(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.stack, 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.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) while True: c = self.stack.currentWidget() if not c: break self.stack.removeWidget(c) 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: self.stack.addWidget(w) w.set_help_signal.connect(self.help.setPlainText) 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.stack.setCurrentIndex(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: 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( (unicode_type(x.upper()) for x in input_formats)) self.output_formats.addItems( (unicode_type(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): self.stack.setCurrentIndex(index.row()) 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 i in range(self.stack.count()): w = self.stack.widget(i) 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)
class EditRules(QWidget): # {{{ changed = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) self.enabled = c = QCheckBox(self) l.addWidget(c, l.rowCount(), 0, 1, 2) c.setVisible(False) c.stateChanged.connect(self.changed) self.l1 = l1 = QLabel('') l1.setWordWrap(True) l.addWidget(l1, l.rowCount(), 0, 1, 2) self.add_button = QPushButton(QIcon(I('plus.png')), _('Add Rule'), self) self.remove_button = QPushButton(QIcon(I('minus.png')), _('Remove Rule'), self) self.add_button.clicked.connect(self.add_rule) self.remove_button.clicked.connect(self.remove_rule) l.addWidget(self.add_button, l.rowCount(), 0) l.addWidget(self.remove_button, l.rowCount() - 1, 1) self.g = g = QGridLayout() self.rules_view = QListView(self) self.rules_view.doubleClicked.connect(self.edit_rule) self.rules_view.setSelectionMode(self.rules_view.SingleSelection) self.rules_view.setAlternatingRowColors(True) self.rtfd = RichTextDelegate(parent=self.rules_view, max_width=400) self.rules_view.setItemDelegate(self.rtfd) g.addWidget(self.rules_view, 0, 0, 2, 1) self.up_button = b = QToolButton(self) b.setIcon(QIcon(I('arrow-up.png'))) b.setToolTip(_('Move the selected rule up')) b.clicked.connect(self.move_up) g.addWidget(b, 0, 1, 1, 1, Qt.AlignTop) self.down_button = b = QToolButton(self) b.setIcon(QIcon(I('arrow-down.png'))) b.setToolTip(_('Move the selected rule down')) b.clicked.connect(self.move_down) g.addWidget(b, 1, 1, 1, 1, Qt.AlignBottom) l.addLayout(g, l.rowCount(), 0, 1, 2) l.setRowStretch(l.rowCount() - 1, 10) self.add_advanced_button = b = QPushButton(QIcon(I('plus.png')), _('Add Advanced Rule'), self) b.clicked.connect(self.add_advanced) l.addWidget(b, l.rowCount(), 0, 1, 2) def initialize(self, fm, prefs, mi, pref_name): self.pref_name = pref_name self.model = RulesModel(prefs, fm, self.pref_name) self.rules_view.setModel(self.model) self.fm = fm self.mi = mi if pref_name == 'column_color_rules': text = _( 'You can control the color of columns in the' ' book list by creating "rules" that tell calibre' ' what color to use. Click the Add Rule button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') elif pref_name == 'column_icon_rules': text = _( 'You can add icons to columns in the' ' book list by creating "rules" that tell calibre' ' what icon to use. Click the Add Rule button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') elif pref_name == 'cover_grid_icon_rules': text = _('You can add emblems (small icons) that are displayed on the side of covers' ' in the cover grid by creating "rules" that tell calibre' ' what image to use. Click the Add Rule button below' ' to get started.<p>You can <b>change an existing rule</b> by' ' double clicking it.') self.enabled.setVisible(True) self.enabled.setChecked(gprefs['show_emblems']) self.enabled.setText(_('Show &emblems next to the covers')) self.enabled.stateChanged.connect(self.enabled_toggled) self.enabled.setToolTip(_( 'If checked, you can tell calibre to displays icons of your choosing' ' next to the covers shown in the cover grid, controlled by the' ' metadata of the book.')) self.enabled_toggled() self.l1.setText('<p>'+ text) def enabled_toggled(self): enabled = self.enabled.isChecked() for x in ('add_advanced_button', 'rules_view', 'up_button', 'down_button', 'add_button', 'remove_button'): getattr(self, x).setEnabled(enabled) def add_rule(self): d = RuleEditor(self.model.fm, self.pref_name) d.add_blank_condition() if d.exec_() == d.Accepted: kind, col, r = d.rule if kind and r and col: idx = self.model.add_rule(kind, col, r) self.rules_view.scrollTo(idx) self.changed.emit() def add_advanced(self): if self.pref_name == 'column_color_rules': td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='') if td.exec_() == td.Accepted: col, r = td.rule if r and col: idx = self.model.add_rule('color', col, r) self.rules_view.scrollTo(idx) self.changed.emit() else: if self.pref_name == 'cover_grid_icon_rules': td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, doing_emblem=True) else: td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, icon_field_key='') if td.exec_() == td.Accepted: typ, col, r = td.rule if typ and r and col: idx = self.model.add_rule(typ, col, r) self.rules_view.scrollTo(idx) self.changed.emit() def edit_rule(self, index): try: kind, col, rule = self.model.data(index, Qt.UserRole) except: return if isinstance(rule, Rule): d = RuleEditor(self.model.fm, self.pref_name) d.apply_rule(kind, col, rule) elif self.pref_name == 'column_color_rules': d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, color_field=col) elif self.pref_name == 'cover_grid_icon_rules': d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, doing_emblem=True) else: d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, icon_field_key=col, icon_rule_kind=kind) if d.exec_() == d.Accepted: if len(d.rule) == 2: # Convert template dialog rules to a triple d.rule = ('color', d.rule[0], d.rule[1]) kind, col, r = d.rule if kind and r is not None and col: self.model.replace_rule(index, kind, col, r) self.rules_view.scrollTo(index) self.changed.emit() def get_selected_row(self, txt): sm = self.rules_view.selectionModel() rows = list(sm.selectedRows()) if not rows: error_dialog(self, _('No rule selected'), _('No rule selected for %s.')%txt, show=True) return None return rows[0] def remove_rule(self): row = self.get_selected_row(_('removal')) if row is not None: self.model.remove_rule(row) self.changed.emit() def move_up(self): idx = self.rules_view.currentIndex() if idx.isValid(): idx = self.model.move(idx, -1) if idx is not None: sm = self.rules_view.selectionModel() sm.select(idx, sm.ClearAndSelect) self.rules_view.setCurrentIndex(idx) self.changed.emit() def move_down(self): idx = self.rules_view.currentIndex() if idx.isValid(): idx = self.model.move(idx, 1) if idx is not None: sm = self.rules_view.selectionModel() sm.select(idx, sm.ClearAndSelect) self.rules_view.setCurrentIndex(idx) self.changed.emit() def clear(self): self.model.clear() self.changed.emit() def commit(self, prefs): self.model.commit(prefs) if self.pref_name == 'cover_grid_icon_rules': gprefs['show_emblems'] = self.enabled.isChecked()
class SideView(QObject): onBeforeItemDeletion = pyqtSignal(str) onEntrySelected = pyqtSignal(str) onDirectoryChanged = pyqtSignal(str) onRemoveRequested = pyqtSignal(str) def __init__(self, dirLister, entryProvider: IEntryProvider, newEntryText: str, itemNameNormalizer: IItemNameNormalizer): super().__init__() def initListView(): self.listView = QListView() self.listView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.listView.setModel(self.model) self.listView.setMinimumWidth(200) actionRemove = QAction("Remove", None) self.listView.addAction(actionRemove) self.listView.selectionModel().currentChanged.connect( lambda selectedItem, unselectedItem: self.onEntrySelected.emit( self.itemNameNormalizer.normalizeName( self.currentDir.filePath(selectedItem.data())))) def contextMenu(position): menu = QMenu() index = self.listView.indexAt(position) entry = self.model.data(index, Qt.DisplayRole) deleteAction = None renameAction = None addAction = menu.addAction("Add") if entry is not None: deleteAction = menu.addAction("Delete") renameAction = menu.addAction("Rename") refreshAction = menu.addAction("Refresh") chosenAction = menu.exec_(self.listView.mapToGlobal(position)) if chosenAction is None: return if chosenAction == deleteAction: self.onRemoveRequested.emit(entry) elif chosenAction == renameAction: self.renameEntry(index, entry) elif chosenAction == addAction: self.onCreateNewEntry() elif chosenAction == refreshAction: self.refreshListViewEntries() self.listView.customContextMenuRequested.connect(contextMenu) self.listView.setContextMenuPolicy(Qt.CustomContextMenu) self.currentDir: QDir = None self.model = QStringListModel() self.directoryLister = dirLister self.entryProvider = entryProvider self.newEntryText = newEntryText self.itemNameNormalizer = itemNameNormalizer self.sortingParser: IEntrySorting initListView() def renameEntry(self, index, oldName): def onNewEntryCommited(editedLine: QLineEdit): self.listView.itemDelegate().commitData.disconnect( onNewEntryCommited) try: newName = editedLine.text() normalizedNameOld = self.itemNameNormalizer.normalizeName( oldName) normalizedNameNew = self.itemNameNormalizer.normalizeName( newName) self.emitOnItemDeletion(normalizedNameOld) self.entryProvider.renameEnty(normalizedNameOld, normalizedNameNew) self.sortingParser.rename(oldName, newName) self.refreshListViewEntries() except Exception: pass self.listView.itemDelegate().commitData.connect(onNewEntryCommited) self.listView.edit(index) def emitOnItemDeletion(self, normalizedName): self.onBeforeItemDeletion.emit( self.currentDir.filePath(normalizedName)) def removeEntry(self, entry): normalizedName = self.itemNameNormalizer.normalizeName(entry) self.emitOnItemDeletion(normalizedName) self.entryProvider.removeEntry(normalizedName) self.refreshListViewEntries() def setDirectory(self, dirPath: str): self.currentDir = QDir(dirPath) self.sortingParser = EntrySortingFile( self.currentDir.filePath('.sorting')) self.entryProvider.setContext(dirPath) self.refreshListViewEntries() self.onDirectoryChanged.emit(self.currentDir.absolutePath()) def refreshListViewEntries(self): if self.currentDir is None: return entries = self.directoryLister.listEntries(self.currentDir) sortedEntries = self.sortingParser.getSortedList(entries) self.model.setStringList(sortedEntries) def onCreateNewEntry(self): def onNewEntryCommited(editedLine: QLineEdit): self.listView.itemDelegate().commitData.disconnect( onNewEntryCommited) try: if editedLine.text() == self.newEntryText: raise Exception() normalizedName = self.itemNameNormalizer.normalizeName( editedLine.text()) self.entryProvider.addEntry(normalizedName) self.refreshListViewEntries() self.listView.setCurrentIndex(index) except Exception: print(f"Error adding entry {editedLine.text()}", file=sys.stderr) self.model.removeRow(self.model.rowCount() - 1) if not self.model.insertRow(self.model.rowCount()): return self.listView.itemDelegate().commitData.connect(onNewEntryCommited) index = self.model.index(self.model.rowCount() - 1, 0) self.model.setData(index, self.newEntryText) self.listView.edit(index) pass