mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates
This commit is contained in:
commit
66e4d7a56f
@ -32,15 +32,15 @@ class NewYorker(BasicNewsRecipe):
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'headers'})
|
||||
,dict(name='div', attrs={'id':['articleheads','items-container','articleRail','articletext','photocredits']})
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name=['meta','iframe','base','link','embed','object'])
|
||||
,dict(attrs={'class':['utils','articleRailLinks','icons'] })
|
||||
,dict(attrs={'id':['show-header','show-footer'] })
|
||||
,dict(attrs={'class':['utils','articleRailLinks','icons'] })
|
||||
,dict(attrs={'id':['show-header','show-footer'] })
|
||||
]
|
||||
remove_attributes = ['lang']
|
||||
feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')]
|
||||
@ -58,4 +58,4 @@ class NewYorker(BasicNewsRecipe):
|
||||
if cover_item:
|
||||
cover_url = 'http://www.newyorker.com' + cover_item['src'].strip()
|
||||
return cover_url
|
||||
|
||||
|
||||
|
@ -13,6 +13,8 @@ class RevistaMuyInteresante(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
|
||||
conversion_options = {'linearize_tables': True}
|
||||
|
||||
extra_css = ' .txt_articulo{ font-family: sans-serif; font-size: medium; text-align: justify } .contentheading{font-family: serif; font-size: large; font-weight: bold; color: #000000; text-align: center}'
|
||||
|
||||
|
||||
@ -39,11 +41,12 @@ class RevistaMuyInteresante(BasicNewsRecipe):
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['article']}),dict(name='td', attrs={'class':['txt_articulo']})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link','script','ul'])
|
||||
dict(name=['object','link','script','ul','iframe','ins'])
|
||||
,dict(name='div', attrs={'id':['comment']})
|
||||
,dict(name='td', attrs={'class':['buttonheading']})
|
||||
,dict(name='div', attrs={'class':['tags_articles']})
|
||||
,dict(name='div', attrs={'class':['tags_articles','bajo_title']})
|
||||
,dict(name='table', attrs={'class':['pagenav']})
|
||||
,dict(name='form', attrs={'class':['voteform']})
|
||||
]
|
||||
|
||||
remove_tags_after = dict(name='div', attrs={'class':'tags_articles'})
|
||||
@ -115,3 +118,5 @@ class RevistaMuyInteresante(BasicNewsRecipe):
|
||||
if link_item:
|
||||
cover_url = "http://www.muyinteresante.es"+link_item['src']
|
||||
return cover_url
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ class RusiaHoy(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
language = 'es'
|
||||
remove_empty_feeds = True
|
||||
extra_css = """
|
||||
extra_css = """
|
||||
body{font-family: Arial,sans-serif }
|
||||
.article_article_title{font-size: xx-large; font-weight: bold}
|
||||
.article_date{color: black; font-size: small}
|
||||
@ -44,4 +44,4 @@ class RusiaHoy(BasicNewsRecipe):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
||||
|
||||
|
@ -370,6 +370,15 @@ class InterfaceActionBase(Plugin): # {{{
|
||||
can_be_disabled = False
|
||||
|
||||
actual_plugin = None
|
||||
|
||||
def load_actual_plugin(self, gui):
|
||||
'''
|
||||
This method must return the actual interface action plugin object.
|
||||
'''
|
||||
mod, cls = self.actual_plugin.split(':')
|
||||
return getattr(__import__(mod, fromlist=['1'], level=0), cls)(gui,
|
||||
self.site_customization)
|
||||
|
||||
# }}}
|
||||
|
||||
class PreferencesPlugin(Plugin): # {{{
|
||||
|
@ -292,7 +292,8 @@ class RTFInput(InputFormatPlugin):
|
||||
# Replace newlines inserted by the 'empty_paragraphs' option in rtf2xml with html blank lines
|
||||
if not getattr(self.options, 'remove_paragraph_spacing', False):
|
||||
res = re.sub('\s*<body>', '<body>', res)
|
||||
res = re.sub('(?<=\n)\n{2}', u'<p>\u00a0</p>\n', res)
|
||||
res = re.sub('(?<=\n)\n{2}',
|
||||
u'<p>\u00a0</p>\n'.encode('utf-8'), res)
|
||||
if self.options.preprocess_html:
|
||||
preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None))
|
||||
res = preprocessor(res)
|
||||
|
@ -214,9 +214,18 @@ class BookInfo(QWebView):
|
||||
|
||||
|
||||
def _show_data(self, rows, comments):
|
||||
|
||||
def color_to_string(col):
|
||||
ans = '#000000'
|
||||
if col.isValid():
|
||||
col = col.toRgb()
|
||||
if col.isValid():
|
||||
ans = unicode(col.name())
|
||||
return ans
|
||||
|
||||
f = QFontInfo(QApplication.font(self.parent())).pixelSize()
|
||||
c = unicode(QApplication.palette().color(QPalette.Normal,
|
||||
QPalette.WindowText).name())
|
||||
c = color_to_string(QApplication.palette().color(QPalette.Normal,
|
||||
QPalette.WindowText))
|
||||
templ = u'''\
|
||||
<html>
|
||||
<head>
|
||||
|
@ -437,7 +437,7 @@ class BulkBool(BulkBase, Bool):
|
||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||
val = False
|
||||
if value is not None and value != val:
|
||||
return None
|
||||
return 'nochange'
|
||||
value = val
|
||||
return value
|
||||
|
||||
@ -445,19 +445,23 @@ class BulkBool(BulkBase, Bool):
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
||||
QComboBox(parent)]
|
||||
w = self.widgets[1]
|
||||
items = [_('Yes'), _('No'), _('Undefined')]
|
||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||
items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')]
|
||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')]
|
||||
for icon, text in zip(icons, items):
|
||||
w.addItem(QIcon(icon), text)
|
||||
|
||||
def getter(self):
|
||||
val = self.widgets[1].currentIndex()
|
||||
return {3: 'nochange', 2: None, 1: False, 0: True}[val]
|
||||
|
||||
def setter(self, val):
|
||||
val = {None: 2, False: 1, True: 0}[val]
|
||||
val = {'nochange': 3, None: 2, False: 1, True: 0}[val]
|
||||
self.widgets[1].setCurrentIndex(val)
|
||||
|
||||
def commit(self, book_ids, notify=False):
|
||||
val = self.gui_val
|
||||
val = self.normalize_ui_val(val)
|
||||
if val != self.initial_val:
|
||||
if val != self.initial_val and val != 'nochange':
|
||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||
val = False
|
||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||
|
@ -102,9 +102,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.device_connected = None
|
||||
acmap = OrderedDict()
|
||||
for action in interface_actions():
|
||||
mod, cls = action.actual_plugin.split(':')
|
||||
ac = getattr(__import__(mod, fromlist=['1'], level=0), cls)(self,
|
||||
action.site_customization)
|
||||
ac = action.load_actual_plugin(self)
|
||||
if ac.name in acmap:
|
||||
if ac.priority >= acmap[ac.name].priority:
|
||||
acmap[ac.name] = ac
|
||||
|
@ -403,7 +403,7 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
'<=':[2, lambda r, q: r <= q]
|
||||
}
|
||||
|
||||
def get_numeric_matches(self, location, query):
|
||||
def get_numeric_matches(self, location, query, val_func = None):
|
||||
matches = set([])
|
||||
if len(query) == 0:
|
||||
return matches
|
||||
@ -419,7 +419,10 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
if relop is None:
|
||||
(p, relop) = self.numeric_search_relops['=']
|
||||
|
||||
loc = self.field_metadata[location]['rec_index']
|
||||
if val_func is None:
|
||||
loc = self.field_metadata[location]['rec_index']
|
||||
val_func = lambda item, loc=loc: item[loc]
|
||||
|
||||
dt = self.field_metadata[location]['datatype']
|
||||
if dt == 'int':
|
||||
cast = (lambda x: int (x))
|
||||
@ -430,6 +433,9 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
elif dt == 'float':
|
||||
cast = lambda x : float (x)
|
||||
adjust = lambda x: x
|
||||
else: # count operation
|
||||
cast = (lambda x: int (x))
|
||||
adjust = lambda x: x
|
||||
|
||||
if len(query) > 1:
|
||||
mult = query[-1:].lower()
|
||||
@ -446,10 +452,11 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
for item in self._data:
|
||||
if item is None:
|
||||
continue
|
||||
if not item[loc]:
|
||||
v = val_func(item)
|
||||
if not v:
|
||||
i = 0
|
||||
else:
|
||||
i = adjust(item[loc])
|
||||
i = adjust(v)
|
||||
if relop(i, q):
|
||||
matches.add(item[0])
|
||||
return matches
|
||||
@ -467,15 +474,23 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
return matches
|
||||
raise ParseException(query, len(query), 'Recursive query group detected', self)
|
||||
|
||||
# take care of dates special case
|
||||
if location in self.field_metadata and \
|
||||
self.field_metadata[location]['datatype'] == 'datetime':
|
||||
return self.get_dates_matches(location, query.lower())
|
||||
if location in self.field_metadata:
|
||||
fm = self.field_metadata[location]
|
||||
# take care of dates special case
|
||||
if fm['datatype'] == 'datetime':
|
||||
return self.get_dates_matches(location, query.lower())
|
||||
|
||||
# take care of numbers special case
|
||||
if location in self.field_metadata and \
|
||||
self.field_metadata[location]['datatype'] in ('rating', 'int', 'float'):
|
||||
return self.get_numeric_matches(location, query.lower())
|
||||
# take care of numbers special case
|
||||
if fm['datatype'] in ('rating', 'int', 'float'):
|
||||
return self.get_numeric_matches(location, query.lower())
|
||||
|
||||
# take care of the 'count' operator for is_multiples
|
||||
if fm['is_multiple'] and \
|
||||
len(query) > 1 and query.startswith('#') and \
|
||||
query[1:1] in '=<>!':
|
||||
vf = lambda item, loc=fm['rec_index'], ms=fm['is_multiple']:\
|
||||
len(item[loc].split(ms)) if item[loc] is not None else 0
|
||||
return self.get_numeric_matches(location, query[1:], val_func=vf)
|
||||
|
||||
# everything else, or 'all' matches
|
||||
matchkind = CONTAINS_MATCH
|
||||
|
@ -268,8 +268,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
base,
|
||||
prefer_custom=True)
|
||||
|
||||
self.field_metadata.set_field_record_index('cover',
|
||||
self.FIELD_MAP['cover'], prefer_custom=False)
|
||||
self.FIELD_MAP['ondevice'] = base+1
|
||||
self.field_metadata.set_field_record_index('ondevice', base+1, prefer_custom=False)
|
||||
self.FIELD_MAP['all_metadata'] = base+2
|
||||
|
@ -3,6 +3,7 @@ Created on 25 May 2010
|
||||
|
||||
@author: charles
|
||||
'''
|
||||
import copy
|
||||
|
||||
from calibre.utils.ordered_dict import OrderedDict
|
||||
from calibre.utils.config import tweaks
|
||||
@ -86,7 +87,7 @@ class FieldMetadata(dict):
|
||||
|
||||
# Builtin metadata {{{
|
||||
|
||||
_field_metadata = [
|
||||
_field_metadata_prototype = [
|
||||
('authors', {'table':'authors',
|
||||
'column':'name',
|
||||
'link_column':'author',
|
||||
@ -161,6 +162,15 @@ class FieldMetadata(dict):
|
||||
'search_terms':['tags', 'tag'],
|
||||
'is_custom':False,
|
||||
'is_category':True}),
|
||||
('all_metadata',{'table':None,
|
||||
'column':None,
|
||||
'datatype':None,
|
||||
'is_multiple':None,
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':[],
|
||||
'is_custom':False,
|
||||
'is_category':False}),
|
||||
('author_sort',{'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
@ -180,7 +190,7 @@ class FieldMetadata(dict):
|
||||
'is_custom':False, 'is_category':False}),
|
||||
('cover', {'table':None,
|
||||
'column':None,
|
||||
'datatype':None,
|
||||
'datatype':'int',
|
||||
'is_multiple':None,
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
@ -223,15 +233,6 @@ class FieldMetadata(dict):
|
||||
'search_terms':[],
|
||||
'is_custom':False,
|
||||
'is_category':False}),
|
||||
('all_metadata',{'table':None,
|
||||
'column':None,
|
||||
'datatype':None,
|
||||
'is_multiple':None,
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'search_terms':[],
|
||||
'is_custom':False,
|
||||
'is_category':False}),
|
||||
('ondevice', {'table':None,
|
||||
'column':None,
|
||||
'datatype':'text',
|
||||
@ -322,6 +323,7 @@ class FieldMetadata(dict):
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._field_metadata = copy.deepcopy(self._field_metadata_prototype)
|
||||
self._tb_cats = OrderedDict()
|
||||
self._search_term_map = {}
|
||||
self.custom_label_to_key_map = {}
|
||||
|
@ -98,6 +98,44 @@ Every time you use calibre to convert a book, the plugin's :meth:`run` method wi
|
||||
converted book will have its publisher set to "Hello World". For more information about
|
||||
|app|'s plugin system, read on...
|
||||
|
||||
|
||||
A Hello World GUI plugin
|
||||
---------------------------
|
||||
|
||||
Here's a simple Hello World plugin for the |app| GUI. It will cause a box to popup with the message "Hellooo World!" when you press Ctrl+Shift+H
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from calibre.customize import InterfaceActionBase
|
||||
|
||||
class HelloWorldBase(InterfaceActionBase):
|
||||
|
||||
name = 'Hello World GUI'
|
||||
author = 'The little green man'
|
||||
|
||||
def load_actual_plugin(self, gui):
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
|
||||
class HelloWorld(InterfaceAction):
|
||||
name = 'Hello World GUI'
|
||||
action_spec = ('Hello World!', 'add_book.png', None,
|
||||
_('Ctrl+Shift+H'))
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.hello_world)
|
||||
|
||||
def hello_world(self, *args):
|
||||
from calibre.gui2 import info_dialog
|
||||
info_dialog(self.gui, 'Hello World!', 'Hellooo World!',
|
||||
show=True)
|
||||
|
||||
return HelloWorld(gui, self.site_customization)
|
||||
|
||||
You can also have it show up in the toolbars/context menu by going to Preferences->Toolbars and adding this plugin to the locations you want it to be in.
|
||||
|
||||
While this plugin is utterly useless, note that all calibre GUI actions like adding/saving/removing/viewing/etc. are implemented as plugins, so there is no limit to what you can achieve. The key thing to remember is that the plugin has access to the full |app| GUI via ``self.gui``.
|
||||
|
||||
|
||||
The Plugin base class
|
||||
------------------------
|
||||
|
||||
|
@ -274,6 +274,14 @@ Searching for ``no`` or ``unchecked`` will find all books with ``No`` in the col
|
||||
|
||||
:guilabel:`Advanced Search Dialog`
|
||||
|
||||
You can test for the number of items in multiple-value columns, such as tags, formats, authors, and tags-like custom columns. This is done using a syntax very similar to numeric tests (discussed above), except that the relational operator begins with a ``#`` character. For example::
|
||||
|
||||
tags:#>3 will give you books with more than three tags
|
||||
tags:#!=3 will give you books that do not have three tags
|
||||
authors:#=1 will give you books with exactly one author
|
||||
#cust:#<5 will give you books with less than five items in custom column #cust
|
||||
formats:#>1 will give you books with more than one format
|
||||
|
||||
Saving searches
|
||||
-----------------
|
||||
|
||||
|
@ -161,11 +161,20 @@ The base class for such devices is :class:`calibre.devices.usbms.driver.USBMS`.
|
||||
User Interface Actions
|
||||
--------------------------
|
||||
|
||||
If you are adding your own plugin in a zip file, you should subclass both InterfaceActionBase and InterfaceAction. The :meth:`load_actual_plugin` method of you InterfaceActionBase subclass must return an instantiated object of your InterfaceBase subclass.
|
||||
|
||||
|
||||
.. autoclass:: calibre.gui2.actions.InterfaceAction
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
.. autoclass:: calibre.customize.InterfaceActionBase
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
|
||||
Preferences Plugins
|
||||
--------------------------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user