1 /*
2 * Copyright 2012 Alessandro Russo <axela74@yahoo.it>
3 * Copyright 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "ktagsview.h"
20 #include "ktagsview_p.h"
21
22 // ----------------------------------------------------------------------------
23 // QT Includes
24
25 #include <QTimer>
26 #include <QMenu>
27
28 // ----------------------------------------------------------------------------
29 // KDE Includes
30
31 #include <KMessageBox>
32 #include <KHelpClient>
33
34 // ----------------------------------------------------------------------------
35 // Project Includes
36
37 #include "mymoneyexception.h"
38 #include "mymoneymoney.h"
39 #include "mymoneyprice.h"
40 #include "kmymoneysettings.h"
41 #include "ktagreassigndlg.h"
42 #include "kmymoneyutils.h"
43 #include "kmymoneymvccombo.h"
44 #include "mymoneysecurity.h"
45 #include "mymoneysplit.h"
46 #include "mymoneytransaction.h"
47 #include "mymoneyschedule.h"
48 #include "transaction.h"
49 #include "menuenums.h"
50
51 using namespace Icons;
52
53 /* -------------------------------------------------------------------------*/
54 /* KTransactionPtrVector */
55 /* -------------------------------------------------------------------------*/
56
57 // *** KTagsView Implementation ***
KTagsView(QWidget * parent)58 KTagsView::KTagsView(QWidget *parent) :
59 KMyMoneyViewBase(*new KTagsViewPrivate(this), parent)
60 {
61 typedef void(KTagsView::*KTagsViewFunc)();
62 const QHash<eMenu::Action, KTagsViewFunc> actionConnections {
63 {eMenu::Action::NewTag, &KTagsView::slotNewTag},
64 {eMenu::Action::RenameTag, &KTagsView::slotRenameTag},
65 {eMenu::Action::DeleteTag, &KTagsView::slotDeleteTag}
66 };
67
68 for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a)
69 connect(pActions[a.key()], &QAction::triggered, this, a.value());
70 }
71
~KTagsView()72 KTagsView::~KTagsView()
73 {
74 }
75
executeCustomAction(eView::Action action)76 void KTagsView::executeCustomAction(eView::Action action)
77 {
78 switch(action) {
79 case eView::Action::Refresh:
80 refresh();
81 break;
82
83 case eView::Action::SetDefaultFocus:
84 {
85 Q_D(KTagsView);
86 QTimer::singleShot(0, d->m_searchWidget, SLOT(setFocus()));
87 }
88 break;
89
90 default:
91 break;
92 }
93 }
94
refresh()95 void KTagsView::refresh()
96 {
97 Q_D(KTagsView);
98 if (isVisible()) {
99 if (d->m_inSelection)
100 QTimer::singleShot(0, this, SLOT(refresh()));
101 else
102 loadTags();
103 d->m_needsRefresh = false;
104 } else {
105 d->m_needsRefresh = true;
106 }
107 }
108
slotStartRename(QListWidgetItem * item)109 void KTagsView::slotStartRename(QListWidgetItem* item)
110 {
111 Q_D(KTagsView);
112 d->m_allowEditing = true;
113 d->ui->m_tagsList->editItem(item);
114 }
115
116 // This variant is only called when a single tag is selected and renamed.
slotRenameSingleTag(QListWidgetItem * ta)117 void KTagsView::slotRenameSingleTag(QListWidgetItem* ta)
118 {
119 Q_D(KTagsView);
120 //if there is no current item selected, exit
121 if (d->m_allowEditing == false || !d->ui->m_tagsList->currentItem() || ta != d->ui->m_tagsList->currentItem())
122 return;
123
124 //qDebug() << "[KTagsView::slotRenameTag]";
125 // create a copy of the new name without appended whitespaces
126 auto new_name = ta->text();
127 if (d->m_tag.name() != new_name) {
128 MyMoneyFileTransaction ft;
129 try {
130 // check if we already have a tag with the new name
131 try {
132 // this function call will throw an exception, if the tag
133 // hasn't been found.
134 MyMoneyFile::instance()->tagByName(new_name);
135 // the name already exists, ask the user whether he's sure to keep the name
136 if (KMessageBox::questionYesNo(this,
137 i18n("A tag with the name '%1' already exists. It is not advisable to have "
138 "multiple tags with the same identification name. Are you sure you would like "
139 "to rename the tag?", new_name)) != KMessageBox::Yes) {
140 ta->setText(d->m_tag.name());
141 return;
142 }
143 } catch (const MyMoneyException &) {
144 // all ok, the name is unique
145 }
146
147 d->m_tag.setName(new_name);
148 d->m_newName = new_name;
149 MyMoneyFile::instance()->modifyTag(d->m_tag);
150
151 // the above call to modifyTag will reload the view so
152 // all references and pointers to the view have to be
153 // re-established.
154
155 // make sure, that the record is visible even if it moved
156 // out of sight due to the rename operation
157 ensureTagVisible(d->m_tag.id());
158
159 ft.commit();
160
161 } catch (const MyMoneyException &e) {
162 KMessageBox::detailedSorry(this, i18n("Unable to modify tag"), QString::fromLatin1(e.what()));
163 }
164 } else {
165 ta->setText(new_name);
166 }
167 }
168
ensureTagVisible(const QString & id)169 void KTagsView::ensureTagVisible(const QString& id)
170 {
171 Q_D(KTagsView);
172 for (int i = 0; i < d->ui->m_tagsList->count(); ++i) {
173 KTagListItem* ta = dynamic_cast<KTagListItem*>(d->ui->m_tagsList->item(0));
174 if (ta && ta->tag().id() == id) {
175 d->ui->m_tagsList->scrollToItem(ta, QAbstractItemView::PositionAtCenter);
176
177 d->ui->m_tagsList->setCurrentItem(ta); // active item and deselect all others
178 d->ui->m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
179 break;
180 }
181 }
182 }
183
selectedTags(QList<MyMoneyTag> & tagsList) const184 void KTagsView::selectedTags(QList<MyMoneyTag>& tagsList) const
185 {
186 Q_D(const KTagsView);
187 QList<QListWidgetItem *> selectedItems = d->ui->m_tagsList->selectedItems();
188 QList<QListWidgetItem *>::ConstIterator itemsIt = selectedItems.constBegin();
189 while (itemsIt != selectedItems.constEnd()) {
190 KTagListItem* item = dynamic_cast<KTagListItem*>(*itemsIt);
191 if (item)
192 tagsList << item->tag();
193 ++itemsIt;
194 }
195 }
196
slotSelectTag(QListWidgetItem * cur,QListWidgetItem * prev)197 void KTagsView::slotSelectTag(QListWidgetItem* cur, QListWidgetItem* prev)
198 {
199 Q_D(KTagsView);
200 Q_UNUSED(cur);
201 Q_UNUSED(prev);
202
203 d->m_allowEditing = false;
204 }
205
slotSelectTag()206 void KTagsView::slotSelectTag()
207 {
208 Q_D(KTagsView);
209 // check if the content of a currently selected tag was modified
210 // and ask to store the data
211 if (d->ui->m_updateButton->isEnabled()) {
212 if (KMessageBox::questionYesNo(this, QString("<qt>%1</qt>").arg(
213 i18n("Do you want to save the changes for <b>%1</b>?", d->m_newName)),
214 i18n("Save changes")) == KMessageBox::Yes) {
215 d->m_inSelection = true;
216 slotUpdateTag();
217 d->m_inSelection = false;
218 }
219 }
220 // loop over all tags and count the number of tags, also
221 // obtain last selected tag
222 QList<MyMoneyTag> tagsList;
223 selectedTags(tagsList);
224 slotSelectTags(tagsList);
225
226 if (tagsList.isEmpty()) {
227 d->ui->m_tabWidget->setEnabled(false); // disable tab widget
228 d->ui->m_balanceLabel->hide();
229 d->ui->m_deleteButton->setEnabled(false); //disable delete and rename button
230 d->ui->m_renameButton->setEnabled(false);
231 clearItemData();
232 d->m_tag = MyMoneyTag();
233 return; // make sure we don't access an undefined tag
234 }
235
236 d->ui->m_deleteButton->setEnabled(true); //re-enable delete button
237
238 // if we have multiple tags selected, clear and disable the tag information
239 if (tagsList.count() > 1) {
240 d->ui->m_tabWidget->setEnabled(false); // disable tab widget
241 d->ui->m_renameButton->setEnabled(false); // disable also the rename button
242 d->ui->m_balanceLabel->hide();
243 clearItemData();
244 } else d->ui->m_renameButton->setEnabled(true);
245
246 // otherwise we have just one selected, enable tag information widget and renameButton
247 d->ui->m_tabWidget->setEnabled(true);
248 d->ui->m_balanceLabel->show();
249
250 // as of now we are updating only the last selected tag, and until
251 // selection mode of the QListView has been changed to Extended, this
252 // will also be the only selection and behave exactly as before - Andreas
253 try {
254 d->m_tag = tagsList[0];
255
256 d->m_newName = d->m_tag.name();
257 d->ui->m_colorbutton->setEnabled(true);
258 d->ui->m_colorbutton->setColor(d->m_tag.tagColor());
259 d->ui->m_closed->setEnabled(true);
260 d->ui->m_closed->setChecked(d->m_tag.isClosed());
261 d->ui->m_notes->setEnabled(true);
262 d->ui->m_notes->setText(d->m_tag.notes());
263 slotTagDataChanged();
264
265 showTransactions();
266
267 } catch (const MyMoneyException &e) {
268 qDebug("exception during display of tag: %s", e.what());
269 d->ui->m_register->clear();
270 d->m_tag = MyMoneyTag();
271 }
272 d->m_allowEditing = true;
273 }
274
clearItemData()275 void KTagsView::clearItemData()
276 {
277 Q_D(KTagsView);
278 d->ui->m_colorbutton->setColor(QColor());
279 d->ui->m_closed->setChecked(false);
280 d->ui->m_notes->setText(QString());
281 showTransactions();
282 }
283
showTransactions()284 void KTagsView::showTransactions()
285 {
286 Q_D(KTagsView);
287 MyMoneyMoney balance;
288 auto file = MyMoneyFile::instance();
289 MyMoneySecurity base = file->baseCurrency();
290
291 // setup sort order
292 d->ui->m_register->setSortOrder(KMyMoneySettings::sortSearchView());
293
294 // clear the register
295 d->ui->m_register->clear();
296
297 if (d->m_tag.id().isEmpty() || !d->ui->m_tabWidget->isEnabled()) {
298 d->ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
299 return;
300 }
301
302 // setup the list and the pointer vector
303 MyMoneyTransactionFilter filter;
304 filter.setConsiderCategorySplits();
305 filter.addTag(d->m_tag.id());
306 filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate());
307
308 // retrieve the list from the engine
309 file->transactionList(d->m_transactionList, filter);
310
311 // create the elements for the register
312 QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
313 QMap<QString, int> uniqueMap;
314 MyMoneyMoney deposit, payment;
315
316 int splitCount = 0;
317 bool balanceAccurate = true;
318 for (it = d->m_transactionList.constBegin(); it != d->m_transactionList.constEnd(); ++it) {
319 const MyMoneySplit& split = (*it).second;
320 MyMoneyAccount acc = file->account(split.accountId());
321 ++splitCount;
322 uniqueMap[(*it).first.id()]++;
323
324 KMyMoneyRegister::Register::transactionFactory(d->ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
325
326 // take care of foreign currencies
327 MyMoneyMoney val = split.shares().abs();
328 if (acc.currencyId() != base.id()) {
329 const MyMoneyPrice &price = file->price(acc.currencyId(), base.id());
330 // in case the price is valid, we use it. Otherwise, we keep
331 // a flag that tells us that the balance is somewhat inaccurate
332 if (price.isValid()) {
333 val *= price.rate(base.id());
334 } else {
335 balanceAccurate = false;
336 }
337 }
338
339 if (split.shares().isNegative()) {
340 payment += val;
341 } else {
342 deposit += val;
343 }
344 }
345 balance = deposit - payment;
346
347 // add the group markers
348 d->ui->m_register->addGroupMarkers();
349
350 // sort the transactions according to the sort setting
351 d->ui->m_register->sortItems();
352
353 // remove trailing and adjacent markers
354 d->ui->m_register->removeUnwantedGroupMarkers();
355
356 d->ui->m_register->updateRegister(true);
357
358 // we might end up here with updates disabled on the register so
359 // make sure that we enable updates here
360 d->ui->m_register->setUpdatesEnabled(true);
361 d->ui->m_balanceLabel->setText(i18n("Balance: %1%2",
362 balanceAccurate ? "" : "~",
363 balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
364 }
365
slotTagDataChanged()366 void KTagsView::slotTagDataChanged()
367 {
368 Q_D(KTagsView);
369 auto rc = false;
370
371 if (d->ui->m_tabWidget->isEnabled()) {
372 rc |= ((d->m_tag.tagColor().isValid() != d->ui->m_colorbutton->color().isValid())
373 || (d->ui->m_colorbutton->color().isValid() && d->m_tag.tagColor() != d->ui->m_colorbutton->color()));
374 rc |= (d->ui->m_closed->isChecked() != d->m_tag.isClosed());
375 rc |= ((d->m_tag.notes().isEmpty() != d->ui->m_notes->toPlainText().isEmpty())
376 || (!d->ui->m_notes->toPlainText().isEmpty() && d->m_tag.notes() != d->ui->m_notes->toPlainText()));
377 }
378 d->ui->m_updateButton->setEnabled(rc);
379 }
380
slotUpdateTag()381 void KTagsView::slotUpdateTag()
382 {
383 Q_D(KTagsView);
384 if (d->ui->m_updateButton->isEnabled()) {
385 MyMoneyFileTransaction ft;
386 d->ui->m_updateButton->setEnabled(false);
387 try {
388 d->m_tag.setName(d->m_newName);
389 d->m_tag.setTagColor(d->ui->m_colorbutton->color());
390 d->m_tag.setClosed(d->ui->m_closed->isChecked());
391 d->m_tag.setNotes(d->ui->m_notes->toPlainText());
392
393 MyMoneyFile::instance()->modifyTag(d->m_tag);
394 ft.commit();
395
396 } catch (const MyMoneyException &e) {
397 KMessageBox::detailedSorry(this, i18n("Unable to modify tag"), QString::fromLatin1(e.what()));
398 }
399 }
400 }
401
showEvent(QShowEvent * event)402 void KTagsView::showEvent(QShowEvent* event)
403 {
404 if (MyMoneyFile::instance()->storageAttached()) {
405 Q_D(KTagsView);
406 if (d->m_needLoad)
407 d->init();
408
409 emit customActionRequested(View::Tags, eView::Action::AboutToShow);
410
411 if (d->m_needsRefresh)
412 refresh();
413
414 QList<MyMoneyTag> list;
415 selectedTags(list);
416 slotSelectTags(list);
417 }
418
419 // don't forget base class implementation
420 QWidget::showEvent(event);
421 }
422
updateTagActions(const QList<MyMoneyTag> & tags)423 void KTagsView::updateTagActions(const QList<MyMoneyTag>& tags)
424 {
425 pActions[eMenu::Action::NewTag]->setEnabled(true);
426 const auto tagsCount = tags.count();
427 auto b = tagsCount == 1 ? true : false;
428 pActions[eMenu::Action::RenameTag]->setEnabled(b);
429 b = tagsCount >= 1 ? true : false;
430 pActions[eMenu::Action::DeleteTag]->setEnabled(b);
431 }
432
loadTags()433 void KTagsView::loadTags()
434 {
435 Q_D(KTagsView);
436 if (d->m_inSelection)
437 return;
438
439 QMap<QString, bool> isSelected;
440 QString id;
441 MyMoneyFile* file = MyMoneyFile::instance();
442
443 // remember which items are selected in the list
444 QList<QListWidgetItem *> selectedItems = d->ui->m_tagsList->selectedItems();
445 QList<QListWidgetItem *>::const_iterator tagsIt = selectedItems.constBegin();
446
447 while (tagsIt != selectedItems.constEnd()) {
448 KTagListItem* item = dynamic_cast<KTagListItem*>(*tagsIt);
449 if (item)
450 isSelected[item->tag().id()] = true;
451 ++tagsIt;
452 }
453
454 // keep current selected item
455 KTagListItem *currentItem = static_cast<KTagListItem *>(d->ui->m_tagsList->currentItem());
456 if (currentItem)
457 id = currentItem->tag().id();
458
459 d->m_allowEditing = false;
460 // clear the list
461 d->m_searchWidget->clear();
462 d->m_searchWidget->updateSearch();
463 d->ui->m_tagsList->clear();
464 d->ui->m_register->clear();
465 currentItem = 0;
466
467 QList<MyMoneyTag>list = file->tagList();
468 QList<MyMoneyTag>::ConstIterator it;
469
470 for (it = list.constBegin(); it != list.constEnd(); ++it) {
471 if (d->m_tagFilterType == (int)eView::Tag::All ||
472 (d->m_tagFilterType == (int)eView::Tag::Referenced && file->isReferenced(*it)) ||
473 (d->m_tagFilterType == (int)eView::Tag::Unused && !file->isReferenced(*it)) ||
474 (d->m_tagFilterType == (int)eView::Tag::Opened && !(*it).isClosed()) ||
475 (d->m_tagFilterType == (int)eView::Tag::Closed && (*it).isClosed())) {
476 KTagListItem* item = new KTagListItem(d->ui->m_tagsList, *it);
477 if (item->tag().id() == id)
478 currentItem = item;
479 if (isSelected[item->tag().id()])
480 item->setSelected(true);
481 }
482 }
483 d->ui->m_tagsList->sortItems();
484
485 if (currentItem) {
486 d->ui->m_tagsList->setCurrentItem(currentItem);
487 d->ui->m_tagsList->scrollToItem(currentItem);
488 }
489
490 slotSelectTag(0, 0);
491 d->m_allowEditing = true;
492 }
493
slotSelectTransaction()494 void KTagsView::slotSelectTransaction()
495 {
496 Q_D(KTagsView);
497 QList<KMyMoneyRegister::RegisterItem*> list = d->ui->m_register->selectedItems();
498 if (!list.isEmpty()) {
499 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(list[0]);
500 if (t)
501 emit selectByVariant(QVariantList {QVariant(t->split().accountId()), QVariant(t->transaction().id())}, eView::Intent::ShowTransaction);
502 }
503 }
504
slotSelectTagAndTransaction(const QString & tagId,const QString & accountId,const QString & transactionId)505 void KTagsView::slotSelectTagAndTransaction(const QString& tagId, const QString& accountId, const QString& transactionId)
506 {
507 if (!isVisible())
508 return;
509
510 Q_D(KTagsView);
511 try {
512 // clear filter
513 d->m_searchWidget->clear();
514 d->m_searchWidget->updateSearch();
515
516 // deselect all other selected items
517 QList<QListWidgetItem *> selectedItems = d->ui->m_tagsList->selectedItems();
518 QList<QListWidgetItem *>::const_iterator tagsIt = selectedItems.constBegin();
519 while (tagsIt != selectedItems.constEnd()) {
520 KTagListItem* item = dynamic_cast<KTagListItem*>(*tagsIt);
521 if (item)
522 item->setSelected(false);
523 ++tagsIt;
524 }
525
526 // find the tag in the list
527 QListWidgetItem* it;
528 for (int i = 0; i < d->ui->m_tagsList->count(); ++i) {
529 it = d->ui->m_tagsList->item(i);
530 KTagListItem* item = dynamic_cast<KTagListItem *>(it);
531 if (item && item->tag().id() == tagId) {
532 d->ui->m_tagsList->scrollToItem(it, QAbstractItemView::PositionAtCenter);
533
534 d->ui->m_tagsList->setCurrentItem(it); // active item and deselect all others
535 d->ui->m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
536
537 //make sure the tag selection is updated and transactions are updated accordingly
538 slotSelectTag();
539
540 KMyMoneyRegister::RegisterItem *registerItem = 0;
541 for (i = 0; i < d->ui->m_register->rowCount(); ++i) {
542 registerItem = d->ui->m_register->itemAtRow(i);
543 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(registerItem);
544 if (t) {
545 if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) {
546 d->ui->m_register->selectItem(registerItem);
547 d->ui->m_register->ensureItemVisible(registerItem);
548 break;
549 }
550 }
551 }
552 // quit out of outer for() loop
553 break;
554 }
555 }
556 } catch (const MyMoneyException &e) {
557 qWarning("Unexpected exception in KTagsView::slotSelectTagAndTransaction %s", e.what());
558 }
559 }
560
slotSelectTagAndTransaction(const QString & tagId)561 void KTagsView::slotSelectTagAndTransaction(const QString& tagId)
562 {
563 slotSelectTagAndTransaction(tagId, QString(), QString());
564 }
565
slotShowTagsMenu(const QPoint &)566 void KTagsView::slotShowTagsMenu(const QPoint& /*ta*/)
567 {
568 Q_D(KTagsView);
569 auto item = dynamic_cast<KTagListItem*>(d->ui->m_tagsList->currentItem());
570 if (item) {
571 slotSelectTag();
572 pMenus[eMenu::Menu::Tag]->exec(QCursor::pos());
573 }
574 }
575
slotHelp()576 void KTagsView::slotHelp()
577 {
578 KHelpClient::invokeHelp("details.tags.attributes");
579 //FIXME-ALEX update help file
580 }
581
slotChangeFilter(int index)582 void KTagsView::slotChangeFilter(int index)
583 {
584 Q_D(KTagsView);
585 //update the filter type then reload the tags list
586 d->m_tagFilterType = index;
587 loadTags();
588 }
589
slotSelectTags(const QList<MyMoneyTag> & list)590 void KTagsView::slotSelectTags(const QList<MyMoneyTag>& list)
591 {
592 Q_D(KTagsView);
593 d->m_selectedTags = list;
594 updateTagActions(list);
595 }
596
slotNewTag()597 void KTagsView::slotNewTag()
598 {
599 QString id;
600 KMyMoneyUtils::newTag(i18n("New Tag"), id);
601 slotSelectTagAndTransaction(id);
602 }
603
slotRenameTag()604 void KTagsView::slotRenameTag()
605 {
606 Q_D(KTagsView);
607 if (d->ui->m_tagsList->currentItem() && d->ui->m_tagsList->selectedItems().count() == 1) {
608 slotStartRename(d->ui->m_tagsList->currentItem());
609 }
610 }
611
slotDeleteTag()612 void KTagsView::slotDeleteTag()
613 {
614 Q_D(KTagsView);
615 if (d->m_selectedTags.isEmpty())
616 return; // shouldn't happen
617
618 const auto file = MyMoneyFile::instance();
619
620 // first create list with all non-selected tags
621 QList<MyMoneyTag> remainingTags = file->tagList();
622 QList<MyMoneyTag>::iterator it_ta;
623 for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) {
624 if (d->m_selectedTags.contains(*it_ta)) {
625 it_ta = remainingTags.erase(it_ta);
626 } else {
627 ++it_ta;
628 }
629 }
630
631 // get confirmation from user
632 QString prompt;
633 if (d->m_selectedTags.size() == 1)
634 prompt = i18n("<p>Do you really want to remove the tag <b>%1</b>?</p>", d->m_selectedTags.front().name());
635 else
636 prompt = i18n("Do you really want to remove all selected tags?");
637
638 if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No)
639 return;
640
641 MyMoneyFileTransaction ft;
642 try {
643 // create a transaction filter that contains all tags selected for removal
644 MyMoneyTransactionFilter f = MyMoneyTransactionFilter();
645 for (QList<MyMoneyTag>::const_iterator it = d->m_selectedTags.constBegin();
646 it != d->m_selectedTags.constEnd(); ++it) {
647 f.addTag((*it).id());
648 }
649 // request a list of all transactions that still use the tags in question
650 auto translist = file->transactionList(f);
651 // qDebug() << "[KTagsView::slotDeleteTag] " << translist.count() << " transaction still assigned to tags";
652
653 // now get a list of all schedules that make use of one of the tags
654 QList<MyMoneySchedule> used_schedules;
655 foreach (const auto schedule, file->scheduleList()) {
656 // loop over all splits in the transaction of the schedule
657 foreach (const auto split, schedule.transaction().splits()) {
658 for (auto i = 0; i < split.tagIdList().size(); ++i) {
659 // is the tag in the split to be deleted?
660 if (d->tagInList(d->m_selectedTags, split.tagIdList()[i])) {
661 used_schedules.push_back(schedule); // remember this schedule
662 break;
663 }
664 }
665 }
666 }
667 // qDebug() << "[KTagsView::slotDeleteTag] " << used_schedules.count() << " schedules use one of the selected tags";
668
669 MyMoneyTag newTag;
670 // if at least one tag is still referenced, we need to reassign its transactions first
671 if (!translist.isEmpty() || !used_schedules.isEmpty()) {
672 // show error message if no tags remain
673 //FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction
674 if (remainingTags.isEmpty()) {
675 KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. "
676 "Currently you have all tags selected. However, at least one tag must remain so "
677 "that the transaction/scheduled transaction can be reassigned."));
678 return;
679 }
680
681 // show transaction reassignment dialog
682 auto dlg = new KTagReassignDlg(this);
683 KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
684 auto tag_id = dlg->show(remainingTags);
685 delete dlg; // and kill the dialog
686 if (tag_id.isEmpty()) //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one.
687 return; // the user aborted the dialog, so let's abort as well
688
689 newTag = file->tag(tag_id);
690
691 // TODO : check if we have a report that explicitly uses one of our tags
692 // and issue an appropriate warning
693 try {
694 // now loop over all transactions and reassign tag
695 for (auto& transaction : translist) {
696 // create a copy of the splits list in the transaction
697 // loop over all splits
698 for (auto& split : transaction.splits()) {
699 QList<QString> tagIdList = split.tagIdList();
700 for (int i = 0; i < tagIdList.size(); ++i) {
701 // if the split is assigned to one of the selected tags, we need to modify it
702 if (d->tagInList(d->m_selectedTags, tagIdList[i])) {
703 tagIdList.removeAt(i);
704 if (tagIdList.indexOf(tag_id) == -1)
705 tagIdList.append(tag_id);
706 i = -1; // restart from the first element
707 }
708 }
709 split.setTagIdList(tagIdList); // first modify tag list in current split
710 // then modify the split in our local copy of the transaction list
711 transaction.modifySplit(split); // this does not modify the list object 'splits'!
712 } // for - Splits
713 file->modifyTransaction(transaction); // modify the transaction in the MyMoney object
714 } // for - Transactions
715
716 // now loop over all schedules and reassign tags
717 for (auto& schedule : used_schedules) {
718 // create copy of transaction in current schedule
719 auto trans = schedule.transaction();
720 // create copy of lists of splits
721 for (auto& split : trans.splits()) {
722 QList<QString> tagIdList = split.tagIdList();
723 for (auto i = 0; i < tagIdList.size(); ++i) {
724 if (d->tagInList(d->m_selectedTags, tagIdList[i])) {
725 tagIdList.removeAt(i);
726 if (tagIdList.indexOf(tag_id) == -1)
727 tagIdList.append(tag_id);
728 i = -1; // restart from the first element
729 }
730 }
731 split.setTagIdList(tagIdList);
732 trans.modifySplit(split); // does not modify the list object 'splits'!
733 } // for - Splits
734 // store transaction in current schedule
735 schedule.setTransaction(trans);
736 file->modifySchedule(schedule); // modify the schedule in the MyMoney engine
737 } // for - Schedules
738
739 } catch (const MyMoneyException &e) {
740 KMessageBox::detailedSorry(this, i18n("Unable to reassign tag of transaction/split"), QString::fromLatin1(e.what()));
741 }
742 } // if !translist.isEmpty()
743
744 // now loop over all selected tags and remove them
745 for (QList<MyMoneyTag>::iterator it = d->m_selectedTags.begin();
746 it != d->m_selectedTags.end(); ++it) {
747 file->removeTag(*it);
748 }
749
750 ft.commit();
751
752 // If we just deleted the tags, they sure don't exist anymore
753 slotSelectTags(QList<MyMoneyTag>());
754
755 } catch (const MyMoneyException &e) {
756 KMessageBox::detailedSorry(this, i18n("Unable to remove tag(s)"), QString::fromLatin1(e.what()));
757 }
758 }
759