diff options
| author | Joris Guyonvarch | 2025-12-29 17:35:51 +0100 |
|---|---|---|
| committer | Joris Guyonvarch | 2025-12-29 17:35:51 +0100 |
| commit | 31d87c86ce2a5f2a8f8aa1d404e39a093e975da7 (patch) | |
| tree | 3f5e7f20544c3c05137e58803ac24973346d458c /src | |
| parent | 64be629f81dc30f865d84958fc3f68a6217e6482 (diff) | |
Allow filtering by genre
Diffstat (limited to 'src')
| -rw-r--r-- | src/application.py | 45 | ||||
| -rw-r--r-- | src/book_flow.py | 52 | ||||
| -rw-r--r-- | src/book_form.py | 6 | ||||
| -rw-r--r-- | src/filters.py | 63 | ||||
| -rw-r--r-- | src/main_window.py | 71 | ||||
| -rw-r--r-- | src/models.py | 14 | ||||
| -rw-r--r-- | src/picture_cache.py | 3 |
7 files changed, 157 insertions, 97 deletions
diff --git a/src/application.py b/src/application.py index 46ab901..24c2a9a 100644 --- a/src/application.py +++ b/src/application.py @@ -2,51 +2,10 @@ # https://github.com/Taiko2k/GTK4PythonTutorial/blob/main/README.md#adding-your-custom-css-stylesheet import gi -gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -from gi.repository import Gtk, Adw +from gi.repository import Adw -from src.book_flow import BookFlow -from src.filters import Filters -from src.book_form import BookForm -import src.utils as utils - -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, resources, library, ereader, conn, *args, **kwargs): - super().__init__(*args, **kwargs) - - utils.set_header_bar(self, 'Books') - - scrolled_window = Gtk.ScrolledWindow() - self.set_child(scrolled_window) - - init_progress = 'Reading' - - add_book_button = Gtk.Button(label='Ajouter un livre') - add_book_button.connect('clicked', lambda _: BookForm(self, resources, library, conn, self._filters.get_progress(), self._on_book_added).present()) - - header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - utils.set_margin(header, 20) - self._filters = Filters(init_progress, self._update_book_flow_progress) - self._filters.set_hexpand(True) - header.append(self._filters) - header.append(add_book_button) - - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - box.append(header) - self._book_flow = BookFlow(self, resources, library, ereader, conn, init_progress, self._update_filters_progress) - box.append(self._book_flow) - scrolled_window.set_child(box) - - def _update_book_flow_progress(self, progress): - self._book_flow.update_progress(progress) - - def _update_filters_progress(self, progress): - self._filters.update_progress(progress) - - def _on_book_added(self, book_id, data): - self._update_filters_progress(data['progress']) - self._book_flow.add_book(book_id, data) +from src.main_window import MainWindow class Application(Adw.Application): def __init__(self, resources, library, ereader, conn, **kwargs): diff --git a/src/book_flow.py b/src/book_flow.py index ff77616..37f4866 100644 --- a/src/book_flow.py +++ b/src/book_flow.py @@ -9,6 +9,7 @@ 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 @@ -18,7 +19,7 @@ import src.book_files as book_files class BookFlow(Gtk.FlowBox): - def __init__(self, window, resources, library, ereader, conn, progress, update_filter_progress): + def __init__(self, window, resources, library, ereader, conn, books, progress, genre, msg): Gtk.FlowBox.__init__(self) self._window = window @@ -26,29 +27,23 @@ class BookFlow(Gtk.FlowBox): self._library = library self._ereader = ereader self._conn = conn - self._progress = progress - self._update_filter_progress = update_filter_progress + self._msg = msg self._picture_cache = PictureCache() - self._books = db.get_books(self._conn) - self._reset_view() + self.reset(books, progress, genre) - 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 select_book(self, book_id): + if book_id in self._flow_box_children: + self.select_child(self._flow_box_children[book_id]) - def update_progress(self, progress): - self._progress = progress - self._reset_view() + def reset(self, books, progress, genre, book_id=None): + if book_id: + self._picture_cache.invalidate(f'{self._library}/{book_id}/cover-min.png') - # Private - - 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: + for book_id, data in sorted(books.items(), key=book_sort): + if self._is_selected(data, progress, genre): picture = self._picture_cache.get(f'{self._library}/{book_id}/cover-min.png') picture.set_can_shrink(False) @@ -66,6 +61,17 @@ class BookFlow(Gtk.FlowBox): self._flow_box_children[book_id] = flow_box_child self.append(flow_box_child) + # Private + + def _is_selected(self, data, progress, genre): + if data['progress'] != progress: + return False + if genre == models.all_genres: + return True + if genre == models.no_genre: + return len(data['genres']) == 0 + return genre in data['genres'] + def _on_left_click(self, gesture, n_press, x, y, book_id, data): if n_press == 2: self._open_detail(book_id, data) @@ -103,23 +109,15 @@ class BookFlow(Gtk.FlowBox): 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]) + BookForm(self._window, self._resources, self._library, self._conn, data['progress'], 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): - 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() + 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() diff --git a/src/book_form.py b/src/book_form.py index cff4907..baabe58 100644 --- a/src/book_form.py +++ b/src/book_form.py @@ -13,13 +13,13 @@ import src.str_format as str_format class BookForm(Gtk.Window): - def __init__(self, parent_window, resources, library, conn, init_progress, on_book_saved, book = None): + def __init__(self, parent_window, resources, library, conn, init_progress, msg, book = None): Gtk.Window.__init__(self) self._book = book self._library = library self._conn = conn - self._on_book_saved = on_book_saved + self._msg = msg title = 'Modifier un livre' if book else 'Ajouter un livre' utils.configure_dialog(self, parent_window, title, height=800) @@ -125,7 +125,7 @@ class BookForm(Gtk.Window): book_id = book_store.store(self._library, self._conn, data, cover, books, self._book, self._remove_book_sources.get_active()) if book_id: self.close() - self._on_book_saved(book_id, data) + self._msg(['book-saved', book_id, data]) def non_empty(xs): return [x.strip() for x in xs if x.strip()] diff --git a/src/filters.py b/src/filters.py index f62eee1..e81c3cf 100644 --- a/src/filters.py +++ b/src/filters.py @@ -2,30 +2,47 @@ import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk -import src.utils as utils import src.models as models class Filters(Gtk.Box): - def __init__(self, init_progress, on_progress_updated): - Gtk.Box.__init__(self) - - self._progress = init_progress - self._on_progress_updated = on_progress_updated - - self._dropdown = Gtk.DropDown.new_from_strings(models.all_progress) - self._dropdown.set_selected(models.all_progress.index(init_progress)) - self._dropdown.connect('notify::selected-item', self._on_selected_item) - - self.append(self._dropdown) - - def get_progress(self): - return self._progress - - def update_progress(self, progress): - self._progress = progress - self._dropdown.set_selected(models.all_progress.index(progress)) - - def _on_selected_item(self, _dropdown, _data): - self._progress = self._dropdown.get_selected_item().get_string() - self._on_progress_updated(self._progress) + def __init__(self, init_books, progress, genres, genre, msg): + Gtk.Box.__init__(self, spacing=10) + + self._msg = msg + + self._progress_dropdown = Gtk.DropDown.new_from_strings(models.all_progress) + self._progress_dropdown.set_selected(models.all_progress.index(progress)) + self._progress_dropdown.connect('notify::selected-item', self._on_selected_progress) + self.append(self._progress_dropdown) + + self._genres_dropdown = self._get_genres_dropdown(genres, genre) + self.append(self._genres_dropdown) + + def select_progress(self, progress): + self._progress_dropdown.set_selected(models.all_progress.index(progress)) + + # Hacky, this replaces the dropdown by a new one + def actualize_genres(self, genres, genre): + if self._last_installed_genres != genres: + new_dropdown = self._get_genres_dropdown(genres, genre) + self.insert_child_after(new_dropdown, self._genres_dropdown) + self.remove(self._genres_dropdown) + self._genres_dropdown = new_dropdown + else: + self._genres_dropdown.set_selected(genres.index(genre)) + + def _get_genres_dropdown(self, genres, genre): + self._last_installed_genres = genres + dropdown = Gtk.DropDown.new_from_strings(genres) + dropdown.set_selected(genres.index(genre)) + dropdown.connect('notify::selected-item', self._on_selected_genre) + return dropdown + + def _on_selected_progress(self, dropdown, _data): + progress = dropdown.get_selected_item().get_string() + self._msg(['progress-selected', progress]) + + def _on_selected_genre(self, dropdown, _data): + genre = dropdown.get_selected_item().get_string() + self._msg(['genre-selected', genre]) diff --git a/src/main_window.py b/src/main_window.py new file mode 100644 index 0000000..420b4ea --- /dev/null +++ b/src/main_window.py @@ -0,0 +1,71 @@ +# To add CSS +# https://github.com/Taiko2k/GTK4PythonTutorial/blob/main/README.md#adding-your-custom-css-stylesheet + +import gi +gi.require_version('Gtk', '4.0') +from gi.repository import Gtk + +from src.book_flow import BookFlow +from src.filters import Filters +from src.book_form import BookForm +import src.utils as utils +import src.db as db +import src.models as models + +class MainWindow(Gtk.ApplicationWindow): + def __init__(self, resources, library, ereader, conn, *args, **kwargs): + super().__init__(*args, **kwargs) + + utils.set_header_bar(self, 'Books') + + # State + self._books = db.get_books(conn) + self._progress = 'Reading' + self._genres = models.get_genres(self._books) + self._genre = models.all_genres + + scrolled_window = Gtk.ScrolledWindow() + self.set_child(scrolled_window) + + add_book_button = Gtk.Button(label='Ajouter un livre') + add_book_button.connect('clicked', lambda _: BookForm(self, resources, library, conn, self._progress, self._msg).present()) + + header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + utils.set_margin(header, 20) + self._filters = Filters(self._books, self._progress, self._genres, self._genre, self._msg) + self._filters.set_hexpand(True) + header.append(self._filters) + header.append(add_book_button) + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + box.append(header) + self._book_flow = BookFlow(self, resources, library, ereader, conn, self._books, self._progress, self._genre, self._msg) + box.append(self._book_flow) + scrolled_window.set_child(box) + + def _msg(self, msg): + match msg: + case ['progress-selected', progress]: + self._progress = progress + self._book_flow.reset(self._books, self._progress, self._genre) + case ['genre-selected', genre]: + self._genre = genre + self._book_flow.reset(self._books, self._progress, self._genre) + case ['book-saved', book_id, data]: + self._books[book_id] = data + self._progress = data['progress'] + self._filters.select_progress(data['progress']) + if not self._genre in data['genres']: + self._genre = models.all_genres + self._actualize_genres() + self._book_flow.reset(self._books, self._progress, self._genre, book_id) + self._book_flow.select_book(book_id) + case ['book-deleted', book_id, data]: + del self._books[book_id] + self._actualize_genres() + self._book_flow.reset(self._books, self._progress, self._genre) + + def _actualize_genres(self): + self._genres = models.get_genres(self._books) + self._genre = self._genre if self._genre in self._genres else models.all_genres + self._filters.actualize_genres(self._genres, self._genre) diff --git a/src/models.py b/src/models.py index d271b62..0297551 100644 --- a/src/models.py +++ b/src/models.py @@ -1,2 +1,16 @@ all_progress = ['Unread', 'Reading', 'Read', 'Stopped'] langs = ['fr', 'en', 'de'] + +# Genres + +all_genres = 'Tous les genres' +no_genre = 'Aucun genre' + +def get_genres(books): + genres = {all_genres} + for data in books.values(): + for genre in data['genres']: + genres.add(genre) + if len(data['genres']) == 0: + genres.add(no_genre) + return list(genres) diff --git a/src/picture_cache.py b/src/picture_cache.py index 8da136b..af4e1bb 100644 --- a/src/picture_cache.py +++ b/src/picture_cache.py @@ -22,4 +22,5 @@ class PictureCache: def invalidate(self, path): logger.debug('Invalidating: %s', path) - del self._cache[path] + if path in self._cache: + del self._cache[path] |
