Merge from trunk

This commit is contained in:
Charles Haley 2011-03-02 08:30:57 +00:00
commit 0a31b6ac00
16 changed files with 326 additions and 94 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1,31 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1298137661(BasicNewsRecipe):
title = u'Helsingin Sanomat'
__author__ = 'oneillpt'
language = 'fi'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
remove_javascript = True
conversion_options = {
'linearize_tables' : True
}
remove_tags = [
dict(name='a', attrs={'id':'articleCommentUrl'}),
dict(name='p', attrs={'class':'newsSummary'}),
dict(name='div', attrs={'class':'headerTools'})
]
feeds = [(u'Uutiset - HS.fi', u'http://www.hs.fi/uutiset/rss/'), (u'Politiikka - HS.fi', u'http://www.hs.fi/politiikka/rss/'),
(u'Ulkomaat - HS.fi', u'http://www.hs.fi/ulkomaat/rss/'), (u'Kulttuuri - HS.fi', u'http://www.hs.fi/kulttuuri/rss/'),
(u'Kirjat - HS.fi', u'http://www.hs.fi/kulttuuri/kirjat/rss/'), (u'Elokuvat - HS.fi', u'http://www.hs.fi/kulttuuri/elokuvat/rss/')
]
def print_version(self, url):
j = url.rfind("/")
s = url[j:]
i = s.rfind("?ref=rss")
if i > 0:
s = s[:i]
return "http://www.hs.fi/tulosta" + s

View File

@ -0,0 +1,104 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2011, Davide Cavalca <davide125 at tiscali.it>'
'''
lwn.net
'''
from calibre.web.feeds.news import BasicNewsRecipe
import re
class WeeklyLWN(BasicNewsRecipe):
title = 'LWN.net Weekly Edition'
description = 'Weekly summary of what has happened in the free software world.'
__author__ = 'Davide Cavalca'
language = 'en'
cover_url = 'http://lwn.net/images/lcorner.png'
#masthead_url = 'http://lwn.net/images/lcorner.png'
publication_type = 'magazine'
remove_tags_before = dict(attrs={'class':'PageHeadline'})
remove_tags_after = dict(attrs={'class':'ArticleText'})
remove_tags = [dict(name=['h2', 'form'])]
conversion_options = { 'linearize_tables' : True }
oldest_article = 7.0
needs_subscription = 'optional'
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('https://lwn.net/login')
br.select_form(name='loginform')
br['Username'] = self.username
br['Password'] = self.password
br.submit()
return br
def parse_index(self):
if self.username is not None and self.password is not None:
index_url = 'http://lwn.net/current/bigpage'
else:
index_url = 'http://lwn.net/free/bigpage'
soup = self.index_to_soup(index_url)
body = soup.body
articles = {}
ans = []
url_re = re.compile('^http://lwn.net/Articles/')
while True:
tag_title = body.findNext(name='p', attrs={'class':'SummaryHL'})
if tag_title == None:
break
tag_section = tag_title.findPrevious(name='p', attrs={'class':'Cat1HL'})
if tag_section == None:
section = 'Front Page'
else:
section = tag_section.string
tag_section2 = tag_title.findPrevious(name='p', attrs={'class':'Cat2HL'})
if tag_section2 != None:
if tag_section2.findPrevious(name='p', attrs={'class':'Cat1HL'}) == tag_section:
section = "%s: %s" %(section, tag_section2.string)
if section not in articles.keys():
articles[section] = []
if section not in ans:
ans.append(section)
body = tag_title
while True:
tag_url = body.findNext(name='a', attrs={'href':url_re})
if tag_url == None:
break
body = tag_url
if tag_url.string == None:
continue
elif tag_url.string == 'Full Story':
break
elif tag_url.string.startswith('Comments ('):
break
else:
continue
if tag_url == None:
break
article = dict(
title=tag_title.string,
url=tag_url['href'].split('#')[0],
description='', content='', date='')
articles[section].append(article)
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
if not ans:
raise Exception('Could not find any articles.')
return ans
# vim: expandtab:ts=4:sw=4

