mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge from trunk
This commit is contained in:
commit
84b5978b71
161
resources/recipes/apple_daily.recipe
Normal file
161
resources/recipes/apple_daily.recipe
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AppleDaily(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'蘋果日報'
|
||||||
|
__author__ = u'蘋果日報'
|
||||||
|
__publisher__ = u'蘋果日報'
|
||||||
|
description = u'蘋果日報'
|
||||||
|
masthead_url = 'http://hk.apple.nextmedia.com/template/common/header/2009/images/atnextheader_logo_appledaily.gif'
|
||||||
|
language = 'zh_TW'
|
||||||
|
encoding = 'UTF-8'
|
||||||
|
timefmt = ' [%a, %d %b, %Y]'
|
||||||
|
needs_subscription = False
|
||||||
|
remove_javascript = True
|
||||||
|
remove_tags_before = dict(name=['ul', 'h1'])
|
||||||
|
remove_tags_after = dict(name='form')
|
||||||
|
remove_tags = [dict(attrs={'class':['articleTools', 'post-tools', 'side_tool', 'nextArticleLink clearfix']}),
|
||||||
|
dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index']),
|
||||||
|
dict(name=['script', 'noscript', 'style', 'form'])]
|
||||||
|
no_stylesheets = True
|
||||||
|
extra_css = '''
|
||||||
|
@font-face {font-family: "uming", serif, sans-serif; src: url(res:///usr/share/fonts/truetype/arphic/uming.ttc); }\n
|
||||||
|
body {margin-right: 8pt; font-family: 'uming', serif;}
|
||||||
|
h1 {font-family: 'uming', serif, sans-serif}
|
||||||
|
'''
|
||||||
|
#extra_css = 'h1 {font: sans-serif large;}\n.byline {font:monospace;}'
|
||||||
|
|
||||||
|
preprocess_regexps = [
|
||||||
|
(re.compile(r'img.php?server=(?P<server>[^&]+)&path=(?P<path>[^&]+).*', re.DOTALL|re.IGNORECASE),
|
||||||
|
lambda match: 'http://' + match.group('server') + '/' + match.group('path')),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_cover_url(self):
|
||||||
|
return 'http://hk.apple.nextmedia.com/template/common/header/2009/images/atnextheader_logo_appledaily.gif'
|
||||||
|
|
||||||
|
|
||||||
|
#def get_browser(self):
|
||||||
|
#br = BasicNewsRecipe.get_browser()
|
||||||
|
#if self.username is not None and self.password is not None:
|
||||||
|
# br.open('http://www.nytimes.com/auth/login')
|
||||||
|
# br.select_form(name='login')
|
||||||
|
# br['USERID'] = self.username
|
||||||
|
# br['PASSWORD'] = self.password
|
||||||
|
# br.submit()
|
||||||
|
#return br
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
#process all the images
|
||||||
|
for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')):
|
||||||
|
iurl = tag['src']
|
||||||
|
#print 'checking image: ' + iurl
|
||||||
|
|
||||||
|
#img\.php?server\=(?P<server>[^&]+)&path=(?P<path>[^&]+)
|
||||||
|
p = re.compile(r'img\.php\?server=(?P<server>[^&]+)&path=(?P<path>[^&]+)', re.DOTALL|re.IGNORECASE)
|
||||||
|
|
||||||
|
m = p.search(iurl)
|
||||||
|
|
||||||
|
if m is not None:
|
||||||
|
iurl = 'http://' + m.group('server') + '/' + m.group('path')
|
||||||
|
#print 'working! new url: ' + iurl
|
||||||
|
tag['src'] = iurl
|
||||||
|
#else:
|
||||||
|
#print 'not good'
|
||||||
|
|
||||||
|
for tag in soup.findAll(lambda tag: tag.name.lower()=='a' and tag.has_key('href')):
|
||||||
|
iurl = tag['href']
|
||||||
|
#print 'checking image: ' + iurl
|
||||||
|
|
||||||
|
#img\.php?server\=(?P<server>[^&]+)&path=(?P<path>[^&]+)
|
||||||
|
p = re.compile(r'img\.php\?server=(?P<server>[^&]+)&path=(?P<path>[^&]+)', re.DOTALL|re.IGNORECASE)
|
||||||
|
|
||||||
|
m = p.search(iurl)
|
||||||
|
|
||||||
|
if m is not None:
|
||||||
|
iurl = 'http://' + m.group('server') + '/' + m.group('path')
|
||||||
|
#print 'working! new url: ' + iurl
|
||||||
|
tag['href'] = iurl
|
||||||
|
#else:
|
||||||
|
#print 'not good'
|
||||||
|
|
||||||
|
return soup
|
||||||
|
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
base = 'http://news.hotpot.hk/fruit'
|
||||||
|
soup = self.index_to_soup('http://news.hotpot.hk/fruit/index.php')
|
||||||
|
|
||||||
|
#def feed_title(div):
|
||||||
|
# return ''.join(div.findAll(text=True, recursive=False)).strip()
|
||||||
|
|
||||||
|
articles = {}
|
||||||
|
key = None
|
||||||
|
ans = []
|
||||||
|
for div in soup.findAll('li'):
|
||||||
|
key = div.find(text=True, recursive=True);
|
||||||
|
#if key == u'豪情':
|
||||||
|
# continue;
|
||||||
|
|
||||||
|
print 'section=' + key
|
||||||
|
|
||||||
|
articles[key] = []
|
||||||
|
|
||||||
|
ans.append(key)
|
||||||
|
|
||||||
|
a = div.find('a', href=True)
|
||||||
|
|
||||||
|
if not a:
|
||||||
|
continue
|
||||||
|
|
||||||
|
url = base + '/' + a['href']
|
||||||
|
print 'url=' + url
|
||||||
|
|
||||||
|
if not articles.has_key(key):
|
||||||
|
articles[key] = []
|
||||||
|
else:
|
||||||
|
# sub page
|
||||||
|
subSoup = self.index_to_soup(url)
|
||||||
|
|
||||||
|
for subDiv in subSoup.findAll('li'):
|
||||||
|
subA = subDiv.find('a', href=True)
|
||||||
|
subTitle = subDiv.find(text=True, recursive=True)
|
||||||
|
subUrl = base + '/' + subA['href']
|
||||||
|
|
||||||
|
print 'subUrl' + subUrl
|
||||||
|
|
||||||
|
articles[key].append(
|
||||||
|
dict(title=subTitle,
|
||||||
|
url=subUrl,
|
||||||
|
date='',
|
||||||
|
description='',
|
||||||
|
content=''))
|
||||||
|
|
||||||
|
|
||||||
|
# elif div['class'] in ['story', 'story headline']:
|
||||||
|
# a = div.find('a', href=True)
|
||||||
|
# if not a:
|
||||||
|
# continue
|
||||||
|
# url = re.sub(r'\?.*', '', a['href'])
|
||||||
|
# url += '?pagewanted=all'
|
||||||
|
# title = self.tag_to_string(a, use_alt=True).strip()
|
||||||
|
# description = ''
|
||||||
|
# pubdate = strftime('%a, %d %b')
|
||||||
|
# summary = div.find(True, attrs={'class':'summary'})
|
||||||
|
# if summary:
|
||||||
|
# description = self.tag_to_string(summary, use_alt=False)
|
||||||
|
#
|
||||||
|
# feed = key if key is not None else 'Uncategorized'
|
||||||
|
# if not articles.has_key(feed):
|
||||||
|
# articles[feed] = []
|
||||||
|
# if not 'podcasts' in url:
|
||||||
|
# articles[feed].append(
|
||||||
|
# dict(title=title, url=url, date=pubdate,
|
||||||
|
# description=description,
|
||||||
|
# content=''))
|
||||||
|
# ans = self.sort_index_by(ans, {'The Front Page':-1, 'Dining In, Dining Out':1, 'Obituaries':2})
|
||||||
|
ans = [(unicode(key), articles[key]) for key in ans if articles.has_key(key)]
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
26
resources/recipes/workers_world.recipe
Normal file
26
resources/recipes/workers_world.recipe
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class WorkersWorld(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = u'Workers World'
|
||||||
|
description = u'Socialist news and analysis'
|
||||||
|
__author__ = u'urslnx'
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
remove_javascript = True
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
encoding = 'utf8'
|
||||||
|
publisher = 'workers.org'
|
||||||
|
category = 'news, politics, USA, world'
|
||||||
|
language = 'en'
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
extra_css = ' body{ font-family: Verdana,Arial,Helvetica,sans-serif; } h1{ font-size: x-large; text-align: left; margin-top:0.5em; margin-bottom:0.25em; } h2{ font-size: large; } p{ text-align: left; } .published{ font-size: small; } .byline{ font-size: small; } .copyright{ font-size: small; } '
|
||||||
|
remove_tags_before = dict(name='div', attrs={'id':'evernote'})
|
||||||
|
remove_tags_after = dict(name='div', attrs={'id':'footer'})
|
||||||
|
|
||||||
|
masthead_url='http://www.workers.org/graphics/wwlogo300.gif'
|
||||||
|
cover_url = 'http://www.workers.org/pdf/current.jpg'
|
||||||
|
feeds = [(u'Headlines', u'http://www.workers.org/rss/nonstandard_rss.xml'),
|
||||||
|
]
|
||||||
|
|
@ -90,6 +90,11 @@ class Plugin(object): # {{{
|
|||||||
an optional method validate() that takes no arguments and is called
|
an optional method validate() that takes no arguments and is called
|
||||||
immediately after the user clicks OK. Changes are applied if and only
|
immediately after the user clicks OK. Changes are applied if and only
|
||||||
if the method returns True.
|
if the method returns True.
|
||||||
|
|
||||||
|
If for some reason you cannot perform the configuration at this time,
|
||||||
|
return a tuple of two strings (message, details), these will be
|
||||||
|
displayed as a warning dialog to the user and the process will be
|
||||||
|
aborted.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -133,6 +138,12 @@ class Plugin(object): # {{{
|
|||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
config_widget = None
|
config_widget = None
|
||||||
|
|
||||||
|
if isinstance(config_widget, tuple):
|
||||||
|
from calibre.gui2 import warning_dialog
|
||||||
|
warning_dialog(parent, _('Cannot configure'), config_widget[0],
|
||||||
|
det_msg=config_widget[1], show=True)
|
||||||
|
return False
|
||||||
|
|
||||||
if config_widget is not None:
|
if config_widget is not None:
|
||||||
v.addWidget(config_widget)
|
v.addWidget(config_widget)
|
||||||
v.addWidget(button_box)
|
v.addWidget(button_box)
|
||||||
|
@ -19,7 +19,7 @@ class ANDROID(USBMS):
|
|||||||
|
|
||||||
VENDOR_ID = {
|
VENDOR_ID = {
|
||||||
# HTC
|
# HTC
|
||||||
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226],
|
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226, 0x222],
|
||||||
0x0c01 : [0x100, 0x0227, 0x0226],
|
0x0c01 : [0x100, 0x0227, 0x0226],
|
||||||
0x0ff9 : [0x0100, 0x0227, 0x0226],
|
0x0ff9 : [0x0100, 0x0227, 0x0226],
|
||||||
0x0c87 : [0x0100, 0x0227, 0x0226],
|
0x0c87 : [0x0100, 0x0227, 0x0226],
|
||||||
|
@ -39,6 +39,7 @@ if iswindows:
|
|||||||
class DriverBase(DeviceConfig, DevicePlugin):
|
class DriverBase(DeviceConfig, DevicePlugin):
|
||||||
# Needed for config_widget to work
|
# Needed for config_widget to work
|
||||||
FORMATS = ['epub', 'pdf']
|
FORMATS = ['epub', 'pdf']
|
||||||
|
USER_CAN_ADD_NEW_FORMATS = False
|
||||||
SUPPORTS_SUB_DIRS = True # To enable second checkbox in customize widget
|
SUPPORTS_SUB_DIRS = True # To enable second checkbox in customize widget
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -32,6 +32,7 @@ class BAMBOOK(DeviceConfig, DevicePlugin):
|
|||||||
ip = None
|
ip = None
|
||||||
|
|
||||||
FORMATS = [ "snb" ]
|
FORMATS = [ "snb" ]
|
||||||
|
USER_CAN_ADD_NEW_FORMATS = False
|
||||||
VENDOR_ID = 0x230b
|
VENDOR_ID = 0x230b
|
||||||
PRODUCT_ID = 0x0001
|
PRODUCT_ID = 0x0001
|
||||||
BCD = None
|
BCD = None
|
||||||
@ -421,7 +422,7 @@ class BAMBOOK(DeviceConfig, DevicePlugin):
|
|||||||
from calibre.gui2.device_drivers.configwidget import ConfigWidget
|
from calibre.gui2.device_drivers.configwidget import ConfigWidget
|
||||||
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
|
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
|
||||||
cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT,
|
cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT,
|
||||||
cls.EXTRA_CUSTOMIZATION_MESSAGE)
|
cls.EXTRA_CUSTOMIZATION_MESSAGE, cls)
|
||||||
# Turn off the Save template
|
# Turn off the Save template
|
||||||
cw.opt_save_template.setVisible(False)
|
cw.opt_save_template.setVisible(False)
|
||||||
cw.label.setVisible(False)
|
cw.label.setVisible(False)
|
||||||
|
@ -93,11 +93,11 @@ class MIBUK(USBMS):
|
|||||||
|
|
||||||
VENDOR_ID = [0x0525]
|
VENDOR_ID = [0x0525]
|
||||||
PRODUCT_ID = [0xa4a5]
|
PRODUCT_ID = [0xa4a5]
|
||||||
BCD = [0x314]
|
BCD = [0x314, 0x319]
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
VENDOR_NAME = 'LINUX'
|
VENDOR_NAME = ['LINUX', 'FILE_BAC']
|
||||||
WINDOWS_MAIN_MEM = 'WOLDERMIBUK'
|
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['WOLDERMIBUK', 'KED_STORAGE_GADG']
|
||||||
|
|
||||||
class JETBOOK_MINI(USBMS):
|
class JETBOOK_MINI(USBMS):
|
||||||
|
|
||||||
|
@ -98,7 +98,6 @@ class KOBO(USBMS):
|
|||||||
|
|
||||||
def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus, MimeType):
|
def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus, MimeType):
|
||||||
changed = False
|
changed = False
|
||||||
# if path_to_ext(path) in self.FORMATS:
|
|
||||||
try:
|
try:
|
||||||
lpath = path.partition(self.normalize_path(prefix))[2]
|
lpath = path.partition(self.normalize_path(prefix))[2]
|
||||||
if lpath.startswith(os.sep):
|
if lpath.startswith(os.sep):
|
||||||
@ -220,7 +219,7 @@ class KOBO(USBMS):
|
|||||||
# 2) volume_shorcover
|
# 2) volume_shorcover
|
||||||
# 2) content
|
# 2) content
|
||||||
|
|
||||||
debug_print('delete_via_sql: ContentID: ', ContentID, 'ContentType: ', ContentType)
|
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,)
|
||||||
@ -532,7 +531,7 @@ class KOBO(USBMS):
|
|||||||
if result is None:
|
if result is None:
|
||||||
datelastread = '1970-01-01T00:00:00'
|
datelastread = '1970-01-01T00:00:00'
|
||||||
else:
|
else:
|
||||||
datelastread = result[0] if result[0] is not None else '1970-01-01T00:00:00'
|
datelastread = result[0] if result[0] is not None else '1970-01-01T00:00:00'
|
||||||
|
|
||||||
t = (datelastread,ContentID,)
|
t = (datelastread,ContentID,)
|
||||||
|
|
||||||
|
@ -34,6 +34,10 @@ class DeviceConfig(object):
|
|||||||
#: If None the default is used
|
#: If None the default is used
|
||||||
SAVE_TEMPLATE = None
|
SAVE_TEMPLATE = None
|
||||||
|
|
||||||
|
#: If True the user can add new formats to the driver
|
||||||
|
USER_CAN_ADD_NEW_FORMATS = True
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _default_save_template(cls):
|
def _default_save_template(cls):
|
||||||
from calibre.library.save_to_disk import config
|
from calibre.library.save_to_disk import config
|
||||||
@ -73,7 +77,7 @@ class DeviceConfig(object):
|
|||||||
from calibre.gui2.device_drivers.configwidget import ConfigWidget
|
from calibre.gui2.device_drivers.configwidget import ConfigWidget
|
||||||
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
|
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
|
||||||
cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT,
|
cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT,
|
||||||
cls.EXTRA_CUSTOMIZATION_MESSAGE)
|
cls.EXTRA_CUSTOMIZATION_MESSAGE, cls)
|
||||||
return cw
|
return cw
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -93,9 +93,11 @@ class USBMS(CLI, Device):
|
|||||||
for idx,b in enumerate(bl):
|
for idx,b in enumerate(bl):
|
||||||
bl_cache[b.lpath] = idx
|
bl_cache[b.lpath] = idx
|
||||||
|
|
||||||
|
all_formats = set(self.settings().format_map) | set(self.FORMATS)
|
||||||
|
|
||||||
def update_booklist(filename, path, prefix):
|
def update_booklist(filename, path, prefix):
|
||||||
changed = False
|
changed = False
|
||||||
if path_to_ext(filename) in self.FORMATS:
|
if path_to_ext(filename) in all_formats:
|
||||||
try:
|
try:
|
||||||
lpath = os.path.join(path, filename).partition(self.normalize_path(prefix))[2]
|
lpath = os.path.join(path, filename).partition(self.normalize_path(prefix))[2]
|
||||||
if lpath.startswith(os.sep):
|
if lpath.startswith(os.sep):
|
||||||
|
@ -156,17 +156,17 @@ class HeuristicProcessor(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
ITALICIZE_STYLE_PATS = [
|
ITALICIZE_STYLE_PATS = [
|
||||||
r'(?msu)(?<=[\s>])_(?P<words>[^_]+)?_',
|
r'(?msu)(?<=[\s>])_(?P<words>[^_]+)_',
|
||||||
r'(?msu)(?<=[\s>])/(?P<words>[^/]+)?/',
|
r'(?msu)(?<=[\s>])/(?P<words>[^/]+)/',
|
||||||
r'(?msu)(?<=[\s>])~~(?P<words>[^~]+)?~~',
|
r'(?msu)(?<=[\s>])~~(?P<words>[^~]+)~~',
|
||||||
r'(?msu)(?<=[\s>])\*(?P<words>[^\*]+)?\*',
|
r'(?msu)(?<=[\s>])\*(?P<words>[^\*]+)\*',
|
||||||
r'(?msu)(?<=[\s>])~(?P<words>[^~]+)?~',
|
r'(?msu)(?<=[\s>])~(?P<words>[^~]+)~',
|
||||||
r'(?msu)(?<=[\s>])_/(?P<words>[^/_]+)?/_',
|
r'(?msu)(?<=[\s>])_/(?P<words>[^/_]+)/_',
|
||||||
r'(?msu)(?<=[\s>])_\*(?P<words>[^\*_]+)?\*_',
|
r'(?msu)(?<=[\s>])_\*(?P<words>[^\*_]+)\*_',
|
||||||
r'(?msu)(?<=[\s>])\*/(?P<words>[^/\*]+)?/\*',
|
r'(?msu)(?<=[\s>])\*/(?P<words>[^/\*]+)/\*',
|
||||||
r'(?msu)(?<=[\s>])_\*/(?P<words>[^\*_]+)?/\*_',
|
r'(?msu)(?<=[\s>])_\*/(?P<words>[^\*_]+)/\*_',
|
||||||
r'(?msu)(?<=[\s>])/:(?P<words>[^:/]+)?:/',
|
r'(?msu)(?<=[\s>])/:(?P<words>[^:/]+):/',
|
||||||
r'(?msu)(?<=[\s>])\|:(?P<words>[^:\|]+)?:\|',
|
r'(?msu)(?<=[\s>])\|:(?P<words>[^:\|]+):\|',
|
||||||
]
|
]
|
||||||
|
|
||||||
for word in ITALICIZE_WORDS:
|
for word in ITALICIZE_WORDS:
|
||||||
|
@ -145,6 +145,7 @@ class LibraryThingCovers(CoverDownload): # {{{
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
def has_cover(self, mi, ans, timeout=5.):
|
def has_cover(self, mi, ans, timeout=5.):
|
||||||
|
return False
|
||||||
if not mi.isbn or not self.site_customization:
|
if not mi.isbn or not self.site_customization:
|
||||||
return False
|
return False
|
||||||
from calibre.ebooks.metadata.library_thing import get_browser, login
|
from calibre.ebooks.metadata.library_thing import get_browser, login
|
||||||
|
@ -204,7 +204,8 @@ class AddAction(InterfaceAction):
|
|||||||
]
|
]
|
||||||
to_device = self.gui.stack.currentIndex() != 0
|
to_device = self.gui.stack.currentIndex() != 0
|
||||||
if to_device:
|
if to_device:
|
||||||
filters = [(_('Supported books'), self.gui.device_manager.device.FORMATS)]
|
fmts = self.gui.device_manager.device.settings().format_map
|
||||||
|
filters = [(_('Supported books'), fmts)]
|
||||||
|
|
||||||
books = choose_files(self.gui, 'add books dialog dir', 'Select books',
|
books = choose_files(self.gui, 'add books dialog dir', 'Select books',
|
||||||
filters=filters)
|
filters=filters)
|
||||||
|
@ -158,6 +158,8 @@ class MultiCompleteComboBox(EnComboBox):
|
|||||||
# item that matches case insensitively
|
# item that matches case insensitively
|
||||||
c = self.lineEdit().completer()
|
c = self.lineEdit().completer()
|
||||||
c.setCaseSensitivity(Qt.CaseSensitive)
|
c.setCaseSensitivity(Qt.CaseSensitive)
|
||||||
|
self.dummy_model = CompleteModel(self)
|
||||||
|
c.setModel(self.dummy_model)
|
||||||
|
|
||||||
def update_items_cache(self, complete_items):
|
def update_items_cache(self, complete_items):
|
||||||
self.lineEdit().update_items_cache(complete_items)
|
self.lineEdit().update_items_cache(complete_items)
|
||||||
|
@ -551,7 +551,11 @@ class BulkBool(BulkBase, Bool):
|
|||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.make_widgets(parent, QComboBox)
|
self.make_widgets(parent, QComboBox)
|
||||||
items = [_('Yes'), _('No'), _('Undefined')]
|
items = [_('Yes'), _('No')]
|
||||||
|
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
||||||
|
items.append('')
|
||||||
|
else:
|
||||||
|
items.append(_('Undefined'))
|
||||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||||
self.main_widget.blockSignals(True)
|
self.main_widget.blockSignals(True)
|
||||||
for icon, text in zip(icons, items):
|
for icon, text in zip(icons, items):
|
||||||
@ -560,7 +564,10 @@ class BulkBool(BulkBase, Bool):
|
|||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.main_widget.currentIndex()
|
val = self.main_widget.currentIndex()
|
||||||
return {2: None, 1: False, 0: True}[val]
|
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
||||||
|
return {2: False, 1: False, 0: True}[val]
|
||||||
|
else:
|
||||||
|
return {2: None, 1: False, 0: True}[val]
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
val = {None: 2, False: 1, True: 0}[val]
|
val = {None: 2, False: 1, True: 0}[val]
|
||||||
@ -576,6 +583,14 @@ class BulkBool(BulkBase, Bool):
|
|||||||
val = False
|
val = False
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||||
|
|
||||||
|
def a_c_checkbox_changed(self):
|
||||||
|
if not self.ignore_change_signals:
|
||||||
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and \
|
||||||
|
self.main_widget.currentIndex() == 2:
|
||||||
|
self.a_c_checkbox.setChecked(False)
|
||||||
|
else:
|
||||||
|
self.a_c_checkbox.setChecked(True)
|
||||||
|
|
||||||
class BulkInt(BulkBase):
|
class BulkInt(BulkBase):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
|
@ -1292,6 +1292,16 @@ class DeviceMixin(object): # {{{
|
|||||||
to both speed up matching and to count matches.
|
to both speed up matching and to count matches.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
if not self.device_manager.is_device_connected:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# It might be possible to get here without having initialized the
|
||||||
|
# library view. In this case, simply give up
|
||||||
|
try:
|
||||||
|
db = self.library_view.model().db
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
string_pat = re.compile('(?u)\W|[_]')
|
string_pat = re.compile('(?u)\W|[_]')
|
||||||
def clean_string(x):
|
def clean_string(x):
|
||||||
x = x.lower() if x else ''
|
x = x.lower() if x else ''
|
||||||
@ -1299,26 +1309,19 @@ class DeviceMixin(object): # {{{
|
|||||||
|
|
||||||
update_metadata = prefs['manage_device_metadata'] == 'on_connect'
|
update_metadata = prefs['manage_device_metadata'] == 'on_connect'
|
||||||
|
|
||||||
|
get_covers = False
|
||||||
|
if update_metadata and self.device_manager.is_device_connected:
|
||||||
|
if self.device_manager.device.WANTS_UPDATED_THUMBNAILS:
|
||||||
|
get_covers = True
|
||||||
|
|
||||||
# Force a reset if the caches are not initialized
|
# Force a reset if the caches are not initialized
|
||||||
if reset or not hasattr(self, 'db_book_title_cache'):
|
if reset or not hasattr(self, 'db_book_title_cache'):
|
||||||
# Build a cache (map) of the library, so the search isn't On**2
|
# Build a cache (map) of the library, so the search isn't On**2
|
||||||
db_book_title_cache = {}
|
db_book_title_cache = {}
|
||||||
db_book_uuid_cache = {}
|
db_book_uuid_cache = {}
|
||||||
# It might be possible to get here without having initialized the
|
|
||||||
# library view. In this case, simply give up
|
|
||||||
try:
|
|
||||||
db = self.library_view.model().db
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
get_covers = False
|
for id_ in db.data.iterallids():
|
||||||
if update_metadata and self.device_manager.is_device_connected:
|
title = clean_string(db.title(id_, index_is_id=True))
|
||||||
if self.device_manager.device.WANTS_UPDATED_THUMBNAILS:
|
|
||||||
get_covers = True
|
|
||||||
|
|
||||||
for id in db.data.iterallids():
|
|
||||||
mi = db.get_metadata(id, index_is_id=True, get_cover=get_covers)
|
|
||||||
title = clean_string(mi.title)
|
|
||||||
if title not in db_book_title_cache:
|
if title not in db_book_title_cache:
|
||||||
db_book_title_cache[title] = \
|
db_book_title_cache[title] = \
|
||||||
{'authors':{}, 'author_sort':{}, 'db_ids':{}}
|
{'authors':{}, 'author_sort':{}, 'db_ids':{}}
|
||||||
@ -1326,14 +1329,14 @@ class DeviceMixin(object): # {{{
|
|||||||
# and author, then remember the last one. That is OK, because as
|
# and author, then remember the last one. That is OK, because as
|
||||||
# we can't tell the difference between the books, one is as good
|
# we can't tell the difference between the books, one is as good
|
||||||
# as another.
|
# as another.
|
||||||
if mi.authors:
|
authors = clean_string(db.authors(id_, index_is_id=True))
|
||||||
authors = clean_string(authors_to_string(mi.authors))
|
if authors:
|
||||||
db_book_title_cache[title]['authors'][authors] = mi
|
db_book_title_cache[title]['authors'][authors] = id_
|
||||||
if mi.author_sort:
|
if db.author_sort(id_, index_is_id=True):
|
||||||
aus = clean_string(mi.author_sort)
|
aus = clean_string(db.author_sort(id_, index_is_id=True))
|
||||||
db_book_title_cache[title]['author_sort'][aus] = mi
|
db_book_title_cache[title]['author_sort'][aus] = id_
|
||||||
db_book_title_cache[title]['db_ids'][mi.application_id] = mi
|
db_book_title_cache[title]['db_ids'][id_] = id_
|
||||||
db_book_uuid_cache[mi.uuid] = mi
|
db_book_uuid_cache[db.uuid(id_, index_is_id=True)] = id_
|
||||||
self.db_book_title_cache = db_book_title_cache
|
self.db_book_title_cache = db_book_title_cache
|
||||||
self.db_book_uuid_cache = db_book_uuid_cache
|
self.db_book_uuid_cache = db_book_uuid_cache
|
||||||
|
|
||||||
@ -1341,19 +1344,22 @@ class DeviceMixin(object): # {{{
|
|||||||
# in_library field. If the UUID matches a book in the library, then
|
# in_library field. If the UUID matches a book in the library, then
|
||||||
# do not consider that book for other matching. In all cases set
|
# do not consider that book for other matching. In all cases set
|
||||||
# the application_id to the db_id of the matching book. This value
|
# the application_id to the db_id of the matching book. This value
|
||||||
# will be used by books_on_device to indicate matches.
|
# will be used by books_on_device to indicate matches. While we are
|
||||||
|
# going by, update the metadata for a book if automatic management is on
|
||||||
|
|
||||||
for booklist in booklists:
|
for booklist in booklists:
|
||||||
for book in booklist:
|
for book in booklist:
|
||||||
book.in_library = None
|
book.in_library = None
|
||||||
if getattr(book, 'uuid', None) in self.db_book_uuid_cache:
|
if getattr(book, 'uuid', None) in self.db_book_uuid_cache:
|
||||||
|
id_ = db_book_uuid_cache[book.uuid]
|
||||||
if update_metadata:
|
if update_metadata:
|
||||||
book.smart_update(self.db_book_uuid_cache[book.uuid],
|
book.smart_update(db.get_metadata(id_,
|
||||||
|
index_is_id=True,
|
||||||
|
get_cover=get_covers),
|
||||||
replace_metadata=True)
|
replace_metadata=True)
|
||||||
book.in_library = 'UUID'
|
book.in_library = 'UUID'
|
||||||
# ensure that the correct application_id is set
|
# ensure that the correct application_id is set
|
||||||
book.application_id = \
|
book.application_id = id_
|
||||||
self.db_book_uuid_cache[book.uuid].application_id
|
|
||||||
continue
|
continue
|
||||||
# No UUID exact match. Try metadata matching.
|
# No UUID exact match. Try metadata matching.
|
||||||
book_title = clean_string(book.title)
|
book_title = clean_string(book.title)
|
||||||
@ -1363,21 +1369,25 @@ class DeviceMixin(object): # {{{
|
|||||||
# will match if any of the db_id, author, or author_sort
|
# will match if any of the db_id, author, or author_sort
|
||||||
# also match.
|
# also match.
|
||||||
if getattr(book, 'application_id', None) in d['db_ids']:
|
if getattr(book, 'application_id', None) in d['db_ids']:
|
||||||
# app_id already matches a db_id. No need to set it.
|
|
||||||
if update_metadata:
|
if update_metadata:
|
||||||
book.smart_update(d['db_ids'][book.application_id],
|
id_ = getattr(book, 'application_id', None)
|
||||||
|
book.smart_update(db.get_metadata(id_,
|
||||||
|
index_is_id=True,
|
||||||
|
get_cover=get_covers),
|
||||||
replace_metadata=True)
|
replace_metadata=True)
|
||||||
book.in_library = 'APP_ID'
|
book.in_library = 'APP_ID'
|
||||||
|
# app_id already matches a db_id. No need to set it.
|
||||||
continue
|
continue
|
||||||
# Sonys know their db_id independent of the application_id
|
# Sonys know their db_id independent of the application_id
|
||||||
# in the metadata cache. Check that as well.
|
# in the metadata cache. Check that as well.
|
||||||
if getattr(book, 'db_id', None) in d['db_ids']:
|
if getattr(book, 'db_id', None) in d['db_ids']:
|
||||||
if update_metadata:
|
if update_metadata:
|
||||||
book.smart_update(d['db_ids'][book.db_id],
|
book.smart_update(db.get_metadata(book.db_id,
|
||||||
|
index_is_id=True,
|
||||||
|
get_cover=get_covers),
|
||||||
replace_metadata=True)
|
replace_metadata=True)
|
||||||
book.in_library = 'DB_ID'
|
book.in_library = 'DB_ID'
|
||||||
book.application_id = \
|
book.application_id = book.db_id
|
||||||
d['db_ids'][book.db_id].application_id
|
|
||||||
continue
|
continue
|
||||||
# We now know that the application_id is not right. Set it
|
# We now know that the application_id is not right. Set it
|
||||||
# to None to prevent book_on_device from accidentally
|
# to None to prevent book_on_device from accidentally
|
||||||
@ -1389,19 +1399,23 @@ class DeviceMixin(object): # {{{
|
|||||||
# either can appear as the author
|
# either can appear as the author
|
||||||
book_authors = clean_string(authors_to_string(book.authors))
|
book_authors = clean_string(authors_to_string(book.authors))
|
||||||
if book_authors in d['authors']:
|
if book_authors in d['authors']:
|
||||||
|
id_ = d['authors'][book_authors]
|
||||||
if update_metadata:
|
if update_metadata:
|
||||||
book.smart_update(d['authors'][book_authors],
|
book.smart_update(db.get_metadata(id_,
|
||||||
replace_metadata=True)
|
index_is_id=True,
|
||||||
|
get_cover=get_covers),
|
||||||
|
replace_metadata=True)
|
||||||
book.in_library = 'AUTHOR'
|
book.in_library = 'AUTHOR'
|
||||||
book.application_id = \
|
book.application_id = id_
|
||||||
d['authors'][book_authors].application_id
|
|
||||||
elif book_authors in d['author_sort']:
|
elif book_authors in d['author_sort']:
|
||||||
|
id_ = d['author_sort'][book_authors]
|
||||||
if update_metadata:
|
if update_metadata:
|
||||||
book.smart_update(d['author_sort'][book_authors],
|
book.smart_update(db.get_metadata(id_,
|
||||||
|
index_is_id=True,
|
||||||
|
get_cover=get_covers),
|
||||||
replace_metadata=True)
|
replace_metadata=True)
|
||||||
book.in_library = 'AUTH_SORT'
|
book.in_library = 'AUTH_SORT'
|
||||||
book.application_id = \
|
book.application_id = id_
|
||||||
d['author_sort'][book_authors].application_id
|
|
||||||
else:
|
else:
|
||||||
# Book definitely not matched. Clear its application ID
|
# Book definitely not matched. Clear its application ID
|
||||||
book.application_id = None
|
book.application_id = None
|
||||||
|
@ -9,15 +9,16 @@ import textwrap
|
|||||||
from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL, \
|
from PyQt4.Qt import QWidget, QListWidgetItem, Qt, QVariant, SIGNAL, \
|
||||||
QLabel, QLineEdit, QCheckBox
|
QLabel, QLineEdit, QCheckBox
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog, question_dialog
|
||||||
from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
|
from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
|
||||||
from calibre.utils.formatter import validation_formatter
|
from calibre.utils.formatter import validation_formatter
|
||||||
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
|
||||||
class ConfigWidget(QWidget, Ui_ConfigWidget):
|
class ConfigWidget(QWidget, Ui_ConfigWidget):
|
||||||
|
|
||||||
def __init__(self, settings, all_formats, supports_subdirs,
|
def __init__(self, settings, all_formats, supports_subdirs,
|
||||||
must_read_metadata, supports_use_author_sort,
|
must_read_metadata, supports_use_author_sort,
|
||||||
extra_customization_message):
|
extra_customization_message, device):
|
||||||
|
|
||||||
QWidget.__init__(self)
|
QWidget.__init__(self)
|
||||||
Ui_ConfigWidget.__init__(self)
|
Ui_ConfigWidget.__init__(self)
|
||||||
@ -25,9 +26,15 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
|
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
|
||||||
|
all_formats = set(all_formats)
|
||||||
|
self.calibre_known_formats = device.FORMATS
|
||||||
|
self.device_name = device.get_gui_name()
|
||||||
|
if device.USER_CAN_ADD_NEW_FORMATS:
|
||||||
|
all_formats = set(all_formats) | set(BOOK_EXTENSIONS)
|
||||||
|
|
||||||
format_map = settings.format_map
|
format_map = settings.format_map
|
||||||
disabled_formats = list(set(all_formats).difference(format_map))
|
disabled_formats = list(set(all_formats).difference(format_map))
|
||||||
for format in format_map + disabled_formats:
|
for format in format_map + list(sorted(disabled_formats)):
|
||||||
item = QListWidgetItem(format, self.columns)
|
item = QListWidgetItem(format, self.columns)
|
||||||
item.setData(Qt.UserRole, QVariant(format))
|
item.setData(Qt.UserRole, QVariant(format))
|
||||||
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
|
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
|
||||||
@ -110,6 +117,18 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
return self.opt_use_author_sort.isChecked()
|
return self.opt_use_author_sort.isChecked()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
formats = set(self.format_map())
|
||||||
|
extra = formats - set(self.calibre_known_formats)
|
||||||
|
if extra:
|
||||||
|
fmts = sorted([x.upper() for x in extra])
|
||||||
|
if not question_dialog(self, _('Unknown formats'),
|
||||||
|
_('You have enabled the <b>{0}</b> formats for'
|
||||||
|
' your {1}. The {1} may not support them.'
|
||||||
|
' If you send these formats to your {1} they '
|
||||||
|
'may not work. Are you sure?').format(
|
||||||
|
(', '.join(fmts)), self.device_name)):
|
||||||
|
return False
|
||||||
|
|
||||||
tmpl = unicode(self.opt_save_template.text())
|
tmpl = unicode(self.opt_save_template.text())
|
||||||
try:
|
try:
|
||||||
validation_formatter.validate(tmpl)
|
validation_formatter.validate(tmpl)
|
||||||
|
@ -912,6 +912,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
|
|
||||||
def series_changed(self, *args):
|
def series_changed(self, *args):
|
||||||
self.write_series = True
|
self.write_series = True
|
||||||
|
self.autonumber_series.setEnabled(True)
|
||||||
|
|
||||||
def s_r_remove_query(self, *args):
|
def s_r_remove_query(self, *args):
|
||||||
if self.query_field.currentIndex() == 0:
|
if self.query_field.currentIndex() == 0:
|
||||||
|
@ -303,6 +303,9 @@
|
|||||||
<layout class="QHBoxLayout" name="HLayout_3">
|
<layout class="QHBoxLayout" name="HLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="autonumber_series">
|
<widget class="QCheckBox" name="autonumber_series">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>If not checked, the series number for the books will be set to 1.
|
<string>If not checked, the series number for the books will be set to 1.
|
||||||
If checked, selected books will be automatically numbered, in the order
|
If checked, selected books will be automatically numbered, in the order
|
||||||
@ -1006,8 +1009,8 @@ not multiple and the destination field is multiple</string>
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>938</width>
|
<width>197</width>
|
||||||
<height>268</height>
|
<height>60</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="testgrid">
|
<layout class="QGridLayout" name="testgrid">
|
||||||
|
@ -685,7 +685,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.dc[col] = functools.partial(bool_type, idx=idx)
|
self.dc[col] = functools.partial(bool_type, idx=idx)
|
||||||
self.dc_decorator[col] = functools.partial(
|
self.dc_decorator[col] = functools.partial(
|
||||||
bool_type_decorator, idx=idx,
|
bool_type_decorator, idx=idx,
|
||||||
bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] == 'yes')
|
bool_cols_are_tristate=tweaks['bool_custom_columns_are_tristate'] != 'no')
|
||||||
elif datatype == 'rating':
|
elif datatype == 'rating':
|
||||||
self.dc[col] = functools.partial(rating_type, idx=idx)
|
self.dc[col] = functools.partial(rating_type, idx=idx)
|
||||||
elif datatype == 'series':
|
elif datatype == 'series':
|
||||||
|
@ -26,12 +26,19 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
r('limit_search_columns_to', prefs, setting=CommaSeparatedList)
|
r('limit_search_columns_to', prefs, setting=CommaSeparatedList)
|
||||||
fl = gui.library_view.model().db.field_metadata.get_search_terms()
|
fl = gui.library_view.model().db.field_metadata.get_search_terms()
|
||||||
self.opt_limit_search_columns_to.update_items_cache(fl)
|
self.opt_limit_search_columns_to.update_items_cache(fl)
|
||||||
|
self.clear_history_button.clicked.connect(self.clear_histories)
|
||||||
|
|
||||||
def refresh_gui(self, gui):
|
def refresh_gui(self, gui):
|
||||||
gui.search.search_as_you_type(config['search_as_you_type'])
|
gui.search.search_as_you_type(config['search_as_you_type'])
|
||||||
gui.library_view.model().set_highlight_only(config['highlight_search_matches'])
|
gui.library_view.model().set_highlight_only(config['highlight_search_matches'])
|
||||||
gui.search.do_search()
|
gui.search.do_search()
|
||||||
|
|
||||||
|
def clear_histories(self, *args):
|
||||||
|
for key, val in config.defaults.iteritems():
|
||||||
|
if key.endswith('_search_history') and isinstance(val, list):
|
||||||
|
config[key] = []
|
||||||
|
self.gui.search.clear_history()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
test_widget('Interface', 'Search')
|
test_widget('Interface', 'Search')
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="4" column="0">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -90,13 +90,23 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QPushButton" name="clear_history_button">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Clear search histories from all over calibre. Including the book list, e-book viewer, fetch news dialog, etc.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Clear search &histories</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>MultiCompleteLineEdit</class>
|
<class>MultiCompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>calibre/gui2.complete.h</header>
|
<header>calibre/gui2/complete.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -114,6 +114,9 @@ class SearchBox2(QComboBox): # {{{
|
|||||||
def text(self):
|
def text(self):
|
||||||
return self.currentText()
|
return self.currentText()
|
||||||
|
|
||||||
|
def clear_history(self, *args):
|
||||||
|
QComboBox.clear(self)
|
||||||
|
|
||||||
def clear(self, emit_search=True):
|
def clear(self, emit_search=True):
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
self.setEditText('')
|
self.setEditText('')
|
||||||
|
@ -17,16 +17,16 @@ from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
|
|||||||
from calibre.gui2.widgets import ProgressIndicator
|
from calibre.gui2.widgets import ProgressIndicator
|
||||||
from calibre.gui2.main_window import MainWindow
|
from calibre.gui2.main_window import MainWindow
|
||||||
from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
|
from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
|
||||||
info_dialog, error_dialog, open_url, available_height
|
info_dialog, error_dialog, open_url, available_height, gprefs
|
||||||
from calibre.ebooks.oeb.iterator import EbookIterator
|
from calibre.ebooks.oeb.iterator import EbookIterator
|
||||||
from calibre.ebooks import DRMError
|
from calibre.ebooks import DRMError
|
||||||
from calibre.constants import islinux, isfreebsd, isosx
|
from calibre.constants import islinux, isfreebsd, isosx, filesystem_encoding
|
||||||
from calibre.utils.config import Config, StringConfig, dynamic
|
from calibre.utils.config import Config, StringConfig, dynamic
|
||||||
from calibre.gui2.search_box import SearchBox2
|
from calibre.gui2.search_box import SearchBox2
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.customize.ui import available_input_formats
|
from calibre.customize.ui import available_input_formats
|
||||||
from calibre.gui2.viewer.dictionary import Lookup
|
from calibre.gui2.viewer.dictionary import Lookup
|
||||||
from calibre import as_unicode
|
from calibre import as_unicode, force_unicode, isbytestring
|
||||||
|
|
||||||
class TOCItem(QStandardItem):
|
class TOCItem(QStandardItem):
|
||||||
|
|
||||||
@ -160,6 +160,12 @@ class HelpfulLineEdit(QLineEdit):
|
|||||||
self.setPalette(self.gray)
|
self.setPalette(self.gray)
|
||||||
self.setText(self.HELP_TEXT)
|
self.setText(self.HELP_TEXT)
|
||||||
|
|
||||||
|
class RecentAction(QAction):
|
||||||
|
|
||||||
|
def __init__(self, path, parent):
|
||||||
|
self.path = path
|
||||||
|
QAction.__init__(self, os.path.basename(path), parent)
|
||||||
|
|
||||||
class EbookViewer(MainWindow, Ui_EbookViewer):
|
class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||||
|
|
||||||
STATE_VERSION = 1
|
STATE_VERSION = 1
|
||||||
@ -284,8 +290,26 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
ca = self.view.copy_action
|
ca = self.view.copy_action
|
||||||
ca.setShortcut(QKeySequence.Copy)
|
ca.setShortcut(QKeySequence.Copy)
|
||||||
self.addAction(ca)
|
self.addAction(ca)
|
||||||
|
self.open_history_menu = QMenu()
|
||||||
|
self.build_recent_menu()
|
||||||
|
self.action_open_ebook.setMenu(self.open_history_menu)
|
||||||
|
self.open_history_menu.triggered[QAction].connect(self.open_recent)
|
||||||
|
w = self.tool_bar.widgetForAction(self.action_open_ebook)
|
||||||
|
w.setPopupMode(QToolButton.MenuButtonPopup)
|
||||||
|
|
||||||
self.restore_state()
|
self.restore_state()
|
||||||
|
|
||||||
|
def build_recent_menu(self):
|
||||||
|
m = self.open_history_menu
|
||||||
|
m.clear()
|
||||||
|
count = 0
|
||||||
|
for path in gprefs.get('viewer_open_history', []):
|
||||||
|
if count > 9:
|
||||||
|
break
|
||||||
|
if os.path.exists(path):
|
||||||
|
m.addAction(RecentAction(path, m))
|
||||||
|
count += 1
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
self.save_state()
|
self.save_state()
|
||||||
return MainWindow.closeEvent(self, e)
|
return MainWindow.closeEvent(self, e)
|
||||||
@ -425,6 +449,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if files:
|
if files:
|
||||||
self.load_ebook(files[0])
|
self.load_ebook(files[0])
|
||||||
|
|
||||||
|
def open_recent(self, action):
|
||||||
|
self.load_ebook(action.path)
|
||||||
|
|
||||||
def font_size_larger(self, checked):
|
def font_size_larger(self, checked):
|
||||||
frac = self.view.magnify_fonts()
|
frac = self.view.magnify_fonts()
|
||||||
self.action_font_size_larger.setEnabled(self.view.multiplier() < 3)
|
self.action_font_size_larger.setEnabled(self.view.multiplier() < 3)
|
||||||
@ -647,6 +674,17 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.action_table_of_contents.setChecked(True)
|
self.action_table_of_contents.setChecked(True)
|
||||||
else:
|
else:
|
||||||
self.action_table_of_contents.setChecked(False)
|
self.action_table_of_contents.setChecked(False)
|
||||||
|
if isbytestring(pathtoebook):
|
||||||
|
pathtoebook = force_unicode(pathtoebook, filesystem_encoding)
|
||||||
|
vh = gprefs.get('viewer_open_history', [])
|
||||||
|
try:
|
||||||
|
vh.remove(pathtoebook)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
vh.insert(0, pathtoebook)
|
||||||
|
gprefs.set('viewer_open_history', vh[:50])
|
||||||
|
self.build_recent_menu()
|
||||||
|
|
||||||
self.action_table_of_contents.setDisabled(not self.iterator.toc)
|
self.action_table_of_contents.setDisabled(not self.iterator.toc)
|
||||||
self.current_book_has_toc = bool(self.iterator.toc)
|
self.current_book_has_toc = bool(self.iterator.toc)
|
||||||
self.current_title = title
|
self.current_title = title
|
||||||
|
@ -528,7 +528,7 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
location[i] = db_col[loc]
|
location[i] = db_col[loc]
|
||||||
|
|
||||||
# get the tweak here so that the string lookup and compare aren't in the loop
|
# get the tweak here so that the string lookup and compare aren't in the loop
|
||||||
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] == 'yes'
|
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] != 'no'
|
||||||
|
|
||||||
for loc in location: # location is now an array of field indices
|
for loc in location: # location is now an array of field indices
|
||||||
if loc == db_col['authors']:
|
if loc == db_col['authors']:
|
||||||
@ -812,7 +812,10 @@ class SortKeyGenerator(object):
|
|||||||
val = self.string_sort_key(val)
|
val = self.string_sort_key(val)
|
||||||
|
|
||||||
elif dt == 'bool':
|
elif dt == 'bool':
|
||||||
val = {True: 1, False: 2, None: 3}.get(val, 3)
|
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
||||||
|
val = {True: 1, False: 2, None: 2}.get(val, 2)
|
||||||
|
else:
|
||||||
|
val = {True: 1, False: 2, None: 3}.get(val, 3)
|
||||||
|
|
||||||
yield val
|
yield val
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user