Ensure that QPixmap objects are never deleted on non-GUI threads

This commit is contained in:
Kovid Goyal 2013-08-04 16:07:24 +05:30
parent 0541a118e6
commit a914e95d9f

View File

@ -10,7 +10,7 @@ import itertools, operator, os
from types import MethodType from types import MethodType
from time import time from time import time
from collections import OrderedDict from collections import OrderedDict
from threading import Lock, Event, Thread from threading import Lock, Event, Thread, current_thread
from Queue import Queue from Queue import Queue
from functools import wraps, partial from functools import wraps, partial
@ -276,17 +276,29 @@ class CoverCache(dict):
self.items = OrderedDict() self.items = OrderedDict()
self.lock = Lock() self.lock = Lock()
self.limit = limit self.limit = limit
self.pixmap_staging = []
self.gui_thread = current_thread()
def clear_staging(self):
' Must be called in the GUI thread '
self.pixmap_staging = []
def invalidate(self, book_id): def invalidate(self, book_id):
with self.lock: with self.lock:
self.items.pop(book_id, None) self._pop(book_id)
def _pop(self, book_id):
val = self.items.pop(book_id, None)
if type(val) is QPixmap and current_thread() is not self.gui_thread:
self.pixmap_staging.append(val)
def __getitem__(self, key): def __getitem__(self, key):
' Must be called in the GUI thread ' ' Must be called in the GUI thread '
with self.lock: with self.lock:
self.clear_staging()
ans = self.items.pop(key, False) # pop() so that item is moved to the top ans = self.items.pop(key, False) # pop() so that item is moved to the top
if ans is not False: if ans is not False:
if isinstance(ans, QImage): if type(ans) is QImage:
# Convert to QPixmap, since rendering QPixmap is much # Convert to QPixmap, since rendering QPixmap is much
# faster # faster
ans = QPixmap.fromImage(ans) ans = QPixmap.fromImage(ans)
@ -296,13 +308,16 @@ class CoverCache(dict):
def set(self, key, val): def set(self, key, val):
with self.lock: with self.lock:
self.items.pop(key, None) # pop() so that item is moved to the top self._pop(key) # pop() so that item is moved to the top
self.items[key] = val self.items[key] = val
if len(self.items) > self.limit: if len(self.items) > self.limit:
del self.items[next(self.items.iterkeys())] del self.items[next(self.items.iterkeys())]
def clear(self): def clear(self):
with self.lock: with self.lock:
if current_thread() is not self.gui_thread:
pixmaps = (x for x in self.items.itervalues() if type(x) is QPixmap)
self.pixmap_staging.extend(pixmaps)
self.items.clear() self.items.clear()
def __hash__(self): def __hash__(self):
@ -315,7 +330,7 @@ class CoverCache(dict):
extra = len(self.items) - self.limit extra = len(self.items) - self.limit
remove = tuple(self.iterkeys())[:extra] remove = tuple(self.iterkeys())[:extra]
for k in remove: for k in remove:
del self.items[k] self._pop(k)
class CoverDelegate(QStyledItemDelegate): class CoverDelegate(QStyledItemDelegate):
@ -471,6 +486,7 @@ class GridView(QListView):
self.update_item.emit(book_id) self.update_item.emit(book_id)
def re_render(self, book_id): def re_render(self, book_id):
self.delegate.cover_cache.clear_staging()
m = self.model() m = self.model()
try: try:
index = m.db.row(book_id) index = m.db.row(book_id)