Merge from trunk

This commit is contained in:
Charles Haley 2010-12-21 10:35:29 +00:00
commit ff59581ac6
21 changed files with 563 additions and 181 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View 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

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

View File

@ -40,13 +40,12 @@ class GazetvanAntwerpen(BasicNewsRecipe):
remove_tags_after = dict(name='span', attrs={'class':'author'})
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'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'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' )
]

View 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

View File

@ -318,7 +318,11 @@ class LinuxFreeze(Command):
import codecs
def set_default_encoding():
locale.setlocale(locale.LC_ALL, '')
try:
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]
if not enc:
enc = locale.nl_langinfo(locale.CODESET)

View File

@ -605,8 +605,9 @@ class Device(DeviceConfig, DevicePlugin):
main, carda, cardb = self.find_device_nodes()
if main is None:
raise DeviceError(_('Unable to detect the %s disk drive. Your '
' kernel is probably exporting a deprecated version of SYSFS.')
raise DeviceError(_('Unable to detect the %s disk drive. Either '
'the device has already been ejected, or your '
'kernel is exporting a deprecated version of SYSFS.')
%self.__class__.__name__)
self._linux_mount_map = {}

View File

@ -97,10 +97,15 @@ class DeleteAction(InterfaceAction):
for action in list(self.delete_menu.actions())[1:]:
action.setEnabled(enabled)
def _get_selected_formats(self, msg):
def _get_selected_formats(self, msg, ids):
from calibre.gui2.dialogs.select_formats import SelectFormats
fmts = self.gui.library_view.model().db.all_formats()
d = SelectFormats([x.lower() for x in fmts], msg, parent=self.gui)
fmts = set([])
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:
return None
return d.selected_formats
@ -118,7 +123,7 @@ class DeleteAction(InterfaceAction):
if not ids:
return
fmts = self._get_selected_formats(
_('Choose formats to be deleted'))
_('Choose formats to be deleted'), ids)
if not fmts:
return
for id in ids:
@ -136,7 +141,7 @@ class DeleteAction(InterfaceAction):
if not ids:
return
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:
return
for id in ids:

View File

@ -5,18 +5,19 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import re, os
from lxml import html
from lxml.html import soupparser
from PyQt4.Qt import QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit, \
QToolBar, QVBoxLayout, QAction, QIcon, QWebPage, Qt, QTabWidget, \
QSyntaxHighlighter, QColor, QChar
QToolBar, QVBoxLayout, QAction, QIcon, QWebPage, Qt, QTabWidget, QUrl, \
QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QInputDialog
from PyQt4.QtWebKit import QWebView
from calibre.ebooks.chardet import xml_to_unicode
from calibre import xml_replace_entities
from calibre.gui2 import open_url
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): # {{{
def __init__(self, parent=None):
@ -90,14 +103,109 @@ class EditorWidget(QWebView): # {{{
ac = PageAction(wac, icon, text, checkable, self)
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):
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
def html(self):
def fget(self):
ans = u''
check = unicode(self.page().mainFrame().toPlainText()).strip()
if not check:
return ans
try:
raw = unicode(self.page().mainFrame().toHtml())
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):
QWidget.__init__(self, parent)
@ -404,6 +512,15 @@ class Editor(QWidget):
self.toolbar1.addAction(ac)
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.editor.page().contentsChanged.connect(self.wyswyg_dirtied)
@ -411,18 +528,21 @@ class Editor(QWidget):
def html(self):
def fset(self, 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):
#print 'reloading:', (index and self.wyswyg_dirty) or (not index and
# self.source_dirty)
if index == 1: # changing to code view
if self.wyswyg_dirty:
self.code_edit.setPlainText(self.html)
self.code_edit.setPlainText(self.editor.html)
self.wyswyg_dirty = False
elif index == 0: #changing to wyswyg
if self.source_dirty:
self.html = unicode(self.code_edit.toPlainText())
self.editor.html = unicode(self.code_edit.toPlainText())
self.source_dirty = False
def wyswyg_dirtied(self, *args):
@ -431,6 +551,8 @@ class Editor(QWidget):
def code_dirtied(self, *args):
self.source_dirty = True
# }}}
if __name__ == '__main__':
app = QApplication([])
w = Editor()

