From 48458ac7870ef9b36b2052f30564320d24634c55 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 12 Jun 2010 10:19:47 -0600 Subject: [PATCH 1/9] GwR revisions --- src/calibre/devices/apple/driver.py | 257 ++++++++-------------------- src/calibre/web/feeds/news.py | 5 - src/calibre/web/feeds/templates.py | 13 +- 3 files changed, 76 insertions(+), 199 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 3e16eccbbc..ae440a359e 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -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 = {} - - 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: + for book in library_books: + if isosx: + 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]) - - 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: + 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: + 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): ''' @@ -2040,7 +1921,7 @@ class ITUNES(DevicePlugin): except: # We get here if there was an error with .location().path - self.log.info(" removing orphan '%s' from iTunes" % cached_book['title']) + self.log.info(" removing orphan '%s' from iTunes" % cached_book['title']) self.iTunes.delete(cached_book['lib_book']) @@ -2049,33 +1930,33 @@ 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: - self.log.info(" removing '%s' at %s" % - (cached_book['title'], path)) - try: - os.remove(path) - except: - self.log.warning(" could not find '%s' in iTunes storage" % path) - try: - os.rmdir(storage_path[0]) - self.log.info(" removed folder '%s'" % storage_path[0]) - except: - self.log.info(" folder '%s' not found or not empty" % storage_path[0]) - # Delete from iTunes database - else: - self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) - - book.Delete() + storage_path = os.path.split(book.Location) + if book.Location.startswith(self.iTunes_media): + if DEBUG: + self.log.info(" removing '%s' at %s" % + (cached_book['title'], path)) + try: + os.remove(path) + except: + self.log.warning(" could not find '%s' in iTunes storage" % path) + try: + os.rmdir(storage_path[0]) + self.log.info(" removed folder '%s'" % storage_path[0]) + except: + self.log.info(" folder '%s' not found or not empty" % storage_path[0]) + # Delete from iTunes database else: - self.log.warning(" could not find '%s' in iTunes database" % cached_book['title']) + self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) + + book.Delete() def _update_device(self, msg='', wait=True): ''' diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 8977f64d60..f54d5bde9d 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -801,11 +801,6 @@ class BasicNewsRecipe(Recipe): .calibre_navbar { font-family:monospace; } - hr { - border-color:gray; - border-style:solid; - border-width:thin; - } ''' diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index b64795b816..d23596a274 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -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,10 +258,9 @@ 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): div.append(DIV(IMG( From 421f2ebc902b10590c033764e9401b4afa41ea6e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 10:52:40 -0600 Subject: [PATCH 2/9] ... --- src/calibre/gui2/actions.py | 3 +++ src/calibre/gui2/ui.py | 13 +++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/actions.py b/src/calibre/gui2/actions.py index cabc17cd00..4b2e367080 100644 --- a/src/calibre/gui2/actions.py +++ b/src/calibre/gui2/actions.py @@ -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') diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index dcc1470bed..682ede1978 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -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() From ea2d2139c9cf8d798c662bf395eeab576121f1a8 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 12 Jun 2010 11:27:10 -0600 Subject: [PATCH 3/9] GwR revisions --- src/calibre/web/feeds/templates.py | 38 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index d23596a274..7ebf7294ae 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -279,17 +279,33 @@ class TouchscreenFeedTemplate(Template): if not getattr(article, 'downloaded', False): continue tr = TR() - td = TD( - A(article.title, CLASS('summary_headline','calibre_rescale_120', - href=article.url)) - ) - if article.author: - td.append(DIV(article.author, - CLASS('summary_byline', 'calibre_rescale_100'))) - if article.summary: - td.append(DIV(cutoff(article.text_summary), - CLASS('summary_text', 'calibre_rescale_100'))) - tr.append(td) + + 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)) + ) + if article.author: + td.append(DIV(article.author, + CLASS('summary_byline', 'calibre_rescale_100'))) + 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) From 2511b5d13571a3ef8fe95705d6f6d2d95c9823f5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 15:35:37 -0600 Subject: [PATCH 4/9] Cover Browser: Scale text size with height of cover browser. Only show a reflection of half the cover. Also Fix #5808 (Cover browser in 0.7.2 now has reduced quality images.) --- src/calibre/gui2/cover_flow.py | 1 - src/calibre/gui2/pictureflow/pictureflow.cpp | 36 ++++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index f06d912a5d..6a9709cd8b 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -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() diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp index 60985a1a12..58b6cd32e0 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.cpp +++ b/src/calibre/gui2/pictureflow/pictureflow.cpp @@ -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(); } + +// }}} From 0d66fe64cf96cd9be095e00a46b93384aa037094 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 16:42:21 -0600 Subject: [PATCH 5/9] EPUB Output: Default cover is generated is now generated as a JPEG, reducing size by an order of magnitude. Fixes #5810 (0.7.2 creating larger epubs from RTF) --- src/calibre/ebooks/oeb/transforms/cover.py | 4 ++-- src/calibre/utils/magick_draw.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/oeb/transforms/cover.py b/src/calibre/ebooks/oeb/transforms/cover.py index 4d41ab14b4..83c5ec93e4 100644 --- a/src/calibre/ebooks/oeb/transforms/cover.py +++ b/src/calibre/ebooks/oeb/transforms/cover.py @@ -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) diff --git a/src/calibre/utils/magick_draw.py b/src/calibre/utils/magick_draw.py index 160f4b70a5..2a259301db 100644 --- a/src/calibre/utils/magick_draw.py +++ b/src/calibre/utils/magick_draw.py @@ -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) From 7cf81e7bff74d91e3e114f4b4ca4bb559c0f6542 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 17:08:26 -0600 Subject: [PATCH 6/9] News download: Fix prepreprocess_html method --- src/calibre/manual/news_recipe.rst | 2 +- src/calibre/web/feeds/news.py | 11 ++++++----- src/calibre/web/fetch/simple.py | 6 ++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/calibre/manual/news_recipe.rst b/src/calibre/manual/news_recipe.rst index 14cc41d436..7e5045ea47 100644 --- a/src/calibre/manual/news_recipe.rst +++ b/src/calibre/manual/news_recipe.rst @@ -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 diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index f54d5bde9d..9e05babecc 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -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 `_ 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 diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py index bde91ec0d2..b6186f785d 100644 --- a/src/calibre/web/fetch/simple.py +++ b/src/calibre/web/fetch/simple.py @@ -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') From 86dee77d368b9e8c0571992a438ce9e435cb11b7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 20:28:04 -0600 Subject: [PATCH 7/9] Make the book details pane animated --- resources/recipes/the_oz.recipe | 2 +- src/calibre/gui2/status.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/resources/recipes/the_oz.recipe b/resources/recipes/the_oz.recipe index a55f31e63e..ccdce0acb6 100644 --- a/resources/recipes/the_oz.recipe +++ b/resources/recipes/the_oz.recipe @@ -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' diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py index 50a256ef2d..06c3e9c85f 100644 --- a/src/calibre/gui2/status.py +++ b/src/calibre/gui2/status.py @@ -2,9 +2,10 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' 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()) From 7dbdf55b13f1d34a617e9442b0ecf0782e252794 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 21:05:16 -0600 Subject: [PATCH 8/9] Support for the Samsung Galaxy --- src/calibre/devices/android/driver.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 54bd745879..0bbdf0f22c 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -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' From ac4c623c39a8551d0044ed45a22138f3969b0d48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 12 Jun 2010 21:21:09 -0600 Subject: [PATCH 9/9] Cover cache: Resize covers larger than 600x800 in the cover cache to reduce memory consumption in the GUI --- src/calibre/library/caches.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index bb6001794a..57f9d0baaf 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -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()