KG updates

This commit is contained in:
GRiker 2010-06-13 03:51:54 -06:00
commit 4ec5710249
23 changed files with 1618 additions and 1536 deletions

View File

@ -14,8 +14,8 @@ class LiberoNews(BasicNewsRecipe):
__author__ = 'Marini Gabriele'
description = 'Italian daily newspaper'
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif'
title = u'Libero'
cover_url = 'http://www.libero-news.it/images/logo.png'
title = u'Libero '
publisher = 'EDITORIALE LIBERO s.r.l 2006'
category = 'News, politics, culture, economy, general interest'

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

@ -445,7 +445,7 @@ from calibre.devices.nook.driver import NOOK
from calibre.devices.prs505.driver import PRS505
from calibre.devices.android.driver import ANDROID, S60
from calibre.devices.nokia.driver import N770, N810, E71X
from calibre.devices.eslick.driver import ESLICK
from calibre.devices.eslick.driver import ESLICK, EBK52
from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY
from calibre.devices.binatone.driver import README
@ -519,6 +519,7 @@ plugins += [
N810,
COOL_ER,
ESLICK,
EBK52,
NUUT2,
IRIVER_STORY,
GER2,

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

@ -36,4 +36,29 @@ class ESLICK(USBMS):
SUPPORTS_SUB_DIRS = True
@classmethod
def can_handle(cls, dev, debug=False):
return (dev[3], dev[4]) != ('philips', 'Philips d')
class EBK52(ESLICK):
name = 'EBK-52 Device Interface'
gui_name = 'Sigmatek EBK'
description = _('Communicate with the Sigmatek eBook reader.')
FORMATS = ['epub', 'fb2', 'pdf', 'txt']
VENDOR_NAME = ''
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_READER'
MAIN_MEMORY_VOLUME_LABEL = 'Sigmatek Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Sigmatek Storage Card'
@classmethod
def can_handle(cls, dev, debug=False):
return (dev[3], dev[4]) == ('philips', 'Philips d')

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)

1209
src/calibre/gui2/actions.py Normal file

File diff suppressed because it is too large Load Diff

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

