mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
2a1029d692
@ -4,6 +4,14 @@
|
|||||||
# for important features/bug fixes.
|
# for important features/bug fixes.
|
||||||
# Also, each release can have new and improved recipes.
|
# Also, each release can have new and improved recipes.
|
||||||
|
|
||||||
|
- version: 0.7.31
|
||||||
|
date: 2010-11-27
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Fix various regressions in the calibre windows build caused by the switch to python 2.7. If you are on windows and upgraded to 0.7.30, it is highly recommended that you upgrade to 0.7.31. If you are not on windows, you can ignore 0.7.31"
|
||||||
|
tickets: [7685, 7694, 7691]
|
||||||
|
|
||||||
|
|
||||||
- version: 0.7.30
|
- version: 0.7.30
|
||||||
date: 2010-11-26
|
date: 2010-11-26
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ www.getwokingham.co.uk
|
|||||||
|
|
||||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
class TheWorkinghamTimes(BasicNewsRecipe):
|
class TheWokinghamTimes(BasicNewsRecipe):
|
||||||
title = 'The Workingham Times'
|
title = 'The Wokingham Times'
|
||||||
__author__ = 'Darko Miletic'
|
__author__ = 'Darko Miletic'
|
||||||
description = 'News from UK'
|
description = 'News from UK'
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
|
@ -108,7 +108,6 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
for f in x[-1]:
|
for f in x[-1]:
|
||||||
if f.lower().endswith('.dll'):
|
if f.lower().endswith('.dll'):
|
||||||
f = self.j(x[0], f)
|
f = self.j(x[0], f)
|
||||||
if 'py2exe' not in f:
|
|
||||||
shutil.copy2(f, self.dll_dir)
|
shutil.copy2(f, self.dll_dir)
|
||||||
shutil.copy2(
|
shutil.copy2(
|
||||||
r'C:\Python%(v)s\Lib\site-packages\pywin32_system32\pywintypes%(v)s.dll'
|
r'C:\Python%(v)s\Lib\site-packages\pywin32_system32\pywintypes%(v)s.dll'
|
||||||
@ -118,7 +117,7 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
ans = []
|
ans = []
|
||||||
for x in items:
|
for x in items:
|
||||||
ext = os.path.splitext(x)[1]
|
ext = os.path.splitext(x)[1]
|
||||||
if (not ext and (x in ('demos', 'tests') or 'py2exe' in x)) or \
|
if (not ext and (x in ('demos', 'tests'))) or \
|
||||||
(ext in ('.dll', '.chm', '.htm', '.txt')):
|
(ext in ('.dll', '.chm', '.htm', '.txt')):
|
||||||
ans.append(x)
|
ans.append(x)
|
||||||
return ans
|
return ans
|
||||||
|
@ -21,6 +21,8 @@ This is where all dependencies will be installed.
|
|||||||
|
|
||||||
Add C:\Python27\Scripts and C:\Python27 to PATH
|
Add C:\Python27\Scripts and C:\Python27 to PATH
|
||||||
|
|
||||||
|
Edit mimetypes.py in C:\Python27\Lib and change line 250 UnicodeEncodeError to ValueError and set _winreg = None to prevent reading of mimetypes from the windows registry
|
||||||
|
|
||||||
Install setuptools from http://pypi.python.org/pypi/setuptools
|
Install setuptools from http://pypi.python.org/pypi/setuptools
|
||||||
If there are no windows binaries already compiled for the version of python you are using then download the source and run the following command in the folder where the source has been unpacked::
|
If there are no windows binaries already compiled for the version of python you are using then download the source and run the following command in the folder where the source has been unpacked::
|
||||||
|
|
||||||
@ -32,6 +34,8 @@ Run the following command to install python dependencies::
|
|||||||
|
|
||||||
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
|
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
|
||||||
|
|
||||||
|
Install pywin32 and edit win32com\__init__.py setting _frozen = True and
|
||||||
|
__gen_path__ to a temp dir (otherwise it tries to set it to a dir in the install tree which leads to permission errors)
|
||||||
|
|
||||||
SQLite
|
SQLite
|
||||||
---------
|
---------
|
||||||
@ -66,7 +70,11 @@ Compiling instructions::
|
|||||||
Python Imaging Library
|
Python Imaging Library
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Install as normal using provided installer.
|
Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||||
|
|
||||||
|
Test it on the target system with
|
||||||
|
|
||||||
|
calibre-debug -c "import _imaging, _imagingmath, _imagingft, _imagingcms"
|
||||||
|
|
||||||
Libunrar
|
Libunrar
|
||||||
----------
|
----------
|
||||||
|
@ -48,6 +48,13 @@ mimetypes.add_type('application/x-cbz', '.cbz')
|
|||||||
mimetypes.add_type('application/x-cbr', '.cbr')
|
mimetypes.add_type('application/x-cbr', '.cbr')
|
||||||
mimetypes.add_type('application/x-koboreader-ebook', '.kobo')
|
mimetypes.add_type('application/x-koboreader-ebook', '.kobo')
|
||||||
mimetypes.add_type('image/wmf', '.wmf')
|
mimetypes.add_type('image/wmf', '.wmf')
|
||||||
|
mimetypes.add_type('image/jpeg', '.jpg')
|
||||||
|
mimetypes.add_type('image/jpeg', '.jpeg')
|
||||||
|
mimetypes.add_type('image/png', '.png')
|
||||||
|
mimetypes.add_type('image/gif', '.gif')
|
||||||
|
mimetypes.add_type('image/bmp', '.bmp')
|
||||||
|
mimetypes.add_type('image/svg+xml', '.svg')
|
||||||
|
|
||||||
guess_type = mimetypes.guess_type
|
guess_type = mimetypes.guess_type
|
||||||
import cssutils
|
import cssutils
|
||||||
cssutils.log.setLevel(logging.WARN)
|
cssutils.log.setLevel(logging.WARN)
|
||||||
@ -362,6 +369,8 @@ def walk(dir):
|
|||||||
def strftime(fmt, t=None):
|
def strftime(fmt, t=None):
|
||||||
''' A version of strftime that returns unicode strings and tries to handle dates
|
''' A version of strftime that returns unicode strings and tries to handle dates
|
||||||
before 1900 '''
|
before 1900 '''
|
||||||
|
if not fmt:
|
||||||
|
return u''
|
||||||
if t is None:
|
if t is None:
|
||||||
t = time.localtime()
|
t = time.localtime()
|
||||||
if hasattr(t, 'timetuple'):
|
if hasattr(t, 'timetuple'):
|
||||||
@ -378,6 +387,7 @@ def strftime(fmt, t=None):
|
|||||||
if isinstance(fmt, unicode):
|
if isinstance(fmt, unicode):
|
||||||
fmt = fmt.encode('mbcs')
|
fmt = fmt.encode('mbcs')
|
||||||
ans = plugins['winutil'][0].strftime(fmt, t)
|
ans = plugins['winutil'][0].strftime(fmt, t)
|
||||||
|
else:
|
||||||
ans = time.strftime(fmt, t).decode(preferred_encoding, 'replace')
|
ans = time.strftime(fmt, t).decode(preferred_encoding, 'replace')
|
||||||
if early_year:
|
if early_year:
|
||||||
ans = ans.replace('_early year hack##', str(orig_year))
|
ans = ans.replace('_early year hack##', str(orig_year))
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.7.30'
|
__version__ = '0.7.31'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -370,6 +370,15 @@ class InterfaceActionBase(Plugin): # {{{
|
|||||||
can_be_disabled = False
|
can_be_disabled = False
|
||||||
|
|
||||||
actual_plugin = None
|
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): # {{{
|
class PreferencesPlugin(Plugin): # {{{
|
||||||
|
@ -190,6 +190,10 @@ class BookInfo(QWebView):
|
|||||||
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
|
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
|
||||||
self.linkClicked.connect(self.link_activated)
|
self.linkClicked.connect(self.link_activated)
|
||||||
self._link_clicked = False
|
self._link_clicked = False
|
||||||
|
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
||||||
|
palette = self.palette()
|
||||||
|
palette.setBrush(QPalette.Base, Qt.transparent)
|
||||||
|
self.page().setPalette(palette)
|
||||||
|
|
||||||
def link_activated(self, link):
|
def link_activated(self, link):
|
||||||
self._link_clicked = True
|
self._link_clicked = True
|
||||||
@ -210,16 +214,23 @@ class BookInfo(QWebView):
|
|||||||
|
|
||||||
|
|
||||||
def _show_data(self, rows, comments):
|
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()
|
f = QFontInfo(QApplication.font(self.parent())).pixelSize()
|
||||||
p = unicode(QApplication.palette().color(QPalette.Normal,
|
c = color_to_string(QApplication.palette().color(QPalette.Normal,
|
||||||
QPalette.Window).name())
|
QPalette.WindowText))
|
||||||
c = unicode(QApplication.palette().color(QPalette.Normal,
|
|
||||||
QPalette.WindowText).name())
|
|
||||||
templ = u'''\
|
templ = u'''\
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body, td {background-color: %s; font-size: %dpx; color: %s }
|
body, td {background-color: transparent; font-size: %dpx; color: %s }
|
||||||
a { text-decoration: none; color: blue }
|
a { text-decoration: none; color: blue }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -227,7 +238,7 @@ class BookInfo(QWebView):
|
|||||||
%%s
|
%%s
|
||||||
</body>
|
</body>
|
||||||
<html>
|
<html>
|
||||||
'''%(p, f, c)
|
'''%(f, c)
|
||||||
if self.vertical:
|
if self.vertical:
|
||||||
if comments:
|
if comments:
|
||||||
rows += u'<tr><td colspan="2">%s</td></tr>'%comments
|
rows += u'<tr><td colspan="2">%s</td></tr>'%comments
|
||||||
|
@ -437,7 +437,7 @@ class BulkBool(BulkBase, Bool):
|
|||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||||
val = False
|
val = False
|
||||||
if value is not None and value != val:
|
if value is not None and value != val:
|
||||||
return None
|
return 'nochange'
|
||||||
value = val
|
value = val
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@ -445,19 +445,23 @@ class BulkBool(BulkBase, Bool):
|
|||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
||||||
QComboBox(parent)]
|
QComboBox(parent)]
|
||||||
w = self.widgets[1]
|
w = self.widgets[1]
|
||||||
items = [_('Yes'), _('No'), _('Undefined')]
|
items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')]
|
||||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')]
|
||||||
for icon, text in zip(icons, items):
|
for icon, text in zip(icons, items):
|
||||||
w.addItem(QIcon(icon), text)
|
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):
|
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)
|
self.widgets[1].setCurrentIndex(val)
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
val = self.gui_val
|
val = self.gui_val
|
||||||
val = self.normalize_ui_val(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:
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||||
val = False
|
val = False
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
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
|
self.device_connected = None
|
||||||
acmap = OrderedDict()
|
acmap = OrderedDict()
|
||||||
for action in interface_actions():
|
for action in interface_actions():
|
||||||
mod, cls = action.actual_plugin.split(':')
|
ac = action.load_actual_plugin(self)
|
||||||
ac = getattr(__import__(mod, fromlist=['1'], level=0), cls)(self,
|
|
||||||
action.site_customization)
|
|
||||||
if ac.name in acmap:
|
if ac.name in acmap:
|
||||||
if ac.priority >= acmap[ac.name].priority:
|
if ac.priority >= acmap[ac.name].priority:
|
||||||
acmap[ac.name] = ac
|
acmap[ac.name] = ac
|
||||||
|
@ -403,7 +403,7 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
'<=':[2, lambda r, q: r <= q]
|
'<=':[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([])
|
matches = set([])
|
||||||
if len(query) == 0:
|
if len(query) == 0:
|
||||||
return matches
|
return matches
|
||||||
@ -419,7 +419,10 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
if relop is None:
|
if relop is None:
|
||||||
(p, relop) = self.numeric_search_relops['=']
|
(p, relop) = self.numeric_search_relops['=']
|
||||||
|
|
||||||
|
if val_func is None:
|
||||||
loc = self.field_metadata[location]['rec_index']
|
loc = self.field_metadata[location]['rec_index']
|
||||||
|
val_func = lambda item, loc=loc: item[loc]
|
||||||
|
|
||||||
dt = self.field_metadata[location]['datatype']
|
dt = self.field_metadata[location]['datatype']
|
||||||
if dt == 'int':
|
if dt == 'int':
|
||||||
cast = (lambda x: int (x))
|
cast = (lambda x: int (x))
|
||||||
@ -430,6 +433,9 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
elif dt == 'float':
|
elif dt == 'float':
|
||||||
cast = lambda x : float (x)
|
cast = lambda x : float (x)
|
||||||
adjust = lambda x: x
|
adjust = lambda x: x
|
||||||
|
else: # count operation
|
||||||
|
cast = (lambda x: int (x))
|
||||||
|
adjust = lambda x: x
|
||||||
|
|
||||||
if len(query) > 1:
|
if len(query) > 1:
|
||||||
mult = query[-1:].lower()
|
mult = query[-1:].lower()
|
||||||
@ -446,10 +452,11 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
for item in self._data:
|
for item in self._data:
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
if not item[loc]:
|
v = val_func(item)
|
||||||
|
if not v:
|
||||||
i = 0
|
i = 0
|
||||||
else:
|
else:
|
||||||
i = adjust(item[loc])
|
i = adjust(v)
|
||||||
if relop(i, q):
|
if relop(i, q):
|
||||||
matches.add(item[0])
|
matches.add(item[0])
|
||||||
return matches
|
return matches
|
||||||
@ -467,16 +474,24 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
return matches
|
return matches
|
||||||
raise ParseException(query, len(query), 'Recursive query group detected', self)
|
raise ParseException(query, len(query), 'Recursive query group detected', self)
|
||||||
|
|
||||||
|
if location in self.field_metadata:
|
||||||
|
fm = self.field_metadata[location]
|
||||||
# take care of dates special case
|
# take care of dates special case
|
||||||
if location in self.field_metadata and \
|
if fm['datatype'] == 'datetime':
|
||||||
self.field_metadata[location]['datatype'] == 'datetime':
|
|
||||||
return self.get_dates_matches(location, query.lower())
|
return self.get_dates_matches(location, query.lower())
|
||||||
|
|
||||||
# take care of numbers special case
|
# take care of numbers special case
|
||||||
if location in self.field_metadata and \
|
if fm['datatype'] in ('rating', 'int', 'float'):
|
||||||
self.field_metadata[location]['datatype'] in ('rating', 'int', 'float'):
|
|
||||||
return self.get_numeric_matches(location, query.lower())
|
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
|
# everything else, or 'all' matches
|
||||||
matchkind = CONTAINS_MATCH
|
matchkind = CONTAINS_MATCH
|
||||||
if (len(query) > 1):
|
if (len(query) > 1):
|
||||||
|
@ -268,8 +268,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
base,
|
base,
|
||||||
prefer_custom=True)
|
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_MAP['ondevice'] = base+1
|
||||||
self.field_metadata.set_field_record_index('ondevice', base+1, prefer_custom=False)
|
self.field_metadata.set_field_record_index('ondevice', base+1, prefer_custom=False)
|
||||||
self.FIELD_MAP['all_metadata'] = base+2
|
self.FIELD_MAP['all_metadata'] = base+2
|
||||||
|
@ -3,6 +3,7 @@ Created on 25 May 2010
|
|||||||
|
|
||||||
@author: charles
|
@author: charles
|
||||||
'''
|
'''
|
||||||
|
import copy
|
||||||
|
|
||||||
from calibre.utils.ordered_dict import OrderedDict
|
from calibre.utils.ordered_dict import OrderedDict
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
@ -86,7 +87,7 @@ class FieldMetadata(dict):
|
|||||||
|
|
||||||
# Builtin metadata {{{
|
# Builtin metadata {{{
|
||||||
|
|
||||||
_field_metadata = [
|
_field_metadata_prototype = [
|
||||||
('authors', {'table':'authors',
|
('authors', {'table':'authors',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
'link_column':'author',
|
'link_column':'author',
|
||||||
@ -161,6 +162,15 @@ class FieldMetadata(dict):
|
|||||||
'search_terms':['tags', 'tag'],
|
'search_terms':['tags', 'tag'],
|
||||||
'is_custom':False,
|
'is_custom':False,
|
||||||
'is_category':True}),
|
'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,
|
('author_sort',{'table':None,
|
||||||
'column':None,
|
'column':None,
|
||||||
'datatype':'text',
|
'datatype':'text',
|
||||||
@ -180,7 +190,7 @@ class FieldMetadata(dict):
|
|||||||
'is_custom':False, 'is_category':False}),
|
'is_custom':False, 'is_category':False}),
|
||||||
('cover', {'table':None,
|
('cover', {'table':None,
|
||||||
'column':None,
|
'column':None,
|
||||||
'datatype':None,
|
'datatype':'int',
|
||||||
'is_multiple':None,
|
'is_multiple':None,
|
||||||
'kind':'field',
|
'kind':'field',
|
||||||
'name':None,
|
'name':None,
|
||||||
@ -223,15 +233,6 @@ class FieldMetadata(dict):
|
|||||||
'search_terms':[],
|
'search_terms':[],
|
||||||
'is_custom':False,
|
'is_custom':False,
|
||||||
'is_category':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,
|
('ondevice', {'table':None,
|
||||||
'column':None,
|
'column':None,
|
||||||
'datatype':'text',
|
'datatype':'text',
|
||||||
@ -322,6 +323,7 @@ class FieldMetadata(dict):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self._field_metadata = copy.deepcopy(self._field_metadata_prototype)
|
||||||
self._tb_cats = OrderedDict()
|
self._tb_cats = OrderedDict()
|
||||||
self._search_term_map = {}
|
self._search_term_map = {}
|
||||||
self.custom_label_to_key_map = {}
|
self.custom_label_to_key_map = {}
|
||||||
|
@ -150,7 +150,7 @@ class DBThread(Thread):
|
|||||||
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
|
detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES)
|
||||||
self.conn.execute('pragma cache_size=5000')
|
self.conn.execute('pragma cache_size=5000')
|
||||||
encoding = self.conn.execute('pragma encoding').fetchone()[0]
|
encoding = self.conn.execute('pragma encoding').fetchone()[0]
|
||||||
c_ext_loaded = False #load_c_extensions(self.conn)
|
c_ext_loaded = load_c_extensions(self.conn)
|
||||||
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
|
self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row)
|
||||||
self.conn.create_aggregate('concat', 1, Concatenate)
|
self.conn.create_aggregate('concat', 1, Concatenate)
|
||||||
if not c_ext_loaded:
|
if not c_ext_loaded:
|
||||||
|
@ -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
|
converted book will have its publisher set to "Hello World". For more information about
|
||||||
|app|'s plugin system, read on...
|
|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, None)
|
||||||
|
|
||||||
|
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 acheive. The key thing to remember is that the plugin has access to the full |app| GUI via ``self.gui``.
|
||||||
|
|
||||||
|
|
||||||
The Plugin base class
|
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`
|
: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
|
Saving searches
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -161,11 +161,20 @@ The base class for such devices is :class:`calibre.devices.usbms.driver.USBMS`.
|
|||||||
User Interface Actions
|
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
|
.. autoclass:: calibre.gui2.actions.InterfaceAction
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members:
|
:members:
|
||||||
:member-order: bysource
|
:member-order: bysource
|
||||||
|
|
||||||
|
.. autoclass:: calibre.customize.InterfaceActionBase
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
:member-order: bysource
|
||||||
|
|
||||||
|
|
||||||
Preferences Plugins
|
Preferences Plugins
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: calibre 0.7.30\n"
|
"Project-Id-Version: calibre 0.7.31\n"
|
||||||
"POT-Creation-Date: 2010-11-26 10:43+MST\n"
|
"POT-Creation-Date: 2010-11-27 11:31+MST\n"
|
||||||
"PO-Revision-Date: 2010-11-26 10:43+MST\n"
|
"PO-Revision-Date: 2010-11-27 11:31+MST\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: LANGUAGE\n"
|
"Language-Team: LANGUAGE\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user