Many changes, working Sync in, fixed some problems with item expanding, items now can inform model that it wants some items expanded or not.

This commit is contained in:
Martin Rotter 2015-12-08 09:11:55 +01:00
parent 746e7aae1f
commit 683517948d
18 changed files with 282 additions and 70 deletions

View File

@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS Categories (
parent_id INTEGER NOT NULL,
title VARCHAR(100) NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT NOT NULL CHECK (date_created != 0),
date_created BIGINT,
icon BLOB,
account_id INTEGER NOT NULL,
custom_id TEXT,
@ -50,17 +50,17 @@ CREATE TABLE IF NOT EXISTS Feeds (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT NOT NULL CHECK (date_created != 0),
date_created BIGINT,
icon BLOB,
category INTEGER NOT NULL CHECK (category >= -1),
encoding TEXT NOT NULL CHECK (encoding != ''),
url VARCHAR(100) NOT NULL CHECK (url != ''),
encoding TEXT,
url VARCHAR(100),
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
username TEXT,
password TEXT,
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 5),
type INTEGER NOT NULL CHECK (type >= 0),
type INTEGER,
account_id INTEGER NOT NULL,
custom_id TEXT,

View File

@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS Categories (
parent_id INTEGER NOT NULL,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER NOT NULL CHECK (date_created != 0),
date_created INTEGER,
icon BLOB,
account_id INTEGER NOT NULL,
custom_id TEXT,
@ -45,17 +45,17 @@ CREATE TABLE IF NOT EXISTS Feeds (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER NOT NULL CHECK (date_created != 0),
date_created INTEGER,
icon BLOB,
category INTEGER NOT NULL CHECK (category >= -1),
encoding TEXT NOT NULL CHECK (encoding != ''),
url TEXT NOT NULL CHECK (url != ''),
encoding TEXT,
url TEXT,
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
username TEXT,
password TEXT,
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15,
type INTEGER NOT NULL CHECK (type >= 0),
type INTEGER,
account_id INTEGER NOT NULL,
custom_id TEXT,

View File

@ -40,4 +40,19 @@ DROP FOREIGN KEY feed;
ALTER TABLE Messages
MODIFY Feeds TEXT;
-- !
ALTER TABLE Feeds
MODIFY date_created BIGINT;
-- !
ALTER TABLE Feeds
MODIFY encoding TEXT;
-- !
ALTER TABLE Feeds
MODIFY url VARCHAR(100);
-- !
ALTER TABLE Feeds
MODIFY type INTEGER;
-- !
ALTER TABLE Categories
MODIFY date_created BIGINT;
-- !
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';

View File

@ -61,4 +61,54 @@ INSERT INTO Messages SELECT * FROM backup_Messages;
-- !
DROP TABLE backup_Messages;
-- !
CREATE TABLE backup_Feeds AS SELECT * FROM Feeds;
-- !
DROP TABLE Feeds;
-- !
CREATE TABLE Feeds (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER,
icon BLOB,
category INTEGER NOT NULL CHECK (category >= -1),
encoding TEXT,
url TEXT,
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
username TEXT,
password TEXT,
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15,
type INTEGER,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
INSERT INTO Feeds SELECT * FROM backup_Feeds;
-- !
DROP TABLE backup_Feeds;
-- !
CREATE TABLE backup_Categories AS SELECT * FROM Categories;
-- !
DROP TABLE Categories;
-- !
CREATE TABLE Categories (
id INTEGER PRIMARY KEY,
parent_id INTEGER NOT NULL,
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created INTEGER,
icon BLOB,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id)
);
-- !
INSERT INTO Categories SELECT * FROM backup_Categories;
-- !
DROP TABLE backup_Categories;
-- !
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';

View File

@ -697,6 +697,7 @@ bool FeedsModel::addServiceAccount(ServiceRoot *root) {
connect(root, SIGNAL(readFeedsFilterInvalidationRequested()), this, SIGNAL(readFeedsFilterInvalidationRequested()));
connect(root, SIGNAL(dataChanged(QList<RootItem*>)), this, SLOT(onItemDataChanged(QList<RootItem*>)));
connect(root, SIGNAL(reloadMessageListRequested(bool)), this, SIGNAL(reloadMessageListRequested(bool)));
connect(root, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)));
root->start();
return true;

View File

@ -206,6 +206,9 @@ class FeedsModel : public QAbstractItemModel {
// Emitted if counts of messages are changed.
void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages);
// Emitted if any item requested that any view should expand it.
void itemExpandRequested(QList<RootItem*> items, bool expand);
// Emitted when there is a need of reloading of displayed messages.
void reloadMessageListRequested(bool mark_selected_messages_read);

View File

@ -55,6 +55,7 @@ FeedsView::FeedsView(QWidget *parent)
// Connections.
connect(m_sourceModel, SIGNAL(requireItemValidationAfterDragDrop(QModelIndex)), this, SLOT(validateItemAfterDragDrop(QModelIndex)));
connect(m_sourceModel, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SLOT(onItemExpandRequested(QList<RootItem*>,bool)));
connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSortState(int,Qt::SortOrder)));
setModel(m_proxyModel);
@ -102,11 +103,11 @@ void FeedsView::saveExpandedStates() {
Settings *settings = qApp->settings();
QList<RootItem*> expandable_items;
expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category));
expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot));
// Iterate all categories and save their expand statuses.
foreach (RootItem *item, expandable_items) {
QString setting_name = QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id());
QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id());
settings->setValue(GROUP(Categories),
setting_name,
@ -122,10 +123,10 @@ void FeedsView::loadExpandedStates() {
// Iterate all categories and save their expand statuses.
foreach (RootItem *item, expandable_items) {
QString setting_name = QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id());
QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id());
setExpanded(model()->mapFromSource(sourceModel()->indexForItem(item)),
settings->value(GROUP(Categories), setting_name, true).toBool());
settings->value(GROUP(Categories), setting_name, item->childCount() > 0).toBool());
}
}
@ -466,3 +467,13 @@ void FeedsView::validateItemAfterDragDrop(const QModelIndex &source_index) {
setCurrentIndex(mapped);
}
}
void FeedsView::onItemExpandRequested(const QList<RootItem*> &items, bool exp) {
foreach (RootItem *item, items) {
QModelIndex source_index = m_sourceModel->indexForItem(item);
QModelIndex proxy_index = m_proxyModel->mapFromSource(source_index);
setExpanded(proxy_index, !exp);
setExpanded(proxy_index, exp);
}
}

