Fixed #549 and refactored the image tooltip also

Issue #549 was caused because the request and reply object urls are not
guaranteed to be the same. Redirects are the most common cause, but a
malformed URL apparently also qualifies. We now make sure to look at the
original request.

Because the code confused me while I was working on it, I decided to
refactor and document it in order to understand what was going on. I am
glad I did: I found another crashing bug involving the rapid-firing of
tooltip requests, and the processing dict never had its entries removed
either, leading to a (very slow) memory leak over time.

All is good in the world of image tooltips now.
This commit is contained in:
Jan Wester 2019-04-21 12:09:30 +02:00 committed by Curtis Gedak
parent 2b4943c36b
commit 0238ccec7b

View file

@ -527,7 +527,7 @@ class MDEditView(textEditView):
+"<p><img src='data:image/png;base64,{}'></p>") +"<p><img src='data:image/png;base64,{}'></p>")
tooltip = None tooltip = None
pos = event.pos() + QPoint(0, ct.rect.height()) pos = event.pos() + QPoint(0, ct.rect.height())
imageTooltiper.fromUrl(ct.texts[2], pos, self) ImageTooltip.fromUrl(ct.texts[2], pos, self)
elif ct.regex == self.inlineLinkRegex: elif ct.regex == self.inlineLinkRegex:
tooltip = ct.texts[1] or ct.texts[2] tooltip = ct.texts[1] or ct.texts[2]
@ -582,53 +582,85 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkRepl
from PyQt5.QtCore import QIODevice, QUrl, QBuffer from PyQt5.QtCore import QIODevice, QUrl, QBuffer
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap
class imageTooltiper: class ImageTooltip:
"""
This class handles the retrieving and caching of images in order to display these in tooltips.
"""
cache = {} cache = {}
manager = QNetworkAccessManager() manager = QNetworkAccessManager()
data = {} processing = {}
def fromUrl(url, pos, editor): def fromUrl(url, pos, editor):
cache = imageTooltiper.cache """
imageTooltiper.editor = editor Shows the image tooltip for the given url if available, or requests it for future use.
"""
ImageTooltip.editor = editor
if url in cache: if ImageTooltip.showTooltip(url, pos):
if not cache[url][0]: # error, image was not found return # the url already exists in the cache
imageTooltiper.tooltipError(cache[url][1], pos)
else:
imageTooltiper.tooltip(cache[url][1], pos)
return
try: try:
imageTooltiper.manager.finished.connect(imageTooltiper.finished, F.AUC) ImageTooltip.manager.finished.connect(ImageTooltip.finished, F.AUC)
except: except:
pass pass # already connected
request = QNetworkRequest(QUrl(url)) qurl = QUrl(url)
imageTooltiper.data[QUrl(url)] = (pos, url) if (qurl in ImageTooltip.processing):
imageTooltiper.manager.get(request) return # one download is more than enough
# Request the image for later processing.
request = QNetworkRequest(qurl)
ImageTooltip.processing[qurl] = (pos, url)
ImageTooltip.manager.get(request)
def finished(reply): def finished(reply):
cache = imageTooltiper.cache """
pos, url = imageTooltiper.data[reply.url()] After retrieving an image, we add it to the cache.
"""
cache = ImageTooltip.cache
# Update cache with retrieved data.
pos, url = ImageTooltip.processing[reply.request().url()]
if reply.error() != QNetworkReply.NoError: if reply.error() != QNetworkReply.NoError:
cache[url] = (False, reply.errorString()) cache[url] = (False, reply.errorString())
imageTooltiper.tooltipError(reply.errorString(), pos)
else: else:
px = QPixmap() px = QPixmap()
px.loadFromData(reply.readAll()) px.loadFromData(reply.readAll())
px = px.scaled(800, 600, Qt.KeepAspectRatio) px = px.scaled(800, 600, Qt.KeepAspectRatio)
cache[url] = (True, px) cache[url] = (True, px)
imageTooltiper.tooltip(px, pos) del ImageTooltip.processing[reply.request().url()]
ImageTooltip.showTooltip(url, pos)
def showTooltip(url, pos):
"""
Show a tooltip for the given url based on cached information.
"""
cache = ImageTooltip.cache
if url in cache:
if not cache[url][0]: # error, image was not found
ImageTooltip.tooltipError(cache[url][1], pos)
else:
ImageTooltip.tooltip(cache[url][1], pos)
return True
return False
def tooltipError(message, pos): def tooltipError(message, pos):
imageTooltiper.editor.doTooltip(pos, message) """
Display a tooltip with an error message at the given position.
"""
ImageTooltip.editor.doTooltip(pos, message)
def tooltip(image, pos): def tooltip(image, pos):
"""
Display a tooltip with an image at the given position.
"""
px = image px = image
buffer = QBuffer() buffer = QBuffer()
buffer.open(QIODevice.WriteOnly) buffer.open(QIODevice.WriteOnly)
px.save(buffer, "PNG", quality=100) px.save(buffer, "PNG", quality=100)
image = bytes(buffer.data().toBase64()).decode() image = bytes(buffer.data().toBase64()).decode()
tt = "<p><img src='data:image/png;base64,{}'></p>".format(image) tt = "<p><img src='data:image/png;base64,{}'></p>".format(image)
imageTooltiper.editor.doTooltip(pos, tt) ImageTooltip.editor.doTooltip(pos, tt)