GwR fixes to catalog code
BIN
resources/images/news/novaya_gazeta.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
resources/images/news/vedomosti.png
Normal file
After Width: | Height: | Size: 693 B |
1381
resources/mime.types
Normal file
54
resources/recipes/la_rioja.recipe
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
www.larioja.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class LaRioja(BasicNewsRecipe):
|
||||||
|
title = 'La Rioja'
|
||||||
|
__author__ = 'Arturo Martinez Nieves'
|
||||||
|
description = 'Noticias de La Rioja y el resto del mundo'
|
||||||
|
publisher = 'La Rioja'
|
||||||
|
category = 'news, politics, Spain'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 200
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'cp1252'
|
||||||
|
use_embedded_content = False
|
||||||
|
language = 'es'
|
||||||
|
remove_empty_feeds = True
|
||||||
|
masthead_url = 'http://www.larioja.com/includes/manuales/larioja/include-lariojapapeldigital-zonac-fondocabecera01.jpg'
|
||||||
|
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } img{margin-bottom: 0.4em} .photo-caption{font-size: x-small} '
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher' : publisher
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(attrs={'id':'title'})
|
||||||
|
,dict(attrs={'class':['overhead','headline','subhead','date','text','noticia_cont','desarrollo']})
|
||||||
|
]
|
||||||
|
remove_tags = [dict(name='ul')]
|
||||||
|
remove_attributes = ['width','height']
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Ultimas Noticias' , u'http://www.larioja.com/rss/feeds/ultima.xml' )
|
||||||
|
,(u'Portada' , u'http://www.larioja.com/rss/feeds/portada.xml' )
|
||||||
|
,(u'Mundo' , u'http://www.larioja.com/rss/feeds/mundo.xml' )
|
||||||
|
,(u'Espana' , u'http://www.larioja.com/rss/feeds/espana.xml' )
|
||||||
|
,(u'Region' , u'http://www.larioja.com/rss/feeds/region.xml' )
|
||||||
|
,(u'Comarcas' , u'http://www.larioja.com/rss/feeds/comarcas.xml')
|
||||||
|
,(u'Deportes' , u'http://www.larioja.com/rss/feeds/deportes.xml' )
|
||||||
|
,(u'Economia' , u'http://www.larioja.com/rss/feeds/economia.xml' )
|
||||||
|
,(u'Cultura' , u'http://www.larioja.com/rss/feeds/cultura.xml' )
|
||||||
|
,(u'Opinion' , u'http://www.larioja.com/rss/feeds/opinion.xml' )
|
||||||
|
,(u'Sociedad' , u'http://www.larioja.com/rss/feeds/sociedad.xml' )
|
||||||
|
|
||||||
|
]
|
||||||
|
|
11
resources/recipes/nacionred.recipe
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1291022049(BasicNewsRecipe):
|
||||||
|
title = u'NacionRed.com'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
language = 'es'
|
||||||
|
__author__ = 'Arturo Martinez Nieves'
|
||||||
|
|
||||||
|
feeds = [(u'NacionRed.com', u'http://feeds.weblogssl.com/nacionred?format=xml')]
|
||||||
|
|
@ -18,7 +18,7 @@ __all__ = [
|
|||||||
'pypi_register', 'pypi_upload', 'upload_to_server',
|
'pypi_register', 'pypi_upload', 'upload_to_server',
|
||||||
'upload_user_manual', 'upload_to_mobileread', 'upload_demo',
|
'upload_user_manual', 'upload_to_mobileread', 'upload_demo',
|
||||||
'upload_to_sourceforge', 'upload_to_google_code',
|
'upload_to_sourceforge', 'upload_to_google_code',
|
||||||
'linux32', 'linux64', 'linux', 'linux_freeze', 'linux_freeze2',
|
'linux32', 'linux64', 'linux', 'linux_freeze',
|
||||||
'osx32_freeze', 'osx', 'rsync', 'push',
|
'osx32_freeze', 'osx', 'rsync', 'push',
|
||||||
'win32_freeze', 'win32', 'win',
|
'win32_freeze', 'win32', 'win',
|
||||||
'stage1', 'stage2', 'stage3', 'stage4', 'publish'
|
'stage1', 'stage2', 'stage3', 'stage4', 'publish'
|
||||||
@ -79,10 +79,8 @@ from setup.installer.linux import Linux, Linux32, Linux64
|
|||||||
linux = Linux()
|
linux = Linux()
|
||||||
linux32 = Linux32()
|
linux32 = Linux32()
|
||||||
linux64 = Linux64()
|
linux64 = Linux64()
|
||||||
from setup.installer.linux.freeze import LinuxFreeze
|
from setup.installer.linux.freeze2 import LinuxFreeze
|
||||||
linux_freeze = LinuxFreeze()
|
linux_freeze = LinuxFreeze()
|
||||||
from setup.installer.linux.freeze2 import LinuxFreeze2
|
|
||||||
linux_freeze2 = LinuxFreeze2()
|
|
||||||
|
|
||||||
from setup.installer.osx import OSX
|
from setup.installer.osx import OSX
|
||||||
osx = OSX()
|
osx = OSX()
|
||||||
|
@ -17,7 +17,7 @@ class Linux32(VMInstaller):
|
|||||||
INSTALLER_EXT = 'tar.bz2'
|
INSTALLER_EXT = 'tar.bz2'
|
||||||
VM_NAME = 'gentoo32_build'
|
VM_NAME = 'gentoo32_build'
|
||||||
VM = '/vmware/bin/gentoo32_build'
|
VM = '/vmware/bin/gentoo32_build'
|
||||||
FREEZE_COMMAND = 'linux_freeze2'
|
FREEZE_COMMAND = 'linux_freeze'
|
||||||
FREEZE_TEMPLATE = 'sudo python -OO setup.py {freeze_command}'
|
FREEZE_TEMPLATE = 'sudo python -OO setup.py {freeze_command}'
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,21 +16,9 @@ SITE_PACKAGES = ['IPython', 'PIL', 'dateutil', 'dns', 'PyQt4', 'mechanize',
|
|||||||
'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml',
|
'sip.so', 'BeautifulSoup.py', 'cssutils', 'encutils', 'lxml',
|
||||||
'sipconfig.py', 'xdg']
|
'sipconfig.py', 'xdg']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gcc = subprocess.Popen(["gcc-config", "-c"], stdout=subprocess.PIPE).communicate()[0]
|
|
||||||
chost, _, gcc = gcc.rpartition('-')
|
|
||||||
stdcpp = '/usr/lib/gcc/%s/%s/libstdc++.so.?'%(chost.strip(), gcc.strip())
|
|
||||||
stdcpp = glob.glob(stdcpp)[-1]
|
|
||||||
is64bit = platform.architecture()[0] == '64bit'
|
|
||||||
arch = 'x86_64' if is64bit else 'i686'
|
|
||||||
ffi = '/usr/lib/libffi.so.5' if is64bit else '/usr/lib/gcc/i686-pc-linux-gnu/4.4.1/libffi.so.4'
|
|
||||||
|
|
||||||
|
|
||||||
QTDIR = '/usr/lib/qt4'
|
QTDIR = '/usr/lib/qt4'
|
||||||
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml', 'QtWebKit', 'QtDBus')
|
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml', 'QtWebKit', 'QtDBus')
|
||||||
|
|
||||||
|
|
||||||
binary_includes = [
|
binary_includes = [
|
||||||
'/usr/bin/pdftohtml',
|
'/usr/bin/pdftohtml',
|
||||||
'/usr/lib/libwmflite-0.2.so.7',
|
'/usr/lib/libwmflite-0.2.so.7',
|
||||||
@ -49,8 +37,6 @@ binary_includes = [
|
|||||||
'/usr/lib/libjpeg.so.8',
|
'/usr/lib/libjpeg.so.8',
|
||||||
'/usr/lib/libxslt.so.1',
|
'/usr/lib/libxslt.so.1',
|
||||||
'/usr/lib/libgthread-2.0.so.0',
|
'/usr/lib/libgthread-2.0.so.0',
|
||||||
stdcpp,
|
|
||||||
ffi,
|
|
||||||
'/usr/lib/libpng14.so.14',
|
'/usr/lib/libpng14.so.14',
|
||||||
'/usr/lib/libexslt.so.0',
|
'/usr/lib/libexslt.so.0',
|
||||||
'/usr/lib/libMagickWand.so.4',
|
'/usr/lib/libMagickWand.so.4',
|
||||||
@ -66,7 +52,11 @@ binary_includes = [
|
|||||||
]
|
]
|
||||||
binary_includes += [os.path.join(QTDIR, 'lib%s.so.4'%x) for x in QTDLLS]
|
binary_includes += [os.path.join(QTDIR, 'lib%s.so.4'%x) for x in QTDLLS]
|
||||||
|
|
||||||
class LinuxFreeze2(Command):
|
is64bit = platform.architecture()[0] == '64bit'
|
||||||
|
arch = 'x86_64' if is64bit else 'i686'
|
||||||
|
|
||||||
|
|
||||||
|
class LinuxFreeze(Command):
|
||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
self.drop_privileges()
|
self.drop_privileges()
|
||||||
@ -93,7 +83,21 @@ class LinuxFreeze2(Command):
|
|||||||
self.info('Copying libs...')
|
self.info('Copying libs...')
|
||||||
os.mkdir(self.lib_dir)
|
os.mkdir(self.lib_dir)
|
||||||
os.mkdir(self.bin_dir)
|
os.mkdir(self.bin_dir)
|
||||||
for x in binary_includes:
|
|
||||||
|
gcc = subprocess.Popen(["gcc-config", "-c"], stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
chost, _, gcc = gcc.rpartition('-')
|
||||||
|
gcc_lib = '/usr/lib/gcc/%s/%s/'%(chost.strip(), gcc.strip())
|
||||||
|
stdcpp = gcc_lib+'libstdc++.so.?'
|
||||||
|
stdcpp = glob.glob(stdcpp)[-1]
|
||||||
|
ffi = gcc_lib+'libffi.so.?'
|
||||||
|
ffi = glob.glob(ffi)
|
||||||
|
if ffi:
|
||||||
|
ffi = ffi[-1]
|
||||||
|
else:
|
||||||
|
ffi = glob.glob('/usr/lib/libffi.so.?')[-1]
|
||||||
|
|
||||||
|
|
||||||
|
for x in binary_includes + [stdcpp, ffi]:
|
||||||
dest = self.bin_dir if '/bin/' in x else self.lib_dir
|
dest = self.bin_dir if '/bin/' in x else self.lib_dir
|
||||||
shutil.copy2(x, dest)
|
shutil.copy2(x, dest)
|
||||||
shutil.copy2('/usr/lib/libpython%s.so.1.0'%self.py_ver, dest)
|
shutil.copy2('/usr/lib/libpython%s.so.1.0'%self.py_ver, dest)
|
||||||
@ -268,7 +272,6 @@ class LinuxFreeze2(Command):
|
|||||||
base=`dirname $path`
|
base=`dirname $path`
|
||||||
lib=$base/lib
|
lib=$base/lib
|
||||||
export LD_LIBRARY_PATH=$lib:$LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH=$lib:$LD_LIBRARY_PATH
|
||||||
export QT_PLUGIN_PATH=$lib/qt_plugins
|
|
||||||
export MAGICK_CONFIGURE_PATH=$lib/ImageMagick/config
|
export MAGICK_CONFIGURE_PATH=$lib/ImageMagick/config
|
||||||
export MAGICK_CODER_MODULE_PATH=$lib/ImageMagick/modules-Q16/coders
|
export MAGICK_CODER_MODULE_PATH=$lib/ImageMagick/modules-Q16/coders
|
||||||
export MAGICK_CODER_FILTER_PATH=$lib/ImageMagick/modules-Q16/filters
|
export MAGICK_CODER_FILTER_PATH=$lib/ImageMagick/modules-Q16/filters
|
||||||
@ -336,12 +339,21 @@ class LinuxFreeze2(Command):
|
|||||||
def set_helper():
|
def set_helper():
|
||||||
__builtin__.help = _Helper()
|
__builtin__.help = _Helper()
|
||||||
|
|
||||||
|
def set_qt_plugin_path():
|
||||||
|
import uuid
|
||||||
|
uuid.uuid4() # Workaround for libuuid/PyQt conflict
|
||||||
|
from PyQt4.Qt import QCoreApplication
|
||||||
|
paths = list(map(unicode, QCoreApplication.libraryPaths()))
|
||||||
|
paths.insert(0, sys.frozen_path + '/lib/qt_plugins')
|
||||||
|
QCoreApplication.setLibraryPaths(paths)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
sys.argv[0] = sys.calibre_basename
|
sys.argv[0] = sys.calibre_basename
|
||||||
set_default_encoding()
|
set_default_encoding()
|
||||||
set_helper()
|
set_helper()
|
||||||
|
set_qt_plugin_path()
|
||||||
mod = __import__(sys.calibre_module, fromlist=[1])
|
mod = __import__(sys.calibre_module, fromlist=[1])
|
||||||
func = getattr(mod, sys.calibre_function)
|
func = getattr(mod, sys.calibre_function)
|
||||||
return func()
|
return func()
|
||||||
|
@ -3,7 +3,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import uuid, sys, os, re, logging, time, mimetypes, \
|
import uuid, sys, os, re, logging, time, \
|
||||||
__builtin__, warnings, multiprocessing
|
__builtin__, warnings, multiprocessing
|
||||||
from urllib import getproxies
|
from urllib import getproxies
|
||||||
__builtin__.__dict__['dynamic_property'] = lambda(func): func(None)
|
__builtin__.__dict__['dynamic_property'] = lambda(func): func(None)
|
||||||
@ -19,43 +19,18 @@ from calibre.constants import iswindows, isosx, islinux, isfreebsd, isfrozen, \
|
|||||||
__appname__, __version__, __author__, \
|
__appname__, __version__, __author__, \
|
||||||
win32event, win32api, winerror, fcntl, \
|
win32event, win32api, winerror, fcntl, \
|
||||||
filesystem_encoding, plugins, config_dir
|
filesystem_encoding, plugins, config_dir
|
||||||
from calibre.startup import winutil, winutilerror
|
from calibre.startup import winutil, winutilerror, guess_type
|
||||||
|
|
||||||
uuid.uuid4() # Imported before PyQt4 to workaround PyQt4 util-linux conflict on gentoo
|
if islinux and not getattr(sys, 'frozen', False):
|
||||||
|
# Imported before PyQt4 to workaround PyQt4 util-linux conflict on gentoo
|
||||||
|
uuid.uuid4()
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
|
# Prevent pyflakes from complaining
|
||||||
winutil, winutilerror, __appname__, islinux, __version__
|
winutil, winutilerror, __appname__, islinux, __version__
|
||||||
fcntl, win32event, isfrozen, __author__, terminal_controller
|
fcntl, win32event, isfrozen, __author__, terminal_controller
|
||||||
winerror, win32api, isfreebsd
|
winerror, win32api, isfreebsd, guess_type
|
||||||
|
|
||||||
mimetypes.add_type('application/epub+zip', '.epub')
|
|
||||||
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
|
||||||
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
|
||||||
mimetypes.add_type('image/svg+xml', '.svg')
|
|
||||||
mimetypes.add_type('text/fb2+xml', '.fb2')
|
|
||||||
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
|
|
||||||
mimetypes.add_type('application/x-sony-bbeb', '.lrx')
|
|
||||||
mimetypes.add_type('application/x-dtbncx+xml', '.ncx')
|
|
||||||
mimetypes.add_type('application/adobe-page-template+xml', '.xpgt')
|
|
||||||
mimetypes.add_type('application/x-font-opentype', '.otf')
|
|
||||||
mimetypes.add_type('application/x-font-truetype', '.ttf')
|
|
||||||
mimetypes.add_type('application/oebps-package+xml', '.opf')
|
|
||||||
mimetypes.add_type('application/vnd.palm', '.pdb')
|
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
|
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
|
|
||||||
mimetypes.add_type('application/x-mobipocket-ebook', '.azw')
|
|
||||||
mimetypes.add_type('application/x-cbz', '.cbz')
|
|
||||||
mimetypes.add_type('application/x-cbr', '.cbr')
|
|
||||||
mimetypes.add_type('application/x-koboreader-ebook', '.kobo')
|
|
||||||
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
|
|
||||||
import cssutils
|
import cssutils
|
||||||
cssutils.log.setLevel(logging.WARN)
|
cssutils.log.setLevel(logging.WARN)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class ANDROID(USBMS):
|
|||||||
|
|
||||||
VENDOR_ID = {
|
VENDOR_ID = {
|
||||||
# HTC
|
# HTC
|
||||||
0x0bb4 : { 0x0c02 : [0x100, 0x0227], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
|
||||||
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
|
||||||
0xc92 : [0x100]},
|
0xc92 : [0x100]},
|
||||||
|
|
||||||
|
@ -79,11 +79,11 @@ class KOBO(USBMS):
|
|||||||
|
|
||||||
# Determine the firmware version
|
# Determine the firmware version
|
||||||
f = open(self.normalize_path(self._main_prefix + '.kobo/version'), 'r')
|
f = open(self.normalize_path(self._main_prefix + '.kobo/version'), 'r')
|
||||||
fwversion = f.readline().split(',')[2]
|
self.fwversion = f.readline().split(',')[2]
|
||||||
f.close()
|
f.close()
|
||||||
if fwversion != '1.0' and fwversion != '1.4':
|
if self.fwversion != '1.0' and self.fwversion != '1.4':
|
||||||
self.has_kepubs = True
|
self.has_kepubs = True
|
||||||
debug_print('Version of firmware: ', fwversion, 'Has kepubs:', self.has_kepubs)
|
debug_print('Version of firmware: ', self.fwversion, 'Has kepubs:', self.has_kepubs)
|
||||||
|
|
||||||
self.booklist_class.rebuild_collections = self.rebuild_collections
|
self.booklist_class.rebuild_collections = self.rebuild_collections
|
||||||
|
|
||||||
@ -220,6 +220,7 @@ class KOBO(USBMS):
|
|||||||
# 2) volume_shorcover
|
# 2) volume_shorcover
|
||||||
# 2) content
|
# 2) content
|
||||||
|
|
||||||
|
debug_print('delete_via_sql: ContentID: ', ContentID, 'ContentType: ', ContentType)
|
||||||
connection = sqlite.connect(self.normalize_path(self._main_prefix + '.kobo/KoboReader.sqlite'))
|
connection = sqlite.connect(self.normalize_path(self._main_prefix + '.kobo/KoboReader.sqlite'))
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
t = (ContentID,)
|
t = (ContentID,)
|
||||||
@ -400,6 +401,12 @@ class KOBO(USBMS):
|
|||||||
elif extension == '.pdf' or extension == '.epub':
|
elif extension == '.pdf' or extension == '.epub':
|
||||||
# print "ePub or pdf"
|
# print "ePub or pdf"
|
||||||
ContentType = 16
|
ContentType = 16
|
||||||
|
elif extension == '.rtf' or extension == '.txt' or extension == '.htm' or extension == '.html':
|
||||||
|
# print "txt"
|
||||||
|
if self.fwversion == '1.0' or self.fwversion == '1.4' or self.fwversion == '1.7.4':
|
||||||
|
ContentType = 999
|
||||||
|
else:
|
||||||
|
ContentType = 901
|
||||||
else: # if extension == '.html' or extension == '.txt':
|
else: # if extension == '.html' or extension == '.txt':
|
||||||
ContentType = 999 # Yet another hack: to get around Kobo changing how ContentID is stored
|
ContentType = 999 # Yet another hack: to get around Kobo changing how ContentID is stored
|
||||||
return ContentType
|
return ContentType
|
||||||
|
@ -240,18 +240,26 @@ class Stylizer(object):
|
|||||||
else:
|
else:
|
||||||
for elem in matches:
|
for elem in matches:
|
||||||
self.style(elem)._update_cssdict(cssdict)
|
self.style(elem)._update_cssdict(cssdict)
|
||||||
for elem in xpath(tree, '//h:img[@width or @height]'):
|
|
||||||
base = elem.get('style', '').strip()
|
|
||||||
if base:
|
|
||||||
base += ';'
|
|
||||||
for prop in ('width', 'height'):
|
|
||||||
val = elem.get(prop, False)
|
|
||||||
if val:
|
|
||||||
base += '%s: %s;'%(prop, val)
|
|
||||||
del elem.attrib[prop]
|
|
||||||
elem.set('style', base)
|
|
||||||
for elem in xpath(tree, '//h:*[@style]'):
|
for elem in xpath(tree, '//h:*[@style]'):
|
||||||
self.style(elem)._apply_style_attr()
|
self.style(elem)._apply_style_attr()
|
||||||
|
num_pat = re.compile(r'\d+$')
|
||||||
|
for elem in xpath(tree, '//h:img[@width or @height]'):
|
||||||
|
style = self.style(elem)
|
||||||
|
# Check if either height or width is not default
|
||||||
|
is_styled = style._style.get('width', 'auto') != 'auto' or \
|
||||||
|
style._style.get('height', 'auto') != 'auto'
|
||||||
|
if not is_styled:
|
||||||
|
# Update img style dimension using width and height
|
||||||
|
upd = {}
|
||||||
|
for prop in ('width', 'height'):
|
||||||
|
val = elem.get(prop, '').strip()
|
||||||
|
del elem.attrib[prop]
|
||||||
|
if val:
|
||||||
|
if num_pat.match(val) is not None:
|
||||||
|
val += 'px'
|
||||||
|
upd[prop] = val
|
||||||
|
if upd:
|
||||||
|
style._update_cssdict(upd)
|
||||||
|
|
||||||
def _fetch_css_file(self, path):
|
def _fetch_css_file(self, path):
|
||||||
hrefs = self.oeb.manifest.hrefs
|
hrefs = self.oeb.manifest.hrefs
|
||||||
|
@ -171,24 +171,18 @@ def render_jacket(mi, output_profile,
|
|||||||
generated_html = P('jacket/template.xhtml',
|
generated_html = P('jacket/template.xhtml',
|
||||||
data=True).decode('utf-8').format(**args)
|
data=True).decode('utf-8').format(**args)
|
||||||
|
|
||||||
print generated_html
|
|
||||||
|
|
||||||
# Post-process the generated html to strip out empty header items
|
# Post-process the generated html to strip out empty header items
|
||||||
soup = BeautifulSoup(generated_html)
|
soup = BeautifulSoup(generated_html)
|
||||||
if not series:
|
if not series:
|
||||||
#series_tag = soup.find('tr', attrs={'class':'cbj_series'})
|
|
||||||
series_tag = soup.find(attrs={'class':'cbj_series'})
|
series_tag = soup.find(attrs={'class':'cbj_series'})
|
||||||
series_tag.extract()
|
series_tag.extract()
|
||||||
if not rating:
|
if not rating:
|
||||||
#rating_tag = soup.find('tr', attrs={'class':'cbj_rating'})
|
|
||||||
rating_tag = soup.find(attrs={'class':'cbj_rating'})
|
rating_tag = soup.find(attrs={'class':'cbj_rating'})
|
||||||
rating_tag.extract()
|
rating_tag.extract()
|
||||||
if not tags:
|
if not tags:
|
||||||
#tags_tag = soup.find('tr', attrs={'class':'cbj_tags'})
|
|
||||||
tags_tag = soup.find(attrs={'class':'cbj_tags'})
|
tags_tag = soup.find(attrs={'class':'cbj_tags'})
|
||||||
tags_tag.extract()
|
tags_tag.extract()
|
||||||
if not pubdate:
|
if not pubdate:
|
||||||
#pubdate_tag = soup.find('tr', attrs={'class':'cbj_pubdate'})
|
|
||||||
pubdate_tag = soup.find(attrs={'class':'cbj_pubdate'})
|
pubdate_tag = soup.find(attrs={'class':'cbj_pubdate'})
|
||||||
pubdate_tag.extract()
|
pubdate_tag.extract()
|
||||||
if output_profile.short_name != 'kindle':
|
if output_profile.short_name != 'kindle':
|
||||||
|
@ -18,6 +18,7 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
|||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.constants import preferred_encoding, filesystem_encoding
|
from calibre.constants import preferred_encoding, filesystem_encoding
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
|
from calibre.gui2 import config
|
||||||
|
|
||||||
class AddAction(InterfaceAction):
|
class AddAction(InterfaceAction):
|
||||||
|
|
||||||
@ -101,7 +102,12 @@ class AddAction(InterfaceAction):
|
|||||||
else:
|
else:
|
||||||
ids.add(db.import_book(mi, []))
|
ids.add(db.import_book(mi, []))
|
||||||
self.gui.library_view.model().books_added(len(books))
|
self.gui.library_view.model().books_added(len(books))
|
||||||
|
orig = config['overwrite_author_title_metadata']
|
||||||
|
config['overwrite_author_title_metadata'] = True
|
||||||
|
try:
|
||||||
self.gui.iactions['Edit Metadata'].do_download_metadata(ids)
|
self.gui.iactions['Edit Metadata'].do_download_metadata(ids)
|
||||||
|
finally:
|
||||||
|
config['overwrite_author_title_metadata'] = orig
|
||||||
|
|
||||||
|
|
||||||
def files_dropped(self, paths):
|
def files_dropped(self, paths):
|
||||||
|
@ -37,7 +37,8 @@ class GenerateCatalogAction(InterfaceAction):
|
|||||||
dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)}
|
dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)}
|
||||||
|
|
||||||
# Calling gui2.tools:generate_catalog()
|
# Calling gui2.tools:generate_catalog()
|
||||||
ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager)
|
ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager,
|
||||||
|
db)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -162,9 +162,14 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
return
|
return
|
||||||
# Prevent the TagView from updating due to signals from the database
|
# Prevent the TagView from updating due to signals from the database
|
||||||
self.gui.tags_view.blockSignals(True)
|
self.gui.tags_view.blockSignals(True)
|
||||||
|
changed = False
|
||||||
try:
|
try:
|
||||||
changed = MetadataBulkDialog(self.gui, rows,
|
while True:
|
||||||
self.gui.library_view.model()).changed
|
dialog = MetadataBulkDialog(self.gui, rows, self.gui.library_view.model())
|
||||||
|
if dialog.changed:
|
||||||
|
changed = True
|
||||||
|
if not dialog.do_again:
|
||||||
|
break
|
||||||
finally:
|
finally:
|
||||||
self.gui.tags_view.blockSignals(False)
|
self.gui.tags_view.blockSignals(False)
|
||||||
if changed:
|
if changed:
|
||||||
|
@ -34,7 +34,7 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
self.all_fields.append(x)
|
self.all_fields.append(x)
|
||||||
QListWidgetItem(x, self.db_fields)
|
QListWidgetItem(x, self.db_fields)
|
||||||
|
|
||||||
def initialize(self, name): #not working properly to update
|
def initialize(self, name, db): #not working properly to update
|
||||||
self.name = name
|
self.name = name
|
||||||
fields = gprefs.get(name+'_db_fields', self.all_fields)
|
fields = gprefs.get(name+'_db_fields', self.all_fields)
|
||||||
# Restore the activated db_fields from last use
|
# Restore the activated db_fields from last use
|
||||||
|
@ -28,7 +28,7 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
self.all_fields.append(x)
|
self.all_fields.append(x)
|
||||||
QListWidgetItem(x, self.db_fields)
|
QListWidgetItem(x, self.db_fields)
|
||||||
|
|
||||||
def initialize(self, name):
|
def initialize(self, name, db):
|
||||||
self.name = name
|
self.name = name
|
||||||
fields = gprefs.get(name+'_db_fields', self.all_fields)
|
fields = gprefs.get(name+'_db_fields', self.all_fields)
|
||||||
# Restore the activated fields from last use
|
# Restore the activated fields from last use
|
||||||
|
@ -7,16 +7,11 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from calibre.ebooks.conversion.config import load_defaults
|
from calibre.ebooks.conversion.config import load_defaults
|
||||||
from calibre.gui2 import gprefs
|
from calibre.gui2 import gprefs
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
|
||||||
from calibre.utils.config import prefs
|
|
||||||
|
|
||||||
from catalog_epub_mobi_ui import Ui_Form
|
from catalog_epub_mobi_ui import Ui_Form
|
||||||
from PyQt4 import QtGui
|
from PyQt4.Qt import QWidget, QLineEdit
|
||||||
from PyQt4.Qt import QWidget
|
|
||||||
|
|
||||||
class PluginWidget(QWidget,Ui_Form):
|
class PluginWidget(QWidget,Ui_Form):
|
||||||
|
|
||||||
@ -45,12 +40,10 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def initialize(self, name):
|
def initialize(self, name, db):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
# Populate the 'Read book' source fields
|
# Populate the 'Read book' source fields
|
||||||
dbpath = os.path.abspath(prefs['library_path'])
|
|
||||||
db = LibraryDatabase2(dbpath)
|
|
||||||
all_custom_fields = db.custom_field_keys()
|
all_custom_fields = db.custom_field_keys()
|
||||||
custom_fields = {}
|
custom_fields = {}
|
||||||
custom_fields['Tag'] = {'field':'tag', 'datatype':u'text'}
|
custom_fields['Tag'] = {'field':'tag', 'datatype':u'text'}
|
||||||
@ -91,8 +84,8 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
getattr(self, opt[0]).setText(opt_value)
|
getattr(self, opt[0]).setText(opt_value)
|
||||||
|
|
||||||
# Init self.read_source_field
|
# Init self.read_source_field
|
||||||
cs = str(self.read_source_field_cb.currentText())
|
cs = unicode(self.read_source_field_cb.currentText())
|
||||||
read_source_spec = self.read_source_fields[str(cs)]
|
read_source_spec = self.read_source_fields[cs]
|
||||||
self.read_source_field = read_source_spec['field']
|
self.read_source_field = read_source_spec['field']
|
||||||
|
|
||||||
def options(self):
|
def options(self):
|
||||||
@ -151,9 +144,9 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
|
|
||||||
# Change pattern input widget to match the source field datatype
|
# Change pattern input widget to match the source field datatype
|
||||||
if read_source_spec['datatype'] in ['bool','composite','datetime','text']:
|
if read_source_spec['datatype'] in ['bool','composite','datetime','text']:
|
||||||
if type(self.read_pattern) != type(QtGui.QLineEdit()):
|
if not isinstance(self.read_pattern, QLineEdit):
|
||||||
self.read_spec_hl.removeWidget(self.read_pattern)
|
self.read_spec_hl.removeWidget(self.read_pattern)
|
||||||
dw = QtGui.QLineEdit()
|
dw = QLineEdit(self)
|
||||||
dw.setObjectName('read_pattern')
|
dw.setObjectName('read_pattern')
|
||||||
dw.setToolTip('Pattern for read book')
|
dw.setToolTip('Pattern for read book')
|
||||||
self.read_pattern = dw
|
self.read_pattern = dw
|
||||||
|
@ -19,7 +19,7 @@ from calibre.customize.ui import catalog_plugins
|
|||||||
class Catalog(QDialog, Ui_Dialog):
|
class Catalog(QDialog, Ui_Dialog):
|
||||||
''' Catalog Dialog builder'''
|
''' Catalog Dialog builder'''
|
||||||
|
|
||||||
def __init__(self, parent, dbspec, ids):
|
def __init__(self, parent, dbspec, ids, db):
|
||||||
import re, cStringIO
|
import re, cStringIO
|
||||||
from calibre import prints as info
|
from calibre import prints as info
|
||||||
from PyQt4.uic import compileUi
|
from PyQt4.uic import compileUi
|
||||||
@ -51,7 +51,7 @@ class Catalog(QDialog, Ui_Dialog):
|
|||||||
catalog_widget = __import__('calibre.gui2.catalog.'+name,
|
catalog_widget = __import__('calibre.gui2.catalog.'+name,
|
||||||
fromlist=[1])
|
fromlist=[1])
|
||||||
pw = catalog_widget.PluginWidget()
|
pw = catalog_widget.PluginWidget()
|
||||||
pw.initialize(name)
|
pw.initialize(name, db)
|
||||||
pw.ICON = I('forward.png')
|
pw.ICON = I('forward.png')
|
||||||
self.widgets.append(pw)
|
self.widgets.append(pw)
|
||||||
[self.fmts.append([file_type.upper(), pw.sync_enabled,pw]) for file_type in plugin.file_types]
|
[self.fmts.append([file_type.upper(), pw.sync_enabled,pw]) for file_type in plugin.file_types]
|
||||||
|
@ -6,7 +6,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
|
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
|
||||||
pyqtSignal
|
pyqtSignal, QDialogButtonBox
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||||
@ -232,8 +232,19 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
self.create_custom_column_editors()
|
self.create_custom_column_editors()
|
||||||
|
|
||||||
self.prepare_search_and_replace()
|
self.prepare_search_and_replace()
|
||||||
|
|
||||||
|
self.button_box.clicked.connect(self.button_clicked)
|
||||||
|
self.button_box.button(QDialogButtonBox.Apply).setToolTip(_(
|
||||||
|
'Immediately make all changes without closing the dialog. '
|
||||||
|
'This operation cannot be canceled or undone'))
|
||||||
|
self.do_again = False
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
|
def button_clicked(self, which):
|
||||||
|
if which == self.button_box.button(QDialogButtonBox.Apply):
|
||||||
|
self.do_again = True
|
||||||
|
self.accept()
|
||||||
|
|
||||||
def prepare_search_and_replace(self):
|
def prepare_search_and_replace(self):
|
||||||
self.search_for.initialize('bulk_edit_search_for')
|
self.search_for.initialize('bulk_edit_search_for')
|
||||||
self.replace_with.initialize('bulk_edit_replace_with')
|
self.replace_with.initialize('bulk_edit_replace_with')
|
||||||
@ -692,7 +703,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
self.db.clean()
|
self.db.clean()
|
||||||
return QDialog.accept(self)
|
return QDialog.accept(self)
|
||||||
|
|
||||||
|
|
||||||
def series_changed(self, *args):
|
def series_changed(self, *args):
|
||||||
self.write_series = True
|
self.write_series = True
|
||||||
|
|
||||||
|
@ -710,7 +710,7 @@ nothing should be put between the original text and the inserted text</string>
|
|||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="standardButtons">
|
<property name="standardButtons">
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -10,7 +10,8 @@ Scheduler for automated recipe downloads
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \
|
from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \
|
||||||
QAction, QIcon, QMutex, QTimer, pyqtSignal
|
QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QHBoxLayout, \
|
||||||
|
QLabel
|
||||||
|
|
||||||
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
||||||
from calibre.gui2.search_box import SearchBox2
|
from calibre.gui2.search_box import SearchBox2
|
||||||
@ -28,15 +29,21 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
|||||||
self.recipe_model = recipe_model
|
self.recipe_model = recipe_model
|
||||||
self.recipe_model.do_refresh()
|
self.recipe_model.do_refresh()
|
||||||
|
|
||||||
|
self._cont = QWidget(self)
|
||||||
|
self._cont.l = QHBoxLayout()
|
||||||
|
self._cont.setLayout(self._cont.l)
|
||||||
|
self._cont.la = QLabel(_('&Search:'))
|
||||||
|
self._cont.l.addWidget(self._cont.la, 1)
|
||||||
self.search = SearchBox2(self)
|
self.search = SearchBox2(self)
|
||||||
|
self._cont.l.addWidget(self.search, 100)
|
||||||
|
self._cont.la.setBuddy(self.search)
|
||||||
self.search.setMinimumContentsLength(25)
|
self.search.setMinimumContentsLength(25)
|
||||||
self.search.initialize('scheduler_search_history')
|
self.search.initialize('scheduler_search_history')
|
||||||
self.recipe_box.layout().insertWidget(0, self.search)
|
self.recipe_box.layout().insertWidget(0, self._cont)
|
||||||
self.search.search.connect(self.recipe_model.search)
|
self.search.search.connect(self.recipe_model.search)
|
||||||
self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'),
|
self.recipe_model.searched.connect(self.search.search_done,
|
||||||
self.search.search_done)
|
type=Qt.QueuedConnection)
|
||||||
self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'),
|
self.recipe_model.searched.connect(self.search_done)
|
||||||
self.search_done)
|
|
||||||
self.search.setFocus(Qt.OtherFocusReason)
|
self.search.setFocus(Qt.OtherFocusReason)
|
||||||
self.commit_on_change = True
|
self.commit_on_change = True
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class TagCategories(QDialog, Ui_TagCategories):
|
|||||||
|
|
||||||
cc_map = self.db.custom_column_label_map
|
cc_map = self.db.custom_column_label_map
|
||||||
for cc in cc_map:
|
for cc in cc_map:
|
||||||
if cc_map[cc]['datatype'] == 'text':
|
if cc_map[cc]['datatype'] in ['text', 'series']:
|
||||||
self.category_labels.append(db.field_metadata.label_to_key(cc))
|
self.category_labels.append(db.field_metadata.label_to_key(cc))
|
||||||
category_icons.append(cc_icon)
|
category_icons.append(cc_icon)
|
||||||
category_values.append(lambda col=cc: self.db.all_custom(label=col))
|
category_values.append(lambda col=cc: self.db.all_custom(label=col))
|
||||||
|
@ -517,6 +517,8 @@ class BooksView(QTableView): # {{{
|
|||||||
|
|
||||||
md.setUrls([url_for_id(i) for i in selected])
|
md.setUrls([url_for_id(i) for i in selected])
|
||||||
drag = QDrag(self)
|
drag = QDrag(self)
|
||||||
|
col = self.selectionModel().currentIndex().column()
|
||||||
|
md.column_name = self.column_map[col]
|
||||||
drag.setMimeData(md)
|
drag.setMimeData(md)
|
||||||
cover = self.drag_icon(m.cover(self.currentIndex().row()),
|
cover = self.drag_icon(m.cover(self.currentIndex().row()),
|
||||||
len(selected) > 1)
|
len(selected) > 1)
|
||||||
|
@ -127,7 +127,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.progress_label.setText('Parsing '+ self.file_name)
|
self.progress_label.setText('Parsing '+ self.file_name)
|
||||||
self.renderer = RenderWorker(self, stream, self.logger, self.opts)
|
self.renderer = RenderWorker(self, stream, self.logger, self.opts)
|
||||||
QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection)
|
QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection)
|
||||||
self.search.clear_to_help()
|
self.search.clear()
|
||||||
self.last_search = None
|
self.last_search = None
|
||||||
else:
|
else:
|
||||||
self.stack.setCurrentIndex(0)
|
self.stack.setCurrentIndex(0)
|
||||||
|
@ -8,9 +8,8 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \
|
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, QDialog, \
|
||||||
pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \
|
pyqtSignal, QCompleter, QAction, QKeySequence, QTimer
|
||||||
QAction, QKeySequence, QTimer
|
|
||||||
|
|
||||||
from calibre.gui2 import config
|
from calibre.gui2 import config
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
@ -20,34 +19,29 @@ from calibre.utils.search_query_parser import saved_searches
|
|||||||
|
|
||||||
class SearchLineEdit(QLineEdit):
|
class SearchLineEdit(QLineEdit):
|
||||||
key_pressed = pyqtSignal(object)
|
key_pressed = pyqtSignal(object)
|
||||||
mouse_released = pyqtSignal(object)
|
|
||||||
focus_out = pyqtSignal(object)
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
self.key_pressed.emit(event)
|
self.key_pressed.emit(event)
|
||||||
QLineEdit.keyPressEvent(self, event)
|
QLineEdit.keyPressEvent(self, event)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
self.mouse_released.emit(event)
|
|
||||||
QLineEdit.mouseReleaseEvent(self, event)
|
QLineEdit.mouseReleaseEvent(self, event)
|
||||||
|
QLineEdit.selectAll(self)
|
||||||
|
|
||||||
def focusOutEvent(self, event):
|
def focusInEvent(self, event):
|
||||||
self.focus_out.emit(event)
|
QLineEdit.focusInEvent(self, event)
|
||||||
QLineEdit.focusOutEvent(self, event)
|
QLineEdit.selectAll(self)
|
||||||
|
|
||||||
def dropEvent(self, ev):
|
def dropEvent(self, ev):
|
||||||
if self.parent().help_state:
|
|
||||||
self.parent().normalize_state()
|
self.parent().normalize_state()
|
||||||
return QLineEdit.dropEvent(self, ev)
|
return QLineEdit.dropEvent(self, ev)
|
||||||
|
|
||||||
def contextMenuEvent(self, ev):
|
def contextMenuEvent(self, ev):
|
||||||
if self.parent().help_state:
|
|
||||||
self.parent().normalize_state()
|
self.parent().normalize_state()
|
||||||
return QLineEdit.contextMenuEvent(self, ev)
|
return QLineEdit.contextMenuEvent(self, ev)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def paste(self, *args):
|
def paste(self, *args):
|
||||||
if self.parent().help_state:
|
|
||||||
self.parent().normalize_state()
|
self.parent().normalize_state()
|
||||||
return QLineEdit.paste(self)
|
return QLineEdit.paste(self)
|
||||||
|
|
||||||
@ -59,14 +53,17 @@ class SearchBox2(QComboBox):
|
|||||||
* Call initialize()
|
* Call initialize()
|
||||||
* Connect to the search() and cleared() signals from this widget.
|
* Connect to the search() and cleared() signals from this widget.
|
||||||
* Connect to the cleared() signal to know when the box content changes
|
* Connect to the cleared() signal to know when the box content changes
|
||||||
|
* Connect to focus_to_library signal to be told to manually change focus
|
||||||
* Call search_done() after every search is complete
|
* Call search_done() after every search is complete
|
||||||
* Use clear() to clear back to the help message
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
INTERVAL = 1500 #: Time to wait before emitting search signal
|
INTERVAL = 1500 #: Time to wait before emitting search signal
|
||||||
MAX_COUNT = 25
|
MAX_COUNT = 25
|
||||||
|
|
||||||
search = pyqtSignal(object)
|
search = pyqtSignal(object)
|
||||||
|
cleared = pyqtSignal()
|
||||||
|
changed = pyqtSignal()
|
||||||
|
focus_to_library = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QComboBox.__init__(self, parent)
|
QComboBox.__init__(self, parent)
|
||||||
@ -75,15 +72,10 @@ class SearchBox2(QComboBox):
|
|||||||
self.setLineEdit(self.line_edit)
|
self.setLineEdit(self.line_edit)
|
||||||
c = self.line_edit.completer()
|
c = self.line_edit.completer()
|
||||||
c.setCompletionMode(c.PopupCompletion)
|
c.setCompletionMode(c.PopupCompletion)
|
||||||
self.line_edit.key_pressed.connect(self.key_pressed,
|
self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection)
|
||||||
type=Qt.DirectConnection)
|
|
||||||
self.line_edit.mouse_released.connect(self.mouse_released,
|
|
||||||
type=Qt.DirectConnection)
|
|
||||||
self.activated.connect(self.history_selected)
|
self.activated.connect(self.history_selected)
|
||||||
self.setEditable(True)
|
self.setEditable(True)
|
||||||
self.help_state = False
|
|
||||||
self.as_you_type = True
|
self.as_you_type = True
|
||||||
self.prev_search = ''
|
|
||||||
self.timer = QTimer()
|
self.timer = QTimer()
|
||||||
self.timer.setSingleShot(True)
|
self.timer.setSingleShot(True)
|
||||||
self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
|
self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
|
||||||
@ -98,49 +90,40 @@ class SearchBox2(QComboBox):
|
|||||||
self.as_you_type = config['search_as_you_type']
|
self.as_you_type = config['search_as_you_type']
|
||||||
self.opt_name = opt_name
|
self.opt_name = opt_name
|
||||||
self.addItems(QStringList(list(set(config[opt_name]))))
|
self.addItems(QStringList(list(set(config[opt_name]))))
|
||||||
self.help_text = help_text
|
try:
|
||||||
|
self.line_edit.setPlaceholderText(help_text)
|
||||||
|
except:
|
||||||
|
# Using Qt < 4.7
|
||||||
|
pass
|
||||||
self.colorize = colorize
|
self.colorize = colorize
|
||||||
self.clear_to_help()
|
self.clear()
|
||||||
|
|
||||||
def normalize_state(self):
|
def normalize_state(self):
|
||||||
self.setToolTip(self.tool_tip_text)
|
self.setToolTip(self.tool_tip_text)
|
||||||
if self.help_state:
|
|
||||||
self.setEditText('')
|
|
||||||
self.line_edit.setStyleSheet(
|
self.line_edit.setStyleSheet(
|
||||||
'QLineEdit { color: black; background-color: %s; }' %
|
'QLineEdit{color:black;background-color:%s;}' % self.normal_background)
|
||||||
self.normal_background)
|
|
||||||
self.help_state = False
|
|
||||||
else:
|
|
||||||
self.line_edit.setStyleSheet(
|
|
||||||
'QLineEdit { color: black; background-color: %s; }' %
|
|
||||||
self.normal_background)
|
|
||||||
|
|
||||||
def clear_to_help(self):
|
|
||||||
self.setToolTip(self.tool_tip_text)
|
|
||||||
if self.help_state:
|
|
||||||
return
|
|
||||||
self.help_state = True
|
|
||||||
self.search.emit('')
|
|
||||||
self._in_a_search = False
|
|
||||||
self.setEditText(self.help_text)
|
|
||||||
self.line_edit.home(False)
|
|
||||||
self.line_edit.setStyleSheet(
|
|
||||||
'QLineEdit { color: gray; background-color: %s; }' %
|
|
||||||
self.normal_background)
|
|
||||||
self.emit(SIGNAL('cleared()'))
|
|
||||||
|
|
||||||
def text(self):
|
def text(self):
|
||||||
return self.currentText()
|
return self.currentText()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self, emit_search=True):
|
||||||
self.clear_to_help()
|
self.normalize_state()
|
||||||
|
self.setEditText('')
|
||||||
|
if emit_search:
|
||||||
|
self.search.emit('')
|
||||||
|
self._in_a_search = False
|
||||||
|
self.cleared.emit()
|
||||||
|
|
||||||
|
def clear_clicked(self, *args):
|
||||||
|
self.clear()
|
||||||
|
|
||||||
def search_done(self, ok):
|
def search_done(self, ok):
|
||||||
if isinstance(ok, basestring):
|
if isinstance(ok, basestring):
|
||||||
self.setToolTip(ok)
|
self.setToolTip(ok)
|
||||||
ok = False
|
ok = False
|
||||||
if not unicode(self.currentText()).strip():
|
if not unicode(self.currentText()).strip():
|
||||||
return self.clear_to_help()
|
self.clear(emit_search=False)
|
||||||
|
return
|
||||||
self._in_a_search = ok
|
self._in_a_search = ok
|
||||||
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
|
||||||
if not self.colorize:
|
if not self.colorize:
|
||||||
@ -150,45 +133,30 @@ class SearchBox2(QComboBox):
|
|||||||
def key_pressed(self, event):
|
def key_pressed(self, event):
|
||||||
k = event.key()
|
k = event.key()
|
||||||
if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
|
if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
|
||||||
Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown):
|
Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown,
|
||||||
|
Qt.Key_unknown):
|
||||||
return
|
return
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
if self._in_a_search:
|
if self._in_a_search:
|
||||||
self.emit(SIGNAL('changed()'))
|
self.changed.emit()
|
||||||
self._in_a_search = False
|
self._in_a_search = False
|
||||||
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||||
self.do_search()
|
self.do_search()
|
||||||
if self.as_you_type:
|
self.focus_to_library.emit()
|
||||||
|
elif self.as_you_type and unicode(event.text()):
|
||||||
self.timer.start(1500)
|
self.timer.start(1500)
|
||||||
|
|
||||||
def mouse_released(self, event):
|
|
||||||
self.normalize_state()
|
|
||||||
# Dont trigger a search since it make
|
|
||||||
# re-positioning the cursor using the mouse
|
|
||||||
# impossible
|
|
||||||
#if self.as_you_type:
|
|
||||||
# self.timer.start(1500)
|
|
||||||
|
|
||||||
def timer_event(self):
|
def timer_event(self):
|
||||||
self.do_search()
|
self.do_search()
|
||||||
|
|
||||||
def history_selected(self, text):
|
def history_selected(self, text):
|
||||||
self.emit(SIGNAL('changed()'))
|
self.changed.emit()
|
||||||
self.do_search()
|
self.do_search()
|
||||||
|
|
||||||
@property
|
|
||||||
def smart_text(self):
|
|
||||||
text = unicode(self.currentText()).strip()
|
|
||||||
if not text or text == self.help_text:
|
|
||||||
return ''
|
|
||||||
return text
|
|
||||||
|
|
||||||
def do_search(self, *args):
|
def do_search(self, *args):
|
||||||
text = unicode(self.currentText()).strip()
|
text = unicode(self.currentText()).strip()
|
||||||
if not text or text == self.help_text:
|
if not text:
|
||||||
return self.clear()
|
return self.clear()
|
||||||
self.help_state = False
|
|
||||||
self.prev_search = text
|
|
||||||
self.search.emit(text)
|
self.search.emit(text)
|
||||||
|
|
||||||
idx = self.findText(text, Qt.MatchFixedString)
|
idx = self.findText(text, Qt.MatchFixedString)
|
||||||
@ -220,7 +188,7 @@ class SearchBox2(QComboBox):
|
|||||||
|
|
||||||
def set_search_string(self, txt):
|
def set_search_string(self, txt):
|
||||||
if not txt:
|
if not txt:
|
||||||
self.clear_to_help()
|
self.clear()
|
||||||
return
|
return
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
self.setEditText(txt)
|
self.setEditText(txt)
|
||||||
@ -243,25 +211,24 @@ class SavedSearchBox(QComboBox):
|
|||||||
if you care about changes to the list of saved searches.
|
if you care about changes to the list of saved searches.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
changed = pyqtSignal()
|
||||||
|
focus_to_library = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QComboBox.__init__(self, parent)
|
QComboBox.__init__(self, parent)
|
||||||
self.normal_background = 'rgb(255, 255, 255, 0%)'
|
self.normal_background = 'rgb(255, 255, 255, 0%)'
|
||||||
|
|
||||||
self.line_edit = SearchLineEdit(self)
|
self.line_edit = SearchLineEdit(self)
|
||||||
self.setLineEdit(self.line_edit)
|
self.setLineEdit(self.line_edit)
|
||||||
self.line_edit.key_pressed.connect(self.key_pressed,
|
self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection)
|
||||||
type=Qt.DirectConnection)
|
|
||||||
self.line_edit.mouse_released.connect(self.mouse_released,
|
|
||||||
type=Qt.DirectConnection)
|
|
||||||
self.line_edit.focus_out.connect(self.focus_out,
|
|
||||||
type=Qt.DirectConnection)
|
|
||||||
self.activated[str].connect(self.saved_search_selected)
|
self.activated[str].connect(self.saved_search_selected)
|
||||||
|
|
||||||
completer = QCompleter(self) # turn off auto-completion
|
# Turn off auto-completion so that it doesn't interfere with typing
|
||||||
|
# names of new searches.
|
||||||
|
completer = QCompleter(self)
|
||||||
self.setCompleter(completer)
|
self.setCompleter(completer)
|
||||||
|
|
||||||
self.setEditable(True)
|
self.setEditable(True)
|
||||||
self.help_state = True
|
|
||||||
self.prev_search = ''
|
|
||||||
self.setInsertPolicy(self.NoInsert)
|
self.setInsertPolicy(self.NoInsert)
|
||||||
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
|
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.setMinimumContentsLength(10)
|
self.setMinimumContentsLength(10)
|
||||||
@ -269,50 +236,40 @@ class SavedSearchBox(QComboBox):
|
|||||||
|
|
||||||
def initialize(self, _search_box, colorize=False, help_text=_('Search')):
|
def initialize(self, _search_box, colorize=False, help_text=_('Search')):
|
||||||
self.search_box = _search_box
|
self.search_box = _search_box
|
||||||
self.help_text = help_text
|
self.line_edit.setPlaceholderText(help_text)
|
||||||
self.colorize = colorize
|
self.colorize = colorize
|
||||||
self.clear_to_help()
|
self.clear()
|
||||||
|
|
||||||
def normalize_state(self):
|
def normalize_state(self):
|
||||||
self.setEditText('')
|
# need this because line_edit will call it in some cases such as paste
|
||||||
self.line_edit.setStyleSheet(
|
pass
|
||||||
'QLineEdit { color: black; background-color: %s; }' %
|
|
||||||
self.normal_background)
|
|
||||||
self.help_state = False
|
|
||||||
|
|
||||||
def clear_to_help(self):
|
def clear(self):
|
||||||
self.setToolTip(self.tool_tip_text)
|
QComboBox.clear(self)
|
||||||
self.initialize_saved_search_names()
|
self.initialize_saved_search_names()
|
||||||
self.setEditText(self.help_text)
|
self.setEditText('')
|
||||||
self.line_edit.home(False)
|
self.line_edit.home(False)
|
||||||
self.help_state = True
|
|
||||||
self.line_edit.setStyleSheet(
|
|
||||||
'QLineEdit { color: gray; background-color: %s; }' %
|
|
||||||
self.normal_background)
|
|
||||||
|
|
||||||
def focus_out(self, event):
|
|
||||||
if self.currentText() == '':
|
|
||||||
self.clear_to_help()
|
|
||||||
|
|
||||||
def key_pressed(self, event):
|
def key_pressed(self, event):
|
||||||
if self.help_state:
|
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||||
self.normalize_state()
|
self.saved_search_selected(self.currentText())
|
||||||
|
self.focus_to_library.emit()
|
||||||
def mouse_released(self, event):
|
|
||||||
if self.help_state:
|
|
||||||
self.normalize_state()
|
|
||||||
|
|
||||||
def saved_search_selected(self, qname):
|
def saved_search_selected(self, qname):
|
||||||
qname = unicode(qname)
|
qname = unicode(qname)
|
||||||
if qname is None or not qname.strip():
|
if qname is None or not qname.strip():
|
||||||
|
self.search_box.clear()
|
||||||
|
return
|
||||||
|
if not saved_searches().lookup(qname):
|
||||||
|
self.search_box.clear()
|
||||||
|
self.setEditText(qname)
|
||||||
return
|
return
|
||||||
self.normalize_state()
|
|
||||||
self.search_box.set_search_string(u'search:"%s"' % qname)
|
self.search_box.set_search_string(u'search:"%s"' % qname)
|
||||||
self.setEditText(qname)
|
self.setEditText(qname)
|
||||||
self.setToolTip(saved_searches().lookup(qname))
|
self.setToolTip(saved_searches().lookup(qname))
|
||||||
|
self.focus_to_library.emit()
|
||||||
|
|
||||||
def initialize_saved_search_names(self):
|
def initialize_saved_search_names(self):
|
||||||
self.clear()
|
|
||||||
qnames = saved_searches().names()
|
qnames = saved_searches().names()
|
||||||
self.addItems(qnames)
|
self.addItems(qnames)
|
||||||
self.setCurrentIndex(-1)
|
self.setCurrentIndex(-1)
|
||||||
@ -330,25 +287,24 @@ class SavedSearchBox(QComboBox):
|
|||||||
if ss is None:
|
if ss is None:
|
||||||
return
|
return
|
||||||
saved_searches().delete(unicode(self.currentText()))
|
saved_searches().delete(unicode(self.currentText()))
|
||||||
self.clear_to_help()
|
self.clear()
|
||||||
self.search_box.clear_to_help()
|
self.search_box.clear()
|
||||||
self.emit(SIGNAL('changed()'))
|
self.changed.emit()
|
||||||
|
|
||||||
# SIGNALed from the main UI
|
# SIGNALed from the main UI
|
||||||
def save_search_button_clicked(self):
|
def save_search_button_clicked(self):
|
||||||
name = unicode(self.currentText())
|
name = unicode(self.currentText())
|
||||||
if self.help_state or not name.strip():
|
if not name.strip():
|
||||||
name = unicode(self.search_box.text()).replace('"', '')
|
name = unicode(self.search_box.text()).replace('"', '')
|
||||||
saved_searches().delete(name)
|
saved_searches().delete(name)
|
||||||
saved_searches().add(name, unicode(self.search_box.text()))
|
saved_searches().add(name, unicode(self.search_box.text()))
|
||||||
# now go through an initialization cycle to ensure that the combobox has
|
# now go through an initialization cycle to ensure that the combobox has
|
||||||
# the new search in it, that it is selected, and that the search box
|
# the new search in it, that it is selected, and that the search box
|
||||||
# references the new search instead of the text in the search.
|
# references the new search instead of the text in the search.
|
||||||
self.clear_to_help()
|
self.clear()
|
||||||
self.normalize_state()
|
|
||||||
self.setCurrentIndex(self.findText(name))
|
self.setCurrentIndex(self.findText(name))
|
||||||
self.saved_search_selected (name)
|
self.saved_search_selected (name)
|
||||||
self.emit(SIGNAL('changed()'))
|
self.changed.emit()
|
||||||
|
|
||||||
# SIGNALed from the main UI
|
# SIGNALed from the main UI
|
||||||
def copy_search_button_clicked (self):
|
def copy_search_button_clicked (self):
|
||||||
@ -362,11 +318,11 @@ class SearchBoxMixin(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.search.initialize('main_search_history', colorize=True,
|
self.search.initialize('main_search_history', colorize=True,
|
||||||
help_text=_('Search (For Advanced Search click the button to the left)'))
|
help_text=_('Search (For Advanced Search click the button to the left)'))
|
||||||
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
|
self.search.cleared.connect(self.search_box_cleared)
|
||||||
self.connect(self.search, SIGNAL('changed()'), self.search_box_changed)
|
self.search.changed.connect(self.search_box_changed)
|
||||||
self.connect(self.clear_button, SIGNAL('clicked()'), self.search.clear)
|
self.search.focus_to_library.connect(self.focus_to_library)
|
||||||
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'),
|
self.clear_button.clicked.connect(self.search.clear_clicked)
|
||||||
self.do_advanced_search)
|
self.advanced_search_button.clicked[bool].connect(self.do_advanced_search)
|
||||||
|
|
||||||
self.search.clear()
|
self.search.clear()
|
||||||
self.search.setMaximumWidth(self.width()-150)
|
self.search.setMaximumWidth(self.width()-150)
|
||||||
@ -384,11 +340,11 @@ class SearchBoxMixin(object):
|
|||||||
|
|
||||||
def search_box_cleared(self):
|
def search_box_cleared(self):
|
||||||
self.tags_view.clear()
|
self.tags_view.clear()
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
self.set_number_of_books_shown()
|
self.set_number_of_books_shown()
|
||||||
|
|
||||||
def search_box_changed(self):
|
def search_box_changed(self):
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
self.tags_view.clear()
|
self.tags_view.clear()
|
||||||
|
|
||||||
def do_advanced_search(self, *args):
|
def do_advanced_search(self, *args):
|
||||||
@ -396,20 +352,24 @@ class SearchBoxMixin(object):
|
|||||||
if d.exec_() == QDialog.Accepted:
|
if d.exec_() == QDialog.Accepted:
|
||||||
self.search.set_search_string(d.search_string())
|
self.search.set_search_string(d.search_string())
|
||||||
|
|
||||||
|
def focus_to_library(self):
|
||||||
|
self.current_view().setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
class SavedSearchBoxMixin(object):
|
class SavedSearchBoxMixin(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed)
|
self.saved_search.changed.connect(self.saved_searches_changed)
|
||||||
|
self.clear_button.clicked.connect(self.saved_search.clear)
|
||||||
|
self.saved_search.focus_to_library.connect(self.focus_to_library)
|
||||||
|
self.save_search_button.clicked.connect(
|
||||||
|
self.saved_search.save_search_button_clicked)
|
||||||
|
self.delete_search_button.clicked.connect(
|
||||||
|
self.saved_search.delete_search_button_clicked)
|
||||||
|
self.copy_search_button.clicked.connect(
|
||||||
|
self.saved_search.copy_search_button_clicked)
|
||||||
self.saved_searches_changed()
|
self.saved_searches_changed()
|
||||||
self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help)
|
|
||||||
self.saved_search.initialize(self.search, colorize=True,
|
self.saved_search.initialize(self.search, colorize=True,
|
||||||
help_text=_('Saved Searches'))
|
help_text=_('Saved Searches'))
|
||||||
self.connect(self.save_search_button, SIGNAL('clicked()'),
|
|
||||||
self.saved_search.save_search_button_clicked)
|
|
||||||
self.connect(self.delete_search_button, SIGNAL('clicked()'),
|
|
||||||
self.saved_search.delete_search_button_clicked)
|
|
||||||
self.connect(self.copy_search_button, SIGNAL('clicked()'),
|
|
||||||
self.saved_search.copy_search_button_clicked)
|
|
||||||
self.saved_search.setToolTip(
|
self.saved_search.setToolTip(
|
||||||
_('Choose saved search or enter name for new saved search'))
|
_('Choose saved search or enter name for new saved search'))
|
||||||
self.saved_search.setStatusTip(self.saved_search.toolTip())
|
self.saved_search.setStatusTip(self.saved_search.toolTip())
|
||||||
@ -420,7 +380,8 @@ class SavedSearchBoxMixin(object):
|
|||||||
def saved_searches_changed(self):
|
def saved_searches_changed(self):
|
||||||
p = sorted(saved_searches().names(), cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
p = sorted(saved_searches().names(), cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
||||||
t = unicode(self.search_restriction.currentText())
|
t = unicode(self.search_restriction.currentText())
|
||||||
self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches
|
# rebuild the restrictions combobox using current saved searches
|
||||||
|
self.search_restriction.clear()
|
||||||
self.search_restriction.addItem('')
|
self.search_restriction.addItem('')
|
||||||
self.tags_view.recount()
|
self.tags_view.recount()
|
||||||
for s in p:
|
for s in p:
|
||||||
@ -433,6 +394,8 @@ class SavedSearchBoxMixin(object):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
self.saved_searches_changed()
|
self.saved_searches_changed()
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
|
|
||||||
|
def focus_to_library(self):
|
||||||
|
self.current_view().setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ class SearchRestrictionMixin(object):
|
|||||||
restriction = ''
|
restriction = ''
|
||||||
self.restriction_count_of_books_in_view = \
|
self.restriction_count_of_books_in_view = \
|
||||||
self.library_view.model().set_search_restriction(restriction)
|
self.library_view.model().set_search_restriction(restriction)
|
||||||
self.search.clear_to_help()
|
self.search.clear()
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
self.tags_view.set_search_restriction(restriction)
|
self.tags_view.set_search_restriction(restriction)
|
||||||
self.set_number_of_books_shown()
|
self.set_number_of_books_shown()
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
author_sort_edit = pyqtSignal(object, object)
|
author_sort_edit = pyqtSignal(object, object)
|
||||||
tag_item_renamed = pyqtSignal()
|
tag_item_renamed = pyqtSignal()
|
||||||
search_item_renamed = pyqtSignal()
|
search_item_renamed = pyqtSignal()
|
||||||
drag_drop_finished = pyqtSignal(object)
|
drag_drop_finished = pyqtSignal(object, object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QTreeView.__init__(self, parent=None)
|
QTreeView.__init__(self, parent=None)
|
||||||
@ -252,6 +252,28 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.context_menu.popup(self.mapToGlobal(point))
|
self.context_menu.popup(self.mapToGlobal(point))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def dragMoveEvent(self, event):
|
||||||
|
QTreeView.dragMoveEvent(self, event)
|
||||||
|
self.setDropIndicatorShown(False)
|
||||||
|
index = self.indexAt(event.pos())
|
||||||
|
if not index.isValid():
|
||||||
|
return
|
||||||
|
item = index.internalPointer()
|
||||||
|
flags = self._model.flags(index)
|
||||||
|
if item.type == TagTreeItem.TAG and flags & Qt.ItemIsDropEnabled:
|
||||||
|
self.setDropIndicatorShown(True)
|
||||||
|
else:
|
||||||
|
if item.type == TagTreeItem.CATEGORY:
|
||||||
|
fm_dest = self.db.metadata_for_field(item.category_key)
|
||||||
|
if fm_dest['kind'] == 'user':
|
||||||
|
md = event.mimeData()
|
||||||
|
fm_src = self.db.metadata_for_field(md.column_name)
|
||||||
|
if md.column_name in ['authors', 'publisher', 'series'] or \
|
||||||
|
(fm_src['is_custom'] and
|
||||||
|
fm_src['datatype'] in ['series', 'text'] and
|
||||||
|
not fm_src['is_multiple']):
|
||||||
|
self.setDropIndicatorShown(True)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
if self.model():
|
if self.model():
|
||||||
self.model().clear_state()
|
self.model().clear_state()
|
||||||
@ -448,8 +470,59 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
ids = list(map(int, str(md.data(mime)).split()))
|
ids = list(map(int, str(md.data(mime)).split()))
|
||||||
self.handle_drop(node, ids)
|
self.handle_drop(node, ids)
|
||||||
return True
|
return True
|
||||||
|
elif node.type == TagTreeItem.CATEGORY:
|
||||||
|
fm_dest = self.db.metadata_for_field(node.category_key)
|
||||||
|
if fm_dest['kind'] == 'user':
|
||||||
|
fm_src = self.db.metadata_for_field(md.column_name)
|
||||||
|
if md.column_name in ['authors', 'publisher', 'series'] or \
|
||||||
|
(fm_src['is_custom'] and
|
||||||
|
fm_src['datatype'] in ['series', 'text'] and
|
||||||
|
not fm_src['is_multiple']):
|
||||||
|
mime = 'application/calibre+from_library'
|
||||||
|
ids = list(map(int, str(md.data(mime)).split()))
|
||||||
|
self.handle_user_category_drop(node, ids, md.column_name)
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def handle_user_category_drop(self, on_node, ids, column):
|
||||||
|
categories = self.db.prefs.get('user_categories', {})
|
||||||
|
category = categories.get(on_node.category_key[:-1], None)
|
||||||
|
if category is None:
|
||||||
|
return
|
||||||
|
fm_src = self.db.metadata_for_field(column)
|
||||||
|
for id in ids:
|
||||||
|
vmap = {}
|
||||||
|
label = fm_src['label']
|
||||||
|
if not fm_src['is_custom']:
|
||||||
|
if label == 'authors':
|
||||||
|
items = self.db.get_authors_with_ids()
|
||||||
|
items = [(i[0], i[1].replace('|', ',')) for i in items]
|
||||||
|
value = self.db.authors(id, index_is_id=True)
|
||||||
|
value = [v.replace('|', ',') for v in value.split(',')]
|
||||||
|
elif label == 'publisher':
|
||||||
|
items = self.db.get_publishers_with_ids()
|
||||||
|
value = self.db.publisher(id, index_is_id=True)
|
||||||
|
elif label == 'series':
|
||||||
|
items = self.db.get_series_with_ids()
|
||||||
|
value = self.db.series(id, index_is_id=True)
|
||||||
|
else:
|
||||||
|
items = self.db.get_custom_items_with_ids(label=label)
|
||||||
|
value = self.db.get_custom(id, label=label, index_is_id=True)
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
if not isinstance(value, list):
|
||||||
|
value = [value]
|
||||||
|
for v in items:
|
||||||
|
vmap[v[1]] = v[0]
|
||||||
|
for val in value:
|
||||||
|
for (v, c, id) in category:
|
||||||
|
if v == val and c == column:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
category.append([val, column, vmap[val]])
|
||||||
|
categories[on_node.category_key[:-1]] = category
|
||||||
|
self.db.prefs.set('user_categories', categories)
|
||||||
|
self.drag_drop_finished.emit(None, True)
|
||||||
|
|
||||||
def handle_drop(self, on_node, ids):
|
def handle_drop(self, on_node, ids):
|
||||||
#print 'Dropped ids:', ids, on_node.tag
|
#print 'Dropped ids:', ids, on_node.tag
|
||||||
@ -499,7 +572,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
self.db.set_metadata(id, mi, set_title=False,
|
self.db.set_metadata(id, mi, set_title=False,
|
||||||
set_authors=set_authors, commit=False)
|
set_authors=set_authors, commit=False)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
self.drag_drop_finished.emit(ids)
|
self.drag_drop_finished.emit(ids, False)
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
def set_search_restriction(self, s):
|
||||||
self.search_restriction = s
|
self.search_restriction = s
|
||||||
@ -641,6 +714,8 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
(fm['is_custom'] and \
|
(fm['is_custom'] and \
|
||||||
fm['datatype'] in ['text', 'rating', 'series']):
|
fm['datatype'] in ['text', 'rating', 'series']):
|
||||||
ans |= Qt.ItemIsDropEnabled
|
ans |= Qt.ItemIsDropEnabled
|
||||||
|
else:
|
||||||
|
ans |= Qt.ItemIsDropEnabled
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def supportedDropActions(self):
|
def supportedDropActions(self):
|
||||||
@ -768,7 +843,7 @@ class TagBrowserMixin(object): # {{{
|
|||||||
self.tags_view.set_database(self.library_view.model().db,
|
self.tags_view.set_database(self.library_view.model().db,
|
||||||
self.tag_match, self.sort_by)
|
self.tag_match, self.sort_by)
|
||||||
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
||||||
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
|
self.tags_view.tags_marked.connect(self.saved_search.clear)
|
||||||
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
||||||
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
||||||
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
||||||
@ -835,14 +910,14 @@ class TagBrowserMixin(object): # {{{
|
|||||||
self.library_view.model().refresh()
|
self.library_view.model().refresh()
|
||||||
self.tags_view.set_new_model()
|
self.tags_view.set_new_model()
|
||||||
self.tags_view.recount()
|
self.tags_view.recount()
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
self.search.clear_to_help()
|
self.search.clear()
|
||||||
|
|
||||||
def do_tag_item_renamed(self):
|
def do_tag_item_renamed(self):
|
||||||
# Clean up library view and search
|
# Clean up library view and search
|
||||||
self.library_view.model().refresh()
|
self.library_view.model().refresh()
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
self.search.clear_to_help()
|
self.search.clear()
|
||||||
|
|
||||||
def do_author_sort_edit(self, parent, id):
|
def do_author_sort_edit(self, parent, id):
|
||||||
db = self.library_view.model().db
|
db = self.library_view.model().db
|
||||||
@ -857,7 +932,10 @@ class TagBrowserMixin(object): # {{{
|
|||||||
self.library_view.model().refresh()
|
self.library_view.model().refresh()
|
||||||
self.tags_view.recount()
|
self.tags_view.recount()
|
||||||
|
|
||||||
def drag_drop_finished(self, ids):
|
def drag_drop_finished(self, ids, is_category):
|
||||||
|
if is_category:
|
||||||
|
self.tags_view.recount()
|
||||||
|
else:
|
||||||
self.library_view.model().refresh_ids(ids)
|
self.library_view.model().refresh_ids(ids)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -245,11 +245,11 @@ def fetch_scheduled_recipe(arg):
|
|||||||
|
|
||||||
return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
|
return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
|
||||||
|
|
||||||
def generate_catalog(parent, dbspec, ids, device_manager):
|
def generate_catalog(parent, dbspec, ids, device_manager, db):
|
||||||
from calibre.gui2.dialogs.catalog import Catalog
|
from calibre.gui2.dialogs.catalog import Catalog
|
||||||
|
|
||||||
# Build the Catalog dialog in gui2.dialogs.catalog
|
# Build the Catalog dialog in gui2.dialogs.catalog
|
||||||
d = Catalog(parent, dbspec, ids)
|
d = Catalog(parent, dbspec, ids, db)
|
||||||
|
|
||||||
if d.exec_() != d.Accepted:
|
if d.exec_() != d.Accepted:
|
||||||
return None
|
return None
|
||||||
|
@ -383,8 +383,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.tags_view.set_database(db, self.tag_match, self.sort_by)
|
self.tags_view.set_database(db, self.tag_match, self.sort_by)
|
||||||
self.library_view.model().set_book_on_device_func(self.book_on_device)
|
self.library_view.model().set_book_on_device_func(self.book_on_device)
|
||||||
self.status_bar.clear_message()
|
self.status_bar.clear_message()
|
||||||
self.search.clear_to_help()
|
self.search.clear()
|
||||||
self.saved_search.clear_to_help()
|
self.saved_search.clear()
|
||||||
self.book_details.reset_info()
|
self.book_details.reset_info()
|
||||||
self.library_view.model().count_changed()
|
self.library_view.model().count_changed()
|
||||||
prefs['library_path'] = self.library_path
|
prefs['library_path'] = self.library_path
|
||||||
|
@ -237,9 +237,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.connect(self.action_previous_page, SIGNAL('triggered(bool)'),
|
self.connect(self.action_previous_page, SIGNAL('triggered(bool)'),
|
||||||
lambda x:self.view.previous_page())
|
lambda x:self.view.previous_page())
|
||||||
self.connect(self.action_find_next, SIGNAL('triggered(bool)'),
|
self.connect(self.action_find_next, SIGNAL('triggered(bool)'),
|
||||||
lambda x:self.find(self.search.smart_text, repeat=True))
|
lambda x:self.find(unicode(self.search.text()), repeat=True))
|
||||||
self.connect(self.action_find_previous, SIGNAL('triggered(bool)'),
|
self.connect(self.action_find_previous, SIGNAL('triggered(bool)'),
|
||||||
lambda x:self.find(self.search.smart_text,
|
lambda x:self.find(unicode(self.search.text()),
|
||||||
repeat=True, backwards=True))
|
repeat=True, backwards=True))
|
||||||
|
|
||||||
self.connect(self.action_full_screen, SIGNAL('triggered(bool)'),
|
self.connect(self.action_full_screen, SIGNAL('triggered(bool)'),
|
||||||
|
@ -898,8 +898,8 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__plugin = plugin
|
self.__plugin = plugin
|
||||||
self.__progressInt = 0.0
|
self.__progressInt = 0.0
|
||||||
self.__progressString = ''
|
self.__progressString = ''
|
||||||
self.__read_book_marker = {'field':opts.read_book_marker.split(':')[0],
|
f, _, p = opts.read_book_marker.partition(':')
|
||||||
'pattern':opts.read_book_marker.split(':')[1]}
|
self.__read_book_marker = {'field':f, 'pattern':p}
|
||||||
self.__reporter = report_progress
|
self.__reporter = report_progress
|
||||||
self.__stylesheet = stylesheet
|
self.__stylesheet = stylesheet
|
||||||
self.__thumbs = None
|
self.__thumbs = None
|
||||||
@ -1211,7 +1211,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
def READING_SYMBOL(self):
|
def READING_SYMBOL(self):
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return '<span style="color:black">▷</span>' if self.generateForKindle else \
|
return '<span style="color:black">▷</span>' if self.generateForKindle else \
|
||||||
'<span style="color:white">%s</span>' % self.opts.read_tag
|
'<span style="color:white">+</span>'
|
||||||
return property(fget=fget)
|
return property(fget=fget)
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def READ_SYMBOL(self):
|
def READ_SYMBOL(self):
|
||||||
@ -1402,7 +1402,6 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
if record['cover']:
|
if record['cover']:
|
||||||
this_title['cover'] = re.sub('&', '&', record['cover'])
|
this_title['cover'] = re.sub('&', '&', record['cover'])
|
||||||
|
|
||||||
# This may be updated in self.processSpecialTags()
|
|
||||||
this_title['read'] = self.discoverReadStatus(record)
|
this_title['read'] = self.discoverReadStatus(record)
|
||||||
|
|
||||||
if record['tags']:
|
if record['tags']:
|
||||||
@ -2684,7 +2683,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
|
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
|
||||||
ptc += 1
|
ptc += 1
|
||||||
else:
|
else:
|
||||||
if book['read']:
|
if book.get('read', False):
|
||||||
# check mark
|
# check mark
|
||||||
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
|
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
|
||||||
pBookTag['class'] = "read_book"
|
pBookTag['class'] = "read_book"
|
||||||
@ -4035,24 +4034,21 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
# Legacy handling of special 'read' tag
|
# Legacy handling of special 'read' tag
|
||||||
if self.__read_book_marker['field'] == 'tag':
|
field = self.__read_book_marker['field']
|
||||||
return self.__read_book_marker['pattern'] in record['tags']
|
pat = self.__read_book_marker['pattern']
|
||||||
|
if field == 'tag' and pat in record['tags']:
|
||||||
|
return True
|
||||||
|
|
||||||
# Custom fields
|
|
||||||
elif self.__read_book_marker['field'].startswith('#'):
|
|
||||||
field_contents = self.__db.get_field(record['id'],
|
field_contents = self.__db.get_field(record['id'],
|
||||||
self.__read_book_marker['field'],
|
field,
|
||||||
index_is_id=True)
|
index_is_id=True)
|
||||||
if field_contents == '':
|
if field_contents:
|
||||||
field_contents = None
|
if re.search(pat, unicode(field_contents),
|
||||||
|
re.IGNORECASE) is not None:
|
||||||
if field_contents is not None:
|
|
||||||
if re.match(self.__read_book_marker['pattern'],str(field_contents), re.IGNORECASE):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def filterDbTags(self, tags):
|
def filterDbTags(self, tags):
|
||||||
# Remove the special marker tags from the database's tag list,
|
# Remove the special marker tags from the database's tag list,
|
||||||
# return sorted list of normalized genre tags
|
# return sorted list of normalized genre tags
|
||||||
@ -4787,7 +4783,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
for key in keys:
|
for key in keys:
|
||||||
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
||||||
'exclude_genre','exclude_tags','note_tag','numbers_as_text',
|
'exclude_genre','exclude_tags','note_tag','numbers_as_text',
|
||||||
'output_profile','read_book_marker','read_tag',
|
'output_profile','read_book_marker',
|
||||||
'search_text','sort_by','sort_descriptions_by_author','sync',
|
'search_text','sort_by','sort_descriptions_by_author','sync',
|
||||||
'wishlist_tag']:
|
'wishlist_tag']:
|
||||||
build_log.append(" %s: %s" % (key, opts_dict[key]))
|
build_log.append(" %s: %s" % (key, opts_dict[key]))
|
||||||
|
@ -359,16 +359,16 @@ class BrowseServer(object):
|
|||||||
icon = 'blank.png'
|
icon = 'blank.png'
|
||||||
cats.append((meta['name'], category, icon))
|
cats.append((meta['name'], category, icon))
|
||||||
|
|
||||||
cats = [('<li><a title="{2} {0}" href="/browse/category/{1}"> </a>'
|
cats = [(u'<li><a title="{2} {0}" href="/browse/category/{1}"> </a>'
|
||||||
'<img src="{3}{src}" alt="{0}" />'
|
u'<img src="{3}{src}" alt="{0}" />'
|
||||||
'<span class="label">{0}</span>'
|
u'<span class="label">{0}</span>'
|
||||||
'</li>')
|
u'</li>')
|
||||||
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')),
|
.format(xml(x, True), xml(quote(y)), xml(_('Browse books by')),
|
||||||
self.opts.url_prefix, src='/browse/icon/'+z)
|
self.opts.url_prefix, src='/browse/icon/'+z)
|
||||||
for x, y, z in cats]
|
for x, y, z in cats]
|
||||||
|
|
||||||
main = '<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\
|
main = u'<div class="toplevel"><h3>{0}</h3><ul>{1}</ul></div>'\
|
||||||
.format(_('Choose a category to browse by:'), '\n\n'.join(cats))
|
.format(_('Choose a category to browse by:'), u'\n\n'.join(cats))
|
||||||
return self.browse_template('name').format(title='',
|
return self.browse_template('name').format(title='',
|
||||||
script='toplevel();', main=main)
|
script='toplevel();', main=main)
|
||||||
|
|
||||||
|
@ -104,6 +104,8 @@ 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
|
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
|
||||||
|
|
||||||
|
.. note:: Only available in calibre versions ``>= 0.7.32``.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from calibre.customize import InterfaceActionBase
|
from calibre.customize import InterfaceActionBase
|
||||||
|
@ -247,6 +247,18 @@ Also, ::
|
|||||||
|
|
||||||
must return ``CONFIG_SCSI_MULTI_LUN=y``. If you don't see either, you have to recompile your kernel with the correct settings.
|
must return ``CONFIG_SCSI_MULTI_LUN=y``. If you don't see either, you have to recompile your kernel with the correct settings.
|
||||||
|
|
||||||
|
My device is getting mounted read-only in linux, so |app| cannot connect to it?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
linux kernels mount devices read-only when their filesystems have errors. You can repair the filesystem with::
|
||||||
|
|
||||||
|
sudo fsck.vfat -y /dev/sdc
|
||||||
|
|
||||||
|
Replace /dev/sdc with the path to the device node of your device. You can find the device node of your device, which
|
||||||
|
will always be under /dev by examining the output of::
|
||||||
|
|
||||||
|
mount
|
||||||
|
|
||||||
|
|
||||||
Why does |app| not support collection on the Kindle or shelves on the Nook?
|
Why does |app| not support collection on the Kindle or shelves on the Nook?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -12,6 +12,7 @@ for using |app| is to first add books to the library from your hard disk.
|
|||||||
to its internal database. Once they are in the database, you can perform a various
|
to its internal database. Once they are in the database, you can perform a various
|
||||||
:ref:`actions` on them that include conversion from one format to another,
|
:ref:`actions` on them that include conversion from one format to another,
|
||||||
transfer to the reading device, viewing on your computer, editing metadata, including covers, etc.
|
transfer to the reading device, viewing on your computer, editing metadata, including covers, etc.
|
||||||
|
Note that |app| creates copies of the files you add to it, your original files are left untouched.
|
||||||
|
|
||||||
The interface is divided into various sections:
|
The interface is divided into various sections:
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ Actions
|
|||||||
.. image:: images/actions.png
|
.. image:: images/actions.png
|
||||||
:alt: The Actions Toolbar
|
:alt: The Actions Toolbar
|
||||||
|
|
||||||
The actions toolbar provides convenient shortcuts to commonly used actions. Most of the action buttons have little arrows next to them. By clicking the arrows, you can perform variations on the default action.
|
The actions toolbar provides convenient shortcuts to commonly used actions. Most of the action buttons have little arrows next to them. By clicking the arrows, you can perform variations on the default action. Please note that the actions toolbar will look slightly different depending on whether you have an ebook reader attached to your computer.
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
:depth: 1
|
:depth: 1
|
||||||
@ -39,91 +40,40 @@ Add books
|
|||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
.. |adbi| image:: images/add_books.png
|
.. |adbi| image:: images/add_books.png
|
||||||
|
|
||||||
|adbi| The :guilabel:`Add books` action has three variations, accessed by the arrow next to the button.
|
|adbi| The :guilabel:`Add books` action has five variations, accessed by the clicking the down arrow on the right side of the button.
|
||||||
|
|
||||||
|
|
||||||
1. **Add books from a single directory**: Opens a file chooser dialog and allows you to specify which books in a directory should be added. This action is *context sensitive*, i.e. it depends on which :ref:`catalog <catalogs>` you have selected. If you have selected the :guilabel:`Library`, books will be added to the library. If you have selected the ebook reader device, the books will be uploaded to the device, and so on.
|
1. **Add books from a single directory**: Opens a file chooser dialog and allows you to specify which books in a directory should be added. This action is *context sensitive*, i.e. it depends on which :ref:`catalog <catalogs>` you have selected. If you have selected the :guilabel:`Library`, books will be added to the library. If you have selected the ebook reader device, the books will be uploaded to the device, and so on.
|
||||||
|
|
||||||
2. **Add books recursively (One book per directory)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library.The algorithm assumes that each directory contains a single book. All ebook files in a directory are assumedto be the same book in different formats. This action is the inverse of the :ref:`Save to disk <save_to_disk_multiple>` action, i.e. you can :guilabel:`Save to disk`, delete the books and re-add them with no lost information (except date).
|
2. **Add books from directories, including sub-directories (One book per directory, assumes every ebook file is the same book in a different format)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library. The algorithm assumes that each directory contains a single book. All ebook files in a directory are assumedto be the same book in different formats. This action is the inverse of the :ref:`Save to disk <save_to_disk_multiple>` action, i.e. you can :guilabel:`Save to disk`, delete the books and re-add them with no lost information (except date).
|
||||||
|
|
||||||
3. **Add books recursively (Multiple books per directory)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library.The algorithm assumes that each directory contains many books. All ebook files with the same name in a directory are assumed to be the same book in different formats. This action is the inverse of the :ref:`Save to disk <save_to_disk_single>` action, i.e. you can :guilabel:`Save to disk`, delete the books and re-add them with no lost information (except date).
|
3. **Add books directories, including sub-directories (Multiple books per directory, assumes every ebook file is a different book)**: Allows you to choose a directory. The directory and all its sub-directories are scanned recursively and any ebooks found are added to the library. The algorithm assumes that each directory contains many books. All ebook files with the same name in a directory are assumed to be the same book in different formats. Ebooks with different names are added as different books. This action is the inverse of the :ref:`Save to disk <save_to_disk_single>` action, i.e. you can :guilabel:`Save to disk`, delete the books and re-add them with no lost information (except date).
|
||||||
|
|
||||||
|
4. **Add empty book. (Book Entry with blank formats)**: Allows you to create a blank book record. This can be used to then manually fill out the information about a book that you may not have yet in your collection.
|
||||||
|
|
||||||
The :guilabel:`Add books` action can read metadata from the following ebook formats: ``LRF, EPUB, LIT, MOBI, RTF, PDF, PRC, HTML``. In addition it tries to guess metadata from the filename. See the :ref:`config_filename_metadata` section, to learn how to configure this.
|
5. **Add by ISBN**: Allows you to add one or more books by entering just their ISBN into a list or pasting the list of ISBNs from your clipboard.
|
||||||
|
|
||||||
To add a new format to an existing book, use the :ref:`edit_meta_information` action.
|
The :guilabel:`Add books` action can read metadata from a wide variety of e-book formats. In addition it tries to guess metadata from the filename.
|
||||||
|
See the :ref:`config_filename_metadata` section, to learn how to configure this.
|
||||||
|
|
||||||
.. _remove_books:
|
To add an additional format for an existing book, use the :ref:`edit_meta_information` action.
|
||||||
|
|
||||||
Remove books
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
.. |rbi| image:: images/remove_books.png
|
|
||||||
|
|
||||||
|rbi| The :guilabel:`Remove books` action deletes books permanently, so use it with care. It is *context sensitive*, i.e. it depends on which :ref:`catalog <catalogs>` you have selected. If you have selected the :guilabel:`Library`, books will be removed from the library. If you have selected the ebook reader device, the books will be removed from the device. To remove only a particular format for a given book use the :ref:`edit_meta_information` action.
|
|
||||||
|
|
||||||
.. _edit_meta_information:
|
.. _edit_meta_information:
|
||||||
|
|
||||||
Edit meta information
|
Edit metadata
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
.. |emii| image:: images/edit_meta_information.png
|
.. |emii| image:: images/edit_meta_information.png
|
||||||
|
|
||||||
|emii| The :guilabel:`Edit meta information` action has two variations, accessed by the arrow next to the button.
|
|emii| The :guilabel:`Edit metadata` action has six variations, which can be accessed by clicking the down arrow on the right side of the button.
|
||||||
|
|
||||||
1. **Edit metadata individually**: This allows you to edit the metadata of books one-by-one, with the option of fetching metadata, including covers from the internet. It also allows you to add/remove particular ebook formats from a book. For more detail see :ref:`metadata`.
|
1. **Edit metadata individually**: This allows you to edit the metadata of books one-by-one, with the option of fetching metadata, including covers from the internet. It also allows you to add/remove particular ebook formats from a book. For more detail see :ref:`metadata`.
|
||||||
|
|
||||||
2. **Edit metadata in bulk**: This allows you to edit common metadata fields for large numbers of books simulataneously. It operates on all the books you have selected in the :ref:`Library view <search_sort>`.
|
2. **Edit metadata in bulk**: This allows you to edit common metadata fields for large numbers of books simulataneously. It operates on all the books you have selected in the :ref:`Library view <search_sort>`.
|
||||||
|
3. **Download metadata and covers**: Downloads metadata and covers (if available), for the books that are selected in the book list.
|
||||||
|
4. **Download only metadata**: Downloads only metadata (if available), for the books that are selected in the book list.
|
||||||
|
5. **Download only covers**: Downloads only covers (if available), for the books that are selected in the book list.
|
||||||
|
6. **Download only social metadata**: Downloads only social metadata such as tags and reviews (if available), for the books that are selected in the book list.
|
||||||
|
7. **Merge Book Records**: Gives you the capability of merging the metadata and formats of two or more book records together. You can choose to either delete or keep the records that were not clicked first.
|
||||||
|
|
||||||
.. _send_to_device:
|
|
||||||
|
|
||||||
Send to device
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
.. |stdi| image:: images/send_to_device.png
|
|
||||||
|
|
||||||
|stdi| The :guilabel:`Send to device` action has two variations, accessed by the arrow next to the button.
|
|
||||||
|
|
||||||
1. **Send to main memory**: The selected books are transferred to the main memory of the ebook reader.
|
|
||||||
2. **Send to card**: The selected books are transferred to the storage card on the ebook reader.
|
|
||||||
|
|
||||||
You can control the file name and folder structure of files sent to the device by setting up a template in
|
|
||||||
:guilabel:`Preferences->Import/Export->Sending books to devices`. Also see :ref:`templatelangcalibre`.
|
|
||||||
|
|
||||||
.. _save_to_disk:
|
|
||||||
|
|
||||||
Save to disk
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
.. |svdi| image:: images/save_to_disk.png
|
|
||||||
|
|
||||||
|svdi| The :guilabel:`Save to disk` action has two variations, accessed by the arrow next to the button.
|
|
||||||
|
|
||||||
.. _save_to_disk_multiple:
|
|
||||||
|
|
||||||
1. **Save to disk**: This will save the selected books to disk organized in directories. The directory structure looks like::
|
|
||||||
|
|
||||||
Author
|
|
||||||
Title
|
|
||||||
Book Files
|
|
||||||
|
|
||||||
.. _save_to_disk_single:
|
|
||||||
|
|
||||||
2. **Save to disk in a single directory**: The selected books are saved to disk in a single directory.
|
|
||||||
|
|
||||||
All available formats as well as metadata is stored to disk for each selected book. Metadata is stored in an OPF file.
|
|
||||||
|
|
||||||
Saved books can be re-imported to the library without any loss of information by using the :ref:`Add books <add_books>` action.
|
|
||||||
|
|
||||||
You can control the file name and folder structure of files saved to disk by setting up a template in
|
|
||||||
:guilabel:`Preferences->Import/Export->Saving books to disk`. Also see :ref:`templatelangcalibre`.
|
|
||||||
|
|
||||||
|
|
||||||
.. _fetch_news:
|
|
||||||
|
|
||||||
Fetch news
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
.. |fni| image:: images/fetch_news.png
|
|
||||||
|
|
||||||
|fni| The :guilabel:`Fetch news` action downloads news from various websites and converts it into an ebook that can be read on your ebook reader. Normally, the newly created ebook is added to your ebook library, but if an ebook reader is connected at the time the download finishes, the news is uploaded to the reader directly.
|
|
||||||
|
|
||||||
The :guilabel:`Fetch news` action uses simple recipes (10-15 lines of code) for each news site. To learn how to create recipes for your own news sources, see :ref:`news`.
|
|
||||||
|
|
||||||
.. _convert_ebooks:
|
.. _convert_ebooks:
|
||||||
|
|
||||||
@ -131,7 +81,10 @@ Convert e-books
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
.. |cei| image:: images/convert_ebooks.png
|
.. |cei| image:: images/convert_ebooks.png
|
||||||
|
|
||||||
|cei| Ebooks can be converted from a number of formats into the LRF format (for the SONY Reader). Note that ebooks you purchase will typically have `Digital Rights Management <http://bugs.calibre-ebook.com/wiki/DRM>`_ *(DRM)*. |app| will not convert these ebooks. For many DRM formats, it is easy to remove the DRM, but as this is illegal, you have to find tools to liberate your books yourself and then use |app| to convert them.
|
|cei| Ebooks can be converted from a number of formats into whatever format your e-book reader prefers.
|
||||||
|
Note that ebooks you purchase will typically have `Digital Rights Management <http://bugs.calibre-ebook.com/wiki/DRM>`_ *(DRM)*.
|
||||||
|
|app| will not convert these ebooks. For many DRM formats, it is easy to remove the DRM, but as this may be illegal,
|
||||||
|
you have to find tools to liberate your books yourself and then use |app| to convert them.
|
||||||
|
|
||||||
For most people, conversion should be a simple 1-click affair. But if you want to learn more about the conversion process, see :ref:`conversion`.
|
For most people, conversion should be a simple 1-click affair. But if you want to learn more about the conversion process, see :ref:`conversion`.
|
||||||
|
|
||||||
@ -141,7 +94,12 @@ The :guilabel:`Convert E-books` action has three variations, accessed by the arr
|
|||||||
|
|
||||||
2. **Bulk convert**: This allows you to specify options only once to convert a number of ebooks in bulk.
|
2. **Bulk convert**: This allows you to specify options only once to convert a number of ebooks in bulk.
|
||||||
|
|
||||||
3. **Create catalog**: This action allows you to generate a complete listing with all metadata of the books in your library, in several formats, like XML, CSV, EPUB and MOBI. The catalog will contain all the books showing in the library view currently, so you can use the search features to limit the books to be catalogued. In addition, if you select multiple books using the mouse, only those books will be added to the catalog. If you generate the catalog in an e-book format such as EPUB or MOBI, the next time you connect your e-book reader, the catalog will be automatically sent to the device. For details on how catalogs work, see `here <http://www.mobileread.com/forums/showthread.php?p=755468#post755468>`_.
|
3. **Create catalog**: This action allows you to generate a complete listing with all metadata of the books in your library,
|
||||||
|
in several formats, like XML, CSV, BiBTeX, EPUB and MOBI. The catalog will contain all the books showing in the library view currently,
|
||||||
|
so you can use the search features to limit the books to be catalogued. In addition, if you select multiple books using the mouse,
|
||||||
|
only those books will be added to the catalog. If you generate the catalog in an e-book format such as EPUB or MOBI,
|
||||||
|
the next time you connect your e-book reader, the catalog will be automatically sent to the device.
|
||||||
|
For details on how catalogs work, see `here <http://www.mobileread.com/forums/showthread.php?p=755468#post755468>`.
|
||||||
|
|
||||||
.. _view:
|
.. _view:
|
||||||
|
|
||||||
@ -149,7 +107,152 @@ View
|
|||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
.. |vi| image:: images/view.png
|
.. |vi| image:: images/view.png
|
||||||
|
|
||||||
|vi| The :guilabel:`View` action displays the book in an ebook viewer program. |app| has a builtin viewer for the LRF format. For other formats it uses the default operating system application. If a book has more than one format, you can view a particular format by clicking the arrow next to the :guilabel:`View` button.
|
|vi| The :guilabel:`View` action displays the book in an ebook viewer program. |app| has a builtin viewer for the most e-book formats.
|
||||||
|
For other formats it uses the default operating system application. You can configure which formats should open with the internal viewer via
|
||||||
|
Preferences->Behavior. If a book has more than one format, you can view a particular format by clicking the down arrow
|
||||||
|
on the right of the :guilabel:`View` button.
|
||||||
|
|
||||||
|
|
||||||
|
.. _send_to_device:
|
||||||
|
|
||||||
|
Send to device
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
.. |stdi| image:: images/send_to_device.png
|
||||||
|
|
||||||
|
|stdi| The :guilabel:`Send to device` action has eight variations, accessed by clicking the down arrow on the right of the button.
|
||||||
|
|
||||||
|
1. **Send to main memory**: The selected books are transferred to the main memory of the ebook reader.
|
||||||
|
2. **Send to card (A)**: The selected books are transferred to the storage card (A) on the ebook reader.
|
||||||
|
3. **Send to card (B)**: The selected books are transferred to the storage card (B) on the ebook reader.
|
||||||
|
4. **Send and delete from library>**: The selected books are transferred to the selected storage location on the device, and then **deleted** from the Library.
|
||||||
|
5. **Send Specific format>**: The selected books are transferred to the selected storage location on the device, in the format that you specify.
|
||||||
|
6. **Eject device**: The device is detached from |app|.
|
||||||
|
7. **Set default send to device action>**: This action allows you to Specify which of the option 1) through 6) above will be the default action when you click the main button.
|
||||||
|
8. **Fetch Annotations**: This is an experimental action which will transfer annotations you may have made on an ebook on your device, and add those annotations to the comments metadata of the book in the |app| library
|
||||||
|
|
||||||
|
You can control the file name and folder structure of files sent to the device by setting up a template in
|
||||||
|
:guilabel:`Preferences->Import/Export->Sending books to devices`. Also see :ref:`templatelangcalibre`.
|
||||||
|
|
||||||
|
.. _fetch_news:
|
||||||
|
|
||||||
|
Fetch news
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
.. |fni| image:: images/fetch_news.png
|
||||||
|
|
||||||
|
|fni| The :guilabel:`Fetch news` action downloads news from various websites and converts it into an ebook that can be read on your ebook reader. Normally, the newly created ebook is added to your ebook library, but if an ebook reader is connected at the time the download finishes, the news is also uploaded to the reader automatically.
|
||||||
|
|
||||||
|
The :guilabel:`Fetch news` action uses simple recipes (10-15 lines of code) for each news site. To learn how to create recipes for your own news sources, see :ref:`news`.
|
||||||
|
|
||||||
|
The :guilabel:`Fetch news` action has three variations, accessed by clicking the down arrow on the right of the button.
|
||||||
|
|
||||||
|
1. **Schedule news download**: This action allows you to schedule the download of of your selected news sources from a list of hundreds of available. Scheduling can be set individually for each news source you select and the scheduling is flexible allowing you to select specific days of the week or a frequency of days between downloads.
|
||||||
|
2. **Add a custom news service**: This action allows you to create a simple recipe for downloading news from a custom news site that you wish to access. Creating the recipe can be as simple as specifying an RSS news feed URL, or you can be more prescriptive by creating python based code for the task, see :ref:`news`.
|
||||||
|
3. **Download all scheduled news sources**: This action causes |app| to immediately begin to download all news sources that you have previously scheduled.
|
||||||
|
|
||||||
|
|
||||||
|
.. _library:
|
||||||
|
|
||||||
|
Library
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
.. |lii| image:: images/library.png
|
||||||
|
|
||||||
|
|lii| The :guilabel: `Library` action allows you to create, switch between, rename or delete a Library. |app| allows you to create as many libraries as you wish. You coudl for instance create a fiction library, a non fiction library, a foreign language library a project library, basically any structure that suits your needs. Libraries are the highest organizational structure within |app|, each library has its own set of books, tags, categories and base storage location.
|
||||||
|
|
||||||
|
1. **Switch\Create library..**: This action allows you to; a) connect to a pre-existing |app| library at another location from your currently open library, b) Create and empty library at a nw location or, c) Move the current Library to a newly specified location.
|
||||||
|
2. **Quick Switch>**: This action allows you to switch between libraries that have been registered or created within |app|.
|
||||||
|
3. **Rename Library>**: This action allows you to rename a Library.
|
||||||
|
4. **Delete Library>**: This action allows you to **permanenetly delete** a Library.
|
||||||
|
5. **<calibre library>**: Actions 5, 6 etc .. give you immediate switch access between multiple Libraries that you have created or attached to.
|
||||||
|
|
||||||
|
.. _device:
|
||||||
|
|
||||||
|
Device
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
.. |dvi| image:: images/device.png
|
||||||
|
|
||||||
|
|dvi| The :guilabel:`Device` action allows you to view the books in the main memory or storage cards of your device, or to eject the device (detach it from |app|).
|
||||||
|
This icon shows up automatically on the main |app| toolbar when you connect a supported device. You can click on it to see the books on your device. You can also drag and drop books from your |app| library onto the icon to transfer them to your device. Conversely, you can drag and drop books from your device onto the |app| icon on the toolbar to transfer books from your device to the |app| library.
|
||||||
|
|
||||||
|
|
||||||
|
.. _save_to_disk:
|
||||||
|
|
||||||
|
Save to disk
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
.. |svdi| image:: images/save_to_disk.png
|
||||||
|
|
||||||
|
|svdi| The :guilabel:`Save to disk` action has five variations, accessed by the arrow next to the button.
|
||||||
|
|
||||||
|
.. _save_to_disk_multiple:
|
||||||
|
|
||||||
|
1. **Save to disk**: This will save the selected books to disk organized in directories. The directory structure looks like::
|
||||||
|
|
||||||
|
Author_(sort)
|
||||||
|
Title
|
||||||
|
Book Files
|
||||||
|
|
||||||
|
You can control the file name and folder structure of files saved to disk by setting up a template in
|
||||||
|
:guilabel:`Preferences->Import/Export->Saving books to disk`. Also see :ref:`templatelangcalibre`.
|
||||||
|
|
||||||
|
.. _save_to_disk_single:
|
||||||
|
|
||||||
|
2. **Save to disk in a single directory**: The selected books are saved to disk in a single directory.
|
||||||
|
|
||||||
|
For 1. and 2. All available formats as well as metadata is stored to disk for each selected book. Metadata is stored in an OPF file.
|
||||||
|
|
||||||
|
Saved books can be re-imported to the library without any loss of information by using the :ref:`Add books <add_books>` action.
|
||||||
|
|
||||||
|
3. **Save only *<your preferred>* format to disk**: The selected books are saved to disk in the directory structure as shown in (1.) but only in your preferred ebook format you can set <your preferred> format in :guilabel:`Preferences->Behaviour->Preferred output format`
|
||||||
|
|
||||||
|
4. **Save only *<your preferred>* format to disk in a single directory**: The selected books are saved to disk in a single directory but only in <your preferred> ebook format you can set <your preferred> format in :guilabel:`Preferences->Behaviour->Preferred output format`
|
||||||
|
|
||||||
|
5. **Save single format to disk ..**: The selected books are saved to disk in the directory structure as shown in (1.) but only in the format you select from the pop-out list. There are currently 35 formats available and new ones are being added all the time.
|
||||||
|
|
||||||
|
.. _connect_share:
|
||||||
|
|
||||||
|
Connect/Share
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
.. |csi| image:: images/connect_share.png
|
||||||
|
|
||||||
|
|csi| The :guilabel:`Connect/Share` action allows you to manually connect to a device or folder on your computer, it also allows you to set up you |app| library for access via a web browser, or email.
|
||||||
|
|
||||||
|
The :guilabel:`Connect/Share` action has four variations, accessed by clicking the down arrow on the right of the button.
|
||||||
|
|
||||||
|
1. **Connect to folder**: This action allows you to connect to any folder on your computer as though it were a device and use all the facilities |app| has for devices with that folder. Useful if your device cannot be supported by |app| but is available as a USB disk.
|
||||||
|
|
||||||
|
2. **Connect to iTunes**: Allows you to connect to your iTunes books database as though it were a device. Once the books are sent to iTunes, you can then use iTunes to make them available on your various iDevices. Useful if you would rather not have |app| send books to your iDevice directly.
|
||||||
|
|
||||||
|
3. **Start Content Server**: This action causes |app| to start up its built-in web server. When this is started, your |app| library will be accessible via a web browser from the internet (if you choose). You can configure how the web server is accessed by setting preferences at :guilabel:`Preferences->Sharing->Sharing over the net`
|
||||||
|
|
||||||
|
4. **Setup email based sharing of books**: This action allows you to setup |app| to share books (and news feeds) by email. After setting up email addresses for this option |app| will send news updates and book updates to the entered email addresses. You can configure how the |app| sends email by setting preferences at :guilabel:`Preferences->Sharing->Sharing books by email`. Once you have setup one or more email addresses, this menu entry get replaced by menu entries to send books to the setup email addresses.
|
||||||
|
|
||||||
|
.. _remove_books:
|
||||||
|
|
||||||
|
Remove books
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
.. |rbi| image:: images/remove_books.png
|
||||||
|
|
||||||
|
|rbi| The :guilabel:`Remove books` action **deletes books permanently**, so use it with care. It is *context sensitive*, i.e. it depends on which :ref:`catalog <catalogs>` you have selected. If you have selected the :guilabel:`Library`, books will be removed from the library. If you have selected the ebook reader device, the books will be removed from the device. To remove only a particular format for a given book use the :ref:`edit_meta_information` action. Remove books also has five variations which can be accessed by clicking the down arrow on the right side of the button.
|
||||||
|
|
||||||
|
1. **Remove Selected Books**: Allows you to **permanently** remove all books that are selected in the book list.
|
||||||
|
|
||||||
|
2. **Remove files of a specified format from selected books..**: Allows you to **permanently** remove ebook files of a specified format, from books that are selected in the book list.
|
||||||
|
|
||||||
|
3. **Remove all files of a specified format, except..**: Allows you to **permanently** remove ebook files of a multiple formats except a given format, from books that are selected in the book list.
|
||||||
|
|
||||||
|
4. **Remove covers from selected books**: Allows you to **permanently** remove cover images files, from books that are selected in the book list.
|
||||||
|
|
||||||
|
5. **Remove matching books from device**: Allows you to remove ebook files from a connected device, that match the books that are selected in the book list.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Note that when you use Remove books to delete books from your |app| library, the book record is permanently deleted, but, on (Windows and OS X) the files are placed into the recycle bin, so you can recover them if you change your mind.
|
||||||
|
|
||||||
|
.. _configuration:
|
||||||
|
|
||||||
|
Preferences
|
||||||
|
---------------
|
||||||
|
.. |cbi| image:: images/preferences.png
|
||||||
|
|
||||||
|
The Preferences Action allows you to change the way various aspects of |app| work. To access it, click the |cbi|.
|
||||||
|
|
||||||
.. _catalogs:
|
.. _catalogs:
|
||||||
|
|
||||||
@ -157,13 +260,14 @@ Catalogs
|
|||||||
----------
|
----------
|
||||||
.. image:: images/catalogs.png
|
.. image:: images/catalogs.png
|
||||||
|
|
||||||
A *catalog* is a collection of books. |app| can manage three different catalogs:
|
A *catalog* is a collection of books. |app| can manage two types of different catalogs:
|
||||||
|
|
||||||
1. **Library**: This is a collection of books stored in a database file on your computers harddisk.
|
1. **Library**: This is a collection of books stored in your |app| library on your computer
|
||||||
|
|
||||||
2. **Reader**: This is a collection of books stored in the main memory of your ebook reader. It will be available when you connect the reader to your computer.
|
2. **Device**: This is a collection of books stored in the main memory of your ebook reader. It will be available when you connect the reader to your computer.
|
||||||
|
- In addition, you can see the books on the storage card (if any) in your reader device.
|
||||||
|
|
||||||
3. **Card**: This is a collection of books stored on the storage card in your reader.
|
Many operations, like Adding books, deleting, viewing, etc. are context sensitive. So, for example, if you click the View button when you have the **Device** catalog selected, |app| will open the files on the device to view. If you have the **Library** catalog selected, files in your |app| library will be opened instead.
|
||||||
|
|
||||||
.. _search_sort:
|
.. _search_sort:
|
||||||
|
|
||||||
@ -274,14 +378,6 @@ 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
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@ -289,14 +385,6 @@ Saving searches
|
|||||||
|
|
||||||
Now, you can access your saved search in the Tag Browser under "Searches". A single click will allow you to re-use any arbitrarily complex search easily, without needing to re-create it.
|
Now, you can access your saved search in the Tag Browser under "Searches". A single click will allow you to re-use any arbitrarily complex search easily, without needing to re-create it.
|
||||||
|
|
||||||
.. _configuration:
|
|
||||||
|
|
||||||
Preferences
|
|
||||||
---------------
|
|
||||||
The Preferences dialog allows you to change the way various aspects of |app| work. To access it, click the |cbi|.
|
|
||||||
|
|
||||||
.. |cbi| image:: images/configuration.png
|
|
||||||
|
|
||||||
.. _config_filename_metadata:
|
.. _config_filename_metadata:
|
||||||
|
|
||||||
Guessing metadata from file names
|
Guessing metadata from file names
|
||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
src/calibre/manual/images/auto_author_sort.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 13 KiB |
BIN
src/calibre/manual/images/connect_share.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
src/calibre/manual/images/device.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
src/calibre/manual/images/folder_device.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
src/calibre/manual/images/library.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
src/calibre/manual/images/preferences.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
src/calibre/manual/images/show_tag_editor.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/calibre/manual/images/swap_title_author.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.0 KiB |
@ -199,6 +199,11 @@ if not _run_once:
|
|||||||
|
|
||||||
__builtin__.__dict__['lopen'] = local_open
|
__builtin__.__dict__['lopen'] = local_open
|
||||||
|
|
||||||
|
|
||||||
|
import mimetypes
|
||||||
|
mimetypes.init([P('mime.types')])
|
||||||
|
guess_type = mimetypes.guess_type
|
||||||
|
|
||||||
def test_lopen():
|
def test_lopen():
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre import CurrentDir
|
from calibre import CurrentDir
|
||||||
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os, copy
|
import os, copy
|
||||||
|
|
||||||
from PyQt4.Qt import QAbstractItemModel, QVariant, Qt, QColor, QFont, QIcon, \
|
from PyQt4.Qt import QAbstractItemModel, QVariant, Qt, QColor, QFont, QIcon, \
|
||||||
QModelIndex, SIGNAL, QMetaObject, pyqtSlot
|
QModelIndex, QMetaObject, pyqtSlot, pyqtSignal
|
||||||
|
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.gui2 import NONE
|
from calibre.gui2 import NONE
|
||||||
@ -120,6 +120,7 @@ class NewsItem(NewsTreeItem):
|
|||||||
class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||||
|
|
||||||
LOCATIONS = ['all']
|
LOCATIONS = ['all']
|
||||||
|
searched = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, db, *args):
|
def __init__(self, db, *args):
|
||||||
QAbstractItemModel.__init__(self, *args)
|
QAbstractItemModel.__init__(self, *args)
|
||||||
@ -254,14 +255,17 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query):
|
||||||
|
results = []
|
||||||
try:
|
try:
|
||||||
results = self.parse(unicode(query))
|
query = unicode(query).strip()
|
||||||
|
if query:
|
||||||
|
results = self.parse(query)
|
||||||
if not results:
|
if not results:
|
||||||
results = None
|
results = None
|
||||||
except ParseException:
|
except ParseException:
|
||||||
results = []
|
results = []
|
||||||
self.do_refresh(restrict_to_urns=results)
|
self.do_refresh(restrict_to_urns=results)
|
||||||
self.emit(SIGNAL('searched(PyQt_PyObject)'), True)
|
self.searched.emit(True)
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
return 1
|
return 1
|
||||||
|