def __init__(self, parent, db): QDialog.__init__(self, parent) self.setupUi(self) for val, text in [(0, '')] + [(i, date(2010, i, 1).strftime('%B')) for i in xrange(1, 13)]: self.date_month.addItem(text, val) for val, text in [('today', _('Today')), ('yesterday', _('Yesterday')), ('thismonth', _('This month'))]: self.date_human.addItem(text, val) self.date_year.setValue(now().year) self.date_day.setSpecialValueText(u' \xa0') vals = [((v['search_terms'] or [k])[0], v['name'] or k) for k, v in db.field_metadata.iteritems() if v.get('datatype', None) == 'datetime'] for k, v in sorted(vals, key=lambda (k, v): sort_key(v)): self.date_field.addItem(v, k) self.date_year.valueChanged.connect(lambda : self.sel_date.setChecked(True)) self.date_month.currentIndexChanged.connect(lambda : self.sel_date.setChecked(True)) self.date_day.valueChanged.connect(lambda : self.sel_date.setChecked(True)) self.date_daysago.valueChanged.connect(lambda : self.sel_daysago.setChecked(True)) self.date_human.currentIndexChanged.connect(lambda : self.sel_human.setChecked(True)) init_dateop(self.dateop_date) self.sel_date.setChecked(True) self.mc = '' searchables = sorted(db.field_metadata.searchable_fields(), key=lambda x: sort_key(x if x[0] != '#' else x[1:])) self.general_combo.addItems(searchables) all_authors = db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) self.authors_box.setEditText('') self.authors_box.set_separator('&') self.authors_box.set_space_before_sep(True) self.authors_box.set_add_separator(tweaks['authors_completer_append_separator']) self.authors_box.update_items_cache(db.all_author_names()) all_series = db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) self.series_box.set_separator(None) self.series_box.update_items_cache([x[1] for x in all_series]) self.series_box.show_initial_value('') all_tags = db.all_tags() self.tags_box.update_items_cache(all_tags) self.box_last_values = copy.deepcopy(box_values) if self.box_last_values: for k,v in self.box_last_values.items(): if k == 'general_index': continue getattr(self, k).setText(v) self.general_combo.setCurrentIndex( self.general_combo.findText(self.box_last_values['general_index'])) self.clear_button.clicked.connect(self.clear_button_pushed) current_tab = gprefs.get('advanced search dialog current tab', 0) self.tabWidget.setCurrentIndex(current_tab) if current_tab == 1: self.matchkind.setCurrentIndex(last_matchkind) self.tabWidget.currentChanged[int].connect(self.tab_changed) self.tab_changed(current_tab)
def __lt__(self, other): l = sort_key(self.sort) r = sort_key(other.sort) if l < r: return 1 if l == r: return self.sort_idx < other.sort_idx return 0
def __ge__(self, other): l = sort_key(self.sort) r = sort_key(other.sort) if l > r: return 1 if l == r: return self.sort_idx >= other.sort_idx return 0
def categories(ctx, rd, library_id): ''' Return the list of top-level categories as a list of dictionaries. Each dictionary is of the form:: { 'name': Display Name, 'url':URL that gives the JSON object corresponding to all entries in this category, 'icon': URL to icon of this category, 'is_category': False for the All Books and Newest categories, True for everything else } ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: ans = {} categories = ctx.get_categories(rd, db) category_meta = db.field_metadata library_id = db.server_library_id def getter(x): return category_meta[x]['name'] displayed_custom_fields = custom_fields_to_display(db) for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue display_name = meta['name'] if category.startswith('@'): category = category.partition('.')[0] display_name = category[1:] url = force_unicode(category) icon = category_icon(category, meta) ans[url] = (display_name, icon) ans = [{'url':k, 'name':v[0], 'icon':v[1], 'is_category':True} for k, v in ans.iteritems()] ans.sort(key=lambda x: sort_key(x['name'])) for name, url, icon in [ (_('All books'), 'allbooks', 'book.png'), (_('Newest'), 'newest', 'forward.png'), ]: ans.insert(0, {'name':name, 'url':url, 'icon':icon, 'is_category':False}) for c in ans: c['url'] = ctx.url_for(globals()['category'], encoded_name=encode_name(c['url']), library_id=library_id) c['icon'] = ctx.url_for(get_icon, which=c['icon']) return ans
def ajax_categories(self): ''' Return the list of top-level categories as a list of dictionaries. Each dictionary is of the form:: { 'name': Display Name, 'url':URL that gives the JSON object corresponding to all entries in this category, 'icon': URL to icon of this category, 'is_category': False for the All Books and Newest categories, True for everything else } ''' ans = {} categories = self.categories_cache() category_meta = self.db.field_metadata def getter(x): return category_meta[x]['name'] displayed_custom_fields = custom_fields_to_display(self.db) for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue display_name = meta['name'] if category.startswith('@'): category = category.partition('.')[0] display_name = category[1:] url = force_unicode(category) icon = category_icon(category, meta) ans[url] = (display_name, icon) ans = [{'url':k, 'name':v[0], 'icon':v[1], 'is_category':True} for k, v in ans.iteritems()] ans.sort(key=lambda x: sort_key(x['name'])) for name, url, icon in [ (_('All books'), 'allbooks', 'book.png'), (_('Newest'), 'newest', 'forward.png'), ]: ans.insert(0, {'name':name, 'url':url, 'icon':icon, 'is_category':False}) for c in ans: c['url'] = category_url(self.opts.url_prefix, c['url']) c['icon'] = icon_url(self.opts.url_prefix, c['icon']) return ans
def init_langs(self, db): if db is not None: pmap = {self._lang_map.get(x[1], x[1]):1 for x in db.get_languages_with_ids()} all_items = sorted(self._lang_map.itervalues(), key=lambda x: (-pmap.get(x, 0), sort_key(x))) else: all_items = sorted(self._lang_map.itervalues(), key=lambda x: sort_key(x)) self.update_items_cache(all_items)
def sort_categories(items, sort, first_letter_sort=False): if sort == "popularity": key = lambda x: (-getattr(x, "count", 0), sort_key(x.sort or x.name)) elif sort == "rating": key = lambda x: (-getattr(x, "avg_rating", 0.0), sort_key(x.sort or x.name)) else: if first_letter_sort: key = lambda x: (collation_order(icu_upper(x.sort or x.name or " ")), sort_key(x.sort or x.name)) else: key = lambda x: sort_key(x.sort or x.name) items.sort(key=key) return items
def categories(ctx, rd, library_id): """ Return the list of top-level categories as a list of dictionaries. Each dictionary is of the form:: { 'name': Display Name, 'url':URL that gives the JSON object corresponding to all entries in this category, 'icon': URL to icon of this category, 'is_category': False for the All Books and Newest categories, True for everything else } """ db = get_db(ctx, library_id) with db.safe_read_lock: ans = {} categories = ctx.get_categories(rd, db) category_meta = db.field_metadata library_id = db.server_library_id def getter(x): return category_meta[x]["name"] displayed_custom_fields = custom_fields_to_display(db) for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ("formats", "identifiers"): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and category not in displayed_custom_fields: continue display_name = meta["name"] if category.startswith("@"): category = category.partition(".")[0] display_name = category[1:] url = force_unicode(category) icon = category_icon(category, meta) ans[url] = (display_name, icon) ans = [{"url": k, "name": v[0], "icon": v[1], "is_category": True} for k, v in ans.iteritems()] ans.sort(key=lambda x: sort_key(x["name"])) for name, url, icon in [(_("All books"), "allbooks", "book.png"), (_("Newest"), "newest", "forward.png")]: ans.insert(0, {"name": name, "url": url, "icon": icon, "is_category": False}) for c in ans: c["url"] = ctx.url_for(globals()["category"], encoded_name=encode_name(c["url"]), library_id=library_id) c["icon"] = ctx.url_for(get_icon, which=c["icon"]) return ans
def sort_categories(items, sort, first_letter_sort=False): if sort == 'popularity': key=lambda x:(-getattr(x, 'count', 0), sort_key(x.sort or x.name)) elif sort == 'rating': key=lambda x:(-getattr(x, 'avg_rating', 0.0), sort_key(x.sort or x.name)) else: if first_letter_sort: key=lambda x:(collation_order(icu_upper(x.sort or x.name or ' ')), sort_key(x.sort or x.name)) else: key=lambda x:sort_key(x.sort or x.name) items.sort(key=key) return items
def __init__(self, name, table, bools_are_tristate, get_template_functions): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'} self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else IDENTITY) # This will be compared to the output of sort_key() which is a # bytestring, therefore it is safer to have it be a bytestring. # Coercing an empty bytestring to unicode will never fail, but the # output of sort_key cannot be coerced to unicode self._default_sort_key = b'' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None self._sort_key = bool_sort_key(bools_are_tristate) elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if tweaks['sort_dates_using_visible_fields']: fmt = None if name in {'timestamp', 'pubdate', 'last_modified'}: fmt = tweaks['gui_%s_display_format' % name] elif self.metadata['is_custom']: fmt = self.metadata.get('display', {}).get('date_format', None) self._sort_key = partial(clean_date_for_sort, fmt=fmt) elif dt == 'comments' or name == 'identifiers': self._default_sort_key = '' if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.sort_sort_key = True if self.is_multiple and '&' in self.metadata['is_multiple']['list_to_ui']: self._sort_key = lambda x: sort_key(author_to_author_sort(x)) self.sort_sort_key = False self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None self.category_formatter = unicode_type if dt == 'rating': if self.metadata['display'].get('allow_half_stars', False): self.category_formatter = lambda x: rating_to_stars(x, True) else: self.category_formatter = rating_to_stars elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None self.get_template_functions = get_template_functions
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db try: categories = rc.get_categories(report_parse_errors=True) except ParseException as p: raise HTTPInternalServerError(p.msg) category_meta = db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x fm = rc.db.field_metadata for category in sorted(categories, key=lambda x: sort_key(getter(x))): if fm.is_ignorable_field(category) and not rc.ctx.is_field_displayable(category): continue if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta['name'], meta['name'], 'N'+category)) last_modified = db.last_modified() rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db categories = rc.get_categories() category_meta = db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): try: return category_meta[x]['name'].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta['name'], meta['name'], 'N'+category)) last_modified = db.last_modified() rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def get_library_init_data(ctx, rd, db, num, sorts, orders): ans = {} with db.safe_read_lock: try: ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders)) except ParseException: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) ans['sortable_fields'] = sorted((( sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name):sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() mdata = ans['metadata'] = {} try: extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(',')) except Exception: extra_books = () for coll in (ans['search_result']['book_ids'], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans
def create_widgets(self, opt): val = self.plugin.prefs[opt.name] if opt.type == 'number': c = QSpinBox if isinstance(opt.default, int) else QDoubleSpinBox widget = c(self) widget.setValue(val) elif opt.type == 'string': widget = QLineEdit(self) widget.setText(val if val else '') elif opt.type == 'bool': widget = QCheckBox(opt.label, self) widget.setChecked(bool(val)) elif opt.type == 'choices': widget = QComboBox(self) items = list(opt.choices.iteritems()) items.sort(key=lambda (k, v): sort_key(v)) for key, label in items: widget.addItem(label, (key)) idx = widget.findData((val)) widget.setCurrentIndex(idx) widget.opt = opt widget.setToolTip(textwrap.fill(opt.desc)) self.widgets.append(widget) r = self.l.rowCount() if opt.type == 'bool': self.l.addWidget(widget, r, 0, 1, self.l.columnCount()) else: l = QLabel(opt.label) l.setToolTip(widget.toolTip()) self.memory.append(l) l.setBuddy(widget) self.l.addWidget(l, r, 0, 1, 1) self.l.addWidget(widget, r, 1, 1, 1)
def browse_toplevel(self): categories = self.categories_cache() category_meta = self.db.field_metadata cats = [ (_('Newest'), 'newest', 'forward.png'), (_('All books'), 'allbooks', 'book.png'), ] def getter(x): return category_meta[x]['name'].lower() displayed_custom_fields = custom_fields_to_display(self.db) uc_displayed = set() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if meta['is_custom'] and category not in displayed_custom_fields: continue # get the icon files if category in self.icon_map: icon = '_'+quote(self.icon_map[category]) elif category in category_icon_map: icon = category_icon_map[category] elif meta['is_custom']: icon = category_icon_map['custom:'] elif meta['kind'] == 'user': icon = category_icon_map['user:'******'blank.png' if meta['kind'] == 'user': dot = category.find('.') if dot > 0: cat = category[:dot] if cat not in uc_displayed: cats.append((meta['name'][:dot-1], cat, icon)) uc_displayed.add(cat) else: cats.append((meta['name'], category, icon)) uc_displayed.add(category) else: cats.append((meta['name'], category, icon)) cats = [(u'<li><a title="{2} {0}" href="{3}/browse/category/{1}"> </a>' u'<img src="{3}{src}" alt="{0}" />' u'<span class="label">{0}</span>' u'</li>') .format(xml(x, True), xml(quote(y)), xml(_('Browse books by')), self.opts.url_prefix, src='/browse/icon/'+z) for x, y, z in cats] main = u'<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\ .format(_('Choose a category to browse by:'), u'\n\n'.join(cats)) return self.browse_template('name').format(title='', script='toplevel();', main=main)
def __init__(self, name, table): self.name, self.table = name, table dt = self.metadata['datatype'] self.has_text_data = dt in {'text', 'comments', 'series', 'enumeration'} self.table_type = self.table.table_type self._sort_key = (sort_key if dt in ('text', 'series', 'enumeration') else lambda x: x) # This will be compared to the output of sort_key() which is a # bytestring, therefore it is safer to have it be a bytestring. # Coercing an empty bytestring to unicode will never fail, but the # output of sort_key cannot be coerced to unicode self._default_sort_key = b'' if dt in {'int', 'float', 'rating'}: self._default_sort_key = 0 elif dt == 'bool': self._default_sort_key = None elif dt == 'datetime': self._default_sort_key = UNDEFINED_DATE if self.name == 'languages': self._sort_key = lambda x:sort_key(calibre_langcode_to_name(x)) self.is_multiple = (bool(self.metadata['is_multiple']) or self.name == 'formats') self.default_value = {} if name == 'identifiers' else () if self.is_multiple else None self.category_formatter = type(u'') if dt == 'rating': self.category_formatter = lambda x:'\u2605'*int(x/2) elif name == 'languages': self.category_formatter = calibre_langcode_to_name self.writer = Writer(self) self.series_field = None
def manifest_key(x): mt = x.get('media-type', '') href = x.get('href', '') ext = href.rpartition('.')[-1].lower() cat = 1000 if mt in OEB_DOCS: cat = 0 elif mt == guess_type('a.ncx'): cat = 1 elif mt in OEB_STYLES: cat = 2 elif mt.startswith('image/'): cat = 3 elif ext in {'otf', 'ttf', 'woff'}: cat = 4 elif mt.startswith('audio/'): cat = 5 elif mt.startswith('video/'): cat = 6 if cat == 0: i = spine_ids.get(x.get('id', None), 1000000000) else: i = sort_key(href) return (cat, i)
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 interface_data(ctx, rd): ''' Return the data needed to create the server main UI Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books='' ''' ans = { 'username':rd.username, 'output_format':prefs['output_format'].upper(), 'input_formats':{x.upper():True for x in available_input_formats()}, 'gui_pubdate_display_format':tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format':tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format':tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), } ans['library_map'], ans['default_library'] = ctx.library_info(rd) ud = {} if rd.username: # Override session data with stored values for the authenticated user, # if any ud = ctx.user_manager.get_session_data(rd.username) lid = ud.get('library_id') if lid and lid in ans['library_map']: rd.query.set('library_id', lid) usort = ud.get('sort') if usort: rd.query.set('sort', usort) ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd) ans['user_session_data'] = ud try: num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS)) except Exception: raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) with db.safe_read_lock: try: ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders)) except ParseException: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) ans['sortable_fields'] = sorted((( sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name):sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() ans['icon_map'] = icon_map() ans['icon_path'] = ctx.url_for('/icon', which='') mdata = ans['metadata'] = {} try: extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(',')) except Exception: extra_books = () for coll in (ans['search_result']['book_ids'], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans
def initialize_category_lists(self, book_ids): self.db_categories = self.db.new_api.get_categories(book_ids=book_ids) self.all_items = [] self.all_items_dict = {} for idx,label in enumerate(self.category_labels): if idx == 0: continue for n in self.category_values[idx](): t = Item(name=n, label=label, index=len(self.all_items), icon=self.category_icons[idx], exists=True) self.all_items.append(t) self.all_items_dict[icu_lower(label+':'+n)] = t for cat in self.categories: for item,l in enumerate(self.categories[cat]): key = icu_lower(':'.join([l[1], l[0]])) t = self.all_items_dict.get(key, None) if l[1] in self.category_labels: if t is None: t = Item(name=l[0], label=l[1], index=len(self.all_items), icon=self.category_icons[self.category_labels.index(l[1])], exists=False) self.all_items.append(t) self.all_items_dict[key] = t l[2] = t.index else: # remove any references to a category that no longer exists del self.categories[cat][item] self.all_items_sorted = sorted(self.all_items, key=lambda x: sort_key(x.name))
def opds(self, version=0): version = int(version) if version not in BASE_HREFS: raise cherrypy.HTTPError(404, "Not found") categories = self.categories_cache(self.get_opds_allowed_ids_for_version(version)) category_meta = self.db.field_metadata cats = [(_("Newest"), _("Date"), "Onewest"), (_("Title"), _("Title"), "Otitle")] def getter(x): try: return category_meta[x]["name"].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ("formats", "identifiers"): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and category not in custom_fields_to_display(self.db): continue cats.append((meta["name"], meta["name"], "N" + category)) updated = self.db.last_modified() cherrypy.response.headers["Last-Modified"] = self.last_modified(updated) cherrypy.response.headers["Content-Type"] = "application/atom+xml" feed = TopLevel(updated, cats, version) return str(feed)
def opds(ctx, rd): rc = RequestContext(ctx, rd) db = rc.db categories = rc.get_categories() category_meta = db.field_metadata cats = [(_("Newest"), _("Date"), "Onewest"), (_("Title"), _("Title"), "Otitle")] def getter(x): try: return category_meta[x]["name"].lower() except KeyError: return x for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ("formats", "identifiers"): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta["name"], meta["name"], "N" + category)) last_modified = db.last_modified() rd.outheaders["Last-Modified"] = http_date(timestampfromdt(last_modified)) return TopLevel(last_modified, cats, rc).root
def setup_ui(self): self.l = l = QVBoxLayout(self) self.la = la = QLabel(_( 'Create rules to convert identifiers into links.')) la.setWordWrap(True) l.addWidget(la) items = [] for k, lx in msprefs['id_link_rules'].iteritems(): for n, t in lx: items.append((k, n, t)) items.sort(key=lambda x:sort_key(x[1])) self.table = t = QTableWidget(len(items), 3, self) t.setHorizontalHeaderLabels([_('Key'), _('Name'), _('Template')]) for r, (key, val, template) in enumerate(items): t.setItem(r, 0, QTableWidgetItem(key)) t.setItem(r, 1, QTableWidgetItem(val)) t.setItem(r, 2, QTableWidgetItem(template)) l.addWidget(t) t.horizontalHeader().setSectionResizeMode(2, t.horizontalHeader().Stretch) self.cb = b = QPushButton(QIcon(I('plus.png')), _('&Add rule'), self) b.clicked.connect(lambda : self.edit_rule()) self.bb.addButton(b, self.bb.ActionRole) self.rb = b = QPushButton(QIcon(I('minus.png')), _('&Remove rule'), self) b.clicked.connect(lambda : self.remove_rule()) self.bb.addButton(b, self.bb.ActionRole) self.eb = b = QPushButton(QIcon(I('modified.png')), _('&Edit rule'), self) b.clicked.connect(lambda : self.edit_rule(self.table.currentRow())) self.bb.addButton(b, self.bb.ActionRole) l.addWidget(self.bb)
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 rebuild(self): self.currentChanged.disconnect(self.tab_changed) db = self.current_db virt_libs = frozenset(db.prefs.get("virtual_libraries", {})) hidden = frozenset(db.prefs["virt_libs_hidden"]) if hidden - virt_libs: db.prefs["virt_libs_hidden"] = list(hidden.intersection(virt_libs)) order = db.prefs["virt_libs_order"] while self.count(): self.removeTab(0) current_lib = db.data.get_base_restriction_name() current_idx = all_idx = None virt_libs = (set(virt_libs) - hidden) | {""} order = {x: i for i, x in enumerate(order)} for i, vl in enumerate(sorted(virt_libs, key=lambda x: (order.get(x, 0), sort_key(x)))): self.addTab(vl or _("All books")) self.setTabData(i, vl) if vl == current_lib: current_idx = i if not vl: all_idx = i self.setCurrentIndex(all_idx if current_idx is None else current_idx) self.currentChanged.connect(self.tab_changed) try: self.tabButton(all_idx, self.RightSide).setVisible(False) except AttributeError: try: self.tabButton(all_idx, self.LeftSide).setVisible(False) except AttributeError: # On some OS X machines (using native style) the tab button is # on the left pass
def opds(self, version=0): version = int(version) if version not in BASE_HREFS: raise cherrypy.HTTPError(404, 'Not found') categories = self.categories_cache( self.get_opds_allowed_ids_for_version(version)) category_meta = self.db.field_metadata cats = [ (_('Newest'), _('Date'), 'Onewest'), (_('Title'), _('Title'), 'Otitle'), ] def getter(x): return category_meta[x]['name'].lower() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue cats.append((meta['name'], meta['name'], 'N'+category)) updated = self.db.last_modified() cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) cherrypy.response.headers['Content-Type'] = 'application/atom+xml' feed = TopLevel(updated, cats, version) return str(feed)
def fill_applied_items(self): if self.current_cat_name: self.applied_items = [cat[2] for cat in self.categories.get(self.current_cat_name, [])] else: self.applied_items = [] self.applied_items.sort(key=lambda x:sort_key(self.all_items[x].name)) self.display_filtered_categories(None)
def __init__(self, parent, db): QDialog.__init__(self, parent) self.setupUi(self) self.mc = '' searchables = sorted(db.field_metadata.searchable_fields(), key=lambda x: sort_key(x if x[0] != '#' else x[1:])) self.general_combo.addItems(searchables) all_authors = db.all_authors() all_authors.sort(key=lambda x : sort_key(x[1])) self.authors_box.setEditText('') self.authors_box.set_separator('&') self.authors_box.set_space_before_sep(True) self.authors_box.set_add_separator(tweaks['authors_completer_append_separator']) self.authors_box.update_items_cache(db.all_author_names()) all_series = db.all_series() all_series.sort(key=lambda x : sort_key(x[1])) self.series_box.set_separator(None) self.series_box.update_items_cache([x[1] for x in all_series]) self.series_box.show_initial_value('') all_tags = db.all_tags() self.tags_box.update_items_cache(all_tags) self.box_last_values = copy.deepcopy(box_values) if self.box_last_values: for k,v in self.box_last_values.items(): if k == 'general_index': continue getattr(self, k).setText(v) self.general_combo.setCurrentIndex( self.general_combo.findText(self.box_last_values['general_index'])) self.buttonBox.accepted.connect(self.advanced_search_button_pushed) self.tab_2_button_box.accepted.connect(self.accept) self.tab_2_button_box.rejected.connect(self.reject) self.clear_button.clicked.connect(self.clear_button_pushed) self.adv_search_used = False current_tab = gprefs.get('advanced search dialog current tab', 0) self.tabWidget.setCurrentIndex(current_tab) if current_tab == 1: self.matchkind.setCurrentIndex(last_matchkind) self.tabWidget.currentChanged[int].connect(self.tab_changed) self.tab_changed(current_tab)
def add_builtin_recipe(self): from calibre.web.feeds.recipes.collection import \ get_builtin_recipe_collection, get_builtin_recipe_by_id from PyQt5.Qt import QDialog, QVBoxLayout, QListWidgetItem, \ QListWidget, QDialogButtonBox, QSize d = QDialog(self) d.l = QVBoxLayout() d.setLayout(d.l) d.list = QListWidget(d) d.list.doubleClicked.connect(lambda x: d.accept()) d.l.addWidget(d.list) d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal, d) d.bb.accepted.connect(d.accept) d.bb.rejected.connect(d.reject) d.l.addWidget(d.bb) d.setWindowTitle(_('Choose builtin recipe')) items = [] for r in get_builtin_recipe_collection(): id_ = r.get('id', '') title = r.get('title', '') lang = r.get('language', '') if id_ and title: items.append((title + ' [%s]'%lang, id_)) items.sort(key=lambda x:sort_key(x[0])) for title, id_ in items: item = QListWidgetItem(title) item.setData(Qt.UserRole, id_) d.list.addItem(item) d.resize(QSize(450, 400)) ret = d.exec_() d.list.doubleClicked.disconnect() if ret != d.Accepted: return items = list(d.list.selectedItems()) if not items: return item = items[-1] id_ = unicode(item.data(Qt.UserRole) or '') title = unicode(item.data(Qt.DisplayRole) or '').rpartition(' [')[0] profile = get_builtin_recipe_by_id(id_, download_recipe=True) if profile is None: raise Exception('Something weird happened') if self._model.has_title(title): if question_dialog(self, _('Replace recipe?'), _('A custom recipe named %s already exists. Do you want to ' 'replace it?')%title): self._model.replace_by_title(title, profile) else: return else: self.model.add(title, profile) self.clear()
def none_cmp(xx, yy): x = xx[1] y = yy[1] if x is None and y is None: # No sort_key needed here, because defaults are ascii return cmp(xx[2], yy[2]) if x is None: return 1 if y is None: return -1 if isinstance(x, basestring) and isinstance(y, basestring): x, y = sort_key(force_unicode(x)), sort_key(force_unicode(y)) c = cmp(x, y) if c != 0: return c # same as above -- no sort_key needed here return cmp(xx[2], yy[2])
def sort_language_items_key(self, val): idx = self.recently_used.get(val, 100000) return (idx, sort_key(val))
def field_sort_key(y, fm=None): m1 = fm[y] name = icu_lower(m1['name']) n1 = 'zzzzz' + name if m1['datatype'] == 'comments' and m1.get( 'display', {}).get('interpret_as') != 'short-text' else name return sort_key(n1)
def initialize_series(self): all_series = self.db.all_series() all_series.sort(key=lambda x: sort_key(x[1])) self.series.set_separator(None) self.series.update_items_cache([x[1] for x in all_series])
def entry_sort_key(entry): return sort_key(entry['Name'])
def interface_data(ctx, rd): ''' Return the data needed to create the server main UI Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books='' ''' ans = { 'username':rd.username, 'output_format':prefs['output_format'].upper(), 'input_formats':{x.upper():True for x in available_input_formats()}, 'gui_pubdate_display_format':tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format':tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format':tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), 'allow_console_print':getattr(rd.opts, 'allow_console_print', False), } ans['library_map'], ans['default_library'] = ctx.library_info(rd) ud = {} if rd.username: # Override session data with stored values for the authenticated user, # if any ud = ctx.user_manager.get_session_data(rd.username) lid = ud.get('library_id') if lid and lid in ans['library_map']: rd.query.set('library_id', lid) usort = ud.get('sort') if usort: rd.query.set('sort', usort) ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd) ans['user_session_data'] = ud try: num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS)) except Exception: raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) with db.safe_read_lock: try: ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders)) except ParseException: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) ans['sortable_fields'] = sorted((( sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name):sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() ans['icon_map'] = icon_map() ans['icon_path'] = ctx.url_for('/icon', which='') mdata = ans['metadata'] = {} try: extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(',')) except Exception: extra_books = () for coll in (ans['search_result']['book_ids'], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans
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 browse_toplevel(self): categories = self.categories_cache() category_meta = self.db.field_metadata cats = [ (_('Newest'), 'newest', 'forward.png'), (_('All books'), 'allbooks', 'book.png'), ] def getter(x): return category_meta[x]['name'].lower() displayed_custom_fields = custom_fields_to_display(self.db) uc_displayed = set() for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if meta['is_custom'] and category not in displayed_custom_fields: continue # get the icon files if category in category_icon_map: icon = category_icon_map[category] elif meta['is_custom']: icon = category_icon_map['custom:'] elif meta['kind'] == 'user': icon = category_icon_map['user:'******'blank.png' if meta['kind'] == 'user': dot = category.find('.') if dot > 0: cat = category[:dot] if cat not in uc_displayed: cats.append((meta['name'][:dot - 1], cat, icon)) uc_displayed.add(cat) else: cats.append((meta['name'], category, icon)) uc_displayed.add(category) else: cats.append((meta['name'], category, icon)) cats = [( u'<li><a title="{2} {0}" href="{3}/browse/category/{1}"> </a>' u'<img src="{3}{src}" alt="{0}" />' u'<span class="label">{0}</span>' u'</li>').format(xml(x, True), xml(quote(y)), xml(_('Browse books by')), self.opts.url_prefix, src='/browse/icon/' + z) for x, y, z in cats] main = u'<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\ .format(_('Choose a category to browse by:'), u'\n\n'.join(cats)) return self.browse_template('name').format(title='', script='toplevel();', main=main)
def __lt__(self, other): return sort_key(unicode_type(self.text())) < sort_key( unicode_type(other.text()))
def __ge__(self, other): return sort_key(unicode_type(self.text())) >= sort_key( unicode_type(other.text()))
def icu_collator(s1, s2): return cmp(sort_key(force_unicode(s1, 'utf-8')), sort_key(force_unicode(s2, 'utf-8')))
def genesis(self, gui): self.gui = gui if not ismacos and not iswindows: self.label_widget_style.setVisible(False) self.opt_ui_style.setVisible(False) db = gui.library_view.model().db r = self.register try: self.icon_theme_title = json.loads(I('icon-theme.json', data=True))['name'] except Exception: self.icon_theme_title = _('Default icons') self.icon_theme.setText(_('Icon theme: <b>%s</b>') % self.icon_theme_title) self.commit_icon_theme = None self.icon_theme_button.clicked.connect(self.choose_icon_theme) self.default_author_link = DefaultAuthorLink(self.default_author_link_container) self.default_author_link.changed_signal.connect(self.changed_signal) r('gui_layout', config, restart_required=True, choices=[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')]) r('hidpi', gprefs, restart_required=True, choices=[(_('Automatic'), 'auto'), (_('On'), 'on'), (_('Off'), 'off')]) if ismacos: self.opt_hidpi.setVisible(False), self.label_hidpi.setVisible(False) r('ui_style', gprefs, restart_required=True, choices=[(_('System default'), 'system'), (_('calibre style'), 'calibre')]) r('book_list_tooltips', gprefs) r('dnd_merge', gprefs) r('wrap_toolbar_text', gprefs, restart_required=True) r('show_layout_buttons', gprefs, restart_required=True) r('row_numbers_in_book_list', gprefs) r('tag_browser_old_look', gprefs) r('tag_browser_hide_empty_categories', gprefs) r('tag_browser_always_autocollapse', gprefs) r('tag_browser_show_tooltips', gprefs) r('tag_browser_allow_keyboard_focus', gprefs) r('bd_show_cover', gprefs) r('bd_overlay_cover_size', gprefs) r('cover_grid_width', gprefs) r('cover_grid_height', gprefs) r('cover_grid_cache_size_multiple', gprefs) r('cover_grid_disk_cache_size', gprefs) r('cover_grid_spacing', gprefs) r('cover_grid_show_title', gprefs) r('tag_browser_show_counts', gprefs) r('tag_browser_item_padding', gprefs) r('qv_respects_vls', gprefs) r('qv_dclick_changes_column', gprefs) r('qv_retkey_changes_column', gprefs) r('qv_follows_column', gprefs) r('cover_flow_queue_length', config, restart_required=True) r('cover_browser_reflections', gprefs) r('cover_browser_title_template', db.prefs) fm = db.field_metadata r('cover_browser_subtitle_field', db.prefs, choices=[(_('No subtitle'), 'none')] + sorted( (fm[k].get('name'), k) for k in fm.all_field_keys() if fm[k].get('name') )) r('emblem_size', gprefs) r('emblem_position', gprefs, choices=[ (_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')]) r('book_list_extra_row_spacing', gprefs) r('booklist_grid', gprefs) r('book_details_comments_heading_pos', gprefs, choices=[ (_('Never'), 'hide'), (_('Above text'), 'above'), (_('Beside text'), 'side')]) self.cover_browser_title_template_button.clicked.connect(self.edit_cb_title_template) self.id_links_button.clicked.connect(self.edit_id_link_rules) def get_esc_lang(l): if l == 'en': return 'English' return get_language(l) lang = get_lang() if lang is None or lang not in available_translations(): lang = 'en' items = [(l, get_esc_lang(l)) for l in available_translations() if l != lang] if lang != 'en': items.append(('en', get_esc_lang('en'))) items.sort(key=lambda x: x[1].lower()) choices = [(y, x) for x, y in items] # Default language is the autodetected one choices = [(get_language(lang), lang)] + choices r('language', prefs, choices=choices, restart_required=True) r('show_avg_rating', config) r('disable_animations', config) r('systray_icon', config, restart_required=True) r('show_splash_screen', gprefs) r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) r('separate_cover_flow', config, restart_required=True) r('cb_fullscreen', gprefs) r('cb_preserve_aspect_ratio', gprefs) choices = [(_('Off'), 'off'), (_('Small'), 'small'), (_('Medium'), 'medium'), (_('Large'), 'large')] r('toolbar_icon_size', gprefs, choices=choices) choices = [(_('If there is enough room'), 'auto'), (_('Always'), 'always'), (_('Never'), 'never')] r('toolbar_text', gprefs, choices=choices) choices = [(_('Disabled'), 'disable'), (_('By first letter'), 'first letter'), (_('Partitioned'), 'partition')] r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_collapse_at', gprefs) r('tags_browser_collapse_fl_at', gprefs) choices = {k for k in db.field_metadata.all_field_keys() if (db.field_metadata[k]['is_category'] and ( db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration' ]) and not db.field_metadata[k]['display'].get('is_names', False)) or ( db.field_metadata[k]['datatype'] in ['composite' ] and db.field_metadata[k]['display'].get('make_category', False))} choices |= {'search'} r('tag_browser_dont_collapse', gprefs, setting=CommaSeparatedList, choices=sorted(choices, key=sort_key)) choices -= {'authors', 'publisher', 'formats', 'news', 'identifiers'} self.opt_categories_using_hierarchy.update_items_cache(choices) r('categories_using_hierarchy', db.prefs, setting=CommaSeparatedList, choices=sorted(choices, key=sort_key)) fm = db.field_metadata choices = sorted(((fm[k]['name'], k) for k in fm.displayable_field_keys() if fm[k]['name']), key=lambda x:sort_key(x[0])) r('field_under_covers_in_grid', db.prefs, choices=choices) self.current_font = self.initial_font = None self.change_font_button.clicked.connect(self.change_font) self.display_model = DisplayedFields(self.gui.current_db, self.field_display_order) self.display_model.dataChanged.connect(self.changed_signal) self.field_display_order.setModel(self.display_model) connect_lambda(self.df_up_button.clicked, self, lambda self: move_field_up(self.field_display_order, self.display_model)) connect_lambda(self.df_down_button.clicked, self, lambda self: move_field_down(self.field_display_order, self.display_model)) self.qv_display_model = QVDisplayedFields(self.gui.current_db, self.qv_display_order) self.qv_display_model.dataChanged.connect(self.changed_signal) self.qv_display_order.setModel(self.qv_display_model) connect_lambda(self.qv_up_button.clicked, self, lambda self: move_field_up(self.qv_display_order, self.qv_display_model)) connect_lambda(self.qv_down_button.clicked, self, lambda self: move_field_down(self.qv_display_order, self.qv_display_model)) self.edit_rules = EditRules(self.tabWidget) self.edit_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.edit_rules, QIcon(I('format-fill-color.png')), _('Column &coloring')) self.icon_rules = EditRules(self.tabWidget) self.icon_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.icon_rules, QIcon(I('icon_choose.png')), _('Column &icons')) self.grid_rules = EditRules(self.emblems_tab) self.grid_rules.changed.connect(self.changed_signal) self.emblems_tab.setLayout(QVBoxLayout()) self.emblems_tab.layout().addWidget(self.grid_rules) self.tabWidget.setCurrentIndex(0) keys = [QKeySequence('F11', QKeySequence.SequenceFormat.PortableText), QKeySequence( 'Ctrl+Shift+F', QKeySequence.SequenceFormat.PortableText)] keys = [unicode_type(x.toString(QKeySequence.SequenceFormat.NativeText)) for x in keys] self.fs_help_msg.setText(unicode_type(self.fs_help_msg.text())%( _(' or ').join(keys))) self.size_calculated.connect(self.update_cg_cache_size, type=Qt.ConnectionType.QueuedConnection) self.tabWidget.currentChanged.connect(self.tab_changed) l = self.cg_background_box.layout() self.cg_bg_widget = w = Background(self) l.addWidget(w, 0, 0, 3, 1) self.cover_grid_color_button = b = QPushButton(_('Change &color'), self) b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) l.addWidget(b, 0, 1) b.clicked.connect(self.change_cover_grid_color) self.cover_grid_texture_button = b = QPushButton(_('Change &background image'), self) b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) l.addWidget(b, 1, 1) b.clicked.connect(self.change_cover_grid_texture) self.cover_grid_default_appearance_button = b = QPushButton(_('Restore &default appearance'), self) b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) l.addWidget(b, 2, 1) b.clicked.connect(self.restore_cover_grid_appearance) self.cover_grid_empty_cache.clicked.connect(self.empty_cache) self.cover_grid_open_cache.clicked.connect(self.open_cg_cache) connect_lambda(self.cover_grid_smaller_cover.clicked, self, lambda self: self.resize_cover(True)) connect_lambda(self.cover_grid_larger_cover.clicked, self, lambda self: self.resize_cover(False)) self.cover_grid_reset_size.clicked.connect(self.cg_reset_size) self.opt_cover_grid_disk_cache_size.setMinimum(self.gui.grid_view.thumbnail_cache.min_disk_cache) self.opt_cover_grid_disk_cache_size.setMaximum(self.gui.grid_view.thumbnail_cache.min_disk_cache * 100) self.opt_cover_grid_width.valueChanged.connect(self.update_aspect_ratio) self.opt_cover_grid_height.valueChanged.connect(self.update_aspect_ratio) self.opt_book_details_css.textChanged.connect(self.changed_signal) from calibre.gui2.tweak_book.editor.text import get_highlighter, get_theme self.css_highlighter = get_highlighter('css')() self.css_highlighter.apply_theme(get_theme(None)) self.css_highlighter.set_document(self.opt_book_details_css.document())
def __init__(self, parent, text, mi=None, fm=None, color_field=None, icon_field_key=None, icon_rule_kind=None, doing_emblem=False, text_is_placeholder=False, dialog_is_st_editor=False, global_vars=None, all_functions=None, builtin_functions=None): QDialog.__init__(self, parent) Ui_TemplateDialog.__init__(self) self.setupUi(self) self.coloring = color_field is not None self.iconing = icon_field_key is not None self.embleming = doing_emblem self.dialog_is_st_editor = dialog_is_st_editor if global_vars is None: self.global_vars = {} else: self.global_vars = global_vars cols = [] self.fm = fm if fm is not None: 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 not self.coloring: continue from calibre.gui2.preferences.coloring import all_columns_string name = all_columns_string if key == color_row_key else fm[key][ 'name'] if name: cols.append((name, key)) self.color_layout.setVisible(False) self.icon_layout.setVisible(False) if self.coloring: self.color_layout.setVisible(True) for n1, k1 in cols: self.colored_field.addItem( n1 + (' (' + k1 + ')' if k1 != color_row_key else ''), k1) self.colored_field.setCurrentIndex( self.colored_field.findData(color_field)) elif self.iconing or self.embleming: self.icon_layout.setVisible(True) if self.embleming: self.icon_kind_label.setVisible(False) self.icon_kind.setVisible(False) self.icon_chooser_label.setVisible(False) self.icon_field.setVisible(False) for n1, k1 in cols: self.icon_field.addItem('{} ({})'.format(n1, k1), k1) self.icon_file_names = [] d = os.path.join(config_dir, 'cc_icons') if os.path.exists(d): for icon_file in os.listdir(d): icon_file = icu_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() if self.iconing: dex = 0 from calibre.gui2.preferences.coloring import icon_rule_kinds for i, tup in enumerate(icon_rule_kinds): txt, val = tup self.icon_kind.addItem(txt, userData=(val)) if val == icon_rule_kind: dex = i self.icon_kind.setCurrentIndex(dex) self.icon_field.setCurrentIndex( self.icon_field.findData(icon_field_key)) self.setup_saved_template_editor(not dialog_is_st_editor, dialog_is_st_editor) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags() & (~Qt.WindowType.WindowContextHelpButtonHint)) self.setWindowIcon(icon) self.all_functions = all_functions if all_functions else formatter_functions( ).get_functions() self.builtins = (builtin_functions if builtin_functions else formatter_functions().get_builtins_and_aliases()) # Set up the display table self.table_column_widths = None try: self.table_column_widths = \ gprefs.get('template_editor_table_widths', None) except: pass self.set_mi(mi, fm) self.last_text = '' self.highlighter = TemplateHighlighter(self.textbox.document(), builtin_functions=self.builtins) self.textbox.cursorPositionChanged.connect(self.text_cursor_changed) self.textbox.textChanged.connect(self.textbox_changed) self.textbox.setFont(self.get_current_font()) self.textbox.setTabStopWidth(10) self.source_code.setTabStopWidth(10) self.documentation.setReadOnly(True) self.source_code.setReadOnly(True) if text is not None: if text_is_placeholder: self.textbox.setPlaceholderText(text) self.textbox.clear() text = '' else: self.textbox.setPlainText(text) else: text = '' self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText( _('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText( _('&Cancel')) self.color_copy_button.clicked.connect(self.color_to_clipboard) self.filename_button.clicked.connect(self.filename_button_clicked) self.icon_copy_button.clicked.connect(self.icon_to_clipboard) try: with open(P('template-functions.json'), 'rb') as f: self.builtin_source_dict = json.load(f, encoding='utf-8') except: self.builtin_source_dict = {} func_names = sorted(self.all_functions) self.function.clear() self.function.addItem('') for f in func_names: self.function.addItem( '{} -- {}'.format( f, self.function_type_string(f, longform=False)), f) self.function.setCurrentIndex(0) self.function.currentIndexChanged.connect(self.function_changed) self.rule = (None, '') tt = _('Template language tutorial') self.template_tutorial.setText( '<a href="%s">%s</a>' % (localize_user_manual_link( 'https://manual.calibre-ebook.com/template_lang.html'), tt)) tt = _('Template function reference') self.template_func_reference.setText( '<a href="%s">%s</a>' % (localize_user_manual_link( 'https://manual.calibre-ebook.com/generated/en/template_ref.html' ), tt)) s = gprefs.get('template_editor_break_on_print', False) self.go_button.setEnabled(s) self.remove_all_button.setEnabled(s) self.set_all_button.setEnabled(s) self.toggle_button.setEnabled(s) self.breakpoint_line_box.setEnabled(s) self.breakpoint_line_box_label.setEnabled(s) self.break_box.setChecked(s) self.break_box.stateChanged.connect(self.break_box_changed) self.go_button.clicked.connect(self.go_button_pressed) self.textbox.setFocus() self.set_up_font_boxes() self.toggle_button.clicked.connect(self.toggle_button_pressed) self.remove_all_button.clicked.connect(self.remove_all_button_pressed) self.set_all_button.clicked.connect(self.set_all_button_pressed) self.load_button.clicked.connect(self.load_template_from_file) self.save_button.clicked.connect(self.save_template) self.textbox.setWordWrapMode(QTextOption.WordWrap) self.textbox.setContextMenuPolicy( Qt.ContextMenuPolicy.CustomContextMenu) self.textbox.customContextMenuRequested.connect(self.show_context_menu) # Now geometry try: geom = gprefs.get('template_editor_dialog_geometry', None) if geom is not None: QApplication.instance().safe_restore_geometry( self, QByteArray(geom)) except Exception: pass
def sort_keys_for_books(self, get_metadata, lang_map, all_book_ids): return {id_: sort_key(self.get_value_with_cache(id_, get_metadata)) for id_ in all_book_ids}
def sort_languages(x): from calibre.utils.icu import sort_key lc, name = x if lc == language: return '' return sort_key(unicode(name))
def field_sort_key(y, fm=None): m1 = fm[y] name = icu_lower(m1['name']) n1 = 'zzzzz' + name if m1['datatype'] == 'comments' else name return sort_key(n1)
def browse_render_details(self, id_): try: mi = self.db.get_metadata(id_, index_is_id=True) except: return _('This book has been deleted') else: args, fmt, fmts, fname = self.browse_get_book_args( mi, id_, add_category_links=True) args['formats'] = '' if fmts: ofmts = [u'<a href="{4}/get/{0}/{1}_{2}.{0}" title="{3}">{3}</a>'\ .format(fmt, fname, id_, fmt.upper(), self.opts.url_prefix) for fmt in fmts] ofmts = ', '.join(ofmts) args['formats'] = ofmts fields, comments = [], [] displayed_custom_fields = custom_fields_to_display(self.db) for field, m in list(mi.get_all_standard_metadata(False).items()) + \ list(mi.get_all_user_metadata(False).items()): if m['is_custom'] and field not in displayed_custom_fields: continue if m['datatype'] == 'comments' or field == 'comments' or ( m['datatype'] == 'composite' and \ m['display'].get('contains_html', False)): val = mi.get(field, '') if val and val.strip(): comments.append((m['name'], comments_to_html(val))) continue if field in ('title', 'formats') or not args.get(field, False) \ or not m['name']: continue if m['datatype'] == 'rating': r = u'<strong>%s: </strong>'%xml(m['name']) + \ render_rating(mi.get(field)/2.0, self.opts.url_prefix, prefix=m['name'])[0] else: r = u'<strong>%s: </strong>'%xml(m['name']) + \ args[field] fields.append((m['name'], r)) fields.sort(key=lambda x: sort_key(x[0])) fields = [ u'<div class="field">{0}</div>'.format(f[1]) for f in fields ] fields = u'<div class="fields">%s</div>' % ('\n\n'.join(fields)) comments.sort(key=lambda x: x[0].lower()) comments = [ (u'<div class="field"><strong>%s: </strong>' u'<div class="comment">%s</div></div>') % (xml(c[0]), c[1]) for c in comments ] comments = u'<div class="comments">%s</div>' % ( '\n\n'.join(comments)) return self.browse_details_template.format(id=id_, title=xml( mi.title, True), fields=fields, formats=args['formats'], comments=comments)
def mobile(self, start='1', num='25', sort='date', search='', _=None, order='descending'): ''' Serves metadata from the calibre database as XML. :param sort: Sort results by ``sort``. Can be one of `title,author,rating`. :param search: Filter results by ``search`` query. See :class:`SearchQueryParser` for query syntax :param start,num: Return the slice `[start:start+num]` of the sorted and filtered results :param _: Firefox seems to sometimes send this when using XMLHttpRequest with no caching ''' try: start = int(start) except ValueError: raise cherrypy.HTTPError(400, 'start: %s is not an integer' % start) try: num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer' % num) if not search: search = '' if isbytestring(search): search = search.decode('UTF-8') ids = self.search_for_books(search) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: self.sort(items, sort, (order.lower().strip() == 'ascending')) CFM = self.db.field_metadata CKEYS = [ key for key in sorted(custom_fields_to_display(self.db), key=lambda x: sort_key(CFM[x]['name'])) ] # This method uses its own book dict, not the Metadata dict. The loop # below could be changed to use db.get_metadata instead of reading # info directly from the record made by the view, but it doesn't seem # worth it at the moment. books = [] for record in items[(start - 1):(start - 1) + num]: book = { 'formats': record[FM['formats']], 'size': record[FM['size']] } if not book['formats']: book['formats'] = '' if not book['size']: book['size'] = 0 book['size'] = human_readable(book['size']) aus = record[FM['authors']] if record[ FM['authors']] else __builtin__._('Unknown') aut_is = CFM['authors']['is_multiple'] authors = aut_is['list_to_ui'].join( [i.replace('|', ',') for i in aus.split(',')]) book['authors'] = authors book['series_index'] = fmt_sidx(float(record[FM['series_index']])) book['series'] = record[FM['series']] book['tags'] = format_tag_string(record[FM['tags']], ',', no_tag_count=True) book['title'] = record[FM['title']] for x in ('timestamp', 'pubdate'): book[x] = strftime('%d %b, %Y', as_local_time(record[FM[x]])) book['id'] = record[FM['id']] books.append(book) for key in CKEYS: def concat(name, val): return '%s:#:%s' % (name, unicode(val)) mi = self.db.get_metadata(record[CFM['id']['rec_index']], index_is_id=True) name, val = mi.format_field(key) if not val: continue datatype = CFM[key]['datatype'] if datatype in ['comments']: continue if datatype == 'text' and CFM[key]['is_multiple']: book[key] = concat( name, format_tag_string( val, CFM[key]['is_multiple']['ui_to_list'], no_tag_count=True, joinval=CFM[key]['is_multiple']['list_to_ui'])) else: book[key] = concat(name, val) updated = self.db.last_modified() cherrypy.response.headers['Content-Type'] = 'text/html; charset=utf-8' cherrypy.response.headers['Last-Modified'] = self.last_modified( updated) url_base = "/mobile?search=" + search + ";order=" + order + ";sort=" + sort + ";num=" + str( num) ua = cherrypy.request.headers.get('User-Agent', '').strip() have_kobo_browser = self.is_kobo_browser(ua) raw = html.tostring(build_index(books, num, search, sort, order, start, len(ids), url_base, CKEYS, self.opts.url_prefix, have_kobo_browser=have_kobo_browser), encoding='utf-8', pretty_print=True) # tostring's include_meta_content_type is broken raw = raw.replace( '<head>', '<head>\n' '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">' ) return raw
def create_menubar(self): if isosx: p, q = self.create_application_menubar() q.triggered.connect(self.action_quit.trigger) p.triggered.connect(self.action_preferences.trigger) f = factory(app_id='com.calibre-ebook.EditBook-%d' % os.getpid()) b = f.create_window_menubar(self) f = b.addMenu(_('&File')) f.addAction(self.action_new_file) f.addAction(self.action_import_files) f.addSeparator() f.addAction(self.action_open_book) f.addAction(self.action_new_book) f.addAction(self.action_import_book) f.addAction(self.action_open_book_folder) self.recent_books_menu = f.addMenu(_('&Recently opened books')) self.update_recent_books() f.addSeparator() f.addAction(self.action_save) f.addAction(self.action_save_copy) f.addSeparator() f.addAction(self.action_compare_book) f.addAction(self.action_quit) e = b.addMenu(_('&Edit')) e.addAction(self.action_global_undo) e.addAction(self.action_global_redo) e.addAction(self.action_create_checkpoint) e.addSeparator() e.addAction(self.action_editor_undo) e.addAction(self.action_editor_redo) e.addSeparator() e.addAction(self.action_editor_cut) e.addAction(self.action_editor_copy) e.addAction(self.action_editor_paste) e.addAction(self.action_insert_char) e.addSeparator() e.addAction(self.action_quick_edit) e.addAction(self.action_preferences) e = b.addMenu(_('&Tools')) tm = e.addMenu(_('Table of Contents')) tm.addAction(self.action_toc) tm.addAction(self.action_inline_toc) e.addAction(self.action_manage_fonts) e.addAction(self.action_embed_fonts) e.addAction(self.action_subset_fonts) e.addAction(self.action_compress_images) e.addAction(self.action_smarten_punctuation) e.addAction(self.action_remove_unused_css) e.addAction(self.action_transform_styles) e.addAction(self.action_fix_html_all) e.addAction(self.action_pretty_all) e.addAction(self.action_rationalize_folders) e.addAction(self.action_add_cover) e.addAction(self.action_set_semantics) e.addAction(self.action_filter_css) e.addAction(self.action_spell_check_book) er = e.addMenu(_('External &links')) er.addAction(self.action_check_external_links) er.addAction(self.action_get_ext_resources) e.addAction(self.action_check_book) e.addAction(self.action_reports) e.addAction(self.action_upgrade_book_internals) e = b.addMenu(_('&View')) t = e.addMenu(_('Tool&bars')) e.addSeparator() for name in sorted(actions, key=lambda x: sort_key(actions[x].text())): ac = actions[name] if name.endswith('-dock'): e.addAction(ac) elif name.endswith('-bar'): t.addAction(ac) e.addAction(self.action_browse_images) e.addSeparator() e.addAction(self.action_close_current_tab) e.addAction(self.action_close_all_but_current_tab) e = b.addMenu(_('&Search')) a = e.addAction a(self.action_find) e.addSeparator() a(self.action_find_next) a(self.action_find_previous) e.addSeparator() a(self.action_replace) a(self.action_replace_next) a(self.action_replace_previous) a(self.action_replace_all) e.addSeparator() a(self.action_count) e.addSeparator() a(self.action_mark) e.addSeparator() a(self.action_go_to_line) e.addSeparator() a(self.action_saved_searches) e.aboutToShow.connect(self.search_menu_about_to_show) e.addSeparator() a(self.action_text_search) if self.plugin_menu_actions: e = b.addMenu(_('&Plugins')) for ac in sorted(self.plugin_menu_actions, key=lambda x: sort_key(unicode_type(x.text()))): e.addAction(ac) e = b.addMenu(_('&Help')) a = e.addAction a(self.action_help) a(QIcon(I('donate.png')), _('&Donate to support calibre development'), open_donate) a(self.action_preferences)
def categories(ctx, rd, library_id): ''' Return the list of top-level categories as a list of dictionaries. Each dictionary is of the form:: { 'name': Display Name, 'url':URL that gives the JSON object corresponding to all entries in this category, 'icon': URL to icon of this category, 'is_category': False for the All Books and Newest categories, True for everything else } ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: ans = {} categories = ctx.get_categories(rd, db) category_meta = db.field_metadata library_id = db.server_library_id def getter(x): return category_meta[x]['name'] displayed_custom_fields = custom_fields_to_display(db) for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue display_name = meta['name'] if category.startswith('@'): category = category.partition('.')[0] display_name = category[1:] url = force_unicode(category) icon = category_icon(category, meta) ans[url] = (display_name, icon) ans = [{ 'url': k, 'name': v[0], 'icon': v[1], 'is_category': True } for k, v in ans.iteritems()] ans.sort(key=lambda x: sort_key(x['name'])) for name, url, icon in [ (_('All books'), 'allbooks', 'book.png'), (_('Newest'), 'newest', 'forward.png'), ]: ans.insert(0, { 'name': name, 'url': url, 'icon': icon, 'is_category': False }) for c in ans: c['url'] = ctx.url_for(globals()['category'], encoded_name=encode_name(c['url']), library_id=library_id) c['icon'] = ctx.url_for(get_icon, which=c['icon']) return ans
def category(ctx, rd, encoded_name, library_id): ''' Return a dictionary describing the category specified by name. The Optional: ?num=100&offset=0&sort=name&sort_order=asc The dictionary looks like:: { 'category_name': Category display name, 'base_url': Base URL for this category, 'total_num': Total numberof items in this category, 'offset': The offset for the items returned in this result, 'num': The number of items returned in this result, 'sort': How the returned items are sorted, 'sort_order': asc or desc 'subcategories': List of sub categories of this category. 'items': List of items in this category, } Each subcategory is a dictionary of the same form as those returned by /ajax/categories Each item is a dictionary of the form:: { 'name': Display name, 'average_rating': Average rating for books in this item, 'count': Number of books in this item, 'url': URL to get list of books in this item, 'has_children': If True this item contains sub categories, look for an entry corresponding to this item in subcategories int he main dictionary, } :param sort: How to sort the returned items. Choices are: name, rating, popularity :param sort_order: asc or desc To learn how to create subcategories see https://manual.calibre-ebook.com/sub_groups.html ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: num, offset = get_pagination(rd.query) sort, sort_order = rd.query.get('sort'), rd.query.get('sort_order') sort = ensure_val(sort, 'name', 'rating', 'popularity') sort_order = ensure_val(sort_order, 'asc', 'desc') try: dname = decode_name(encoded_name) except: raise HTTPNotFound('Invalid encoding of category name %r' % encoded_name) base_url = ctx.url_for(globals()['category'], encoded_name=encoded_name, library_id=db.server_library_id) if dname in ('newest', 'allbooks'): sort, sort_order = 'timestamp', 'desc' rd.query['sort'], rd.query['sort_order'] = sort, sort_order return books_in(ctx, rd, encoded_name, encode_name('0'), library_id) fm = db.field_metadata categories = ctx.get_categories(rd, db) hierarchical_categories = db.pref('categories_using_hierarchy', ()) subcategory = dname toplevel = subcategory.partition('.')[0] if toplevel == subcategory: subcategory = None if toplevel not in categories or toplevel not in fm: raise HTTPNotFound('Category %r not found' % toplevel) # Find items and sub categories subcategories = [] meta = fm[toplevel] item_names = {} children = set() if meta['kind'] == 'user': fullname = ((toplevel + '.' + subcategory) if subcategory is not None else toplevel) try: # User categories cannot be applied to books, so this is the # complete set of items, no need to consider sub categories items = categories[fullname] except: raise HTTPNotFound('User category %r not found' % fullname) parts = fullname.split('.') for candidate in categories: cparts = candidate.split('.') if len(cparts) == len(parts) + 1 and cparts[:-1] == parts: subcategories.append({ 'name': cparts[-1], 'url': candidate, 'icon': category_icon(toplevel, meta) }) category_name = toplevel[1:].split('.') # When browsing by user categories we ignore hierarchical normal # columns, so children can be empty elif toplevel in hierarchical_categories: items = [] category_names = [ x.original_name.split('.') for x in categories[toplevel] if '.' in x.original_name ] if subcategory is None: children = set(x[0] for x in category_names) category_name = [meta['name']] items = [ x for x in categories[toplevel] if '.' not in x.original_name ] else: subcategory_parts = subcategory.split('.')[1:] category_name = [meta['name']] + subcategory_parts lsp = len(subcategory_parts) children = set( '.'.join(x) for x in category_names if len(x) == lsp + 1 and x[:lsp] == subcategory_parts) items = [ x for x in categories[toplevel] if x.original_name in children ] item_names = { x: x.original_name.rpartition('.')[-1] for x in items } # Only mark the subcategories that have children themselves as # subcategories children = set( '.'.join(x[:lsp + 1]) for x in category_names if len(x) > lsp + 1 and x[:lsp] == subcategory_parts) subcategories = [{ 'name': x.rpartition('.')[-1], 'url': toplevel + '.' + x, 'icon': category_icon(toplevel, meta) } for x in children] else: items = categories[toplevel] category_name = meta['name'] for x in subcategories: x['url'] = ctx.url_for(globals()['category'], encoded_name=encode_name(x['url']), library_id=db.server_library_id) x['icon'] = ctx.url_for(get_icon, which=x['icon']) x['is_category'] = True sort_keygen = { 'name': lambda x: sort_key(x.sort if x.sort else x.original_name), 'popularity': lambda x: x.count, 'rating': lambda x: x.avg_rating } items.sort(key=sort_keygen[sort], reverse=sort_order == 'desc') total_num = len(items) items = items[offset:offset + num] items = [{ 'name': item_names.get(x, x.original_name), 'average_rating': x.avg_rating, 'count': x.count, 'url': ctx.url_for( books_in, encoded_category=encode_name( x.category if x.category else toplevel), encoded_item=encode_name( x.original_name if x.id is None else unicode(x.id)), library_id=db.server_library_id), 'has_children': x.original_name in children, } for x in items] return { 'category_name': category_name, 'base_url': base_url, 'total_num': total_num, 'offset': offset, 'num': len(items), 'sort': sort, 'sort_order': sort_order, 'subcategories': subcategories, 'items': items, }
def entry_sort_key(entry): return sort_key(entry.get('name') or '')
def genesis(self, gui): self.gui = gui db = gui.library_view.model().db r = self.register r('gui_layout', config, restart_required=True, choices=[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')]) r('ui_style', gprefs, restart_required=True, choices=[(_('System default'), 'system'), (_('Calibre style'), 'calibre')]) r('book_list_tooltips', gprefs) r('tag_browser_old_look', gprefs, restart_required=True) r('bd_show_cover', gprefs) r('bd_overlay_cover_size', gprefs) r('cover_grid_width', gprefs) r('cover_grid_height', gprefs) r('cover_grid_cache_size_multiple', gprefs) r('cover_grid_disk_cache_size', gprefs) r('cover_grid_spacing', gprefs) r('cover_grid_show_title', gprefs) r('cover_flow_queue_length', config, restart_required=True) r('cover_browser_reflections', gprefs) r('show_rating_in_cover_browser', gprefs) r('emblem_size', gprefs) r('emblem_position', gprefs, choices=[(_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')]) r('book_list_extra_row_spacing', gprefs) def get_esc_lang(l): if l == 'en': return 'English' return get_language(l) lang = get_lang() if lang is None or lang not in available_translations(): lang = 'en' items = [(l, get_esc_lang(l)) for l in available_translations() if l != lang] if lang != 'en': items.append(('en', get_esc_lang('en'))) items.sort(cmp=lambda x, y: cmp(x[1].lower(), y[1].lower())) choices = [(y, x) for x, y in items] # Default language is the autodetected one choices = [(get_language(lang), lang)] + choices r('language', prefs, choices=choices, restart_required=True) r('show_avg_rating', config) r('disable_animations', config) r('systray_icon', config, restart_required=True) r('show_splash_screen', gprefs) r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) r('separate_cover_flow', config, restart_required=True) r('cb_fullscreen', gprefs) r('cb_preserve_aspect_ratio', gprefs) choices = [(_('Off'), 'off'), (_('Small'), 'small'), (_('Medium'), 'medium'), (_('Large'), 'large')] r('toolbar_icon_size', gprefs, choices=choices) choices = [(_('If there is enough room'), 'auto'), (_('Always'), 'always'), (_('Never'), 'never')] r('toolbar_text', gprefs, choices=choices) choices = [(_('Disabled'), 'disable'), (_('By first letter'), 'first letter'), (_('Partitioned'), 'partition')] r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_collapse_at', gprefs) r('default_author_link', gprefs) r('tag_browser_dont_collapse', gprefs, setting=CommaSeparatedList) self.search_library_for_author_button.clicked.connect( lambda: self.opt_default_author_link.setText('search-calibre')) choices = set([ k for k in db.field_metadata.all_field_keys() if (db.field_metadata[k]['is_category'] and (db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']) and not db.field_metadata[k]['display'].get('is_names', False)) or (db.field_metadata[k]['datatype'] in ['composite'] and db.field_metadata[k]['display'].get('make_category', False)) ]) choices -= set( ['authors', 'publisher', 'formats', 'news', 'identifiers']) choices |= set(['search']) self.opt_categories_using_hierarchy.update_items_cache(choices) r('categories_using_hierarchy', db.prefs, setting=CommaSeparatedList, choices=sorted(list(choices), key=sort_key)) fm = db.field_metadata choices = sorted( ((fm[k]['name'], k) for k in fm.displayable_field_keys() if fm[k]['name']), key=lambda x: sort_key(x[0])) r('field_under_covers_in_grid', db.prefs, choices=choices) self.current_font = self.initial_font = None self.change_font_button.clicked.connect(self.change_font) self.display_model = DisplayedFields(self.gui.current_db, self.field_display_order) self.display_model.dataChanged.connect(self.changed_signal) self.field_display_order.setModel(self.display_model) self.df_up_button.clicked.connect(self.move_df_up) self.df_down_button.clicked.connect(self.move_df_down) self.edit_rules = EditRules(self.tabWidget) self.edit_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.edit_rules, QIcon(I('format-fill-color.png')), _('Column coloring')) self.icon_rules = EditRules(self.tabWidget) self.icon_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.icon_rules, QIcon(I('icon_choose.png')), _('Column icons')) self.grid_rules = EditRules(self.emblems_tab) self.grid_rules.changed.connect(self.changed_signal) self.emblems_tab.setLayout(QVBoxLayout()) self.emblems_tab.layout().addWidget(self.grid_rules) self.tabWidget.setCurrentIndex(0) keys = [ QKeySequence('F11', QKeySequence.PortableText), QKeySequence('Ctrl+Shift+F', QKeySequence.PortableText) ] keys = [unicode(x.toString(QKeySequence.NativeText)) for x in keys] self.fs_help_msg.setText( unicode(self.fs_help_msg.text()) % (_(' or ').join(keys))) self.size_calculated.connect(self.update_cg_cache_size, type=Qt.QueuedConnection) self.tabWidget.currentChanged.connect(self.tab_changed) l = self.cg_background_box.layout() self.cg_bg_widget = w = Background(self) l.addWidget(w, 0, 0, 3, 1) self.cover_grid_color_button = b = QPushButton(_('Change &color'), self) b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) l.addWidget(b, 0, 1) b.clicked.connect(self.change_cover_grid_color) self.cover_grid_texture_button = b = QPushButton( _('Change &background image'), self) b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) l.addWidget(b, 1, 1) b.clicked.connect(self.change_cover_grid_texture) self.cover_grid_default_appearance_button = b = QPushButton( _('Restore &default appearance'), self) b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) l.addWidget(b, 2, 1) b.clicked.connect(self.restore_cover_grid_appearance) self.cover_grid_empty_cache.clicked.connect(self.empty_cache) self.cover_grid_open_cache.clicked.connect(self.open_cg_cache) self.cover_grid_smaller_cover.clicked.connect( partial(self.resize_cover, True)) self.cover_grid_larger_cover.clicked.connect( partial(self.resize_cover, False)) self.cover_grid_reset_size.clicked.connect(self.cg_reset_size) self.opt_cover_grid_disk_cache_size.setMinimum( self.gui.grid_view.thumbnail_cache.min_disk_cache) self.opt_cover_grid_disk_cache_size.setMaximum( self.gui.grid_view.thumbnail_cache.min_disk_cache * 100) self.opt_cover_grid_width.valueChanged.connect( self.update_aspect_ratio) self.opt_cover_grid_height.valueChanged.connect( self.update_aspect_ratio)
def initialize_publisher(self): all_publishers = self.db.all_publishers() all_publishers.sort(key=lambda x: sort_key(x[1])) self.publisher.set_separator(None) self.publisher.update_items_cache([x[1] for x in all_publishers])
numeric_collation = prefs['numeric_collation'] def sort_key_for_name_and_first_letter(x): v1 = icu_upper(x.sort or x.name) v2 = v1 or ' ' # The idea is that '9999999999' is larger than any digit so all digits # will sort in front. Non-digits will sort according to their ICU first letter c = v2[0] return (c if numeric_collation and c.isdigit() else '9999999999', collation_order(v2), sort_key(v1)) category_sort_keys = {True: {}, False: {}} category_sort_keys[True]['popularity'] = category_sort_keys[False]['popularity'] = \ lambda x:(-getattr(x, 'count', 0), sort_key(x.sort or x.name)) category_sort_keys[True]['rating'] = category_sort_keys[False]['rating'] = \ lambda x:(-getattr(x, 'avg_rating', 0.0), sort_key(x.sort or x.name)) category_sort_keys[True]['name'] = \ sort_key_for_name_and_first_letter category_sort_keys[False]['name'] = \ lambda x:sort_key(x.sort or x.name) def get_categories(dbcache, sort='name', book_ids=None, first_letter_sort=False): if sort not in CATEGORY_SORTS: raise ValueError('sort ' + sort + ' not a valid value')
def sort_by_name(self): bm = self.get_bookmarks() bm.sort(key=lambda x:sort_key(x['title'])) self.set_bookmarks(bm) self.edited.emit(bm)
def ajax_category(self, name, sort='title', num=100, offset=0, sort_order='asc'): ''' Return a dictionary describing the category specified by name. The dictionary looks like:: { 'category_name': Category display name, 'base_url': Base URL for this category, 'total_num': Total numberof items in this category, 'offset': The offset for the items returned in this result, 'num': The number of items returned in this result, 'sort': How the returned items are sorted, 'sort_order': asc or desc 'subcategories': List of sub categories of this category. 'items': List of items in this category, } Each subcategory is a dictionary of the same form as those returned by ajax_categories(). Each item is a dictionary of the form:: { 'name': Display name, 'average_rating': Average rating for books in this item, 'count': Number of books in this item, 'url': URL to get list of books in this item, 'has_children': If True this item contains sub categories, look for an entry corresponding to this item in subcategories int he main dictionary, } :param sort: How to sort the returned items. CHoices are: name, rating, popularity :param sort_order: asc or desc To learn how to create subcategories see http://manual.calibre-ebook.com/sub_groups.html ''' try: num = int(num) except: raise cherrypy.HTTPError(404, "Invalid num: %r" % num) try: offset = int(offset) except: raise cherrypy.HTTPError(404, "Invalid offset: %r" % offset) base_url = absurl(self.opts.url_prefix, '/ajax/category/' + name) if sort not in ('rating', 'name', 'popularity'): sort = 'name' if sort_order not in ('asc', 'desc'): sort_order = 'asc' try: dname = decode_name(name) except: raise cherrypy.HTTPError( 404, 'Invalid encoding of category name' ' %r' % name) if dname in ('newest', 'allbooks'): if dname == 'newest': sort, sort_order = 'timestamp', 'desc' raise cherrypy.InternalRedirect( '/ajax/books_in/%s/%s?sort=%s&sort_order=%s' % (encode_name(dname), encode_name('0'), sort, sort_order)) fm = self.db.field_metadata categories = self.categories_cache() hierarchical_categories = self.db.prefs['categories_using_hierarchy'] subcategory = dname toplevel = subcategory.partition('.')[0] if toplevel == subcategory: subcategory = None if toplevel not in categories or toplevel not in fm: raise cherrypy.HTTPError(404, 'Category %r not found' % toplevel) # Find items and sub categories subcategories = [] meta = fm[toplevel] item_names = {} children = set() if meta['kind'] == 'user': fullname = ((toplevel + '.' + subcategory) if subcategory is not None else toplevel) try: # User categories cannot be applied to books, so this is the # complete set of items, no need to consider sub categories items = categories[fullname] except: raise cherrypy.HTTPError( 404, 'User category %r not found' % fullname) parts = fullname.split('.') for candidate in categories: cparts = candidate.split('.') if len(cparts) == len(parts) + 1 and cparts[:-1] == parts: subcategories.append({ 'name': cparts[-1], 'url': candidate, 'icon': category_icon(toplevel, meta) }) category_name = toplevel[1:].split('.') # When browsing by user categories we ignore hierarchical normal # columns, so children can be empty elif toplevel in hierarchical_categories: items = [] category_names = [ x.original_name.split('.') for x in categories[toplevel] if '.' in x.original_name ] if subcategory is None: children = set(x[0] for x in category_names) category_name = [meta['name']] items = [ x for x in categories[toplevel] if '.' not in x.original_name ] else: subcategory_parts = subcategory.split('.')[1:] category_name = [meta['name']] + subcategory_parts lsp = len(subcategory_parts) children = set( '.'.join(x) for x in category_names if len(x) == lsp + 1 and x[:lsp] == subcategory_parts) items = [ x for x in categories[toplevel] if x.original_name in children ] item_names = { x: x.original_name.rpartition('.')[-1] for x in items } # Only mark the subcategories that have children themselves as # subcategories children = set( '.'.join(x[:lsp + 1]) for x in category_names if len(x) > lsp + 1 and x[:lsp] == subcategory_parts) subcategories = [{ 'name': x.rpartition('.')[-1], 'url': toplevel + '.' + x, 'icon': category_icon(toplevel, meta) } for x in children] else: items = categories[toplevel] category_name = meta['name'] for x in subcategories: x['url'] = category_url(self.opts.url_prefix, x['url']) x['icon'] = icon_url(self.opts.url_prefix, x['icon']) x['is_category'] = True sort_keygen = { 'name': lambda x: sort_key(x.sort if x.sort else x.original_name), 'popularity': lambda x: x.count, 'rating': lambda x: x.avg_rating } items.sort(key=sort_keygen[sort], reverse=sort_order == 'desc') total_num = len(items) items = items[offset:offset + num] items = [{ 'name': item_names.get(x, x.original_name), 'average_rating': x.avg_rating, 'count': x.count, 'url': books_in_url(self.opts.url_prefix, x.category if x.category else toplevel, x.original_name if x.id is None else unicode(x.id)), 'has_children': x.original_name in children, } for x in items] return { 'category_name': category_name, 'base_url': base_url, 'total_num': total_num, 'offset': offset, 'num': len(items), 'sort': sort, 'sort_order': sort_order, 'subcategories': subcategories, 'items': items, }
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() self.filenamebox_view = v = QListView() v.setIconSize(QSize(32, 32)) self.filename_box.setView(v) self.orig_filenamebox_view = f.view() 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 icon'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) b.setToolTip('<p>' + _( 'Remove a previously added icon. Note that doing so will cause rules that use it to stop working.' )) 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 b''): 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 ajax_categories(self): ''' Return the list of top-level categories as a list of dictionaries. Each dictionary is of the form:: { 'name': Display Name, 'url':URL that gives the JSON object corresponding to all entries in this category, 'icon': URL to icon of this category, 'is_category': False for the All Books and Newest categories, True for everything else } ''' ans = {} categories = self.categories_cache() category_meta = self.db.field_metadata def getter(x): return category_meta[x]['name'] displayed_custom_fields = custom_fields_to_display(self.db) for category in sorted(categories, key=lambda x: sort_key(getter(x))): if len(categories[category]) == 0: continue if category in ('formats', 'identifiers'): continue meta = category_meta.get(category, None) if meta is None: continue if category_meta.is_ignorable_field(category) and \ category not in displayed_custom_fields: continue display_name = meta['name'] if category.startswith('@'): category = category.partition('.')[0] display_name = category[1:] url = force_unicode(category) icon = category_icon(category, meta) ans[url] = (display_name, icon) ans = [{ 'url': k, 'name': v[0], 'icon': v[1], 'is_category': True } for k, v in ans.iteritems()] ans.sort(key=lambda x: sort_key(x['name'])) for name, url, icon in [ (_('All books'), 'allbooks', 'book.png'), (_('Newest'), 'newest', 'forward.png'), ]: ans.insert(0, { 'name': name, 'url': url, 'icon': icon, 'is_category': False }) for c in ans: c['url'] = category_url(self.opts.url_prefix, c['url']) c['icon'] = icon_url(self.opts.url_prefix, c['icon']) return ans
def __init__(self, window, db, on_category=None): QDialog.__init__(self, window) Ui_TagCategories.__init__(self) self.setupUi(self) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags() & (~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) self.db = db self.applied_items = [] cc_icon = QIcon(I('column.png')) self.category_labels = self.category_labels_orig[:] category_icons = [ None, QIcon(I('user_profile.png')), QIcon(I('series.png')), QIcon(I('publisher.png')), QIcon(I('tags.png')) ] category_values = [ None, lambda: [n.replace('|', ',') for (id, n) in self.db.all_authors()], lambda: [n for (id, n) in self.db.all_series()], lambda: [n for (id, n) in self.db.all_publishers()], lambda: self.db.all_tags() ] category_names = [ '', _('Authors'), ngettext('Series', 'Series', 2), _('Publishers'), _('Tags') ] cvals = {} for key, cc in self.db.custom_field_metadata().iteritems(): if cc['datatype'] in ['text', 'series', 'enumeration']: self.category_labels.append(key) category_icons.append(cc_icon) category_values.append( lambda col=cc['label']: self.db.all_custom(label=col)) category_names.append(cc['name']) elif cc['datatype'] == 'composite' and \ cc['display'].get('make_category', False): self.category_labels.append(key) category_icons.append(cc_icon) category_names.append(cc['name']) dex = cc['rec_index'] cvals = set() for book in db.data.iterall(): if book[dex]: cvals.add(book[dex]) category_values.append(lambda s=list(cvals): s) self.all_items = [] self.all_items_dict = {} for idx, label in enumerate(self.category_labels): if idx == 0: continue for n in category_values[idx](): t = Item(name=n, label=label, index=len(self.all_items), icon=category_icons[idx], exists=True) self.all_items.append(t) self.all_items_dict[icu_lower(label + ':' + n)] = t self.categories = dict.copy(db.prefs.get('user_categories', {})) if self.categories is None: self.categories = {} for cat in self.categories: for item, l in enumerate(self.categories[cat]): key = icu_lower(':'.join([l[1], l[0]])) t = self.all_items_dict.get(key, None) if l[1] in self.category_labels: if t is None: t = Item( name=l[0], label=l[1], index=len(self.all_items), icon=category_icons[self.category_labels.index( l[1])], exists=False) self.all_items.append(t) self.all_items_dict[key] = t l[2] = t.index else: # remove any references to a category that no longer exists del self.categories[cat][item] self.all_items_sorted = sorted(self.all_items, key=lambda x: sort_key(x.name)) self.display_filtered_categories(0) for v in category_names: self.category_filter_box.addItem(v) self.current_cat_name = None self.apply_button.clicked.connect(self.apply_button_clicked) self.unapply_button.clicked.connect(self.unapply_button_clicked) self.add_category_button.clicked.connect(self.add_category) self.rename_category_button.clicked.connect(self.rename_category) self.category_box.currentIndexChanged[int].connect( self.select_category) self.category_filter_box.currentIndexChanged[int].connect( self.display_filtered_categories) self.delete_category_button.clicked.connect(self.del_category) if islinux: self.available_items_box.itemDoubleClicked.connect(self.apply_tags) else: self.available_items_box.itemActivated.connect(self.apply_tags) self.applied_items_box.itemActivated.connect(self.unapply_tags) self.populate_category_list() if on_category is not None: l = self.category_box.findText(on_category) if l >= 0: self.category_box.setCurrentIndex(l) if self.current_cat_name is None: self.category_box.setCurrentIndex(0) self.select_category(0)
def __init__(self, parent, text, mi=None, fm=None, color_field=None, icon_field_key=None, icon_rule_kind=None, doing_emblem=False, text_is_placeholder=False, dialog_is_st_editor=False): QDialog.__init__(self, parent) Ui_TemplateDialog.__init__(self) self.setupUi(self) self.coloring = color_field is not None self.iconing = icon_field_key is not None self.embleming = doing_emblem self.dialog_is_st_editor = dialog_is_st_editor cols = [] if fm is not None: 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 not self.coloring: continue from calibre.gui2.preferences.coloring import all_columns_string name = all_columns_string if key == color_row_key else fm[key]['name'] if name: cols.append((name, key)) self.color_layout.setVisible(False) self.icon_layout.setVisible(False) if self.coloring: self.color_layout.setVisible(True) for n1, k1 in cols: self.colored_field.addItem(n1, k1) self.colored_field.setCurrentIndex(self.colored_field.findData(color_field)) elif self.iconing or self.embleming: self.icon_layout.setVisible(True) if self.embleming: self.icon_kind_label.setVisible(False) self.icon_kind.setVisible(False) self.icon_chooser_label.setVisible(False) self.icon_field.setVisible(False) for n1, k1 in cols: self.icon_field.addItem(n1, k1) self.icon_file_names = [] d = os.path.join(config_dir, 'cc_icons') if os.path.exists(d): for icon_file in os.listdir(d): icon_file = icu_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() if self.iconing: dex = 0 from calibre.gui2.preferences.coloring import icon_rule_kinds for i,tup in enumerate(icon_rule_kinds): txt,val = tup self.icon_kind.addItem(txt, userData=(val)) if val == icon_rule_kind: dex = i self.icon_kind.setCurrentIndex(dex) self.icon_field.setCurrentIndex(self.icon_field.findData(icon_field_key)) if dialog_is_st_editor: self.buttonBox.setVisible(False) else: self.new_doc_label.setVisible(False) self.new_doc.setVisible(False) self.template_name_label.setVisible(False) self.template_name.setVisible(False) if mi: self.mi = mi else: self.mi = Metadata(_('Title'), [_('Author')]) self.mi.author_sort = _('Author Sort') self.mi.series = ngettext('Series', 'Series', 1) self.mi.series_index = 3 self.mi.rating = 4.0 self.mi.tags = [_('Tag 1'), _('Tag 2')] self.mi.languages = ['eng'] self.mi.id = 1 if fm is not None: self.mi.set_all_user_metadata(fm.custom_field_metadata()) else: # No field metadata. Grab a copy from the current library so # that we can validate any custom column names. The values for # the columns will all be empty, which in some very unusual # cases might cause formatter errors. We can live with that. from calibre.gui2.ui import get_gui self.mi.set_all_user_metadata( get_gui().current_db.new_api.field_metadata.custom_field_metadata()) for col in self.mi.get_all_user_metadata(False): self.mi.set(col, (col,), 0) # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint)) self.setWindowIcon(icon) self.last_text = '' self.highlighter = TemplateHighlighter(self.textbox.document()) self.textbox.cursorPositionChanged.connect(self.text_cursor_changed) self.textbox.textChanged.connect(self.textbox_changed) self.textbox.setTabStopWidth(10) self.source_code.setTabStopWidth(10) self.documentation.setReadOnly(True) self.source_code.setReadOnly(True) if text is not None: if text_is_placeholder: self.textbox.setPlaceholderText(text) self.textbox.clear() else: self.textbox.setPlainText(text) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel')) self.color_copy_button.clicked.connect(self.color_to_clipboard) self.filename_button.clicked.connect(self.filename_button_clicked) self.icon_copy_button.clicked.connect(self.icon_to_clipboard) try: with open(P('template-functions.json'), 'rb') as f: self.builtin_source_dict = json.load(f, encoding='utf-8') except: self.builtin_source_dict = {} self.funcs = formatter_functions().get_functions() self.builtins = formatter_functions().get_builtins() func_names = sorted(self.funcs) self.function.clear() self.function.addItem('') self.function.addItems(func_names) self.function.setCurrentIndex(0) self.function.currentIndexChanged[native_string_type].connect(self.function_changed) self.textbox_changed() self.rule = (None, '') tt = _('Template language tutorial') self.template_tutorial.setText( '<a href="%s">%s</a>' % ( localize_user_manual_link('https://manual.calibre-ebook.com/template_lang.html'), tt)) tt = _('Template function reference') self.template_func_reference.setText( '<a href="%s">%s</a>' % ( localize_user_manual_link('https://manual.calibre-ebook.com/generated/en/template_ref.html'), tt)) self.font_size_box.setValue(gprefs['gpm_template_editor_font_size']) self.font_size_box.valueChanged.connect(self.font_size_changed)