13.6. Exercises: Models and Views

[ fromfile: ex-modelview.xml id: ex-modelview ]

  1. Write a file system browser, with an address bar, Up button, and optionally other commonly found buttons and features of your favorite file browser. Use the QFileSystemModel and at least two View classes separated by a QSplitter. One of the Views should be a QTableView. You can choose a Windows Explorer-style tree on the side of the table, or a Mac OSX-style browser with a QColumnView and a table, as shown in Figure 13.14. Make it possible to select a directory in the tree/columnview, addressbar, or with the Up button, so that any of them will update the contents of the table to reflect the newly selected directory.

    Figure 13.14.  ColumnView File Browser

    ColumnView File Browser

      See solution/modelview/filebrowser.

  2. Extend QAbstractTableModel and define a PlayListModel. It should represent a list of MetaDataValue (or MetaDataObject) objects. These can be based on audio tracks or videos, your choice.

    Generate and display test data either based on real media files and MetaDataLoader, or using your own test data/factory method. Implement actions for load/save playlist to disk.

      solution/modelview/tablemodel shows an example of how to do it reusing the dataobjecttablemodel and metadataloader classes, where the sample data is based on actual id3 tags.

  3. Revisiting the program in Section 11.6, implement a GUI for showing friendslists, or a bidirectional relationship between symbols. The GUI should show two QListViews or QListWidgets, like Figure 13.15.

    Figure 13.15.  Friends Lists

    Friends Lists


    • Both lists should show the set of symbols available.

    • Click add to add a new symbol to both lists.

    • When a symbol is selected on the left, the right list shows the friends checked and the strangers unchecked.

    • Checking/unchecking a box on the right adds/removes a relationship between the two people.

    • Do not allow the user to uncheck a contact if it is the same as the selected one on the left. Always show it as checked.

     

    It's a lot easier with a proxy model!

    Example 13.26. solution/modelview/friendslist-gui/friendslistmodel.h

    #ifndef FRIENDLISTMODEL_H
    #define FRIENDLISTMODEL_H
    
    #include <QAbstractListModel>
    #include <QVariant>
    #include <QMultiMap>
    #include <QString>
    #include <QStringList>
    class FriendsList : public QMultiMap<QString, QString> {
    public:
        QStringList friends(QString person) ;
        bool friendsWith(QString a, QString b) const;
    };
    class FriendsListModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        explicit FriendsListModel(FriendsList& fl, QObject *parent = 0);
        int rowCount(const QModelIndex &parent = QModelIndex()) const;
        QVariant data(const QModelIndex &index, int role) const;
        bool setData(const QModelIndex &index, const QVariant &value, int role);
        Qt::ItemFlags flags(const QModelIndex &index) const;
        QModelIndex indexOf(QString key) ;
    
        void add(QString k);    
        void set(QString k1, QString k2);
        void unset(QString k1, QString k2);
    
    public slots:
        void contactSelected(QModelIndex idx);
    
    protected:
        QString m_contact;
        FriendsList& m_friendsList;
        QStringList m_allPeople;
    };
    
    #endif // FRIENDLISTMODEL_H
    

    <include src="solution/modelview/friendslist-gui/friendslistmodel.h" href="solution/modelview/friendslist-gui/friendslistmodel.h" role="solution" mode="cpp"/>


    Example 13.27. solution/modelview/friendslist-gui/friendslistmodel.cpp

    #include "friendslistmodel.h"
    
    QStringList FriendsList::friends(QString key)  {
        QStringList retval;
        const_iterator itr = constFind(key);
        while (itr != constEnd()) {
            retval << itr.value();
            itr++;
        }
        return retval;
    }
    
    bool FriendsList::friendsWith(QString a, QString b) const {
        if (a == b) return true;
        return constFind(a, b) != constEnd();
    }
    
    FriendsListModel::FriendsListModel(FriendsList& fl, QObject *parent)
        : QAbstractListModel(parent), m_friendsList(fl) {
        // updateAllPeople();
    }
    
    Qt::ItemFlags FriendsListModel::flags(const QModelIndex &index) const {
        int r = index.row();
        if (r >= rowCount()) return 0;
        QString d = m_allPeople[r];
        
        if (d == m_contact || m_contact == QString()) 
            return 0; 1  
        return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    }
    
    void FriendsListModel::add(QString k) {
        int pos = 0;
        if (m_allPeople.contains(k)) {
            QMessageBox::critical(0, "Duplicate Person", "Contact already exists");
            return;
        }
        if (k == QString()) return;
        for (int i = m_allPeople.length() - 1; i >= 0; --i) {
            if (k >= m_allPeople[i]) {pos = i+1; break;}
        }
        if (pos >= rowCount()) pos = rowCount();
        if (pos < 0) pos = 0;
        beginInsertRows(QModelIndex(), pos, pos);
        m_allPeople << k;
        m_allPeople.sort();
        set(k,k);
        endInsertRows();
    }
    
    
    int FriendsListModel::rowCount(const QModelIndex &parent) const {
        if (parent.isValid()) return 0;
        return m_allPeople.count();
    }
    
    QVariant FriendsListModel::data(const QModelIndex &index, int role) const {
        int r = index.row();
        if (r >= rowCount()) return QVariant();
        QString d = m_allPeople[index.row()];
        if ((role == Qt::DisplayRole) || (role == Qt::EditRole))
            return d;
        if ((role == Qt::CheckStateRole)) {
            return m_friendsList.friendsWith(d, m_contact);
        }
        return QVariant();
    }
    
    bool FriendsListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
        if (role == Qt::CheckStateRole) { 2
            QString d = m_allPeople[index.row()];
            if (d == m_contact || m_contact == QString()) return false;
            bool isFriends = m_friendsList.friendsWith(d, m_contact);
            if (!isFriends) 
                set(m_contact, d);
            else 
                unset(m_contact, d);
            return true;
        }
        return false;
    }
    
    
    void FriendsListModel::contactSelected(QModelIndex idx) {
        int r = idx.row();
        QString old = m_contact;
        if (idx.isValid()) {
            m_contact = m_allPeople[r];
        }
        else m_contact = QString();
        if (old != m_contact) {
            emit dataChanged(createIndex(0,0), createIndex(rowCount(), 0));
        }
    }
    
    
    QModelIndex FriendsListModel::indexOf(QString key) {
        int r = m_allPeople.indexOf(key);
        return createIndex(r, 0);
    }
    
    void FriendsListModel::set(QString k1, QString k2) {
        m_friendsList.insert (k1, k2);
        m_friendsList.insert (k2, k1);
        int index1 = m_allPeople.indexOf(k1);
        int index2 = m_allPeople.indexOf(k2);
        emit dataChanged(createIndex(index1,0),createIndex(index1,0));
        emit dataChanged(createIndex(index2,0),createIndex(index2,0));
    }
    
    void FriendsListModel::unset(QString k1, QString k2) {
        if (k1 == k2) return;
        m_friendsList.remove(k1, k2);
        m_friendsList.remove(k2, k1);
        int index1 = m_allPeople.indexOf(k1);
        int index2 = m_allPeople.indexOf(k2);
        emit dataChanged(createIndex(index1,0),createIndex(index1,0));
        emit dataChanged(createIndex(index2,0),createIndex(index2,0));
    }
    
    

    1

    Don't let user interact with this item unless a different contact is selected on the left.

    2

    The item is not editable but we go here if the user tries to check an item that is ItemIsUserCheckable.

    <include src="solution/modelview/friendslist-gui/friendslistmodel.cpp" href="solution/modelview/friendslist-gui/friendslistmodel.cpp" role="solution" mode="cpp"/>


    Example 13.28. solution/modelview/friendslist-gui/friendslists.h

    #ifndef FRIENDSLISTS_H
    #define FRIENDSLISTS_H
    
    #include <QDialog>
    #include <QModelIndex>
    #include "friendslistmodel.h"
    #include "contactproxymodel.h"
    
    namespace Ui {
        class FriendsLists;
    }
    class FriendsLists : public QDialog
    {
        Q_OBJECT
    public:
        explicit FriendsLists(QWidget *parent = 0);
        ~FriendsLists();
    protected:
        void changeEvent(QEvent *e);
    private slots:
        void on_addButton_clicked();
    private:
        QString m_lastSelected;
        Ui::FriendsLists *ui;
        FriendsList m_friendsList;
        // list on right:
        FriendsListModel *m_cmodel;
        // list on left:
        ContactProxyModel *m_cpmodel;
    
    };
    
    #endif // FRIENDSLISTS_H
    

    <include src="solution/modelview/friendslist-gui/friendslists.h" href="solution/modelview/friendslist-gui/friendslists.h" role="solution" mode="cpp"/>


    Example 13.29. solution/modelview/friendslist-gui/friendslists.cpp

    #include "friendslistmodel.h"
    #include "friendslists.h"
    #include "ui_friendslists.h"
    #include <QtGui>
    
    FriendsLists::FriendsLists(QWidget *parent) 
    : QDialog(parent), ui(new Ui::FriendsLists) {
        ui->setupUi(this);
        m_cmodel = new FriendsListModel(m_friendsList, this);
        m_cmodel->add("a");
        m_cmodel->add("z");
        m_cmodel->add("m");
        m_cmodel->add("b");
        //m_cmodel->updateAllPeople();
        m_cpmodel = new ContactProxyModel(this);
        m_cpmodel->setSourceModel(m_cmodel);
        ui->contactsListView->setModel(m_cpmodel);
        ui->friendsListView->setModel(m_cmodel);
        connect (ui->contactsListView, SIGNAL(clicked(QModelIndex)),
                 m_cmodel, SLOT(contactSelected(QModelIndex)));
    }
    
    FriendsLists::~FriendsLists() {
        delete ui;
    }
    
    void FriendsLists::changeEvent(QEvent *e) {
        QDialog::changeEvent(e);
        switch (e->type()) {
        case QEvent::LanguageChange:
            ui->retranslateUi(this);
            break;
        default:
            break;
        }
    }
    
    void FriendsLists::on_addButton_clicked() {
        QString str = QInputDialog::getText(this, 
            tr("Enter a symbol"), tr("Symbol: ") );
        if (str == QString()) return;
        m_cmodel->add(str);
    }
    

    <include src="solution/modelview/friendslist-gui/friendslists.cpp" href="solution/modelview/friendslist-gui/friendslists.cpp" role="solution" mode="cpp"/>


    Example 13.30. solution/modelview/friendslist-gui/contactproxymodel.h

    #ifndef CONTACTPROXYMODEL_H
    #define CONTACTPROXYMODEL_H
    
    #include <QSortFilterProxyModel>
    class ContactProxyModel: public QSortFilterProxyModel
    {
        Q_OBJECT
    public:
        typedef QSortFilterProxyModel SUPER;
        explicit ContactProxyModel(QObject *parent = 0); 
        Qt::ItemFlags flags(const QModelIndex &index) const;
        QVariant data(const QModelIndex &index, int role) const;
    };
    
    #endif // CONTACTPROXYMODEL_H
    

    <include src="solution/modelview/friendslist-gui/contactproxymodel.h" href="solution/modelview/friendslist-gui/contactproxymodel.h" role="solution" mode="cpp"/>


    Example 13.31. solution/modelview/friendslist-gui/contactproxymodel.cpp

    #include "contactproxymodel.h"
    
    
    ContactProxyModel::ContactProxyModel(QObject *parent)
        : SUPER(parent) {}
    
    Qt::ItemFlags ContactProxyModel::flags(const QModelIndex &index) const {
        int r = index.row();
        if ((r < 0 ) || r >= rowCount()) return 0;
        return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    }
    
    QVariant ContactProxyModel::data(const QModelIndex &index, int role) const {
        if (role == Qt::CheckStateRole)
            return QVariant();
        else return SUPER::data(index, role);
    }
    
    

    <include src="solution/modelview/friendslist-gui/contactproxymodel.cpp" href="solution/modelview/friendslist-gui/contactproxymodel.cpp" role="solution" mode="cpp"/>


  4. Rewrite the ShortcutEditor example to use in-place editing. You need to write a delegate class for the table view, which provides a custom editor when the user wants to change a shortcut.

      See solution/modelview/shortcutmodel-delegate