From e8fbb1707b72c8583483c625fbe2ff54e557bafa Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Sun, 17 Nov 2013 16:41:44 +0100 Subject: [PATCH] Model work. --- resources/misc/db_init.sql | 98 +++++++++---------- src/core/defs.h.in | 21 ++++ src/core/messagesmodel.cpp | 163 +++++++++++++++++++++++++++++--- src/core/messagesmodel.h | 54 +++++++++-- src/core/messagesproxymodel.cpp | 6 ++ src/core/messagesproxymodel.h | 5 + src/gui/messagesview.cpp | 50 ++++++++++ src/gui/messagesview.h | 13 +++ src/main.cpp | 2 - 9 files changed, 343 insertions(+), 69 deletions(-) diff --git a/resources/misc/db_init.sql b/resources/misc/db_init.sql index 1d8db6fd1..de5fe520c 100644 --- a/resources/misc/db_init.sql +++ b/resources/misc/db_init.sql @@ -1,50 +1,50 @@ -DROP TABLE IF EXISTS Information; --- ! -CREATE TABLE IF NOT EXISTS Information ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL -); --- ! -INSERT INTO Information VALUES ('schema_version', '0.0.1'); --- ! -DROP TABLE IF EXISTS Categories; --- ! -CREATE TABLE IF NOT EXISTS Categories ( - id INTEGER PRIMARY KEY, - title TEXT NOT NULL UNIQUE CHECK (title != ''), - description TEXT, - date_created TEXT NOT NULL CHECK (date_created != ''), - icon BLOB -); --- ! -DROP TABLE IF EXISTS Feeds; --- ! -CREATE TABLE IF NOT EXISTS Feeds ( - id INTEGER PRIMARY KEY, - title TEXT NOT NULL CHECK (title != ''), - description TEXT, - date_created TEXT NOT NULL CHECK (date_created != ''), - icon BLOB, - category INTEGER NOT NULL CHECK (category >= -1), - encoding TEXT NOT NULL CHECK (encoding != ''), - url TEXT NOT NULL UNIQUE CHECK (url != ''), - type INTEGER NOT NULL -); --- ! -DROP TABLE IF EXISTS Messages; --- ! -CREATE TABLE IF NOT EXISTS Messages ( - id INTEGER PRIMARY KEY, - feed INTEGER NOT NULL, - title TEXT NOT NULL CHECK (title != ''), - url TEXT, - author TEXT, - date_created TEXT NOT NULL CHECK (date_created != ''), - date_updated TEXT, - read INTEGER(1) NOT NULL CHECK (read >= 0 AND read <= 1) DEFAULT (0), - deleted INTEGER(1) NOT NULL CHECK (deleted >= 0 AND deleted <= 1) DEFAULT (0), - important INTEGER(1) NOT NULL CHECK (important >= 0 AND important <= 1) DEFAULT (0), - contents TEXT, - - FOREIGN KEY (feed) REFERENCES Feeds (id) +DROP TABLE IF EXISTS Information; +-- ! +CREATE TABLE IF NOT EXISTS Information ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); +-- ! +INSERT INTO Information VALUES ('schema_version', '0.0.1'); +-- ! +DROP TABLE IF EXISTS Categories; +-- ! +CREATE TABLE IF NOT EXISTS Categories ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL UNIQUE CHECK (title != ''), + description TEXT, + date_created TEXT NOT NULL CHECK (date_created != ''), + icon BLOB +); +-- ! +DROP TABLE IF EXISTS Feeds; +-- ! +CREATE TABLE IF NOT EXISTS Feeds ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL CHECK (title != ''), + description TEXT, + date_created TEXT NOT NULL CHECK (date_created != ''), + icon BLOB, + category INTEGER NOT NULL CHECK (category >= -1), + encoding TEXT NOT NULL CHECK (encoding != ''), + url TEXT NOT NULL UNIQUE CHECK (url != ''), + type INTEGER NOT NULL +); +-- ! +DROP TABLE IF EXISTS Messages; +-- ! +CREATE TABLE IF NOT EXISTS Messages ( + id INTEGER PRIMARY KEY, + read INTEGER(1) NOT NULL CHECK (read >= 0 AND read <= 1) DEFAULT (0), + deleted INTEGER(1) NOT NULL CHECK (deleted >= 0 AND deleted <= 1) DEFAULT (0), + important INTEGER(1) NOT NULL CHECK (important >= 0 AND important <= 1) DEFAULT (0), + feed INTEGER NOT NULL, + title TEXT NOT NULL CHECK (title != ''), + url TEXT, + author TEXT, + date_created TEXT NOT NULL CHECK (date_created != ''), + date_updated TEXT, + contents TEXT, + + FOREIGN KEY (feed) REFERENCES Feeds (id) ); \ No newline at end of file diff --git a/src/core/defs.h.in b/src/core/defs.h.in index 87cf7bd54..75d5fc897 100644 --- a/src/core/defs.h.in +++ b/src/core/defs.h.in @@ -53,6 +53,27 @@ #define APP_SKIN_DEFAULT "base/vergilius.xml" #define APP_THEME_DEFAULT "mini-kfaenza" +// Indexes of columns as they are DEFINED IN THE TABLE for MESSAGES. +#define MSG_DB_ID_INDEX 0 +#define MSG_DB_READ_INDEX 1 +#define MSG_DB_DELETED_INDEX 2 +#define MSG_DB_IMPORTANT_INDEX 3 +#define MSG_DB_FEED_INDEX 4 +#define MSG_DB_TITLE_INDEX 5 +#define MSG_DB_URL_INDEX 6 +#define MSG_DB_AUTHOR_INDEX 7 +#define MSG_DB_DCREATED_INDEX 8 +#define MSG_DB_DUPDATED_INDEX 9 +#define MSG_DB_CONTENTS_INDEX 10 + +// Indexes of columns as they are USED IN THE MODEL for MESSAGES. +#define MSG_MODEL_READ_INDEX 0 +#define MSG_MODEL_IMPORTANT_INDEX 1 +#define MSG_MODEL_TITLE_INDEX 2 +#define MSG_MODEL_AUTHOR_INDEX 3 +#define MSG_MODEL_DCREATED_INDEX 4 +#define MSG_MODEL_DUPDATED_INDEX 5 + #if defined(Q_OS_LINUX) #define APP_DESKTOP_ENTRY_PATH "@DESKTOP_ENTRY@" #define APP_DESKTOP_ENTRY_FILE "@APP_LOW_NAME@.desktop" diff --git a/src/core/messagesmodel.cpp b/src/core/messagesmodel.cpp index 573dcc7b4..7719e5a26 100644 --- a/src/core/messagesmodel.cpp +++ b/src/core/messagesmodel.cpp @@ -1,37 +1,102 @@ +#include +#include +#include + +#include "qtsingleapplication/qtsingleapplication.h" + +#include "core/defs.h" #include "core/messagesmodel.h" #include "core/databasefactory.h" +#include "gui/iconthemefactory.h" MessagesModel::MessagesModel(QObject *parent) - : QSqlTableModel(parent, - DatabaseFactory::getInstance()->addConnection("MessagesModel")) { + : QAbstractItemModel(parent) { setObjectName("MessagesModel"); - setEditStrategy(QSqlTableModel::OnFieldChange); + QSqlDatabase d = DatabaseFactory::getInstance()->addConnection("MessagesModel2"); + QSqlQuery prikaz = d.exec("SELECT id, read, deleted, important, " + "title, url, author, date_created, " + "date_updated, contents FROM Messages;"); + + m_columnMappings.insert(MSG_MODEL_READ_INDEX, MSG_DB_READ_INDEX); + m_columnMappings.insert(MSG_MODEL_IMPORTANT_INDEX, MSG_DB_IMPORTANT_INDEX); + m_columnMappings.insert(MSG_MODEL_TITLE_INDEX, MSG_DB_TITLE_INDEX); + m_columnMappings.insert(MSG_MODEL_AUTHOR_INDEX, MSG_DB_AUTHOR_INDEX); + m_columnMappings.insert(MSG_MODEL_DCREATED_INDEX, MSG_DB_DCREATED_INDEX); + m_columnMappings.insert(MSG_MODEL_DUPDATED_INDEX, MSG_DB_DUPDATED_INDEX); + + + QList list; + while (prikaz.next()) { + Message mess; + mess.m_data.append(prikaz.value(MSG_DB_ID_INDEX).toInt()); + mess.m_data.append(prikaz.value(MSG_DB_READ_INDEX).toInt()); + mess.m_data.append(prikaz.value(MSG_DB_DELETED_INDEX).toInt()); + mess.m_data.append(prikaz.value(MSG_DB_IMPORTANT_INDEX).toInt()); + mess.m_data.append(prikaz.value(MSG_DB_TITLE_INDEX).toString()); + mess.m_data.append(prikaz.value(MSG_DB_URL_INDEX).toString()); + mess.m_data.append(prikaz.value(MSG_DB_AUTHOR_INDEX).toString()); + mess.m_data.append(prikaz.value(MSG_DB_DCREATED_INDEX).toString()); + mess.m_data.append(prikaz.value(MSG_DB_DUPDATED_INDEX).toString()); + mess.m_data.append(prikaz.value(MSG_DB_CONTENTS_INDEX).toString()); + + list.append(mess); + } + m_messages = list; + + setupFonts(); setupHeaderData(); - - setTable("Messages"); - select(); } MessagesModel::~MessagesModel() { qDebug("Destroying MessagesModel instance."); } +void MessagesModel::setupFonts() { + m_normalFont = QtSingleApplication::font("MessagesView"); + m_boldFont = m_normalFont; + m_boldFont.setBold(true); +} + void MessagesModel::setupHeaderData() { - // TODO: Enhance this. - m_headerData << tr("Id") << tr("Feed") << tr("Title") << - tr("URL") << tr("Author") << tr("Created on") << - tr("Last updated on") << tr("Read") << tr("Deleted") << - tr("Important") << tr("Contents"); + m_headerData << tr("Read") << tr("Important") << + tr("Title") << tr("Author") << + tr("Created on") << tr("Updated on"); } Qt::ItemFlags MessagesModel::flags(const QModelIndex &index) const { Q_UNUSED(index); + // Each item can be selected and is enabled. return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } +int MessagesModel::rowCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + + return m_messages.count(); +} + +int MessagesModel::columnCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + + return m_headerData.count(); +} + +QModelIndex MessagesModel::parent(const QModelIndex &child) const { + Q_UNUSED(child); + + return QModelIndex(); +} + +QModelIndex MessagesModel::index(int row, int column, + const QModelIndex &parent) const { + Q_UNUSED(parent); + + return createIndex(row, column); +} + QVariant MessagesModel::headerData(int section, Qt::Orientation orientation, int role) const { @@ -42,9 +107,85 @@ QVariant MessagesModel::headerData(int section, switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: + case Qt::EditRole: return m_headerData.at(section); default: return QVariant(); } } + +QVariant MessagesModel::data(const QModelIndex &index, int role) const { + // TODO: Return ISO date on EditRole and human readable date on + // DisplayRole. EditRole is used for sorting (and ISO date can be + // sorted as a string. + + switch (role) { + case Qt::EditRole: + // Just return RAW data. + return m_messages.at(index.row()).m_data.at(m_columnMappings[index.column()]); + + case Qt::DisplayRole: { + int real_column = m_columnMappings[index.column()]; + + if (real_column != MSG_DB_IMPORTANT_INDEX) { + return m_messages.at(index.row()).m_data.at(real_column); + } + else { + return QVariant(); + } + } + + + case Qt::FontRole: + return m_messages.at(index.row()).m_data.at(m_columnMappings[MSG_MODEL_READ_INDEX]).toInt() == 1 ? + m_normalFont : + m_boldFont; + + case Qt::DecorationRole:/* + if (index.column() == 0 && m_messages[index.row()].m_important == 1) { + return IconThemeFactory::getInstance()->fromTheme("zoom-fit-best"); + } + else { + return QVariant(); + }*/ + + default: + return QVariant(); + } +} + +bool MessagesModel::setData(const QModelIndex &index, const QVariant &value, + int role) { + Q_UNUSED(role); + + m_messages[index.row()].m_data[m_columnMappings[index.column()]] = value; + + QModelIndex left = this->index(index.row(), 0); + QModelIndex right = this->index(index.row(), columnCount() - 1); + + emit dataChanged(left, right); + return true; +} + +bool MessagesModel::setData(int row, int column, const QVariant &value) { + return setData(index(row, column), value); +} + +void MessagesModel::setMessageRead(int row_index, int read) { + //int read_column = fieldIndex("read"); + //blockSignals(true); + + + if (m_messages.at(row_index).m_data.at(MSG_DB_READ_INDEX).toInt() != read) { + // Old "read" status of this message differs from + // the new status, update it. + setData(row_index, MSG_MODEL_READ_INDEX, read); + } + + + //blockSignals(false); + //submitAll(); + //emit dataChanged(index(message_row_index, 0), index(message_row_index, columnCount())); +} + diff --git a/src/core/messagesmodel.h b/src/core/messagesmodel.h index ac85aa162..f98cd3e9e 100644 --- a/src/core/messagesmodel.h +++ b/src/core/messagesmodel.h @@ -1,11 +1,30 @@ #ifndef MESSAGESMODEL_H #define MESSAGESMODEL_H -#include -#include +#include +#include -class MessagesModel : public QSqlTableModel { +// Representation of ONE message. +class Message { + private: + QList m_data; +/* + int m_id; + int m_read; + int m_deleted; + int m_important; + QString m_title; + QString m_url; + QString m_author; + QString m_dateCreated; + QString m_dateUpdate; + QString m_contents; +*/ + friend class MessagesModel; +}; + +class MessagesModel : public QAbstractItemModel { Q_OBJECT public: @@ -14,18 +33,39 @@ class MessagesModel : public QSqlTableModel { virtual ~MessagesModel(); // Model implementation. + + // Data accessors/manipulators. QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + bool setData(int row, int column, const QVariant &value); Qt::ItemFlags flags(const QModelIndex &index) const; + // Model dimensions. + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + // Model navigation. + QModelIndex parent(const QModelIndex &child) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + + public slots: + // Sets "read" status of message with given row index. + void setMessageRead(int row_index, int read); + private: void setupHeaderData(); - signals: - - public slots: + // Creates "normal" and "bold" fonts. + void setupFonts(); private: - QStringList m_headerData; + QHash m_columnMappings; + QList m_messages; + + QFont m_normalFont; + QFont m_boldFont; + QList m_headerData; }; diff --git a/src/core/messagesproxymodel.cpp b/src/core/messagesproxymodel.cpp index ecf386ded..9dd19975b 100644 --- a/src/core/messagesproxymodel.cpp +++ b/src/core/messagesproxymodel.cpp @@ -11,6 +11,8 @@ MessagesProxyModel::MessagesProxyModel(QObject *parent) setSortCaseSensitivity(Qt::CaseInsensitive); setFilterCaseSensitivity(Qt::CaseInsensitive); setFilterKeyColumn(-1); + setFilterRole(Qt::EditRole); + setDynamicSortFilter(false); setSourceModel(m_sourceModel); } @@ -18,6 +20,10 @@ MessagesProxyModel::~MessagesProxyModel() { qDebug("Destroying MessagesProxyModel instance."); } +MessagesModel *MessagesProxyModel::sourceModel() { + return m_sourceModel; +} + bool MessagesProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { return QSortFilterProxyModel::lessThan(left, right); } diff --git a/src/core/messagesproxymodel.h b/src/core/messagesproxymodel.h index b3ba492db..861500919 100644 --- a/src/core/messagesproxymodel.h +++ b/src/core/messagesproxymodel.h @@ -14,10 +14,15 @@ class MessagesProxyModel : public QSortFilterProxyModel { explicit MessagesProxyModel(QObject *parent = 0); virtual ~MessagesProxyModel(); + // Source model getter. + MessagesModel *sourceModel(); + protected: + // Compares two rows of data. bool lessThan(const QModelIndex &left, const QModelIndex &right) const; private: + // Source model pointer. MessagesModel *m_sourceModel; }; diff --git a/src/gui/messagesview.cpp b/src/gui/messagesview.cpp index 1d2deeb62..44a9ab4b0 100644 --- a/src/gui/messagesview.cpp +++ b/src/gui/messagesview.cpp @@ -1,3 +1,5 @@ +#include + #include "gui/messagesview.h" #include "core/messagesproxymodel.h" #include "core/messagesmodel.h" @@ -5,6 +7,7 @@ MessagesView::MessagesView(QWidget *parent) : QTreeView(parent) { m_proxyModel = new MessagesProxyModel(this); + m_sourceModel = m_proxyModel->sourceModel(); setModel(m_proxyModel); setupAppearance(); @@ -22,4 +25,51 @@ void MessagesView::setupAppearance() { setRootIsDecorated(false); setItemsExpandable(false); setSortingEnabled(true); + setAllColumnsShowFocus(true); + setSelectionMode(QAbstractItemView::ExtendedSelection); +} + +void MessagesView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) { +/* + if (selected.indexes().size() > 0) { + QModelIndex ind = m_proxyModel->mapToSource(selected.indexes().at(0)); + QModelIndex a = selected.indexes().at(0); + + qDebug("SelectionChanged %d %d source %d %d", + selected.indexes().at(0).row(), selected.indexes().at(0).column(), + ind.row(), ind.column()); + + m_sourceModel->setMessageRead(1, ind.row()); + + sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); + + QModelIndex new_index = m_proxyModel->mapFromSource(ind); + + // TODO: Buď tady obnovovat celý předchozí výběr nějak + // nebo použít starší kod a optimalizovat ho. + selectionModel()->clearSelection(); + selectionModel()->blockSignals(true); + setCurrentIndex(new_index); + scrollTo(new_index); + selectionModel()->select(new_index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current ); + selectionModel()->blockSignals(false); + + }*/ + + QTreeView::selectionChanged(selected, deselected); +} + +void MessagesView::currentChanged(const QModelIndex ¤t, + const QModelIndex &previous) { + QModelIndex ind = m_proxyModel->mapToSource(current); + + + qDebug("CurrentChanged %d %d source %d %d", + current.row(), current.column(), + ind.row(), ind.column()); + + m_sourceModel->setMessageRead(ind.row(), 1); + + QTreeView::currentChanged(current, previous); } diff --git a/src/gui/messagesview.h b/src/gui/messagesview.h index d4a1297b1..d40c78cc1 100644 --- a/src/gui/messagesview.h +++ b/src/gui/messagesview.h @@ -3,6 +3,8 @@ #include +#include "core/messagesmodel.h" + class MessagesProxyModel; @@ -17,8 +19,19 @@ class MessagesView : public QTreeView { protected: void setupAppearance(); + void currentChanged(const QModelIndex ¤t, + const QModelIndex &previous); + + void selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected); + + signals: + void currentMessageChanged(const Message &message); + void currentMessageRemoved(); + private: MessagesProxyModel *m_proxyModel; + MessagesModel *m_sourceModel; }; diff --git a/src/main.cpp b/src/main.cpp index 4b46622c0..ae4d275a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,8 +74,6 @@ int main(int argc, char *argv[]) { IconThemeFactory::getInstance()->loadCurrentIconTheme(false); SkinFactory::getInstance()->loadCurrentSkin(); - DatabaseFactory::getInstance()->addConnection("abc"); - // Load localization and setup locale before any widget is constructed. LoadLocalization();