class RuleEditor(QDialog): # {{{ def __init__(self, fm, parent=None): QDialog.__init__(self, parent) self.fm = fm self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a column coloring rule')) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a coloring rule by' ' filling in the boxes below')) l.addWidget(l1, 0, 0, 1, 5) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 5) self.l2 = l2 = QLabel(_('Set the color of the column:')) l.addWidget(l2, 2, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 1) self.l3 = l3 = QLabel(_('to')) l.addWidget(l3, 2, 2) self.color_box = QComboBox(self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 3) l.addWidget(self.color_label, 2, 4) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 5) self.l4 = l4 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l4, 3, 0, 1, 6) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 6) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) l.addWidget(b, 5, 0, 1, 6) b.clicked.connect(self.add_blank_condition) self.l5 = l5 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l5, 6, 0, 1, 6) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 6) self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] for b in (self.column_box, self.color_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted( displayable_columns(fm), key=sort_key): name = fm[key]['name'] if name: self.column_box.addItem(key, key) self.column_box.setCurrentIndex(0) self.color_box.addItems(QColor.colorNames()) self.color_box.setCurrentIndex(0) self.update_color_label() self.color_box.currentIndexChanged.connect(self.update_color_label) self.resize(self.sizeHint()) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = unicode(self.color_box.currentText()) self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, col, rule): for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i).toString()) if col == c: self.column_box.setCurrentIndex(i) break if rule.color: idx = self.color_box.findText(rule.color) if idx >= 0: self.color_box.setCurrentIndex(idx) for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) r.color = unicode(self.color_box.currentText()) idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx).toString()) for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) return col, r
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool' : ( (_('is true'), 'is true',), (_('is false'), 'is false'), (_('is undefined'), 'is undefined') ), 'ondevice' : ( (_('is true'), 'is set',), (_('is false'), 'is not set'), ), 'identifiers' : ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt') ), 'multiple' : ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single' : ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating', 'datetime'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), key=sort_key): self.column_box.addItem(key, key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx).toString()) if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found'%val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx).toString()) if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column'%val) return property(fget=fget, fset=fset) @property def current_val(self): ans = unicode(self.value_box.text()).strip() if self.current_col == 'languages': rmap = {lower(v):k for k, v in lang_map().iteritems()} ans = rmap.get(lower(ans), ans) return ans @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return m = self.fm[col] dt = m['datatype'] action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif col == 'languages': tt = _('Enter a 3 letter ISO language code, like fra for French' ' or deu for German or eng for English. You can also use' ' the full language name, in which case calibre will try to' ' automatically convert it to the language code.') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _('You can match multiple values by separating' ' them with %s')%m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool': (( _('is true'), 'is true', ), (_('is false'), 'is false'), (_('is undefined'), 'is undefined')), 'ondevice': ( ( _('is true'), 'is set', ), (_('is false'), 'is not set'), ), 'identifiers': ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int': ((_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt')), 'datetime': ((_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), (_('is more days ago than'), 'older count days'), (_('is fewer days ago than'), 'count_days'), (_('is more days from now than'), 'newer future days'), (_('is fewer days from now than'), 'older future days')), 'multiple': ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single': ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted(conditionable_columns(fm), key=lambda (key): sort_key(fm[key]['name'])): self.column_box.addItem(fm[key]['name'], key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(20) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx).toString()) if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found' % val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx).toString()) if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column' % val) return property(fget=fget, fset=fset) @property def current_val(self): ans = unicode(self.value_box.text()).strip() if self.current_col == 'languages': rmap = {lower(v): k for k, v in lang_map().iteritems()} ans = rmap.get(lower(ans), ans) return ans @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif col == 'languages': tt = _('Enter a 3 letter ISO language code, like fra for French' ' or deu for German or eng for English. You can also use' ' the full language name, in which case calibre will try to' ' automatically convert it to the language code.') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': if action == 'count_days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _( 'Enter the maximum days old the item can be. Zero is today. ' 'Dates in the future always match') elif action == 'older count days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _( 'Enter the minimum days old the item can be. Zero is today. ' 'Dates in the future never match') elif action == 'older future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the maximum days in the future the item can be. ' 'Zero is today. Dates in the past always match') elif action == 'newer future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the minimum days in the future the item can be. ' 'Zero is today. Dates in the past never match') else: self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _( 'You can match multiple values by separating' ' them with %s') % m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)
class InsertSemantics(Dialog): def __init__(self, container, parent=None): self.container = container self.anchor_cache = {} self.original_type_map = { item.get('type', ''): (container.href_to_name(item.get('href'), container.opf_name), item.get('href', '').partition('#')[-1]) for item in container.opf_xpath( '//opf:guide/opf:reference[@href and @type]') } self.final_type_map = self.original_type_map.copy() self.create_known_type_map() Dialog.__init__(self, _('Set Semantics'), 'insert-semantics', parent=parent) def sizeHint(self): return QSize(800, 600) def create_known_type_map(self): _ = lambda x: x self.known_type_map = { 'title-page': _('Title Page'), 'toc': _('Table of Contents'), 'index': _('Index'), 'glossary': _('Glossary'), 'acknowledgements': _('Acknowledgements'), 'bibliography': _('Bibliography'), 'colophon': _('Colophon'), 'copyright-page': _('Copyright page'), 'dedication': _('Dedication'), 'epigraph': _('Epigraph'), 'foreword': _('Foreword'), 'loi': _('List of Illustrations'), 'lot': _('List of Tables'), 'notes:': _('Notes'), 'preface': _('Preface'), 'text': _('Text'), } _ = __builtins__['_'] type_map_help = { 'title-page': _('Page with title, author, publisher, etc.'), 'index': _('Back-of-book style index'), 'text': _('First "real" page of content'), } t = _ all_types = [ (k, (('%s (%s)' % (t(v), type_map_help[k])) if k in type_map_help else t(v))) for k, v in self.known_type_map.iteritems() ] all_types.sort(key=lambda x: sort_key(x[1])) self.all_types = OrderedDict(all_types) def setup_ui(self): self.l = l = QVBoxLayout(self) self.setLayout(l) self.tl = tl = QFormLayout() self.semantic_type = QComboBox(self) for key, val in self.all_types.iteritems(): self.semantic_type.addItem(val, key) tl.addRow(_('Type of &semantics:'), self.semantic_type) self.target = t = QLineEdit(self) t.setPlaceholderText(_('The destination (href) for the link')) tl.addRow(_('&Target:'), t) l.addLayout(tl) self.hline = hl = QFrame(self) hl.setFrameStyle(hl.HLine) l.addWidget(hl) self.h = h = QHBoxLayout() l.addLayout(h) names = [n for n, linear in self.container.spine_names] fn, f = create_filterable_names_list(names, filter_text=_('Filter files'), parent=self) self.file_names, self.file_names_filter = fn, f fn.selectionModel().selectionChanged.connect( self.selected_file_changed) self.fnl = fnl = QVBoxLayout() self.la1 = la = QLabel(_('Choose a &file:')) la.setBuddy(fn) fnl.addWidget(la), fnl.addWidget(f), fnl.addWidget(fn) h.addLayout(fnl), h.setStretch(0, 2) fn, f = create_filterable_names_list([], filter_text=_('Filter locations'), parent=self) self.anchor_names, self.anchor_names_filter = fn, f fn.selectionModel().selectionChanged.connect(self.update_target) fn.doubleClicked.connect(self.accept, type=Qt.QueuedConnection) self.anl = fnl = QVBoxLayout() self.la2 = la = QLabel(_('Choose a &location (anchor) in the file:')) la.setBuddy(fn) fnl.addWidget(la), fnl.addWidget(f), fnl.addWidget(fn) h.addLayout(fnl), h.setStretch(1, 1) self.bb.addButton(self.bb.Help) self.bb.helpRequested.connect(self.help_requested) l.addWidget(self.bb) self.semantic_type_changed() self.semantic_type.currentIndexChanged.connect( self.semantic_type_changed) self.target.textChanged.connect(self.target_text_changed) def help_requested(self): d = info_dialog( self, _('About semantics'), _('Semantics refer to additional information about specific locations in the book.' ' For example, you can specify that a particular location is the dedication or the preface' ' or the table of contents and so on.\n\nFirst choose the type of semantic information, then' ' choose a file and optionally a location within the file to point to.\n\nThe' ' semantic information will be written in the <guide> section of the opf file.' )) d.resize(d.sizeHint()) d.exec_() def semantic_type_changed(self): item_type = unicode( self.semantic_type.itemData( self.semantic_type.currentIndex()).toString()) name, frag = self.final_type_map.get(item_type, (None, None)) self.show_type(name, frag) def show_type(self, name, frag): self.file_names_filter.clear(), self.anchor_names_filter.clear() self.file_names.clearSelection(), self.anchor_names.clearSelection() if name is not None: row = self.file_names.model().find_name(name) if row is not None: sm = self.file_names.selectionModel() sm.select(self.file_names.model().index(row), sm.ClearAndSelect) if frag: row = self.anchor_names.model().find_name(frag) if row is not None: sm = self.anchor_names.selectionModel() sm.select(self.anchor_names.model().index(row), sm.ClearAndSelect) self.target.blockSignals(True) if name is not None: self.target.setText(name + (('#' + frag) if frag else '')) else: self.target.setText('') self.target.blockSignals(False) def target_text_changed(self): name, frag = unicode(self.target.text()).partition('#')[::2] item_type = unicode( self.semantic_type.itemData( self.semantic_type.currentIndex()).toString()) self.final_type_map[item_type] = (name, frag or None) def selected_file_changed(self, *args): rows = list(self.file_names.selectionModel().selectedRows()) if not rows: self.anchor_names.model().set_names([]) else: name, positions = self.file_names.model().data( rows[0], Qt.UserRole).toPyObject() self.populate_anchors(name) def populate_anchors(self, name): if name not in self.anchor_cache: from calibre.ebooks.oeb.base import XHTML_NS root = self.container.parsed(name) self.anchor_cache[name] = sorted((set(root.xpath('//*/@id')) | set( root.xpath('//h:a/@name', namespaces={'h': XHTML_NS}))) - {''}, key=primary_sort_key) self.anchor_names.model().set_names(self.anchor_cache[name]) self.update_target() def update_target(self): rows = list(self.file_names.selectionModel().selectedRows()) if not rows: return name = self.file_names.model().data(rows[0], Qt.UserRole).toPyObject()[0] href = name frag = '' rows = list(self.anchor_names.selectionModel().selectedRows()) if rows: anchor = self.anchor_names.model().data( rows[0], Qt.UserRole).toPyObject()[0] if anchor: frag = '#' + anchor href += frag self.target.setText(href or '#') @property def changed_type_map(self): return { k: v for k, v in self.final_type_map.iteritems() if v != self.original_type_map.get(k, None) } def apply_changes(self, container): from calibre.ebooks.oeb.polish.opf import set_guide_item, get_book_language from calibre.translations.dynamic import translate lang = get_book_language(container) for item_type, (name, frag) in self.changed_type_map.iteritems(): title = self.known_type_map[item_type] if lang: title = translate(lang, title) set_guide_item(container, item_type, title, name, frag=frag) @classmethod def test(cls): import sys from calibre.ebooks.oeb.polish.container import get_container c = get_container(sys.argv[-1], tweak_mode=True) d = cls(c) if d.exec_() == d.Accepted: import pprint pprint.pprint(d.changed_type_map) d.apply_changes(d.container)
class RuleEditor(QDialog): # {{{ def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('coloring') else: self.rule_kind = 'icon' rule_text = _('icon') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a column {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a column {0} rule by' ' filling in the boxes below'.format(rule_text))) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) else: self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'color': self.color_box = QComboBox(self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) else: self.filename_box = QComboBox() self.filename_box.setInsertPolicy(self.filename_box.InsertAlphabetically) d = os.path.join(config_dir, 'cc_icons') self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)): if icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) self.update_filename_box() l.addWidget(self.filename_box, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, self.color_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.addItems(QColor.colorNames()) self.color_box.setCurrentIndex(0) self.update_color_label() self.color_box.currentIndexChanged.connect(self.update_color_label) else: self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def update_filename_box(self): self.filename_box.clear() self.icon_file_names.sort(key=sort_key) self.filename_box.addItem('') self.filename_box.addItems(self.icon_file_names) for i,filename in enumerate(self.icon_file_names): icon = QIcon(os.path.join(config_dir, 'cc_icons', filename)) self.filename_box.setItemIcon(i+1, icon) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = unicode(self.color_box.currentText()) self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ ('Images', ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png') if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) self.update_filename_box() try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = os.path.join(config_dir, 'cc_icons') if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: idx = self.color_box.findText(rule.color) if idx >= 0: self.color_box.setCurrentIndex(idx) else: self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1) if rule.color: idx = self.filename_box.findText(rule.color) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i).toString()) if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = lower(unicode(self.filename_box.currentText())) if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = unicode(self.filename_box.currentText()) else: r.color = unicode(self.color_box.currentText()) idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx).toString()) for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode(self.kind_box.itemData( self.kind_box.currentIndex()).toString()) else: kind = 'color' return kind, col, r
class RuleEditor(QDialog): # {{{ def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('coloring') else: self.rule_kind = 'icon' rule_text = _('icon') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle( _('Create/edit a column {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel( _('Create a column {0} rule by' ' filling in the boxes below'.format(rule_text))) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) else: self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'color': self.color_box = QComboBox(self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) else: self.filename_box = QComboBox() self.filename_box.setInsertPolicy( self.filename_box.InsertAlphabetically) d = os.path.join(config_dir, 'cc_icons') self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)): if icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) self.update_filename_box() l.addWidget(self.filename_box, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel( _('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, self.color_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda (k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key][ 'name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.addItems(QColor.colorNames()) self.color_box.setCurrentIndex(0) self.update_color_label() self.color_box.currentIndexChanged.connect(self.update_color_label) else: self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def update_filename_box(self): self.filename_box.clear() self.icon_file_names.sort(key=sort_key) self.filename_box.addItem('') self.filename_box.addItems(self.icon_file_names) for i, filename in enumerate(self.icon_file_names): icon = QIcon(os.path.join(config_dir, 'cc_icons', filename)) self.filename_box.setItemIcon(i + 1, icon) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = unicode(self.color_box.currentText()) self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[('Images', ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = sanitize_file_name_unicode( os.path.splitext(os.path.basename(icon_path))[0] + '.png') if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) self.update_filename_box() try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = os.path.join(config_dir, 'cc_icons') if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.filename_box.setCurrentIndex( self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: idx = self.color_box.findText(rule.color) if idx >= 0: self.color_box.setCurrentIndex(idx) else: self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1) if rule.color: idx = self.filename_box.findText(rule.color) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i).toString()) if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = lower(unicode(self.filename_box.currentText())) if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>') % e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = unicode(self.filename_box.currentText()) else: r.color = unicode(self.color_box.currentText()) idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx).toString()) for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode( self.kind_box.itemData( self.kind_box.currentIndex()).toString()) else: kind = 'color' return kind, col, r
class StepEditDialog(QDialog): def __init__(self, parent, step): super(StepEditDialog, self).__init__(parent) self._step = step self._initGui() #--------------------------------------------------------------------# def _initGui(self): layout = QVBoxLayout() self.setLayout(layout) self.cb_step_type = QComboBox() self.widgets = QStackedWidget() layout.addWidget(self.cb_step_type) layout.addWidget(self.widgets) if self._step is None: for step_class in Step.REGISTERED_STEPS.values(): self.cb_step_type.addItem(step_class.NAME) w = step_class.GET_WIDGET() self.widgets.addWidget(w) self.cb_step_type.currentIndexChanged.connect( self._stepTypeChanged) else: self.cb_step_type.addItems(Step.REGISTERED_STEPS.keys()) self.cb_step_type.setEnabled(False) index = self.cb_step_type.findText(QString(str(self._step.NAME))) self.cb_step_type.setCurrentIndex(index) w = self._step.getWidget() self.widgets.addWidget(w) self._showStepProperties() ################### # Action Buttons: # ################### buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) #--------------------------------------------------------------------# def _showStepProperties(self): index = self.cb_step_type.currentIndex() self.widgets.setCurrentIndex(index) #--------------------------------------------------------------------# def _stepTypeChanged(self): self._showStepProperties() #--------------------------------------------------------------------# def sizeHint(self): hint = QDialog.sizeHint(self) hint.setWidth(500) return hint #--------------------------------------------------------------------# def step(self): return self._step #--------------------------------------------------------------------# def accept(self, *args, **kwargs): if self._step is None: name = str(self.cb_step_type.currentText()) step_class = Step.REGISTERED_STEPS[name] self._step = step_class() self.widgets.currentWidget().save(self._step.attributes()) else: self._selected_widget.save() return QDialog.accept(self, *args, **kwargs)
class InsertSemantics(Dialog): def __init__(self, container, parent=None): self.container = container self.anchor_cache = {} self.original_type_map = {item.get('type', ''):(container.href_to_name(item.get('href'), container.opf_name), item.get('href', '').partition('#')[-1]) for item in container.opf_xpath('//opf:guide/opf:reference[@href and @type]')} self.final_type_map = self.original_type_map.copy() self.create_known_type_map() Dialog.__init__(self, _('Set Semantics'), 'insert-semantics', parent=parent) def sizeHint(self): return QSize(800, 600) def create_known_type_map(self): _ = lambda x: x self.known_type_map = { 'title-page': _('Title Page'), 'toc': _('Table of Contents'), 'index': _('Index'), 'glossary': _('Glossary'), 'acknowledgements': _('Acknowledgements'), 'bibliography': _('Bibliography'), 'colophon': _('Colophon'), 'copyright-page': _('Copyright page'), 'dedication': _('Dedication'), 'epigraph': _('Epigraph'), 'foreword': _('Foreword'), 'loi': _('List of Illustrations'), 'lot': _('List of Tables'), 'notes:': _('Notes'), 'preface': _('Preface'), 'text': _('Text'), } _ = __builtins__['_'] type_map_help = { 'title-page': _('Page with title, author, publisher, etc.'), 'index': _('Back-of-book style index'), 'text': _('First "real" page of content'), } t = _ all_types = [(k, (('%s (%s)' % (t(v), type_map_help[k])) if k in type_map_help else t(v))) for k, v in self.known_type_map.iteritems()] all_types.sort(key=lambda x: sort_key(x[1])) self.all_types = OrderedDict(all_types) def setup_ui(self): self.l = l = QVBoxLayout(self) self.setLayout(l) self.tl = tl = QFormLayout() self.semantic_type = QComboBox(self) for key, val in self.all_types.iteritems(): self.semantic_type.addItem(val, key) tl.addRow(_('Type of &semantics:'), self.semantic_type) self.target = t = QLineEdit(self) t.setPlaceholderText(_('The destination (href) for the link')) tl.addRow(_('&Target:'), t) l.addLayout(tl) self.hline = hl = QFrame(self) hl.setFrameStyle(hl.HLine) l.addWidget(hl) self.h = h = QHBoxLayout() l.addLayout(h) names = [n for n, linear in self.container.spine_names] fn, f = create_filterable_names_list(names, filter_text=_('Filter files'), parent=self) self.file_names, self.file_names_filter = fn, f fn.selectionModel().selectionChanged.connect(self.selected_file_changed) self.fnl = fnl = QVBoxLayout() self.la1 = la = QLabel(_('Choose a &file:')) la.setBuddy(fn) fnl.addWidget(la), fnl.addWidget(f), fnl.addWidget(fn) h.addLayout(fnl), h.setStretch(0, 2) fn, f = create_filterable_names_list([], filter_text=_('Filter locations'), parent=self) self.anchor_names, self.anchor_names_filter = fn, f fn.selectionModel().selectionChanged.connect(self.update_target) fn.doubleClicked.connect(self.accept, type=Qt.QueuedConnection) self.anl = fnl = QVBoxLayout() self.la2 = la = QLabel(_('Choose a &location (anchor) in the file:')) la.setBuddy(fn) fnl.addWidget(la), fnl.addWidget(f), fnl.addWidget(fn) h.addLayout(fnl), h.setStretch(1, 1) self.bb.addButton(self.bb.Help) self.bb.helpRequested.connect(self.help_requested) l.addWidget(self.bb) self.semantic_type_changed() self.semantic_type.currentIndexChanged.connect(self.semantic_type_changed) self.target.textChanged.connect(self.target_text_changed) def help_requested(self): d = info_dialog(self, _('About semantics'), _( 'Semantics refer to additional information about specific locations in the book.' ' For example, you can specify that a particular location is the dedication or the preface' ' or the table of contents and so on.\n\nFirst choose the type of semantic information, then' ' choose a file and optionally a location within the file to point to.\n\nThe' ' semantic information will be written in the <guide> section of the opf file.')) d.resize(d.sizeHint()) d.exec_() def semantic_type_changed(self): item_type = unicode(self.semantic_type.itemData(self.semantic_type.currentIndex()).toString()) name, frag = self.final_type_map.get(item_type, (None, None)) self.show_type(name, frag) def show_type(self, name, frag): self.file_names_filter.clear(), self.anchor_names_filter.clear() self.file_names.clearSelection(), self.anchor_names.clearSelection() if name is not None: row = self.file_names.model().find_name(name) if row is not None: sm = self.file_names.selectionModel() sm.select(self.file_names.model().index(row), sm.ClearAndSelect) if frag: row = self.anchor_names.model().find_name(frag) if row is not None: sm = self.anchor_names.selectionModel() sm.select(self.anchor_names.model().index(row), sm.ClearAndSelect) self.target.blockSignals(True) if name is not None: self.target.setText(name + (('#' + frag) if frag else '')) else: self.target.setText('') self.target.blockSignals(False) def target_text_changed(self): name, frag = unicode(self.target.text()).partition('#')[::2] item_type = unicode(self.semantic_type.itemData(self.semantic_type.currentIndex()).toString()) self.final_type_map[item_type] = (name, frag or None) def selected_file_changed(self, *args): rows = list(self.file_names.selectionModel().selectedRows()) if not rows: self.anchor_names.model().set_names([]) else: name, positions = self.file_names.model().data(rows[0], Qt.UserRole).toPyObject() self.populate_anchors(name) def populate_anchors(self, name): if name not in self.anchor_cache: from calibre.ebooks.oeb.base import XHTML_NS root = self.container.parsed(name) self.anchor_cache[name] = sorted( (set(root.xpath('//*/@id')) | set(root.xpath('//h:a/@name', namespaces={'h':XHTML_NS}))) - {''}, key=primary_sort_key) self.anchor_names.model().set_names(self.anchor_cache[name]) self.update_target() def update_target(self): rows = list(self.file_names.selectionModel().selectedRows()) if not rows: return name = self.file_names.model().data(rows[0], Qt.UserRole).toPyObject()[0] href = name frag = '' rows = list(self.anchor_names.selectionModel().selectedRows()) if rows: anchor = self.anchor_names.model().data(rows[0], Qt.UserRole).toPyObject()[0] if anchor: frag = '#' + anchor href += frag self.target.setText(href or '#') @property def changed_type_map(self): return {k:v for k, v in self.final_type_map.iteritems() if v != self.original_type_map.get(k, None)} def apply_changes(self, container): from calibre.ebooks.oeb.polish.opf import set_guide_item, get_book_language from calibre.translations.dynamic import translate lang = get_book_language(container) for item_type, (name, frag) in self.changed_type_map.iteritems(): title = self.known_type_map[item_type] if lang: title = translate(lang, title) set_guide_item(container, item_type, title, name, frag=frag) @classmethod def test(cls): import sys from calibre.ebooks.oeb.polish.container import get_container c = get_container(sys.argv[-1], tweak_mode=True) d = cls(c) if d.exec_() == d.Accepted: import pprint pprint.pprint(d.changed_type_map) d.apply_changes(d.container)
class ValueTypeEditor(QWidget): ValueTypes = (bool, int, float, complex, str) def __init__(self, *args): QWidget.__init__(self, *args) lo = QHBoxLayout(self) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(5) # type selector self.wtypesel = QComboBox(self) for i, tp in enumerate(self.ValueTypes): self.wtypesel.addItem(tp.__name__) QObject.connect(self.wtypesel, SIGNAL("activated(int)"), self._selectTypeNum) typesel_lab = QLabel("&Type:", self) typesel_lab.setBuddy(self.wtypesel) lo.addWidget(typesel_lab, 0) lo.addWidget(self.wtypesel, 0) self.wvalue = QLineEdit(self) self.wvalue_lab = QLabel("&Value:", self) self.wvalue_lab.setBuddy(self.wvalue) self.wbool = QComboBox(self) self.wbool.addItems(["false", "true"]) self.wbool.setCurrentIndex(1) lo.addWidget(self.wvalue_lab, 0) lo.addWidget(self.wvalue, 1) lo.addWidget(self.wbool, 1) self.wvalue.hide() # make input validators self._validators = { int: QIntValidator(self), float: QDoubleValidator(self) } # select bool type initially self._selectTypeNum(0) def _selectTypeNum(self, index): tp = self.ValueTypes[index] self.wbool.setShown(tp is bool) self.wvalue.setShown(tp is not bool) self.wvalue_lab.setBuddy(self.wbool if tp is bool else self.wvalue) self.wvalue.setValidator(self._validators.get(tp, None)) def setValue(self, value): """Sets current value""" for i, tp in enumerate(self.ValueTypes): if isinstance(value, tp): self.wtypesel.setCurrentIndex(i) self._selectTypeNum(i) if tp is bool: self.wbool.setCurrentIndex(1 if value else 0) else: self.wvalue.setText(str(value)) return # unknown value: set bool self.setValue(True) def getValue(self): """Returns current value, or None if no legal value is set""" tp = self.ValueTypes[self.wtypesel.currentIndex()] if tp is bool: return bool(self.wbool.currentIndex()) else: try: return tp(self.wvalue.text()) except: print("Error converting input to type ", tp.__name__) traceback.print_exc() return None
class RuleEditor(QDialog): # {{{ @property def doing_multiple(self): return hasattr(self, 'multiple_icon_cb') and self.multiple_icon_cb.isChecked() def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('column coloring') elif pref_name == 'column_icon_rules': self.rule_kind = 'icon' rule_text = _('column icon') elif pref_name == 'cover_grid_icon_rules': self.rule_kind = 'emblem' rule_text = _('cover grid emblem') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a {0} rule by' ' filling in the boxes below'.format(rule_text))) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Add the emblem:') if self.rule_kind == 'emblem' else _('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) elif self.rule_kind == 'icon': self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.kind_box.setToolTip(textwrap.fill(_( 'If you choose composed icons and multiple rules match, then all the' ' matching icons will be combined, otherwise the icon from the' ' first rule to match will be used.'))) else: pass self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'emblem': l3.setVisible(False), self.column_box.setVisible(False), l4.setVisible(False) def create_filename_box(): self.filename_box = f = QComboBox() f.setMinimumContentsLength(20), f.setSizeAdjustPolicy(f.AdjustToMinimumContentsLengthWithIcon) self.populate_icon_filenames() if self.rule_kind == 'color': self.color_box = ColorButton(parent=self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) elif self.rule_kind == 'emblem': create_filename_box() self.update_filename_box() self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add new image')) l.addWidget(self.filename_box) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('(Images should be square-ish)')), 2, 7) l.setColumnStretch(7, 10) else: create_filename_box() vb = QVBoxLayout() self.multiple_icon_cb = QCheckBox(_('Choose more than one icon')) vb.addWidget(self.multiple_icon_cb) self.update_filename_box() self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked) vb.addWidget(self.filename_box) l.addLayout(vb, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('Remove image'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) self.update_remove_button() self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, ): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.color = '#000' self.update_color_label() self.color_box.color_changed.connect(self.update_color_label) else: self.rule_icon_files = [] self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def multiple_box_clicked(self): self.update_filename_box() self.update_icon_filenames_in_box() @property def icon_folder(self): return os.path.join(config_dir, 'cc_icons') def populate_icon_filenames(self): d = self.icon_folder self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)) and icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) def update_filename_box(self): doing_multiple = self.doing_multiple model = QStandardItemModel() self.filename_box.setModel(model) self.icon_file_names.sort(key=sort_key) if doing_multiple: item = QStandardItem(_('Open to see checkboxes')) else: item = QStandardItem('') model.appendRow(item) for i,filename in enumerate(self.icon_file_names): item = QStandardItem(filename) if doing_multiple: item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setData(Qt.Unchecked, Qt.CheckStateRole) else: item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) icon = QIcon(os.path.join(self.icon_folder, filename)) item.setIcon(icon) model.appendRow(item) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = self.color_box.color self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ ('Images', ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = lower(sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png')) if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) self.update_filename_box() self.update_remove_button() try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = self.icon_folder if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() if self.doing_multiple: if icon_name not in self.rule_icon_files: self.rule_icon_files.append(icon_name) self.update_icon_filenames_in_box() else: self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def get_filenames_from_box(self): if self.doing_multiple: model = self.filename_box.model() fnames = [] for i in range(1, model.rowCount()): item = model.item(i, 0) if item.checkState() == Qt.Checked: fnames.append(lower(unicode(item.text()))) fname = ' : '.join(fnames) else: fname = lower(unicode(self.filename_box.currentText())) return fname def update_icon_filenames_in_box(self): if self.rule_icon_files: if not self.doing_multiple: idx = self.filename_box.findText(self.rule_icon_files[0]) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) else: model = self.filename_box.model() for icon in self.rule_icon_files: idx = self.filename_box.findText(icon) if idx >= 0: item = model.item(idx) item.setCheckState(Qt.Checked) def update_remove_button(self): m = self.remove_button.menu() m.clear() for name in self.icon_file_names: m.addAction(QIcon(os.path.join(self.icon_folder, name)), name).triggered.connect(partial( self.remove_image, name)) def remove_image(self, name): try: os.remove(os.path.join(self.icon_folder, name)) except EnvironmentError: pass else: self.populate_icon_filenames() self.update_remove_button() self.update_filename_box() self.update_icon_filenames_in_box() def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: self.color_box.color = rule.color else: if self.rule_kind == 'icon': for i, tup in enumerate(icon_rule_kinds): if kind == tup[1]: self.kind_box.setCurrentIndex(i) break self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')] if len(self.rule_icon_files) > 1: self.multiple_icon_cb.setChecked(True) self.update_filename_box() self.update_icon_filenames_in_box() for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i).toString()) if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = self.get_filenames_from_box() if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = self.get_filenames_from_box() else: r.color = self.color_box.color idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx).toString()) for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode(self.kind_box.itemData( self.kind_box.currentIndex()).toString()) else: kind = self.rule_kind return kind, col, r
class ValueTypeEditor(QWidget): ValueTypes = (bool, int, float, complex, str) def __init__(self, *args): QWidget.__init__(self, *args) lo = QHBoxLayout(self) lo.setContentsMargins(0, 0, 0, 0) lo.setSpacing(5) # type selector self.wtypesel = QComboBox(self) for i, tp in enumerate(self.ValueTypes): self.wtypesel.addItem(tp.__name__) QObject.connect(self.wtypesel, SIGNAL("activated(int)"), self._selectTypeNum) typesel_lab = QLabel("&Type:", self) typesel_lab.setBuddy(self.wtypesel) lo.addWidget(typesel_lab, 0) lo.addWidget(self.wtypesel, 0) self.wvalue = QLineEdit(self) self.wvalue_lab = QLabel("&Value:", self) self.wvalue_lab.setBuddy(self.wvalue) self.wbool = QComboBox(self) self.wbool.addItems(["false", "true"]) self.wbool.setCurrentIndex(1) lo.addWidget(self.wvalue_lab, 0) lo.addWidget(self.wvalue, 1) lo.addWidget(self.wbool, 1) self.wvalue.hide() # make input validators self._validators = {int: QIntValidator(self), float: QDoubleValidator(self)} # select bool type initially self._selectTypeNum(0) def _selectTypeNum(self, index): tp = self.ValueTypes[index] self.wbool.setShown(tp is bool) self.wvalue.setShown(tp is not bool) self.wvalue_lab.setBuddy(self.wbool if tp is bool else self.wvalue) self.wvalue.setValidator(self._validators.get(tp, None)) def setValue(self, value): """Sets current value""" for i, tp in enumerate(self.ValueTypes): if isinstance(value, tp): self.wtypesel.setCurrentIndex(i) self._selectTypeNum(i) if tp is bool: self.wbool.setCurrentIndex(1 if value else 0) else: self.wvalue.setText(str(value)) return # unknown value: set bool self.setValue(True) def getValue(self): """Returns current value, or None if no legal value is set""" tp = self.ValueTypes[self.wtypesel.currentIndex()] if tp is bool: return bool(self.wbool.currentIndex()) else: try: return tp(self.wvalue.text()) except: print("Error converting input to type ", tp.__name__) traceback.print_exc() return None
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool' : ( (_('is true'), 'is true',), (_('is false'), 'is false'), (_('is undefined'), 'is undefined') ), 'ondevice' : ( (_('is true'), 'is set',), (_('is false'), 'is not set'), ), 'identifiers' : ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt') ), 'multiple' : ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single' : ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating', 'datetime'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), key=sort_key): self.column_box.addItem(key, key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx).toString()) if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found'%val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx).toString()) def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx).toString()) if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column'%val) return property(fget=fget, fset=fset) @property def current_val(self): return unicode(self.value_box.text()).strip() @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return m = self.fm[col] dt = m['datatype'] action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _('You can match multiple values by separating' ' them with %s')%m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)