2 * Copyright (C) 2014 Daniel Vrátil <dvratil@redhat.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "tagpropertiesdialog.h"
23 #include <AkonadiCore/AttributeFactory>
26 #include <KConfigGroup>
27 #include <QDialogButtonBox>
29 using namespace Akonadi
;
31 TagPropertiesDialog::TagPropertiesDialog(QWidget
*parent
)
38 TagPropertiesDialog::TagPropertiesDialog(const Akonadi::Tag
&tag
, QWidget
*parent
)
46 TagPropertiesDialog::~TagPropertiesDialog()
50 Tag
TagPropertiesDialog::tag() const
55 bool TagPropertiesDialog::changed() const
60 void TagPropertiesDialog::setupUi()
62 QDialogButtonBox
*buttonBox
= new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel
);
63 QPushButton
*okButton
= buttonBox
->button(QDialogButtonBox::Ok
);
64 okButton
->setDefault(true);
65 okButton
->setShortcut(Qt::CTRL
| Qt::Key_Return
);
66 connect(buttonBox
, &QDialogButtonBox::accepted
, this, &TagPropertiesDialog::slotAccept
);
67 connect(buttonBox
, &QDialogButtonBox::rejected
, this, &TagPropertiesDialog::reject
);
69 QWidget
*widget
= new QWidget(this);
71 QVBoxLayout
*mainLayout
= new QVBoxLayout
;
72 setLayout(mainLayout
);
73 mainLayout
->addWidget(widget
);
74 mainLayout
->addWidget(buttonBox
);
76 connect(ui
.addAttrButton
, &QPushButton::clicked
, this, &TagPropertiesDialog::addAttributeClicked
);
77 connect(ui
.deleteAttrButton
, &QPushButton::clicked
, this, &TagPropertiesDialog::deleteAttributeClicked
);
79 connect(ui
.addRIDButton
, &QPushButton::clicked
, this, &TagPropertiesDialog::addRIDClicked
);
80 connect(ui
.deleteRIDButton
, &QPushButton::clicked
, this, &TagPropertiesDialog::deleteRIDClicked
);
82 Attribute::List attributes
= mTag
.attributes();
83 mAttributesModel
= new QStandardItemModel(attributes
.size(), 2, this);
84 connect(mAttributesModel
, &QStandardItemModel::itemChanged
, this, &TagPropertiesDialog::attributeChanged
);
86 labels
<< QStringLiteral("Attribute") << QStringLiteral("Value");
87 mAttributesModel
->setHorizontalHeaderLabels(labels
);
89 mRemoteIdsModel
= new QStandardItemModel(this);
90 connect(mRemoteIdsModel
, &QStandardItemModel::itemChanged
, this, &TagPropertiesDialog::remoteIdChanged
);
91 mRemoteIdsModel
->setColumnCount(2);
93 labels
<< QStringLiteral("Resource") << QStringLiteral("Remote ID");
94 mRemoteIdsModel
->setHorizontalHeaderLabels(labels
);
95 ui
.ridsView
->setModel(mRemoteIdsModel
);
98 ui
.idLabel
->setText(QString::number(mTag
.id()));
99 ui
.typeEdit
->setText(QLatin1String(mTag
.type()));
100 ui
.gidEdit
->setText(QLatin1String(mTag
.gid()));
101 ui
.parentIdLabel
->setText(QString::number(mTag
.parent().id()));
103 for (int i
= 0; i
< attributes
.count(); ++i
) {
104 QModelIndex index
= mAttributesModel
->index(i
, 0);
105 Q_ASSERT(index
.isValid());
106 mAttributesModel
->setData(index
, QLatin1String(attributes
[i
]->type()));
107 mAttributesModel
->item(i
, 0)->setEditable(false);
108 index
= mAttributesModel
->index(i
, 1);
109 Q_ASSERT(index
.isValid());
110 mAttributesModel
->setData(index
, QLatin1String(attributes
[i
]->serialized()));
111 mAttributesModel
->item(i
, 1)->setEditable(true);
115 // There is (intentionally) no way to retrieve Tag RID for another
116 // resource than in the current context. Since Akonadi Console has
117 // not resource context at all, we need to retrieve the IDs the hard way
118 QSqlQuery
query(DbAccess::database());
119 query
.prepare(QStringLiteral("SELECT ResourceTable.name, TagRemoteIdResourceRelationTable.remoteId "
120 "FROM TagRemoteIdResourceRelationTable "
121 "LEFT JOIN ResourceTable ON ResourceTable.id = TagRemoteIdResourceRelationTable.resourceId "
122 "WHERE TagRemoteIdResourceRelationTable.tagid = ?"));
123 query
.addBindValue(mTag
.id());
125 while (query
.next()) {
126 QList
<QStandardItem
*> items
;
127 QStandardItem
*item
= new QStandardItem(query
.value(0).toString());
128 item
->setEditable(false);
130 item
= new QStandardItem(query
.value(1).toString());
131 item
->setEditable(true);
133 mRemoteIdsModel
->appendRow(items
);
136 qCritical() << query
.executedQuery();
137 qCritical() << query
.lastError().text();
141 ui
.idLabel
->setVisible(false);
142 ui
.idLabelBuddy
->setVisible(false);
143 if (mTag
.parent().isValid()) {
144 ui
.parentIdLabel
->setText(QString::number(mTag
.parent().id()));
146 ui
.parentIdLabel
->setVisible(false);
147 ui
.parentIdLabelBuddy
->setVisible(false);
149 // Since we are using direct SQL to update RIDs, we cannot do this
150 // when creating a new Tag, because the tag is created by caller after
151 // this dialog is closed
152 ui
.tabWidget
->setTabEnabled(2, false);
155 ui
.attrsView
->setModel(mAttributesModel
);
158 void TagPropertiesDialog::addAttributeClicked()
160 const QString newType
= ui
.newAttrEdit
->text();
161 if (newType
.isEmpty()) {
164 ui
.newAttrEdit
->clear();
166 mChangedAttrs
.insert(newType
);
167 mRemovedAttrs
.remove(newType
);
170 const int row
= mAttributesModel
->rowCount();
171 mAttributesModel
->insertRow(row
);
172 const QModelIndex index
= mAttributesModel
->index(row
, 0);
173 Q_ASSERT(index
.isValid());
174 mAttributesModel
->setData(index
, newType
);
175 mAttributesModel
->item(row
, 0)->setEditable(false);
176 mAttributesModel
->setItem(row
, 1, new QStandardItem
);
177 mAttributesModel
->item(row
, 1)->setEditable(true);
180 void TagPropertiesDialog::deleteAttributeClicked()
182 const QModelIndexList selection
= ui
.attrsView
->selectionModel()->selectedRows();
183 if (selection
.count() != 1) {
186 const QString attr
= selection
.first().data().toString();
187 mChangedAttrs
.remove(attr
);
188 mRemovedAttrs
.insert(attr
);
190 mAttributesModel
->removeRow(selection
.first().row());
193 void TagPropertiesDialog::attributeChanged(QStandardItem
*item
)
195 const QString attr
= mAttributesModel
->data(mAttributesModel
->index(item
->row(), 0)).toString();
196 mRemovedAttrs
.remove(attr
);
197 mChangedAttrs
.insert(attr
);
201 void TagPropertiesDialog::addRIDClicked()
203 const QString newResource
= ui
.newRIDEdit
->text();
204 if (newResource
.isEmpty()) {
207 ui
.newRIDEdit
->clear();
209 mChangedRIDs
.insert(newResource
);
210 mRemovedRIDs
.remove(newResource
);
211 // Don't change mChanged here, we will handle this internally
213 const int row
= mRemoteIdsModel
->rowCount();
214 mRemoteIdsModel
->insertRow(row
);
215 const QModelIndex index
= mRemoteIdsModel
->index(row
, 0);
216 Q_ASSERT(index
.isValid());
217 mRemoteIdsModel
->setData(index
, newResource
);
218 mRemoteIdsModel
->item(row
, 0)->setEditable(false);
219 mRemoteIdsModel
->setItem(row
, 1, new QStandardItem
);
220 mRemoteIdsModel
->item(row
, 1)->setEditable(true);
223 void TagPropertiesDialog::deleteRIDClicked()
225 const QModelIndexList selection
= ui
.ridsView
->selectionModel()->selectedRows();
226 if (selection
.count() != 1) {
229 const QString res
= selection
.first().data().toString();
230 mChangedRIDs
.remove(res
);
231 mRemovedRIDs
.insert(res
);
232 // Don't change mChanged here, we will handle this internally
233 mRemoteIdsModel
->removeRow(selection
.first().row());
236 void TagPropertiesDialog::remoteIdChanged(QStandardItem
*item
)
238 const QString res
= mRemoteIdsModel
->data(mRemoteIdsModel
->index(item
->row(), 0)).toString();
239 mRemovedRIDs
.remove(res
);
240 mChangedRIDs
.insert(res
);
241 // Don't change mChanged here, we will handle this internally
244 void TagPropertiesDialog::slotAccept()
246 mChanged
|= (mTag
.type() != ui
.typeEdit
->text().toLatin1());
247 mChanged
|= (mTag
.gid() != ui
.gidEdit
->text().toLatin1());
249 if (!mChanged
&& mChangedRIDs
.isEmpty() && mRemovedRIDs
.isEmpty()) {
254 mTag
.setType(ui
.typeEdit
->text().toLatin1());
255 mTag
.setGid(ui
.gidEdit
->text().toLatin1());
257 Q_FOREACH (const QString
&removedAttr
, mRemovedAttrs
) {
258 mTag
.removeAttribute(removedAttr
.toLatin1());
260 for (int i
= 0; i
< mAttributesModel
->rowCount(); ++i
) {
261 const QModelIndex typeIndex
= mAttributesModel
->index(i
, 0);
262 Q_ASSERT(typeIndex
.isValid());
263 if (!mChangedAttrs
.contains(typeIndex
.data().toString())) {
266 const QModelIndex valueIndex
= mAttributesModel
->index(i
, 1);
267 Attribute
*attr
= AttributeFactory::createAttribute(mAttributesModel
->data(typeIndex
).toString().toLatin1());
271 attr
->deserialize(mAttributesModel
->data(valueIndex
).toString().toLatin1());
272 mTag
.addAttribute(attr
);
276 if (mTag
.isValid() && (!mRemovedRIDs
.isEmpty() || !mChangedRIDs
.isEmpty())) {
277 DbAccess::database().transaction();
280 if (mTag
.isValid() && !mRemovedRIDs
.isEmpty()) {
281 QSqlQuery
query(DbAccess::database());
282 QString queryStr
= QStringLiteral("DELETE FROM TagRemoteIdResourceRelationTable "
283 "WHERE tagId = ? AND "
284 "resourceId IN (SELECT id "
285 "FROM ResourceTable "
288 for (int i
= 0; i
< mRemovedRIDs
.count(); ++i
) {
289 conds
<< QStringLiteral("name = ?");
291 queryStr
+= conds
.join(QStringLiteral(" OR ")) + QLatin1String(")");
292 query
.prepare(queryStr
);
293 query
.addBindValue(mTag
.id());
294 Q_FOREACH (const QString
&removedRid
, mRemovedRIDs
) {
295 query
.addBindValue(removedRid
);
298 qCritical() << query
.executedQuery();
299 qCritical() << query
.lastError().text();
303 if (queryOK
&& mTag
.isValid() && !mChangedRIDs
.isEmpty()) {
304 QMap
<QString
, qint64
> resourceNameToIdMap
;
305 QVector
<qint64
> existingResourceRecords
;
307 QSqlQuery
query(DbAccess::database());
308 QString queryStr
= QStringLiteral("SELECT id, name FROM ResourceTable WHERE ");
310 for (int i
= 0; i
< mChangedRIDs
.count(); ++i
) {
311 conds
<< QStringLiteral("name = ?");
313 queryStr
+= conds
.join(QStringLiteral(" OR "));
314 query
.prepare(queryStr
);
315 Q_FOREACH (const QString
&res
, mChangedRIDs
) {
316 query
.addBindValue(res
);
319 qCritical() << query
.executedQuery();
320 qCritical() << query
.lastError().text();
324 while (query
.next()) {
325 resourceNameToIdMap
[query
.value(1).toString()] = query
.value(0).toLongLong();
329 // This is a workaround for PSQL not supporting UPSERTs
331 QSqlQuery
query(DbAccess::database());
332 query
.prepare(QStringLiteral("SELECT resourceId FROM TagRemoteIdResourceRelationTable WHERE tagId = ?"));
333 query
.addBindValue(mTag
.id());
335 qCritical() << query
.executedQuery();
336 qCritical() << query
.lastError().text();
340 while (query
.next()) {
341 existingResourceRecords
<< query
.value(0).toLongLong();
345 for (int i
= 0; i
< mRemoteIdsModel
->rowCount() && queryOK
; ++i
) {
346 const QModelIndex resIndex
= mRemoteIdsModel
->index(i
, 0);
347 Q_ASSERT(resIndex
.isValid());
348 if (!mChangedRIDs
.contains(resIndex
.data().toString())) {
351 const QModelIndex valueIndex
= mRemoteIdsModel
->index(i
, 1);
353 QSqlQuery
query(DbAccess::database());
354 const qlonglong resourceId
= resourceNameToIdMap
[resIndex
.data().toString()];
355 if (existingResourceRecords
.contains(resourceId
)) {
356 query
.prepare(QStringLiteral("UPDATE TagRemoteIdResourceRelationTable SET remoteId = ? WHERE tagId = ? AND resourceId = ?"));
357 query
.addBindValue(valueIndex
.data().toString());
358 query
.addBindValue(mTag
.id());
359 query
.addBindValue(resourceId
);
361 query
.prepare(QStringLiteral("INSERT INTO TagRemoteIdResourceRelationTable (tagId, resourceId, remoteId) VALUES (?, ?, ?)"));
362 query
.addBindValue(mTag
.id());
363 query
.addBindValue(resourceId
);
364 query
.addBindValue(valueIndex
.data().toString());
367 qCritical() << query
.executedQuery();
368 qCritical() << query
.lastError().text();
375 if (mTag
.isValid() && (!mRemovedRIDs
.isEmpty() || !mChangedRIDs
.isEmpty())) {
377 DbAccess::database().commit();
379 DbAccess::database().rollback();