mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
ff59581ac6
BIN
resources/images/format-fill-color.png
Normal file
BIN
resources/images/format-fill-color.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
resources/images/format-text-color.png
Normal file
BIN
resources/images/format-text-color.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
BIN
resources/images/format-text-heading.png
Normal file
BIN
resources/images/format-text-heading.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 965 B |
BIN
resources/images/insert-link.png
Normal file
BIN
resources/images/insert-link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
67
resources/recipes/cnd.recipe
Normal file
67
resources/recipes/cnd.recipe
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Derek Liang <Derek.liang.ca @@@at@@@ gmail.com>'
|
||||||
|
'''
|
||||||
|
cnd.org
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class TheCND(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = 'CND'
|
||||||
|
__author__ = 'Derek Liang'
|
||||||
|
description = ''
|
||||||
|
INDEX = 'http://cnd.org'
|
||||||
|
language = 'zh'
|
||||||
|
conversion_options = {'linearize_tables':True}
|
||||||
|
|
||||||
|
remove_tags_before = dict(name='div', id='articleHead')
|
||||||
|
remove_tags_after = dict(id='copyright')
|
||||||
|
remove_tags = [dict(name='table', attrs={'align':'right'}), dict(name='img', attrs={'src':'http://my.cnd.org/images/logo.gif'}), dict(name='hr', attrs={}), dict(name='small', attrs={})]
|
||||||
|
no_stylesheets = True
|
||||||
|
|
||||||
|
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
if url.find('news/article.php') >= 0:
|
||||||
|
return re.sub("^[^=]*", "http://my.cnd.org/modules/news/print.php?storyid", url)
|
||||||
|
else:
|
||||||
|
return re.sub("^[^=]*", "http://my.cnd.org/modules/wfsection/print.php?articleid", url)
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
soup = self.index_to_soup(self.INDEX)
|
||||||
|
|
||||||
|
feeds = []
|
||||||
|
articles = {}
|
||||||
|
|
||||||
|
for a in soup.findAll('a', attrs={'target':'_cnd'}):
|
||||||
|
url = a['href']
|
||||||
|
if url.find('article.php') < 0 :
|
||||||
|
continue
|
||||||
|
if url.startswith('/'):
|
||||||
|
url = 'http://cnd.org'+url
|
||||||
|
title = self.tag_to_string(a)
|
||||||
|
self.log('\tFound article: ', title, 'at', url)
|
||||||
|
date = a.nextSibling
|
||||||
|
if (date is not None) and len(date)>2:
|
||||||
|
if not articles.has_key(date):
|
||||||
|
articles[date] = []
|
||||||
|
articles[date].append({'title':title, 'url':url, 'description': '', 'date':''})
|
||||||
|
self.log('\t\tAppend to : ', date)
|
||||||
|
|
||||||
|
self.log('log articles', articles)
|
||||||
|
mostCurrent = sorted(articles).pop()
|
||||||
|
self.title = 'CND ' + mostCurrent
|
||||||
|
|
||||||
|
feeds.append((self.title, articles[mostCurrent]))
|
||||||
|
|
||||||
|
return feeds
|
||||||
|
|
||||||
|
def populate_article_metadata(self, article, soup, first):
|
||||||
|
header = soup.find('h3')
|
||||||
|
self.log('header: ' + self.tag_to_string(header))
|
||||||
|
pass
|
||||||
|
|
42
resources/recipes/ecotrend.recipe
Normal file
42
resources/recipes/ecotrend.recipe
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
globaleconomicanalysis.blogspot.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class GlobalEconomicAnalysis(BasicNewsRecipe):
|
||||||
|
title = "Mish's Global Economic Trend Analysis"
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'Thoughts on the global economy, housing, gold, silver, interest rates, oil, energy, China, commodities, the dollar, Euro, Renminbi, Yen, inflation, deflation, stagflation, precious metals, emerging markets, and policy decisions that affect the global markets.'
|
||||||
|
publisher = 'Mike Shedlock'
|
||||||
|
category = 'news, politics, economy, banking'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 200
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'utf8'
|
||||||
|
use_embedded_content = True
|
||||||
|
language = 'en'
|
||||||
|
remove_empty_feeds = True
|
||||||
|
publication_type = 'blog'
|
||||||
|
masthead_url = 'http://www.pagina12.com.ar/commons/imgs/logo-home.gif'
|
||||||
|
extra_css = """
|
||||||
|
body{font-family: Arial,Helvetica,sans-serif }
|
||||||
|
img{margin-bottom: 0.4em; display:block}
|
||||||
|
"""
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comment' : description
|
||||||
|
, 'tags' : category
|
||||||
|
, 'publisher' : publisher
|
||||||
|
, 'language' : language
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name=['meta','link','iframe','object','embed'])
|
||||||
|
,dict(attrs={'class':'blogger-post-footer'})
|
||||||
|
]
|
||||||
|
remove_attributes=['border']
|
||||||
|
|
||||||
|
feeds = [(u'Articles', u'http://feeds2.feedburner.com/MishsGlobalEconomicTrendAnalysis')]
|
@ -40,13 +40,12 @@ class GazetvanAntwerpen(BasicNewsRecipe):
|
|||||||
remove_tags_after = dict(name='span', attrs={'class':'author'})
|
remove_tags_after = dict(name='span', attrs={'class':'author'})
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'Overzicht & Blikvanger', u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/overview/overzicht' )
|
(u'Binnenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/binnenland' )
|
||||||
|
,(u'Buitenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/buitenland' )
|
||||||
,(u'Stad & Regio' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/stadenregio' )
|
,(u'Stad & Regio' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/stadenregio' )
|
||||||
,(u'Economie' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/economie' )
|
,(u'Economie' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/economie' )
|
||||||
,(u'Binnenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/binnenland' )
|
|
||||||
,(u'Buitenland' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/buitenland' )
|
|
||||||
,(u'Media & Cultur' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/mediaencultuur')
|
,(u'Media & Cultur' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/mediaencultuur')
|
||||||
,(u'Wetenschap' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/mediaencultuur')
|
,(u'Wetenschap' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/wetenschap' )
|
||||||
,(u'Sport' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/sport' )
|
,(u'Sport' , u'http://www.gva.be/syndicationservices/artfeedservice.svc/rss/mostrecent/sport' )
|
||||||
]
|
]
|
||||||
|
|
||||||
|
62
resources/recipes/wenxuecity-znjy.recipe
Normal file
62
resources/recipes/wenxuecity-znjy.recipe
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Derek Liang <Derek.liang.ca @@@at@@@ gmail.com>'
|
||||||
|
'''
|
||||||
|
wenxuecity.com
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class TheCND(BasicNewsRecipe):
|
||||||
|
|
||||||
|
title = 'wenxuecity - znjy'
|
||||||
|
__author__ = 'Derek Liang'
|
||||||
|
description = ''
|
||||||
|
INDEX = 'http://bbs.wenxuecity.com/znjy/?elite=1'
|
||||||
|
language = 'zh'
|
||||||
|
conversion_options = {'linearize_tables':True}
|
||||||
|
|
||||||
|
remove_tags_before = dict(name='div', id='message')
|
||||||
|
remove_tags_after = dict(name='div', id='message')
|
||||||
|
remove_tags = [dict(name='div', id='postmeta'), dict(name='div', id='footer')]
|
||||||
|
no_stylesheets = True
|
||||||
|
|
||||||
|
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url + '?print'
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
soup = self.index_to_soup(self.INDEX)
|
||||||
|
|
||||||
|
feeds = []
|
||||||
|
articles = {}
|
||||||
|
|
||||||
|
for a in soup.findAll('a', attrs={'class':'post'}):
|
||||||
|
url = a['href']
|
||||||
|
if url.startswith('/'):
|
||||||
|
url = 'http://bbs.wenxuecity.com'+url
|
||||||
|
title = self.tag_to_string(a)
|
||||||
|
self.log('\tFound article: ', title, ' at:', url)
|
||||||
|
dateReg = re.search( '(\d\d?)/(\d\d?)/(\d\d)', self.tag_to_string(a.parent) )
|
||||||
|
date = '%(y)s/%(m)02d/%(d)02d' % {'y' : dateReg.group(3), 'm' : int(dateReg.group(1)), 'd' : int(dateReg.group(2)) }
|
||||||
|
if not articles.has_key(date):
|
||||||
|
articles[date] = []
|
||||||
|
articles[date].append({'title':title, 'url':url, 'description': '', 'date':''})
|
||||||
|
self.log('\t\tAppend to : ', date)
|
||||||
|
|
||||||
|
self.log('log articles', articles)
|
||||||
|
mostCurrent = sorted(articles).pop()
|
||||||
|
self.title = '文学城 - 子女教育 - ' + mostCurrent
|
||||||
|
|
||||||
|
feeds.append((self.title, articles[mostCurrent]))
|
||||||
|
|
||||||
|
return feeds
|
||||||
|
|
||||||
|
def populate_article_metadata(self, article, soup, first):
|
||||||
|
header = soup.find('h3')
|
||||||
|
self.log('header: ' + self.tag_to_string(header))
|
||||||
|
pass
|
||||||
|
|
@ -318,7 +318,11 @@ class LinuxFreeze(Command):
|
|||||||
import codecs
|
import codecs
|
||||||
|
|
||||||
def set_default_encoding():
|
def set_default_encoding():
|
||||||
|
try:
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
|
except:
|
||||||
|
print 'WARNING: Failed to set default libc locale, using en_US.UTF-8'
|
||||||
|
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||||
enc = locale.getdefaultlocale()[1]
|
enc = locale.getdefaultlocale()[1]
|
||||||
if not enc:
|
if not enc:
|
||||||
enc = locale.nl_langinfo(locale.CODESET)
|
enc = locale.nl_langinfo(locale.CODESET)
|
||||||
|
@ -605,8 +605,9 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
|
|
||||||
main, carda, cardb = self.find_device_nodes()
|
main, carda, cardb = self.find_device_nodes()
|
||||||
if main is None:
|
if main is None:
|
||||||
raise DeviceError(_('Unable to detect the %s disk drive. Your '
|
raise DeviceError(_('Unable to detect the %s disk drive. Either '
|
||||||
' kernel is probably exporting a deprecated version of SYSFS.')
|
'the device has already been ejected, or your '
|
||||||
|
'kernel is exporting a deprecated version of SYSFS.')
|
||||||
%self.__class__.__name__)
|
%self.__class__.__name__)
|
||||||
|
|
||||||
self._linux_mount_map = {}
|
self._linux_mount_map = {}
|
||||||
|
@ -97,10 +97,15 @@ class DeleteAction(InterfaceAction):
|
|||||||
for action in list(self.delete_menu.actions())[1:]:
|
for action in list(self.delete_menu.actions())[1:]:
|
||||||
action.setEnabled(enabled)
|
action.setEnabled(enabled)
|
||||||
|
|
||||||
def _get_selected_formats(self, msg):
|
def _get_selected_formats(self, msg, ids):
|
||||||
from calibre.gui2.dialogs.select_formats import SelectFormats
|
from calibre.gui2.dialogs.select_formats import SelectFormats
|
||||||
fmts = self.gui.library_view.model().db.all_formats()
|
fmts = set([])
|
||||||
d = SelectFormats([x.lower() for x in fmts], msg, parent=self.gui)
|
db = self.gui.library_view.model().db
|
||||||
|
for x in ids:
|
||||||
|
fmts_ = db.formats(x, index_is_id=True, verify_formats=False)
|
||||||
|
if fmts_:
|
||||||
|
fmts.update(frozenset([x.lower() for x in fmts_.split(',')]))
|
||||||
|
d = SelectFormats(list(sorted(fmts)), msg, parent=self.gui)
|
||||||
if d.exec_() != d.Accepted:
|
if d.exec_() != d.Accepted:
|
||||||
return None
|
return None
|
||||||
return d.selected_formats
|
return d.selected_formats
|
||||||
@ -118,7 +123,7 @@ class DeleteAction(InterfaceAction):
|
|||||||
if not ids:
|
if not ids:
|
||||||
return
|
return
|
||||||
fmts = self._get_selected_formats(
|
fmts = self._get_selected_formats(
|
||||||
_('Choose formats to be deleted'))
|
_('Choose formats to be deleted'), ids)
|
||||||
if not fmts:
|
if not fmts:
|
||||||
return
|
return
|
||||||
for id in ids:
|
for id in ids:
|
||||||
@ -136,7 +141,7 @@ class DeleteAction(InterfaceAction):
|
|||||||
if not ids:
|
if not ids:
|
||||||
return
|
return
|
||||||
fmts = self._get_selected_formats(
|
fmts = self._get_selected_formats(
|
||||||
'<p>'+_('Choose formats <b>not</b> to be deleted'))
|
'<p>'+_('Choose formats <b>not</b> to be deleted'), ids)
|
||||||
if fmts is None:
|
if fmts is None:
|
||||||
return
|
return
|
||||||
for id in ids:
|
for id in ids:
|
||||||
|
@ -5,18 +5,19 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re, os
|
||||||
|
|
||||||
from lxml import html
|
from lxml import html
|
||||||
from lxml.html import soupparser
|
from lxml.html import soupparser
|
||||||
|
|
||||||
from PyQt4.Qt import QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit, \
|
from PyQt4.Qt import QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit, \
|
||||||
QToolBar, QVBoxLayout, QAction, QIcon, QWebPage, Qt, QTabWidget, \
|
QToolBar, QVBoxLayout, QAction, QIcon, QWebPage, Qt, QTabWidget, QUrl, \
|
||||||
QSyntaxHighlighter, QColor, QChar
|
QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QInputDialog
|
||||||
from PyQt4.QtWebKit import QWebView
|
from PyQt4.QtWebKit import QWebView
|
||||||
|
|
||||||
from calibre.ebooks.chardet import xml_to_unicode
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
from calibre import xml_replace_entities
|
from calibre import xml_replace_entities
|
||||||
|
from calibre.gui2 import open_url
|
||||||
|
|
||||||
class PageAction(QAction): # {{{
|
class PageAction(QAction): # {{{
|
||||||
|
|
||||||
@ -44,6 +45,18 @@ class PageAction(QAction): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class BlockStyleAction(QAction): # {{{
|
||||||
|
|
||||||
|
def __init__(self, text, name, view):
|
||||||
|
QAction.__init__(self, text, view)
|
||||||
|
self._name = name
|
||||||
|
self.triggered.connect(self.apply_style)
|
||||||
|
|
||||||
|
def apply_style(self, *args):
|
||||||
|
self.parent().exec_command('formatBlock', self._name)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class EditorWidget(QWebView): # {{{
|
class EditorWidget(QWebView): # {{{
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@ -90,14 +103,109 @@ class EditorWidget(QWebView): # {{{
|
|||||||
ac = PageAction(wac, icon, text, checkable, self)
|
ac = PageAction(wac, icon, text, checkable, self)
|
||||||
setattr(self, 'action_'+name, ac)
|
setattr(self, 'action_'+name, ac)
|
||||||
|
|
||||||
|
self.action_color = QAction(QIcon(I('format-text-color')), _('Foreground color'),
|
||||||
|
self)
|
||||||
|
self.action_color.triggered.connect(self.foreground_color)
|
||||||
|
|
||||||
|
self.action_background = QAction(QIcon(I('format-fill-color')),
|
||||||
|
_('Background color'), self)
|
||||||
|
self.action_background.triggered.connect(self.background_color)
|
||||||
|
|
||||||
|
self.action_block_style = QAction(QIcon(I('format-text-heading')),
|
||||||
|
_('Style text block'), self)
|
||||||
|
self.action_block_style.setToolTip(
|
||||||
|
_('Style the selected text block'))
|
||||||
|
self.block_style_menu = QMenu(self)
|
||||||
|
self.action_block_style.setMenu(self.block_style_menu)
|
||||||
|
self.block_style_actions = []
|
||||||
|
for text, name in [
|
||||||
|
(_('Normal'), 'p'),
|
||||||
|
(_('Heading') +' 1', 'h1'),
|
||||||
|
(_('Heading') +' 2', 'h2'),
|
||||||
|
(_('Heading') +' 3', 'h3'),
|
||||||
|
(_('Heading') +' 4', 'h4'),
|
||||||
|
(_('Heading') +' 5', 'h5'),
|
||||||
|
(_('Heading') +' 6', 'h6'),
|
||||||
|
(_('Pre-formatted'), 'pre'),
|
||||||
|
(_('Blockquote'), 'blockquote'),
|
||||||
|
(_('Address'), 'address'),
|
||||||
|
]:
|
||||||
|
ac = BlockStyleAction(text, name, self)
|
||||||
|
self.block_style_menu.addAction(ac)
|
||||||
|
self.block_style_actions.append(ac)
|
||||||
|
|
||||||
|
self.action_insert_link = QAction(QIcon(I('insert-link.png')),
|
||||||
|
_('Insert link'), self)
|
||||||
|
self.action_insert_link.triggered.connect(self.insert_link)
|
||||||
|
|
||||||
|
self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
||||||
|
self.page().linkClicked.connect(self.link_clicked)
|
||||||
|
|
||||||
|
def link_clicked(self, url):
|
||||||
|
open_url(url)
|
||||||
|
|
||||||
|
def foreground_color(self):
|
||||||
|
col = QColorDialog.getColor(Qt.black, self,
|
||||||
|
_('Choose foreground color'), QColorDialog.ShowAlphaChannel)
|
||||||
|
if col.isValid():
|
||||||
|
self.exec_command('foreColor', unicode(col.name()))
|
||||||
|
|
||||||
|
def background_color(self):
|
||||||
|
col = QColorDialog.getColor(Qt.white, self,
|
||||||
|
_('Choose background color'), QColorDialog.ShowAlphaChannel)
|
||||||
|
if col.isValid():
|
||||||
|
self.exec_command('hiliteColor', unicode(col.name()))
|
||||||
|
|
||||||
|
def insert_link(self, *args):
|
||||||
|
link, ok = QInputDialog.getText(self, _('Create link'),
|
||||||
|
_('Enter URL'))
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
url = self.parse_link(unicode(link))
|
||||||
|
if url.isValid():
|
||||||
|
url = unicode(url.toString())
|
||||||
|
self.exec_command('createLink', url)
|
||||||
|
|
||||||
|
def parse_link(self, link):
|
||||||
|
link = link.strip()
|
||||||
|
has_schema = re.match(r'^[a-zA-Z]+:', link)
|
||||||
|
if has_schema is not None:
|
||||||
|
url = QUrl(link, QUrl.TolerantMode)
|
||||||
|
if url.isValid():
|
||||||
|
return url
|
||||||
|
if os.path.exists(link):
|
||||||
|
return QUrl.fromLocalFile(link)
|
||||||
|
|
||||||
|
if has_schema is None:
|
||||||
|
first, _, rest = link.partition('.')
|
||||||
|
prefix = 'http'
|
||||||
|
if first == 'ftp':
|
||||||
|
prefix = 'ftp'
|
||||||
|
url = QUrl(prefix +'://'+link, QUrl.TolerantMode)
|
||||||
|
if url.isValid():
|
||||||
|
return url
|
||||||
|
|
||||||
|
return QUrl(link, QUrl.TolerantMode)
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return QSize(150, 150)
|
return QSize(150, 150)
|
||||||
|
|
||||||
|
def exec_command(self, cmd, arg=None):
|
||||||
|
frame = self.page().mainFrame()
|
||||||
|
if arg is not None:
|
||||||
|
js = 'document.execCommand("%s", false, "%s");' % (cmd, arg)
|
||||||
|
else:
|
||||||
|
js = 'document.execCommand("%s", false, null);' % cmd
|
||||||
|
frame.evaluateJavaScript(js)
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def html(self):
|
def html(self):
|
||||||
|
|
||||||
def fget(self):
|
def fget(self):
|
||||||
ans = u''
|
ans = u''
|
||||||
|
check = unicode(self.page().mainFrame().toPlainText()).strip()
|
||||||
|
if not check:
|
||||||
|
return ans
|
||||||
try:
|
try:
|
||||||
raw = unicode(self.page().mainFrame().toHtml())
|
raw = unicode(self.page().mainFrame().toHtml())
|
||||||
raw = xml_to_unicode(raw, strip_encoding_pats=True,
|
raw = xml_to_unicode(raw, strip_encoding_pats=True,
|
||||||
@ -348,7 +456,7 @@ class Highlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class Editor(QWidget):
|
class Editor(QWidget): # {{{
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
@ -404,6 +512,15 @@ class Editor(QWidget):
|
|||||||
self.toolbar1.addAction(ac)
|
self.toolbar1.addAction(ac)
|
||||||
self.toolbar1.addSeparator()
|
self.toolbar1.addSeparator()
|
||||||
|
|
||||||
|
self.toolbar1.addAction(self.editor.action_color)
|
||||||
|
self.toolbar1.addAction(self.editor.action_background)
|
||||||
|
self.toolbar1.addSeparator()
|
||||||
|
|
||||||
|
self.toolbar1.addAction(self.editor.action_block_style)
|
||||||
|
w = self.toolbar1.widgetForAction(self.editor.action_block_style)
|
||||||
|
w.setPopupMode(w.InstantPopup)
|
||||||
|
self.toolbar1.addAction(self.editor.action_insert_link)
|
||||||
|
|
||||||
self.code_edit.textChanged.connect(self.code_dirtied)
|
self.code_edit.textChanged.connect(self.code_dirtied)
|
||||||
self.editor.page().contentsChanged.connect(self.wyswyg_dirtied)
|
self.editor.page().contentsChanged.connect(self.wyswyg_dirtied)
|
||||||
|
|
||||||
@ -411,18 +528,21 @@ class Editor(QWidget):
|
|||||||
def html(self):
|
def html(self):
|
||||||
def fset(self, v):
|
def fset(self, v):
|
||||||
self.editor.html = v
|
self.editor.html = v
|
||||||
return property(fget=lambda self:self.editor.html, fset=fset)
|
def fget(self):
|
||||||
|
self.tabs.setCurrentIndex(0)
|
||||||
|
return self.editor.html
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def change_tab(self, index):
|
def change_tab(self, index):
|
||||||
#print 'reloading:', (index and self.wyswyg_dirty) or (not index and
|
#print 'reloading:', (index and self.wyswyg_dirty) or (not index and
|
||||||
# self.source_dirty)
|
# self.source_dirty)
|
||||||
if index == 1: # changing to code view
|
if index == 1: # changing to code view
|
||||||
if self.wyswyg_dirty:
|
if self.wyswyg_dirty:
|
||||||
self.code_edit.setPlainText(self.html)
|
self.code_edit.setPlainText(self.editor.html)
|
||||||
self.wyswyg_dirty = False
|
self.wyswyg_dirty = False
|
||||||
elif index == 0: #changing to wyswyg
|
elif index == 0: #changing to wyswyg
|
||||||
if self.source_dirty:
|
if self.source_dirty:
|
||||||
self.html = unicode(self.code_edit.toPlainText())
|
self.editor.html = unicode(self.code_edit.toPlainText())
|
||||||
self.source_dirty = False
|
self.source_dirty = False
|
||||||
|
|
||||||
def wyswyg_dirtied(self, *args):
|
def wyswyg_dirtied(self, *args):
|
||||||
@ -431,6 +551,8 @@ class Editor(QWidget):
|
|||||||
def code_dirtied(self, *args):
|
def code_dirtied(self, *args):
|
||||||
self.source_dirty = True
|
self.source_dirty = True
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
w = Editor()
|
w = Editor()
|
||||||
|
@ -11,7 +11,6 @@ from PyQt4.Qt import Qt
|
|||||||
from calibre.gui2.convert.mobi_output_ui import Ui_Form
|
from calibre.gui2.convert.mobi_output_ui import Ui_Form
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget
|
||||||
from calibre.gui2.widgets import FontFamilyModel
|
from calibre.gui2.widgets import FontFamilyModel
|
||||||
from calibre.utils.fonts import fontconfig
|
|
||||||
|
|
||||||
font_family_model = None
|
font_family_model = None
|
||||||
|
|
||||||
@ -28,6 +27,7 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
'mobi_ignore_margins',
|
'mobi_ignore_margins',
|
||||||
'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc']
|
'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc']
|
||||||
)
|
)
|
||||||
|
from calibre.utils.fonts import fontconfig
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
|
|
||||||
global font_family_model
|
global font_family_model
|
||||||
|
@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
'''Dialog to edit metadata in bulk'''
|
'''Dialog to edit metadata in bulk'''
|
||||||
|
|
||||||
import re
|
import re, os
|
||||||
|
|
||||||
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
|
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
|
||||||
pyqtSignal, QDialogButtonBox
|
pyqtSignal, QDialogButtonBox
|
||||||
@ -13,12 +13,41 @@ from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
|||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
||||||
from calibre.ebooks.metadata.book.base import composite_formatter
|
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||||
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||||
from calibre.utils.config import dynamic
|
from calibre.utils.config import dynamic
|
||||||
from calibre.utils.titlecase import titlecase
|
from calibre.utils.titlecase import titlecase
|
||||||
from calibre.utils.icu import sort_key, capitalize
|
from calibre.utils.icu import sort_key, capitalize
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.utils.magick.draw import identify_data
|
||||||
|
|
||||||
|
def get_cover_data(path):
|
||||||
|
old = prefs['read_file_metadata']
|
||||||
|
if not old:
|
||||||
|
prefs['read_file_metadata'] = True
|
||||||
|
cdata = area = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
mi = get_metadata(open(path, 'rb'),
|
||||||
|
os.path.splitext(path)[1][1:].lower())
|
||||||
|
if mi.cover and os.access(mi.cover, os.R_OK):
|
||||||
|
cdata = open(mi.cover).read()
|
||||||
|
elif mi.cover_data[1] is not None:
|
||||||
|
cdata = mi.cover_data[1]
|
||||||
|
if cdata:
|
||||||
|
width, height, fmt = identify_data(cdata)
|
||||||
|
area = width*height
|
||||||
|
except:
|
||||||
|
cdata = area = None
|
||||||
|
|
||||||
|
if old != prefs['read_file_metadata']:
|
||||||
|
prefs['read_file_metadata'] = old
|
||||||
|
|
||||||
|
return cdata, area
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MyBlockingBusy(QDialog):
|
class MyBlockingBusy(QDialog):
|
||||||
|
|
||||||
@ -147,6 +176,20 @@ class MyBlockingBusy(QDialog):
|
|||||||
cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
|
cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
|
||||||
series_string=series_string)
|
series_string=series_string)
|
||||||
self.db.set_cover(id, cdata)
|
self.db.set_cover(id, cdata)
|
||||||
|
elif cover_action == 'fromfmt':
|
||||||
|
fmts = self.db.formats(id, index_is_id=True, verify_formats=False)
|
||||||
|
if fmts:
|
||||||
|
covers = []
|
||||||
|
for fmt in fmts.split(','):
|
||||||
|
fmt = self.db.format_abspath(id, fmt, index_is_id=True)
|
||||||
|
if not fmt: continue
|
||||||
|
cdata, area = get_cover_data(fmt)
|
||||||
|
if cdata:
|
||||||
|
covers.append((cdata, area))
|
||||||
|
covers.sort(key=lambda x: x[1])
|
||||||
|
if covers:
|
||||||
|
self.db.set_cover(id, covers[-1][0])
|
||||||
|
covers = []
|
||||||
elif self.current_phase == 2:
|
elif self.current_phase == 2:
|
||||||
# All of these just affect the DB, so we can tolerate a total rollback
|
# All of these just affect the DB, so we can tolerate a total rollback
|
||||||
if do_auto_author:
|
if do_auto_author:
|
||||||
@ -714,6 +757,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
cover_action = 'remove'
|
cover_action = 'remove'
|
||||||
elif self.cover_generate.isChecked():
|
elif self.cover_generate.isChecked():
|
||||||
cover_action = 'generate'
|
cover_action = 'generate'
|
||||||
|
elif self.cover_from_fmt.isChecked():
|
||||||
|
cover_action = 'fromfmt'
|
||||||
|
|
||||||
args = (remove_all, remove, add, au, aus, do_aus, rating, pub, do_series,
|
args = (remove_all, remove, add, au, aus, do_aus, rating, pub, do_series,
|
||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
||||||
|
@ -414,6 +414,13 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="cover_from_fmt">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set from &ebook file(s)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -710,8 +717,8 @@ nothing should be put between the original text and the inserted text</string>
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>122</width>
|
<width>726</width>
|
||||||
<height>38</height>
|
<height>334</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="testgrid">
|
<layout class="QGridLayout" name="testgrid">
|
||||||
|
@ -34,6 +34,7 @@ from calibre.customize.ui import run_plugins_on_import, get_isbndb_key
|
|||||||
from calibre.gui2.preferences.social import SocialMetadata
|
from calibre.gui2.preferences.social import SocialMetadata
|
||||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||||
from calibre import strftime
|
from calibre import strftime
|
||||||
|
from calibre.library.comments import comments_to_html
|
||||||
|
|
||||||
class CoverFetcher(Thread): # {{{
|
class CoverFetcher(Thread): # {{{
|
||||||
|
|
||||||
@ -195,7 +196,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
_file + _(" is not a valid picture"))
|
_file + _(" is not a valid picture"))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
else:
|
else:
|
||||||
self.cover_path.setText(_file)
|
|
||||||
self.cover.setPixmap(pix)
|
self.cover.setPixmap(pix)
|
||||||
self.update_cover_tooltip()
|
self.update_cover_tooltip()
|
||||||
self.cover_changed = True
|
self.cover_changed = True
|
||||||
@ -409,7 +409,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if mi.series_index is not None:
|
if mi.series_index is not None:
|
||||||
self.series_index.setValue(float(mi.series_index))
|
self.series_index.setValue(float(mi.series_index))
|
||||||
if mi.comments and mi.comments.strip():
|
if mi.comments and mi.comments.strip():
|
||||||
self.comments.setPlainText(mi.comments)
|
comments = comments_to_html(mi.comments)
|
||||||
|
self.comments.html = comments
|
||||||
|
|
||||||
|
|
||||||
def sync_formats(self):
|
def sync_formats(self):
|
||||||
@ -556,7 +557,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if rating > 0:
|
if rating > 0:
|
||||||
self.rating.setValue(int(rating/2.))
|
self.rating.setValue(int(rating/2.))
|
||||||
comments = self.db.comments(row)
|
comments = self.db.comments(row)
|
||||||
self.comments.setPlainText(comments if comments else '')
|
if comments and comments.strip():
|
||||||
|
comments = comments_to_html(comments)
|
||||||
|
self.comments.html = comments
|
||||||
cover = self.db.cover(row)
|
cover = self.db.cover(row)
|
||||||
pubdate = db.pubdate(self.id, index_is_id=True)
|
pubdate = db.pubdate(self.id, index_is_id=True)
|
||||||
self.pubdate.setDate(QDate(pubdate.year, pubdate.month,
|
self.pubdate.setDate(QDate(pubdate.year, pubdate.month,
|
||||||
@ -806,10 +809,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.pubdate.setDate(QDate(dt.year, dt.month, dt.day))
|
self.pubdate.setDate(QDate(dt.year, dt.month, dt.day))
|
||||||
summ = book.comments
|
summ = book.comments
|
||||||
if summ:
|
if summ:
|
||||||
prefix = unicode(self.comments.toPlainText())
|
prefix = self.comment.html
|
||||||
if prefix:
|
if prefix:
|
||||||
prefix += '\n'
|
prefix += '\n'
|
||||||
self.comments.setPlainText(prefix + summ)
|
self.comments.html = prefix + comments_to_html(summ)
|
||||||
if book.rating is not None:
|
if book.rating is not None:
|
||||||
self.rating.setValue(int(book.rating))
|
self.rating.setValue(int(book.rating))
|
||||||
if book.tags:
|
if book.tags:
|
||||||
@ -899,7 +902,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.db.set_series_index(self.id, self.series_index.value(),
|
self.db.set_series_index(self.id, self.series_index.value(),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
self.db.set_comment(self.id,
|
self.db.set_comment(self.id,
|
||||||
unicode(self.comments.toPlainText()).strip(),
|
self.comments.html,
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
d = self.pubdate.date()
|
d = self.pubdate.date()
|
||||||
d = qt_to_dt(d)
|
d = qt_to_dt(d)
|
||||||
@ -936,16 +939,16 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
QDialog.reject(self, *args)
|
QDialog.reject(self, *args)
|
||||||
|
|
||||||
def read_state(self):
|
def read_state(self):
|
||||||
wg = dynamic.get('metasingle_window_geometry', None)
|
wg = dynamic.get('metasingle_window_geometry2', None)
|
||||||
ss = dynamic.get('metasingle_splitter_state', None)
|
ss = dynamic.get('metasingle_splitter_state2', None)
|
||||||
if wg is not None:
|
if wg is not None:
|
||||||
self.restoreGeometry(wg)
|
self.restoreGeometry(wg)
|
||||||
if ss is not None:
|
if ss is not None:
|
||||||
self.splitter.restoreState(ss)
|
self.splitter.restoreState(ss)
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry()))
|
dynamic.set('metasingle_window_geometry2', bytes(self.saveGeometry()))
|
||||||
dynamic.set('metasingle_splitter_state',
|
dynamic.set('metasingle_splitter_state2',
|
||||||
bytes(self.splitter.saveState()))
|
bytes(self.splitter.saveState()))
|
||||||
|
|
||||||
def break_cycles(self):
|
def break_cycles(self):
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>887</width>
|
<width>994</width>
|
||||||
<height>750</height>
|
<height>726</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -43,8 +43,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>879</width>
|
<width>986</width>
|
||||||
<height>711</height>
|
<height>687</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
@ -66,8 +66,8 @@
|
|||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>&Basic metadata</string>
|
<string>&Basic metadata</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QGridLayout" name="gridLayout_5">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<widget class="QSplitter" name="splitter">
|
<widget class="QSplitter" name="splitter">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -495,29 +495,132 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
|
||||||
<property name="title">
|
|
||||||
<string>&Comments</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_4">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QTextEdit" name="comments">
|
|
||||||
<property name="tabChangesFocus">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="acceptRichText">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="layoutWidget_2">
|
<widget class="QWidget" name="layoutWidget_2">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="bc_box">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>10</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Book Cover</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="ImageView" name="cover" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>100</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="_4">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetMaximumSize</enum>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Change &cover image:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>cover_button</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="_5">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cover_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Browse</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="trim_cover_button">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Remove border (if any) from cover</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>T&rim</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/trim.png</normaloff>:/images/trim.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="reset_cover">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Reset cover to default</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Remove</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="fetch_cover_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Download co&ver</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="generate_cover_button">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Generate a default cover based on the title and author</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Generate cover</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="layoutWidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="af_group_box">
|
<widget class="QGroupBox" name="af_group_box">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -546,6 +649,12 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
<height>140</height>
|
<height>140</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="baseSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="dragDropMode">
|
<property name="dragDropMode">
|
||||||
<enum>QAbstractItemView::DropOnly</enum>
|
<enum>QAbstractItemView::DropOnly</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -644,129 +753,22 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="bc_box">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>10</verstretch>
|
<verstretch>10</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Book Cover</string>
|
<string>&Comments</string>
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
|
||||||
<item>
|
|
||||||
<widget class="ImageView" name="cover" native="true">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>100</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="_4">
|
|
||||||
<property name="spacing">
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<property name="sizeConstraint">
|
|
||||||
<enum>QLayout::SetMaximumSize</enum>
|
|
||||||
</property>
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||||
<property name="margin">
|
<property name="margin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="Editor" name="comments" native="true"/>
|
||||||
<property name="text">
|
|
||||||
<string>Change &cover image:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>cover_path</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="_5">
|
|
||||||
<property name="spacing">
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="cover_path">
|
|
||||||
<property name="readOnly">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="cover_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Browse</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="trim_cover_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Remove border (if any) from cover</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>T&rim</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/trim.png</normaloff>:/images/trim.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="toolButtonStyle">
|
|
||||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="reset_cover">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Reset cover to default</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="_6">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="fetch_cover_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>Download co&ver</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="generate_cover_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Generate a default cover based on the title and author</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Generate cover</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@ -828,6 +830,12 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
<header>calibre/gui2/widgets.h</header>
|
<header>calibre/gui2/widgets.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>Editor</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header location="global">calibre/gui2/comments_editor.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>title</tabstop>
|
<tabstop>title</tabstop>
|
||||||
@ -848,13 +856,11 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
<tabstop>date</tabstop>
|
<tabstop>date</tabstop>
|
||||||
<tabstop>pubdate</tabstop>
|
<tabstop>pubdate</tabstop>
|
||||||
<tabstop>fetch_metadata_button</tabstop>
|
<tabstop>fetch_metadata_button</tabstop>
|
||||||
<tabstop>comments</tabstop>
|
|
||||||
<tabstop>button_set_cover</tabstop>
|
<tabstop>button_set_cover</tabstop>
|
||||||
<tabstop>button_set_metadata</tabstop>
|
<tabstop>button_set_metadata</tabstop>
|
||||||
<tabstop>formats</tabstop>
|
<tabstop>formats</tabstop>
|
||||||
<tabstop>add_format_button</tabstop>
|
<tabstop>add_format_button</tabstop>
|
||||||
<tabstop>remove_format_button</tabstop>
|
<tabstop>remove_format_button</tabstop>
|
||||||
<tabstop>cover_path</tabstop>
|
|
||||||
<tabstop>cover_button</tabstop>
|
<tabstop>cover_button</tabstop>
|
||||||
<tabstop>trim_cover_button</tabstop>
|
<tabstop>trim_cover_button</tabstop>
|
||||||
<tabstop>reset_cover</tabstop>
|
<tabstop>reset_cover</tabstop>
|
||||||
|
@ -650,7 +650,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.action_table_of_contents.setDisabled(not self.iterator.toc)
|
self.action_table_of_contents.setDisabled(not self.iterator.toc)
|
||||||
self.current_book_has_toc = bool(self.iterator.toc)
|
self.current_book_has_toc = bool(self.iterator.toc)
|
||||||
self.current_title = title
|
self.current_title = title
|
||||||
self.setWindowTitle(self.base_window_title+' - '+title)
|
self.setWindowTitle(self.base_window_title+' - '+title +
|
||||||
|
' [%s]'%os.path.splitext(pathtoebook)[1][1:].upper())
|
||||||
self.pos.setMaximum(sum(self.iterator.pages))
|
self.pos.setMaximum(sum(self.iterator.pages))
|
||||||
self.pos.setSuffix(' / %d'%sum(self.iterator.pages))
|
self.pos.setSuffix(' / %d'%sum(self.iterator.pages))
|
||||||
self.vertical_scrollbar.setMinimum(100)
|
self.vertical_scrollbar.setMinimum(100)
|
||||||
|
@ -19,7 +19,6 @@ from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
|
|||||||
from calibre.constants import isosx
|
from calibre.constants import isosx
|
||||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
from calibre.gui2.filename_pattern_ui import Ui_Form
|
||||||
from calibre import fit_image
|
from calibre import fit_image
|
||||||
from calibre.utils.fonts import fontconfig
|
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||||
from calibre.utils.config import prefs, XMLConfig
|
from calibre.utils.config import prefs, XMLConfig
|
||||||
@ -283,6 +282,7 @@ class FontFamilyModel(QAbstractListModel):
|
|||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
QAbstractListModel.__init__(self, *args)
|
QAbstractListModel.__init__(self, *args)
|
||||||
|
from calibre.utils.fonts import fontconfig
|
||||||
try:
|
try:
|
||||||
self.families = fontconfig.find_font_families()
|
self.families = fontconfig.find_font_families()
|
||||||
except:
|
except:
|
||||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import re, os
|
import re, os, posixpath
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
@ -88,17 +88,24 @@ class ContentServer(object):
|
|||||||
def static(self, name):
|
def static(self, name):
|
||||||
'Serves static content'
|
'Serves static content'
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
|
fname = posixpath.basename(name)
|
||||||
|
try:
|
||||||
cherrypy.response.headers['Content-Type'] = {
|
cherrypy.response.headers['Content-Type'] = {
|
||||||
'js' : 'text/javascript',
|
'js' : 'text/javascript',
|
||||||
'css' : 'text/css',
|
'css' : 'text/css',
|
||||||
'png' : 'image/png',
|
'png' : 'image/png',
|
||||||
'gif' : 'image/gif',
|
'gif' : 'image/gif',
|
||||||
'html' : 'text/html',
|
'html' : 'text/html',
|
||||||
'' : 'application/octet-stream',
|
}[fname.rpartition('.')[-1].lower()]
|
||||||
}[name.rpartition('.')[-1].lower()]
|
except KeyError:
|
||||||
|
raise cherrypy.HTTPError(404, '%r not a valid resource type'%name)
|
||||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time)
|
cherrypy.response.headers['Last-Modified'] = self.last_modified(self.build_time)
|
||||||
path = P('content_server/'+name)
|
basedir = os.path.abspath(P('content_server'))
|
||||||
if not os.path.exists(path):
|
path = os.path.join(basedir, name.replace('/', os.sep))
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
if not path.startswith(basedir):
|
||||||
|
raise cherrypy.HTTPError(403, 'Access to %s is forbidden'%name)
|
||||||
|
if not os.path.exists(path) or not os.path.isfile(path):
|
||||||
raise cherrypy.HTTPError(404, '%s not found'%name)
|
raise cherrypy.HTTPError(404, '%s not found'%name)
|
||||||
if self.opts.develop:
|
if self.opts.develop:
|
||||||
lm = fromtimestamp(os.stat(path).st_mtime)
|
lm = fromtimestamp(os.stat(path).st_mtime)
|
||||||
|
@ -7,15 +7,19 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
from calibre.constants import plugins, iswindows
|
from calibre.constants import plugins, iswindows, islinux, isfreebsd
|
||||||
|
|
||||||
_fc, _fc_err = plugins['fontconfig']
|
_fc, _fc_err = plugins['fontconfig']
|
||||||
|
|
||||||
if _fc is None:
|
if _fc is None:
|
||||||
raise RuntimeError('Failed to load fontconfig with error:'+_fc_err)
|
raise RuntimeError('Failed to load fontconfig with error:'+_fc_err)
|
||||||
|
|
||||||
|
if islinux or isfreebsd:
|
||||||
|
Thread = object
|
||||||
|
else:
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
class FontConfig(Thread):
|
class FontConfig(Thread):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -45,6 +49,7 @@ class FontConfig(Thread):
|
|||||||
self.failed = True
|
self.failed = True
|
||||||
|
|
||||||
def wait(self):
|
def wait(self):
|
||||||
|
if not (islinux or isfreebsd):
|
||||||
self.join()
|
self.join()
|
||||||
if self.failed:
|
if self.failed:
|
||||||
raise RuntimeError('Failed to initialize fontconfig')
|
raise RuntimeError('Failed to initialize fontconfig')
|
||||||
@ -144,7 +149,13 @@ class FontConfig(Thread):
|
|||||||
return fonts if all else (fonts[0] if fonts else None)
|
return fonts if all else (fonts[0] if fonts else None)
|
||||||
|
|
||||||
fontconfig = FontConfig()
|
fontconfig = FontConfig()
|
||||||
fontconfig.start()
|
if islinux or isfreebsd:
|
||||||
|
# On X11 Qt also uses fontconfig, so initialization must happen in the
|
||||||
|
# main thread. In any case on X11 initializing fontconfig should be very
|
||||||
|
# fast
|
||||||
|
fontconfig.run()
|
||||||
|
else:
|
||||||
|
fontconfig.start()
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
from pprint import pprint;
|
from pprint import pprint;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user