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 import src.models as models 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 import src.str_format as str_format class BookFlow(Gtk.ScrolledWindow): def __init__(self, window, resources, library, ereader, conn, books, progress, genre, search, msg): Gtk.ScrolledWindow.__init__(self) self.set_vexpand(True) self._flowbox = Gtk.FlowBox() self.set_child(self._flowbox) self._books = books self._window = window self._resources = resources self._library = library self._ereader = ereader self._conn = conn self._msg = msg self._picture_cache = PictureCache() self._flowbox_children = {} self.update_filters(progress, genre, search) self._flowbox.set_sort_func(self._sort_books) for book_id, data in books.items(): self.add(book_id, data) def get(self): return self._books def select_book(self, book_id): if book_id in self._flowbox_children: self._flowbox.select_child(self._flowbox_children[book_id]) def update_filters(self, progress, genre, search): def f(flow_box_child): book_id = flow_box_child.get_name() data = self._books[book_id] return self._is_selected(data, progress, genre, search) self._flowbox.set_filter_func(f) def add(self, book_id, data): self._books[book_id] = data picture = self._picture_cache.get(f'{self._library}/{book_id}/cover-min.png') picture.set_can_shrink(False) utils.set_margin(picture, 10) 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) flow_box_child.set_name(book_id) self._flowbox_children[book_id] = flow_box_child self._flowbox.append(flow_box_child) def remove(self, book_id): del self._books[book_id] self._flowbox.remove(self._flowbox_children[book_id]) del self._flowbox_children[book_id] self._picture_cache.invalidate(f'{self._library}/{book_id}/cover-min.png') # Private def _sort_books(self, child1, child2): book_id1 = child1.get_name() book_id2 = child2.get_name() data1 = self._books[book_id1] data2 = self._books[book_id2] author1 = author_key(data1) author2 = author_key(data2) if author1 < author2: return -1 elif author1 > author2: return 1 year1 = year_key(data1) year2 = year_key(data2) if year1 < year2: return -1 elif year1 > year2: return 1 return 0 def _is_selected(self, data, progress, genre, search): if data['progress'] != progress: return False if genre == models.no_genre and len(data['genres']) > 0: return False if genre != models.all_genres and not genre in data['genres']: return False return match_search(data, search) 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, self._msg, book).present() 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): db.delete_book(self._conn, book_id) shutil.rmtree(f'{self._library}/{book_id}', ignore_errors=True) self._msg(['book-deleted', book_id, data]) def _transfer_book(self, book_id, data, paths): BookTransfer(self._window, self._ereader, book_id, data, paths).present() def author_key(data): match data['authors']: case [author, *_]: return author.split()[-1].lower() return '' def year_key(data): if 'year' in data: try: return int(data['year']) except Exception: return 0 return 0 def match_search(data, search): for word in search.split(): if not match_word(data, word): return False return True def match_word(data, word): s = str_format.for_search(word) if s in str_format.for_search(data['title']): return True if 'subtitle' in data and s in str_format.for_search(data['subtitle']): return True for author in data['authors']: if s in str_format.for_search(author): return True if 'year' in data and s == str(data['year']): return True return False