@ -3,24 +3,26 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
# Imports {{{
import os, traceback, Queue, time, socket, cStringIO, re
import os, traceback, Queue, time, socket, cStringIO, re, sys
from threading import Thread, RLock
from itertools import repeat
from functools import partial
from binascii import unhexlify
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \
Qt, pyqtSignal
Qt, pyqtSignal, QColor, QPainter
from PyQt4.QtSvg import QSvgRenderer
from calibre.customize.ui import available_input_formats, available_output_formats, \
device_plugins
from calibre.devices.interface import DevicePlugin
from calibre.devices.errors import UserFeedback
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.utils.ipc.job import BaseJob
from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \
question_dialog
question_dialog, info_dialog, choose_dir
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
from calibre import preferred_encoding, prints
from calibre.utils.filenames import ascii_filename
@ -597,10 +599,204 @@ class Emailer(Thread): # {{{
# }}}
class DeviceMixin(object):
class DeviceMixin(object): # {{{
def __init__(self):
self.db_book_uuid_cache = set()
self.device_error_dialog = error_dialog(self, _('Error'),
_('Error communicating with device'), ' ')
self.device_error_dialog.setModal(Qt.NonModal)
self.device_connected = None
self.emailer = Emailer()
self.emailer.start()
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
self.job_manager, Dispatcher(self.status_bar.show_message))
self.device_manager.start()
def set_default_thumbnail(self, height):
r = QSvgRenderer(I('book.svg'))
pixmap = QPixmap(height, height)
pixmap.fill(QColor(255,255,255))
p = QPainter(pixmap)
r.render(p)
p.end()
self.default_thumbnail = (pixmap.width(), pixmap.height(),
pixmap_to_data(pixmap))
def connect_to_folder(self):
dir = choose_dir(self, 'Select Device Folder',
_('Select folder to open as device'))
if dir is not None:
self.device_manager.connect_to_folder(dir)
def disconnect_from_folder(self):
self.device_manager.disconnect_folder()
def _sync_action_triggered(self, *args):
m = getattr(self, '_sync_menu', None)
if m is not None:
m.trigger_default()
def create_device_menu(self):
self._sync_menu = DeviceMenu(self)
self.action_sync.setMenu(self._sync_menu)
self.connect(self._sync_menu,
SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self.dispatch_sync_event)
self._sync_menu.fetch_annotations.connect(self.fetch_annotations)
self._sync_menu.connect_to_folder.connect(self.connect_to_folder)
self._sync_menu.disconnect_from_folder.connect(self.disconnect_from_folder)
if self.device_connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
if self.device_connected == 'folder':
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
else:
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
def device_job_exception(self, job):
'''
Handle exceptions in threaded device jobs.
'''
if isinstance(getattr(job, 'exception', None), UserFeedback):
ex = job.exception
func = {UserFeedback.ERROR:error_dialog,
UserFeedback.WARNING:warning_dialog,
UserFeedback.INFO:info_dialog}[ex.level]
return func(self, _('Failed'), ex.msg, det_msg=ex.details if
ex.details else '', show=True)
try:
if 'Could not read 32 bytes on the control bus.' in \
unicode(job.details):
error_dialog(self, _('Error talking to device'),
_('There was a temporary error talking to the '
'device. Please unplug and reconnect the device '
'and or reboot.')).show()
return
except:
pass
try:
prints(job.details, file=sys.stderr)
except:
pass
if not self.device_error_dialog.isVisible():
self.device_error_dialog.setDetailedText(job.details)
self.device_error_dialog.show()
# Device connected {{{
def device_detected(self, connected, is_folder_device):
'''
Called when a device is connected to the computer.
'''
if connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
if is_folder_device:
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
self.device_manager.get_device_information(\
Dispatcher(self.info_read))
self.set_default_thumbnail(\
self.device_manager.device.THUMBNAIL_HEIGHT)
self.status_bar.show_message(_('Device: ')+\
self.device_manager.device.__class__.get_gui_name()+\
_(' detected.'), 3000)
self.device_connected = 'device' if not is_folder_device else 'folder'
self._sync_menu.enable_device_actions(True,
self.device_manager.device.card_prefix(),
self.device_manager.device)
self.location_view.model().device_connected(self.device_manager.device)
self.eject_action.setEnabled(True)
self.refresh_ondevice_info (device_connected = True, reset_only = True)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
self.device_connected = None
self._sync_menu.enable_device_actions(False)
self.location_view.model().update_devices()
self.vanity.setText(self.vanity_template%\
dict(version=self.latest_version, device=' '))
self.device_info = ' '
if self.current_view() != self.library_view:
self.book_details.reset_info()
self.location_view.setCurrentIndex(self.location_view.model().index(0))
self.eject_action.setEnabled(False)
self.refresh_ondevice_info (device_connected = False)
def info_read(self, job):
'''
Called once device information has been read.
'''
if job.failed:
return self.device_job_exception(job)
info, cp, fs = job.result
self.location_view.model().update_devices(cp, fs)
self.device_info = _('Connected ')+info[0]
self.vanity.setText(self.vanity_template%\
dict(version=self.latest_version, device=self.device_info))
self.device_manager.books(Dispatcher(self.metadata_downloaded))
def metadata_downloaded(self, job):
'''
Called once metadata has been read for all books on the device.
'''
if job.failed:
self.device_job_exception(job)
return
self.set_books_in_library(job.result, reset=True)
mainlist, cardalist, cardblist = job.result
self.memory_view.set_database(mainlist)
self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.card_a_view.set_database(cardalist)
self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.card_b_view.set_database(cardblist)
self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
self.sync_news()
self.sync_catalogs()
self.refresh_ondevice_info(device_connected = True)
def refresh_ondevice_info(self, device_connected, reset_only = False):
'''
Force the library view to refresh, taking into consideration
books information
'''
self.book_on_device(None, reset=True)
if reset_only:
return
self.library_view.set_device_connected(device_connected)
# }}}
def remove_paths(self, paths):
return self.device_manager.delete_books(
Dispatcher(self.books_deleted), paths)
def books_deleted(self, job):
'''
Called once deletion is done on the device
'''
for view in (self.memory_view, self.card_a_view, self.card_b_view):
view.model().deletion_done(job, job.failed)
if job.failed:
self.device_job_exception(job)
return
if self.delete_memory.has_key(job):
paths, model = self.delete_memory.pop(job)
self.device_manager.remove_books_from_metadata(paths,
self.booklists())
model.paths_deleted(paths)
self.upload_booklists()
# Clear the ondevice info so it will be recomputed
self.book_on_device(None, None, reset=True)
# We want to reset all the ondevice flags in the library. Use a big
# hammer, so we don't need to worry about whether some succeeded or not
self.library_view.model().refresh()
def dispatch_sync_event(self, dest, delete, specific):
rows = self.library_view.selectionModel().selectedRows()
@ -1220,3 +1416,6 @@ class DeviceMixin(object):
# Correct the metadata cache on device.
if self.device_manager.is_device_connected:
self.device_manager.sync_booklists(None, booklists)
# }}}

