File indexing completed on 2024-05-19 05:08:21

0001 /*
0002     SPDX-FileCopyrightText: 2012 Alessandro Russo <axela74@yahoo.it>
0003     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-FileCopyrightText: 2020 Thomas Baumgart <tbaumgart@kde.org>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "ktagsview.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QComboBox>
0014 #include <QMenu>
0015 #include <QTimer>
0016 #include <QVector>
0017 
0018 // ----------------------------------------------------------------------------
0019 // KDE Includes
0020 
0021 #include <KConfigGroup>
0022 #include <KHelpClient>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 #include <KPageWidgetItem>
0026 #include <KSharedConfig>
0027 
0028 // ----------------------------------------------------------------------------
0029 // Project Includes
0030 
0031 #include "icons.h"
0032 #include "itemrenameproxymodel.h"
0033 #include "journalmodel.h"
0034 #include "kmymoneymvccombo.h"
0035 #include "kmymoneysettings.h"
0036 #include "kmymoneyutils.h"
0037 #include "kmymoneyviewbase_p.h"
0038 #include "ktagreassigndlg.h"
0039 #include "ledgertagfilter.h"
0040 #include "ledgerviewsettings.h"
0041 #include "mymoneyexception.h"
0042 #include "mymoneymoney.h"
0043 #include "mymoneyprice.h"
0044 #include "mymoneyreport.h"
0045 #include "mymoneyschedule.h"
0046 #include "mymoneysecurity.h"
0047 #include "mymoneysplit.h"
0048 #include "mymoneytransaction.h"
0049 #include "specialdatesmodel.h"
0050 #include "specialledgeritemfilter.h"
0051 #include "tagsmodel.h"
0052 
0053 #include "ui_ktagsview.h"
0054 
0055 using namespace Icons;
0056 
0057 namespace Ui {
0058 class KTagsView;
0059 }
0060 
0061 class KTagsViewPrivate : public KMyMoneyViewBasePrivate
0062 {
0063     Q_DECLARE_PUBLIC(KTagsView)
0064 
0065 public:
0066     explicit KTagsViewPrivate(KTagsView* qq)
0067         : KMyMoneyViewBasePrivate(qq)
0068         , ui(new Ui::KTagsView)
0069         , m_transactionFilter(nullptr)
0070         , m_renameProxyModel(nullptr)
0071         , m_updateAction(nullptr)
0072         , m_needLoad(true)
0073     {
0074     }
0075 
0076     ~KTagsViewPrivate() override
0077     {
0078         if (!m_needLoad) {
0079             // remember the splitter settings for startup
0080             KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings");
0081             grp.writeEntry("KTagsViewSplitterSize", ui->m_splitter->saveState());
0082             grp.sync();
0083         }
0084         delete ui;
0085     }
0086 
0087     void init()
0088     {
0089         Q_Q(KTagsView);
0090         m_needLoad = false;
0091         ui->setupUi(q);
0092 
0093         m_updateAction = new QAction(Icons::get(Icon::DialogOK), i18nc("@action:button Update button in tags view", "Update"), q);
0094         q->connect(m_updateAction, &QAction::triggered, q, &KTagsView::slotUpdateTag);
0095         m_updateAction->setEnabled(false);
0096 
0097         ui->m_register->setSingleLineDetailRole(eMyMoney::Model::TransactionCounterAccountRole);
0098         ui->m_tagsList->setContextMenuPolicy(Qt::CustomContextMenu);
0099 
0100         ui->m_filterBox->addItem(i18nc("@item Show all tags", "All"), ItemRenameProxyModel::eAllItem);
0101         ui->m_filterBox->addItem(i18nc("@item Show only used tags", "Used"), ItemRenameProxyModel::eReferencedItems);
0102         ui->m_filterBox->addItem(i18nc("@item Show only unused tags", "Unused"), ItemRenameProxyModel::eUnReferencedItems);
0103         ui->m_filterBox->addItem(i18nc("@item Show only opened tags", "Opened"), ItemRenameProxyModel::eOpenedItems);
0104         ui->m_filterBox->addItem(i18nc("@item Show only closed tags", "Closed"), ItemRenameProxyModel::eClosedItems);
0105         ui->m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0106 
0107         ui->m_newButton->setDefaultAction(pActions[eMenu::Action::NewTag]);
0108         ui->m_renameButton->setDefaultAction(pActions[eMenu::Action::RenameTag]);
0109         ui->m_deleteButton->setDefaultAction(pActions[eMenu::Action::DeleteTag]);
0110         ui->m_updateButton->setDefaultAction(m_updateAction);
0111 
0112         // setup the model stack
0113         auto file = MyMoneyFile::instance();
0114         m_transactionFilter = new LedgerTagFilter(ui->m_register, QVector<QAbstractItemModel*>{file->specialDatesModel()});
0115         m_transactionFilter->setHideReconciledTransactions(LedgerViewSettings::instance()->hideReconciledTransactions());
0116         m_transactionFilter->setHideTransactionsBefore(LedgerViewSettings::instance()->hideTransactionsBefore());
0117 
0118         auto specialItemFilter = new SpecialLedgerItemFilter(q);
0119         specialItemFilter->setSourceModel(m_transactionFilter);
0120         ui->m_register->setModel(specialItemFilter);
0121 
0122         // keep track of changing balances
0123         q->connect(file->journalModel(), &JournalModel::balanceChanged, m_transactionFilter, &LedgerTagFilter::recalculateBalancesOnIdle);
0124 
0125         ui->m_balanceLabel->hide();
0126 
0127         m_renameProxyModel = new ItemRenameProxyModel(q);
0128         ui->m_tagsList->setModel(m_renameProxyModel);
0129 
0130         m_renameProxyModel->setReferenceFilter(ItemRenameProxyModel::eAllItem);
0131         m_renameProxyModel->setFilterKeyColumn(TagsModel::Column::Name);
0132         m_renameProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
0133         m_renameProxyModel->setRenameColumn(TagsModel::Column::Name);
0134         m_renameProxyModel->setSortRole(eMyMoney::Model::TagNameRole);
0135         m_renameProxyModel->setSortLocaleAware(true);
0136         m_renameProxyModel->sort(0);
0137         m_renameProxyModel->setDynamicSortFilter(true);
0138 
0139         m_renameProxyModel->setSourceModel(MyMoneyFile::instance()->tagsModel());
0140 
0141         ui->m_tagsList->setSelectionMode(QAbstractItemView::ExtendedSelection);
0142         q->connect(ui->m_tagsList->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KTagsView::slotTagSelectionChanged);
0143         q->connect(ui->m_register->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KTagsView::slotTransactionSelectionChanged);
0144 
0145         q->connect(m_renameProxyModel, &ItemRenameProxyModel::renameItem, q, &KTagsView::slotRenameSingleTag);
0146         q->connect(m_renameProxyModel, &ItemRenameProxyModel::dataChanged, q, &KTagsView::slotModelDataChanged);
0147 
0148         q->connect(ui->m_colorbutton, &KColorButton::changed, q, &KTagsView::slotTagDataChanged);
0149         q->connect(ui->m_closed, &QCheckBox::stateChanged, q, &KTagsView::slotTagDataChanged);
0150         q->connect(ui->m_notes, &QTextEdit::textChanged, q, &KTagsView::slotTagDataChanged);
0151 
0152         q->connect(ui->m_helpButton, &QAbstractButton::clicked, q, &KTagsView::slotHelp);
0153 
0154         // use the size settings of the last run (if any)
0155         auto grp = KSharedConfig::openConfig()->group("Last Use Settings");
0156         ui->m_splitter->restoreState(grp.readEntry("KTagsViewSplitterSize", QByteArray()));
0157         ui->m_splitter->setChildrenCollapsible(false);
0158 
0159         QVector<int> columns;
0160         columns = {
0161             JournalModel::Column::Number,
0162             JournalModel::Column::Security,
0163             JournalModel::Column::CostCenter,
0164             JournalModel::Column::Quantity,
0165             JournalModel::Column::Price,
0166             JournalModel::Column::Amount,
0167             JournalModel::Column::Value,
0168             JournalModel::Column::Balance,
0169         };
0170         ui->m_register->setColumnsHidden(columns);
0171         columns = {
0172             JournalModel::Column::Date,
0173             JournalModel::Column::Account,
0174             JournalModel::Column::Detail,
0175             JournalModel::Column::Reconciliation,
0176             JournalModel::Column::Payment,
0177             JournalModel::Column::Deposit,
0178         };
0179         ui->m_register->setColumnsShown(columns);
0180 
0181         // setup the searchline widget
0182         q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_renameProxyModel, &QSortFilterProxyModel::setFilterFixedString);
0183         ui->m_searchWidget->setClearButtonEnabled(true);
0184         ui->m_searchWidget->setPlaceholderText(i18nc("Placeholder text", "Search"));
0185 
0186         // At start we haven't any tag selected
0187         ui->m_tabWidget->setEnabled(false); // disable tab widget
0188 
0189         m_tag = MyMoneyTag(); // make sure we don't access an undefined tag
0190         clearItemData();
0191 
0192         m_focusWidget = ui->m_searchWidget;
0193     }
0194 
0195     void clearItemData()
0196     {
0197         ui->m_colorbutton->setColor(QColor());
0198         ui->m_closed->setChecked(false);
0199         ui->m_notes->setText(QString());
0200         showTransactions();
0201     }
0202 
0203     void showTransactions()
0204     {
0205         MyMoneyMoney balance;
0206         auto file = MyMoneyFile::instance();
0207         MyMoneySecurity base = file->baseCurrency();
0208 
0209         const auto tagIds = m_selections.selection(SelectedObjects::Tag);
0210 
0211         if (tagIds.isEmpty()) {
0212             ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
0213             return;
0214         }
0215 
0216         m_transactionFilter->setTagIdList(tagIds);
0217 
0218         MyMoneyMoney deposit, payment;
0219         bool balanceAccurate = true;
0220         QSet<QString> accountIds;
0221 
0222         const auto viewModel = ui->m_register->model();
0223         const auto rows = viewModel->rowCount();
0224 
0225         for (int row = 0; row < rows; ++row) {
0226             const auto idx = viewModel->index(row, 0);
0227             const auto baseIdx = file->journalModel()->mapToBaseSource(idx);
0228             if (baseIdx.model() == file->journalModel()) {
0229                 const auto shares = baseIdx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>();
0230                 const auto splitAccountId = baseIdx.data(eMyMoney::Model::SplitAccountIdRole).toString();
0231                 accountIds.insert(splitAccountId);
0232                 MyMoneyAccount acc = file->account(splitAccountId);
0233 
0234                 // take care of foreign currencies
0235                 MyMoneyMoney val = shares.abs();
0236                 if (acc.currencyId() != base.id()) {
0237                     const auto price = file->price(acc.currencyId(), base.id());
0238                     // in case the price is valid, we use it. Otherwise, we keep
0239                     // a flag that tells us that the balance is somewhat inaccurate
0240                     if (price.isValid()) {
0241                         val *= price.rate(base.id());
0242                     } else {
0243                         balanceAccurate = false;
0244                     }
0245                 }
0246                 if (shares.isNegative()) {
0247                     payment += val;
0248                 } else {
0249                     deposit += val;
0250                 }
0251             }
0252         }
0253 
0254         balance = deposit - payment;
0255         ui->m_balanceLabel->setText(
0256             i18n("Balance: %1%2", balanceAccurate ? QString() : QStringLiteral("~ "), balance.formatMoney(file->baseCurrency().smallestAccountFraction())));
0257         // only make balance visible if all transactions cover a single account
0258         ui->m_balanceLabel->setVisible(accountIds.count() < 2);
0259     }
0260 
0261     void ensureTagVisible(const QString& id)
0262     {
0263         const auto baseIdx = MyMoneyFile::instance()->tagsModel()->indexById(id);
0264         if (baseIdx.isValid()) {
0265             const auto idx = MyMoneyFile::baseModel()->mapFromBaseSource(m_renameProxyModel, baseIdx);
0266             ui->m_tagsList->setCurrentIndex(idx);
0267             ui->m_tagsList->scrollTo(idx);
0268         }
0269     }
0270 
0271     void loadDetails()
0272     {
0273         ui->m_colorbutton->setEnabled(true);
0274         ui->m_colorbutton->setColor(m_tag.tagColor());
0275         ui->m_closed->setEnabled(true);
0276         ui->m_closed->setChecked(m_tag.isClosed());
0277         ui->m_notes->setEnabled(true);
0278         ui->m_notes->setText(m_tag.notes());
0279     }
0280 
0281     void finalizePendingChanges()
0282     {
0283         Q_Q(KTagsView);
0284         // check if the content of a currently selected tag was modified
0285         // and ask to store the data
0286         if (m_havePendingChanges) {
0287             if (KMessageBox::questionTwoActions(q,
0288                                                 QString("<qt>%1</qt>").arg(i18n("Do you want to save the changes for <b>%1</b>?", m_newName)),
0289                                                 i18n("Save changes"),
0290                                                 KMMYesNo::yes(),
0291                                                 KMMYesNo::no())
0292                 == KMessageBox::PrimaryAction) {
0293                 q->slotUpdateTag();
0294             }
0295         }
0296     }
0297 
0298     /**
0299      * Check if a list contains a tag with a given id
0300      *
0301      * @param list const reference to value list
0302      * @param id const reference to id
0303      *
0304      * @retval true object has been found
0305      * @retval false object is not in list
0306      */
0307     bool tagInList(const QList<MyMoneyTag>& list, const QString& id) const
0308     {
0309         bool rc = false;
0310         QList<MyMoneyTag>::const_iterator it_p = list.begin();
0311         while (it_p != list.end()) {
0312             if ((*it_p).id() == id) {
0313                 rc = true;
0314                 break;
0315             }
0316             ++it_p;
0317         }
0318         return rc;
0319     }
0320 
0321     Ui::KTagsView* ui;
0322     LedgerTagFilter* m_transactionFilter;
0323     ItemRenameProxyModel* m_renameProxyModel;
0324     QAction* m_updateAction;
0325 
0326     MyMoneyTag m_tag;
0327     QString m_newName;
0328 
0329     /**
0330      * This member holds the load state of page
0331      */
0332     bool m_needLoad;
0333 };
0334 
0335 // *** KTagsView Implementation ***
0336 KTagsView::KTagsView(QWidget *parent) :
0337     KMyMoneyViewBase(*new KTagsViewPrivate(this), parent)
0338 {
0339     typedef void(KTagsView::*KTagsViewFunc)();
0340     const QHash<eMenu::Action, KTagsViewFunc> actionConnections {
0341         {eMenu::Action::NewTag,    &KTagsView::slotNewTag},
0342         {eMenu::Action::RenameTag, &KTagsView::slotRenameTag},
0343         {eMenu::Action::DeleteTag, &KTagsView::slotDeleteTag},
0344     };
0345 
0346     for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a)
0347         connect(pActions[a.key()], &QAction::triggered, this, a.value());
0348 }
0349 
0350 KTagsView::~KTagsView()
0351 {
0352 }
0353 
0354 void KTagsView::slotRenameSingleTag(const QModelIndex& idx, const QVariant& value)
0355 {
0356     Q_D(KTagsView);
0357     //if there is no current item selected, exit
0358     if (!idx.isValid())
0359         return;
0360 
0361     //qDebug() << "[KTagsView::slotRenameTag]";
0362     // create a copy of the new name without appended whitespaces
0363     const auto new_name = value.toString();
0364     // reload
0365     d->m_tag = MyMoneyFile::instance()->tagsModel()->itemById(idx.data(eMyMoney::Model::IdRole).toString());
0366     ;
0367     if (d->m_tag.name() != new_name) {
0368         MyMoneyFileTransaction ft;
0369         try {
0370             // check if we already have a tag with the new name
0371             const auto tag = MyMoneyFile::instance()->tagByName(new_name);
0372             // if the name already exists, ask the user whether he's sure to keep the name
0373             if (!tag.id().isEmpty()) {
0374                 if (KMessageBox::questionTwoActions(this,
0375                                                     i18n("A tag with the name '%1' already exists. It is not advisable to have "
0376                                                          "multiple tags with the same identification name. Are you sure you would like "
0377                                                          "to rename the tag?",
0378                                                          new_name),
0379                                                     i18nc("@title:window", "Duplicate tag name"),
0380                                                     KMMYesNo::yes(),
0381                                                     KMMYesNo::no())
0382                     != KMessageBox::PrimaryAction) {
0383                     return;
0384                 }
0385             }
0386 
0387             d->m_tag.setName(new_name);
0388             d->m_newName = new_name;
0389             MyMoneyFile::instance()->modifyTag(d->m_tag);
0390 
0391             // the above call to modifyTag will reload the view so
0392             // all references and pointers to the view have to be
0393             // re-established.
0394 
0395             // make sure, that the record is visible even if it moved
0396             // out of sight due to the rename operation
0397             d->ensureTagVisible(d->m_tag.id());
0398 
0399             ft.commit();
0400 
0401         } catch (const MyMoneyException &e) {
0402             KMessageBox::detailedError(this, i18n("Unable to modify tag"), QString::fromLatin1(e.what()));
0403         }
0404     }
0405 }
0406 
0407 void KTagsView::aboutToShow()
0408 {
0409     Q_D(KTagsView);
0410     d->loadDetails();
0411 
0412     // don't forget base class logic
0413     KMyMoneyViewBase::aboutToShow();
0414 }
0415 
0416 void KTagsView::aboutToHide()
0417 {
0418     Q_D(KTagsView);
0419 
0420     d->finalizePendingChanges();
0421 
0422     // don't forget base class logic
0423     KMyMoneyViewBase::aboutToHide();
0424 }
0425 
0426 void KTagsView::slotTagSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
0427 {
0428     Q_UNUSED(deselected)
0429 
0430     Q_D(KTagsView);
0431     d->finalizePendingChanges();
0432 
0433     // loop over all tags and count the number of tags, also
0434     // obtain last selected tag
0435 
0436     for (const auto& idx : deselected.indexes()) {
0437         d->m_selections.removeSelection(SelectedObjects::Tag, idx.data(eMyMoney::Model::IdRole).toString());
0438     }
0439     for (const auto& idx : selected.indexes()) {
0440         d->m_selections.addSelection(SelectedObjects::Tag, idx.data(eMyMoney::Model::IdRole).toString());
0441     }
0442 
0443     if (d->m_selections.selection(SelectedObjects::Tag).isEmpty()) {
0444         d->m_tag = MyMoneyTag();
0445     } else {
0446         d->m_tag = MyMoneyFile::instance()->tagsModel()->itemById(d->m_selections.selection(SelectedObjects::Tag).at(0));
0447     }
0448 
0449     try {
0450         d->m_newName = d->m_tag.name();
0451         d->loadDetails();
0452         slotTagDataChanged();
0453         d->showTransactions();
0454 
0455     } catch (const MyMoneyException &e) {
0456         qDebug("exception during display of tag: %s", e.what());
0457         d->m_tag = MyMoneyTag();
0458     }
0459 
0460     Q_EMIT requestSelectionChange(d->m_selections);
0461 }
0462 
0463 void KTagsView::slotTransactionSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
0464 {
0465     Q_UNUSED(deselected)
0466 
0467     Q_D(KTagsView);
0468 
0469     d->m_selections.clearSelections(SelectedObjects::JournalEntry);
0470     if (!selected.indexes().isEmpty()) {
0471         d->m_selections.addSelection(SelectedObjects::JournalEntry, selected.indexes().first().data(eMyMoney::Model::IdRole).toString());
0472     }
0473 
0474     Q_EMIT requestSelectionChange(d->m_selections);
0475 }
0476 
0477 void KTagsView::slotModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
0478 {
0479     Q_D(KTagsView);
0480     QModelIndex idx;
0481     if (topLeft.model() == d->m_renameProxyModel) {
0482         const auto baseModel = MyMoneyFile::instance()->tagsModel();
0483         for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
0484             idx = topLeft.model()->index(row, 0, topLeft.parent());
0485             if (d->m_tag.id() == idx.data(eMyMoney::Model::IdRole).toString()) {
0486                 d->m_tag = baseModel->itemById(d->m_tag.id());
0487                 d->loadDetails();
0488             }
0489         }
0490     }
0491 }
0492 
0493 void KTagsView::slotTagDataChanged()
0494 {
0495     Q_D(KTagsView);
0496     d->m_havePendingChanges = false;
0497 
0498     if (d->ui->m_tabWidget->isEnabled()) {
0499         d->m_havePendingChanges |= ((d->m_tag.tagColor().isValid() != d->ui->m_colorbutton->color().isValid())
0500                                     || (d->ui->m_colorbutton->color().isValid() && d->m_tag.tagColor() != d->ui->m_colorbutton->color()));
0501         d->m_havePendingChanges |= (d->ui->m_closed->isChecked() != d->m_tag.isClosed());
0502         d->m_havePendingChanges |= ((d->m_tag.notes().isEmpty() != d->ui->m_notes->toPlainText().isEmpty())
0503                                     || (!d->ui->m_notes->toPlainText().isEmpty() && d->m_tag.notes() != d->ui->m_notes->toPlainText()));
0504     }
0505     d->m_updateAction->setEnabled(d->m_havePendingChanges);
0506 }
0507 
0508 void KTagsView::slotUpdateTag()
0509 {
0510     Q_D(KTagsView);
0511     if (d->m_havePendingChanges) {
0512         MyMoneyFileTransaction ft;
0513         try {
0514             d->m_tag.setName(d->m_newName);
0515             d->m_tag.setTagColor(d->ui->m_colorbutton->color());
0516             d->m_tag.setClosed(d->ui->m_closed->isChecked());
0517             d->m_tag.setNotes(d->ui->m_notes->toPlainText());
0518 
0519             MyMoneyFile::instance()->modifyTag(d->m_tag);
0520             ft.commit();
0521 
0522             d->m_updateAction->setEnabled(false);
0523             d->m_havePendingChanges = false;
0524 
0525         } catch (const MyMoneyException &e) {
0526             KMessageBox::detailedError(this, i18n("Unable to modify tag"), QString::fromLatin1(e.what()));
0527         }
0528     }
0529 }
0530 
0531 void KTagsView::showEvent(QShowEvent* event)
0532 {
0533     Q_D(KTagsView);
0534     if (d->m_needLoad) {
0535         d->init();
0536         connect(d->ui->m_filterBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int idx) {
0537             Q_D(KTagsView);
0538             d->m_renameProxyModel->setReferenceFilter(d->ui->m_filterBox->itemData(idx));
0539         } );
0540 
0541         connect(d->ui->m_tagsList, &QWidget::customContextMenuRequested, this, [&](QPoint pos) {
0542             Q_D(KTagsView);
0543             Q_EMIT requestCustomContextMenu(eMenu::Menu::Tag, d->ui->m_tagsList->mapToGlobal(pos));
0544         });
0545 
0546         connect(d->ui->m_register, &QWidget::customContextMenuRequested, this, [&](QPoint pos) {
0547             Q_D(KTagsView);
0548             Q_EMIT requestCustomContextMenu(eMenu::Menu::Transaction, d->ui->m_register->mapToGlobal(pos));
0549         });
0550 
0551         connect(LedgerViewSettings::instance(), &LedgerViewSettings::settingsChanged, this, [&]() {
0552             Q_D(KTagsView);
0553             d->m_transactionFilter->setHideReconciledTransactions(LedgerViewSettings::instance()->hideReconciledTransactions());
0554             d->m_transactionFilter->setHideTransactionsBefore(LedgerViewSettings::instance()->hideTransactionsBefore());
0555         });
0556     }
0557     // don't forget base class implementation
0558     QWidget::showEvent(event);
0559 }
0560 
0561 void KTagsView::updateActions(const SelectedObjects& selections)
0562 {
0563     Q_D(KTagsView);
0564 
0565     // needs complete initialization
0566     if (d->m_needLoad) {
0567         return;
0568     }
0569 
0570     pActions[eMenu::Action::DeleteTag]->setEnabled(false);
0571     pActions[eMenu::Action::RenameTag]->setEnabled(false);
0572 
0573     switch(selections.selection(SelectedObjects::Tag).count()) {
0574     case 0:
0575         d->ui->m_tabWidget->setEnabled(false); // disable tab widget
0576         d->ui->m_balanceLabel->hide();
0577         d->clearItemData();
0578         break;
0579     case 1:
0580         d->ui->m_tabWidget->setEnabled(true); // disable tab widget
0581         d->ui->m_balanceLabel->show();
0582         pActions[eMenu::Action::DeleteTag]->setEnabled(true);
0583         pActions[eMenu::Action::RenameTag]->setEnabled(true);
0584         break;
0585     default:
0586         d->ui->m_tabWidget->setEnabled(false); // disable tab widget
0587         d->ui->m_balanceLabel->hide();
0588         pActions[eMenu::Action::DeleteTag]->setEnabled(true);
0589         d->clearItemData();
0590         break;
0591     }
0592 }
0593 
0594 void KTagsView::slotSelectTag(const QString& tagId)
0595 {
0596     Q_D(KTagsView);
0597     if (!isVisible())
0598         return;
0599 
0600     const auto model = MyMoneyFile::instance()->tagsModel();
0601     const auto baseIdx = model->indexById(tagId);
0602     auto idx = model->mapFromBaseSource(d->m_renameProxyModel, baseIdx);
0603     if (!idx.isValid()) {
0604         // item not found, maybe it is not visible due to filter and search
0605         // clear out any filter so it may become visible
0606         d->ui->m_searchWidget->clear();
0607         d->ui->m_filterBox->setCurrentIndex(0);
0608         // and try again
0609         idx = model->mapFromBaseSource(d->m_renameProxyModel, baseIdx);
0610     }
0611     if (idx.isValid()) {
0612         d->ui->m_tagsList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
0613     }
0614 
0615 }
0616 
0617 void KTagsView::slotSettingsChanged()
0618 {
0619     Q_D(KTagsView);
0620     if (d->ui->m_register) {
0621         d->ui->m_register->slotSettingsChanged();
0622     }
0623 }
0624 
0625 void KTagsView::slotHelp()
0626 {
0627     KHelpClient::invokeHelp("details.tags");
0628 }
0629 
0630 void KTagsView::slotNewTag()
0631 {
0632     QString tagId;
0633     bool ok;
0634     std::tie(ok, tagId) = KMyMoneyUtils::newTag(i18n("New Tag"));
0635     if (ok) {
0636         slotSelectTag(tagId);
0637     }
0638 }
0639 
0640 void KTagsView::slotRenameTag()
0641 {
0642     Q_D(KTagsView);
0643     if (d->ui->m_tagsList->currentIndex().isValid() && d->ui->m_tagsList->selectionModel()->selectedIndexes().count() == 1) {
0644         d->ui->m_tagsList->edit(d->ui->m_tagsList->currentIndex());
0645     }
0646 }
0647 
0648 void KTagsView::slotDeleteTag()
0649 {
0650     Q_D(KTagsView);
0651     QList<MyMoneyTag> selectedTags;
0652     const auto file = MyMoneyFile::instance();
0653     const auto model = file->tagsModel();
0654     QModelIndex baseIdx;
0655 
0656     for (const auto& idx : d->ui->m_tagsList->selectionModel()->selectedIndexes()) {
0657         baseIdx = model->mapToBaseSource(idx);
0658         const auto tag = model->itemByIndex(baseIdx);
0659         if (!tag.id().isEmpty()) {
0660             selectedTags.append(tag);
0661         }
0662     }
0663     if (selectedTags.isEmpty())
0664         return; // shouldn't happen
0665 
0666     // first create list with all non-selected tags
0667     QList<MyMoneyTag> remainingTags = file->tagList();
0668     QList<MyMoneyTag>::iterator it_ta;
0669     for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) {
0670         if (selectedTags.contains(*it_ta)) {
0671             it_ta = remainingTags.erase(it_ta);
0672         } else {
0673             ++it_ta;
0674         }
0675     }
0676 
0677     MyMoneyFileTransaction ft;
0678     try {
0679         // create a transaction filter that contains all tags selected for removal
0680         MyMoneyTransactionFilter f = MyMoneyTransactionFilter();
0681         for (const auto& tag : selectedTags) {
0682             f.addTag(tag.id());
0683         }
0684         // request a list of all transactions that still use the tags in question
0685         QList<MyMoneyTransaction> translist;
0686         file->transactionList(translist, f);
0687         //     qDebug() << "[KTagsView::slotDeleteTag]  " << translist.count() << " transaction still assigned to tags";
0688 
0689         // now get a list of all schedules that make use of one of the tags
0690         QList<MyMoneySchedule> used_schedules;
0691         for (const auto& schedule : file->scheduleList()) {
0692             // loop over all splits in the transaction of the schedule
0693             const auto splits = schedule.transaction().splits();
0694             for (const auto& split : splits) {
0695                 for (auto i = 0; i < split.tagIdList().size(); ++i) {
0696                     // is the tag in the split to be deleted?
0697                     if (d->tagInList(selectedTags, split.tagIdList()[i])) {
0698                         used_schedules.push_back(schedule); // remember this schedule
0699                         break;
0700                     }
0701                 }
0702             }
0703         }
0704 
0705         QList<MyMoneyReport> used_reports;
0706         for (const auto& report : file->reportList()) {
0707             QStringList tagList(report.tags());
0708             for (const auto& tag : tagList) {
0709                 if (d->tagInList(selectedTags, tag)) {
0710                     used_reports.push_back(report);
0711                     break;
0712                 }
0713             }
0714         }
0715 
0716 //     qDebug() << "[KTagsView::slotDeleteTag]  " << used_schedules.count() << " schedules use one of the selected tags";
0717 
0718         MyMoneyTag newTag;
0719         // if at least one tag is still referenced, we need to reassign its transactions first
0720         if (!translist.isEmpty() || !used_schedules.isEmpty() || !used_reports.isEmpty()) {
0721             // show transaction reassignment dialog
0722             QPointer<KTagReassignDlg> dlg = new KTagReassignDlg(this);
0723             KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
0724             dlg->setupFilter(d->m_selections.selection(SelectedObjects::Tag));
0725             if ((dlg->exec() == QDialog::Rejected) || !dlg) {
0726                 delete dlg;
0727                 return; // the user aborted the dialog, so let's abort as well
0728             }
0729             auto newTagId = dlg->reassignTo();
0730             delete dlg; // and kill the dialog
0731 
0732             if (!newTagId.isEmpty()) {
0733                 newTag = file->tag(newTagId);
0734             }
0735 
0736             // check if we have a report that explicitly uses one of our tags
0737             // and remove/replace it
0738             try {
0739                 // now loop over all report and reassign tag
0740                 for (auto report : used_reports) {
0741                     QStringList tagIdList(report.tags());
0742                     for (const auto& tagId : tagIdList) {
0743                         if (d->tagInList(selectedTags, tagId)) {
0744                             report.removeReference(tagId);
0745                             if (!newTagId.isEmpty()) {
0746                                 report.addTag(newTagId);
0747                             }
0748                         }
0749                     }
0750                     file->modifyReport(report); // modify the transaction in the MyMoney object
0751                 }
0752 
0753                 // now loop over all transactions and reassign tag
0754                 for (auto& transaction : translist) {
0755                     // create a copy of the splits list in the transaction
0756                     // loop over all splits
0757                     for (auto split : transaction.splits()) {
0758                         QList<QString> tagIdList = split.tagIdList();
0759                         for (int i = 0; i < tagIdList.size(); ++i) {
0760                             // if the split is assigned to one of the selected tags, we need to modify it
0761                             if (d->tagInList(selectedTags, tagIdList[i])) {
0762                                 tagIdList.removeAt(i);
0763                                 if (!newTagId.isEmpty()) {
0764                                     if (tagIdList.indexOf(newTagId) == -1) {
0765                                         tagIdList.append(newTagId);
0766                                     }
0767                                 }
0768                                 i = -1; // restart from the first element
0769                             }
0770                         }
0771                         split.setTagIdList(tagIdList); // first modify tag list in current split
0772                         // then modify the split in our local copy of the transaction list
0773                         transaction.modifySplit(split); // this does not modify the list object 'splits'!
0774                     } // for - Splits
0775                     file->modifyTransaction(transaction);  // modify the transaction in the MyMoney object
0776                 } // for - Transactions
0777 
0778                 // now loop over all schedules and reassign tags
0779                 for (auto& schedule : used_schedules) {
0780                     // create copy of transaction in current schedule
0781                     auto trans = schedule.transaction();
0782                     // create copy of lists of splits
0783                     for (auto& split : trans.splits()) {
0784                         QList<QString> tagIdList = split.tagIdList();
0785                         for (auto i = 0; i < tagIdList.size(); ++i) {
0786                             if (d->tagInList(selectedTags, tagIdList[i])) {
0787                                 tagIdList.removeAt(i);
0788                                 if (!newTagId.isEmpty()) {
0789                                     if (tagIdList.indexOf(newTagId) == -1) {
0790                                         tagIdList.append(newTagId);
0791                                     }
0792                                 }
0793                                 i = -1; // restart from the first element
0794                             }
0795                         }
0796                         split.setTagIdList(tagIdList);
0797                         trans.modifySplit(split); // does not modify the list object 'splits'!
0798                     } // for - Splits
0799                     // store transaction in current schedule
0800                     schedule.setTransaction(trans);
0801                     file->modifySchedule(schedule);  // modify the schedule in the MyMoney engine
0802                 } // for - Schedules
0803 
0804             } catch (const MyMoneyException &e) {
0805                 KMessageBox::detailedError(this, i18n("Unable to reassign tag of transaction/split"), QString::fromLatin1(e.what()));
0806             }
0807         } // if !translist.isEmpty()
0808 
0809         // now loop over all selected tags and remove them
0810         for (const auto& tag : selectedTags) {
0811             file->removeTag(tag);
0812         }
0813 
0814         ft.commit();
0815 
0816     } catch (const MyMoneyException &e) {
0817         KMessageBox::detailedError(this, i18n("Unable to remove tag(s)"), QString::fromLatin1(e.what()));
0818     }
0819 }