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 }