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 }