View File

@ -121,6 +121,7 @@ class BookInfo(QDialog, Ui_BookInfo):
f = f.strip()
info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f)
for key in info.keys():
if key == 'id': continue
txt = info[key]
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)

View File

@ -47,7 +47,7 @@ class ToolbarMixin(object): # {{{
def __init__(self):
md = QMenu()
md.addAction(_('Edit metadata individually'),
partial(self.edit_metadata, False))
partial(self.edit_metadata, False, bulk=False))
md.addSeparator()
md.addAction(_('Edit metadata in bulk'),
partial(self.edit_metadata, False, bulk=True))
@ -132,6 +132,7 @@ class ToolbarMixin(object): # {{{
self.action_open_containing_folder.setShortcut(Qt.Key_O)
self.addAction(self.action_open_containing_folder)
self.action_open_containing_folder.triggered.connect(self.view_folder)
self.action_sync.setShortcut(Qt.Key_D)
self.action_sync.setEnabled(True)
self.create_device_menu()

View File

@ -21,7 +21,7 @@ from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
from calibre import strftime, isbytestring
from calibre import strftime, isbytestring, prepare_string_for_xml
from calibre.constants import filesystem_encoding
from calibre.gui2.library import DEFAULT_SORT
@ -300,6 +300,7 @@ class BooksModel(QAbstractTableModel): # {{{
formats = _('None')
data[_('Formats')] = formats
data[_('Path')] = self.db.abspath(idx)
data['id'] = self.id(idx)
comments = self.db.comments(idx)
if not comments:
comments = _('None')
@ -308,7 +309,9 @@ class BooksModel(QAbstractTableModel): # {{{
if series:
sidx = self.db.series_index(idx)
sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers)
data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series)
data[_('Series')] = \
_('Book <font face="serif">%s</font> of %s.')%\
(sidx, prepare_string_for_xml(series))
return data

View File

@ -292,7 +292,8 @@ class BooksView(QTableView): # {{{
old_state['column_positions'][name] = i
if name != 'ondevice':
old_state['column_sizes'][name] = \
max(self.sizeHintForColumn(i), h.sectionSizeHint(i))
min(350, max(self.sizeHintForColumn(i),
h.sectionSizeHint(i)))
if name == 'timestamp':
old_state['column_sizes'][name] += 12
return old_state

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

