import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk, Gio import glob import shutil import functools import os import subprocess import src.utils as utils import src.db as db from src.book_detail import BookDetail from src.book_delete import BookDelete from src.book_form import BookForm from src.book_transfer import BookTransfer from src.picture_cache import PictureCache import src.book_files as book_files class BookFlow(Gtk.FlowBox): def __init__(self, window, resources, library, ereader, conn, progress, update_filter_progress): Gtk.FlowBox.__init__(self) self._window = window self._resources = resources self._library = library self._ereader = ereader self._conn = conn self._progress = progress self._update_filter_progress = update_filter_progress self._picture_cache = PictureCache() self._load_books() def add_book(self, book_id, data): self._books[book_id] = data self.update_progress(data['progress']) self.select_child(self._flow_box_children[book_id]) def update_progress(self, progress): self._progress = progress self._reset_view() # Private def _load_books(self): """Get books from DB, but most importantly load covers to memory.""" self._books = db.get_books(self._conn) for book_id in self._books.keys(): _ = self._picture_cache.get(f'{self._library}/{book_id}/cover-min.png') self._reset_view() def _reset_view(self): self.remove_all() self._flow_box_children = {} for book_id, data in sorted(self._books.items(), key=book_sort): if data['progress'] == self._progress: picture = self._picture_cache.get(f'{self._library}/{book_id}/cover-min.png') picture.set_can_shrink(False) gesture_lclick = Gtk.GestureClick() gesture_lclick.connect('released', self._on_left_click, book_id, data) picture.add_controller(gesture_lclick) gesture_rclick = Gtk.GestureClick() gesture_rclick.set_button(3) gesture_rclick.connect('released', self._on_right_click, picture, book_id, data) picture.add_controller(gesture_rclick) flow_box_child = Gtk.FlowBoxChild() flow_box_child.set_child(picture) self._flow_box_children[book_id] = flow_box_child self.append(flow_box_child) def _on_left_click(self, gesture, n_press, x, y, book_id, data): if n_press == 2: self._open_detail(book_id, data) def _on_right_click(self, gesture, n_press, x, y, picture, book_id, data): if n_press == 1: popover = Gtk.Popover() popover.set_parent(picture) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) popover.set_child(box) see_button = Gtk.Button(label='Voir') see_button.connect('clicked', lambda _: self._open_detail(book_id, data)) box.append(see_button) update_button = Gtk.Button(label='Modifier') update_button.connect('clicked', lambda _: self._update_book(book_id, data)) box.append(update_button) delete_button = Gtk.Button(label='Supprimer') delete_button.connect('clicked', lambda _: self._confirm_delete_book(book_id, data)) box.append(delete_button) if self._ereader and os.path.exists(self._ereader): paths = book_files.get(self._library, book_id) if paths: transfer_button = Gtk.Button(label='Transférer') transfer_button.connect('clicked', lambda _: self._transfer_book(book_id, data, paths)) box.append(transfer_button) popover.popup() def _open_detail(self, book_id, data): BookDetail(self._window, self._library, book_id, data).present() def _update_book(self, book_id, data): book = {'id': book_id, 'data': data } BookForm(self._window, self._resources, self._library, self._conn, data['progress'], self._on_book_updated, book).present() def _on_book_updated(self, book_id, data): self._picture_cache.invalidate(f'{self._library}/{book_id}/cover-min.png') self._books[book_id] = data self.update_progress(data['progress']) # Note: this redraws everything, this is overkill in some cases self._update_filter_progress(data['progress']) self.select_child(self._flow_box_children[book_id]) def _confirm_delete_book(self, book_id, data): BookDelete(self._window, self._library, book_id, data, lambda: self._delete_book(book_id, data)).present() def _delete_book(self, book_id, data): del self._books[book_id] db.delete_book(self._conn, book_id) shutil.rmtree(f'{self._library}/{book_id}', ignore_errors=True) self._reset_view() def _transfer_book(self, book_id, data, paths): BookTransfer(self._window, self._ereader, book_id, data, paths).present() def book_sort(b): key, data = b author = author_key(data) year = data['year'] if 'year' in data else '' return f'{author}{year}' def author_key(data): match data['authors']: case [author, *_]: return author.split()[-1] return ''