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
c1fb958380
@ -18,7 +18,7 @@ class TelepolisNews(BasicNewsRecipe):
|
||||
recursion = 0
|
||||
no_stylesheets = True
|
||||
encoding = "utf-8"
|
||||
language = 'de_AT'
|
||||
language = 'de'
|
||||
|
||||
use_embedded_content =False
|
||||
remove_empty_feeds = True
|
||||
|
@ -7,13 +7,11 @@ usatoday.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, NavigableString, Tag
|
||||
import re
|
||||
|
||||
class USAToday(BasicNewsRecipe):
|
||||
|
||||
title = 'USA Today'
|
||||
__author__ = 'GRiker'
|
||||
__author__ = 'Kovid Goyal'
|
||||
oldest_article = 1
|
||||
timefmt = ''
|
||||
max_articles_per_feed = 20
|
||||
@ -31,7 +29,6 @@ class USAToday(BasicNewsRecipe):
|
||||
margin-bottom: 0em; \
|
||||
font-size: smaller;}\n \
|
||||
.articleBody {text-align: left;}\n '
|
||||
conversion_options = { 'linearize_tables' : True }
|
||||
#simultaneous_downloads = 1
|
||||
feeds = [
|
||||
('Top Headlines', 'http://rssfeeds.usatoday.com/usatoday-NewsTopStories'),
|
||||
@ -47,63 +44,26 @@ class USAToday(BasicNewsRecipe):
|
||||
('Most Popular', 'http://rssfeeds.usatoday.com/Usatoday-MostViewedArticles'),
|
||||
('Offbeat News', 'http://rssfeeds.usatoday.com/UsatodaycomOffbeat-TopStories'),
|
||||
]
|
||||
keep_only_tags = [dict(attrs={'class':[
|
||||
'byLine',
|
||||
'inside-copy',
|
||||
'inside-head',
|
||||
'inside-head2',
|
||||
'item',
|
||||
'item-block',
|
||||
'photo-container',
|
||||
]}),
|
||||
dict(id=[
|
||||
'applyMainStoryPhoto',
|
||||
'permalink',
|
||||
])]
|
||||
keep_only_tags = [dict(attrs={'class':'story'})]
|
||||
remove_tags = [
|
||||
dict(attrs={'class':[
|
||||
'share',
|
||||
'reprints',
|
||||
'inline-h3',
|
||||
'info-extras',
|
||||
'ppy-outer',
|
||||
'ppy-caption',
|
||||
'comments',
|
||||
'jump',
|
||||
'pagetools',
|
||||
'post-attributes',
|
||||
'tags',
|
||||
'bottom-tools',
|
||||
'sponsoredlinks',
|
||||
]}),
|
||||
dict(id=['pluck']),
|
||||
]
|
||||
|
||||
remove_tags = [dict(attrs={'class':[
|
||||
'comments',
|
||||
'jump',
|
||||
'pagetools',
|
||||
'post-attributes',
|
||||
'tags',
|
||||
]}),
|
||||
dict(id=[])]
|
||||
|
||||
#feeds = [('Most Popular', 'http://rssfeeds.usatoday.com/Usatoday-MostViewedArticles')]
|
||||
|
||||
def dump_hex(self, src, length=16):
|
||||
''' Diagnostic '''
|
||||
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
|
||||
N=0; result=''
|
||||
while src:
|
||||
s,src = src[:length],src[length:]
|
||||
hexa = ' '.join(["%02X"%ord(x) for x in s])
|
||||
s = s.translate(FILTER)
|
||||
result += "%04X %-*s %s\n" % (N, length*3, hexa, s)
|
||||
N+=length
|
||||
print result
|
||||
|
||||
def fixChars(self,string):
|
||||
# Replace lsquo (\x91)
|
||||
fixed = re.sub("\x91","‘",string)
|
||||
|
||||
# Replace rsquo (\x92)
|
||||
fixed = re.sub("\x92","’",fixed)
|
||||
|
||||
# Replace ldquo (\x93)
|
||||
fixed = re.sub("\x93","“",fixed)
|
||||
|
||||
# Replace rdquo (\x94)
|
||||
fixed = re.sub("\x94","”",fixed)
|
||||
|
||||
# Replace ndash (\x96)
|
||||
fixed = re.sub("\x96","–",fixed)
|
||||
|
||||
# Replace mdash (\x97)
|
||||
fixed = re.sub("\x97","—",fixed)
|
||||
|
||||
return fixed
|
||||
|
||||
def get_masthead_url(self):
|
||||
masthead = 'http://i.usatoday.net/mobile/_common/_images/565x73_usat_mobile.gif'
|
||||
@ -115,321 +75,4 @@ class USAToday(BasicNewsRecipe):
|
||||
masthead = None
|
||||
return masthead
|
||||
|
||||
def massageNCXText(self, description):
|
||||
# Kindle TOC descriptions won't render certain characters
|
||||
if description:
|
||||
massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES))
|
||||
# Replace '&' with '&'
|
||||
massaged = re.sub("&","&", massaged)
|
||||
return self.fixChars(massaged)
|
||||
else:
|
||||
return description
|
||||
|
||||
def parse_feeds(self, *args, **kwargs):
|
||||
parsed_feeds = BasicNewsRecipe.parse_feeds(self, *args, **kwargs)
|
||||
# Count articles for progress dialog
|
||||
article_count = 0
|
||||
for feed in parsed_feeds:
|
||||
article_count += len(feed)
|
||||
self.log( "Queued %d articles" % article_count)
|
||||
return parsed_feeds
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
soup = self.strip_anchors(soup)
|
||||
return soup
|
||||
|
||||
def postprocess_html(self, soup, first_fetch):
|
||||
|
||||
# Remove navLinks <div class="inside-copy" style="padding-bottom:3px">
|
||||
navLinks = soup.find(True,{'style':'padding-bottom:3px'})
|
||||
if navLinks:
|
||||
navLinks.extract()
|
||||
|
||||
# Remove <div class="inside-copy" style="margin-bottom:10px">
|
||||
gibberish = soup.find(True,{'style':'margin-bottom:10px'})
|
||||
if gibberish:
|
||||
gibberish.extract()
|
||||
|
||||
# Change <inside-head> to <h2>
|
||||
headline = soup.find(True, {'class':['inside-head','inside-head2']})
|
||||
if not headline:
|
||||
headline = soup.find('h3')
|
||||
if headline:
|
||||
tag = Tag(soup, "h2")
|
||||
tag['class'] = "headline"
|
||||
tag.insert(0, headline.contents[0])
|
||||
headline.replaceWith(tag)
|
||||
else:
|
||||
print "unable to find headline:\n%s\n" % soup
|
||||
|
||||
# Change byLine to byline, change commas to middot
|
||||
# Kindle renders commas in byline as '&'
|
||||
byline = soup.find(True, {'class':'byLine'})
|
||||
if byline:
|
||||
byline['class'] = 'byline'
|
||||
# Replace comma with middot
|
||||
byline.contents[0].replaceWith(re.sub(","," ·", byline.renderContents()))
|
||||
|
||||
jumpout_punc_list = [':','?']
|
||||
# Remove the inline jumpouts in <div class="inside-copy">
|
||||
paras = soup.findAll(True, {'class':'inside-copy'})
|
||||
for para in paras:
|
||||
if re.match("<b>[\w\W]+ ",para.renderContents()):
|
||||
p = para.find('b')
|
||||
for punc in jumpout_punc_list:
|
||||
punc_offset = p.contents[0].find(punc)
|
||||
if punc_offset == -1:
|
||||
continue
|
||||
if punc_offset > 1:
|
||||
if p.contents[0][:punc_offset] == p.contents[0][:punc_offset].upper():
|
||||
#print "extracting \n%s\n" % para.prettify()
|
||||
para.extract()
|
||||
|
||||
# Reset class for remaining
|
||||
paras = soup.findAll(True, {'class':'inside-copy'})
|
||||
for para in paras:
|
||||
para['class'] = 'articleBody'
|
||||
|
||||
# Remove inline jumpouts in <p>
|
||||
paras = soup.findAll(['p'])
|
||||
for p in paras:
|
||||
if hasattr(p,'contents') and len(p.contents):
|
||||
for punc in jumpout_punc_list:
|
||||
punc_offset = p.contents[0].find(punc)
|
||||
if punc_offset == -1:
|
||||
continue
|
||||
if punc_offset > 2 and hasattr(p,'a') and len(p.contents):
|
||||
#print "evaluating %s\n" % p.contents[0][:punc_offset+1]
|
||||
if p.contents[0][:punc_offset] == p.contents[0][:punc_offset].upper():
|
||||
#print "extracting \n%s\n" % p.prettify()
|
||||
p.extract()
|
||||
|
||||
# Capture the first img, insert after headline
|
||||
imgs = soup.findAll('img')
|
||||
print "postprocess_html(): %d images" % len(imgs)
|
||||
if imgs:
|
||||
divTag = Tag(soup, 'div')
|
||||
divTag['class'] = 'image'
|
||||
body = soup.find('body')
|
||||
img = imgs[0]
|
||||
#print "img: \n%s\n" % img.prettify()
|
||||
|
||||
# Table for photo and credit
|
||||
tableTag = Tag(soup,'table')
|
||||
|
||||
# Photo
|
||||
trimgTag = Tag(soup, 'tr')
|
||||
tdimgTag = Tag(soup, 'td')
|
||||
tdimgTag.insert(0,img)
|
||||
trimgTag.insert(0,tdimgTag)
|
||||
tableTag.insert(0,trimgTag)
|
||||
|
||||
# Credit
|
||||
trcreditTag = Tag(soup, 'tr')
|
||||
|
||||
tdcreditTag = Tag(soup, 'td')
|
||||
tdcreditTag['class'] = 'credit'
|
||||
credit = soup.find('td',{'class':'photoCredit'})
|
||||
if credit:
|
||||
tdcreditTag.insert(0,NavigableString(credit.renderContents()))
|
||||
else:
|
||||
credit = img['credit']
|
||||
if credit:
|
||||
tdcreditTag.insert(0,NavigableString(credit))
|
||||
else:
|
||||
tdcreditTag.insert(0,NavigableString(''))
|
||||
|
||||
trcreditTag.insert(0,tdcreditTag)
|
||||
tableTag.insert(1,trcreditTag)
|
||||
dtc = 0
|
||||
divTag.insert(dtc,tableTag)
|
||||
dtc += 1
|
||||
|
||||
if False:
|
||||
# Add the caption in the table
|
||||
tableCaptionTag = Tag(soup,'caption')
|
||||
tableCaptionTag.insert(0,soup.find('td',{'class':'photoCredit'}).renderContents())
|
||||
tableTag.insert(1,tableCaptionTag)
|
||||
divTag.insert(dtc,tableTag)
|
||||
dtc += 1
|
||||
body.insert(1,divTag)
|
||||
else:
|
||||
# Add the caption below the table
|
||||
#print "Looking for caption in this soup:\n%s" % img.prettify()
|
||||
captionTag = Tag(soup,'p')
|
||||
captionTag['class'] = 'caption'
|
||||
if hasattr(img,'alt') and img['alt']:
|
||||
captionTag.insert(0,NavigableString('<blockquote>%s</blockquote>' % img['alt']))
|
||||
divTag.insert(dtc, captionTag)
|
||||
dtc += 1
|
||||
else:
|
||||
try:
|
||||
captionTag.insert(0,NavigableString('<blockquote>%s</blockquote>' % img['cutline']))
|
||||
divTag.insert(dtc, captionTag)
|
||||
dtc += 1
|
||||
except:
|
||||
pass
|
||||
|
||||
hrTag = Tag(soup, 'hr')
|
||||
divTag.insert(dtc, hrTag)
|
||||
dtc += 1
|
||||
|
||||
# Delete <div id="applyMainStoryPhoto"
|
||||
photoJunk = soup.find('div',{'id':'applyMainStoryPhoto'})
|
||||
if photoJunk:
|
||||
photoJunk.extract()
|
||||
|
||||
# Insert img after headline
|
||||
tag = body.find(True)
|
||||
insertLoc = 0
|
||||
headline_found = False
|
||||
while True:
|
||||
# Scan the top-level tags
|
||||
insertLoc += 1
|
||||
if hasattr(tag,'class') and tag['class'] == 'headline':
|
||||
headline_found = True
|
||||
body.insert(insertLoc,divTag)
|
||||
break
|
||||
tag = tag.nextSibling
|
||||
if not tag:
|
||||
break
|
||||
|
||||
if not headline_found:
|
||||
# Monolithic <div> - restructure
|
||||
tag = body.find(True)
|
||||
while True:
|
||||
insertLoc += 1
|
||||
try:
|
||||
if hasattr(tag,'class') and tag['class'] == 'headline':
|
||||
headline_found = True
|
||||
tag.insert(insertLoc,divTag)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
tag = tag.next
|
||||
if not tag:
|
||||
break
|
||||
|
||||
# Yank out headline, img and caption
|
||||
headline = body.find('h2','headline')
|
||||
img = body.find('div','image')
|
||||
caption = body.find('p''class')
|
||||
|
||||
# body(0) is calibre_navbar
|
||||
# body(1) is <div class="item">
|
||||
|
||||
btc = 1
|
||||
headline.extract()
|
||||
body.insert(1, headline)
|
||||
btc += 1
|
||||
if img:
|
||||
img.extract()
|
||||
body.insert(btc, img)
|
||||
btc += 1
|
||||
if caption:
|
||||
caption.extract()
|
||||
body.insert(btc, caption)
|
||||
btc += 1
|
||||
|
||||
if len(imgs) > 1:
|
||||
if True:
|
||||
[img.extract() for img in imgs[1:]]
|
||||
else:
|
||||
# Format the remaining images
|
||||
# This doesn't work yet
|
||||
for img in imgs[1:]:
|
||||
print "img:\n%s\n" % img.prettify()
|
||||
divTag = Tag(soup, 'div')
|
||||
divTag['class'] = 'image'
|
||||
|
||||
# Table for photo and credit
|
||||
tableTag = Tag(soup,'table')
|
||||
|
||||
# Photo
|
||||
trimgTag = Tag(soup, 'tr')
|
||||
tdimgTag = Tag(soup, 'td')
|
||||
tdimgTag.insert(0,img)
|
||||
trimgTag.insert(0,tdimgTag)
|
||||
tableTag.insert(0,trimgTag)
|
||||
|
||||
# Credit
|
||||
trcreditTag = Tag(soup, 'tr')
|
||||
|
||||
tdcreditTag = Tag(soup, 'td')
|
||||
tdcreditTag['class'] = 'credit'
|
||||
try:
|
||||
tdcreditTag.insert(0,NavigableString(img['credit']))
|
||||
except:
|
||||
tdcreditTag.insert(0,NavigableString(''))
|
||||
trcreditTag.insert(0,tdcreditTag)
|
||||
tableTag.insert(1,trcreditTag)
|
||||
divTag.insert(0,tableTag)
|
||||
soup.img.replaceWith(divTag)
|
||||
|
||||
return soup
|
||||
|
||||
def postprocess_book(self, oeb, opts, log) :
|
||||
|
||||
def extract_byline(href) :
|
||||
# <meta name="byline" content=
|
||||
soup = BeautifulSoup(str(oeb.manifest.hrefs[href]))
|
||||
byline = soup.find('div',attrs={'class':'byline'})
|
||||
if byline:
|
||||
byline['class'] = 'byline'
|
||||
# Replace comma with middot
|
||||
byline.contents[0].replaceWith(re.sub(u",", u" ·",
|
||||
byline.renderContents(encoding=None)))
|
||||
return byline.renderContents(encoding=None)
|
||||
else :
|
||||
paras = soup.findAll(text=True)
|
||||
for para in paras:
|
||||
if para.startswith("Copyright"):
|
||||
return para[len('Copyright xxxx '):para.find('.')]
|
||||
return None
|
||||
|
||||
def extract_description(href) :
|
||||
soup = BeautifulSoup(str(oeb.manifest.hrefs[href]))
|
||||
description = soup.find('meta',attrs={'name':'description'})
|
||||
if description :
|
||||
return self.massageNCXText(description['content'])
|
||||
else:
|
||||
# Take first paragraph of article
|
||||
articleBody = soup.find('div',attrs={'id':['articleBody','item']})
|
||||
if articleBody:
|
||||
paras = articleBody.findAll('p')
|
||||
for p in paras:
|
||||
if p.renderContents() > '' :
|
||||
return self.massageNCXText(self.tag_to_string(p,use_alt=False))
|
||||
else:
|
||||
print "Didn't find <div id='articleBody'> in this soup:\n%s" % soup.prettify()
|
||||
return None
|
||||
|
||||
# Method entry point here
|
||||
# Single section toc looks different than multi-section tocs
|
||||
if oeb.toc.depth() == 2 :
|
||||
for article in oeb.toc :
|
||||
if article.author is None :
|
||||
article.author = extract_byline(article.href)
|
||||
if article.description is None :
|
||||
article.description = extract_description(article.href)
|
||||
elif oeb.toc.depth() == 3 :
|
||||
for section in oeb.toc :
|
||||
for article in section :
|
||||
article.author = extract_byline(article.href)
|
||||
'''
|
||||
if article.author is None :
|
||||
article.author = self.massageNCXText(extract_byline(article.href))
|
||||
else:
|
||||
article.author = self.massageNCXText(article.author)
|
||||
'''
|
||||
if article.description is None :
|
||||
article.description = extract_description(article.href)
|
||||
|
||||
def strip_anchors(self,soup):
|
||||
paras = soup.findAll(True)
|
||||
for para in paras:
|
||||
aTags = para.findAll('a')
|
||||
for a in aTags:
|
||||
if a.img is None:
|
||||
a.replaceWith(a.renderContents().decode('cp1252','replace'))
|
||||
return soup
|
||||
|
@ -966,7 +966,9 @@ class OPF(object): # {{{
|
||||
cover_id = covers[0].get('content')
|
||||
for item in self.itermanifest():
|
||||
if item.get('id', None) == cover_id:
|
||||
return item.get('href', None)
|
||||
mt = item.get('media-type', '')
|
||||
if 'xml' not in mt:
|
||||
return item.get('href', None)
|
||||
|
||||
@dynamic_property
|
||||
def cover(self):
|
||||
|
@ -246,7 +246,7 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
def delete_requested(self, name, location):
|
||||
loc = location.replace('/', os.sep)
|
||||
if not question_dialog(self.gui, _('Are you sure?'), '<p>'+
|
||||
_('All files from %s will be '
|
||||
_('<b style="color: red">All files</b> from <br><br><b>%s</b><br><br> will be '
|
||||
'<b>permanently deleted</b>. Are you sure?') % loc,
|
||||
show_copy_button=False):
|
||||
return
|
||||
|
@ -7,16 +7,16 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, shutil
|
||||
from contextlib import closing
|
||||
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
|
||||
|
||||
from PyQt4.Qt import QDialog
|
||||
|
||||
from calibre.constants import isosx
|
||||
from calibre.gui2 import open_local_file
|
||||
from calibre.gui2 import open_local_file, error_dialog
|
||||
from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
|
||||
from calibre.libunzip import extract as zipextract
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||
from calibre.ptempfile import (PersistentTemporaryDirectory,
|
||||
PersistentTemporaryFile)
|
||||
|
||||
class TweakEpub(QDialog, Ui_Dialog):
|
||||
'''
|
||||
@ -37,11 +37,15 @@ class TweakEpub(QDialog, Ui_Dialog):
|
||||
self.cancel_button.clicked.connect(self.reject)
|
||||
self.explode_button.clicked.connect(self.explode)
|
||||
self.rebuild_button.clicked.connect(self.rebuild)
|
||||
self.preview_button.clicked.connect(self.preview)
|
||||
|
||||
# Position update dialog overlaying top left of app window
|
||||
parent_loc = parent.pos()
|
||||
self.move(parent_loc.x(),parent_loc.y())
|
||||
|
||||
self.gui = parent
|
||||
self._preview_files = []
|
||||
|
||||
def cleanup(self):
|
||||
if isosx:
|
||||
try:
|
||||
@ -55,6 +59,11 @@ class TweakEpub(QDialog, Ui_Dialog):
|
||||
# Delete directory containing exploded ePub
|
||||
if self._exploded is not None:
|
||||
shutil.rmtree(self._exploded, ignore_errors=True)
|
||||
for x in self._preview_files:
|
||||
try:
|
||||
os.remove(x)
|
||||
except:
|
||||
pass
|
||||
|
||||
def display_exploded(self):
|
||||
'''
|
||||
@ -71,9 +80,8 @@ class TweakEpub(QDialog, Ui_Dialog):
|
||||
self.rebuild_button.setEnabled(True)
|
||||
self.explode_button.setEnabled(False)
|
||||
|
||||
def rebuild(self, *args):
|
||||
self._output = os.path.join(self._exploded, 'rebuilt.epub')
|
||||
with closing(ZipFile(self._output, 'w', compression=ZIP_DEFLATED)) as zf:
|
||||
def do_rebuild(self, src):
|
||||
with ZipFile(src, 'w', compression=ZIP_DEFLATED) as zf:
|
||||
# Write mimetype
|
||||
zf.write(os.path.join(self._exploded,'mimetype'), 'mimetype', compress_type=ZIP_STORED)
|
||||
# Write everything else
|
||||
@ -86,5 +94,23 @@ class TweakEpub(QDialog, Ui_Dialog):
|
||||
zfn = os.path.relpath(absfn,
|
||||
self._exploded).replace(os.sep, '/')
|
||||
zf.write(absfn, zfn)
|
||||
|
||||
def preview(self):
|
||||
if not self._exploded:
|
||||
return error_dialog(self, _('Cannot preview'),
|
||||
_('You must first explode the epub before previewing.'),
|
||||
show=True)
|
||||
|
||||
tf = PersistentTemporaryFile('.epub')
|
||||
tf.close()
|
||||
self._preview_files.append(tf.name)
|
||||
|
||||
self.do_rebuild(tf.name)
|
||||
|
||||
self.gui.iactions['View']._view_file(tf.name)
|
||||
|
||||
def rebuild(self, *args):
|
||||
self._output = os.path.join(self._exploded, 'rebuilt.epub')
|
||||
self.do_rebuild(self._output)
|
||||
return QDialog.accept(self)
|
||||
|
||||
|
@ -23,6 +23,16 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><p>Explode the ePub to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window <b>and the editor windows you used to edit files in the epub</b>.</p><p>Rebuild the ePub, updating your calibre library.</p></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="explode_button">
|
||||
<property name="statusTip">
|
||||
@ -37,23 +47,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="rebuild_button">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Rebuild ePub from exploded contents</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Rebuild ePub</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/exec.png</normaloff>:/images/exec.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="cancel_button">
|
||||
<property name="statusTip">
|
||||
@ -68,13 +61,31 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><p>Explode the ePub to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window <b>and the editor windows you used to edit files in the epub</b>.</p><p>Rebuild the ePub, updating your calibre library.</p></string>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="rebuild_button">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<property name="statusTip">
|
||||
<string>Rebuild ePub from exploded contents</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Rebuild ePub</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/exec.png</normaloff>:/images/exec.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="preview_button">
|
||||
<property name="text">
|
||||
<string>&Preview ePub</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/view.png</normaloff>:/images/view.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -190,7 +190,15 @@ class FieldsModel(QAbstractListModel): # {{{
|
||||
return ans | Qt.ItemIsUserCheckable
|
||||
|
||||
def restore_defaults(self):
|
||||
self.overrides = dict([(f, self.state(f, True)) for f in self.fields])
|
||||
self.overrides = dict([(f, self.state(f, Qt.Checked)) for f in self.fields])
|
||||
self.reset()
|
||||
|
||||
def select_all(self):
|
||||
self.overrides = dict([(f, Qt.Checked) for f in self.fields])
|
||||
self.reset()
|
||||
|
||||
def clear_all(self):
|
||||
self.overrides = dict([(f, Qt.Unchecked) for f in self.fields])
|
||||
self.reset()
|
||||
|
||||
def setData(self, index, val, role):
|
||||
@ -273,6 +281,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.fields_view.setModel(self.fields_model)
|
||||
self.fields_model.dataChanged.connect(self.changed_signal)
|
||||
|
||||
self.select_all_button.clicked.connect(self.fields_model.select_all)
|
||||
self.clear_all_button.clicked.connect(self.fields_model.clear_all)
|
||||
|
||||
def configure_plugin(self):
|
||||
for index in self.sources_view.selectionModel().selectedRows():
|
||||
plugin = self.sources_model.data(index, Qt.UserRole)
|
||||
|
@ -77,8 +77,8 @@
|
||||
<property name="title">
|
||||
<string>Downloaded metadata fields</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QListView" name="fields_view">
|
||||
<property name="toolTip">
|
||||
<string>If you uncheck any fields, metadata for those fields will not be downloaded</string>
|
||||
@ -88,6 +88,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="select_all_button">
|
||||
<property name="text">
|
||||
<string>&Select all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="clear_all_button">
|
||||
<property name="text">
|
||||
<string>&Clear all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -107,7 +107,7 @@
|
||||
<string>Open a selected book in the system's web browser</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open external</string>
|
||||
<string>Open in &external browser</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user