@ -1,10 +1,12 @@
__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
import os
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
@ -12,6 +14,7 @@ from calibre.gui2.widgets import IMAGE_EXTENSIONS
from calibre.gui2.notify import get_notifier
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.library.comments import comments_to_html
from calibre.gui2.book_details import render_rows
class BookInfoDisplay(QWidget):
@ -50,6 +53,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,21 +65,23 @@ 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,
pwidth, self.statusbar_height-12)[1:]
pwidth, self.statusbar_height-20)[1:]
self.setMaximumHeight(height)
try:
aspect_ratio = pwidth/float(pheight)
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())
@ -84,24 +93,27 @@ class BookInfoDisplay(QWidget):
class BookDataDisplay(QLabel):
mr = pyqtSignal(int)
mr = pyqtSignal(object)
link_clicked = pyqtSignal(object)
def __init__(self):
QLabel.__init__(self)
self.setText('')
self.setWordWrap(True)
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
self.linkActivated.connect(self.link_activated)
self._link_clicked = False
def mouseReleaseEvent(self, ev):
self.mr.emit(1)
QLabel.mouseReleaseEvent(self, ev)
if not self._link_clicked:
self.mr.emit(ev)
self._link_clicked = False
WEIGHTS = collections.defaultdict(lambda : 100)
WEIGHTS[_('Path')] = 0
WEIGHTS[_('Formats')] = 1
WEIGHTS[_('Collections')] = 2
WEIGHTS[_('Series')] = 3
WEIGHTS[_('Tags')] = 4
WEIGHTS[_('Comments')] = 5
def link_activated(self, link):
self._link_clicked = True
link = unicode(link)
self.link_clicked.emit(link)
show_book_info = pyqtSignal()
@ -122,6 +134,7 @@ class BookInfoDisplay(QWidget):
self._layout.setAlignment(self.cover_display, Qt.AlignTop|Qt.AlignLeft)
def mouseReleaseEvent(self, ev):
ev.accept()
self.show_book_info.emit()
def show_data(self, data):
@ -133,23 +146,11 @@ class BookInfoDisplay(QWidget):
rows, comments = [], ''
self.book_data.setText('')
self.data = data.copy()
keys = data.keys()
keys.sort(cmp=lambda x, y: cmp(self.WEIGHTS[x], self.WEIGHTS[y]))
for key in keys:
txt = data[key]
if not txt or not txt.strip() or txt == 'None':
continue
if isinstance(key, str):
key = key.decode(preferred_encoding, 'replace')
if isinstance(txt, str):
txt = txt.decode(preferred_encoding, 'replace')
if key == _('Comments'):
comments = comments_to_html(txt)
else:
rows.append((key, txt))
rows = render_rows(self.data)
rows = '\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for
k, t in rows])
if comments:
if _('Comments') in self.data:
comments = comments_to_html(self.data[_('Comments')])
comments = '<b>Comments:</b>'+comments
left_pane = u'<table>%s</table>'%rows
right_pane = u'<div>%s</div>'%comments
@ -186,6 +187,8 @@ class BookDetailsInterface(object):
# These signals must be defined in the class implementing this interface
files_dropped = None
show_book_info = None
open_containing_folder = None
view_specific_format = None
def reset_info(self):
raise NotImplementedError()
@ -197,7 +200,8 @@ class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface):
files_dropped = pyqtSignal(object, object)
show_book_info = pyqtSignal()
open_containing_folder = pyqtSignal(int)
view_specific_format = pyqtSignal(int, object)
resized = pyqtSignal(object)
@ -212,11 +216,21 @@ class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface):
type=Qt.QueuedConnection)
self.book_info.files_dropped.connect(self.files_dropped.emit,
type=Qt.QueuedConnection)
self.book_info.book_data.link_clicked.connect(self._link_clicked)
self.addWidget(self.scroll_area, 100)
self.setMinimumHeight(120)
self.resized.connect(self.book_info.cover_display.relayout)
self.book_info.cover_display.relayout(self.size())
def _link_clicked(self, link):
typ, _, val = link.partition(':')
if typ == 'path':
self.open_containing_folder.emit(int(val))
if typ == 'format':
id_, fmt = val.split(':')
self.view_specific_format.emit(int(id_), fmt)
def resizeEvent(self, ev):
self.resized.emit(self.size())

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback
from PyQt4.QtCore import QThread, pyqtSignal
from PyQt4.Qt import QThread, pyqtSignal, QDesktopServices, QUrl, Qt
import mechanize
from calibre.constants import __version__, iswindows, isosx
from calibre.constants import __appname__, __version__, iswindows, isosx
from calibre import browser
from calibre.utils.config import prefs
from calibre.gui2 import config, dynamic, question_dialog
URL = 'http://status.calibre-ebook.com/latest'
@ -36,3 +37,35 @@ class CheckForUpdates(QThread):
traceback.print_exc()
self.sleep(self.INTERVAL)
class UpdateMixin(object):
def __init__(self, opts):
if not opts.no_update_check:
self.update_checker = CheckForUpdates(self)
self.update_checker.update_found.connect(self.update_found,
type=Qt.QueuedConnection)
self.update_checker.start()
def update_found(self, version):
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
url = 'http://calibre-ebook.com/download_%s'%os
self.latest_version = '<br>' + _('<span style="color:red; font-weight:bold">'
'Latest version: <a href="%s">%s</a></span>')%(url, version)
self.vanity.setText(self.vanity_template%\
(dict(version=self.latest_version,
device=self.device_info)))
self.vanity.update()
if config.get('new_version_notification') and \
dynamic.get('update to version %s'%version, True):
if question_dialog(self, _('Update available'),
_('%s has been updated to version %s. '
'See the <a href="http://calibre-ebook.com/whats-new'
'">new features</a>. Visit the download pa'
'ge?')%(__appname__, version)):
url = 'http://calibre-ebook.com/download_'+\
('windows' if iswindows else 'osx' if isosx else 'linux')
QDesktopServices.openUrl(QUrl(url))
dynamic.set('update to version %s'%version, False)

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

@ -31,7 +31,7 @@ class SafeLocalTimeZone(tzlocal):
def compute_locale_info_for_parse_date():
try:
dt = datetime.strptime('1/5/2000', "%x")
except ValueError:
except:
try:
dt = datetime.strptime('1/5/01', '%x')
except:

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

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