bookshelf.md 5.0 KB

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<BookObject>   // 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:

m_shelf = std::make_unique<BookShelf>(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.