mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates
This commit is contained in:
commit
4ec5710249
@ -11,11 +11,11 @@ http://www.libero-news.it/
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class LiberoNews(BasicNewsRecipe):
|
class LiberoNews(BasicNewsRecipe):
|
||||||
__author__ = 'Marini Gabriele'
|
__author__ = 'Marini Gabriele'
|
||||||
description = 'Italian daily newspaper'
|
description = 'Italian daily newspaper'
|
||||||
|
|
||||||
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif'
|
cover_url = 'http://www.libero-news.it/images/logo.png'
|
||||||
title = u'Libero'
|
title = u'Libero '
|
||||||
publisher = 'EDITORIALE LIBERO s.r.l 2006'
|
publisher = 'EDITORIALE LIBERO s.r.l 2006'
|
||||||
category = 'News, politics, culture, economy, general interest'
|
category = 'News, politics, culture, economy, general interest'
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class DailyTelegraph(BasicNewsRecipe):
|
|||||||
language = 'en_AU'
|
language = 'en_AU'
|
||||||
|
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
max_articles_per_feed = 10
|
max_articles_per_feed = 20
|
||||||
remove_javascript = True
|
remove_javascript = True
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
encoding = 'utf8'
|
encoding = 'utf8'
|
||||||
|
@ -445,7 +445,7 @@ from calibre.devices.nook.driver import NOOK
|
|||||||
from calibre.devices.prs505.driver import PRS505
|
from calibre.devices.prs505.driver import PRS505
|
||||||
from calibre.devices.android.driver import ANDROID, S60
|
from calibre.devices.android.driver import ANDROID, S60
|
||||||
from calibre.devices.nokia.driver import N770, N810, E71X
|
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.nuut2.driver import NUUT2
|
||||||
from calibre.devices.iriver.driver import IRIVER_STORY
|
from calibre.devices.iriver.driver import IRIVER_STORY
|
||||||
from calibre.devices.binatone.driver import README
|
from calibre.devices.binatone.driver import README
|
||||||
@ -519,6 +519,7 @@ plugins += [
|
|||||||
N810,
|
N810,
|
||||||
COOL_ER,
|
COOL_ER,
|
||||||
ESLICK,
|
ESLICK,
|
||||||
|
EBK52,
|
||||||
NUUT2,
|
NUUT2,
|
||||||
IRIVER_STORY,
|
IRIVER_STORY,
|
||||||
GER2,
|
GER2,
|
||||||
|
@ -30,7 +30,7 @@ class ANDROID(USBMS):
|
|||||||
0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]},
|
0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]},
|
||||||
|
|
||||||
# Samsung
|
# Samsung
|
||||||
0x04e8 : { 0x681d : [0x0222], 0x681c : [0x0222, 0x0224]},
|
0x04e8 : { 0x681d : [0x0222, 0x0400], 0x681c : [0x0222, 0x0224]},
|
||||||
|
|
||||||
# Acer
|
# Acer
|
||||||
0x502 : { 0x3203 : [0x0100]},
|
0x502 : { 0x3203 : [0x0100]},
|
||||||
@ -41,10 +41,12 @@ class ANDROID(USBMS):
|
|||||||
'be used')
|
'be used')
|
||||||
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(EBOOK_DIR_MAIN)
|
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',
|
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD']
|
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD',
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE']
|
'PROD_GT-I9000']
|
||||||
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'PROD_GT-I9000_CARD']
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
OSX_MAIN_MEM = 'HTC Android Phone Media'
|
||||||
|
|
||||||
|
@ -36,4 +36,29 @@ class ESLICK(USBMS):
|
|||||||
|
|
||||||
SUPPORTS_SUB_DIRS = True
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,8 +103,8 @@ class CoverManager(object):
|
|||||||
32)]
|
32)]
|
||||||
img_data = create_cover_page(lines, I('library.png'))
|
img_data = create_cover_page(lines, I('library.png'))
|
||||||
id, href = self.oeb.manifest.generate('cover_image',
|
id, href = self.oeb.manifest.generate('cover_image',
|
||||||
'cover_image.png')
|
'cover_image.jpg')
|
||||||
item = self.oeb.manifest.add(id, href, guess_type('t.png')[0],
|
item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
|
||||||
data=img_data)
|
data=img_data)
|
||||||
m.clear('cover')
|
m.clear('cover')
|
||||||
m.add('cover', item.id)
|
m.add('cover', item.id)
|
||||||
|
1209
src/calibre/gui2/actions.py
Normal file
1209
src/calibre/gui2/actions.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -83,7 +83,6 @@ if pictureflow is not None:
|
|||||||
self.setFocusPolicy(Qt.WheelFocus)
|
self.setFocusPolicy(Qt.WheelFocus)
|
||||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
||||||
QSizePolicy.Expanding))
|
QSizePolicy.Expanding))
|
||||||
self.setZoomFactor(150)
|
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return self.minimumSize()
|
return self.minimumSize()
|
||||||
|
@ -3,24 +3,26 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
# Imports {{{
|
# Imports {{{
|
||||||
import os, traceback, Queue, time, socket, cStringIO, re
|
import os, traceback, Queue, time, socket, cStringIO, re, sys
|
||||||
from threading import Thread, RLock
|
from threading import Thread, RLock
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
|
|
||||||
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \
|
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, \
|
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||||
device_plugins
|
device_plugins
|
||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
|
from calibre.devices.errors import UserFeedback
|
||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||||
from calibre.utils.ipc.job import BaseJob
|
from calibre.utils.ipc.job import BaseJob
|
||||||
from calibre.devices.scanner import DeviceScanner
|
from calibre.devices.scanner import DeviceScanner
|
||||||
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
|
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
|
||||||
pixmap_to_data, warning_dialog, \
|
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.ebooks.metadata import authors_to_string, authors_to_sort_string
|
||||||
from calibre import preferred_encoding, prints
|
from calibre import preferred_encoding, prints
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
@ -597,10 +599,204 @@ class Emailer(Thread): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class DeviceMixin(object):
|
class DeviceMixin(object): # {{{
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.db_book_uuid_cache = set()
|
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):
|
def dispatch_sync_event(self, dest, delete, specific):
|
||||||
rows = self.library_view.selectionModel().selectedRows()
|
rows = self.library_view.selectionModel().selectedRows()
|
||||||
@ -1220,3 +1416,6 @@ class DeviceMixin(object):
|
|||||||
# Correct the metadata cache on device.
|
# Correct the metadata cache on device.
|
||||||
if self.device_manager.is_device_connected:
|
if self.device_manager.is_device_connected:
|
||||||
self.device_manager.sync_booklists(None, booklists)
|
self.device_manager.sync_booklists(None, booklists)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
@ -121,6 +121,7 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
f = f.strip()
|
f = f.strip()
|
||||||
info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f)
|
info[_('Formats')] += '<a href="%s">%s</a>, '%(f,f)
|
||||||
for key in info.keys():
|
for key in info.keys():
|
||||||
|
if key == 'id': continue
|
||||||
txt = info[key]
|
txt = info[key]
|
||||||
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
|
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
|
||||||
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
|
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
|
||||||
|
@ -47,7 +47,7 @@ class ToolbarMixin(object): # {{{
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
md = QMenu()
|
md = QMenu()
|
||||||
md.addAction(_('Edit metadata individually'),
|
md.addAction(_('Edit metadata individually'),
|
||||||
partial(self.edit_metadata, False))
|
partial(self.edit_metadata, False, bulk=False))
|
||||||
md.addSeparator()
|
md.addSeparator()
|
||||||
md.addAction(_('Edit metadata in bulk'),
|
md.addAction(_('Edit metadata in bulk'),
|
||||||
partial(self.edit_metadata, False, bulk=True))
|
partial(self.edit_metadata, False, bulk=True))
|
||||||
@ -132,6 +132,7 @@ class ToolbarMixin(object): # {{{
|
|||||||
|
|
||||||
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
||||||
self.addAction(self.action_open_containing_folder)
|
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.setShortcut(Qt.Key_D)
|
||||||
self.action_sync.setEnabled(True)
|
self.action_sync.setEnabled(True)
|
||||||
self.create_device_menu()
|
self.create_device_menu()
|
||||||
|
@ -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.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
|
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.constants import filesystem_encoding
|
||||||
from calibre.gui2.library import DEFAULT_SORT
|
from calibre.gui2.library import DEFAULT_SORT
|
||||||
|
|
||||||
@ -300,6 +300,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
formats = _('None')
|
formats = _('None')
|
||||||
data[_('Formats')] = formats
|
data[_('Formats')] = formats
|
||||||
data[_('Path')] = self.db.abspath(idx)
|
data[_('Path')] = self.db.abspath(idx)
|
||||||
|
data['id'] = self.id(idx)
|
||||||
comments = self.db.comments(idx)
|
comments = self.db.comments(idx)
|
||||||
if not comments:
|
if not comments:
|
||||||
comments = _('None')
|
comments = _('None')
|
||||||
@ -308,7 +309,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
if series:
|
if series:
|
||||||
sidx = self.db.series_index(idx)
|
sidx = self.db.series_index(idx)
|
||||||
sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers)
|
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
|
return data
|
||||||
|
|
||||||
|
@ -292,7 +292,8 @@ class BooksView(QTableView): # {{{
|
|||||||
old_state['column_positions'][name] = i
|
old_state['column_positions'][name] = i
|
||||||
if name != 'ondevice':
|
if name != 'ondevice':
|
||||||
old_state['column_sizes'][name] = \
|
old_state['column_sizes'][name] = \
|
||||||
max(self.sizeHintForColumn(i), h.sectionSizeHint(i))
|
min(350, max(self.sizeHintForColumn(i),
|
||||||
|
h.sectionSizeHint(i)))
|
||||||
if name == 'timestamp':
|
if name == 'timestamp':
|
||||||
old_state['column_sizes'][name] += 12
|
old_state['column_sizes'][name] += 12
|
||||||
return old_state
|
return old_state
|
||||||
|
@ -85,7 +85,9 @@ typedef long PFreal;
|
|||||||
|
|
||||||
typedef unsigned short QRgb565;
|
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_RED_MASK 0xF800
|
||||||
#define RGB565_GREEN_MASK 0x07E0
|
#define RGB565_GREEN_MASK 0x07E0
|
||||||
@ -124,6 +126,7 @@ inline PFreal floatToFixed(float val)
|
|||||||
return (PFreal)(val*PFREAL_ONE);
|
return (PFreal)(val*PFREAL_ONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sinTable {{{
|
||||||
#define IANGLE_MAX 1024
|
#define IANGLE_MAX 1024
|
||||||
#define IANGLE_MASK 1023
|
#define IANGLE_MASK 1023
|
||||||
|
|
||||||
@ -293,6 +296,7 @@ int main(int, char**)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
// }}}
|
||||||
|
|
||||||
inline PFreal fsin(int iangle)
|
inline PFreal fsin(int iangle)
|
||||||
{
|
{
|
||||||
@ -315,6 +319,8 @@ struct SlideInfo
|
|||||||
PFreal cy;
|
PFreal cy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// PicturePlowPrivate {{{
|
||||||
|
|
||||||
class PictureFlowPrivate
|
class PictureFlowPrivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -369,6 +375,7 @@ private:
|
|||||||
|
|
||||||
int slideWidth;
|
int slideWidth;
|
||||||
int slideHeight;
|
int slideHeight;
|
||||||
|
int fontSize;
|
||||||
int zoom;
|
int zoom;
|
||||||
int queueLength;
|
int queueLength;
|
||||||
|
|
||||||
@ -406,6 +413,7 @@ PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w, int queueLength_)
|
|||||||
|
|
||||||
slideWidth = 200;
|
slideWidth = 200;
|
||||||
slideHeight = 200;
|
slideHeight = 200;
|
||||||
|
fontSize = 10;
|
||||||
zoom = 100;
|
zoom = 100;
|
||||||
|
|
||||||
centerIndex = 0;
|
centerIndex = 0;
|
||||||
@ -542,8 +550,11 @@ void PictureFlowPrivate::showSlide(int index)
|
|||||||
|
|
||||||
void PictureFlowPrivate::resize(int w, int h)
|
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.);
|
slideWidth = int(float(slideHeight) * 2/3.);
|
||||||
|
fontSize = MAX(int(h/20.), 12);
|
||||||
recalc(w, h);
|
recalc(w, h);
|
||||||
resetSlides();
|
resetSlides();
|
||||||
triggerRender();
|
triggerRender();
|
||||||
@ -592,8 +603,8 @@ static QImage prepareSurface(QImage img, int w, int h)
|
|||||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode);
|
img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode);
|
||||||
|
|
||||||
// slightly larger, to accomodate for the reflection
|
// slightly larger, to accomodate for the reflection
|
||||||
int hs = h * 2;
|
int hs = int(h * REFLECTION_FACTOR);
|
||||||
int hofs = h / 3;
|
int hofs = 0;
|
||||||
|
|
||||||
// offscreen buffer: black is sweet
|
// offscreen buffer: black is sweet
|
||||||
QImage result(hs, w, QImage::Format_RGB16);
|
QImage result(hs, w, QImage::Format_RGB16);
|
||||||
@ -715,13 +726,13 @@ void PictureFlowPrivate::render()
|
|||||||
|
|
||||||
QFont font = QFont();
|
QFont font = QFont();
|
||||||
font.setBold(true);
|
font.setBold(true);
|
||||||
font.setPointSize(FONT_SIZE);
|
font.setPixelSize(fontSize);
|
||||||
painter.setFont(font);
|
painter.setFont(font);
|
||||||
painter.setPen(Qt::white);
|
painter.setPen(Qt::white);
|
||||||
//painter.setPen(QColor(255,255,255,127));
|
//painter.setPen(QColor(255,255,255,127));
|
||||||
|
|
||||||
if (centerIndex < slideCount() && centerIndex > -1)
|
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));
|
Qt::AlignCenter, slideImages->caption(centerIndex));
|
||||||
|
|
||||||
painter.end();
|
painter.end();
|
||||||
@ -766,7 +777,7 @@ void PictureFlowPrivate::render()
|
|||||||
|
|
||||||
QFont font = QFont();
|
QFont font = QFont();
|
||||||
font.setBold(true);
|
font.setBold(true);
|
||||||
font.setPointSize(FONT_SIZE);
|
font.setPixelSize(fontSize);
|
||||||
painter.setFont(font);
|
painter.setFont(font);
|
||||||
|
|
||||||
int leftTextIndex = (step>0) ? centerIndex : centerIndex-1;
|
int leftTextIndex = (step>0) ? centerIndex : centerIndex-1;
|
||||||
@ -774,12 +785,12 @@ void PictureFlowPrivate::render()
|
|||||||
|
|
||||||
painter.setPen(QColor(255,255,255, (255-fade) ));
|
painter.setPen(QColor(255,255,255, (255-fade) ));
|
||||||
if (leftTextIndex < sc && leftTextIndex > -1)
|
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));
|
Qt::AlignCenter, slideImages->caption(leftTextIndex));
|
||||||
|
|
||||||
painter.setPen(QColor(255,255,255, fade));
|
painter.setPen(QColor(255,255,255, fade));
|
||||||
if (leftTextIndex+1 < sc && leftTextIndex > -2)
|
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));
|
Qt::AlignCenter, slideImages->caption(leftTextIndex+1));
|
||||||
|
|
||||||
|
|
||||||
@ -893,7 +904,7 @@ int col1, int col2)
|
|||||||
int center = (sh*BILINEAR_STRETCH_VER/2);
|
int center = (sh*BILINEAR_STRETCH_VER/2);
|
||||||
int dy = dist*BILINEAR_STRETCH_VER / h;
|
int dy = dist*BILINEAR_STRETCH_VER / h;
|
||||||
#else
|
#else
|
||||||
int center = (sh/2);
|
int center = sh/2;
|
||||||
int dy = dist / h;
|
int dy = dist / h;
|
||||||
#endif
|
#endif
|
||||||
int p1 = center*PFREAL_ONE - dy/2;
|
int p1 = center*PFREAL_ONE - dy/2;
|
||||||
@ -1110,8 +1121,9 @@ void PictureFlowPrivate::clearSurfaceCache()
|
|||||||
surfaceCache.clear();
|
surfaceCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// }}}
|
||||||
|
|
||||||
|
// PictureFlow {{{
|
||||||
PictureFlow::PictureFlow(QWidget* parent, int queueLength): QWidget(parent)
|
PictureFlow::PictureFlow(QWidget* parent, int queueLength): QWidget(parent)
|
||||||
{
|
{
|
||||||
d = new PictureFlowPrivate(this, queueLength);
|
d = new PictureFlowPrivate(this, queueLength);
|
||||||
@ -1387,3 +1399,5 @@ void PictureFlow::emitcurrentChanged(int index) { emit currentChanged(index); }
|
|||||||
int FlowImages::count() { return 0; }
|
int FlowImages::count() { return 0; }
|
||||||
QImage FlowImages::image(int index) { index=0; return QImage(); }
|
QImage FlowImages::image(int index) { index=0; return QImage(); }
|
||||||
QString FlowImages::caption(int index) {index=0; return QString(); }
|
QString FlowImages::caption(int index) {index=0; return QString(); }
|
||||||
|
|
||||||
|
// }}}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
import os, collections
|
|
||||||
|
|
||||||
from PyQt4.QtGui import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \
|
import os
|
||||||
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 import fit_image, preferred_encoding, isosx
|
||||||
from calibre.gui2 import config
|
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.gui2.notify import get_notifier
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
|
from calibre.gui2.book_details import render_rows
|
||||||
|
|
||||||
class BookInfoDisplay(QWidget):
|
class BookInfoDisplay(QWidget):
|
||||||
|
|
||||||
@ -50,6 +53,10 @@ class BookInfoDisplay(QWidget):
|
|||||||
|
|
||||||
def __init__(self, coverpath=I('book.svg')):
|
def __init__(self, coverpath=I('book.svg')):
|
||||||
QLabel.__init__(self)
|
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.setMaximumWidth(81)
|
||||||
self.setMaximumHeight(108)
|
self.setMaximumHeight(108)
|
||||||
self.default_pixmap = QPixmap(coverpath)
|
self.default_pixmap = QPixmap(coverpath)
|
||||||
@ -58,21 +65,23 @@ class BookInfoDisplay(QWidget):
|
|||||||
self.setPixmap(self.default_pixmap)
|
self.setPixmap(self.default_pixmap)
|
||||||
|
|
||||||
def do_layout(self):
|
def do_layout(self):
|
||||||
|
self.animation.stop()
|
||||||
pixmap = self.pixmap()
|
pixmap = self.pixmap()
|
||||||
pwidth, pheight = pixmap.width(), pixmap.height()
|
pwidth, pheight = pixmap.width(), pixmap.height()
|
||||||
width, height = fit_image(pwidth, pheight,
|
width, height = fit_image(pwidth, pheight,
|
||||||
pwidth, self.statusbar_height-12)[1:]
|
pwidth, self.statusbar_height-20)[1:]
|
||||||
self.setMaximumHeight(height)
|
self.setMaximumHeight(height)
|
||||||
try:
|
try:
|
||||||
aspect_ratio = pwidth/float(pheight)
|
aspect_ratio = pwidth/float(pheight)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
aspect_ratio = 1
|
aspect_ratio = 1
|
||||||
self.setMaximumWidth(int(aspect_ratio*self.maximumHeight()))
|
self.setMaximumWidth(int(aspect_ratio*self.maximumHeight()))
|
||||||
|
self.animation.setEndValue(self.maximumSize())
|
||||||
|
|
||||||
def setPixmap(self, pixmap):
|
def setPixmap(self, pixmap):
|
||||||
QLabel.setPixmap(self, pixmap)
|
QLabel.setPixmap(self, pixmap)
|
||||||
self.do_layout()
|
self.do_layout()
|
||||||
|
self.animation.start()
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return QSize(self.maximumWidth(), self.maximumHeight())
|
return QSize(self.maximumWidth(), self.maximumHeight())
|
||||||
@ -84,24 +93,27 @@ class BookInfoDisplay(QWidget):
|
|||||||
|
|
||||||
class BookDataDisplay(QLabel):
|
class BookDataDisplay(QLabel):
|
||||||
|
|
||||||
mr = pyqtSignal(int)
|
mr = pyqtSignal(object)
|
||||||
|
link_clicked = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QLabel.__init__(self)
|
QLabel.__init__(self)
|
||||||
self.setText('')
|
self.setText('')
|
||||||
self.setWordWrap(True)
|
self.setWordWrap(True)
|
||||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
|
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
|
||||||
|
self.linkActivated.connect(self.link_activated)
|
||||||
|
self._link_clicked = False
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
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)
|
def link_activated(self, link):
|
||||||
WEIGHTS[_('Path')] = 0
|
self._link_clicked = True
|
||||||
WEIGHTS[_('Formats')] = 1
|
link = unicode(link)
|
||||||
WEIGHTS[_('Collections')] = 2
|
self.link_clicked.emit(link)
|
||||||
WEIGHTS[_('Series')] = 3
|
|
||||||
WEIGHTS[_('Tags')] = 4
|
|
||||||
WEIGHTS[_('Comments')] = 5
|
|
||||||
|
|
||||||
show_book_info = pyqtSignal()
|
show_book_info = pyqtSignal()
|
||||||
|
|
||||||
@ -122,6 +134,7 @@ class BookInfoDisplay(QWidget):
|
|||||||
self._layout.setAlignment(self.cover_display, Qt.AlignTop|Qt.AlignLeft)
|
self._layout.setAlignment(self.cover_display, Qt.AlignTop|Qt.AlignLeft)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
|
ev.accept()
|
||||||
self.show_book_info.emit()
|
self.show_book_info.emit()
|
||||||
|
|
||||||
def show_data(self, data):
|
def show_data(self, data):
|
||||||
@ -133,23 +146,11 @@ class BookInfoDisplay(QWidget):
|
|||||||
rows, comments = [], ''
|
rows, comments = [], ''
|
||||||
self.book_data.setText('')
|
self.book_data.setText('')
|
||||||
self.data = data.copy()
|
self.data = data.copy()
|
||||||
keys = data.keys()
|
rows = render_rows(self.data)
|
||||||
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 = '\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for
|
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])
|
k, t in rows])
|
||||||
if comments:
|
if _('Comments') in self.data:
|
||||||
|
comments = comments_to_html(self.data[_('Comments')])
|
||||||
comments = '<b>Comments:</b>'+comments
|
comments = '<b>Comments:</b>'+comments
|
||||||
left_pane = u'<table>%s</table>'%rows
|
left_pane = u'<table>%s</table>'%rows
|
||||||
right_pane = u'<div>%s</div>'%comments
|
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
|
# These signals must be defined in the class implementing this interface
|
||||||
files_dropped = None
|
files_dropped = None
|
||||||
show_book_info = None
|
show_book_info = None
|
||||||
|
open_containing_folder = None
|
||||||
|
view_specific_format = None
|
||||||
|
|
||||||
def reset_info(self):
|
def reset_info(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@ -197,7 +200,8 @@ class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface):
|
|||||||
|
|
||||||
files_dropped = pyqtSignal(object, object)
|
files_dropped = pyqtSignal(object, object)
|
||||||
show_book_info = pyqtSignal()
|
show_book_info = pyqtSignal()
|
||||||
|
open_containing_folder = pyqtSignal(int)
|
||||||
|
view_specific_format = pyqtSignal(int, object)
|
||||||
|
|
||||||
resized = pyqtSignal(object)
|
resized = pyqtSignal(object)
|
||||||
|
|
||||||
@ -212,11 +216,21 @@ class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface):
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
self.book_info.files_dropped.connect(self.files_dropped.emit,
|
self.book_info.files_dropped.connect(self.files_dropped.emit,
|
||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
|
self.book_info.book_data.link_clicked.connect(self._link_clicked)
|
||||||
self.addWidget(self.scroll_area, 100)
|
self.addWidget(self.scroll_area, 100)
|
||||||
self.setMinimumHeight(120)
|
self.setMinimumHeight(120)
|
||||||
self.resized.connect(self.book_info.cover_display.relayout)
|
self.resized.connect(self.book_info.cover_display.relayout)
|
||||||
self.book_info.cover_display.relayout(self.size())
|
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):
|
def resizeEvent(self, ev):
|
||||||
self.resized.emit(self.size())
|
self.resized.emit(self.size())
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3,12 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from PyQt4.QtCore import QThread, pyqtSignal
|
from PyQt4.Qt import QThread, pyqtSignal, QDesktopServices, QUrl, Qt
|
||||||
import mechanize
|
import mechanize
|
||||||
|
|
||||||
from calibre.constants import __version__, iswindows, isosx
|
from calibre.constants import __appname__, __version__, iswindows, isosx
|
||||||
from calibre import browser
|
from calibre import browser
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.gui2 import config, dynamic, question_dialog
|
||||||
|
|
||||||
URL = 'http://status.calibre-ebook.com/latest'
|
URL = 'http://status.calibre-ebook.com/latest'
|
||||||
|
|
||||||
@ -36,3 +37,35 @@ class CheckForUpdates(QThread):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.sleep(self.INTERVAL)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,14 +10,14 @@ import collections, glob, os, re, itertools, functools
|
|||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from PyQt4.QtCore import QThread, QReadWriteLock
|
from PyQt4.Qt import QThread, QReadWriteLock, QImage, Qt
|
||||||
from PyQt4.QtGui import QImage
|
|
||||||
|
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
|
from calibre import fit_image
|
||||||
|
|
||||||
class CoverCache(QThread):
|
class CoverCache(QThread):
|
||||||
|
|
||||||
@ -96,6 +96,11 @@ class CoverCache(QThread):
|
|||||||
img.loadFromData(data)
|
img.loadFromData(data)
|
||||||
if img.isNull():
|
if img.isNull():
|
||||||
continue
|
continue
|
||||||
|
scaled, nwidth, nheight = fit_image(img.width(),
|
||||||
|
img.height(), 600, 800)
|
||||||
|
if scaled:
|
||||||
|
img = img.scaled(nwidth, nheight, Qt.KeepAspectRatio,
|
||||||
|
Qt.SmoothTransformation)
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
self.cache_lock.lockForWrite()
|
self.cache_lock.lockForWrite()
|
||||||
|
@ -111,7 +111,7 @@ Pre/post processing of downloaded HTML
|
|||||||
|
|
||||||
.. automember:: BasicNewsRecipe.remove_javascript
|
.. automember:: BasicNewsRecipe.remove_javascript
|
||||||
|
|
||||||
.. automethod:: BasicNewsRecipe.prepreprocess_html
|
.. automethod:: BasicNewsRecipe.skip_ad_pages
|
||||||
|
|
||||||
.. automethod:: BasicNewsRecipe.preprocess_html
|
.. automethod:: BasicNewsRecipe.preprocess_html
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class SafeLocalTimeZone(tzlocal):
|
|||||||
def compute_locale_info_for_parse_date():
|
def compute_locale_info_for_parse_date():
|
||||||
try:
|
try:
|
||||||
dt = datetime.strptime('1/5/2000', "%x")
|
dt = datetime.strptime('1/5/2000', "%x")
|
||||||
except ValueError:
|
except:
|
||||||
try:
|
try:
|
||||||
dt = datetime.strptime('1/5/01', '%x')
|
dt = datetime.strptime('1/5/01', '%x')
|
||||||
except:
|
except:
|
||||||
|
@ -175,7 +175,7 @@ def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
|
|||||||
p.DestroyMagickWand(canvas)
|
p.DestroyMagickWand(canvas)
|
||||||
|
|
||||||
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||||
bgcolor='white', output_format='png'):
|
bgcolor='white', output_format='jpg'):
|
||||||
ans = None
|
ans = None
|
||||||
with p.ImageMagick():
|
with p.ImageMagick():
|
||||||
canvas = create_canvas(width, height, bgcolor)
|
canvas = create_canvas(width, height, bgcolor)
|
||||||
|
@ -413,18 +413,19 @@ class BasicNewsRecipe(Recipe):
|
|||||||
return url
|
return url
|
||||||
return article.get('link', None)
|
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
|
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
|
any of the cleanup attributes like remove_tags, keep_only_tags are
|
||||||
applied. Note that preprocess_regexps will have already been applied.
|
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 is meant to allow the recipe to skip ad pages. If the soup represents
|
||||||
It should return `soup` after processing it.
|
an ad page, return the HTML of the real page. Otherwise return
|
||||||
|
None.
|
||||||
|
|
||||||
`soup`: A `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/documentation.html>`_
|
`soup`: A `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/documentation.html>`_
|
||||||
instance containing the downloaded :term:`HTML`.
|
instance containing the downloaded :term:`HTML`.
|
||||||
'''
|
'''
|
||||||
return soup
|
return None
|
||||||
|
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
@ -628,7 +629,7 @@ class BasicNewsRecipe(Recipe):
|
|||||||
|
|
||||||
self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
|
self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
|
||||||
for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps',
|
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'):
|
'remove_tags_before', 'is_link_wanted'):
|
||||||
setattr(self.web2disk_options, extra, getattr(self, extra))
|
setattr(self.web2disk_options, extra, getattr(self, extra))
|
||||||
self.web2disk_options.postprocess_html = self._postprocess_html
|
self.web2disk_options.postprocess_html = self._postprocess_html
|
||||||
|
@ -136,7 +136,7 @@ class RecursiveFetcher(object):
|
|||||||
self.remove_tags_before = getattr(options, 'remove_tags_before', None)
|
self.remove_tags_before = getattr(options, 'remove_tags_before', None)
|
||||||
self.keep_only_tags = getattr(options, 'keep_only_tags', [])
|
self.keep_only_tags = getattr(options, 'keep_only_tags', [])
|
||||||
self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup)
|
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.postprocess_html_ext= getattr(options, 'postprocess_html', None)
|
||||||
self._is_link_wanted = getattr(options, 'is_link_wanted',
|
self._is_link_wanted = getattr(options, 'is_link_wanted',
|
||||||
default_is_link_wanted)
|
default_is_link_wanted)
|
||||||
@ -154,7 +154,9 @@ class RecursiveFetcher(object):
|
|||||||
nmassage.append((re.compile(r'<!--.*?-->', re.DOTALL), lambda m: ''))
|
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 = 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:
|
if self.keep_only_tags:
|
||||||
body = Tag(soup, 'body')
|
body = Tag(soup, 'body')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user