mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
99c8afdd60
@ -16,7 +16,7 @@ class DailyTelegraph(BasicNewsRecipe):
|
||||
language = 'en_AU'
|
||||
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 10
|
||||
max_articles_per_feed = 20
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
|
@ -30,7 +30,7 @@ class ANDROID(USBMS):
|
||||
0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]},
|
||||
|
||||
# Samsung
|
||||
0x04e8 : { 0x681d : [0x0222], 0x681c : [0x0222, 0x0224]},
|
||||
0x04e8 : { 0x681d : [0x0222, 0x0400], 0x681c : [0x0222, 0x0224]},
|
||||
|
||||
# Acer
|
||||
0x502 : { 0x3203 : [0x0100]},
|
||||
@ -41,10 +41,12 @@ class ANDROID(USBMS):
|
||||
'be used')
|
||||
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(EBOOK_DIR_MAIN)
|
||||
|
||||
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER', 'GT-I5700']
|
||||
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
|
||||
'GT-I5700', 'SAMSUNG']
|
||||
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE']
|
||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD',
|
||||
'PROD_GT-I9000']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'PROD_GT-I9000_CARD']
|
||||
|
||||
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
||||
|
||||
|
@ -76,7 +76,7 @@ class ITUNES(DevicePlugin):
|
||||
supported_platforms = ['osx','windows']
|
||||
author = 'GRiker'
|
||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||
version = (0, 5, 0)
|
||||
version = (0,6,0)
|
||||
|
||||
OPEN_FEEDBACK_MESSAGE = _(
|
||||
'Apple device detected, launching iTunes, please wait ...')
|
||||
@ -280,7 +280,7 @@ class ITUNES(DevicePlugin):
|
||||
|
||||
if self.report_progress is not None:
|
||||
self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count))
|
||||
self._purge_orphans(cached_books)
|
||||
self._purge_orphans(library_books, cached_books)
|
||||
|
||||
elif iswindows:
|
||||
try:
|
||||
@ -316,7 +316,7 @@ class ITUNES(DevicePlugin):
|
||||
if self.report_progress is not None:
|
||||
self.report_progress(i+1/book_count,
|
||||
_('%d of %d') % (i+1, book_count))
|
||||
self._purge_orphans(cached_books)
|
||||
self._purge_orphans(library_books, cached_books)
|
||||
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
@ -324,9 +324,9 @@ class ITUNES(DevicePlugin):
|
||||
if self.report_progress is not None:
|
||||
self.report_progress(1.0, _('finished'))
|
||||
self.cached_books = cached_books
|
||||
if DEBUG:
|
||||
self._dump_booklist(booklist, 'returning from books():')
|
||||
self._dump_cached_books('returning from books():')
|
||||
# if DEBUG:
|
||||
# self._dump_booklist(booklist, 'returning from books():')
|
||||
# self._dump_cached_books('returning from books():')
|
||||
return booklist
|
||||
else:
|
||||
return []
|
||||
@ -463,7 +463,7 @@ class ITUNES(DevicePlugin):
|
||||
else:
|
||||
# iTunes running, but not connected iPad
|
||||
if DEBUG:
|
||||
self.log.info(' self.ejected = True')
|
||||
self.log.info(' iDevice has been ejected')
|
||||
self.ejected = True
|
||||
return False
|
||||
|
||||
@ -782,121 +782,6 @@ class ITUNES(DevicePlugin):
|
||||
# self._dump_cached_books('upload_books()')
|
||||
self._dump_update_list('upload_books()')
|
||||
|
||||
'''
|
||||
if isosx:
|
||||
|
||||
for (i,file) in enumerate(files):
|
||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||
|
||||
if self.manual_sync_mode:
|
||||
# Delete existing from Device|Books, add to self.update_list
|
||||
# for deletion from booklist[0] during add_books_to_metadata
|
||||
if path in self.cached_books:
|
||||
self.update_list.append(self.cached_books[path])
|
||||
if DEBUG:
|
||||
self.log.info(" adding '%s' by %s to self.update_list" %
|
||||
(self.cached_books[path]['title'],self.cached_books[path]['author']))
|
||||
|
||||
if DEBUG:
|
||||
self.log.info( " deleting existing '%s'" % (path))
|
||||
self._remove_from_iTunes(self.cached_books[path])
|
||||
if self.manual_sync_mode:
|
||||
dev_book_added = self._remove_from_device(self.cached_books[path])
|
||||
|
||||
|
||||
# Add to iTunes Library|Books
|
||||
fpath = file
|
||||
if getattr(file, 'orig_file_path', None) is not None:
|
||||
fpath = file.orig_file_path
|
||||
elif getattr(file, 'name', None) is not None:
|
||||
fpath = file.name
|
||||
|
||||
if isinstance(file,PersistentTemporaryFile) and self.manual_sync_mode:
|
||||
if DEBUG:
|
||||
self.log.info(" PTF not added to Library|Books")
|
||||
else:
|
||||
added = self.iTunes.add(appscript.mactypes.File(fpath))
|
||||
if DEBUG:
|
||||
self.log.info(" file added to Library|Books")
|
||||
|
||||
dev_book_added = None
|
||||
if self.manual_sync_mode:
|
||||
dev_book_added = self._add_device_book(fpath)
|
||||
|
||||
thumb = None
|
||||
if metadata[i].cover:
|
||||
try:
|
||||
# Use cover data as artwork
|
||||
cover_data = open(metadata[i].cover,'rb')
|
||||
added.artworks[1].data_.set(cover_data.read())
|
||||
|
||||
# Resize for thumb
|
||||
width = metadata[i].thumbnail[0]
|
||||
height = metadata[i].thumbnail[1]
|
||||
im = PILImage.open(metadata[i].cover)
|
||||
im = im.resize((width, height), PILImage.ANTIALIAS)
|
||||
of = cStringIO.StringIO()
|
||||
im.convert('RGB').save(of, 'JPEG')
|
||||
thumb = of.getvalue()
|
||||
|
||||
# Refresh the thumbnail cache
|
||||
if DEBUG:
|
||||
self.log.info( " refreshing cached thumb for '%s'" % metadata[i].title)
|
||||
archive_path = os.path.join(self.cache_dir, "thumbs.zip")
|
||||
zfw = zipfile.ZipFile(archive_path, mode='a')
|
||||
thumb_path = path.rpartition('.')[0] + '.jpg'
|
||||
zfw.writestr(thumb_path, thumb)
|
||||
zfw.close()
|
||||
except:
|
||||
self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0]))
|
||||
self.log.error("ITUNES.upload_books(): error converting '%s' to thumb for '%s'" % (metadata[i].cover,metadata[i].title))
|
||||
|
||||
# Create a new Book
|
||||
this_book = Book(metadata[i].title, metadata[i].author[0])
|
||||
try:
|
||||
this_book.datetime = parse_date(str(added.date_added())).timetuple()
|
||||
except:
|
||||
pass
|
||||
this_book.db_id = None
|
||||
this_book.device_collections = []
|
||||
this_book.library_id = added
|
||||
this_book.path = path
|
||||
this_book.size = self._get_device_book_size(fpath, added.size())
|
||||
this_book.thumbnail = thumb
|
||||
this_book.iTunes_id = added
|
||||
|
||||
new_booklist.append(this_book)
|
||||
|
||||
# Populate the iTunes metadata
|
||||
if metadata[i].comments:
|
||||
added.comment.set(strip_tags.sub('',metadata[i].comments))
|
||||
added.description.set("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S'))
|
||||
added.enabled.set(True)
|
||||
if metadata[i].rating:
|
||||
added.rating.set(metadata[i].rating*10)
|
||||
added.sort_artist.set(metadata[i].author_sort.title())
|
||||
added.sort_name.set(this_book.title_sorter)
|
||||
|
||||
# Set genre from metadata
|
||||
# iTunes grabs the first dc:subject from the opf metadata,
|
||||
# But we can manually override with first tag starting with alpha
|
||||
for tag in metadata[i].tags:
|
||||
if self._is_alpha(tag[0]):
|
||||
added.genre.set(tag)
|
||||
break
|
||||
|
||||
# Add new_book to self.cached_paths
|
||||
self.cached_books[this_book.path] = {
|
||||
'title': this_book.title,
|
||||
'author': this_book.author,
|
||||
'lib_book': added,
|
||||
'dev_book': dev_book_added
|
||||
}
|
||||
|
||||
# Report progress
|
||||
if self.report_progress is not None:
|
||||
self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count))
|
||||
'''
|
||||
if isosx:
|
||||
for (i,file) in enumerate(files):
|
||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||
@ -1378,6 +1263,15 @@ class ITUNES(DevicePlugin):
|
||||
self.log.info(" %s" % file.name)
|
||||
self.log.info()
|
||||
|
||||
def _dump_library_books(self, library_books):
|
||||
'''
|
||||
'''
|
||||
if DEBUG:
|
||||
self.log.info("\n library_books:")
|
||||
for book in library_books:
|
||||
self.log.info(" %s" % book)
|
||||
self.log.info()
|
||||
|
||||
def _dump_update_list(self,header=None):
|
||||
if header:
|
||||
msg = '\nself.update_list called from %s' % header
|
||||
@ -1590,7 +1484,7 @@ class ITUNES(DevicePlugin):
|
||||
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind()))
|
||||
self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.name(), book.artist(), book.kind()))
|
||||
device_books.append(book)
|
||||
|
||||
elif iswindows:
|
||||
@ -1619,7 +1513,7 @@ class ITUNES(DevicePlugin):
|
||||
self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString))
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString))
|
||||
self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.Name, book.Artist, book.KindAsString))
|
||||
device_books.append(book)
|
||||
|
||||
finally:
|
||||
@ -1716,11 +1610,11 @@ class ITUNES(DevicePlugin):
|
||||
if book.location() == appscript.k.missing_value:
|
||||
library_orphans[path] = book
|
||||
if DEBUG:
|
||||
self.log.info(" found calibre orphan '%s' in Library|Books" % book.name())
|
||||
self.log.info(" found iTunes PTF '%s' in Library|Books" % book.name())
|
||||
|
||||
library_books[path] = book
|
||||
if DEBUG:
|
||||
self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind()))
|
||||
self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.name(), book.artist(), book.kind()))
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(' no Library playlists')
|
||||
@ -1730,9 +1624,6 @@ class ITUNES(DevicePlugin):
|
||||
|
||||
elif iswindows:
|
||||
lib = None
|
||||
# try:
|
||||
# pythoncom.CoInitialize()
|
||||
# self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||
for source in self.iTunes.sources:
|
||||
if source.Kind == self.Sources.index('Library'):
|
||||
lib = source
|
||||
@ -1772,16 +1663,14 @@ class ITUNES(DevicePlugin):
|
||||
if not book.Location:
|
||||
library_orphans[path] = book
|
||||
if DEBUG:
|
||||
self.log.info(" found calibre orphan '%s' in Library|Books" % book.Name)
|
||||
self.log.info(" found iTunes PTF '%s' in Library|Books" % book.Name)
|
||||
|
||||
library_books[path] = book
|
||||
if DEBUG:
|
||||
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString))
|
||||
self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.Name, book.Artist, book.KindAsString))
|
||||
except:
|
||||
if DEBUG:
|
||||
self.log.info(" no books in library")
|
||||
# finally:
|
||||
# pythoncom.CoUninitialize()
|
||||
self.library_orphans = library_orphans
|
||||
return library_books
|
||||
|
||||
@ -1905,44 +1794,36 @@ class ITUNES(DevicePlugin):
|
||||
self.version[0],self.version[1],self.version[2]))
|
||||
self.log.info(" iTunes_media: %s" % self.iTunes_media)
|
||||
|
||||
def _purge_orphans(self,cached_books):
|
||||
def _purge_orphans(self,library_books, cached_books):
|
||||
'''
|
||||
Scan self.library_orphans for any paths not on device
|
||||
Remove any true orphans from iTunes
|
||||
This occurs when recipes are uploaded in a previous session
|
||||
and the book has since been deleted on the device
|
||||
Scan library_books for any paths not on device
|
||||
Remove any iTunes orphans originally added by calibre
|
||||
This occurs when the user deletes a book in iBooks while disconnected
|
||||
'''
|
||||
if DEBUG:
|
||||
self.log.info(" ITUNES._purge_orphans")
|
||||
self.log.info("\n ITUNES._purge_orphans")
|
||||
#self._dump_library_books(library_books)
|
||||
#self.log.info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
|
||||
|
||||
orphan_paths = {}
|
||||
|
||||
for book in library_books:
|
||||
if isosx:
|
||||
for orphan in self.library_orphans:
|
||||
path = self.path_template % (self.library_orphans[orphan].name(),
|
||||
self.library_orphans[orphan].artist())
|
||||
orphan_paths[path] = self.library_orphans[orphan]
|
||||
|
||||
# Scan orphan_paths for paths not found in cached_books
|
||||
for orphan in orphan_paths.keys():
|
||||
if orphan not in cached_books:
|
||||
if book not in cached_books and \
|
||||
str(library_books[book].description()).startswith(self.description_prefix):
|
||||
if DEBUG:
|
||||
self.log.info(" '%s' not found on device, removing from iTunes" % orphan)
|
||||
self.iTunes.delete(orphan_paths[orphan])
|
||||
|
||||
self.log.info(" '%s' not found on iDevice, removing from iTunes" % book)
|
||||
btr = { 'title':library_books[book].name(),
|
||||
'author':library_books[book].artist(),
|
||||
'lib_book':library_books[book]}
|
||||
self._remove_from_iTunes(btr)
|
||||
elif iswindows:
|
||||
for orphan in self.library_orphans:
|
||||
path = self.path_template % (self.library_orphans[orphan].Name,
|
||||
self.library_orphans[orphan].Artist)
|
||||
orphan_paths[path] = self.library_orphans[orphan]
|
||||
|
||||
# Scan orphan_paths for paths not found in cached_books
|
||||
for orphan in orphan_paths.keys():
|
||||
if orphan not in cached_books:
|
||||
if book not in cached_books and \
|
||||
library_books[book].Description.startswith(self.description_prefix):
|
||||
if DEBUG:
|
||||
self.log.info(" '%s' not found on device, removing from iTunes" % orphan)
|
||||
orphan_paths[orphan].Delete()
|
||||
self.log.info(" '%s' not found on iDevice, removing from iTunes" % book)
|
||||
btr = { 'title':library_books[book].Name,
|
||||
'author':library_books[book].Artist,
|
||||
'lib_book':library_books[book]}
|
||||
self._remove_from_iTunes(btr)
|
||||
|
||||
def _remove_existing_copies(self,path,file,metadata):
|
||||
'''
|
||||
@ -2049,10 +1930,13 @@ class ITUNES(DevicePlugin):
|
||||
Assume we're wrapped in a pythoncom
|
||||
Windows stores the book under a common author directory, so we just delete the .epub
|
||||
'''
|
||||
|
||||
book = self._find_library_book(cached_book)
|
||||
if book:
|
||||
try:
|
||||
book = cached_book['lib_book']
|
||||
path = book.Location
|
||||
except:
|
||||
book = self._find_library_book(cached_book)
|
||||
path = book.Location
|
||||
|
||||
storage_path = os.path.split(book.Location)
|
||||
if book.Location.startswith(self.iTunes_media):
|
||||
if DEBUG:
|
||||
@ -2074,9 +1958,6 @@ class ITUNES(DevicePlugin):
|
||||
|
||||
book.Delete()
|
||||
|
||||
else:
|
||||
self.log.warning(" could not find '%s' in iTunes database" % cached_book['title'])
|
||||
|
||||
def _update_device(self, msg='', wait=True):
|
||||
'''
|
||||
Trigger a sync, wait for completion
|
||||
|
@ -103,8 +103,8 @@ class CoverManager(object):
|
||||
32)]
|
||||
img_data = create_cover_page(lines, I('library.png'))
|
||||
id, href = self.oeb.manifest.generate('cover_image',
|
||||
'cover_image.png')
|
||||
item = self.oeb.manifest.add(id, href, guess_type('t.png')[0],
|
||||
'cover_image.jpg')
|
||||
item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
|
||||
data=img_data)
|
||||
m.clear('cover')
|
||||
m.add('cover', item.id)
|
||||
|
@ -251,6 +251,9 @@ class AnnotationsAction(object): # {{{
|
||||
|
||||
class AddAction(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||
|
||||
def add_recursive(self, single):
|
||||
root = choose_dir(self, 'recursive book import root dir dialog',
|
||||
'Select root folder')
|
||||
|
@ -83,7 +83,6 @@ if pictureflow is not None:
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
||||
QSizePolicy.Expanding))
|
||||
self.setZoomFactor(150)
|
||||
|
||||
def sizeHint(self):
|
||||
return self.minimumSize()
|
||||
|
@ -85,7 +85,9 @@ typedef long PFreal;
|
||||
|
||||
typedef unsigned short QRgb565;
|
||||
|
||||
#define FONT_SIZE 18
|
||||
#define REFLECTION_FACTOR 1.5
|
||||
|
||||
#define MAX(x, y) ((x > y) ? x : y)
|
||||
|
||||
#define RGB565_RED_MASK 0xF800
|
||||
#define RGB565_GREEN_MASK 0x07E0
|
||||
@ -124,6 +126,7 @@ inline PFreal floatToFixed(float val)
|
||||
return (PFreal)(val*PFREAL_ONE);
|
||||
}
|
||||
|
||||
// sinTable {{{
|
||||
#define IANGLE_MAX 1024
|
||||
#define IANGLE_MASK 1023
|
||||
|
||||
@ -293,6 +296,7 @@ int main(int, char**)
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
// }}}
|
||||
|
||||
inline PFreal fsin(int iangle)
|
||||
{
|
||||
@ -315,6 +319,8 @@ struct SlideInfo
|
||||
PFreal cy;
|
||||
};
|
||||
|
||||
// PicturePlowPrivate {{{
|
||||
|
||||
class PictureFlowPrivate
|
||||
{
|
||||
public:
|
||||
@ -369,6 +375,7 @@ private:
|
||||
|
||||
int slideWidth;
|
||||
int slideHeight;
|
||||
int fontSize;
|
||||
int zoom;
|
||||
int queueLength;
|
||||
|
||||
@ -406,6 +413,7 @@ PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w, int queueLength_)
|
||||
|
||||
slideWidth = 200;
|
||||
slideHeight = 200;
|
||||
fontSize = 10;
|
||||
zoom = 100;
|
||||
|
||||
centerIndex = 0;
|
||||
@ -542,8 +550,11 @@ void PictureFlowPrivate::showSlide(int index)
|
||||
|
||||
void PictureFlowPrivate::resize(int w, int h)
|
||||
{
|
||||
slideHeight = int(float(h)/2.);
|
||||
if (w < 10) w = 10;
|
||||
if (h < 10) h = 10;
|
||||
slideHeight = int(float(h)/REFLECTION_FACTOR);
|
||||
slideWidth = int(float(slideHeight) * 2/3.);
|
||||
fontSize = MAX(int(h/20.), 12);
|
||||
recalc(w, h);
|
||||
resetSlides();
|
||||
triggerRender();
|
||||
@ -592,8 +603,8 @@ static QImage prepareSurface(QImage img, int w, int h)
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode);
|
||||
|
||||
// slightly larger, to accomodate for the reflection
|
||||
int hs = h * 2;
|
||||
int hofs = h / 3;
|
||||
int hs = int(h * REFLECTION_FACTOR);
|
||||
int hofs = 0;
|
||||
|
||||
// offscreen buffer: black is sweet
|
||||
QImage result(hs, w, QImage::Format_RGB16);
|
||||
@ -715,13 +726,13 @@ void PictureFlowPrivate::render()
|
||||
|
||||
QFont font = QFont();
|
||||
font.setBold(true);
|
||||
font.setPointSize(FONT_SIZE);
|
||||
font.setPixelSize(fontSize);
|
||||
painter.setFont(font);
|
||||
painter.setPen(Qt::white);
|
||||
//painter.setPen(QColor(255,255,255,127));
|
||||
|
||||
if (centerIndex < slideCount() && centerIndex > -1)
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-FONT_SIZE*3),
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-fontSize*3),
|
||||
Qt::AlignCenter, slideImages->caption(centerIndex));
|
||||
|
||||
painter.end();
|
||||
@ -766,7 +777,7 @@ void PictureFlowPrivate::render()
|
||||
|
||||
QFont font = QFont();
|
||||
font.setBold(true);
|
||||
font.setPointSize(FONT_SIZE);
|
||||
font.setPixelSize(fontSize);
|
||||
painter.setFont(font);
|
||||
|
||||
int leftTextIndex = (step>0) ? centerIndex : centerIndex-1;
|
||||
@ -774,12 +785,12 @@ void PictureFlowPrivate::render()
|
||||
|
||||
painter.setPen(QColor(255,255,255, (255-fade) ));
|
||||
if (leftTextIndex < sc && leftTextIndex > -1)
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3),
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*3),
|
||||
Qt::AlignCenter, slideImages->caption(leftTextIndex));
|
||||
|
||||
painter.setPen(QColor(255,255,255, fade));
|
||||
if (leftTextIndex+1 < sc && leftTextIndex > -2)
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3),
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - fontSize*3),
|
||||
Qt::AlignCenter, slideImages->caption(leftTextIndex+1));
|
||||
|
||||
|
||||
@ -893,7 +904,7 @@ int col1, int col2)
|
||||
int center = (sh*BILINEAR_STRETCH_VER/2);
|
||||
int dy = dist*BILINEAR_STRETCH_VER / h;
|
||||
#else
|
||||
int center = (sh/2);
|
||||
int center = sh/2;
|
||||
int dy = dist / h;
|
||||
#endif
|
||||
int p1 = center*PFREAL_ONE - dy/2;
|
||||
@ -1110,8 +1121,9 @@ void PictureFlowPrivate::clearSurfaceCache()
|
||||
surfaceCache.clear();
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// }}}
|
||||
|
||||
// PictureFlow {{{
|
||||
PictureFlow::PictureFlow(QWidget* parent, int queueLength): QWidget(parent)
|
||||
{
|
||||
d = new PictureFlowPrivate(this, queueLength);
|
||||
@ -1387,3 +1399,5 @@ void PictureFlow::emitcurrentChanged(int index) { emit currentChanged(index); }
|
||||
int FlowImages::count() { return 0; }
|
||||
QImage FlowImages::image(int index) { index=0; return QImage(); }
|
||||
QString FlowImages::caption(int index) {index=0; return QString(); }
|
||||
|
||||
// }}}
|
||||
|
@ -2,9 +2,10 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, collections
|
||||
|
||||
from PyQt4.QtGui import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \
|
||||
QSizePolicy, QScrollArea
|
||||
from PyQt4.QtCore import Qt, QSize, pyqtSignal
|
||||
from PyQt4.Qt import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \
|
||||
QSizePolicy, QScrollArea, Qt, QSize, pyqtSignal, \
|
||||
QPropertyAnimation, QEasingCurve
|
||||
|
||||
|
||||
from calibre import fit_image, preferred_encoding, isosx
|
||||
from calibre.gui2 import config
|
||||
@ -50,6 +51,10 @@ class BookInfoDisplay(QWidget):
|
||||
|
||||
def __init__(self, coverpath=I('book.svg')):
|
||||
QLabel.__init__(self)
|
||||
self.animation = QPropertyAnimation(self, 'size', self)
|
||||
self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo))
|
||||
self.animation.setDuration(1000)
|
||||
self.animation.setStartValue(QSize(0, 0))
|
||||
self.setMaximumWidth(81)
|
||||
self.setMaximumHeight(108)
|
||||
self.default_pixmap = QPixmap(coverpath)
|
||||
@ -58,6 +63,7 @@ class BookInfoDisplay(QWidget):
|
||||
self.setPixmap(self.default_pixmap)
|
||||
|
||||
def do_layout(self):
|
||||
self.animation.stop()
|
||||
pixmap = self.pixmap()
|
||||
pwidth, pheight = pixmap.width(), pixmap.height()
|
||||
width, height = fit_image(pwidth, pheight,
|
||||
@ -68,11 +74,12 @@ class BookInfoDisplay(QWidget):
|
||||
except ZeroDivisionError:
|
||||
aspect_ratio = 1
|
||||
self.setMaximumWidth(int(aspect_ratio*self.maximumHeight()))
|
||||
self.animation.setEndValue(self.maximumSize())
|
||||
|
||||
def setPixmap(self, pixmap):
|
||||
QLabel.setPixmap(self, pixmap)
|
||||
self.do_layout()
|
||||
|
||||
self.animation.start()
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(self.maximumWidth(), self.maximumHeight())
|
||||
|
@ -13,7 +13,7 @@ import collections, os, sys, textwrap, time
|
||||
from Queue import Queue, Empty
|
||||
from threading import Thread
|
||||
from PyQt4.Qt import Qt, SIGNAL, QObject, QUrl, QTimer, \
|
||||
QPixmap, QMenu, QIcon, \
|
||||
QPixmap, QMenu, QIcon, pyqtSignal, \
|
||||
QDialog, QDesktopServices, \
|
||||
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
||||
QMessageBox, QHelpEvent
|
||||
@ -24,7 +24,7 @@ from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.config import prefs, dynamic
|
||||
from calibre.utils.ipc.server import Server
|
||||
from calibre.gui2 import error_dialog, GetMetadata, \
|
||||
Dispatcher, gprefs, max_available_height, config, info_dialog
|
||||
gprefs, max_available_height, config, info_dialog
|
||||
from calibre.gui2.cover_flow import CoverFlowMixin
|
||||
from calibre.gui2.widgets import ProgressIndicator
|
||||
from calibre.gui2.wizard import move_library
|
||||
@ -77,13 +77,15 @@ class Listener(Thread): # {{{
|
||||
|
||||
class SystemTrayIcon(QSystemTrayIcon): # {{{
|
||||
|
||||
tooltip_requested = pyqtSignal(object)
|
||||
|
||||
def __init__(self, icon, parent):
|
||||
QSystemTrayIcon.__init__(self, icon, parent)
|
||||
|
||||
def event(self, ev):
|
||||
if ev.type() == ev.ToolTip:
|
||||
evh = QHelpEvent(ev)
|
||||
self.emit(SIGNAL('tooltip_requested(PyQt_PyObject)'),
|
||||
self.tooltip_requested.emit(
|
||||
(self, evh.globalPos()))
|
||||
return True
|
||||
return QSystemTrayIcon.event(self, ev)
|
||||
@ -149,8 +151,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
self.content_server = None
|
||||
self.system_tray_icon = SystemTrayIcon(QIcon(I('library.png')), self)
|
||||
self.system_tray_icon.setToolTip('calibre')
|
||||
self.connect(self.system_tray_icon,
|
||||
SIGNAL('tooltip_requested(PyQt_PyObject)'),
|
||||
self.system_tray_icon.tooltip_requested.connect(
|
||||
self.job_manager.show_tooltip)
|
||||
if not config['systray_icon']:
|
||||
self.system_tray_icon.hide()
|
||||
@ -292,8 +293,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
|
||||
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
|
||||
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
||||
AddAction.__init__(self)
|
||||
|
||||
self.read_settings()
|
||||
self.finalize_layout()
|
||||
|
@ -10,14 +10,14 @@ import collections, glob, os, re, itertools, functools
|
||||
from itertools import repeat
|
||||
from datetime import timedelta
|
||||
|
||||
from PyQt4.QtCore import QThread, QReadWriteLock
|
||||
from PyQt4.QtGui import QImage
|
||||
from PyQt4.Qt import QThread, QReadWriteLock, QImage, Qt
|
||||
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
from calibre.utils.pyparsing import ParseException
|
||||
from calibre.ebooks.metadata import title_sort
|
||||
from calibre import fit_image
|
||||
|
||||
class CoverCache(QThread):
|
||||
|
||||
@ -96,6 +96,11 @@ class CoverCache(QThread):
|
||||
img.loadFromData(data)
|
||||
if img.isNull():
|
||||
continue
|
||||
scaled, nwidth, nheight = fit_image(img.width(),
|
||||
img.height(), 600, 800)
|
||||
if scaled:
|
||||
img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation)
|
||||
except:
|
||||
continue
|
||||
self.cache_lock.lockForWrite()
|
||||
|
@ -111,7 +111,7 @@ Pre/post processing of downloaded HTML
|
||||
|
||||
.. automember:: BasicNewsRecipe.remove_javascript
|
||||
|
||||
.. automethod:: BasicNewsRecipe.prepreprocess_html
|
||||
.. automethod:: BasicNewsRecipe.skip_ad_pages
|
||||
|
||||
.. automethod:: BasicNewsRecipe.preprocess_html
|
||||
|
||||
|
@ -175,7 +175,7 @@ def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
|
||||
p.DestroyMagickWand(canvas)
|
||||
|
||||
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||
bgcolor='white', output_format='png'):
|
||||
bgcolor='white', output_format='jpg'):
|
||||
ans = None
|
||||
with p.ImageMagick():
|
||||
canvas = create_canvas(width, height, bgcolor)
|
||||
|
@ -413,18 +413,19 @@ class BasicNewsRecipe(Recipe):
|
||||
return url
|
||||
return article.get('link', None)
|
||||
|
||||
def prepreprocess_html(self, soup):
|
||||
def skip_ad_pages(self, soup):
|
||||
'''
|
||||
This method is called with the source of each downloaded :term:`HTML` file, before
|
||||
any of the cleanup attributes like remove_tags, keep_only_tags are
|
||||
applied. Note that preprocess_regexps will have already been applied.
|
||||
It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`.
|
||||
It should return `soup` after processing it.
|
||||
It is meant to allow the recipe to skip ad pages. If the soup represents
|
||||
an ad page, return the HTML of the real page. Otherwise return
|
||||
None.
|
||||
|
||||
`soup`: A `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/documentation.html>`_
|
||||
instance containing the downloaded :term:`HTML`.
|
||||
'''
|
||||
return soup
|
||||
return None
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
@ -628,7 +629,7 @@ class BasicNewsRecipe(Recipe):
|
||||
|
||||
self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
|
||||
for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps',
|
||||
'prepreprocess_html', 'preprocess_html', 'remove_tags_after',
|
||||
'skip_ad_pages', 'preprocess_html', 'remove_tags_after',
|
||||
'remove_tags_before', 'is_link_wanted'):
|
||||
setattr(self.web2disk_options, extra, getattr(self, extra))
|
||||
self.web2disk_options.postprocess_html = self._postprocess_html
|
||||
@ -801,11 +802,6 @@ class BasicNewsRecipe(Recipe):
|
||||
.calibre_navbar {
|
||||
font-family:monospace;
|
||||
}
|
||||
hr {
|
||||
border-color:gray;
|
||||
border-style:solid;
|
||||
border-width:thin;
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
|
@ -108,7 +108,7 @@ class TouchscreenNavBarTemplate(Template):
|
||||
navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100',
|
||||
style='text-align:'+align))
|
||||
if bottom:
|
||||
navbar.append(HR())
|
||||
navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white"))
|
||||
text = 'This article was downloaded by '
|
||||
p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left')
|
||||
p[0].tail = ' from '
|
||||
@ -136,7 +136,7 @@ class TouchscreenNavBarTemplate(Template):
|
||||
|
||||
navbar.iterchildren(reversed=True).next().tail = ' | '
|
||||
if not bottom:
|
||||
navbar.append(HR())
|
||||
navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white"))
|
||||
|
||||
self.root = HTML(head, BODY(navbar))
|
||||
|
||||
@ -193,6 +193,8 @@ class TouchscreenIndexTemplate(Template):
|
||||
div = DIV(
|
||||
masthead_p,
|
||||
PT(date, style='text-align:center'),
|
||||
#DIV(style="border-color:gray;border-top-style:solid;border-width:thin"),
|
||||
DIV(style="border-top:1px solid gray;border-bottom:1em solid white"),
|
||||
toc)
|
||||
self.root = HTML(head, BODY(div))
|
||||
|
||||
@ -256,9 +258,8 @@ class TouchscreenFeedTemplate(Template):
|
||||
head.append(STYLE(extra_css, type='text/css'))
|
||||
body = BODY(style='page-break-before:always')
|
||||
div = DIV(
|
||||
H2(feed.title,
|
||||
CLASS('calibre_feed_title', 'calibre_rescale_160')),
|
||||
CLASS('calibre_rescale_100')
|
||||
H2(feed.title, CLASS('calibre_feed_title', 'calibre_rescale_160')),
|
||||
DIV(style="border-top:1px solid gray;border-bottom:1em solid white")
|
||||
)
|
||||
body.append(div)
|
||||
if getattr(feed, 'image', None):
|
||||
@ -278,6 +279,20 @@ class TouchscreenFeedTemplate(Template):
|
||||
if not getattr(article, 'downloaded', False):
|
||||
continue
|
||||
tr = TR()
|
||||
|
||||
if True:
|
||||
div_td = DIV(
|
||||
A(article.title, CLASS('summary_headline','calibre_rescale_120',
|
||||
href=article.url)),
|
||||
style="display:inline-block")
|
||||
if article.author:
|
||||
div_td.append(DIV(article.author,
|
||||
CLASS('summary_byline', 'calibre_rescale_100')))
|
||||
if article.summary:
|
||||
div_td.append(DIV(cutoff(article.text_summary),
|
||||
CLASS('summary_text', 'calibre_rescale_100')))
|
||||
tr.append(TD(div_td))
|
||||
else:
|
||||
td = TD(
|
||||
A(article.title, CLASS('summary_headline','calibre_rescale_120',
|
||||
href=article.url))
|
||||
@ -288,7 +303,9 @@ class TouchscreenFeedTemplate(Template):
|
||||
if article.summary:
|
||||
td.append(DIV(cutoff(article.text_summary),
|
||||
CLASS('summary_text', 'calibre_rescale_100')))
|
||||
|
||||
tr.append(td)
|
||||
|
||||
toc.append(tr)
|
||||
div.append(toc)
|
||||
|
||||
|
@ -136,7 +136,7 @@ class RecursiveFetcher(object):
|
||||
self.remove_tags_before = getattr(options, 'remove_tags_before', None)
|
||||
self.keep_only_tags = getattr(options, 'keep_only_tags', [])
|
||||
self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup)
|
||||
self.prepreprocess_html_ext = getattr(options, 'prepreprocess_html', lambda soup: soup)
|
||||
self.prepreprocess_html_ext = getattr(options, 'skip_ad_pages', lambda soup: None)
|
||||
self.postprocess_html_ext= getattr(options, 'postprocess_html', None)
|
||||
self._is_link_wanted = getattr(options, 'is_link_wanted',
|
||||
default_is_link_wanted)
|
||||
@ -154,7 +154,9 @@ class RecursiveFetcher(object):
|
||||
nmassage.append((re.compile(r'<!--.*?-->', re.DOTALL), lambda m: ''))
|
||||
soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
|
||||
|
||||
soup = self.prepreprocess_html_ext(soup)
|
||||
replace = self.prepreprocess_html_ext(soup)
|
||||
if replace is not None:
|
||||
soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
|
||||
|
||||
if self.keep_only_tags:
|
||||
body = Tag(soup, 'body')
|
||||
|
Loading…
x
Reference in New Issue
Block a user