mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
0f62ea111c
@ -19,6 +19,65 @@
|
|||||||
# new recipes:
|
# new recipes:
|
||||||
# - title:
|
# - title:
|
||||||
|
|
||||||
|
|
||||||
|
- version: 0.7.54
|
||||||
|
date: 2011-04-08
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "New output format, HTMLZ which is a single HTML file with its associated images/stylesheets in a zipped up file"
|
||||||
|
description: "Useful when you want to convert your ebook into a single HTML file for easy editing. Note that this output plugin is still new and needs testing"
|
||||||
|
|
||||||
|
- title: "When dealing with ZIP/RAR archives, use the file header rather than the file extension to detrmine the file type, when possible. This fixes the common case of CBZ files being actually cbr files and vice versa"
|
||||||
|
|
||||||
|
- title: "Support for the Motorola Atrix"
|
||||||
|
|
||||||
|
- title: "Allow the icons in the toolbar to be turned off completely via Preferences->Look & Feel"
|
||||||
|
|
||||||
|
- title: "When downloading metadata use the gzip transfer encoding when possible for a speedup."
|
||||||
|
tickets: [749304]
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Conversion pipeline: Workaround for bug in lxml that causes a massive mem leak on windows and OS X when the input document contains non ASCII CSS selectors."
|
||||||
|
tickets: [754555]
|
||||||
|
|
||||||
|
- title: "Conversion pipeline: Handle inline <style> tags that put all the actual CSS inside an XML comment."
|
||||||
|
tickets: [750063]
|
||||||
|
|
||||||
|
- title: "The 'Choose Library' button now shows its popup menu when you already have more than one library instead of the dialog to create a new library"
|
||||||
|
tickets: [754154]
|
||||||
|
|
||||||
|
- title: "Apply all content server setting when clicking the Start Server button in Preferences->Sharing over the net"
|
||||||
|
tickets: [753122]
|
||||||
|
|
||||||
|
- title: "Fix content server breaking if its restriction is set to a saved search that was deleted"
|
||||||
|
tickets: [751950]
|
||||||
|
|
||||||
|
- title: "Fix detection of PocketBook with 2.0.6 firmware on windows"
|
||||||
|
tickets: [750336]
|
||||||
|
|
||||||
|
- title: "ODT Input: Fix handling of the <text:s> element."
|
||||||
|
tickets: [749655]
|
||||||
|
|
||||||
|
- title: "MOBI Output: Don't use self closed tags"
|
||||||
|
|
||||||
|
- title: "Fix book details popup becoming too tall if there is a lot of metadata"
|
||||||
|
|
||||||
|
- title: "Fix new PDF engine crashing on PDF files with embedded fonts with null names"
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- Kommersant
|
||||||
|
- Perfil
|
||||||
|
- Times of India
|
||||||
|
- IHT
|
||||||
|
- Guardian
|
||||||
|
|
||||||
|
new recipes:
|
||||||
|
- title: "Al Ahram"
|
||||||
|
authors: Hassan Williamson
|
||||||
|
|
||||||
|
- title: "F-Secure and developpez.com"
|
||||||
|
authors: louhike
|
||||||
|
|
||||||
- version: 0.7.53
|
- version: 0.7.53
|
||||||
date: 2011-04-01
|
date: 2011-04-01
|
||||||
|
|
||||||
|
62
recipes/al_ahram.recipe
Normal file
62
recipes/al_ahram.recipe
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Hassan Williamson <haz at hazrpg.co.uk>'
|
||||||
|
'''
|
||||||
|
ahram.org.eg
|
||||||
|
'''
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AlAhram(BasicNewsRecipe):
|
||||||
|
title = 'Al-Ahram'
|
||||||
|
__author__ = 'Hassan Williamson'
|
||||||
|
description = 'News from Egypt in Arabic.'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
#delay = 1
|
||||||
|
use_embedded_content = False
|
||||||
|
encoding = 'utf8'
|
||||||
|
publisher = 'Al-Ahram'
|
||||||
|
category = 'News'
|
||||||
|
language = 'ar'
|
||||||
|
publication_type = 'newsportal'
|
||||||
|
extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif; direction: rtl; } .txtTitle{ font-weight: bold; } '
|
||||||
|
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'class':['bbcolright']})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['bbnav', 'bbsp']}),
|
||||||
|
dict(name='div', attrs={'id':['AddThisButton']})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_attributes = [
|
||||||
|
'width','height'
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'الأولى', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=25'),
|
||||||
|
(u'مصر', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=27'),
|
||||||
|
(u'المحافظات', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=29'),
|
||||||
|
(u'الوطن العربي', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=31'),
|
||||||
|
(u'العالم', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=26'),
|
||||||
|
(u'تقارير المراسلين', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=2'),
|
||||||
|
(u'تحقيقات', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=3'),
|
||||||
|
(u'قضايا واراء', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=4'),
|
||||||
|
(u'اقتصاد', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=5'),
|
||||||
|
(u'رياضة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=6'),
|
||||||
|
(u'حوادث', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=38'),
|
||||||
|
(u'دنيا الثقافة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=7'),
|
||||||
|
(u'المراة والطفل', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=8'),
|
||||||
|
(u'يوم جديد', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=9'),
|
||||||
|
(u'الكتاب', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=10'),
|
||||||
|
(u'الاعمدة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=11'),
|
||||||
|
(u'أراء حرة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=59'),
|
||||||
|
(u'ملفات الاهرام', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=12'),
|
||||||
|
(u'بريد الاهرام', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=15'),
|
||||||
|
(u'الاخيرة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=16'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
32
recipes/dvhn.recipe
Normal file
32
recipes/dvhn.recipe
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1302341394(BasicNewsRecipe):
|
||||||
|
title = u'DvhN'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 200
|
||||||
|
|
||||||
|
__author__ = 'Reijndert'
|
||||||
|
no_stylesheets = True
|
||||||
|
cover_url = 'http://www.dvhn.nl/template/Dagblad_v2.0/gfx/logo_DvhN.gif'
|
||||||
|
language = 'nl'
|
||||||
|
country = 'NL'
|
||||||
|
version = 1
|
||||||
|
publisher = u'Dagblad van het Noorden'
|
||||||
|
category = u'Nieuws'
|
||||||
|
description = u'Nieuws uit Noord Nederland'
|
||||||
|
|
||||||
|
|
||||||
|
keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'})
|
||||||
|
,dict(name='div', attrs={'id':'articleText'})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name=['object','link','iframe','base'])
|
||||||
|
,dict(name='span',attrs={'class':'copyright'})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss'), (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss'), (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss'), (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss'), (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss'), (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss'), (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss'), (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')]
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
body {font-family: verdana, arial, helvetica, geneva, sans-serif;}
|
||||||
|
'''
|
@ -18,7 +18,8 @@ class Economist(BasicNewsRecipe):
|
|||||||
|
|
||||||
__author__ = "Kovid Goyal"
|
__author__ = "Kovid Goyal"
|
||||||
INDEX = 'http://www.economist.com/printedition'
|
INDEX = 'http://www.economist.com/printedition'
|
||||||
description = 'Global news and current affairs from a European perspective.'
|
description = ('Global news and current affairs from a European'
|
||||||
|
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||||
|
|
||||||
oldest_article = 7.0
|
oldest_article = 7.0
|
||||||
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||||
|
@ -11,7 +11,8 @@ class Economist(BasicNewsRecipe):
|
|||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
__author__ = "Kovid Goyal"
|
__author__ = "Kovid Goyal"
|
||||||
description = ('Global news and current affairs from a European perspective.'
|
description = ('Global news and current affairs from a European'
|
||||||
|
' perspective. Best downloaded on Friday mornings (GMT).'
|
||||||
' Much slower than the print edition based version.')
|
' Much slower than the print edition based version.')
|
||||||
|
|
||||||
oldest_article = 7.0
|
oldest_article = 7.0
|
||||||
|
@ -11,7 +11,8 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
|||||||
class FinancialTimes(BasicNewsRecipe):
|
class FinancialTimes(BasicNewsRecipe):
|
||||||
title = u'Financial Times'
|
title = u'Financial Times'
|
||||||
__author__ = 'Darko Miletic and Sujata Raman'
|
__author__ = 'Darko Miletic and Sujata Raman'
|
||||||
description = 'Financial world news'
|
description = ('Financial world news. Available after 5AM '
|
||||||
|
'GMT, daily.')
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
|
@ -88,13 +88,6 @@ categories_collapsed_rating_template = r'{first.avg_rating:4.2f:ifempty(0)} - {l
|
|||||||
categories_collapsed_popularity_template = r'{first.count:d} - {last.count:d}'
|
categories_collapsed_popularity_template = r'{first.count:d} - {last.count:d}'
|
||||||
|
|
||||||
|
|
||||||
#: Set boolean custom columns to be tristate
|
|
||||||
# Set whether boolean custom columns are two- or three-valued.
|
|
||||||
# Two-values for true booleans
|
|
||||||
# three-values for yes/no/unknown
|
|
||||||
# Set to 'yes' for three-values, 'no' for two-values
|
|
||||||
bool_custom_columns_are_tristate = 'yes'
|
|
||||||
|
|
||||||
#: Specify columns to sort the booklist by on startup
|
#: Specify columns to sort the booklist by on startup
|
||||||
# Provide a set of columns to be sorted on when calibre starts
|
# Provide a set of columns to be sorted on when calibre starts
|
||||||
# The argument is None if saved sort history is to be used
|
# The argument is None if saved sort history is to be used
|
||||||
|
BIN
resources/images/connect_share_on.png
Normal file
BIN
resources/images/connect_share_on.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.7.53'
|
__version__ = '0.7.54'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re, importlib
|
import re, importlib
|
||||||
|
@ -453,12 +453,15 @@ def epub_fixers():
|
|||||||
# Metadata sources2 {{{
|
# Metadata sources2 {{{
|
||||||
def metadata_plugins(capabilities):
|
def metadata_plugins(capabilities):
|
||||||
capabilities = frozenset(capabilities)
|
capabilities = frozenset(capabilities)
|
||||||
for plugin in _initialized_plugins:
|
for plugin in all_metadata_plugins():
|
||||||
if isinstance(plugin, Source) and \
|
if plugin.capabilities.intersection(capabilities) and \
|
||||||
plugin.capabilities.intersection(capabilities) and \
|
|
||||||
not is_disabled(plugin):
|
not is_disabled(plugin):
|
||||||
yield plugin
|
yield plugin
|
||||||
|
|
||||||
|
def all_metadata_plugins():
|
||||||
|
for plugin in _initialized_plugins:
|
||||||
|
if isinstance(plugin, Source):
|
||||||
|
yield plugin
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Initialize plugins {{{
|
# Initialize plugins {{{
|
||||||
|
@ -26,9 +26,9 @@ class EDGE(USBMS):
|
|||||||
PRODUCT_ID = [0x0c02]
|
PRODUCT_ID = [0x0c02]
|
||||||
BCD = [0x0223]
|
BCD = [0x0223]
|
||||||
|
|
||||||
VENDOR_NAME = 'ANDROID'
|
VENDOR_NAME = ['ANDROID', 'LINUX']
|
||||||
WINDOWS_MAIN_MEM = '__FILE-STOR_GADG'
|
WINDOWS_MAIN_MEM = ['__FILE-STOR_GADG', 'FILE-CD_GADGET']
|
||||||
WINDOWS_CARD_A_MEM = '__FILE-STOR_GADG'
|
WINDOWS_CARD_A_MEM = ['__FILE-STOR_GADG', 'FILE-CD_GADGET']
|
||||||
|
|
||||||
MAIN_MEMORY_VOLUME_LABEL = 'Edge Main Memory'
|
MAIN_MEMORY_VOLUME_LABEL = 'Edge Main Memory'
|
||||||
STORAGE_CARD_VOLUME_LABEL = 'Edge Storage Card'
|
STORAGE_CARD_VOLUME_LABEL = 'Edge Storage Card'
|
||||||
|
@ -279,7 +279,7 @@ class Worker(Thread): # Get details {{{
|
|||||||
|
|
||||||
class Amazon(Source):
|
class Amazon(Source):
|
||||||
|
|
||||||
name = 'Amazon Store'
|
name = 'Amazon Web'
|
||||||
description = _('Downloads metadata from Amazon')
|
description = _('Downloads metadata from Amazon')
|
||||||
|
|
||||||
capabilities = frozenset(['identify', 'cover'])
|
capabilities = frozenset(['identify', 'cover'])
|
||||||
@ -295,6 +295,14 @@ class Amazon(Source):
|
|||||||
'uk' : _('UK'),
|
'uk' : _('UK'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_book_url(self, identifiers): # {{{
|
||||||
|
asin = identifiers.get('amazon', None)
|
||||||
|
if asin is None:
|
||||||
|
asin = identifiers.get('asin', None)
|
||||||
|
if asin:
|
||||||
|
return 'http://amzn.com/%s'%asin
|
||||||
|
# }}}
|
||||||
|
|
||||||
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
||||||
domain = self.prefs.get('domain', 'com')
|
domain = self.prefs.get('domain', 'com')
|
||||||
|
|
||||||
|
@ -301,6 +301,13 @@ class Source(Plugin):
|
|||||||
|
|
||||||
# Metadata API {{{
|
# Metadata API {{{
|
||||||
|
|
||||||
|
def get_book_url(self, identifiers):
|
||||||
|
'''
|
||||||
|
Return the URL for the book identified by identifiers at this source.
|
||||||
|
If no URL is found, return None.
|
||||||
|
'''
|
||||||
|
return None
|
||||||
|
|
||||||
def get_cached_cover_url(self, identifiers):
|
def get_cached_cover_url(self, identifiers):
|
||||||
'''
|
'''
|
||||||
Return cached cover URL for the book identified by
|
Return cached cover URL for the book identified by
|
||||||
|
@ -167,6 +167,12 @@ class GoogleBooks(Source):
|
|||||||
|
|
||||||
GOOGLE_COVER = 'http://books.google.com/books?id=%s&printsec=frontcover&img=1'
|
GOOGLE_COVER = 'http://books.google.com/books?id=%s&printsec=frontcover&img=1'
|
||||||
|
|
||||||
|
def get_book_url(self, identifiers): # {{{
|
||||||
|
goog = identifiers.get('google', None)
|
||||||
|
if goog is not None:
|
||||||
|
return 'http://books.google.com/books?id=%s'%goog
|
||||||
|
# }}}
|
||||||
|
|
||||||
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
||||||
BASE_URL = 'http://books.google.com/books/feeds/volumes?'
|
BASE_URL = 'http://books.google.com/books/feeds/volumes?'
|
||||||
isbn = check_isbn(identifiers.get('isbn', None))
|
isbn = check_isbn(identifiers.get('isbn', None))
|
||||||
|
@ -14,7 +14,7 @@ from threading import Thread
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from calibre.customize.ui import metadata_plugins
|
from calibre.customize.ui import metadata_plugins, all_metadata_plugins
|
||||||
from calibre.ebooks.metadata.sources.base import create_log, msprefs
|
from calibre.ebooks.metadata.sources.base import create_log, msprefs
|
||||||
from calibre.ebooks.metadata.xisbn import xisbn
|
from calibre.ebooks.metadata.xisbn import xisbn
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
@ -217,6 +217,10 @@ class ISBNMerge(object):
|
|||||||
for r in results:
|
for r in results:
|
||||||
ans.identifiers.update(r.identifiers)
|
ans.identifiers.update(r.identifiers)
|
||||||
|
|
||||||
|
# Cover URL
|
||||||
|
ans.has_cached_cover_url = bool([r for r in results if
|
||||||
|
getattr(r, 'has_cached_cover_url', False)])
|
||||||
|
|
||||||
# Merge any other fields with no special handling (random merge)
|
# Merge any other fields with no special handling (random merge)
|
||||||
touched_fields = set()
|
touched_fields = set()
|
||||||
for r in results:
|
for r in results:
|
||||||
@ -362,6 +366,18 @@ def identify(log, abort, # {{{
|
|||||||
return results
|
return results
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
def urls_from_identifiers(identifiers): # {{{
|
||||||
|
ans = []
|
||||||
|
for plugin in all_metadata_plugins():
|
||||||
|
try:
|
||||||
|
url = plugin.get_book_url(identifiers)
|
||||||
|
if url is not None:
|
||||||
|
ans.append((plugin.name, url))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return ans
|
||||||
|
# }}}
|
||||||
|
|
||||||
if __name__ == '__main__': # tests {{{
|
if __name__ == '__main__': # tests {{{
|
||||||
# To run these test use: calibre-debug -e
|
# To run these test use: calibre-debug -e
|
||||||
# src/calibre/ebooks/metadata/sources/identify.py
|
# src/calibre/ebooks/metadata/sources/identify.py
|
||||||
|
@ -97,6 +97,10 @@ class CSSSelector(etree.XPath):
|
|||||||
|
|
||||||
def __init__(self, css, namespaces=XPNSMAP):
|
def __init__(self, css, namespaces=XPNSMAP):
|
||||||
css = self.MIN_SPACE_RE.sub(r'\1', css)
|
css = self.MIN_SPACE_RE.sub(r'\1', css)
|
||||||
|
if isinstance(css, unicode):
|
||||||
|
# Workaround for bug in lxml on windows/OS X that causes a massive
|
||||||
|
# memory leak with non ASCII selectors
|
||||||
|
css = css.encode('ascii', 'ignore').decode('ascii')
|
||||||
try:
|
try:
|
||||||
path = css_to_xpath(css)
|
path = css_to_xpath(css)
|
||||||
except UnicodeEncodeError: # Bug in css_to_xpath
|
except UnicodeEncodeError: # Bug in css_to_xpath
|
||||||
|
@ -13,7 +13,7 @@ from functools import partial
|
|||||||
|
|
||||||
from calibre.ebooks import ConversionError, DRMError
|
from calibre.ebooks import ConversionError, DRMError
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre import isosx, iswindows, islinux, isfreebsd
|
from calibre.constants import isosx, iswindows, islinux, isfreebsd
|
||||||
from calibre import CurrentDir
|
from calibre import CurrentDir
|
||||||
|
|
||||||
PDFTOHTML = 'pdftohtml'
|
PDFTOHTML = 'pdftohtml'
|
||||||
@ -43,6 +43,8 @@ def pdftohtml(output_dir, pdf_path, no_images):
|
|||||||
# This is neccessary as pdftohtml doesn't always (linux) respect absolute paths
|
# This is neccessary as pdftohtml doesn't always (linux) respect absolute paths
|
||||||
pdf_path = os.path.abspath(pdf_path)
|
pdf_path = os.path.abspath(pdf_path)
|
||||||
cmd = [PDFTOHTML, '-enc', 'UTF-8', '-noframes', '-p', '-nomerge', '-nodrm', '-q', pdf_path, os.path.basename(index)]
|
cmd = [PDFTOHTML, '-enc', 'UTF-8', '-noframes', '-p', '-nomerge', '-nodrm', '-q', pdf_path, os.path.basename(index)]
|
||||||
|
if isfreebsd:
|
||||||
|
cmd.remove('-nodrm')
|
||||||
if no_images:
|
if no_images:
|
||||||
cmd.append('-i')
|
cmd.append('-i')
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
|||||||
|
|
||||||
ORG_NAME = 'KovidsBrain'
|
ORG_NAME = 'KovidsBrain'
|
||||||
APP_UID = 'libprs500'
|
APP_UID = 'libprs500'
|
||||||
from calibre.constants import islinux, iswindows, isfreebsd, isfrozen
|
from calibre.constants import islinux, iswindows, isfreebsd, isfrozen, isosx
|
||||||
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
||||||
from calibre.utils.localization import set_qt_translator
|
from calibre.utils.localization import set_qt_translator
|
||||||
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
||||||
@ -23,21 +23,45 @@ from calibre.utils.date import UNDEFINED_DATE
|
|||||||
# Setup gprefs {{{
|
# Setup gprefs {{{
|
||||||
gprefs = JSONConfig('gui')
|
gprefs = JSONConfig('gui')
|
||||||
|
|
||||||
gprefs.defaults['action-layout-toolbar'] = (
|
if isosx:
|
||||||
|
gprefs.defaults['action-layout-menubar'] = (
|
||||||
|
'Add Books', 'Edit Metadata', 'Convert Books',
|
||||||
|
'Choose Library', 'Save To Disk', 'Preferences',
|
||||||
|
'Help',
|
||||||
|
)
|
||||||
|
gprefs.defaults['action-layout-menubar-device'] = (
|
||||||
|
'Add Books', 'Edit Metadata', 'Convert Books',
|
||||||
|
'Location Manager', 'Send To Device',
|
||||||
|
'Save To Disk', 'Preferences', 'Help',
|
||||||
|
)
|
||||||
|
gprefs.defaults['action-layout-toolbar'] = (
|
||||||
|
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
|
||||||
|
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk',
|
||||||
|
'Connect Share', None, 'Remove Books',
|
||||||
|
)
|
||||||
|
gprefs.defaults['action-layout-toolbar-device'] = (
|
||||||
|
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
||||||
|
'Send To Device', None, None, 'Location Manager', None, None,
|
||||||
|
'Fetch News', 'Save To Disk', 'Connect Share', None,
|
||||||
|
'Remove Books',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
gprefs.defaults['action-layout-menubar'] = ()
|
||||||
|
gprefs.defaults['action-layout-menubar-device'] = ()
|
||||||
|
gprefs.defaults['action-layout-toolbar'] = (
|
||||||
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
|
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
|
||||||
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk',
|
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk',
|
||||||
'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences',
|
'Connect Share', None, 'Remove Books', None, 'Help', 'Preferences',
|
||||||
)
|
)
|
||||||
|
gprefs.defaults['action-layout-toolbar-device'] = (
|
||||||
gprefs.defaults['action-layout-toolbar-child'] = ()
|
|
||||||
|
|
||||||
gprefs.defaults['action-layout-toolbar-device'] = (
|
|
||||||
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View',
|
||||||
'Send To Device', None, None, 'Location Manager', None, None,
|
'Send To Device', None, None, 'Location Manager', None, None,
|
||||||
'Fetch News', 'Save To Disk', 'Connect Share', None,
|
'Fetch News', 'Save To Disk', 'Connect Share', None,
|
||||||
'Remove Books', None, 'Help', 'Preferences',
|
'Remove Books', None, 'Help', 'Preferences',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gprefs.defaults['action-layout-toolbar-child'] = ()
|
||||||
|
|
||||||
gprefs.defaults['action-layout-context-menu'] = (
|
gprefs.defaults['action-layout-context-menu'] = (
|
||||||
'Edit Metadata', 'Send To Device', 'Save To Disk',
|
'Edit Metadata', 'Send To Device', 'Save To Disk',
|
||||||
'Connect Share', 'Copy To Library', None,
|
'Connect Share', 'Copy To Library', None,
|
||||||
@ -57,6 +81,7 @@ gprefs.defaults['toolbar_text'] = 'auto'
|
|||||||
gprefs.defaults['font'] = None
|
gprefs.defaults['font'] = None
|
||||||
gprefs.defaults['tags_browser_partition_method'] = 'first letter'
|
gprefs.defaults['tags_browser_partition_method'] = 'first letter'
|
||||||
gprefs.defaults['tags_browser_collapse_at'] = 100
|
gprefs.defaults['tags_browser_collapse_at'] = 100
|
||||||
|
gprefs.defaults['edit_metadata_single_layout'] = 'default'
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class InterfaceAction(QObject):
|
|||||||
dont_remove_from = frozenset([])
|
dont_remove_from = frozenset([])
|
||||||
|
|
||||||
all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
|
all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
|
||||||
'context-menu-device', 'toolbar-child'])
|
'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device'])
|
||||||
|
|
||||||
#: Type of action
|
#: Type of action
|
||||||
#: 'current' means acts on the current view
|
#: 'current' means acts on the current view
|
||||||
@ -145,11 +145,10 @@ class InterfaceAction(QObject):
|
|||||||
ans[candidate] = zf.read(candidate)
|
ans[candidate] = zf.read(candidate)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
'''
|
'''
|
||||||
Setup this plugin. Only called once during initialization. self.gui is
|
Setup this plugin. Only called once during initialization. self.gui is
|
||||||
available. The action secified by :attr:`action_spec` is available as
|
available. The action specified by :attr:`action_spec` is available as
|
||||||
``self.qaction``.
|
``self.qaction``.
|
||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
@ -12,7 +12,7 @@ class AddToLibraryAction(InterfaceAction):
|
|||||||
name = 'Add To Library'
|
name = 'Add To Library'
|
||||||
action_spec = (_('Add books to library'), 'add_book.png',
|
action_spec = (_('Add books to library'), 'add_book.png',
|
||||||
_('Add books to your calibre library from the connected device'), None)
|
_('Add books to your calibre library from the connected device'), None)
|
||||||
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child'])
|
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -18,6 +18,7 @@ class FetchAnnotationsAction(InterfaceAction):
|
|||||||
|
|
||||||
name = 'Fetch Annotations'
|
name = 'Fetch Annotations'
|
||||||
action_spec = (_('Fetch annotations (experimental)'), None, None, None)
|
action_spec = (_('Fetch annotations (experimental)'), None, None, None)
|
||||||
|
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -18,7 +18,7 @@ class GenerateCatalogAction(InterfaceAction):
|
|||||||
|
|
||||||
name = 'Generate Catalog'
|
name = 'Generate Catalog'
|
||||||
action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None)
|
action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None)
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
|
|
||||||
def generate_catalog(self):
|
def generate_catalog(self):
|
||||||
rows = self.gui.library_view.selectionModel().selectedRows()
|
rows = self.gui.library_view.selectionModel().selectedRows()
|
||||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os, shutil
|
import os, shutil
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QMenu, Qt, QInputDialog
|
from PyQt4.Qt import QMenu, Qt, QInputDialog, QToolButton
|
||||||
|
|
||||||
from calibre import isbytestring
|
from calibre import isbytestring
|
||||||
from calibre.constants import filesystem_encoding
|
from calibre.constants import filesystem_encoding
|
||||||
@ -80,7 +80,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
name = 'Choose Library'
|
name = 'Choose Library'
|
||||||
action_spec = (_('%d books'), 'lt.png',
|
action_spec = (_('%d books'), 'lt.png',
|
||||||
_('Choose calibre library to work with'), None)
|
_('Choose calibre library to work with'), None)
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
self.count_changed(0)
|
self.count_changed(0)
|
||||||
@ -88,6 +88,9 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
|
|
||||||
self.stats = LibraryUsageStats()
|
self.stats = LibraryUsageStats()
|
||||||
|
self.popup_type = (QToolButton.InstantPopup if len(self.stats.stats) > 1 else
|
||||||
|
QToolButton.MenuButtonPopup)
|
||||||
|
|
||||||
self.create_action(spec=(_('Switch/create library...'), 'lt.png', None,
|
self.create_action(spec=(_('Switch/create library...'), 'lt.png', None,
|
||||||
None), attr='action_choose')
|
None), attr='action_choose')
|
||||||
self.action_choose.triggered.connect(self.choose_library,
|
self.action_choose.triggered.connect(self.choose_library,
|
||||||
@ -123,6 +126,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
self.choose_menu.addAction(ac)
|
self.choose_menu.addAction(ac)
|
||||||
|
|
||||||
|
|
||||||
self.rename_separator = self.choose_menu.addSeparator()
|
self.rename_separator = self.choose_menu.addSeparator()
|
||||||
|
|
||||||
self.maintenance_menu = QMenu(_('Library Maintenance'))
|
self.maintenance_menu = QMenu(_('Library Maintenance'))
|
||||||
@ -172,6 +176,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
return
|
return
|
||||||
db = self.gui.library_view.model().db
|
db = self.gui.library_view.model().db
|
||||||
locations = list(self.stats.locations(db))
|
locations = list(self.stats.locations(db))
|
||||||
|
|
||||||
for ac in self.switch_actions:
|
for ac in self.switch_actions:
|
||||||
ac.setVisible(False)
|
ac.setVisible(False)
|
||||||
self.quick_menu.clear()
|
self.quick_menu.clear()
|
||||||
@ -205,7 +210,6 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
rename_actions, delete_actions, qs_actions,
|
rename_actions, delete_actions, qs_actions,
|
||||||
self.action_choose)
|
self.action_choose)
|
||||||
|
|
||||||
|
|
||||||
def location_selected(self, loc):
|
def location_selected(self, loc):
|
||||||
enabled = loc == 'library'
|
enabled = loc == 'library'
|
||||||
self.qaction.setEnabled(enabled)
|
self.qaction.setEnabled(enabled)
|
||||||
|
@ -20,7 +20,7 @@ class ConvertAction(InterfaceAction):
|
|||||||
|
|
||||||
name = 'Convert Books'
|
name = 'Convert Books'
|
||||||
action_spec = (_('Convert books'), 'convert.png', None, _('C'))
|
action_spec = (_('Convert books'), 'convert.png', None, _('C'))
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -24,7 +24,7 @@ class ShareConnMenu(QMenu): # {{{
|
|||||||
|
|
||||||
config_email = pyqtSignal()
|
config_email = pyqtSignal()
|
||||||
toggle_server = pyqtSignal()
|
toggle_server = pyqtSignal()
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QMenu.__init__(self, parent)
|
QMenu.__init__(self, parent)
|
||||||
@ -121,8 +121,7 @@ class SendToDeviceAction(InterfaceAction):
|
|||||||
|
|
||||||
name = 'Send To Device'
|
name = 'Send To Device'
|
||||||
action_spec = (_('Send to device'), 'sync.png', None, _('D'))
|
action_spec = (_('Send to device'), 'sync.png', None, _('D'))
|
||||||
dont_remove_from = frozenset(['toolbar-device'])
|
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
|
||||||
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child'])
|
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
self.qaction.triggered.connect(self.do_sync)
|
self.qaction.triggered.connect(self.do_sync)
|
||||||
@ -166,6 +165,10 @@ class ConnectShareAction(InterfaceAction):
|
|||||||
|
|
||||||
def content_server_state_changed(self, running):
|
def content_server_state_changed(self, running):
|
||||||
self.share_conn_menu.server_state_changed(running)
|
self.share_conn_menu.server_state_changed(running)
|
||||||
|
if running:
|
||||||
|
self.qaction.setIcon(QIcon(I('connect_share_on.png')))
|
||||||
|
else:
|
||||||
|
self.qaction.setIcon(QIcon(I('connect_share.png')))
|
||||||
|
|
||||||
def toggle_content_server(self):
|
def toggle_content_server(self):
|
||||||
if self.gui.content_server is None:
|
if self.gui.content_server is None:
|
||||||
|
@ -12,7 +12,7 @@ class EditCollectionsAction(InterfaceAction):
|
|||||||
name = 'Edit Collections'
|
name = 'Edit Collections'
|
||||||
action_spec = (_('Manage collections'), None,
|
action_spec = (_('Manage collections'), None,
|
||||||
_('Manage the collections on this device'), None)
|
_('Manage the collections on this device'), None)
|
||||||
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child'])
|
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -141,15 +141,18 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
list(range(self.gui.library_view.model().rowCount(QModelIndex())))
|
list(range(self.gui.library_view.model().rowCount(QModelIndex())))
|
||||||
current_row = row_list.index(cr)
|
current_row = row_list.index(cr)
|
||||||
|
|
||||||
if test_eight_code:
|
func = (self.do_edit_metadata if test_eight_code else
|
||||||
changed = self.do_edit_metadata(row_list, current_row)
|
self.do_edit_metadata_old)
|
||||||
else:
|
changed, rows_to_refresh = func(row_list, current_row)
|
||||||
changed = self.do_edit_metadata_old(row_list, current_row)
|
|
||||||
|
m = self.gui.library_view.model()
|
||||||
|
|
||||||
|
if rows_to_refresh:
|
||||||
|
m.refresh_rows(rows_to_refresh)
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
self.gui.library_view.model().refresh_ids(list(changed))
|
m.refresh_ids(list(changed))
|
||||||
current = self.gui.library_view.currentIndex()
|
current = self.gui.library_view.currentIndex()
|
||||||
m = self.gui.library_view.model()
|
|
||||||
if self.gui.cover_flow:
|
if self.gui.cover_flow:
|
||||||
self.gui.cover_flow.dataChanged()
|
self.gui.cover_flow.dataChanged()
|
||||||
m.current_changed(current, previous)
|
m.current_changed(current, previous)
|
||||||
@ -183,6 +186,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
current_row += d.row_delta
|
current_row += d.row_delta
|
||||||
self.gui.library_view.set_current_row(current_row)
|
self.gui.library_view.set_current_row(current_row)
|
||||||
self.gui.library_view.scroll_to_row(current_row)
|
self.gui.library_view.scroll_to_row(current_row)
|
||||||
|
return changed, set()
|
||||||
|
|
||||||
def do_edit_metadata(self, row_list, current_row):
|
def do_edit_metadata(self, row_list, current_row):
|
||||||
from calibre.gui2.metadata.single import edit_metadata
|
from calibre.gui2.metadata.single import edit_metadata
|
||||||
@ -190,7 +194,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
changed, rows_to_refresh = edit_metadata(db, row_list, current_row,
|
changed, rows_to_refresh = edit_metadata(db, row_list, current_row,
|
||||||
parent=self.gui, view_slot=self.view_format_callback,
|
parent=self.gui, view_slot=self.view_format_callback,
|
||||||
set_current_callback=self.set_current_callback)
|
set_current_callback=self.set_current_callback)
|
||||||
return changed
|
return changed, rows_to_refresh
|
||||||
|
|
||||||
def set_current_callback(self, id_):
|
def set_current_callback(self, id_):
|
||||||
db = self.gui.library_view.model().db
|
db = self.gui.library_view.model().db
|
||||||
|
@ -11,7 +11,7 @@ class NextMatchAction(InterfaceAction):
|
|||||||
name = 'Move to next highlighted book'
|
name = 'Move to next highlighted book'
|
||||||
action_spec = (_('Move to next match'), 'arrow-down.png',
|
action_spec = (_('Move to next match'), 'arrow-down.png',
|
||||||
_('Move to next highlighted match'), [_('N'), _('F3')])
|
_('Move to next highlighted match'), [_('N'), _('F3')])
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -13,7 +13,7 @@ class OpenFolderAction(InterfaceAction):
|
|||||||
name = 'Open Folder'
|
name = 'Open Folder'
|
||||||
action_spec = (_('Open containing folder'), 'document_open.png', None,
|
action_spec = (_('Open containing folder'), 'document_open.png', None,
|
||||||
_('O'))
|
_('O'))
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -16,7 +16,6 @@ class PreferencesAction(InterfaceAction):
|
|||||||
|
|
||||||
name = 'Preferences'
|
name = 'Preferences'
|
||||||
action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P'))
|
action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P'))
|
||||||
dont_remove_from = frozenset(['toolbar'])
|
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
pm = QMenu()
|
pm = QMenu()
|
||||||
|
@ -15,7 +15,7 @@ class ShowBookDetailsAction(InterfaceAction):
|
|||||||
name = 'Show Book Details'
|
name = 'Show Book Details'
|
||||||
action_spec = (_('Show book details'), 'dialog_information.png', None,
|
action_spec = (_('Show book details'), 'dialog_information.png', None,
|
||||||
_('I'))
|
_('I'))
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -15,7 +15,7 @@ class TweakEpubAction(InterfaceAction):
|
|||||||
action_spec = (_('Tweak ePub'), 'trim.png',
|
action_spec = (_('Tweak ePub'), 'trim.png',
|
||||||
_('Make small changes to ePub format books'),
|
_('Make small changes to ePub format books'),
|
||||||
_('T'))
|
_('T'))
|
||||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
@ -62,7 +62,7 @@ class Bool(Base):
|
|||||||
w = self.widgets[1]
|
w = self.widgets[1]
|
||||||
items = [_('Yes'), _('No'), _('Undefined')]
|
items = [_('Yes'), _('No'), _('Undefined')]
|
||||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
if not self.db.prefs.get('bools_are_tristate'):
|
||||||
items = items[:-1]
|
items = items[:-1]
|
||||||
icons = icons[:-1]
|
icons = icons[:-1]
|
||||||
for icon, text in zip(icons, items):
|
for icon, text in zip(icons, items):
|
||||||
@ -70,7 +70,7 @@ class Bool(Base):
|
|||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
val = {None: 2, False: 1, True: 0}[val]
|
val = {None: 2, False: 1, True: 0}[val]
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val == 2:
|
if not self.db.prefs.get('bools_are_tristate') and val == 2:
|
||||||
val = 1
|
val = 1
|
||||||
self.widgets[1].setCurrentIndex(val)
|
self.widgets[1].setCurrentIndex(val)
|
||||||
|
|
||||||
@ -549,7 +549,7 @@ class BulkBool(BulkBase, Bool):
|
|||||||
value = None
|
value = None
|
||||||
for book_id in book_ids:
|
for book_id in book_ids:
|
||||||
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
if not self.db.prefs.get('bools_are_tristate') and val is None:
|
||||||
val = False
|
val = False
|
||||||
if value is not None and value != val:
|
if value is not None and value != val:
|
||||||
return None
|
return None
|
||||||
@ -559,7 +559,7 @@ 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')]
|
items = [_('Yes'), _('No')]
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
if not self.db.prefs.get('bools_are_tristate'):
|
||||||
items.append('')
|
items.append('')
|
||||||
else:
|
else:
|
||||||
items.append(_('Undefined'))
|
items.append(_('Undefined'))
|
||||||
@ -571,7 +571,7 @@ class BulkBool(BulkBase, Bool):
|
|||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.main_widget.currentIndex()
|
val = self.main_widget.currentIndex()
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
if not self.db.prefs.get('bools_are_tristate'):
|
||||||
return {2: False, 1: False, 0: True}[val]
|
return {2: False, 1: False, 0: True}[val]
|
||||||
else:
|
else:
|
||||||
return {2: None, 1: False, 0: True}[val]
|
return {2: None, 1: False, 0: True}[val]
|
||||||
@ -586,13 +586,13 @@ class BulkBool(BulkBase, Bool):
|
|||||||
return
|
return
|
||||||
val = self.gui_val
|
val = self.gui_val
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
if not self.db.prefs.get('bools_are_tristate') and val is None:
|
||||||
val = False
|
val = False
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||||
|
|
||||||
def a_c_checkbox_changed(self):
|
def a_c_checkbox_changed(self):
|
||||||
if not self.ignore_change_signals:
|
if not self.ignore_change_signals:
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and \
|
if not self.db.prefs.get('bools_are_tristate') and \
|
||||||
self.main_widget.currentIndex() == 2:
|
self.main_widget.currentIndex() == 2:
|
||||||
self.a_c_checkbox.setChecked(False)
|
self.a_c_checkbox.setChecked(False)
|
||||||
else:
|
else:
|
||||||
|
@ -7,12 +7,12 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, \
|
from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize,
|
||||||
pyqtSignal, QToolButton, QMenu, \
|
pyqtSignal, QToolButton, QMenu, QMenuBar, QAction,
|
||||||
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup
|
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
|
||||||
|
|
||||||
|
|
||||||
from calibre.constants import __appname__
|
from calibre.constants import __appname__, isosx
|
||||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||||
from calibre.gui2.throbber import ThrobbingButton
|
from calibre.gui2.throbber import ThrobbingButton
|
||||||
from calibre.gui2 import gprefs
|
from calibre.gui2 import gprefs
|
||||||
@ -238,18 +238,117 @@ class Spacer(QWidget): # {{{
|
|||||||
self.l.addStretch(10)
|
self.l.addStretch(10)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class ToolBar(QToolBar): # {{{
|
class MenuAction(QAction): # {{{
|
||||||
|
|
||||||
def __init__(self, donate, location_manager, child_bar, parent):
|
def __init__(self, clone, parent):
|
||||||
QToolBar.__init__(self, parent)
|
QAction.__init__(self, clone.text(), parent)
|
||||||
|
self.clone = clone
|
||||||
|
clone.changed.connect(self.clone_changed)
|
||||||
|
|
||||||
|
def clone_changed(self):
|
||||||
|
self.setText(self.clone.text())
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class MenuBar(QMenuBar): # {{{
|
||||||
|
|
||||||
|
def __init__(self, location_manager, parent):
|
||||||
|
QMenuBar.__init__(self, parent)
|
||||||
self.gui = parent
|
self.gui = parent
|
||||||
self.child_bar = child_bar
|
self.setNativeMenuBar(True)
|
||||||
|
|
||||||
|
self.location_manager = location_manager
|
||||||
|
self.location_manager.locations_changed.connect(self.build_bar)
|
||||||
|
self.added_actions = []
|
||||||
|
|
||||||
|
self.donate_action = QAction(_('Donate'), self)
|
||||||
|
self.donate_menu = QMenu()
|
||||||
|
self.donate_menu.addAction(self.gui.donate_action)
|
||||||
|
self.donate_action.setMenu(self.donate_menu)
|
||||||
|
self.build_bar()
|
||||||
|
|
||||||
|
def build_bar(self, changed_action=None):
|
||||||
|
showing_device = self.location_manager.has_device
|
||||||
|
actions = '-device' if showing_device else ''
|
||||||
|
actions = gprefs['action-layout-menubar'+actions]
|
||||||
|
|
||||||
|
show_main = len(actions) > 0
|
||||||
|
self.setVisible(show_main)
|
||||||
|
|
||||||
|
for ac in self.added_actions:
|
||||||
|
m = ac.menu()
|
||||||
|
if m is not None:
|
||||||
|
m.setVisible(False)
|
||||||
|
|
||||||
|
self.clear()
|
||||||
|
self.added_actions = []
|
||||||
|
self.action_map = {}
|
||||||
|
|
||||||
|
for what in actions:
|
||||||
|
if what is None:
|
||||||
|
continue
|
||||||
|
elif what == 'Location Manager':
|
||||||
|
for ac in self.location_manager.available_actions:
|
||||||
|
ac = self.build_menu(ac)
|
||||||
|
self.addAction(ac)
|
||||||
|
self.added_actions.append(ac)
|
||||||
|
elif what == 'Donate':
|
||||||
|
self.addAction(self.donate_action)
|
||||||
|
elif what in self.gui.iactions:
|
||||||
|
action = self.gui.iactions[what]
|
||||||
|
ac = self.build_menu(action.qaction)
|
||||||
|
self.addAction(ac)
|
||||||
|
self.added_actions.append(ac)
|
||||||
|
|
||||||
|
def build_menu(self, action):
|
||||||
|
m = action.menu()
|
||||||
|
ac = MenuAction(action, self)
|
||||||
|
if m is None:
|
||||||
|
m = QMenu()
|
||||||
|
m.addAction(action)
|
||||||
|
ac.setMenu(m)
|
||||||
|
return ac
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class BaseToolBar(QToolBar): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QToolBar.__init__(self, parent)
|
||||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
self.setMovable(False)
|
self.setMovable(False)
|
||||||
self.setFloatable(False)
|
self.setFloatable(False)
|
||||||
self.setOrientation(Qt.Horizontal)
|
self.setOrientation(Qt.Horizontal)
|
||||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||||
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
||||||
|
self.preferred_width = self.sizeHint().width()
|
||||||
|
|
||||||
|
def resizeEvent(self, ev):
|
||||||
|
QToolBar.resizeEvent(self, ev)
|
||||||
|
style = self.get_text_style()
|
||||||
|
self.setToolButtonStyle(style)
|
||||||
|
|
||||||
|
def get_text_style(self):
|
||||||
|
style = Qt.ToolButtonTextUnderIcon
|
||||||
|
s = gprefs['toolbar_icon_size']
|
||||||
|
if s != 'off':
|
||||||
|
p = gprefs['toolbar_text']
|
||||||
|
if p == 'never':
|
||||||
|
style = Qt.ToolButtonIconOnly
|
||||||
|
elif p == 'auto' and self.preferred_width > self.width()+35:
|
||||||
|
style = Qt.ToolButtonIconOnly
|
||||||
|
return style
|
||||||
|
|
||||||
|
def contextMenuEvent(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ToolBar(BaseToolBar): # {{{
|
||||||
|
|
||||||
|
def __init__(self, donate, location_manager, child_bar, parent):
|
||||||
|
BaseToolBar.__init__(self, parent)
|
||||||
|
self.gui = parent
|
||||||
|
self.child_bar = child_bar
|
||||||
self.donate_button = donate
|
self.donate_button = donate
|
||||||
self.apply_settings()
|
self.apply_settings()
|
||||||
|
|
||||||
@ -259,24 +358,20 @@ class ToolBar(QToolBar): # {{{
|
|||||||
donate.setCursor(Qt.PointingHandCursor)
|
donate.setCursor(Qt.PointingHandCursor)
|
||||||
self.added_actions = []
|
self.added_actions = []
|
||||||
self.build_bar()
|
self.build_bar()
|
||||||
self.preferred_width = self.sizeHint().width()
|
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
def apply_settings(self):
|
def apply_settings(self):
|
||||||
sz = gprefs['toolbar_icon_size']
|
sz = gprefs['toolbar_icon_size']
|
||||||
sz = {'small':24, 'medium':48, 'large':64}[sz]
|
sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz]
|
||||||
self.setIconSize(QSize(sz, sz))
|
self.setIconSize(QSize(sz, sz))
|
||||||
self.child_bar.setIconSize(QSize(sz, sz))
|
self.child_bar.setIconSize(QSize(sz, sz))
|
||||||
style = Qt.ToolButtonTextUnderIcon
|
style = Qt.ToolButtonTextUnderIcon
|
||||||
if gprefs['toolbar_text'] == 'never':
|
if sz > 0 and gprefs['toolbar_text'] == 'never':
|
||||||
style = Qt.ToolButtonIconOnly
|
style = Qt.ToolButtonIconOnly
|
||||||
self.setToolButtonStyle(style)
|
self.setToolButtonStyle(style)
|
||||||
self.child_bar.setToolButtonStyle(style)
|
self.child_bar.setToolButtonStyle(style)
|
||||||
self.donate_button.set_normal_icon_size(sz, sz)
|
self.donate_button.set_normal_icon_size(sz, sz)
|
||||||
|
|
||||||
def contextMenuEvent(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def build_bar(self):
|
def build_bar(self):
|
||||||
self.showing_donate = False
|
self.showing_donate = False
|
||||||
showing_device = self.location_manager.has_device
|
showing_device = self.location_manager.has_device
|
||||||
@ -284,6 +379,8 @@ class ToolBar(QToolBar): # {{{
|
|||||||
mactions = gprefs['action-layout-toolbar'+mactions]
|
mactions = gprefs['action-layout-toolbar'+mactions]
|
||||||
cactions = gprefs['action-layout-toolbar-child']
|
cactions = gprefs['action-layout-toolbar-child']
|
||||||
|
|
||||||
|
show_main = len(mactions) > 0
|
||||||
|
self.setVisible(show_main)
|
||||||
show_child = len(cactions) > 0
|
show_child = len(cactions) > 0
|
||||||
self.child_bar.setVisible(show_child)
|
self.child_bar.setVisible(show_child)
|
||||||
|
|
||||||
@ -309,6 +406,8 @@ class ToolBar(QToolBar): # {{{
|
|||||||
self.d_widget = QWidget()
|
self.d_widget = QWidget()
|
||||||
self.d_widget.setLayout(QVBoxLayout())
|
self.d_widget.setLayout(QVBoxLayout())
|
||||||
self.d_widget.layout().addWidget(self.donate_button)
|
self.d_widget.layout().addWidget(self.donate_button)
|
||||||
|
if isosx:
|
||||||
|
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
|
||||||
bar.addWidget(self.d_widget)
|
bar.addWidget(self.d_widget)
|
||||||
self.showing_donate = True
|
self.showing_donate = True
|
||||||
elif what in self.gui.iactions:
|
elif what in self.gui.iactions:
|
||||||
@ -316,6 +415,8 @@ class ToolBar(QToolBar): # {{{
|
|||||||
bar.addAction(action.qaction)
|
bar.addAction(action.qaction)
|
||||||
self.added_actions.append(action.qaction)
|
self.added_actions.append(action.qaction)
|
||||||
self.setup_tool_button(bar, action.qaction, action.popup_type)
|
self.setup_tool_button(bar, action.qaction, action.popup_type)
|
||||||
|
self.preferred_width = self.sizeHint().width()
|
||||||
|
self.child_bar.preferred_width = self.child_bar.sizeHint().width()
|
||||||
|
|
||||||
def setup_tool_button(self, bar, ac, menu_mode=None):
|
def setup_tool_button(self, bar, ac, menu_mode=None):
|
||||||
ch = bar.widgetForAction(ac)
|
ch = bar.widgetForAction(ac)
|
||||||
@ -325,19 +426,7 @@ class ToolBar(QToolBar): # {{{
|
|||||||
ch.setAutoRaise(True)
|
ch.setAutoRaise(True)
|
||||||
if ac.menu() is not None and menu_mode is not None:
|
if ac.menu() is not None and menu_mode is not None:
|
||||||
ch.setPopupMode(menu_mode)
|
ch.setPopupMode(menu_mode)
|
||||||
|
return ch
|
||||||
def resizeEvent(self, ev):
|
|
||||||
QToolBar.resizeEvent(self, ev)
|
|
||||||
style = Qt.ToolButtonTextUnderIcon
|
|
||||||
p = gprefs['toolbar_text']
|
|
||||||
if p == 'never':
|
|
||||||
style = Qt.ToolButtonIconOnly
|
|
||||||
|
|
||||||
if p == 'auto' and self.preferred_width > self.width()+35 and \
|
|
||||||
not gprefs['action-layout-toolbar-child']:
|
|
||||||
style = Qt.ToolButtonIconOnly
|
|
||||||
|
|
||||||
self.setToolButtonStyle(style)
|
|
||||||
|
|
||||||
def database_changed(self, db):
|
def database_changed(self, db):
|
||||||
pass
|
pass
|
||||||
@ -416,11 +505,14 @@ class MainWindowMixin(object): # {{{
|
|||||||
self.iactions['Fetch News'].init_scheduler(db)
|
self.iactions['Fetch News'].init_scheduler(db)
|
||||||
|
|
||||||
self.search_bar = SearchBar(self)
|
self.search_bar = SearchBar(self)
|
||||||
self.child_bar = QToolBar(self)
|
self.child_bar = BaseToolBar(self)
|
||||||
self.tool_bar = ToolBar(self.donate_button,
|
self.tool_bar = ToolBar(self.donate_button,
|
||||||
self.location_manager, self.child_bar, self)
|
self.location_manager, self.child_bar, self)
|
||||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||||
self.addToolBar(Qt.BottomToolBarArea, self.child_bar)
|
self.addToolBar(Qt.BottomToolBarArea, self.child_bar)
|
||||||
|
self.menu_bar = MenuBar(self.location_manager, self)
|
||||||
|
self.setMenuBar(self.menu_bar)
|
||||||
|
self.setUnifiedTitleAndToolBarOnMac(True)
|
||||||
|
|
||||||
l = self.centralwidget.layout()
|
l = self.centralwidget.layout()
|
||||||
l.addWidget(self.search_bar)
|
l.addWidget(self.search_bar)
|
||||||
|
@ -353,7 +353,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
|
|||||||
editor = DelegateCB(parent)
|
editor = DelegateCB(parent)
|
||||||
items = [_('Y'), _('N'), ' ']
|
items = [_('Y'), _('N'), ' ']
|
||||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
if not index.model().db.prefs.get('bools_are_tristate'):
|
||||||
items = items[:-1]
|
items = items[:-1]
|
||||||
icons = icons[:-1]
|
icons = icons[:-1]
|
||||||
for icon, text in zip(icons, items):
|
for icon, text in zip(icons, items):
|
||||||
@ -367,7 +367,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
|
|||||||
def setEditorData(self, editor, index):
|
def setEditorData(self, editor, index):
|
||||||
m = index.model()
|
m = index.model()
|
||||||
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
|
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
if not m.db.prefs.get('bools_are_tristate'):
|
||||||
val = 1 if not val else 0
|
val = 1 if not val else 0
|
||||||
else:
|
else:
|
||||||
val = 2 if val is None else 1 if not val else 0
|
val = 2 if val is None else 1 if not val else 0
|
||||||
|
@ -700,7 +700,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
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=
|
bool_cols_are_tristate=
|
||||||
tweaks['bool_custom_columns_are_tristate'] != 'no')
|
self.db.prefs.get('bools_are_tristate'))
|
||||||
elif datatype in ('int', 'float'):
|
elif datatype in ('int', 'float'):
|
||||||
self.dc[col] = functools.partial(number_type, idx=idx)
|
self.dc[col] = functools.partial(number_type, idx=idx)
|
||||||
elif datatype == 'datetime':
|
elif datatype == 'datetime':
|
||||||
@ -710,7 +710,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
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=
|
bool_cols_are_tristate=
|
||||||
tweaks['bool_custom_columns_are_tristate'] != 'no')
|
self.db.prefs.get('bools_are_tristate'))
|
||||||
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':
|
||||||
|
@ -521,7 +521,7 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{
|
class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
||||||
|
|
||||||
cc_two_column = False
|
cc_two_column = False
|
||||||
one_line_comments_toolbar = True
|
one_line_comments_toolbar = True
|
||||||
@ -654,10 +654,14 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
editors = {'default': MetadataSingleDialog, 'alt1': MetadataSingleDialogAlt1}
|
||||||
|
|
||||||
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None,
|
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None,
|
||||||
set_current_callback=None):
|
set_current_callback=None):
|
||||||
d = MetadataSingleDialog(db, parent)
|
cls = gprefs.get('edit_metadata_single_layout', '')
|
||||||
|
if cls not in editors:
|
||||||
|
cls = 'default'
|
||||||
|
d = editors[cls](db, parent)
|
||||||
d.start(row_list, current_row, view_slot=view_slot,
|
d.start(row_list, current_row, view_slot=view_slot,
|
||||||
set_current_callback=set_current_callback)
|
set_current_callback=set_current_callback)
|
||||||
return d.changed, d.rows_to_refresh
|
return d.changed, d.rows_to_refresh
|
||||||
|
@ -7,26 +7,30 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
DEBUG_DIALOG = False
|
||||||
|
|
||||||
|
# Imports {{{
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
|
from operator import attrgetter
|
||||||
|
from Queue import Queue, Empty
|
||||||
|
|
||||||
from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt,
|
from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt,
|
||||||
QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox,
|
QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox,
|
||||||
QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette)
|
QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette,
|
||||||
|
QTimer, pyqtSignal, QAbstractTableModel, QVariant, QSize, QListView,
|
||||||
|
QPixmap, QAbstractListModel, QColor, QRect, QTextBrowser)
|
||||||
from PyQt4.QtWebKit import QWebView
|
from PyQt4.QtWebKit import QWebView
|
||||||
|
|
||||||
from calibre.customize.ui import metadata_plugins
|
from calibre.customize.ui import metadata_plugins
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre.utils.logging import ThreadSafeLog, UnicodeHTMLStream
|
from calibre.utils.logging import GUILog as Log
|
||||||
from calibre.ebooks.metadata.sources.identify import identify
|
from calibre.ebooks.metadata.sources.identify import (identify,
|
||||||
|
urls_from_identifiers)
|
||||||
class Log(ThreadSafeLog): # {{{
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
|
from calibre.gui2 import error_dialog, NONE
|
||||||
def __init__(self):
|
from calibre.utils.date import utcnow, fromordinal, format_date
|
||||||
ThreadSafeLog.__init__(self, level=self.DEBUG)
|
from calibre.library.comments import comments_to_html
|
||||||
self.outputs = [UnicodeHTMLStream()]
|
from calibre import force_unicode
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.outputs[0].clear()
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class RichTextDelegate(QStyledItemDelegate): # {{{
|
class RichTextDelegate(QStyledItemDelegate): # {{{
|
||||||
@ -40,7 +44,11 @@ class RichTextDelegate(QStyledItemDelegate): # {{{
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
def sizeHint(self, option, index):
|
def sizeHint(self, option, index):
|
||||||
ans = self.to_doc(index).size().toSize()
|
doc = self.to_doc(index)
|
||||||
|
ans = doc.size().toSize()
|
||||||
|
if ans.width() > 250:
|
||||||
|
doc.setTextWidth(250)
|
||||||
|
ans = doc.size().toSize()
|
||||||
ans.setHeight(ans.height()+10)
|
ans.setHeight(ans.height()+10)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@ -56,24 +64,227 @@ class RichTextDelegate(QStyledItemDelegate): # {{{
|
|||||||
painter.restore()
|
painter.restore()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class ResultsView(QTableView):
|
class CoverDelegate(QStyledItemDelegate): # {{{
|
||||||
|
|
||||||
|
needs_redraw = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
|
|
||||||
|
self.angle = 0
|
||||||
|
self.timer = QTimer(self)
|
||||||
|
self.timer.timeout.connect(self.frame_changed)
|
||||||
|
self.color = parent.palette().color(QPalette.WindowText)
|
||||||
|
self.spinner_width = 64
|
||||||
|
|
||||||
|
def frame_changed(self, *args):
|
||||||
|
self.angle = (self.angle+30)%360
|
||||||
|
self.needs_redraw.emit()
|
||||||
|
|
||||||
|
def start_animation(self):
|
||||||
|
self.angle = 0
|
||||||
|
self.timer.start(200)
|
||||||
|
|
||||||
|
def stop_animation(self):
|
||||||
|
self.timer.stop()
|
||||||
|
|
||||||
|
def draw_spinner(self, painter, rect):
|
||||||
|
width = rect.width()
|
||||||
|
|
||||||
|
outer_radius = (width-1)*0.5
|
||||||
|
inner_radius = (width-1)*0.5*0.38
|
||||||
|
|
||||||
|
capsule_height = outer_radius - inner_radius
|
||||||
|
capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35))
|
||||||
|
capsule_radius = capsule_width//2
|
||||||
|
|
||||||
|
painter.save()
|
||||||
|
painter.setRenderHint(painter.Antialiasing)
|
||||||
|
|
||||||
|
for i in xrange(12):
|
||||||
|
color = QColor(self.color)
|
||||||
|
color.setAlphaF(1.0 - (i/12.0))
|
||||||
|
painter.setPen(Qt.NoPen)
|
||||||
|
painter.setBrush(color)
|
||||||
|
painter.save()
|
||||||
|
painter.translate(rect.center())
|
||||||
|
painter.rotate(self.angle - i*30.0)
|
||||||
|
painter.drawRoundedRect(-capsule_width*0.5,
|
||||||
|
-(inner_radius+capsule_height), capsule_width,
|
||||||
|
capsule_height, capsule_radius, capsule_radius)
|
||||||
|
painter.restore()
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
def paint(self, painter, option, index):
|
||||||
|
QStyledItemDelegate.paint(self, painter, option, index)
|
||||||
|
if self.timer.isActive() and index.data(Qt.UserRole).toBool():
|
||||||
|
rect = QRect(0, 0, self.spinner_width, self.spinner_width)
|
||||||
|
rect.moveCenter(option.rect.center())
|
||||||
|
self.draw_spinner(painter, rect)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ResultsModel(QAbstractTableModel): # {{{
|
||||||
|
|
||||||
|
COLUMNS = (
|
||||||
|
'#', _('Title'), _('Published'), _('Has cover'), _('Has summary')
|
||||||
|
)
|
||||||
|
HTML_COLS = (1, 2)
|
||||||
|
ICON_COLS = (3, 4)
|
||||||
|
|
||||||
|
def __init__(self, results, parent=None):
|
||||||
|
QAbstractTableModel.__init__(self, parent)
|
||||||
|
self.results = results
|
||||||
|
self.yes_icon = QVariant(QIcon(I('ok.png')))
|
||||||
|
|
||||||
|
def rowCount(self, parent=None):
|
||||||
|
return len(self.results)
|
||||||
|
|
||||||
|
def columnCount(self, parent=None):
|
||||||
|
return len(self.COLUMNS)
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role):
|
||||||
|
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||||
|
try:
|
||||||
|
return QVariant(self.COLUMNS[section])
|
||||||
|
except:
|
||||||
|
return NONE
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def data_as_text(self, book, col):
|
||||||
|
if col == 0:
|
||||||
|
return unicode(book.gui_rank+1)
|
||||||
|
if col == 1:
|
||||||
|
t = book.title if book.title else _('Unknown')
|
||||||
|
a = authors_to_string(book.authors) if book.authors else ''
|
||||||
|
return '<b>%s</b><br><i>%s</i>' % (t, a)
|
||||||
|
if col == 2:
|
||||||
|
d = format_date(book.pubdate, 'yyyy') if book.pubdate else _('Unknown')
|
||||||
|
p = book.publisher if book.publisher else ''
|
||||||
|
return '<b>%s</b><br><i>%s</i>' % (d, p)
|
||||||
|
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
row, col = index.row(), index.column()
|
||||||
|
try:
|
||||||
|
book = self.results[row]
|
||||||
|
except:
|
||||||
|
return NONE
|
||||||
|
if role == Qt.DisplayRole and col not in self.ICON_COLS:
|
||||||
|
res = self.data_as_text(book, col)
|
||||||
|
if res:
|
||||||
|
return QVariant(res)
|
||||||
|
return NONE
|
||||||
|
elif role == Qt.DecorationRole and col in self.ICON_COLS:
|
||||||
|
if col == 3 and getattr(book, 'has_cached_cover_url', False):
|
||||||
|
return self.yes_icon
|
||||||
|
if col == 4 and book.comments:
|
||||||
|
return self.yes_icon
|
||||||
|
elif role == Qt.UserRole:
|
||||||
|
return book
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def sort(self, col, order=Qt.AscendingOrder):
|
||||||
|
key = lambda x: x
|
||||||
|
if col == 0:
|
||||||
|
key = attrgetter('gui_rank')
|
||||||
|
elif col == 1:
|
||||||
|
key = attrgetter('title')
|
||||||
|
elif col == 2:
|
||||||
|
key = attrgetter('authors')
|
||||||
|
elif col == 3:
|
||||||
|
key = attrgetter('has_cached_cover_url')
|
||||||
|
elif key == 4:
|
||||||
|
key = lambda x: bool(x.comments)
|
||||||
|
|
||||||
|
self.results.sort(key=key, reverse=order==Qt.AscendingOrder)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ResultsView(QTableView): # {{{
|
||||||
|
|
||||||
|
show_details_signal = pyqtSignal(object)
|
||||||
|
book_selected = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QTableView.__init__(self, parent)
|
QTableView.__init__(self, parent)
|
||||||
|
self.rt_delegate = RichTextDelegate(self)
|
||||||
|
self.setSelectionMode(self.SingleSelection)
|
||||||
|
self.setAlternatingRowColors(True)
|
||||||
|
self.setSelectionBehavior(self.SelectRows)
|
||||||
|
self.setIconSize(QSize(24, 24))
|
||||||
|
self.clicked.connect(self.show_details)
|
||||||
|
self.doubleClicked.connect(self.select_index)
|
||||||
|
self.setSortingEnabled(True)
|
||||||
|
|
||||||
|
def show_results(self, results):
|
||||||
|
self._model = ResultsModel(results, self)
|
||||||
|
self.setModel(self._model)
|
||||||
|
for i in self._model.HTML_COLS:
|
||||||
|
self.setItemDelegateForColumn(i, self.rt_delegate)
|
||||||
|
self.resizeRowsToContents()
|
||||||
|
self.resizeColumnsToContents()
|
||||||
|
self.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
def currentChanged(self, current, previous):
|
||||||
|
ret = QTableView.currentChanged(self, current, previous)
|
||||||
|
self.show_details(current)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def show_details(self, index):
|
||||||
|
book = self.model().data(index, Qt.UserRole)
|
||||||
|
parts = [
|
||||||
|
'<center>',
|
||||||
|
'<h2>%s</h2>'%book.title,
|
||||||
|
'<div><i>%s</i></div>'%authors_to_string(book.authors),
|
||||||
|
]
|
||||||
|
if not book.is_null('rating'):
|
||||||
|
parts.append('<div>%s</div>'%('\u2605'*int(book.rating)))
|
||||||
|
parts.append('</center>')
|
||||||
|
if book.identifiers:
|
||||||
|
urls = urls_from_identifiers(book.identifiers)
|
||||||
|
ids = ['<a href="%s">%s</a>'%(url, name) for name, url in urls]
|
||||||
|
if ids:
|
||||||
|
parts.append('<div><b>%s:</b> %s</div><br>'%(_('See at'), ', '.join(ids)))
|
||||||
|
if book.tags:
|
||||||
|
parts.append('<div>%s</div><div>\u00a0</div>'%', '.join(book.tags))
|
||||||
|
if book.comments:
|
||||||
|
parts.append(comments_to_html(book.comments))
|
||||||
|
|
||||||
|
self.show_details_signal.emit(''.join(parts))
|
||||||
|
|
||||||
|
def select_index(self, index):
|
||||||
|
if not index.isValid():
|
||||||
|
index = self.model().index(0, 0)
|
||||||
|
book = self.model().data(index, Qt.UserRole)
|
||||||
|
self.book_selected.emit(book)
|
||||||
|
|
||||||
|
def get_result(self):
|
||||||
|
self.select_index(self.currentIndex())
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class Comments(QWebView): # {{{
|
class Comments(QWebView): # {{{
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWebView.__init__(self, parent)
|
QWebView.__init__(self, parent)
|
||||||
self.setAcceptDrops(False)
|
self.setAcceptDrops(False)
|
||||||
self.setMaximumWidth(270)
|
self.setMaximumWidth(300)
|
||||||
self.setMinimumWidth(270)
|
self.setMinimumWidth(300)
|
||||||
|
|
||||||
palette = self.palette()
|
palette = self.palette()
|
||||||
palette.setBrush(QPalette.Base, Qt.transparent)
|
palette.setBrush(QPalette.Base, Qt.transparent)
|
||||||
self.page().setPalette(palette)
|
self.page().setPalette(palette)
|
||||||
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
||||||
|
|
||||||
|
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
|
||||||
|
self.linkClicked.connect(self.link_clicked)
|
||||||
|
|
||||||
|
def link_clicked(self, url):
|
||||||
|
from calibre.gui2 import open_url
|
||||||
|
if unicode(url.toString()).startswith('http://'):
|
||||||
|
open_url(url)
|
||||||
|
|
||||||
def turnoff_scrollbar(self, *args):
|
def turnoff_scrollbar(self, *args):
|
||||||
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
@ -109,7 +320,7 @@ class Comments(QWebView): # {{{
|
|||||||
self.setHtml(templ%html)
|
self.setHtml(templ%html)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class IdentifyWorker(Thread):
|
class IdentifyWorker(Thread): # {{{
|
||||||
|
|
||||||
def __init__(self, log, abort, title, authors, identifiers):
|
def __init__(self, log, abort, title, authors, identifiers):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
@ -122,17 +333,42 @@ class IdentifyWorker(Thread):
|
|||||||
self.results = []
|
self.results = []
|
||||||
self.error = None
|
self.error = None
|
||||||
|
|
||||||
|
def sample_results(self):
|
||||||
|
m1 = Metadata('The Great Gatsby', ['Francis Scott Fitzgerald'])
|
||||||
|
m2 = Metadata('The Great Gatsby', ['F. Scott Fitzgerald'])
|
||||||
|
m1.has_cached_cover_url = True
|
||||||
|
m2.has_cached_cover_url = False
|
||||||
|
m1.comments = 'Some comments '*10
|
||||||
|
m1.tags = ['tag%d'%i for i in range(20)]
|
||||||
|
m1.rating = 4.4
|
||||||
|
m1.language = 'en'
|
||||||
|
m2.language = 'fr'
|
||||||
|
m1.pubdate = utcnow()
|
||||||
|
m2.pubdate = fromordinal(1000000)
|
||||||
|
m1.publisher = 'Publisher 1'
|
||||||
|
m2.publisher = 'Publisher 2'
|
||||||
|
|
||||||
|
return [m1, m2]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
|
if DEBUG_DIALOG:
|
||||||
|
self.results = self.sample_results()
|
||||||
|
else:
|
||||||
self.results = identify(self.log, self.abort, title=self.title,
|
self.results = identify(self.log, self.abort, title=self.title,
|
||||||
authors=self.authors, identifiers=self.identifiers)
|
authors=self.authors, identifiers=self.identifiers)
|
||||||
for i, result in enumerate(self.results):
|
for i, result in enumerate(self.results):
|
||||||
result.gui_rank = i
|
result.gui_rank = i
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
self.error = traceback.format_exc()
|
self.error = force_unicode(traceback.format_exc())
|
||||||
|
# }}}
|
||||||
|
|
||||||
class IdentifyWidget(QWidget):
|
class IdentifyWidget(QWidget): # {{{
|
||||||
|
|
||||||
|
rejected = pyqtSignal()
|
||||||
|
results_found = pyqtSignal()
|
||||||
|
book_selected = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, log, parent=None):
|
def __init__(self, log, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
@ -150,11 +386,15 @@ class IdentifyWidget(QWidget):
|
|||||||
l.addWidget(self.top, 0, 0)
|
l.addWidget(self.top, 0, 0)
|
||||||
|
|
||||||
self.results_view = ResultsView(self)
|
self.results_view = ResultsView(self)
|
||||||
|
self.results_view.book_selected.connect(self.book_selected.emit)
|
||||||
|
self.get_result = self.results_view.get_result
|
||||||
l.addWidget(self.results_view, 1, 0)
|
l.addWidget(self.results_view, 1, 0)
|
||||||
|
|
||||||
self.comments_view = Comments(self)
|
self.comments_view = Comments(self)
|
||||||
l.addWidget(self.comments_view, 1, 1)
|
l.addWidget(self.comments_view, 1, 1)
|
||||||
|
|
||||||
|
self.results_view.show_details_signal.connect(self.comments_view.show_data)
|
||||||
|
|
||||||
self.query = QLabel('download starting...')
|
self.query = QLabel('download starting...')
|
||||||
f = self.query.font()
|
f = self.query.font()
|
||||||
f.setPointSize(f.pointSize()-2)
|
f.setPointSize(f.pointSize()-2)
|
||||||
@ -162,7 +402,7 @@ class IdentifyWidget(QWidget):
|
|||||||
self.query.setWordWrap(True)
|
self.query.setWordWrap(True)
|
||||||
l.addWidget(self.query, 2, 0, 1, 2)
|
l.addWidget(self.query, 2, 0, 1, 2)
|
||||||
|
|
||||||
self.comments_view.show_data('<h2>'+_('Downloading')+
|
self.comments_view.show_data('<h2>'+_('Please wait')+
|
||||||
'<br><span id="dots">.</span></h2>'+
|
'<br><span id="dots">.</span></h2>'+
|
||||||
'''
|
'''
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -197,13 +437,352 @@ class IdentifyWidget(QWidget):
|
|||||||
self.worker = IdentifyWorker(self.log, self.abort, title,
|
self.worker = IdentifyWorker(self.log, self.abort, title,
|
||||||
authors, identifiers)
|
authors, identifiers)
|
||||||
|
|
||||||
# self.worker.start()
|
self.worker.start()
|
||||||
|
|
||||||
class FullFetch(QDialog): # {{{
|
QTimer.singleShot(50, self.update)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if self.worker.is_alive():
|
||||||
|
QTimer.singleShot(50, self.update)
|
||||||
|
else:
|
||||||
|
self.process_results()
|
||||||
|
|
||||||
|
def process_results(self):
|
||||||
|
if self.worker.error is not None:
|
||||||
|
error_dialog(self, _('Download failed'),
|
||||||
|
_('Failed to download metadata. Click '
|
||||||
|
'Show Details to see details'),
|
||||||
|
show=True, det_msg=self.worker.error)
|
||||||
|
self.rejected.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.worker.results:
|
||||||
|
log = ''.join(self.log.plain_text)
|
||||||
|
error_dialog(self, _('No matches found'), '<p>' +
|
||||||
|
_('Failed to find any books that '
|
||||||
|
'match your search. Try making the search <b>less '
|
||||||
|
'specific</b>. For example, use only the author\'s '
|
||||||
|
'last name and a single distinctive word from '
|
||||||
|
'the title.<p>To see the full log, click Show Details.'),
|
||||||
|
show=True, det_msg=log)
|
||||||
|
self.rejected.emit()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.results_view.show_results(self.worker.results)
|
||||||
|
|
||||||
|
self.comments_view.show_data('''
|
||||||
|
<div style="margin-bottom:2ex">Found <b>%d</b> results</div>
|
||||||
|
<div>To see <b>details</b>, click on any result</div>''' %
|
||||||
|
len(self.worker.results))
|
||||||
|
|
||||||
|
self.results_found.emit()
|
||||||
|
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.abort.set()
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class CoverWorker(Thread): # {{{
|
||||||
|
|
||||||
|
def __init__(self, log, abort, title, authors, identifiers):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
|
||||||
|
self.log, self.abort = log, abort
|
||||||
|
self.title, self.authors, self.identifiers = (title, authors,
|
||||||
|
identifiers)
|
||||||
|
|
||||||
|
self.rq = Queue()
|
||||||
|
self.error = None
|
||||||
|
|
||||||
|
def fake_run(self):
|
||||||
|
images = ['donate.png', 'config.png', 'column.png', 'eject.png', ]
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
for pl, im in zip(metadata_plugins(['cover']), images):
|
||||||
|
self.rq.put((pl, 1, 1, 'png', I(im, data=True)))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
if DEBUG_DIALOG:
|
||||||
|
self.fake_run()
|
||||||
|
else:
|
||||||
|
from calibre.ebooks.metadata.sources.covers import run_download
|
||||||
|
run_download(self.log, self.rq, self.abort, title=self.title,
|
||||||
|
authors=self.authors, identifiers=self.identifiers)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
self.error = force_unicode(traceback.format_exc())
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class CoversModel(QAbstractListModel): # {{{
|
||||||
|
|
||||||
|
def __init__(self, current_cover, parent=None):
|
||||||
|
QAbstractListModel.__init__(self, parent)
|
||||||
|
|
||||||
|
if current_cover is None:
|
||||||
|
current_cover = QPixmap(I('default_cover.png'))
|
||||||
|
|
||||||
|
self.blank = QPixmap(I('blank.png')).scaled(150, 200)
|
||||||
|
|
||||||
|
self.covers = [self.get_item(_('Current cover'), current_cover)]
|
||||||
|
self.plugin_map = {}
|
||||||
|
for i, plugin in enumerate(metadata_plugins(['cover'])):
|
||||||
|
self.covers.append((plugin.name+'\n'+_('Searching...'),
|
||||||
|
QVariant(self.blank), None, True))
|
||||||
|
self.plugin_map[plugin] = i+1
|
||||||
|
|
||||||
|
def get_item(self, src, pmap, waiting=False):
|
||||||
|
sz = '%dx%d'%(pmap.width(), pmap.height())
|
||||||
|
text = QVariant(src + '\n' + sz)
|
||||||
|
scaled = pmap.scaled(150, 200, Qt.IgnoreAspectRatio,
|
||||||
|
Qt.SmoothTransformation)
|
||||||
|
return (text, QVariant(scaled), pmap, waiting)
|
||||||
|
|
||||||
|
def rowCount(self, parent=None):
|
||||||
|
return len(self.covers)
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
try:
|
||||||
|
text, pmap, cover, waiting = self.covers[index.row()]
|
||||||
|
except:
|
||||||
|
return NONE
|
||||||
|
if role == Qt.DecorationRole:
|
||||||
|
return pmap
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
return text
|
||||||
|
if role == Qt.UserRole:
|
||||||
|
return waiting
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def plugin_for_index(self, index):
|
||||||
|
row = index.row() if hasattr(index, 'row') else index
|
||||||
|
for k, v in self.plugin_map.iteritems():
|
||||||
|
if v == row:
|
||||||
|
return k
|
||||||
|
|
||||||
|
def clear_failed(self):
|
||||||
|
good = []
|
||||||
|
pmap = {}
|
||||||
|
for i, x in enumerate(self.covers):
|
||||||
|
if not x[-1]:
|
||||||
|
good.append(x)
|
||||||
|
if i > 0:
|
||||||
|
plugin = self.plugin_for_index(i)
|
||||||
|
pmap[plugin] = len(good) - 1
|
||||||
|
good = [x for x in self.covers if not x[-1]]
|
||||||
|
self.covers = good
|
||||||
|
self.plugin_map = pmap
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def index_for_plugin(self, plugin):
|
||||||
|
idx = self.plugin_map.get(plugin, 0)
|
||||||
|
return self.index(idx)
|
||||||
|
|
||||||
|
def update_result(self, plugin, width, height, data):
|
||||||
|
try:
|
||||||
|
idx = self.plugin_map[plugin]
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
pmap = QPixmap()
|
||||||
|
pmap.loadFromData(data)
|
||||||
|
if pmap.isNull():
|
||||||
|
return
|
||||||
|
self.covers[idx] = self.get_item(plugin.name, pmap, waiting=False)
|
||||||
|
self.dataChanged.emit(self.index(idx), self.index(idx))
|
||||||
|
|
||||||
|
def cover_pixmap(self, index):
|
||||||
|
row = index.row()
|
||||||
|
if row > 0 and row < len(self.covers):
|
||||||
|
pmap = self.covers[row][2]
|
||||||
|
if pmap is not None and not pmap.isNull():
|
||||||
|
return pmap
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class CoversView(QListView): # {{{
|
||||||
|
|
||||||
|
chosen = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, current_cover, parent=None):
|
||||||
|
QListView.__init__(self, parent)
|
||||||
|
self.m = CoversModel(current_cover, self)
|
||||||
|
self.setModel(self.m)
|
||||||
|
|
||||||
|
self.setFlow(self.LeftToRight)
|
||||||
|
self.setWrapping(True)
|
||||||
|
self.setResizeMode(self.Adjust)
|
||||||
|
self.setGridSize(QSize(190, 260))
|
||||||
|
self.setIconSize(QSize(150, 200))
|
||||||
|
self.setSelectionMode(self.SingleSelection)
|
||||||
|
self.setViewMode(self.IconMode)
|
||||||
|
|
||||||
|
self.delegate = CoverDelegate(self)
|
||||||
|
self.setItemDelegate(self.delegate)
|
||||||
|
self.delegate.needs_redraw.connect(self.viewport().update,
|
||||||
|
type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
self.doubleClicked.connect(self.chosen, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
def select(self, num):
|
||||||
|
current = self.model().index(num)
|
||||||
|
sm = self.selectionModel()
|
||||||
|
sm.select(current, sm.SelectCurrent)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.select(0)
|
||||||
|
self.delegate.start_animation()
|
||||||
|
|
||||||
|
def clear_failed(self):
|
||||||
|
plugin = self.m.plugin_for_index(self.currentIndex())
|
||||||
|
self.m.clear_failed()
|
||||||
|
self.select(self.m.index_for_plugin(plugin).row())
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class CoversWidget(QWidget): # {{{
|
||||||
|
|
||||||
|
chosen = pyqtSignal()
|
||||||
|
finished = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, log, current_cover, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.log = log
|
||||||
|
self.abort = Event()
|
||||||
|
|
||||||
|
self.l = l = QGridLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self.msg = QLabel()
|
||||||
|
self.msg.setWordWrap(True)
|
||||||
|
l.addWidget(self.msg, 0, 0)
|
||||||
|
|
||||||
|
self.covers_view = CoversView(current_cover, self)
|
||||||
|
self.covers_view.chosen.connect(self.chosen)
|
||||||
|
l.addWidget(self.covers_view, 1, 0)
|
||||||
|
self.continue_processing = True
|
||||||
|
|
||||||
|
def start(self, book, current_cover, title, authors):
|
||||||
|
self.book, self.current_cover = book, current_cover
|
||||||
|
self.title, self.authors = title, authors
|
||||||
|
self.log('\n\nStarting cover download for:', book.title)
|
||||||
|
self.msg.setText('<p>'+_('Downloading covers for <b>%s</b>, please wait...')%book.title)
|
||||||
|
self.covers_view.start()
|
||||||
|
|
||||||
|
self.worker = CoverWorker(self.log, self.abort, self.title,
|
||||||
|
self.authors, book.identifiers)
|
||||||
|
self.worker.start()
|
||||||
|
QTimer.singleShot(50, self.check)
|
||||||
|
self.covers_view.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
if self.worker.is_alive() and not self.abort.is_set():
|
||||||
|
QTimer.singleShot(50, self.check)
|
||||||
|
try:
|
||||||
|
self.process_result(self.worker.rq.get_nowait())
|
||||||
|
except Empty:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.process_results()
|
||||||
|
|
||||||
|
def process_results(self):
|
||||||
|
while self.continue_processing:
|
||||||
|
try:
|
||||||
|
self.process_result(self.worker.rq.get_nowait())
|
||||||
|
except Empty:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.covers_view.clear_failed()
|
||||||
|
|
||||||
|
if self.worker.error is not None:
|
||||||
|
error_dialog(self, _('Download failed'),
|
||||||
|
_('Failed to download any covers, click'
|
||||||
|
' "Show details" for details.'),
|
||||||
|
det_msg=self.worker.error, show=True)
|
||||||
|
|
||||||
|
num = self.covers_view.model().rowCount()
|
||||||
|
if num < 2:
|
||||||
|
txt = _('Could not find any covers for <b>%s</b>')%self.book.title
|
||||||
|
else:
|
||||||
|
txt = _('Found <b>%d</b> covers of %s. Pick the one you like'
|
||||||
|
' best.')%(num-1, self.title)
|
||||||
|
self.msg.setText(txt)
|
||||||
|
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
|
def process_result(self, result):
|
||||||
|
if not self.continue_processing:
|
||||||
|
return
|
||||||
|
plugin, width, height, fmt, data = result
|
||||||
|
self.covers_view.model().update_result(plugin, width, height, data)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.covers_view.delegate.stop_animation()
|
||||||
|
self.continue_processing = False
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.continue_processing = False
|
||||||
|
self.abort.set()
|
||||||
|
|
||||||
|
def cover_pixmap(self):
|
||||||
|
idx = None
|
||||||
|
for i in self.covers_view.selectionModel().selectedIndexes():
|
||||||
|
if i.isValid():
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
if idx is None:
|
||||||
|
idx = self.covers_view.currentIndex()
|
||||||
|
return self.covers_view.model().cover_pixmap(idx)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class LogViewer(QDialog): # {{{
|
||||||
|
|
||||||
def __init__(self, log, parent=None):
|
def __init__(self, log, parent=None):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.log = log
|
self.log = log
|
||||||
|
self.l = l = QVBoxLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
self.tb = QTextBrowser(self)
|
||||||
|
l.addWidget(self.tb)
|
||||||
|
|
||||||
|
self.bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
|
l.addWidget(self.bb)
|
||||||
|
self.bb.rejected.connect(self.reject)
|
||||||
|
self.bb.accepted.connect(self.accept)
|
||||||
|
|
||||||
|
self.setWindowTitle(_('Download log'))
|
||||||
|
self.setWindowIcon(QIcon(I('debug.png')))
|
||||||
|
self.resize(QSize(800, 400))
|
||||||
|
|
||||||
|
self.keep_updating = True
|
||||||
|
self.last_html = None
|
||||||
|
self.finished.connect(self.stop)
|
||||||
|
QTimer.singleShot(1000, self.update_log)
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def stop(self, *args):
|
||||||
|
self.keep_updating = False
|
||||||
|
|
||||||
|
def update_log(self):
|
||||||
|
if not self.keep_updating:
|
||||||
|
return
|
||||||
|
html = self.log.html
|
||||||
|
if html != self.last_html:
|
||||||
|
self.last_html = html
|
||||||
|
self.tb.setHtml('<pre>%s</pre>'%html)
|
||||||
|
QTimer.singleShot(1000, self.update_log)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class FullFetch(QDialog): # {{{
|
||||||
|
|
||||||
|
def __init__(self, log, current_cover=None, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.log, self.current_cover = log, current_cover
|
||||||
|
self.book = self.cover_pixmap = None
|
||||||
|
|
||||||
self.setWindowTitle(_('Downloading metadata...'))
|
self.setWindowTitle(_('Downloading metadata...'))
|
||||||
self.setWindowIcon(QIcon(I('metadata.png')))
|
self.setWindowIcon(QIcon(I('metadata.png')))
|
||||||
@ -213,25 +792,83 @@ class FullFetch(QDialog): # {{{
|
|||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
l.addWidget(self.stack)
|
l.addWidget(self.stack)
|
||||||
|
|
||||||
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
|
||||||
l.addWidget(self.bb)
|
l.addWidget(self.bb)
|
||||||
self.bb.rejected.connect(self.reject)
|
self.bb.rejected.connect(self.reject)
|
||||||
|
self.next_button = self.bb.addButton(_('Next'), self.bb.AcceptRole)
|
||||||
|
self.next_button.setDefault(True)
|
||||||
|
self.next_button.setEnabled(False)
|
||||||
|
self.next_button.setIcon(QIcon(I('ok.png')))
|
||||||
|
self.next_button.clicked.connect(self.next_clicked)
|
||||||
|
self.ok_button = self.bb.button(self.bb.Ok)
|
||||||
|
self.ok_button.clicked.connect(self.ok_clicked)
|
||||||
|
self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole)
|
||||||
|
self.log_button.clicked.connect(self.view_log)
|
||||||
|
self.log_button.setIcon(QIcon(I('debug.png')))
|
||||||
|
self.ok_button.setVisible(False)
|
||||||
|
|
||||||
self.identify_widget = IdentifyWidget(log, self)
|
self.identify_widget = IdentifyWidget(log, self)
|
||||||
|
self.identify_widget.rejected.connect(self.reject)
|
||||||
|
self.identify_widget.results_found.connect(self.identify_results_found)
|
||||||
|
self.identify_widget.book_selected.connect(self.book_selected)
|
||||||
self.stack.addWidget(self.identify_widget)
|
self.stack.addWidget(self.identify_widget)
|
||||||
self.resize(850, 500)
|
|
||||||
|
self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self)
|
||||||
|
self.covers_widget.chosen.connect(self.ok_clicked)
|
||||||
|
self.stack.addWidget(self.covers_widget)
|
||||||
|
|
||||||
|
self.resize(850, 550)
|
||||||
|
|
||||||
|
self.finished.connect(self.cleanup)
|
||||||
|
|
||||||
|
def view_log(self):
|
||||||
|
self._lv = LogViewer(self.log, self)
|
||||||
|
|
||||||
|
def book_selected(self, book):
|
||||||
|
self.next_button.setVisible(False)
|
||||||
|
self.ok_button.setVisible(True)
|
||||||
|
self.book = book
|
||||||
|
self.stack.setCurrentIndex(1)
|
||||||
|
self.covers_widget.start(book, self.current_cover,
|
||||||
|
self.title, self.authors)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
# Prevent pressing Enter from closing the dialog
|
# Prevent the usual dialog accept mechanisms from working
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
self.identify_widget.cancel()
|
||||||
|
return QDialog.reject(self)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.covers_widget.cleanup()
|
||||||
|
|
||||||
|
def identify_results_found(self):
|
||||||
|
self.next_button.setEnabled(True)
|
||||||
|
|
||||||
|
def next_clicked(self, *args):
|
||||||
|
self.identify_widget.get_result()
|
||||||
|
|
||||||
|
def ok_clicked(self, *args):
|
||||||
|
self.cover_pixmap = self.covers_widget.cover_pixmap()
|
||||||
|
if DEBUG_DIALOG:
|
||||||
|
if self.cover_pixmap is not None:
|
||||||
|
self.w = QLabel()
|
||||||
|
self.w.setPixmap(self.cover_pixmap)
|
||||||
|
self.stack.addWidget(self.w)
|
||||||
|
self.stack.setCurrentIndex(2)
|
||||||
|
else:
|
||||||
|
QDialog.accept(self)
|
||||||
|
|
||||||
def start(self, title=None, authors=None, identifiers={}):
|
def start(self, title=None, authors=None, identifiers={}):
|
||||||
|
self.title, self.authors = title, authors
|
||||||
self.identify_widget.start(title=title, authors=authors,
|
self.identify_widget.start(title=title, authors=authors,
|
||||||
identifiers=identifiers)
|
identifiers=identifiers)
|
||||||
self.exec_()
|
self.exec_()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
#DEBUG_DIALOG = True
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
d = FullFetch(Log())
|
d = FullFetch(Log())
|
||||||
d.start(title='great gatsby', authors=['Fitzgerald'])
|
d.start(title='great gatsby', authors=['Fitzgerald'])
|
||||||
|
@ -11,7 +11,7 @@ from PyQt4.Qt import Qt, QVariant, QListWidgetItem
|
|||||||
|
|
||||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
|
||||||
from calibre.gui2.preferences.behavior_ui import Ui_Form
|
from calibre.gui2.preferences.behavior_ui import Ui_Form
|
||||||
from calibre.gui2 import config, info_dialog, dynamic
|
from calibre.gui2 import config, info_dialog, dynamic, gprefs
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.customize.ui import available_output_formats, all_input_formats
|
from calibre.customize.ui import available_output_formats, all_input_formats
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
@ -19,6 +19,7 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
|||||||
from calibre.ebooks.oeb.iterator import is_supported
|
from calibre.ebooks.oeb.iterator import is_supported
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.config import test_eight_code
|
||||||
|
|
||||||
class OutputFormatSetting(Setting):
|
class OutputFormatSetting(Setting):
|
||||||
|
|
||||||
@ -62,6 +63,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
signal = getattr(self.opt_internally_viewed_formats, 'item'+signal)
|
signal = getattr(self.opt_internally_viewed_formats, 'item'+signal)
|
||||||
signal.connect(self.internally_viewed_formats_changed)
|
signal.connect(self.internally_viewed_formats_changed)
|
||||||
|
|
||||||
|
r('bools_are_tristate', db.prefs, restart_required=True)
|
||||||
|
if test_eight_code:
|
||||||
|
r = self.register
|
||||||
|
choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1')]
|
||||||
|
r('edit_metadata_single_layout', gprefs, choices=choices)
|
||||||
|
else:
|
||||||
|
self.opt_edit_metadata_single_layout.setVisible(False)
|
||||||
|
self.edit_metadata_single_label.setVisible(False)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
ConfigWidgetBase.initialize(self)
|
ConfigWidgetBase.initialize(self)
|
||||||
|
@ -14,44 +14,92 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0" colspan="2">
|
<item row="0" column="1">
|
||||||
|
<spacer>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>10</width>
|
||||||
|
<height>00</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
<widget class="QCheckBox" name="opt_overwrite_author_title_metadata">
|
<widget class="QCheckBox" name="opt_overwrite_author_title_metadata">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Overwrite author and title by default when fetching metadata</string>
|
<string>&Overwrite author and title by default when fetching metadata</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0" colspan="2">
|
<item row="0" column="2">
|
||||||
<widget class="QCheckBox" name="opt_get_social_metadata">
|
<widget class="QCheckBox" name="opt_get_social_metadata">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Download &social metadata (tags/ratings/etc.) by default</string>
|
<string>Download &social metadata (tags/ratings/etc.) by default</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="2">
|
<item row="2" column="0">
|
||||||
<widget class="QCheckBox" name="opt_new_version_notification">
|
<widget class="QCheckBox" name="opt_new_version_notification">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Show notification when &new version is available</string>
|
<string>Show notification when &new version is available</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="2" column="2">
|
||||||
|
<widget class="QCheckBox" name="opt_bools_are_tristate">
|
||||||
|
<property name="text">
|
||||||
|
<string>Yes/No columns have three values (Requires restart)</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>If checked, Yes/No custom columns values can be Yes, No, or Unknown.
|
||||||
|
If not checked, the values can be Yes or No.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
<widget class="QCheckBox" name="opt_upload_news_to_device">
|
<widget class="QCheckBox" name="opt_upload_news_to_device">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatically send downloaded &news to ebook reader</string>
|
<string>Automatically send downloaded &news to ebook reader</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" colspan="2">
|
<item row="4" column="2">
|
||||||
<widget class="QCheckBox" name="opt_delete_news_from_library_on_upload">
|
<widget class="QCheckBox" name="opt_delete_news_from_library_on_upload">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Delete news from library when it is automatically sent to reader</string>
|
<string>&Delete news from library when it is automatically sent to reader</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="2">
|
<item row="6" column="0">
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QHBoxLayout">
|
||||||
<item row="1" column="0">
|
<item>
|
||||||
|
<widget class="QLabel" name="label_23">
|
||||||
|
<property name="text">
|
||||||
|
<string>Preferred &output format:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_output_format</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="opt_output_format">
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||||
|
</property>
|
||||||
|
<property name="minimumContentsLength">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="2">
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Default network &timeout:</string>
|
<string>Default network &timeout:</string>
|
||||||
@ -61,7 +109,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item>
|
||||||
<widget class="QSpinBox" name="opt_network_timeout">
|
<widget class="QSpinBox" name="opt_network_timeout">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
|
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
|
||||||
@ -80,7 +128,21 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="priority_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Job &priority:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_worker_process_priority</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
<widget class="QComboBox" name="opt_worker_process_priority">
|
<widget class="QComboBox" name="opt_worker_process_priority">
|
||||||
<property name="sizeAdjustPolicy">
|
<property name="sizeAdjustPolicy">
|
||||||
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||||
@ -105,37 +167,11 @@
|
|||||||
</item>
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
</layout>
|
||||||
<widget class="QLabel" name="priority_label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Job &priority:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>opt_worker_process_priority</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="8" column="2">
|
||||||
<widget class="QLabel" name="label_23">
|
<layout class="QHBoxLayout">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>Preferred &output format:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>opt_output_format</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="opt_output_format">
|
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QComboBox::AdjustToMinimumContentsLengthWithIcon</enum>
|
|
||||||
</property>
|
|
||||||
<property name="minimumContentsLength">
|
|
||||||
<number>10</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="label_170">
|
<widget class="QLabel" name="label_170">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Restriction to apply when the current library is opened:</string>
|
<string>Restriction to apply when the current library is opened:</string>
|
||||||
@ -145,7 +181,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item>
|
||||||
<widget class="QComboBox" name="opt_gui_restriction">
|
<widget class="QComboBox" name="opt_gui_restriction">
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
@ -166,14 +202,28 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0" colspan="2">
|
<item row="9" column="0">
|
||||||
<widget class="QPushButton" name="reset_confirmation_button">
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="edit_metadata_single_label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Reset all disabled &confirmation dialogs</string>
|
<string>Edit metadata (single) layout:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_edit_metadata_single_layout</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="0">
|
<item>
|
||||||
|
<widget class="QComboBox" name="opt_edit_metadata_single_layout">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Choose a different layout for the Edit Metadata dialog. The compact metadata layout favors editing custom metadata over changing covers and formats.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="20" column="0">
|
||||||
<widget class="QGroupBox" name="groupBox_5">
|
<widget class="QGroupBox" name="groupBox_5">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Preferred &input format order:</string>
|
<string>Preferred &input format order:</string>
|
||||||
@ -235,7 +285,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="20" column="2">
|
||||||
<widget class="QGroupBox" name="groupBox_3">
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Use internal &viewer for:</string>
|
<string>Use internal &viewer for:</string>
|
||||||
@ -254,6 +304,13 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="30" column="0" colspan="3">
|
||||||
|
<widget class="QPushButton" name="reset_confirmation_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reset all disabled &confirmation dialogs</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
|
@ -49,8 +49,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
r('use_roman_numerals_for_series_number', config)
|
r('use_roman_numerals_for_series_number', config)
|
||||||
r('separate_cover_flow', config, restart_required=True)
|
r('separate_cover_flow', config, restart_required=True)
|
||||||
|
|
||||||
choices = [(_('Small'), 'small'), (_('Medium'), 'medium'),
|
choices = [(_('Off'), 'off'), (_('Small'), 'small'),
|
||||||
(_('Large'), 'large')]
|
(_('Medium'), 'medium'), (_('Large'), 'large')]
|
||||||
r('toolbar_icon_size', gprefs, choices=choices)
|
r('toolbar_icon_size', gprefs, choices=choices)
|
||||||
|
|
||||||
choices = [(_('Automatic'), 'auto'), (_('Always'), 'always'),
|
choices = [(_('Automatic'), 'auto'), (_('Always'), 'always'),
|
||||||
|
@ -360,6 +360,7 @@ class Preferences(QMainWindow):
|
|||||||
self.gui.create_device_menu()
|
self.gui.create_device_menu()
|
||||||
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
|
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
|
||||||
self.gui.tool_bar.build_bar()
|
self.gui.tool_bar.build_bar()
|
||||||
|
self.gui.menu_bar.build_bar()
|
||||||
self.gui.build_context_menus()
|
self.gui.build_context_menus()
|
||||||
self.gui.tool_bar.apply_settings()
|
self.gui.tool_bar.apply_settings()
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ from calibre.utils.config import ConfigProxy
|
|||||||
from calibre.gui2 import error_dialog, config, open_url, warning_dialog, \
|
from calibre.gui2 import error_dialog, config, open_url, warning_dialog, \
|
||||||
Dispatcher, info_dialog
|
Dispatcher, info_dialog
|
||||||
from calibre import as_unicode
|
from calibre import as_unicode
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||||
|
|
||||||
@ -42,8 +43,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
else self.opt_password.Password))
|
else self.opt_password.Password))
|
||||||
self.opt_password.setEchoMode(self.opt_password.Password)
|
self.opt_password.setEchoMode(self.opt_password.Password)
|
||||||
|
|
||||||
restrictions = sorted(saved_searches().names(),
|
restrictions = sorted(saved_searches().names(), key=sort_key)
|
||||||
cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
# verify that the current restriction still exists. If not, clear it.
|
||||||
|
csr = db.prefs.get('cs_restriction', None)
|
||||||
|
if csr and csr not in restrictions:
|
||||||
|
db.prefs.set('cs_restriction', '')
|
||||||
choices = [('', '')] + [(x, x) for x in restrictions]
|
choices = [('', '')] + [(x, x) for x in restrictions]
|
||||||
r('cs_restriction', db.prefs, choices=choices)
|
r('cs_restriction', db.prefs, choices=choices)
|
||||||
|
|
||||||
|
@ -34,9 +34,12 @@ class BaseModel(QAbstractListModel):
|
|||||||
if name == 'Location Manager':
|
if name == 'Location Manager':
|
||||||
return FakeAction(name, None,
|
return FakeAction(name, None,
|
||||||
_('Switch between library and device views'),
|
_('Switch between library and device views'),
|
||||||
dont_remove_from=set(['toolbar-device']))
|
dont_add_to=frozenset(['menubar', 'toolbar',
|
||||||
|
'toolbar-child', 'context-menu',
|
||||||
|
'context-menu-device']))
|
||||||
if name is None:
|
if name is None:
|
||||||
return FakeAction('--- '+_('Separator')+' ---', None)
|
return FakeAction('--- '+_('Separator')+' ---', None,
|
||||||
|
dont_add_to=frozenset(['menubar', 'menubar-device']))
|
||||||
try:
|
try:
|
||||||
return gui.iactions[name]
|
return gui.iactions[name]
|
||||||
except:
|
except:
|
||||||
@ -77,6 +80,12 @@ class BaseModel(QAbstractListModel):
|
|||||||
ans.append(n)
|
ans.append(n)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def has_action(self, name):
|
||||||
|
for a in self._data:
|
||||||
|
if a.name == name:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class AllModel(BaseModel):
|
class AllModel(BaseModel):
|
||||||
|
|
||||||
@ -89,7 +98,7 @@ class AllModel(BaseModel):
|
|||||||
self._data = self.get_all_actions(current)
|
self._data = self.get_all_actions(current)
|
||||||
|
|
||||||
def get_all_actions(self, current):
|
def get_all_actions(self, current):
|
||||||
all = list(self.gui.iactions.keys()) + ['Donate']
|
all = list(self.gui.iactions.keys()) + ['Donate', 'Location Manager']
|
||||||
all = [x for x in all if x not in current] + [None]
|
all = [x for x in all if x not in current] + [None]
|
||||||
all = [self.name_to_action(x, self.gui) for x in all]
|
all = [self.name_to_action(x, self.gui) for x in all]
|
||||||
all = [x for x in all if self.key not in x.dont_add_to]
|
all = [x for x in all if self.key not in x.dont_add_to]
|
||||||
@ -208,12 +217,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
|
|
||||||
LOCATIONS = [
|
LOCATIONS = [
|
||||||
('toolbar', _('The main toolbar')),
|
('toolbar', _('The main toolbar')),
|
||||||
('toolbar-child', _('The optional second toolbar')),
|
|
||||||
('toolbar-device', _('The main toolbar when a device is connected')),
|
('toolbar-device', _('The main toolbar when a device is connected')),
|
||||||
|
('toolbar-child', _('The optional second toolbar')),
|
||||||
|
('menubar', _('The menubar')),
|
||||||
|
('menubar-device', _('The menubar when a device is connected')),
|
||||||
('context-menu', _('The context menu for the books in the '
|
('context-menu', _('The context menu for the books in the '
|
||||||
'calibre library')),
|
'calibre library')),
|
||||||
('context-menu-device', _('The context menu for the books on '
|
('context-menu-device', _('The context menu for the books on '
|
||||||
'the device'))
|
'the device')),
|
||||||
]
|
]
|
||||||
|
|
||||||
def genesis(self, gui):
|
def genesis(self, gui):
|
||||||
@ -284,6 +295,18 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
self.changed_signal.emit()
|
self.changed_signal.emit()
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
|
# Ensure preferences are showing in either the toolbar or
|
||||||
|
# the menubar.
|
||||||
|
pref_in_toolbar = self.models['toolbar'][1].has_action('Preferences')
|
||||||
|
pref_in_menubar = self.models['menubar'][1].has_action('Preferences')
|
||||||
|
lm_in_toolbar = self.models['toolbar-device'][1].has_action('Location Manager')
|
||||||
|
lm_in_menubar = self.models['menubar-device'][1].has_action('Location Manager')
|
||||||
|
if not pref_in_toolbar and not pref_in_menubar:
|
||||||
|
self.models['menubar'][1].add(['Preferences'])
|
||||||
|
if not lm_in_toolbar and not lm_in_menubar:
|
||||||
|
self.models['menubar-device'][1].add(['Location Manager'])
|
||||||
|
|
||||||
|
# Save data.
|
||||||
for am, cm in self.models.values():
|
for am, cm in self.models.values():
|
||||||
cm.commit()
|
cm.commit()
|
||||||
return False
|
return False
|
||||||
|
@ -153,6 +153,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
for ac in self.iactions.values():
|
for ac in self.iactions.values():
|
||||||
ac.do_genesis()
|
ac.do_genesis()
|
||||||
|
self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self)
|
||||||
MainWindowMixin.__init__(self, db)
|
MainWindowMixin.__init__(self, db)
|
||||||
|
|
||||||
# Jobs Button {{{
|
# Jobs Button {{{
|
||||||
@ -186,8 +187,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.system_tray_menu = QMenu(self)
|
self.system_tray_menu = QMenu(self)
|
||||||
self.restore_action = self.system_tray_menu.addAction(
|
self.restore_action = self.system_tray_menu.addAction(
|
||||||
QIcon(I('page.png')), _('&Restore'))
|
QIcon(I('page.png')), _('&Restore'))
|
||||||
self.donate_action = self.system_tray_menu.addAction(
|
self.system_tray_menu.addAction(self.donate_action)
|
||||||
QIcon(I('donate.png')), _('&Donate to support calibre'))
|
|
||||||
self.donate_button.setDefaultAction(self.donate_action)
|
self.donate_button.setDefaultAction(self.donate_action)
|
||||||
self.donate_button.setStatusTip(self.donate_button.toolTip())
|
self.donate_button.setStatusTip(self.donate_button.toolTip())
|
||||||
self.eject_action = self.system_tray_menu.addAction(
|
self.eject_action = self.system_tray_menu.addAction(
|
||||||
|
@ -547,7 +547,7 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
return matchkind, query
|
return matchkind, query
|
||||||
|
|
||||||
def get_bool_matches(self, location, query, candidates):
|
def get_bool_matches(self, location, query, candidates):
|
||||||
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] != 'no'
|
bools_are_tristate = not self.db_prefs.get('bools_are_tristate')
|
||||||
loc = self.field_metadata[location]['rec_index']
|
loc = self.field_metadata[location]['rec_index']
|
||||||
matches = set()
|
matches = set()
|
||||||
query = icu_lower(query)
|
query = icu_lower(query)
|
||||||
@ -947,7 +947,7 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
if not fields:
|
if not fields:
|
||||||
fields = [('timestamp', False)]
|
fields = [('timestamp', False)]
|
||||||
|
|
||||||
keyg = SortKeyGenerator(fields, self.field_metadata, self._data)
|
keyg = SortKeyGenerator(fields, self.field_metadata, self._data, self.db_prefs)
|
||||||
self._map.sort(key=keyg)
|
self._map.sort(key=keyg)
|
||||||
|
|
||||||
tmap = list(itertools.repeat(False, len(self._data)))
|
tmap = list(itertools.repeat(False, len(self._data)))
|
||||||
@ -970,9 +970,10 @@ class SortKey(object):
|
|||||||
|
|
||||||
class SortKeyGenerator(object):
|
class SortKeyGenerator(object):
|
||||||
|
|
||||||
def __init__(self, fields, field_metadata, data):
|
def __init__(self, fields, field_metadata, data, db_prefs):
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
self.field_metadata = field_metadata
|
self.field_metadata = field_metadata
|
||||||
|
self.db_prefs = db_prefs
|
||||||
self.orders = [1 if x[1] else -1 for x in fields]
|
self.orders = [1 if x[1] else -1 for x in fields]
|
||||||
self.entries = [(x[0], field_metadata[x[0]]) for x in fields]
|
self.entries = [(x[0], field_metadata[x[0]]) for x in fields]
|
||||||
self.library_order = tweaks['title_series_sorting'] == 'library_order'
|
self.library_order = tweaks['title_series_sorting'] == 'library_order'
|
||||||
@ -1032,7 +1033,7 @@ class SortKeyGenerator(object):
|
|||||||
val = self.string_sort_key(val)
|
val = self.string_sort_key(val)
|
||||||
|
|
||||||
elif dt == 'bool':
|
elif dt == 'bool':
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
if not self.db_prefs.get('bools_are_tristate'):
|
||||||
val = {True: 1, False: 2, None: 2}.get(val, 2)
|
val = {True: 1, False: 2, None: 2}.get(val, 2)
|
||||||
else:
|
else:
|
||||||
val = {True: 1, False: 2, None: 3}.get(val, 3)
|
val = {True: 1, False: 2, None: 3}.get(val, 3)
|
||||||
|
@ -41,7 +41,6 @@ from calibre.utils.magick.draw import save_cover_data_to
|
|||||||
from calibre.utils.recycle_bin import delete_file, delete_tree
|
from calibre.utils.recycle_bin import delete_file, delete_tree
|
||||||
from calibre.utils.formatter_functions import load_user_template_functions
|
from calibre.utils.formatter_functions import load_user_template_functions
|
||||||
|
|
||||||
|
|
||||||
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
||||||
|
|
||||||
class Tag(object):
|
class Tag(object):
|
||||||
@ -214,6 +213,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
defs['gui_restriction'] = defs['cs_restriction'] = ''
|
defs['gui_restriction'] = defs['cs_restriction'] = ''
|
||||||
defs['categories_using_hierarchy'] = []
|
defs['categories_using_hierarchy'] = []
|
||||||
|
|
||||||
|
# Migrate the bool tristate tweak
|
||||||
|
defs['bools_are_tristate'] = \
|
||||||
|
tweaks.get('bool_custom_columns_are_tristate', 'yes') == 'yes'
|
||||||
|
if self.prefs.get('bools_are_tristate') is None:
|
||||||
|
self.prefs.set('bools_are_tristate', defs['bools_are_tristate'])
|
||||||
|
|
||||||
# Migrate saved search and user categories to db preference scheme
|
# Migrate saved search and user categories to db preference scheme
|
||||||
def migrate_preference(key, default):
|
def migrate_preference(key, default):
|
||||||
oldval = prefs[key]
|
oldval = prefs[key]
|
||||||
|
@ -17,8 +17,8 @@ from calibre.utils.magick.draw import save_cover_data_to, Image, \
|
|||||||
|
|
||||||
class CSSortKeyGenerator(SortKeyGenerator):
|
class CSSortKeyGenerator(SortKeyGenerator):
|
||||||
|
|
||||||
def __init__(self, fields, fm):
|
def __init__(self, fields, fm, db_prefs):
|
||||||
SortKeyGenerator.__init__(self, fields, fm, None)
|
SortKeyGenerator.__init__(self, fields, fm, None, db_prefs)
|
||||||
|
|
||||||
def __call__(self, record):
|
def __call__(self, record):
|
||||||
return self.itervals(record).next()
|
return self.itervals(record).next()
|
||||||
@ -56,7 +56,8 @@ class ContentServer(object):
|
|||||||
field = self.db.data.sanitize_sort_field_name(field)
|
field = self.db.data.sanitize_sort_field_name(field)
|
||||||
if field not in self.db.field_metadata.sortable_field_keys():
|
if field not in self.db.field_metadata.sortable_field_keys():
|
||||||
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
|
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
|
||||||
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata)
|
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata,
|
||||||
|
self.db.prefs)
|
||||||
items.sort(key=keyg, reverse=not order)
|
items.sort(key=keyg, reverse=not order)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -197,7 +197,7 @@ Once you've located the zip file of your plugin you can then directly update it
|
|||||||
|
|
||||||
zip -R /path/to/plugin/zip/file.zip *
|
zip -R /path/to/plugin/zip/file.zip *
|
||||||
|
|
||||||
This will automatically update all changed files. It relies on the freely available zip command line tool.
|
This will update all changed files. It relies on the freely available zip command line tool. Note that you should quit calibre before running this command.
|
||||||
|
|
||||||
More plugin examples
|
More plugin examples
|
||||||
----------------------
|
----------------------
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user