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