View File

@ -11,7 +11,6 @@ http://www.macworld.co.uk/
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ptempfile import PersistentTemporaryFile
temp_files = [] temp_files = []
articles_are_obfuscated = True articles_are_obfuscated = True
@ -36,26 +35,17 @@ class macWorld(BasicNewsRecipe):
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True
def get_obfuscated_article(self, url):
br = self.get_browser()
br.open(url+'&print')
response = br.follow_link(url, nr = 0)
html = response.read()
self.temp_files.append(PersistentTemporaryFile('_fa.html'))
self.temp_files[-1].write(html)
self.temp_files[-1].close()
return self.temp_files[-1].name
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'id':'article'}) dict(name='div', attrs={'id':'content'})
] ]
remove_tags = [ remove_tags = [
dict(name='div', attrs={'class':['toolBar','mac_tags','toolBar btmTools','textAds']}), {'class':['toolBar','mac_tags','toolBar btmTools','textAds']},
dict(name='p', attrs={'class':'breadcrumbs'}), dict(name='p', attrs={'class':'breadcrumbs'}),
dict(name='div', attrs={'id':['breadcrumb','sidebar','comments']}) dict(id=['breadcrumb','sidebar','comments','topContentWrapper',
'rightColumn', 'aboveFootPromo', 'storyCarousel']),
{'class':lambda x: x and ('tools' in x or 'toolBar'
in x)}
] ]

View File

@ -69,12 +69,16 @@ class SeattleTimes(BasicNewsRecipe):
u'http://seattletimes.nwsource.com/rss/mostreadarticles.xml'), u'http://seattletimes.nwsource.com/rss/mostreadarticles.xml'),
] ]
keep_only_tags = [dict(id='content')]
remove_tags = [ remove_tags = [
dict(name=['object','link','script']) dict(name=['object','link','script']),
,dict(name='p', attrs={'class':'permission'}) {'class':['permission', 'note', 'bottomtools',
'homedelivery']},
dict(id=["rightcolumn", 'footer', 'adbottom']),
] ]
def print_version(self, url): def print_version(self, url):
return url
start_url, sep, rest_url = url.rpartition('_') start_url, sep, rest_url = url.rpartition('_')
rurl, rsep, article_id = start_url.rpartition('/') rurl, rsep, article_id = start_url.rpartition('/')
return u'http://seattletimes.nwsource.com/cgi-bin/PrintStory.pl?document_id=' + article_id return u'http://seattletimes.nwsource.com/cgi-bin/PrintStory.pl?document_id=' + article_id

View File

@ -10,12 +10,14 @@ class AdvancedUserRecipe1278049615(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
feeds = [(u'News', u'http://www.statesman.com/section-rss.do?source=news&includeSubSections=true'), feeds = [(u'News',
(u'Business', u'http://www.statesman.com/section-rss.do?source=business&includeSubSections=true'), u'http://www.statesman.com/section-rss.do?source=news&includeSubSections=true'),
(u'Life', u'http://www.statesman.com/section-rss.do?source=life&includesubsection=true'), (u'Local', u'http://www.statesman.com/section-rss.do?source=local&includeSubSections=true'),
(u'Editorial', u'http://www.statesman.com/section-rss.do?source=opinion&includesubsections=true'), (u'Business', u'http://www.statesman.com/section-rss.do?source=business&includeSubSections=true'),
(u'Sports', u'http://www.statesman.com/section-rss.do?source=sports&includeSubSections=true') (u'Life', u'http://www.statesman.com/section-rss.do?source=life&includesubsection=true'),
] (u'Editorial', u'http://www.statesman.com/section-rss.do?source=opinion&includesubsections=true'),
(u'Sports', u'http://www.statesman.com/section-rss.do?source=sports&includeSubSections=true')
]
masthead_url = "http://www.statesman.com/images/cmg-logo.gif" masthead_url = "http://www.statesman.com/images/cmg-logo.gif"
#temp_files = [] #temp_files = []
#articles_are_obfuscated = True #articles_are_obfuscated = True
@ -28,8 +30,11 @@ class AdvancedUserRecipe1278049615(BasicNewsRecipe):
conversion_options = {'linearize_tables':True} conversion_options = {'linearize_tables':True}
remove_tags = [ remove_tags = [
dict(name='div', attrs={'id':'cxArticleOptions'}), dict(name='div', attrs={'id':'cxArticleOptions'}),
{'class':['perma', 'comments', 'trail', 'share-buttons',
'toggle_show_on']},
] ]
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':'cxArticleHeader'}), dict(name='div', attrs={'class':'cxArticleHeader'}),
dict(name='div', attrs={'id':'cxArticleBodyText'}), dict(name='div', attrs={'id':['cxArticleBodyText',
'content']}),
] ]

