// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
    \example models/threadedsonglist
    \title Models and Views: List Model using a worker thread for data fetching
    \brief Demonstrates how to implement a list model with a responsive UI
    using a worker thread to fetch data.
    \examplecategory {User Interface Components}
    \examplecategory {Data Processing & I/O}
    \meta {tag} {quick,threads}
    \image qml-threadedsonglist-example.webp {Screenshot of the application
    where a songlist with album arts, song names, artist names and album names
    are visible}

    This example introduces a custom item model, inheriting QAbstractListModel.
    The model gets its data from a worker object that is in separate QThread,
    fetching data from a slow data source.

    \section1 Overview of the Threaded Song List example

    The data source simulates a slow data source by adding a delay of 100
    milliseconds per song fetched from it. This means that loading of the
    entire list of 3600 songs would take 6 minutes, making the opening of the
    application impractical. This delay is mitigated by fetching the data only
    for the visible area of the view, using a QObject placed into a worker
    thread.

    The worker object has a limit for the number of fetch requests it holds in
    queue. This ensures that only the elements of the currently visible part of
    the song list are fetched, removing the need to wait for the non-visible
    part of the list to load, when user has already scrolled past some part of
    the list.

    Focus of this example is in the source model of the view. The view itself
    is an unmodified QML ListView with a simple delegate. The use of thread is
    hidden behind the implementation of model data handling and the ListView
    does not need to have any customization to be able to adapt to the
    thread-based model.

    Also since the focus is in the model, the Qt Quick Controls is set to use
    Universal style on all platforms to ensure identical UI behavior.

    \snippet models/threadedsonglist/ThreadedSongList.qml Fixed Controls style

    \section1 How it works

    The business logic of providing the song list data is separated into
    \c DataStorage class that provides a simple ID-based interface for the model.

    \snippet models/threadedsonglist/datastorage.h Interface towards the model

    When model requests data from the DataStorage, the storage will first check
    if it has the data already available. If it does, data is returned
    instantly, as would be the case in a non-threaded model. In case the data
    is not found, DataStorage will emit a \c dataFetchNeeded() signal to the
    worker object and add an empty item to the list of already existing data.
    Adding the empty item ensures that no further signals are sent to the
    worker for the same list item.

    \snippet models/threadedsonglist/datastorage.cpp Send signal if no data

    \c QueueWorker - the worker thread object - processes the dataFetchNeeded()
    signals it has received by sending a signal to itself, which makes it
    possible to receive all signals already in QEventQueue before starting the
    slow data read operation.

    \section1 Applying the approach to dynamic models

    If one wishes to expand the solution towards a case where items may be
    added, moved or removed from the data source (in this case RemoteMedia),
    DataStorage needs to be updated with signals to match
    QAbstractItemModel::rowsMoved(), QAbstractItemModel::rowsInserted() and
    two signals to trigger the QAbstractItemModel::beginRemoveRows() and
    QAbstractItemModel::endRemoveRows() inside ThreadedListModel.

    For the insertion and move the ThreadedListModel can just call
    QAbstractItemModel::beginInsertRows(), then add new IDs to its ID list and
    call QAbstractItemModel::endInsertRows(). As ThreadedListModel holds a copy
    of the ID list and accesses storage by ID, there is no need to signal the
    begin and end from storage. Equally ThreadedListModel can call
    QAbstractItemModel::beginMoveRows(), move IDs in its ID list and then call
    QAbstractItemModel::endMoveRows().

    Removal is a slightly more complex case. The view needs to have a
    possibility to request the data that is going to be removed before it is
    actually removed. So DataStorage needs to signal a warning of the removal,
    causing the Model to call QAbstractItemModel::beginRemoveRows(). At this
    stage ThreadedListModel may get one or more \c data() calls. Once the call
    to direct connected signal returns at DataStorage, it is OK for DataStorage
    to remove the item and then signal the model again with another signal that
    triggers the model to call QAbstractItemModel::endRemoveRows().

    \include examples-run.qdocinc

*/