View File

@ -11,7 +11,6 @@ from PyQt4.Qt import Qt
from calibre.gui2.convert.mobi_output_ui import Ui_Form
from calibre.gui2.convert import Widget
from calibre.gui2.widgets import FontFamilyModel
from calibre.utils.fonts import fontconfig
font_family_model = None
@ -28,6 +27,7 @@ class PluginWidget(Widget, Ui_Form):
'mobi_ignore_margins',
'dont_compress', 'no_inline_toc', 'masthead_font','personal_doc']
)
from calibre.utils.fonts import fontconfig
self.db, self.book_id = db, book_id
global font_family_model

View File

@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Dialog to edit metadata in bulk'''
import re
import re, os
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
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.ebooks.metadata import string_to_authors, authors_to_string
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 import error_dialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils.config import dynamic
from calibre.utils.titlecase import titlecase
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):
@ -147,6 +176,20 @@ class MyBlockingBusy(QDialog):
cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
series_string=series_string)
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:
# All of these just affect the DB, so we can tolerate a total rollback
if do_auto_author:
@ -714,6 +757,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
cover_action = 'remove'
elif self.cover_generate.isChecked():
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,
do_autonumber, do_remove_format, remove_format, do_swap_ta,

View File

@ -414,6 +414,13 @@ Future conversion of these books will use the default settings.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="cover_from_fmt">
<property name="text">
<string>Set from &amp;ebook file(s)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -710,8 +717,8 @@ nothing should be put between the original text and the inserted text</string>
<rect>
<x>0</x>
<y>0</y>
<width>122</width>
<height>38</height>
<width>726</width>
<height>334</height>
</rect>
</property>
<layout class="QGridLayout" name="testgrid">

View File

@ -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.custom_column_widgets import populate_metadata_page
from calibre import strftime
from calibre.library.comments import comments_to_html
class CoverFetcher(Thread): # {{{
@ -195,7 +196,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
_file + _(" is not a valid picture"))
d.exec_()
else:
self.cover_path.setText(_file)
self.cover.setPixmap(pix)
self.update_cover_tooltip()
self.cover_changed = True
@ -409,7 +409,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if mi.series_index is not None:
self.series_index.setValue(float(mi.series_index))
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):
@ -556,7 +557,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if rating > 0:
self.rating.setValue(int(rating/2.))
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)
pubdate = db.pubdate(self.id, index_is_id=True)
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))
summ = book.comments
if summ:
prefix = unicode(self.comments.toPlainText())
prefix = self.comment.html
if prefix:
prefix += '\n'
self.comments.setPlainText(prefix + summ)
self.comments.html = prefix + comments_to_html(summ)
if book.rating is not None:
self.rating.setValue(int(book.rating))
if book.tags:
@ -899,7 +902,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.db.set_series_index(self.id, self.series_index.value(),
notify=False, commit=False)
self.db.set_comment(self.id,
unicode(self.comments.toPlainText()).strip(),
self.comments.html,
notify=False, commit=False)
d = self.pubdate.date()
d = qt_to_dt(d)
@ -936,16 +939,16 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
QDialog.reject(self, *args)
def read_state(self):
wg = dynamic.get('metasingle_window_geometry', None)
ss = dynamic.get('metasingle_splitter_state', None)
wg = dynamic.get('metasingle_window_geometry2', None)
ss = dynamic.get('metasingle_splitter_state2', None)
if wg is not None:
self.restoreGeometry(wg)
if ss is not None:
self.splitter.restoreState(ss)
def save_state(self):
dynamic.set('metasingle_window_geometry', bytes(self.saveGeometry()))
dynamic.set('metasingle_splitter_state',
dynamic.set('metasingle_window_geometry2', bytes(self.saveGeometry()))
dynamic.set('metasingle_splitter_state2',
bytes(self.splitter.saveState()))
def break_cycles(self):

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>887</width>
<height>750</height>
<width>994</width>
<height>726</height>
</rect>
</property>
<property name="sizePolicy">
@ -43,8 +43,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>879</width>
<height>711</height>
<width>986</width>
<height>687</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -66,8 +66,8 @@
<attribute name="title">
<string>&amp;Basic metadata</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -495,29 +495,132 @@ Using this button to create author sort will change author sort from red to gree
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>&amp;Comments</string>
<widget class="QGroupBox" name="bc_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</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 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 &amp;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>&amp;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&amp;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>&amp;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&amp;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>&amp;Generate cover</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="af_group_box">
<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>
</size>
</property>
<property name="baseSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DropOnly</enum>
</property>
@ -644,129 +753,22 @@ Using this button to create author sort will change author sort from red to gree
</widget>
</item>
<item>
<widget class="QGroupBox" name="bc_box">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Book Cover</string>
<string>&amp;Comments</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="margin">
<number>0</number>
</property>
<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 &amp;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>&amp;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&amp;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&amp;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>&amp;Generate cover</string>
</property>
</widget>
</item>
</layout>
<widget class="Editor" name="comments" native="true"/>
</item>
</layout>
</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>
<container>1</container>
</customwidget>
<customwidget>
<class>Editor</class>
<extends>QWidget</extends>
<header location="global">calibre/gui2/comments_editor.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<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>pubdate</tabstop>
<tabstop>fetch_metadata_button</tabstop>
<tabstop>comments</tabstop>
<tabstop>button_set_cover</tabstop>
<tabstop>button_set_metadata</tabstop>
<tabstop>formats</tabstop>
<tabstop>add_format_button</tabstop>
<tabstop>remove_format_button</tabstop>
<tabstop>cover_path</tabstop>
<tabstop>cover_button</tabstop>
<tabstop>trim_cover_button</tabstop>
<tabstop>reset_cover</tabstop>

View File

@ -650,7 +650,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.action_table_of_contents.setDisabled(not self.iterator.toc)
self.current_book_has_toc = bool(self.iterator.toc)
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.setSuffix(' / %d'%sum(self.iterator.pages))
self.vertical_scrollbar.setMinimum(100)

View File

@ -19,7 +19,6 @@ from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
from calibre.constants import isosx
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image
from calibre.utils.fonts import fontconfig
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.metadata.meta import metadata_from_filename
from calibre.utils.config import prefs, XMLConfig
@ -283,6 +282,7 @@ class FontFamilyModel(QAbstractListModel):
def __init__(self, *args):
QAbstractListModel.__init__(self, *args)
from calibre.utils.fonts import fontconfig
try:
self.families = fontconfig.find_font_families()
except:

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import re, os
import re, os, posixpath
import cherrypy
@ -88,17 +88,24 @@ class ContentServer(object):
def static(self, name):
'Serves static content'
name = name.lower()
cherrypy.response.headers['Content-Type'] = {
fname = posixpath.basename(name)
try:
cherrypy.response.headers['Content-Type'] = {
'js' : 'text/javascript',
'css' : 'text/css',
'png' : 'image/png',
'gif' : 'image/gif',
'html' : 'text/html',
'' : 'application/octet-stream',
}[name.rpartition('.')[-1].lower()]
}[fname.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)
path = P('content_server/'+name)
if not os.path.exists(path):
basedir = os.path.abspath(P('content_server'))
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)
if self.opts.develop:
lm = fromtimestamp(os.stat(path).st_mtime)

View File

@ -7,15 +7,19 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
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']
if _fc is None:
raise RuntimeError('Failed to load fontconfig with error:'+_fc_err)
if islinux or isfreebsd:
Thread = object
else:
from threading import Thread
class FontConfig(Thread):
def __init__(self):
@ -45,7 +49,8 @@ class FontConfig(Thread):
self.failed = True
def wait(self):
self.join()
if not (islinux or isfreebsd):
self.join()
if self.failed:
raise RuntimeError('Failed to initialize fontconfig')
@ -144,7 +149,13 @@ class FontConfig(Thread):
return fonts if all else (fonts[0] if fonts else None)
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():
from pprint import pprint;