Merge from trunk

This commit is contained in:
Charles Haley 2010-06-13 05:38:49 +01:00
commit 99c8afdd60
15 changed files with 177 additions and 250 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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):
'''

View File

@ -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)

View File

@ -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')

View File

@ -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()

View File

@ -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(); }
// }}}

View File

@ -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())

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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;
}
'''

View File

@ -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(
@ -278,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)

View File

@ -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')