diff --git a/src/gourmet/image_utils.py b/src/gourmet/image_utils.py index 50e07e18..b812d754 100644 --- a/src/gourmet/image_utils.py +++ b/src/gourmet/image_utils.py @@ -3,6 +3,7 @@ from enum import Enum from pathlib import Path from pkgutil import get_data +from threading import Event, Thread from typing import Dict, List, Optional from urllib.parse import unquote, urlparse @@ -121,34 +122,54 @@ def image_to_pixbuf(image: Image.Image) -> Pixbuf: class ImageBrowser(Gtk.Dialog): def __init__(self, parent: Gtk.Window, uris: List[str]): + """Retrieve the images from the uris and let user select one. + + Image retrieval is done in another thread which is cancelled when the + dialog is destroyed, if not completed already. + """ Gtk.Dialog.__init__(self, title="Choose an image", transient_for=parent, flags=0) self.set_default_size(600, 600) - self.image: Image.Image = None - + self.image: Optional[Image.Image] = None self.liststore = Gtk.ListStore(GdkPixbuf.Pixbuf) + iconview = Gtk.IconView.new() iconview.set_model(self.liststore) iconview.set_pixbuf_column(0) + iconview.connect('selection-changed', self.on_selection) + + scrollable = Gtk.ScrolledWindow() + scrollable.set_vexpand(True) + scrollable.add(iconview) + + box = self.get_content_area() + box.pack_end(scrollable, True, True, 0) + self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, Gtk.ResponseType.OK) + self.show_all() + self._stop_retrieval = Event() + self._image_retrieve_task = Thread(target=self._load_uris, args=[uris]) + self._image_retrieve_task.start() + + def _load_uris(self, uris: List[str]): for uri in uris: + if self._stop_retrieval.is_set(): + return # Cancel retrieval of image as user selection is done. image = make_thumbnail(uri, ThumbnailSize.SMALL) if image is None: continue pixbuf = bytes_to_pixbuf(image_to_bytes(image)) self.liststore.append([pixbuf]) - iconview.connect('selection-changed', self.on_selection) - - box = self.get_content_area() - box.pack_end(iconview, True, True, 0) - self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_OK, Gtk.ResponseType.OK) - self.show_all() - def on_selection(self, iconview: Gtk.IconView): item = iconview.get_selected_items() if item: itr = self.liststore.get_iter(item[0]) self.image = pixbuf_to_image(self.liststore.get_value(itr, 0)) + + def destroy(self): + self._stop_retrieval.set() + self._image_retrieve_task.join() + super().destroy() diff --git a/src/gourmet/importers/interactive_importer.py b/src/gourmet/importers/interactive_importer.py index 257183ce..79416904 100644 --- a/src/gourmet/importers/interactive_importer.py +++ b/src/gourmet/importers/interactive_importer.py @@ -461,13 +461,13 @@ def commit_changes(self): for rec in self.added_recs: browser = ImageBrowser(self.w, self.images) response = browser.run() + browser.destroy() if response == Gtk.ResponseType.OK: thumb = browser.image.copy() thumb.thumbnail((40, 40)) self.rd.modify_rec(rec, {'image': image_to_bytes(browser.image), 'thumb': image_to_bytes(thumb)}) - browser.destroy() if self.modal: self.w.hide()