comparison main.cpp @ 0:fec4d0c461f2

Initial import of the post-prototyping phase code.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 30 Mar 2017 03:20:08 +0300
parents
children db8f47446713
comparison
equal deleted inserted replaced
-1:000000000000 0:fec4d0c461f2
1 //
2 // Syntilista - velkalistasovellus Kampus-kahvilaan
3 // Programmed and designed by Matti Hämäläinen <ccr@tnsp.org>
4 // (C) Copyright 2017 Tecnic Software productions (TNSP)
5 //
6 #include <QApplication>
7 #include <QMessageBox>
8 #include <QSettings>
9 #include "main.h"
10 #include "ui_mainwindow.h"
11 #include "ui_editperson.h"
12
13 static QString query_Person =
14 "SELECT id,last_name,first_name,"
15 "(SELECT SUM(value) FROM transactions WHERE transactions.person=people.id) AS balance,"
16 "updated FROM people";
17
18
19
20 void appError(QString title, QString msg)
21 {
22 QMessageBox::critical(0, title, msg, QMessageBox::Ok);
23 QApplication::exit();
24 }
25
26
27 double moneyStrToValue(const QString &str)
28 {
29 QString str2 = str;
30 return str2.replace(",", ".").toDouble();
31 }
32
33
34 QString moneyValueToStr(double val)
35 {
36 return QStringLiteral("%1").arg(val, 1, 'f', 2);
37 }
38
39
40 QString cleanupStr(const QString &str)
41 {
42 return str.simplified().trimmed();
43 }
44
45
46 const QString dateTimeToStr(const QDateTime &val)
47 {
48 QDateTime tmp = val;
49 tmp.setOffsetFromUtc(0);
50 return tmp.toLocalTime().toString(QStringLiteral("yyyy-MM-dd hh:mm"));
51 }
52
53
54 bool checkAndReportSQLError(const QString where, const QSqlError &err)
55 {
56 if (err.isValid())
57 {
58 printf("SQL Error in %s: %s\n",
59 where.toUtf8().constData(),
60 err.text().toUtf8().constData());
61 return true;
62 }
63 else
64 return false;
65 }
66
67
68 int main(int argc, char *argv[])
69 {
70 QApplication sapp(argc, argv);
71
72 //
73 // Initialize / open SQL database connection
74 //
75 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
76 db.setDatabaseName(qApp->applicationDirPath() + QDir::separator() + "syntilista.sqlite3");
77
78 if (!db.open())
79 {
80 appError(
81 "Cannot open database",
82 "Unable to establish a database connection.\n"
83 "This example needs SQLite support. Please read "
84 "the Qt SQL driver documentation for information how "
85 "to build it."
86 );
87 return false;
88 }
89
90 QSqlQuery query;
91 query.exec(
92 "CREATE TABLE people (id INTEGER PRIMARY KEY, "
93 "first_name VARCHAR(128) NOT NULL, "
94 "last_name VARCHAR(128) NOT NULL, "
95 "extra_info VARCHAR(2048), "
96 "added DATETIME NOT NULL, "
97 "updated DATETIME NOT NULL)");
98
99 checkAndReportSQLError("CREATE TABLE people", query.lastError());
100
101 query.exec(
102 "CREATE TABLE transactions ("
103 "id INTEGER PRIMARY KEY, "
104 "person INT NOT NULL, "
105 "value REAL, "
106 "added DATETIME NOT NULL)");
107
108 checkAndReportSQLError("CREATE TABLE transactions", query.lastError());
109
110 SyntilistaMainWindow swin;
111 swin.show();
112 return sapp.exec();
113 }
114
115
116 //
117 // Main application window code
118 //
119 SyntilistaMainWindow::SyntilistaMainWindow(QWidget *parent) :
120 QMainWindow(parent),
121 ui(new Ui::SyntilistaMainWindow)
122 {
123 ui->setupUi(this);
124 ui->edit_Amount->setValidator(new QDoubleValidator(0, 1000, 2, this));
125
126 readSettings();
127
128 peopleSortIndex = 1;
129 peopleSortOrder = Qt::AscendingOrder;
130 peopleFilter = "";
131
132 model_People = new PersonSQLModel();
133 updatePersonList();
134
135 ui->tableview_People->setModel(model_People);
136 ui->tableview_People->setColumnHidden(0, true);
137 ui->tableview_People->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_People));
138 ui->tableview_People->verticalHeader()->setVisible(false);
139 ui->tableview_People->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
140 ui->tableview_People->setSortingEnabled(true);
141
142 connect(ui->tableview_People->selectionModel(),
143 SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
144 this, SLOT(selectedPersonChanged(const QModelIndex &, const QModelIndex &)));
145
146 connect(ui->tableview_People->horizontalHeader(),
147 SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
148 this,
149 SLOT(updateSortOrder(int, Qt::SortOrder)));
150
151 ui->tableview_People->horizontalHeader()->setSortIndicator(1, Qt::AscendingOrder);
152
153 model_Latest = new TransactionSQLModel();
154 ui->tableview_Latest->setModel(model_Latest);
155 ui->tableview_Latest->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_Latest));
156 ui->tableview_Latest->verticalHeader()->setVisible(false);
157 ui->tableview_Latest->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
158
159 setActivePerson(-1);
160 }
161
162
163 SyntilistaMainWindow::~SyntilistaMainWindow()
164 {
165 printf("QUITTAUS\n");
166
167 saveSettings();
168
169 delete ui;
170 delete model_People;
171 delete model_Latest;
172 }
173
174
175 void SyntilistaMainWindow::statusMsg(const QString &msg)
176 {
177 ui->statusbar->showMessage(msg);
178 }
179
180
181 void SyntilistaMainWindow::readSettings()
182 {
183 QSettings settings(APP_VENDOR, APP_NAME);
184 move(settings.value("pos", QPoint(100, 100)).toPoint());
185 resize(settings.value("size", QSize(1000, 600)).toSize());
186 }
187
188
189 void SyntilistaMainWindow::saveSettings()
190 {
191 QSettings settings(APP_VENDOR, APP_NAME);
192 settings.setValue("pos", pos());
193 settings.setValue("size", size());
194 }
195
196
197 void SyntilistaMainWindow::selectedPersonChanged(const QModelIndex &curr, const QModelIndex &prev)
198 {
199 (void) prev;
200 int row = curr.row();
201 if (row >= 0)
202 {
203 const QAbstractItemModel *model = curr.model();
204 setActivePerson(model->data(model->index(row, 0)).toInt());
205 }
206 else
207 setActivePerson(-1);
208 }
209
210
211 void SyntilistaMainWindow::updateSortOrder(int index, Qt::SortOrder order)
212 {
213 peopleSortIndex = index;
214 peopleSortOrder = order;
215 updatePersonList();
216 }
217
218
219 void SyntilistaMainWindow::setActivePerson(qint64 id)
220 {
221 personID = id;
222
223 if (id >= 0)
224 {
225 QSqlQuery person;
226 person.prepare(query_Person +" WHERE id=?");
227 person.addBindValue(id);
228 person.exec();
229 checkAndReportSQLError("SELECT in setActivePerson()", person.lastError());
230
231 if (!person.next())
232 {
233 statusMsg(tr("ERROR! No person with ID #%1").arg(id));
234 }
235 else
236 {
237 ui->personGB->setEnabled(true);
238 ui->label_PersonName->setText(person.value(1).toString() +", "+ person.value(2).toString());
239
240 double balance = person.value(3).toDouble();
241 ui->label_BalanceValue->setText(moneyValueToStr(balance));
242 ui->label_BalanceValue->setStyleSheet(balance < 0 ? "color: red;" : "color: green;");
243
244 QSqlQuery query;
245 query.prepare("SELECT id,value,added FROM transactions WHERE person=? ORDER BY added DESC LIMIT 5");
246 query.addBindValue(id);
247 query.exec();
248 checkAndReportSQLError("SELECT transactions for tableview_Latest", query.lastError());
249
250 model_Latest->setQuery(query);
251
252 model_Latest->setHeaderData(0, Qt::Horizontal, "ID");
253 model_Latest->setHeaderData(1, Qt::Horizontal, "Summa");
254 model_Latest->setHeaderData(2, Qt::Horizontal, "Aika");
255
256 ui->tableview_Latest->setModel(model_Latest);
257 ui->tableview_Latest->setColumnHidden(0, true);
258 ui->tableview_Latest->verticalHeader()->setVisible(false);
259 ui->tableview_Latest->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
260
261 return; // Ugly
262 }
263 }
264
265 // In case of id < 0 or errors ..
266 ui->personGB->setEnabled(false);
267 ui->edit_Amount->clear();
268 ui->label_BalanceValue->setText("--");
269 ui->label_BalanceValue->setStyleSheet(NULL);
270 ui->label_PersonName->setText("???");
271 ui->tableview_Latest->setModel(NULL);
272 }
273
274
275 //
276 // Widget slot handlers
277 //
278 void SyntilistaMainWindow::on_button_Quit_clicked()
279 {
280 close();
281 }
282
283
284 void SyntilistaMainWindow::on_button_AddPerson_clicked()
285 {
286 EditPerson *person = new EditPerson(this);
287 person->setPerson(-1);
288 }
289
290
291 void SyntilistaMainWindow::on_button_EditPerson_clicked()
292 {
293 EditPerson *person = new EditPerson(this);
294 person->setPerson(personID);
295 }
296
297
298 void SyntilistaMainWindow::on_tableview_People_doubleClicked(const QModelIndex &curr)
299 {
300 int row = curr.row();
301 if (row >= 0)
302 {
303 const QAbstractItemModel *model = curr.model();
304 setActivePerson(model->data(model->index(row, 0)).toInt());
305
306 EditPerson *person = new EditPerson(this);
307 person->setPerson(personID);
308 }
309 else
310 setActivePerson(-1);
311 }
312
313
314 void SyntilistaMainWindow::on_button_ClearFilter_clicked()
315 {
316 ui->edit_PersonFilter->clear();
317 }
318
319
320 void SyntilistaMainWindow::updatePersonData(qint64 id)
321 {
322 printf("updatePersonData(%lld)\n", id);
323 if (id == personID)
324 setActivePerson(id);
325
326 model_People->updateModel();
327 }
328
329
330 void SyntilistaMainWindow::updatePersonList()
331 {
332 QSqlQuery query;
333 QString queryDir, querySort = QStringLiteral("");
334
335 if (peopleSortOrder == Qt::AscendingOrder)
336 queryDir = QStringLiteral("ASC");
337 else
338 queryDir = QStringLiteral("DESC");
339
340 switch (peopleSortIndex)
341 {
342 case 1:
343 case 2:
344 querySort = QStringLiteral(" ORDER BY last_name ") + queryDir + QStringLiteral(",first_name ") + queryDir;
345 break;
346
347 case 3:
348 querySort = QStringLiteral(" ORDER BY balance ") + queryDir;
349 break;
350
351 case 4:
352 querySort = QStringLiteral(" ORDER BY updated ") + queryDir;
353 break;
354 }
355
356 if (peopleFilter != "")
357 {
358 QString tmp = "%"+ peopleFilter +"%";
359 query.prepare(query_Person +" WHERE first_name LIKE ? OR last_name LIKE ?" + querySort);
360
361 query.addBindValue(tmp);
362 query.addBindValue(tmp);
363 }
364 else
365 {
366 query.prepare(query_Person + querySort);
367 }
368
369 checkAndReportSQLError("updatePersonList() before exec", query.lastError());
370 query.exec();
371 checkAndReportSQLError("updatePersonList() after exec", query.lastError());
372
373 model_People->setQuery(query);
374
375 model_People->setHeaderData(0, Qt::Horizontal, "ID");
376 model_People->setHeaderData(1, Qt::Horizontal, "Sukunimi");
377 model_People->setHeaderData(2, Qt::Horizontal, "Etunimi");
378 model_People->setHeaderData(3, Qt::Horizontal, "Tase");
379 model_People->setHeaderData(4, Qt::Horizontal, "Muutettu");
380 }
381
382
383 void SyntilistaMainWindow::on_edit_PersonFilter_textChanged(const QString &str)
384 {
385 peopleFilter = cleanupStr(str);
386 updatePersonList();
387 }
388
389
390 void SyntilistaMainWindow::on_button_XXX_clicked()
391 {
392 // printf("XXX-namiskaa painettu!\n");
393 }
394
395
396 bool SyntilistaMainWindow::addTransaction(bool debt, double value)
397 {
398 if (personID <= 0)
399 return false;
400
401 QSqlQuery person;
402 person.prepare("SELECT * FROM people WHERE id=?");
403 person.addBindValue(personID);
404 person.exec();
405 person.next();
406
407 if (value != 0)
408 {
409 QSqlQuery query;
410 query.prepare("INSERT INTO transactions (person,value,added) VALUES (?,?,?)");
411 query.addBindValue(personID);
412 query.addBindValue(debt ? -value : value);
413 query.addBindValue(QDateTime::currentDateTimeUtc());
414 query.exec();
415 checkAndReportSQLError("addTransaction()", query.lastError());
416
417 query.prepare("UPDATE people SET updated=? WHERE id=?");
418 query.addBindValue(QDateTime::currentDateTimeUtc());
419 query.addBindValue(personID);
420 query.exec();
421 checkAndReportSQLError("addTransaction update timestamp", query.lastError());
422
423 QSqlDatabase::database().commit();
424
425 ui->edit_Amount->clear();
426 updatePersonData(personID);
427
428 QString str;
429 if (debt)
430 {
431 str = tr("Lisättiin velkaa %1 EUR henkilölle '%2 %3' (#%4).").
432 arg(value, 1, 'f', 2).
433 arg(person.value(1).toString()).
434 arg(person.value(2).toString()).
435 arg(person.value(0).toInt());
436 }
437 else
438 {
439 str = tr("Vähennettiin velkaa %1 EUR henkilöltä '%2 %3' (#%4).").
440 arg(value, 1, 'f', 2).
441 arg(person.value(1).toString()).
442 arg(person.value(2).toString()).
443 arg(person.value(0).toInt());
444 }
445
446
447 statusMsg(str);
448 return true;
449 }
450 else
451 {
452 QString tmp = (debt ? "lisätty" : "vähennetty");
453 statusMsg("Velkaa ei "+ tmp +" koska summaa ei määritetty.");
454 return false;
455 }
456 }
457
458
459 void SyntilistaMainWindow::on_button_AddDebt_clicked()
460 {
461 addTransaction(true, moneyStrToValue(ui->edit_Amount->text()));
462 }
463
464
465 void SyntilistaMainWindow::on_button_SubDebt_clicked()
466 {
467 addTransaction(false, moneyStrToValue(ui->edit_Amount->text()));
468 }
469
470
471 //
472 // Edit person dialog
473 //
474 EditPerson::EditPerson(QWidget *parent) :
475 QDialog(parent),
476 ui(new Ui::EditPerson)
477 {
478 ui->setupUi(this);
479
480 setModal(true);
481 setAttribute(Qt::WA_DeleteOnClose);
482 show();
483 activateWindow();
484 raise();
485 setFocus();
486
487 model_Transactions = new TransactionSQLModel();
488 ui->tableview_Transactions->setModel(model_Transactions);
489 ui->tableview_Transactions->setItemDelegate(new QSqlRelationalDelegate(ui->tableview_Transactions));
490 ui->tableview_Transactions->verticalHeader()->setVisible(false);
491 ui->tableview_Transactions->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
492
493 validateForm();
494 }
495
496
497 EditPerson::~EditPerson()
498 {
499 delete ui;
500 delete model_Transactions;
501 }
502
503
504 void EditPerson::statusMsg(const QString &msg)
505 {
506 dynamic_cast<SyntilistaMainWindow *>(parent())->statusMsg(msg);
507 }
508
509
510 bool EditPerson::validateForm(PersonInfo &info)
511 {
512 info.firstName = cleanupStr(ui->edit_FirstName->text());
513 info.lastName = cleanupStr(ui->edit_LastName->text());
514
515 ui->edit_FirstName->setStyleSheet(info.firstName == "" ? "background-color: red;" : NULL);
516 ui->edit_LastName->setStyleSheet(info.lastName == "" ? "background-color: red;" : NULL);
517
518 return info.firstName != "" && info.lastName != "";
519 }
520
521
522 bool EditPerson::validateForm()
523 {
524 PersonInfo info;
525 return validateForm(info);
526 }
527
528
529 void EditPerson::on_button_Cancel_clicked()
530 {
531 close();
532 }
533
534
535 void EditPerson::on_button_OK_clicked()
536 {
537 PersonInfo info;
538 info.id = personID;
539 info.extraInfo = ui->textedit_ExtraInfo->document()->toPlainText();
540
541 if (!validateForm(info))
542 return;
543
544 if (info.id >= 0)
545 {
546 QSqlQuery person;
547 person.prepare("SELECT * FROM people WHERE id <> ? AND first_name=? AND last_name=?");
548 person.addBindValue(info.id);
549 person.addBindValue(info.firstName);
550 person.addBindValue(info.lastName);
551 person.exec();
552
553 checkAndReportSQLError("SELECT check for existing person by same name (UPDATE)", person.lastError());
554
555 if (person.next())
556 {
557 statusMsg(tr("Ei pysty! Samalla nimellä on jo henkilö!"));
558 return;
559 }
560
561 dynamic_cast<SyntilistaMainWindow *>(parent())->model_People->updatePerson(QModelIndex(), info);
562
563 statusMsg(tr("Päivitettiin henkilö '%1 %2' (#%3).").
564 arg(info.firstName).arg(info.lastName).arg(info.id));
565 }
566 else
567 {
568 QSqlQuery person;
569 person.prepare("SELECT * FROM people WHERE first_name=? AND last_name=?");
570 person.addBindValue(info.firstName);
571 person.addBindValue(info.lastName);
572 person.exec();
573
574 checkAndReportSQLError("SELECT check for existing person by same name (ADD)", person.lastError());
575
576 if (person.next())
577 {
578 statusMsg(tr("Ei pysty! Samalla nimellä on jo henkilö!"));
579 return;
580 }
581
582 dynamic_cast<SyntilistaMainWindow *>(parent())->model_People->addPerson(info);
583 dynamic_cast<SyntilistaMainWindow *>(parent())->updatePersonList();
584
585 statusMsg(tr("Lisättiin uusi henkilö '%1 %2'.").
586 arg(info.firstName).arg(info.lastName));
587 }
588
589 close();
590 }
591
592
593 void EditPerson::on_edit_FirstName_textChanged(const QString &arg1)
594 {
595 (void) arg1;
596 validateForm();
597 }
598
599
600 void EditPerson::on_edit_LastName_textChanged(const QString &arg1)
601 {
602 (void) arg1;
603 validateForm();
604 }
605
606
607 void EditPerson::clearForm()
608 {
609 ui->edit_FirstName->clear();
610 ui->edit_LastName->clear();
611 ui->textedit_ExtraInfo->document()->clear();
612 ui->edit_FirstName->setFocus();
613 }
614
615
616 void EditPerson::setPerson(qint64 id)
617 {
618 personID = id;
619
620 if (id >= 0)
621 {
622 QSqlQuery person;
623 person.prepare("SELECT * FROM people WHERE id=?");
624 person.addBindValue(id);
625 person.exec();
626 checkAndReportSQLError("SELECT in EditPerson::setPerson()", person.lastError());
627
628 if (!person.next())
629 {
630 statusMsg(tr("ERROR! No person with ID #%1").arg(id));
631 }
632 else
633 {
634 ui->edit_FirstName->setText(person.value(1).toString());
635 ui->edit_LastName->setText(person.value(2).toString());
636 ui->textedit_ExtraInfo->document()->setPlainText(person.value(3).toString());
637
638 QSqlQuery query;
639 query.prepare("SELECT id,value,added FROM transactions WHERE person=? ORDER BY added DESC");
640 query.addBindValue(id);
641 query.exec();
642 checkAndReportSQLError("SELECT transactions for tableview_Transactions", query.lastError());
643
644 model_Transactions->setQuery(query);
645
646 model_Transactions->setHeaderData(0, Qt::Horizontal, "ID");
647 model_Transactions->setHeaderData(1, Qt::Horizontal, "Summa");
648 model_Transactions->setHeaderData(2, Qt::Horizontal, "Aika");
649
650 ui->tableview_Transactions->setModel(model_Transactions);
651 ui->tableview_Transactions->setColumnHidden(0, true);
652
653 return; // Ugly
654 }
655 }
656
657 // In case of id < 0 or errors ..
658 clearForm();
659 ui->tableview_Transactions->setModel(NULL);
660 }
661
662
663 //
664 // Custom SQL models
665 //
666 PersonSQLModel::PersonSQLModel(QObject *parent) : QSqlQueryModel(parent)
667 {
668 }
669
670
671 QVariant PersonSQLModel::data(const QModelIndex &index, int role) const
672 {
673 QVariant value = QSqlQueryModel::data(index, role);
674
675 if (value.isValid() && role == Qt::DisplayRole)
676 {
677 switch (index.column())
678 {
679 case 3:
680 return moneyValueToStr(value.toDouble());
681
682 case 4:
683 return dateTimeToStr(value.toDateTime());
684 }
685 }
686
687 if (index.column() == 3 && role == Qt::ForegroundRole)
688 {
689 double val = QSqlQueryModel::data(index, Qt::DisplayRole).toDouble();
690 if (val < 0)
691 return QVariant::fromValue(QColor(Qt::red));
692 else
693 return QVariant::fromValue(QColor(Qt::green));
694 }
695
696 return value;
697 }
698
699
700 void PersonSQLModel::updatePerson(const QModelIndex &item, const PersonInfo &person)
701 {
702 QSqlQuery query;
703 query.prepare("UPDATE people SET first_name=?,last_name=?,extra_info=?,updated=? WHERE id=?");
704 query.addBindValue(person.firstName);
705 query.addBindValue(person.lastName);
706 query.addBindValue(person.extraInfo);
707 query.addBindValue(QDateTime::currentDateTimeUtc());
708 query.addBindValue(person.id);
709 query.exec();
710
711 checkAndReportSQLError("PersonSQLModel::updatePerson()", query.lastError());
712 QSqlDatabase::database().commit();
713
714 updateModel();
715 }
716
717
718 void PersonSQLModel::addPerson(const PersonInfo &person)
719 {
720 // beginInsertRows(QModelIndex(), rowCount(), rowCount());
721
722 QSqlQuery np;
723 np.prepare("INSERT INTO people (first_name,last_name,extra_info,added,updated) VALUES (?,?,?,?,?)");
724 np.addBindValue(person.firstName);
725 np.addBindValue(person.lastName);
726 np.addBindValue(person.extraInfo);
727 np.addBindValue(QDateTime::currentDateTimeUtc());
728 np.addBindValue(QDateTime::currentDateTimeUtc());
729 np.exec();
730
731 checkAndReportSQLError("PersonSQLModel::addPerson()", np.lastError());
732 QSqlDatabase::database().commit();
733
734 // endInsertRows();
735 updateModel();
736 }
737
738
739 void PersonSQLModel::updateModel()
740 {
741 printf("PersonSQLModel::updateModelInfo()\n");
742 query().exec();
743 emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
744 }
745
746
747 TransactionSQLModel::TransactionSQLModel(QObject *parent) : QSqlQueryModel(parent)
748 {
749 }
750
751
752 QVariant TransactionSQLModel::data(const QModelIndex &index, int role) const
753 {
754 QVariant value = QSqlQueryModel::data(index, role);
755
756 if (value.isValid() && role == Qt::DisplayRole)
757 {
758 switch (index.column())
759 {
760 case 1:
761 return moneyValueToStr(value.toDouble());
762
763 case 2:
764 return dateTimeToStr(value.toDateTime());
765 }
766 }
767
768 if (index.column() == 1 && role == Qt::ForegroundRole)
769 {
770 double val = QSqlQueryModel::data(index, Qt::DisplayRole).toDouble();
771 if (val < 0)
772 return QVariant::fromValue(QColor(Qt::red));
773 else
774 return QVariant::fromValue(QColor(Qt::green));
775 }
776
777 return value;
778 }
779
780
781 void TransactionSQLModel::updateModel()
782 {
783 printf("TransactionSQLModel::updateModelInfo()\n");
784 query().exec();
785 emit dataChanged(QModelIndex(), QModelIndex());
786 }