View File

@ -7,6 +7,7 @@ swiatczytnikow.pl
''' '''
import re import re
from calibre.web.feeds.news import BasicNewsRecipe
class swiatczytnikow(BasicNewsRecipe): class swiatczytnikow(BasicNewsRecipe):
title = u'Swiat Czytnikow' title = u'Swiat Czytnikow'

View File

@ -265,16 +265,28 @@ class CSSPreProcessor(object):
PAGE_PAT = re.compile(r'@page[^{]*?{[^}]*?}') PAGE_PAT = re.compile(r'@page[^{]*?{[^}]*?}')
# Remove some of the broken CSS Microsoft products # Remove some of the broken CSS Microsoft products
# create, slightly dangerous as it removes to end of line # create
# rather than semi-colon MS_PAT = re.compile(r'''
MS_PAT = re.compile(r'^\s*(mso-|panose-).+?$', (?P<start>^|;|\{)\s* # The end of the previous rule or block start
re.MULTILINE|re.IGNORECASE) (%s).+? # The invalid selectors
(?P<end>$|;|\}) # The end of the declaration
'''%'mso-|panose-|text-underline|tab-interval',
re.MULTILINE|re.IGNORECASE|re.VERBOSE)
def ms_sub(self, match):
end = match.group('end')
try:
start = match.group('start')
except:
start = ''
if end == ';':
end = ''
return start + end
def __call__(self, data, add_namespace=False): def __call__(self, data, add_namespace=False):
from calibre.ebooks.oeb.base import XHTML_CSS_NAMESPACE from calibre.ebooks.oeb.base import XHTML_CSS_NAMESPACE
data = self.PAGE_PAT.sub('', data) data = self.PAGE_PAT.sub('', data)
if '\n' in data: data = self.MS_PAT.sub(self.ms_sub, data)
data = self.MS_PAT.sub('', data)
if not add_namespace: if not add_namespace:
return data return data
ans, namespaced = [], False ans, namespaced = [], False

View File

@ -827,6 +827,24 @@ class Manifest(object):
return None return None
return etree.fromstring(data, parser=RECOVER_PARSER) return etree.fromstring(data, parser=RECOVER_PARSER)
def clean_word_doc(self, data):
prefixes = []
for match in re.finditer(r'xmlns:(\S+?)=".*?microsoft.*?"', data):
prefixes.append(match.group(1))
if prefixes:
self.oeb.log.warn('Found microsoft markup, cleaning...')
# Remove empty tags as they are not rendered by browsers
# but can become renderable HTML tags like <p/> if the
# document is parsed by an HTML parser
pat = re.compile(
r'<(%s):([a-zA-Z0-9]+)[^>/]*?></\1:\2>'%('|'.join(prefixes)),
re.DOTALL)
data = pat.sub('', data)
pat = re.compile(
r'<(%s):([a-zA-Z0-9]+)[^>/]*?/>'%('|'.join(prefixes)))
data = pat.sub('', data)
return data
def _parse_xhtml(self, data): def _parse_xhtml(self, data):
self.oeb.log.debug('Parsing', self.href, '...') self.oeb.log.debug('Parsing', self.href, '...')
# Convert to Unicode and normalize line endings # Convert to Unicode and normalize line endings
@ -884,6 +902,10 @@ class Manifest(object):
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
data = etree.fromstring(data, parser=RECOVER_PARSER) data = etree.fromstring(data, parser=RECOVER_PARSER)
return data return data
try:
data = self.clean_word_doc(data)
except:
pass
data = first_pass(data) data = first_pass(data)
# Handle weird (non-HTML/fragment) files # Handle weird (non-HTML/fragment) files
@ -907,6 +929,7 @@ class Manifest(object):
parent.append(child) parent.append(child)
data = nroot data = nroot
# Force into the XHTML namespace # Force into the XHTML namespace
if not namespace(data.tag): if not namespace(data.tag):
self.oeb.log.warn('Forcing', self.href, 'into XHTML namespace') self.oeb.log.warn('Forcing', self.href, 'into XHTML namespace')

View File

@ -423,6 +423,7 @@ class Stylizer(object):
class Style(object): class Style(object):
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$') UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
MS_PAT = re.compile(r'^\s*(mso-|panose-|text-underline|tab-interval)')
def __init__(self, element, stylizer): def __init__(self, element, stylizer):
self._element = element self._element = element
@ -447,6 +448,8 @@ class Style(object):
return return
css = attrib['style'].split(';') css = attrib['style'].split(';')
css = filter(None, (x.strip() for x in css)) css = filter(None, (x.strip() for x in css))
css = [x.strip() for x in css]
css = [x for x in css if self.MS_PAT.match(x) is None]
try: try:
style = CSSStyleDeclaration('; '.join(css)) style = CSSStyleDeclaration('; '.join(css))
except CSSSyntaxError: except CSSSyntaxError:

View File

@ -19,11 +19,11 @@ single_shot = partial(QTimer.singleShot, 10)
class MultiDeleter(QObject): class MultiDeleter(QObject):
def __init__(self, gui, rows, callback): def __init__(self, gui, ids, callback):
from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.dialogs.progress import ProgressDialog
QObject.__init__(self, gui) QObject.__init__(self, gui)
self.model = gui.library_view.model() self.model = gui.library_view.model()
self.ids = list(map(self.model.id, rows)) self.ids = ids
self.gui = gui self.gui = gui
self.failures = [] self.failures = []
self.deleted_ids = [] self.deleted_ids = []
@ -231,6 +231,7 @@ class DeleteAction(InterfaceAction):
return return
# Library view is visible. # Library view is visible.
if self.gui.stack.currentIndex() == 0: if self.gui.stack.currentIndex() == 0:
to_delete_ids = [view.model().id(r) for r in rows]
# Ask the user if they want to delete the book from the library or device if it is in both. # Ask the user if they want to delete the book from the library or device if it is in both.
if self.gui.device_manager.is_device_connected: if self.gui.device_manager.is_device_connected:
on_device = False on_device = False
@ -264,10 +265,10 @@ class DeleteAction(InterfaceAction):
if ci.isValid(): if ci.isValid():
row = ci.row() row = ci.row()
if len(rows) < 5: if len(rows) < 5:
ids_deleted = view.model().delete_books(rows) view.model().delete_books_by_id(to_delete_ids)
self.library_ids_deleted(ids_deleted, row) self.library_ids_deleted(to_delete_ids, row)
else: else:
self.__md = MultiDeleter(self.gui, rows, self.__md = MultiDeleter(self.gui, to_delete_ids,
partial(self.library_ids_deleted, current_row=row)) partial(self.library_ids_deleted, current_row=row))
# Device view is visible. # Device view is visible.
else: else:

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
import textwrap, os, re import textwrap, os, re
from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \ from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \
QDialog, QPixmap, QGraphicsScene, QIcon, QSize QDialog, QPixmap, QIcon, QSize
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
from calibre.gui2 import dynamic, open_local_file, open_url from calibre.gui2 import dynamic, open_local_file, open_url
@ -14,12 +14,14 @@ from calibre import fit_image
from calibre.library.comments import comments_to_html from calibre.library.comments import comments_to_html
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
class BookInfo(QDialog, Ui_BookInfo): class BookInfo(QDialog, Ui_BookInfo):
def __init__(self, parent, view, row, view_func): def __init__(self, parent, view, row, view_func):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
Ui_BookInfo.__init__(self) Ui_BookInfo.__init__(self)
self.setupUi(self) self.setupUi(self)
self.gui = parent
self.cover_pixmap = None self.cover_pixmap = None
self.comments.sizeHint = self.comments_size_hint self.comments.sizeHint = self.comments_size_hint
self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks)
@ -38,11 +40,26 @@ class BookInfo(QDialog, Ui_BookInfo):
self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path) self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path)
self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
self.cover.resizeEvent = self.cover_view_resized self.cover.resizeEvent = self.cover_view_resized
self.cover.cover_changed.connect(self.cover_changed)
desktop = QCoreApplication.instance().desktop() desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry().height() - 100 screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height) self.resize(self.size().width(), screen_height)
def cover_changed(self, data):
if self.current_row is not None:
id_ = self.view.model().id(self.current_row)
self.view.model().db.set_cover(id_, data)
if self.gui.cover_flow:
self.gui.cover_flow.dataChanged()
ci = self.view.currentIndex()
if ci.isValid():
self.view.model().current_changed(ci, ci)
self.cover_pixmap = QPixmap()
self.cover_pixmap.loadFromData(data)
if self.fit_cover.isChecked():
self.resize_cover()
def link_clicked(self, url): def link_clicked(self, url):
open_url(url) open_url(url)
@ -83,7 +100,6 @@ class BookInfo(QDialog, Ui_BookInfo):
if self.cover_pixmap is None: if self.cover_pixmap is None:
return return
self.setWindowIcon(QIcon(self.cover_pixmap)) self.setWindowIcon(QIcon(self.cover_pixmap))
self.scene = QGraphicsScene()
pixmap = self.cover_pixmap pixmap = self.cover_pixmap
if self.fit_cover.isChecked(): if self.fit_cover.isChecked():
scaled, new_width, new_height = fit_image(pixmap.width(), scaled, new_width, new_height = fit_image(pixmap.width(),
@ -92,8 +108,7 @@ class BookInfo(QDialog, Ui_BookInfo):
if scaled: if scaled:
pixmap = pixmap.scaled(new_width, new_height, pixmap = pixmap.scaled(new_width, new_height,
Qt.KeepAspectRatio, Qt.SmoothTransformation) Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.scene.addPixmap(pixmap) self.cover.set_pixmap(pixmap)
self.cover.setScene(self.scene)
def refresh(self, row): def refresh(self, row):
if isinstance(row, QModelIndex): if isinstance(row, QModelIndex):

View File

@ -25,7 +25,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QGraphicsView" name="cover"/> <widget class="CoverView" name="cover"/>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -115,6 +115,11 @@
<extends>QWidget</extends> <extends>QWidget</extends>
<header>QtWebKit/QWebView</header> <header>QtWebKit/QWebView</header>
</customwidget> </customwidget>
<customwidget>
<class>CoverView</class>
<extends>QGraphicsView</extends>
<header>calibre/gui2/widgets.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -141,6 +141,15 @@ class Stack(QStackedWidget): # {{{
# }}} # }}}
class UpdateLabel(QLabel): # {{{
def __init__(self, *args, **kwargs):
QLabel.__init__(self, *args, **kwargs)
def contextMenuEvent(self, e):
pass
# }}}
class StatusBar(QStatusBar): # {{{ class StatusBar(QStatusBar): # {{{
def __init__(self, parent=None): def __init__(self, parent=None):
@ -148,7 +157,7 @@ class StatusBar(QStatusBar): # {{{
self.default_message = __appname__ + ' ' + _('version') + ' ' + \ self.default_message = __appname__ + ' ' + _('version') + ' ' + \
self.get_version() + ' ' + _('created by Kovid Goyal') self.get_version() + ' ' + _('created by Kovid Goyal')
self.device_string = '' self.device_string = ''
self.update_label = QLabel('') self.update_label = UpdateLabel('')
self.addPermanentWidget(self.update_label) self.addPermanentWidget(self.update_label)
self.update_label.setVisible(False) self.update_label.setVisible(False)
self._font = QFont() self._font = QFont()

View File

@ -11,9 +11,9 @@ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \
QPixmap, QSplitterHandle, QToolButton, \ QPixmap, QSplitterHandle, QToolButton, \
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \
QRegExp, QSettings, QSize, QSplitter, \ QRegExp, QSettings, QSize, QSplitter, \
QPainter, QLineEdit, QComboBox, QPen, \ QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, \
QMenu, QStringListModel, QCompleter, QStringList, \ QMenu, QStringListModel, QCompleter, QStringList, \
QTimer, QRect, QFontDatabase QTimer, QRect, QFontDatabase, QGraphicsView
from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
from calibre.gui2.filename_pattern_ui import Ui_Form from calibre.gui2.filename_pattern_ui import Ui_Form
@ -181,22 +181,16 @@ class FormatList(QListWidget):
else: else:
return QListWidget.keyPressEvent(self, event) return QListWidget.keyPressEvent(self, event)
class ImageDropMixin(object): # {{{
class ImageView(QWidget): '''
Adds support for dropping images onto widgets and a contect menu for
BORDER_WIDTH = 1 copy/pasting images.
cover_changed = pyqtSignal(object) '''
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self._pixmap = QPixmap(self)
self.setMinimumSize(QSize(150, 200))
self.setAcceptDrops(True)
self.draw_border = True
# Drag 'n drop {{{
DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS
def __init__(self):
self.setAcceptDrops(True)
@classmethod @classmethod
def paths_from_event(cls, event): def paths_from_event(cls, event):
''' '''
@ -223,14 +217,58 @@ class ImageView(QWidget):
pmap = QPixmap() pmap = QPixmap()
pmap.load(path) pmap.load(path)
if not pmap.isNull(): if not pmap.isNull():
self.setPixmap(pmap) self.handle_image_drop(path, pmap)
event.accept() event.accept()
self.cover_changed.emit(open(path, 'rb').read())
break break
def handle_image_drop(self, path, pmap):
self.set_pixmap(pmap)
self.cover_changed.emit(open(path, 'rb').read())
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
event.acceptProposedAction() event.acceptProposedAction()
# }}}
def get_pixmap(self):
return self.pixmap()
def set_pixmap(self, pmap):
self.setPixmap(pmap)
def contextMenuEvent(self, ev):
cm = QMenu(self)
copy = cm.addAction(_('Copy Image'))
paste = cm.addAction(_('Paste Image'))
if not QApplication.instance().clipboard().mimeData().hasImage():
paste.setEnabled(False)
copy.triggered.connect(self.copy_to_clipboard)
paste.triggered.connect(self.paste_from_clipboard)
cm.exec_(ev.globalPos())
def copy_to_clipboard(self):
QApplication.instance().clipboard().setPixmap(self.get_pixmap())
def paste_from_clipboard(self):
cb = QApplication.instance().clipboard()
pmap = cb.pixmap()
if pmap.isNull() and cb.supportsSelection():
pmap = cb.pixmap(cb.Selection)
if not pmap.isNull():
self.set_pixmap(pmap)
self.cover_changed.emit(
pixmap_to_data(pmap))
# }}}
class ImageView(QWidget, ImageDropMixin):
BORDER_WIDTH = 1
cover_changed = pyqtSignal(object)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self._pixmap = QPixmap(self)
self.setMinimumSize(QSize(150, 200))
ImageDropMixin.__init__(self)
self.draw_border = True
def setPixmap(self, pixmap): def setPixmap(self, pixmap):
if not isinstance(pixmap, QPixmap): if not isinstance(pixmap, QPixmap):
@ -272,32 +310,23 @@ class ImageView(QWidget):
p.drawRect(target) p.drawRect(target)
p.end() p.end()
class CoverView(QGraphicsView, ImageDropMixin):
# Clipboard copy/paste # {{{ cover_changed = pyqtSignal(object)
def contextMenuEvent(self, ev):
cm = QMenu(self)
copy = cm.addAction(_('Copy Image'))
paste = cm.addAction(_('Paste Image'))
if not QApplication.instance().clipboard().mimeData().hasImage():
paste.setEnabled(False)
copy.triggered.connect(self.copy_to_clipboard)
paste.triggered.connect(self.paste_from_clipboard)
cm.exec_(ev.globalPos())
def copy_to_clipboard(self): def __init__(self, *args, **kwargs):
QApplication.instance().clipboard().setPixmap(self.pixmap()) QGraphicsView.__init__(self, *args, **kwargs)
ImageDropMixin.__init__(self)
def paste_from_clipboard(self): def get_pixmap(self):
cb = QApplication.instance().clipboard() for item in self.scene().items():
pmap = cb.pixmap() if hasattr(item, 'pixmap'):
if pmap.isNull() and cb.supportsSelection(): return item.pixmap()
pmap = cb.pixmap(cb.Selection)
if not pmap.isNull():
self.setPixmap(pmap)
self.cover_changed.emit(
pixmap_to_data(pmap))
# }}}
def set_pixmap(self, pmap):
self.scene = QGraphicsScene()
self.scene.addPixmap(pmap)
self.setScene(self.scene)
class FontFamilyModel(QAbstractListModel): class FontFamilyModel(QAbstractListModel):

View File

@ -217,16 +217,16 @@ The following functions are available in addition to those described in single-f
* ``field(name)`` -- returns the metadata field named by ``name``. * ``field(name)`` -- returns the metadata field named by ``name``.
* ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are:: * ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are::
d : the day as number without a leading zero (1 to 31) d : the day as number without a leading zero (1 to 31)
dd : the day as number with a leading zero (01 to 31) ' dd : the day as number with a leading zero (01 to 31) '
ddd : the abbreviated localized day name (e.g. "Mon" to "Sun"). ' ddd : the abbreviated localized day name (e.g. "Mon" to "Sun"). '
dddd : the long localized day name (e.g. "Monday" to "Sunday"). ' dddd : the long localized day name (e.g. "Monday" to "Sunday"). '
M : the month as number without a leading zero (1 to 12). ' M : the month as number without a leading zero (1 to 12). '
MM : the month as number with a leading zero (01 to 12) ' MM : the month as number with a leading zero (01 to 12) '
MMM : the abbreviated localized month name (e.g. "Jan" to "Dec"). ' MMM : the abbreviated localized month name (e.g. "Jan" to "Dec"). '
MMMM : the long localized month name (e.g. "January" to "December"). ' MMMM : the long localized month name (e.g. "January" to "December"). '
yy : the year as two digit number (00 to 99). ' yy : the year as two digit number (00 to 99). '
yyyy : the year as four digit number.' yyyy : the year as four digit number.'
* ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables. * ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables.
* ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers. * ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers.