# BookShelf Module Overview The `BookShelf` module is the bridge between the data model (`BookList`) and the visual grid of book tiles. It packages a full GTK widget tree that can be embedded into any window: the widget takes care of subscribing to model updates, populating the `Gtk::GridView`, handling selection state, and exposing activation + filtering hooks back to the caller. ## Responsibilities - Mirror the in-memory book collection into a `Gio::ListStore` suitable for a `Gtk::GridView`. - Build and bind `BookTile` widgets on demand using a `Gtk::SignalListItemFactory`. - Provide user interaction glue: - Emits `signalBookActivated()` when a tile is clicked. - Tracks selection state via `Gtk::SingleSelection`. - Clears selection when the user clicks outside the grid or moves the mouse into empty space. - Implement a lightweight fuzzy filter so the grid only shows matches for the active query. ## Structure ``` BookShelf ├── Gio::ListStore // backing data model ├── Gtk::SingleSelection // selection controller passed to the grid ├── Gtk::GridView // renders BookTile cells ├── Gtk::SignalListItemFactory // setup / bind / unbind callbacks ├── BookObject // small wrapper storing Book for the model └── Filtering helpers // scoring & refresh logic ``` ### BookObject wrapper `Gio::ListStore` expects items that derive from `Glib::Object`. `BookObject` wraps a `Book` so we can put books inside the GTK list store while still using value semantics when refreshing the grid. ## Data flow 1. Construction: - `BookShelf` creates the list store, selection model, signal factory, and sets up the GridView. - Background gestures/controllers are added so clicking empty space clears the selection. 2. Synchronisation: - `reload()`, `addTile()`, `updateTile()`, and `removeTile()` all forward to `refreshVisible()`, which rebuilds the list store from `BookList` (using the current filter, if any). - `BookList` signals are connected to these entry points in the constructor so UI stays in sync with database changes. 3. Rendering: - `onFactorySetup()` creates a `BookTile` and stores it in each `Gtk::ListItem`. - `onFactoryBind()` pulls the corresponding `BookObject` from the store and feeds it into the tile via `BookTile::setBook()`. - `onFactoryUnbind()` resets the tile to a blank Book so stale metadata isn’t shown when the item is recycled. 4. Interaction: - When the grid emits `signal_activate(position)`, the selection model is updated and `signalBookActivated()` is forwarded to the outside world. - Background gestures reset the selection so keyboard focus isn’t stuck. ## Filtering algorithm `setFilter(query)` stores a lowercase copy of the query and calls `refreshVisible()`. The refresh routine: 1. Clears the current selection and list store. 2. If the query is empty, re-populates the grid with every book. 3. Otherwise evaluates a fuzzy score for each book using: - substring match (higher score for earlier matches), and - subsequence match (reward tightly clustered characters). 4. Keeps only books with a non-negative score, sorts descending by score, and repopulates the store so the grid shows the ranked results. The helper functions `ascii_lower`, `substring_score`, `subsequence_score`, and `book_score` live inside the module to keep the fuzzy logic confined. ### Tag-based search The filtering supports a special `tag:` prefix syntax for tag filtering: - `tag:fiction` - Show only books tagged with "fiction" (or tags containing "fiction") - Regular search terms also match against tags in fuzzy mode The `has_tag_prefix()` helper detects the `tag:` prefix and switches to exact tag filtering mode, where only books with matching tags are shown. ## Selection API The module provides methods for querying and controlling selection: | Method | Description | |---------------------|--------------------------------------------------| | `getSelectedBook()` | Returns the currently selected book, if any | | `clearSelection()` | Deselects any selected book | These are used by `BibliothecaWindow` for keyboard navigation (Enter to open selected book). ## Extension points - **Custom tile content**: tweak `BookTile` (e.g., add badges or context menus); the factory setup already exposes the widget instance. - **Filtering strategy**: adjust scoring or add new fields (tags, notes) inside `book_score()`. - **Selection behaviour**: change the background event controllers if you want multi-select or persistent selection. ## Expected usage The module is designed to be embedded like so: ```cpp m_shelf = std::make_unique(m_bookList, 180); m_shelf->signalBookActivated().connect(...); container.append(*m_shelf); ``` Call `setFilter(query)` from your search UI, and `reload()` if you ever perform bulk operations that bypass the standard BookList signals.