View File

@ -104,6 +104,7 @@ class FeedsView : public QTreeView {
void saveSortState(int column, Qt::SortOrder order);
void validateItemAfterDragDrop(const QModelIndex &source_index);
void onItemExpandRequested(const QList<RootItem*> &items, bool exp);
private:
// Initializes context menus.

View File

@ -58,7 +58,7 @@ bool ServiceRoot::deleteViaGui() {
return data_removed;
}
void ServiceRoot::itemChanged(QList<RootItem*> items) {
void ServiceRoot::itemChanged(const QList<RootItem *> &items) {
emit dataChanged(items);
}
@ -70,6 +70,10 @@ void ServiceRoot::requestFeedReadFilterReload() {
emit readFeedsFilterInvalidationRequested();
}
void ServiceRoot::requestItemExpand(const QList<RootItem *> &items, bool expand) {
emit itemExpandRequested(items, expand);
}
void ServiceRoot::requestItemReassignment(RootItem *item, RootItem *new_parent) {
emit itemReassignmentRequested(item, new_parent);
}

View File

@ -138,10 +138,10 @@ class ServiceRoot : public RootItem {
/////////////////////////////////////////
// Obvious methods to wrap signals.
void itemChanged(QList<RootItem*> items);
void itemChanged(const QList<RootItem*> &items);
void requestReloadMessageList(bool mark_selected_messages_read);
void requestFeedReadFilterReload();
void requestItemExpand(const QList<RootItem*> &items, bool expand);
void requestItemReassignment(RootItem *item, RootItem *new_parent);
void requestItemRemoval(RootItem *item);
@ -154,6 +154,7 @@ class ServiceRoot : public RootItem {
void dataChanged(QList<RootItem*> items);
void readFeedsFilterInvalidationRequested();
void reloadMessageListRequested(bool mark_selected_messages_read);
void itemExpandRequested(QList<RootItem*> items, bool expand);
void itemReassignmentRequested(RootItem *item, RootItem *new_parent);
void itemRemovalRequested(RootItem *item);

View File

@ -75,7 +75,6 @@ ServiceRoot *StandardServiceEntryPoint::createNewRoot() {
SERVICE_CODE_STD_RSS))) {
StandardServiceRoot *root = new StandardServiceRoot();
root->setAccountId(id_to_assing);
root->loadFromDatabase();
return root;
}
else {
@ -93,7 +92,6 @@ QList<ServiceRoot*> StandardServiceEntryPoint::initializeSubtree() {
while (query.next()) {
StandardServiceRoot *root = new StandardServiceRoot();
root->setAccountId(query.value(0).toInt());
root->loadFromDatabase();
roots.append(root);
}
}

View File

@ -61,6 +61,8 @@ StandardServiceRoot::~StandardServiceRoot() {
}
void StandardServiceRoot::start() {
loadFromDatabase();
if (qApp->isFirstRun()) {
if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, QObject::tr("Load initial set of feeds"),
tr("You started %1 for the first time, now you can load initial set of feeds.").arg(APP_NAME),

View File

@ -148,7 +148,7 @@ void FormEditAccount::onClickedOk() {
m_editableRoot->network()->setUrl(m_ui->m_txtUrl->lineEdit()->text());
m_editableRoot->network()->setUsername(m_ui->m_txtUsername->lineEdit()->text());
m_editableRoot->network()->setPassword(m_ui->m_txtPassword->lineEdit()->text());
m_editableRoot->saveToDatabase();
m_editableRoot->saveAccountDataToDatabase();
accept();
}
@ -185,6 +185,9 @@ void FormEditAccount::onUrlChanged() {
if (url.isEmpty()) {
m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL cannot be empty."));
}
else if (!url.endsWith(QL1S("api/"))) {
m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL must end with \"api/\"."));
}
else {
m_ui->m_txtUrl->setStatus(WidgetWithStatus::Ok, tr("URL is okay."));
}

View File

@ -92,15 +92,22 @@ TtRssLoginResponse TtRssNetworkFactory::login(QNetworkReply::NetworkError &error
}
TtRssResponse TtRssNetworkFactory::logout(QNetworkReply::NetworkError &error) {
QtJson::JsonObject json;
json["op"] = "logout";
json["sid"] = m_sessionId;
if (!m_sessionId.isEmpty()) {
QByteArray result_raw;
NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
QtJson::JsonObject json;
json["op"] = "logout";
json["sid"] = m_sessionId;
error = network_reply.first;
return TtRssResponse(QString::fromUtf8(result_raw));
QByteArray result_raw;
NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw);
error = network_reply.first;
return TtRssResponse(QString::fromUtf8(result_raw));
}
else {
error = QNetworkReply::NoError;
return TtRssResponse();
}
}
TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories(QNetworkReply::NetworkError &error) {
@ -210,9 +217,12 @@ TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString &
TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() {
}
RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories() {
RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) {
RootItem *parent = new RootItem();
// Chop the "api/" from the end of the address.
base_address.chop(4);
if (status() == API_STATUS_OK) {
// We have data, construct object tree according to data.
QList<QVariant> items_to_process = m_rawContent["content"].toMap()["categories"].toMap()["items"].toList();
@ -227,49 +237,61 @@ RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories() {
RootItem *act_parent = pair.first;
QMap<QString,QVariant> item = pair.second.toMap();
if (item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY) {
// Add category to the parent, go through children.
int item_bare_id = item["bare_id"].toInt();
int item_id = item["bare_id"].toInt();
bool is_category = item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY;
if (item_bare_id < 0) {
// Ignore virtual categories or feeds.
continue;
}
if (item_id >= 0) {
if (is_category) {
if (item_id == 0) {
// This is "Uncategorized" category, all its feeds belong to top-level root.
if (item.contains("items")) {
foreach (QVariant child_feed, item["items"].toList()) {
pairs.append(QPair<RootItem*,QVariant>(parent, child_feed));
}
}
}
else {
TtRssCategory *category = new TtRssCategory();
if (item_bare_id == 0) {
// This is "Uncategorized" category, all its feeds belong to total parent.
if (item.contains("items")) {
foreach (QVariant child_feed, item["items"].toList()) {
pairs.append(QPair<RootItem*,QVariant>(parent, child_feed));
category->setIcon(qApp->icons()->fromTheme(QSL("folder-category")));
category->setTitle(item["name"].toString());
category->setCustomId(item_id);
act_parent->appendChild(category);
if (item.contains("items")) {
foreach (QVariant child, item["items"].toList()) {
pairs.append(QPair<RootItem*,QVariant>(category, child));
}
}
}
}
else if (item_bare_id > 0) {
TtRssCategory *category = new TtRssCategory();
else {
// We have feed.
TtRssFeed *feed = new TtRssFeed();
category->setIcon(qApp->icons()->fromTheme(QSL("folder-category")));
category->setTitle(item["name"].toString());
category->setCustomId(item_bare_id);
act_parent->appendChild(category);
if (obtain_icons) {
QString icon_path = item["icon"].type() == QVariant::String ? item["icon"].toString() : QString();
if (item.contains("items")) {
foreach (QVariant child, item["items"].toList()) {
pairs.append(QPair<RootItem*,QVariant>(category, child));
if (!icon_path.isEmpty()) {
// Chop the "api/" suffix out and append
QString full_icon_address = base_address + QL1C('/') + icon_path;
QByteArray icon_data;
if (NetworkFactory::downloadFile(full_icon_address, DOWNLOAD_TIMEOUT, icon_data).first == QNetworkReply::NoError) {
// Icon downloaded, set it up.
QPixmap icon_pixmap;
icon_pixmap.loadFromData(icon_data);
feed->setIcon(QIcon(icon_pixmap));
}
}
}
// TODO: stahnout a nastavit ikonu
feed->setTitle(item["name"].toString());
feed->setCustomId(item_id);
act_parent->appendChild(feed);
}
}
else {
// We have feed.
int item_bare_id = item["bare_id"].toInt();
TtRssFeed *feed = new TtRssFeed();
// TODO: stahnout a nastavit ikonu
feed->setTitle(item["name"].toString());
feed->setCustomId(item_bare_id);
act_parent->appendChild(feed);
}
}
}

View File

@ -58,7 +58,10 @@ class TtRssGetFeedsCategoriesResponse : public TtRssResponse {
explicit TtRssGetFeedsCategoriesResponse(const QString &raw_content = QString());
virtual ~TtRssGetFeedsCategoriesResponse();
RootItem *feedsCategories();
// Returns tree of feeds/categories.
// Top-level root of the tree is not needed here.
// Returned items do not have primary IDs assigned.
RootItem *feedsCategories(bool obtain_icons, QString base_address = QString());
};
class TtRssNetworkFactory {

View File

@ -87,7 +87,6 @@ QList<ServiceRoot*> TtRssServiceEntryPoint::initializeSubtree() {
root->network()->setPassword(query.value(2).toString());
root->network()->setUrl(query.value(3).toString());
root->updateTitle();
root->loadFromDatabase();
roots.append(root);
}
}

View File

@ -21,6 +21,8 @@
#include "miscellaneous/settings.h"
#include "gui/dialogs/formmain.h"
#include "services/tt-rss/ttrssserviceentrypoint.h"
#include "services/tt-rss/ttrssfeed.h"
#include "services/tt-rss/ttrsscategory.h"
#include "services/tt-rss/network/ttrssnetworkfactory.h"
#include "services/tt-rss/gui/formeditaccount.h"
@ -42,13 +44,16 @@ TtRssServiceRoot::~TtRssServiceRoot() {
}
void TtRssServiceRoot::start() {
if (childItems().isEmpty()) {
syncIn();
}
// TODO: posunout starty rootů až je okno uděláno
loadFromDatabase();
// TODO: pokud tady není nic načteno, tak
// syncIn
}
void TtRssServiceRoot::stop() {
QNetworkReply::NetworkError error;
m_network->logout(error);
}
QString TtRssServiceRoot::code() {
@ -164,7 +169,7 @@ TtRssNetworkFactory *TtRssServiceRoot::network() const {
return m_network;
}
void TtRssServiceRoot::saveToDatabase() {
void TtRssServiceRoot::saveAccountDataToDatabase() {
if (accountId() != NO_PARENT_CATEGORY) {
// We are overwritting previously saved data.
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
@ -228,7 +233,97 @@ void TtRssServiceRoot::syncIn() {
// ze serveru, a sloučení s aktuálními
// neprovádí aktualizace kanálů ani stažení počtu nepřečtených zpráv
QNetworkReply::NetworkError err;
TtRssGetFeedsCategoriesResponse feed_cats_response = m_network->getFeedsCategories(err);
if (err == QNetworkReply::NoError) {
RootItem *new_tree = feed_cats_response.feedsCategories(true, m_network->url());
RootItem *aa = m_network->getFeedsCategories(err).feedsCategories();
// Purge old data from SQL and clean all model items.
removeOldFeedTree();
cleanAllItems();
// Model is clean, now store new tree into DB and
// set primary IDs of the items.
storeNewFeedTree(new_tree);
foreach (RootItem *top_level_item, new_tree->childItems()) {
appendChild(top_level_item);
}
new_tree->clearChildren();
new_tree->deleteLater();
itemChanged(QList<RootItem*>() << this);
requestFeedReadFilterReload();
requestReloadMessageList(true);
requestItemExpand(getSubTree(), true);
}
}
void TtRssServiceRoot::removeOldFeedTree() {
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
QSqlQuery query(database);
query.setForwardOnly(true);
query.prepare(QSL("DELETE FROM Feeds WHERE account_id = :account_id;"));
query.bindValue(QSL(":account_id"), accountId());
query.exec();
query.prepare(QSL("DELETE FROM Categories WHERE account_id = :account_id;"));
query.bindValue(QSL(":account_id"), accountId());
query.exec();
}
void TtRssServiceRoot::cleanAllItems() {
foreach (RootItem *top_level_item, childItems()) {
requestItemRemoval(top_level_item);
}
}
void TtRssServiceRoot::storeNewFeedTree(RootItem *root) {
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
QSqlQuery query_category(database);
QSqlQuery query_feed(database);
query_category.prepare("INSERT INTO Categories (parent_id, title, account_id, custom_id) "
"VALUES (:parent_id, :title, :account_id, :custom_id);");
query_feed.prepare("INSERT INTO Feeds (title, icon, category, protected, update_type, update_interval, account_id, custom_id) "
"VALUES (:title, :icon, :category, :protected, :update_type, :update_interval, :account_id, :custom_id);");
// Iterate all children.
foreach (RootItem *child, root->getSubTree()) {
if (child->kind() == RootItemKind::Category) {
query_category.bindValue(QSL(":parent_id"), child->parent()->id());
query_category.bindValue(QSL(":title"), child->title());
query_category.bindValue(QSL(":account_id"), accountId());
query_category.bindValue(QSL(":custom_id"), QString::number(static_cast<TtRssCategory*>(child)->customId()));
if (query_category.exec()) {
child->setId(query_category.lastInsertId().toInt());
}
else {
// TODO: logovat
}
}
else if (child->kind() == RootItemKind::Feed) {
TtRssFeed *feed = static_cast<TtRssFeed*>(child);
query_feed.bindValue(QSL(":title"), feed->title());
query_feed.bindValue(QSL(":icon"), qApp->icons()->toByteArray(feed->icon()));
query_feed.bindValue(QSL(":category"), feed->parent()->id());
query_feed.bindValue(QSL(":protected"), 0);
query_feed.bindValue(QSL(":update_type"), (int) feed->autoUpdateType());
query_feed.bindValue(QSL(":update_interval"), feed->autoUpdateInitialInterval());
query_feed.bindValue(QSL(":account_id"), accountId());
query_feed.bindValue(QSL(":custom_id"), feed->customId());
if (query_feed.exec()) {
feed->setId(query_feed.lastInsertId().toInt());
// TODO: updatecounts;
}
else {
// TODO: logovat.
}
}
}
}

View File

@ -66,14 +66,18 @@ class TtRssServiceRoot : public ServiceRoot {
TtRssNetworkFactory *network() const;
void saveToDatabase();
void loadFromDatabase();
void saveAccountDataToDatabase();
void updateTitle();
private slots:
void syncIn();
private:
void removeOldFeedTree();
void cleanAllItems();
void storeNewFeedTree(RootItem *root);
void loadFromDatabase();
QAction *m_actionSyncIn;
QList<QAction*> m_serviceMenu;