File indexing completed on 2024-05-12 16:43:51

0001 /*
0002     SPDX-FileCopyrightText: 2012 Alessandro Russo <axela74@yahoo.it>
0003     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ktagsview.h"
0008 #include "ktagsview_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QTimer>
0014 #include <QMenu>
0015 
0016 // ----------------------------------------------------------------------------
0017 // KDE Includes
0018 
0019 #include <KMessageBox>
0020 #include <KHelpClient>
0021 
0022 // ----------------------------------------------------------------------------
0023 // Project Includes
0024 
0025 #include "mymoneyexception.h"
0026 #include "mymoneymoney.h"
0027 #include "mymoneyprice.h"
0028 #include "kmymoneysettings.h"
0029 #include "ktagreassigndlg.h"
0030 #include "kmymoneyutils.h"
0031 #include "kmymoneymvccombo.h"
0032 #include "mymoneysecurity.h"
0033 #include "mymoneysplit.h"
0034 #include "mymoneytransaction.h"
0035 #include "mymoneyschedule.h"
0036 #include "transaction.h"
0037 #include "menuenums.h"
0038 
0039 using namespace Icons;
0040 
0041 /* -------------------------------------------------------------------------*/
0042 /*                         KTransactionPtrVector                            */
0043 /* -------------------------------------------------------------------------*/
0044 
0045 // *** KTagsView Implementation ***
0046 KTagsView::KTagsView(QWidget *parent) :
0047     KMyMoneyViewBase(*new KTagsViewPrivate(this), parent)
0048 {
0049     typedef void(KTagsView::*KTagsViewFunc)();
0050     const QHash<eMenu::Action, KTagsViewFunc> actionConnections {
0051         {eMenu::Action::NewTag,    &KTagsView::slotNewTag},
0052         {eMenu::Action::RenameTag, &KTagsView::slotRenameTag},
0053         {eMenu::Action::DeleteTag, &KTagsView::slotDeleteTag},
0054     };
0055 
0056     for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a)
0057         connect(pActions[a.key()], &QAction::triggered, this, a.value());
0058 }
0059 
0060 KTagsView::~KTagsView()
0061 {
0062 }
0063 
0064 void KTagsView::executeCustomAction(eView::Action action)
0065 {
0066     Q_D(KTagsView);
0067     switch(action) {
0068     case eView::Action::Refresh:
0069         refresh();
0070         break;
0071 
0072     case eView::Action::SetDefaultFocus:
0073         QTimer::singleShot(0, d->m_searchWidget, SLOT(setFocus()));
0074         break;
0075 
0076     default:
0077         break;
0078     }
0079 }
0080 
0081 void KTagsView::refresh()
0082 {
0083     Q_D(KTagsView);
0084     if (isVisible()) {
0085         if (d->m_inSelection)
0086             QTimer::singleShot(0, this, SLOT(refresh()));
0087         else
0088             loadTags();
0089         d->m_needsRefresh = false;
0090     } else {
0091         d->m_needsRefresh = true;
0092     }
0093 }
0094 
0095 void KTagsView::slotStartRename(QListWidgetItem* item)
0096 {
0097     Q_D(KTagsView);
0098     d->m_allowEditing = true;
0099     d->ui->m_tagsList->editItem(item);
0100 }
0101 
0102 // This variant is only called when a single tag is selected and renamed.
0103 void KTagsView::slotRenameSingleTag(QListWidgetItem* ta)
0104 {
0105     Q_D(KTagsView);
0106     //if there is no current item selected, exit
0107     if (d->m_allowEditing == false || !d->ui->m_tagsList->currentItem() || ta != d->ui->m_tagsList->currentItem())
0108         return;
0109 
0110     //qDebug() << "[KTagsView::slotRenameTag]";
0111     // create a copy of the new name without appended whitespaces
0112     auto new_name = ta->text();
0113     if (d->m_tag.name() != new_name) {
0114         MyMoneyFileTransaction ft;
0115         try {
0116             // check if we already have a tag with the new name
0117             try {
0118                 // this function call will throw an exception, if the tag
0119                 // hasn't been found.
0120                 MyMoneyFile::instance()->tagByName(new_name);
0121                 // the name already exists, ask the user whether he's sure to keep the name
0122                 if (KMessageBox::questionYesNo(this,
0123                                                i18n("A tag with the name '%1' already exists. It is not advisable to have "
0124                                                     "multiple tags with the same identification name. Are you sure you would like "
0125                                                     "to rename the tag?", new_name)) != KMessageBox::Yes) {
0126                     ta->setText(d->m_tag.name());
0127                     return;
0128                 }
0129             } catch (const MyMoneyException &) {
0130                 // all ok, the name is unique
0131             }
0132 
0133             d->m_tag.setName(new_name);
0134             d->m_newName = new_name;
0135             MyMoneyFile::instance()->modifyTag(d->m_tag);
0136 
0137             // the above call to modifyTag will reload the view so
0138             // all references and pointers to the view have to be
0139             // re-established.
0140 
0141             // make sure, that the record is visible even if it moved
0142             // out of sight due to the rename operation
0143             ensureTagVisible(d->m_tag.id());
0144 
0145             ft.commit();
0146 
0147         } catch (const MyMoneyException &e) {
0148             KMessageBox::detailedSorry(this, i18n("Unable to modify tag"), QString::fromLatin1(e.what()));
0149         }
0150     } else {
0151         ta->setText(new_name);
0152     }
0153 }
0154 
0155 void KTagsView::ensureTagVisible(const QString& id)
0156 {
0157     Q_D(KTagsView);
0158     for (int i = 0; i < d->ui->m_tagsList->count(); ++i) {
0159         KTagListItem* ta = dynamic_cast<KTagListItem*>(d->ui->m_tagsList->item(0));
0160         if (ta && ta->tag().id() == id) {
0161             d->ui->m_tagsList->scrollToItem(ta, QAbstractItemView::PositionAtCenter);
0162 
0163             d->ui->m_tagsList->setCurrentItem(ta);      // active item and deselect all others
0164             d->ui->m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect);   // and select it
0165             break;
0166         }
0167     }
0168 }
0169 
0170 void KTagsView::selectedTags(QList<MyMoneyTag>& tagsList) const
0171 {
0172     Q_D(const KTagsView);
0173     QList<QListWidgetItem *> selectedItems = d->ui->m_tagsList->selectedItems();
0174     QList<QListWidgetItem *>::ConstIterator itemsIt = selectedItems.constBegin();
0175     while (itemsIt != selectedItems.constEnd()) {
0176         KTagListItem* item = dynamic_cast<KTagListItem*>(*itemsIt);
0177         if (item)
0178             tagsList << item->tag();
0179         ++itemsIt;
0180     }
0181 }
0182 
0183 void KTagsView::slotSelectTag(QListWidgetItem* cur, QListWidgetItem* prev)
0184 {
0185     Q_D(KTagsView);
0186     Q_UNUSED(cur);
0187     Q_UNUSED(prev);
0188 
0189     d->m_allowEditing = false;
0190 }
0191 
0192 void KTagsView::slotSelectTag()
0193 {
0194     Q_D(KTagsView);
0195     // check if the content of a currently selected tag was modified
0196     // and ask to store the data
0197     if (d->ui->m_updateButton->isEnabled()) {
0198         if (KMessageBox::questionYesNo(this, QString("<qt>%1</qt>").arg(
0199                                            i18n("Do you want to save the changes for <b>%1</b>?", d->m_newName)),
0200                                        i18n("Save changes")) == KMessageBox::Yes) {
0201             d->m_inSelection = true;
0202             slotUpdateTag();
0203             d->m_inSelection = false;
0204         }
0205     }
0206     // loop over all tags and count the number of tags, also
0207     // obtain last selected tag
0208     QList<MyMoneyTag> tagsList;
0209     selectedTags(tagsList);
0210     slotSelectTags(tagsList);
0211 
0212     if (tagsList.isEmpty()) {
0213         d->ui->m_tabWidget->setEnabled(false); // disable tab widget
0214         d->ui->m_balanceLabel->hide();
0215         d->ui->m_deleteButton->setEnabled(false); //disable delete and rename button
0216         d->ui->m_renameButton->setEnabled(false);
0217         clearItemData();
0218         d->m_tag = MyMoneyTag();
0219         return; // make sure we don't access an undefined tag
0220     }
0221 
0222     d->ui->m_deleteButton->setEnabled(true); //re-enable delete button
0223 
0224     // if we have multiple tags selected, clear and disable the tag information
0225     if (tagsList.count() > 1) {
0226         d->ui->m_tabWidget->setEnabled(false); // disable tab widget
0227         d->ui->m_renameButton->setEnabled(false); // disable also the rename button
0228         d->ui->m_balanceLabel->hide();
0229         clearItemData();
0230     } else d->ui->m_renameButton->setEnabled(true);
0231 
0232     // otherwise we have just one selected, enable tag information widget and renameButton
0233     d->ui->m_tabWidget->setEnabled(true);
0234     d->ui->m_balanceLabel->show();
0235 
0236     // as of now we are updating only the last selected tag, and until
0237     // selection mode of the QListView has been changed to Extended, this
0238     // will also be the only selection and behave exactly as before - Andreas
0239     try {
0240         d->m_tag = tagsList[0];
0241 
0242         d->m_newName = d->m_tag.name();
0243         d->ui->m_colorbutton->setEnabled(true);
0244         d->ui->m_colorbutton->setColor(d->m_tag.tagColor());
0245         d->ui->m_closed->setEnabled(true);
0246         d->ui->m_closed->setChecked(d->m_tag.isClosed());
0247         d->ui->m_notes->setEnabled(true);
0248         d->ui->m_notes->setText(d->m_tag.notes());
0249         slotTagDataChanged();
0250 
0251         showTransactions();
0252 
0253     } catch (const MyMoneyException &e) {
0254         qDebug("exception during display of tag: %s", e.what());
0255         d->ui->m_register->clear();
0256         d->m_tag = MyMoneyTag();
0257     }
0258     d->m_allowEditing = true;
0259 }
0260 
0261 void KTagsView::clearItemData()
0262 {
0263     Q_D(KTagsView);
0264     d->ui->m_colorbutton->setColor(QColor());
0265     d->ui->m_closed->setChecked(false);
0266     d->ui->m_notes->setText(QString());
0267     showTransactions();
0268 }
0269 
0270 void KTagsView::showTransactions()
0271 {
0272     Q_D(KTagsView);
0273     MyMoneyMoney balance;
0274     auto file = MyMoneyFile::instance();
0275     MyMoneySecurity base = file->baseCurrency();
0276 
0277     // setup sort order
0278     d->ui->m_register->setSortOrder(KMyMoneySettings::sortSearchView());
0279 
0280     // clear the register
0281     d->ui->m_register->clear();
0282 
0283     if (d->m_tag.id().isEmpty() || !d->ui->m_tabWidget->isEnabled()) {
0284         d->ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
0285         return;
0286     }
0287 
0288     // setup the list and the pointer vector
0289     MyMoneyTransactionFilter filter;
0290     filter.setConsiderCategorySplits();
0291     filter.addTag(d->m_tag.id());
0292     filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate());
0293 
0294     // retrieve the list from the engine
0295     file->transactionList(d->m_transactionList, filter);
0296 
0297     // create the elements for the register
0298     QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
0299     QMap<QString, int> uniqueMap;
0300     MyMoneyMoney deposit, payment;
0301 
0302     int splitCount = 0;
0303     bool balanceAccurate = true;
0304     for (it = d->m_transactionList.constBegin(); it != d->m_transactionList.constEnd(); ++it) {
0305         const MyMoneySplit& split = (*it).second;
0306         MyMoneyAccount acc = file->account(split.accountId());
0307         if (!acc.isIncomeExpense()) {
0308             ++splitCount;
0309             uniqueMap[(*it).first.id()]++;
0310 
0311             KMyMoneyRegister::Register::transactionFactory(d->ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
0312 
0313             // take care of foreign currencies
0314             MyMoneyMoney val = split.shares().abs();
0315             if (acc.currencyId() != base.id()) {
0316                 const MyMoneyPrice& price = file->price(acc.currencyId(), base.id());
0317                 // in case the price is valid, we use it. Otherwise, we keep
0318                 // a flag that tells us that the balance is somewhat inaccurate
0319                 if (price.isValid()) {
0320                     val *= price.rate(base.id());
0321                 } else {
0322                     balanceAccurate = false;
0323                 }
0324             }
0325 
0326             if (split.shares().isNegative()) {
0327                 payment += val;
0328             } else {
0329                 deposit += val;
0330             }
0331         }
0332     }
0333     balance = deposit - payment;
0334 
0335     // add the group markers
0336     d->ui->m_register->addGroupMarkers();
0337 
0338     // sort the transactions according to the sort setting
0339     d->ui->m_register->sortItems();
0340 
0341     // remove trailing and adjacent markers
0342     d->ui->m_register->removeUnwantedGroupMarkers();
0343 
0344     d->ui->m_register->updateRegister(true);
0345 
0346     // we might end up here with updates disabled on the register so
0347     // make sure that we enable updates here
0348     d->ui->m_register->setUpdatesEnabled(true);
0349     d->ui->m_balanceLabel->setText(i18n("Balance: %1%2",
0350                                         balanceAccurate ? "" : "~",
0351                                         balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
0352 }
0353 
0354 void KTagsView::slotTagDataChanged()
0355 {
0356     Q_D(KTagsView);
0357     auto rc = false;
0358 
0359     if (d->ui->m_tabWidget->isEnabled()) {
0360         rc |= ((d->m_tag.tagColor().isValid() != d->ui->m_colorbutton->color().isValid())
0361                || (d->ui->m_colorbutton->color().isValid() && d->m_tag.tagColor() != d->ui->m_colorbutton->color()));
0362         rc |= (d->ui->m_closed->isChecked() != d->m_tag.isClosed());
0363         rc |= ((d->m_tag.notes().isEmpty() != d->ui->m_notes->toPlainText().isEmpty())
0364                || (!d->ui->m_notes->toPlainText().isEmpty() && d->m_tag.notes() != d->ui->m_notes->toPlainText()));
0365     }
0366     d->ui->m_updateButton->setEnabled(rc);
0367 }
0368 
0369 void KTagsView::slotUpdateTag()
0370 {
0371     Q_D(KTagsView);
0372     if (d->ui->m_updateButton->isEnabled()) {
0373         MyMoneyFileTransaction ft;
0374         d->ui->m_updateButton->setEnabled(false);
0375         try {
0376             d->m_tag.setName(d->m_newName);
0377             d->m_tag.setTagColor(d->ui->m_colorbutton->color());
0378             d->m_tag.setClosed(d->ui->m_closed->isChecked());
0379             d->m_tag.setNotes(d->ui->m_notes->toPlainText());
0380 
0381             MyMoneyFile::instance()->modifyTag(d->m_tag);
0382             ft.commit();
0383 
0384         } catch (const MyMoneyException &e) {
0385             KMessageBox::detailedSorry(this, i18n("Unable to modify tag"), QString::fromLatin1(e.what()));
0386         }
0387     }
0388 }
0389 
0390 void KTagsView::showEvent(QShowEvent* event)
0391 {
0392     if (MyMoneyFile::instance()->storageAttached()) {
0393         Q_D(KTagsView);
0394         if (d->m_needLoad)
0395             d->init();
0396 
0397         emit customActionRequested(View::Tags, eView::Action::AboutToShow);
0398 
0399         if (d->m_needsRefresh)
0400             refresh();
0401 
0402         QList<MyMoneyTag> list;
0403         selectedTags(list);
0404         slotSelectTags(list);
0405     }
0406 
0407     // don't forget base class implementation
0408     QWidget::showEvent(event);
0409 }
0410 
0411 void KTagsView::updateTagActions(const QList<MyMoneyTag>& tags)
0412 {
0413     pActions[eMenu::Action::NewTag]->setEnabled(true);
0414     const auto tagsCount = tags.count();
0415     auto b = tagsCount == 1 ? true : false;
0416     pActions[eMenu::Action::RenameTag]->setEnabled(b);
0417     b = tagsCount >= 1 ? true : false;
0418     pActions[eMenu::Action::DeleteTag]->setEnabled(b);
0419 }
0420 
0421 void KTagsView::loadTags()
0422 {
0423     Q_D(KTagsView);
0424     if (d->m_inSelection)
0425         return;
0426 
0427     QMap<QString, bool> isSelected;
0428     QString id;
0429     MyMoneyFile* file = MyMoneyFile::instance();
0430 
0431     // remember which items are selected in the list
0432     QList<QListWidgetItem *> selectedItems = d->ui->m_tagsList->selectedItems();
0433     QList<QListWidgetItem *>::const_iterator tagsIt = selectedItems.constBegin();
0434 
0435     while (tagsIt != selectedItems.constEnd()) {
0436         KTagListItem* item = dynamic_cast<KTagListItem*>(*tagsIt);
0437         if (item)
0438             isSelected[item->tag().id()] = true;
0439         ++tagsIt;
0440     }
0441 
0442     // keep current selected item
0443     KTagListItem *currentItem = static_cast<KTagListItem *>(d->ui->m_tagsList->currentItem());
0444     if (currentItem)
0445         id = currentItem->tag().id();
0446 
0447     d->m_allowEditing = false;
0448     // clear the list
0449     d->m_searchWidget->clear();
0450     d->m_searchWidget->updateSearch();
0451     d->ui->m_tagsList->clear();
0452     d->ui->m_register->clear();
0453     currentItem = 0;
0454 
0455     QList<MyMoneyTag>list = file->tagList();
0456     QList<MyMoneyTag>::ConstIterator it;
0457 
0458     for (it = list.constBegin(); it != list.constEnd(); ++it) {
0459         if (d->m_tagFilterType == (int)eView::Tag::All ||
0460                 (d->m_tagFilterType == (int)eView::Tag::Referenced && file->isReferenced(*it)) ||
0461                 (d->m_tagFilterType == (int)eView::Tag::Unused && !file->isReferenced(*it)) ||
0462                 (d->m_tagFilterType == (int)eView::Tag::Opened && !(*it).isClosed()) ||
0463                 (d->m_tagFilterType == (int)eView::Tag::Closed && (*it).isClosed())) {
0464             KTagListItem* item = new KTagListItem(d->ui->m_tagsList, *it);
0465             if (item->tag().id() == id)
0466                 currentItem = item;
0467             if (isSelected[item->tag().id()])
0468                 item->setSelected(true);
0469         }
0470     }
0471     d->ui->m_tagsList->sortItems();
0472 
0473     if (currentItem) {
0474         d->ui->m_tagsList->setCurrentItem(currentItem);
0475         d->ui->m_tagsList->scrollToItem(currentItem);
0476     }
0477 
0478     slotSelectTag(0, 0);
0479     d->m_allowEditing = true;
0480 }
0481 
0482 void KTagsView::slotSelectTransaction()
0483 {
0484     Q_D(KTagsView);
0485     QList<KMyMoneyRegister::RegisterItem*> list = d->ui->m_register->selectedItems();
0486     if (!list.isEmpty()) {
0487         KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(list[0]);
0488         if (t)
0489             emit selectByVariant(QVariantList {QVariant(t->split().accountId()), QVariant(t->transaction().id())}, eView::Intent::ShowTransaction);
0490     }
0491 }
0492 
0493 void KTagsView::slotSelectTagAndTransaction(const QString& tagId, const QString& accountId, const QString& transactionId)
0494 {
0495     if (!isVisible())
0496         return;
0497 
0498     Q_D(KTagsView);
0499     try {
0500         // clear filter
0501         d->m_searchWidget->clear();
0502         d->m_searchWidget->updateSearch();
0503 
0504         // deselect all other selected items
0505         QList<QListWidgetItem *> selectedItems = d->ui->m_tagsList->selectedItems();
0506         QList<QListWidgetItem *>::const_iterator tagsIt = selectedItems.constBegin();
0507         while (tagsIt != selectedItems.constEnd()) {
0508             KTagListItem* item = dynamic_cast<KTagListItem*>(*tagsIt);
0509             if (item)
0510                 item->setSelected(false);
0511             ++tagsIt;
0512         }
0513 
0514         // find the tag in the list
0515         QListWidgetItem* it;
0516         for (int i = 0; i < d->ui->m_tagsList->count(); ++i) {
0517             it = d->ui->m_tagsList->item(i);
0518             KTagListItem* item = dynamic_cast<KTagListItem *>(it);
0519             if (item && item->tag().id() == tagId) {
0520                 d->ui->m_tagsList->scrollToItem(it, QAbstractItemView::PositionAtCenter);
0521 
0522                 d->ui->m_tagsList->setCurrentItem(it);     // active item and deselect all others
0523                 d->ui->m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it
0524 
0525                 //make sure the tag selection is updated and transactions are updated accordingly
0526                 slotSelectTag();
0527 
0528                 KMyMoneyRegister::RegisterItem *registerItem = 0;
0529                 for (i = 0; i < d->ui->m_register->rowCount(); ++i) {
0530                     registerItem = d->ui->m_register->itemAtRow(i);
0531                     KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(registerItem);
0532                     if (t) {
0533                         if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) {
0534                             d->ui->m_register->selectItem(registerItem);
0535                             d->ui->m_register->ensureItemVisible(registerItem);
0536                             break;
0537                         }
0538                     }
0539                 }
0540                 // quit out of outer for() loop
0541                 break;
0542             }
0543         }
0544     } catch (const MyMoneyException &e) {
0545         qWarning("Unexpected exception in KTagsView::slotSelectTagAndTransaction %s", e.what());
0546     }
0547 }
0548 
0549 void KTagsView::slotSelectTagAndTransaction(const QString& tagId)
0550 {
0551     slotSelectTagAndTransaction(tagId, QString(), QString());
0552 }
0553 
0554 void KTagsView::slotShowTagsMenu(const QPoint& /*ta*/)
0555 {
0556     Q_D(KTagsView);
0557     auto item = dynamic_cast<KTagListItem*>(d->ui->m_tagsList->currentItem());
0558     if (item) {
0559         slotSelectTag();
0560         pMenus[eMenu::Menu::Tag]->exec(QCursor::pos());
0561     }
0562 }
0563 
0564 void KTagsView::slotHelp()
0565 {
0566     KHelpClient::invokeHelp("details.tags.attributes");
0567     //FIXME-ALEX update help file
0568 }
0569 
0570 void KTagsView::slotChangeFilter(int index)
0571 {
0572     Q_D(KTagsView);
0573     //update the filter type then reload the tags list
0574     d->m_tagFilterType = index;
0575     loadTags();
0576 }
0577 
0578 void KTagsView::slotSelectTags(const QList<MyMoneyTag>& list)
0579 {
0580     Q_D(KTagsView);
0581     d->m_selectedTags = list;
0582     updateTagActions(list);
0583 }
0584 
0585 void KTagsView::slotNewTag()
0586 {
0587     QString id;
0588     KMyMoneyUtils::newTag(i18n("New Tag"), id);
0589     slotSelectTagAndTransaction(id);
0590 }
0591 
0592 void KTagsView::slotRenameTag()
0593 {
0594     Q_D(KTagsView);
0595     if (d->ui->m_tagsList->currentItem() && d->ui->m_tagsList->selectedItems().count() == 1) {
0596         slotStartRename(d->ui->m_tagsList->currentItem());
0597     }
0598 }
0599 
0600 void KTagsView::slotDeleteTag()
0601 {
0602     Q_D(KTagsView);
0603     if (d->m_selectedTags.isEmpty())
0604         return; // shouldn't happen
0605 
0606     const auto file = MyMoneyFile::instance();
0607 
0608     // first create list with all non-selected tags
0609     QList<MyMoneyTag> remainingTags = file->tagList();
0610     QList<MyMoneyTag>::iterator it_ta;
0611     for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) {
0612         if (d->m_selectedTags.contains(*it_ta)) {
0613             it_ta = remainingTags.erase(it_ta);
0614         } else {
0615             ++it_ta;
0616         }
0617     }
0618 
0619     // get confirmation from user
0620     QString prompt;
0621     if (d->m_selectedTags.size() == 1)
0622         prompt = i18n("<p>Do you really want to remove the tag <b>%1</b>?</p>", d->m_selectedTags.front().name());
0623     else
0624         prompt = i18n("Do you really want to remove all selected tags?");
0625 
0626     if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No)
0627         return;
0628 
0629     MyMoneyFileTransaction ft;
0630     try {
0631         // create a transaction filter that contains all tags selected for removal
0632         MyMoneyTransactionFilter f = MyMoneyTransactionFilter();
0633         for (QList<MyMoneyTag>::const_iterator it = d->m_selectedTags.constBegin();
0634                 it != d->m_selectedTags.constEnd(); ++it) {
0635             f.addTag((*it).id());
0636         }
0637         // request a list of all transactions that still use the tags in question
0638         auto translist = file->transactionList(f);
0639 //     qDebug() << "[KTagsView::slotDeleteTag]  " << translist.count() << " transaction still assigned to tags";
0640 
0641         // now get a list of all schedules that make use of one of the tags
0642         QList<MyMoneySchedule> used_schedules;
0643         foreach (const auto schedule, file->scheduleList()) {
0644             // loop over all splits in the transaction of the schedule
0645             foreach (const auto split, schedule.transaction().splits()) {
0646                 for (auto i = 0; i < split.tagIdList().size(); ++i) {
0647                     // is the tag in the split to be deleted?
0648                     if (d->tagInList(d->m_selectedTags, split.tagIdList()[i])) {
0649                         used_schedules.push_back(schedule); // remember this schedule
0650                         break;
0651                     }
0652                 }
0653             }
0654         }
0655 //     qDebug() << "[KTagsView::slotDeleteTag]  " << used_schedules.count() << " schedules use one of the selected tags";
0656 
0657         MyMoneyTag newTag;
0658         // if at least one tag is still referenced, we need to reassign its transactions first
0659         if (!translist.isEmpty() || !used_schedules.isEmpty()) {
0660             // show error message if no tags remain
0661             //FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction
0662             if (remainingTags.isEmpty()) {
0663                 KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. "
0664                                               "Currently you have all tags selected. However, at least one tag must remain so "
0665                                               "that the transaction/scheduled transaction can be reassigned."));
0666                 return;
0667             }
0668 
0669             // show transaction reassignment dialog
0670             auto dlg = new KTagReassignDlg(this);
0671             KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
0672             auto tag_id = dlg->show(remainingTags);
0673             delete dlg; // and kill the dialog
0674             if (tag_id.isEmpty())  //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one.
0675                 return; // the user aborted the dialog, so let's abort as well
0676 
0677             newTag = file->tag(tag_id);
0678 
0679             // TODO : check if we have a report that explicitly uses one of our tags
0680             //        and issue an appropriate warning
0681             try {
0682                 // now loop over all transactions and reassign tag
0683                 for (auto& transaction : translist) {
0684                     // create a copy of the splits list in the transaction
0685                     // loop over all splits
0686                     for (auto& split : transaction.splits()) {
0687                         QList<QString> tagIdList = split.tagIdList();
0688                         for (int i = 0; i < tagIdList.size(); ++i) {
0689                             // if the split is assigned to one of the selected tags, we need to modify it
0690                             if (d->tagInList(d->m_selectedTags, tagIdList[i])) {
0691                                 tagIdList.removeAt(i);
0692                                 if (tagIdList.indexOf(tag_id) == -1)
0693                                     tagIdList.append(tag_id);
0694                                 i = -1; // restart from the first element
0695                             }
0696                         }
0697                         split.setTagIdList(tagIdList); // first modify tag list in current split
0698                         // then modify the split in our local copy of the transaction list
0699                         transaction.modifySplit(split); // this does not modify the list object 'splits'!
0700                     } // for - Splits
0701                     file->modifyTransaction(transaction);  // modify the transaction in the MyMoney object
0702                 } // for - Transactions
0703 
0704                 // now loop over all schedules and reassign tags
0705                 for (auto& schedule : used_schedules) {
0706                     // create copy of transaction in current schedule
0707                     auto trans = schedule.transaction();
0708                     // create copy of lists of splits
0709                     for (auto& split : trans.splits()) {
0710                         QList<QString> tagIdList = split.tagIdList();
0711                         for (auto i = 0; i < tagIdList.size(); ++i) {
0712                             if (d->tagInList(d->m_selectedTags, tagIdList[i])) {
0713                                 tagIdList.removeAt(i);
0714                                 if (tagIdList.indexOf(tag_id) == -1)
0715                                     tagIdList.append(tag_id);
0716                                 i = -1; // restart from the first element
0717                             }
0718                         }
0719                         split.setTagIdList(tagIdList);
0720                         trans.modifySplit(split); // does not modify the list object 'splits'!
0721                     } // for - Splits
0722                     // store transaction in current schedule
0723                     schedule.setTransaction(trans);
0724                     file->modifySchedule(schedule);  // modify the schedule in the MyMoney engine
0725                 } // for - Schedules
0726 
0727             } catch (const MyMoneyException &e) {
0728                 KMessageBox::detailedSorry(this, i18n("Unable to reassign tag of transaction/split"), QString::fromLatin1(e.what()));
0729             }
0730         } // if !translist.isEmpty()
0731 
0732         // now loop over all selected tags and remove them
0733         for (QList<MyMoneyTag>::iterator it = d->m_selectedTags.begin();
0734                 it != d->m_selectedTags.end(); ++it) {
0735             file->removeTag(*it);
0736         }
0737 
0738         ft.commit();
0739 
0740         // If we just deleted the tags, they sure don't exist anymore
0741         slotSelectTags(QList<MyMoneyTag>());
0742 
0743     } catch (const MyMoneyException &e) {
0744         KMessageBox::detailedSorry(this, i18n("Unable to remove tag(s)"), QString::fromLatin1(e.what()));
0745     }
0746 }