diff --git a/kmymoney/views/kaccountsview.cpp b/kmymoney/views/kaccountsview.cpp index 2cff8bfae..b160cadea 100644 --- a/kmymoney/views/kaccountsview.cpp +++ b/kmymoney/views/kaccountsview.cpp @@ -1,542 +1,542 @@ /*************************************************************************** kaccountsview.cpp ------------------- copyright : (C) 2007 by Thomas Baumgart (C) 2017, 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kaccountsview_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "onlinejobadministration.h" #include "knewaccountwizard.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "storageenums.h" #include "menuenums.h" using namespace Icons; KAccountsView::KAccountsView(QWidget *parent) : KMyMoneyAccountsViewBase(*new KAccountsViewPrivate(this), parent) { Q_D(KAccountsView); d->ui->setupUi(this); connect(pActions[eMenu::Action::NewAccount], &QAction::triggered, this, &KAccountsView::slotNewAccount); connect(pActions[eMenu::Action::EditAccount], &QAction::triggered, this, &KAccountsView::slotEditAccount); connect(pActions[eMenu::Action::DeleteAccount], &QAction::triggered, this, &KAccountsView::slotDeleteAccount); connect(pActions[eMenu::Action::CloseAccount], &QAction::triggered, this, &KAccountsView::slotCloseAccount); connect(pActions[eMenu::Action::ReopenAccount], &QAction::triggered, this, &KAccountsView::slotReopenAccount); connect(pActions[eMenu::Action::ChartAccountBalance], &QAction::triggered, this, &KAccountsView::slotChartAccountBalance); connect(pActions[eMenu::Action::MapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountMapOnline); connect(pActions[eMenu::Action::UnmapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountUnmapOnline); connect(pActions[eMenu::Action::UpdateAccount], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnline); connect(pActions[eMenu::Action::UpdateAllAccounts], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnlineAll); } KAccountsView::~KAccountsView() { } void KAccountsView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KAccountsView); QTimer::singleShot(0, d->ui->m_accountTree, SLOT(setFocus())); } break; default: break; } } void KAccountsView::refresh() { Q_D(KAccountsView); if (!isVisible()) { d->m_needsRefresh = true; return; } d->m_needsRefresh = false; // TODO: check why the invalidate is needed here d->m_proxyModel->invalidate(); - d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts()); + d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts() && !KMyMoneySettings::showAllAccounts()); d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); if (KMyMoneySettings::showCategoriesInAccountsView()) { d->m_proxyModel->addAccountGroup(QVector {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense}); } else { d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Income); d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Expense); } // reinitialize the default state of the hidden categories label d->m_haveUnusedCategories = false; d->ui->m_hiddenCategories->hide(); // hides label d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneySettings::hideUnusedCategory()); } void KAccountsView::showEvent(QShowEvent * event) { Q_D(KAccountsView); if (!d->m_proxyModel) d->init(); emit customActionRequested(View::Accounts, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); } void KAccountsView::updateActions(const MyMoneyObject& obj) { Q_D(KAccountsView); const auto file = MyMoneyFile::instance(); if (typeid(obj) != typeid(MyMoneyAccount) && (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled) return; const auto& acc = static_cast(obj); const QVector actionsToBeDisabled { eMenu::Action::NewAccount, eMenu::Action::EditAccount, eMenu::Action::DeleteAccount, eMenu::Action::CloseAccount, eMenu::Action::ReopenAccount, eMenu::Action::ChartAccountBalance, eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, eMenu::Action::UpdateAccount }; for (const auto& a : actionsToBeDisabled) pActions[a]->setEnabled(false); pActions[eMenu::Action::NewAccount]->setEnabled(true); pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts()); if (acc.id().isEmpty()) { d->m_currentAccount = MyMoneyAccount(); return; } else if (file->isStandardAccount(acc.id())) { d->m_currentAccount = acc; return; } d->m_currentAccount = acc; switch (acc.accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: { pActions[eMenu::Action::EditAccount]->setEnabled(true); pActions[eMenu::Action::DeleteAccount]->setEnabled(!file->isReferenced(acc)); auto b = acc.isClosed() ? true : false; pActions[eMenu::Action::ReopenAccount]->setEnabled(b); pActions[eMenu::Action::CloseAccount]->setEnabled(!b); if (!acc.isClosed()) { b = (d->canCloseAccount(acc) == KAccountsViewPrivate::AccountCanClose) ? true : false; pActions[eMenu::Action::CloseAccount]->setEnabled(b); d->hintCloseAccountAction(acc, pActions[eMenu::Action::CloseAccount]); } pActions[eMenu::Action::ChartAccountBalance]->setEnabled(true); if (d->m_currentAccount.hasOnlineMapping()) { pActions[eMenu::Action::UnmapOnlineAccount]->setEnabled(true); if (d->m_onlinePlugins) { // check if provider is available QMap::const_iterator it_p; it_p = d->m_onlinePlugins->constFind(d->m_currentAccount.onlineBankingSettings().value(QLatin1String("provider")).toLower()); if (it_p != d->m_onlinePlugins->constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { pActions[eMenu::Action::UpdateAccount]->setEnabled(true); } } } } else { pActions[eMenu::Action::MapOnlineAccount]->setEnabled(!acc.isClosed() && d->m_onlinePlugins && !d->m_onlinePlugins->isEmpty()); } break; } default: break; } #if 0 // It is unclear to me, what the following code should do // as it just does nothing QBitArray skip((int)eStorage::Reference::Count); if (!d->m_currentAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_currentAccount.id())) { switch (d->m_currentAccount.accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: break; default: break; } } } #endif } /** * The view is notified that an unused income expense account has been hidden. */ void KAccountsView::slotUnusedIncomeExpenseAccountHidden() { Q_D(KAccountsView); d->m_haveUnusedCategories = true; d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories); } void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth) { Q_D(KAccountsView); d->netBalProChanged(netWorth, d->ui->m_totalProfitsLabel, View::Accounts); } void KAccountsView::slotShowAccountMenu(const MyMoneyAccount& acc) { Q_UNUSED(acc); pMenus[eMenu::Menu::Account]->exec(QCursor::pos()); } void KAccountsView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch(intent) { case eView::Intent::UpdateActions: updateActions(obj); break; case eView::Intent::OpenContextMenu: slotShowAccountMenu(static_cast(obj)); break; default: break; } } void KAccountsView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { Q_D(KAccountsView); switch (intent) { case eView::Intent::UpdateNetWorth: if (variant.count() == 1) slotNetWorthChanged(variant.first().value()); break; case eView::Intent::SetOnlinePlugins: if (variant.count() == 1) d->m_onlinePlugins = static_cast*>(variant.first().value()); break; default: break; } } void KAccountsView::slotNewAccount() { MyMoneyAccount account; account.setOpeningDate(KMyMoneySettings::firstFiscalDate()); NewAccountWizard::Wizard::newAccount(account); } void KAccountsView::slotEditAccount() { Q_D(KAccountsView); switch (d->m_currentAccount.accountType()) { case eMyMoney::Account::Type::Loan: case eMyMoney::Account::Type::AssetLoan: d->editLoan(); break; default: d->editAccount(); break; } emit selectByObject(d->m_currentAccount, eView::Intent::None); } void KAccountsView::slotDeleteAccount() { Q_D(KAccountsView); if (d->m_currentAccount.id().isEmpty()) return; // need an account ID const auto file = MyMoneyFile::instance(); // can't delete standard accounts or account which still have transactions assigned if (file->isStandardAccount(d->m_currentAccount.id())) return; // check if the account is referenced by a transaction or schedule QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); skip.setBit((int)eStorage::Reference::Account); skip.setBit((int)eStorage::Reference::Institution); skip.setBit((int)eStorage::Reference::Payee); skip.setBit((int)eStorage::Reference::Tag); skip.setBit((int)eStorage::Reference::Security); skip.setBit((int)eStorage::Reference::Currency); skip.setBit((int)eStorage::Reference::Price); if (file->isReferenced(d->m_currentAccount, skip)) return; MyMoneyFileTransaction ft; // retain the account name for a possible later usage in the error message box // since the account removal notifies the views the selected account can be changed // so we make sure by doing this that we display the correct name in the error message auto selectedAccountName = d->m_currentAccount.name(); try { file->removeAccount(d->m_currentAccount); d->m_currentAccount.clearId(); emit selectByObject(MyMoneyAccount(), eView::Intent::None); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, QString::fromLatin1(e.what()))); } } void KAccountsView::slotCloseAccount() { Q_D(KAccountsView); MyMoneyFileTransaction ft; try { d->m_currentAccount.setClosed(true); MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); emit selectByObject(d->m_currentAccount, eView::Intent::None); ft.commit(); if (KMyMoneySettings::hideClosedAccounts()) KMessageBox::information(this, i18n("You have closed this account. It remains in the system because you have transactions which still refer to it, but it is not shown in the views. You can make it visible again by going to the View menu and selecting Show all accounts or by deselecting the Do not show closed accounts setting."), i18n("Information"), "CloseAccountInfo"); } catch (const MyMoneyException &) { } } void KAccountsView::slotReopenAccount() { Q_D(KAccountsView); const auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { auto& acc = d->m_currentAccount; while (acc.isClosed()) { acc.setClosed(false); file->modifyAccount(acc); acc = file->account(acc.parentAccountId()); } emit selectByObject(d->m_currentAccount, eView::Intent::None); ft.commit(); } catch (const MyMoneyException &) { } } void KAccountsView::slotChartAccountBalance() { Q_D(KAccountsView); if (!d->m_currentAccount.id().isEmpty()) { emit customActionRequested(View::Accounts, eView::Action::ShowBalanceChart); } } void KAccountsView::slotNewCategory() { Q_D(KAccountsView); KNewAccountDlg::newCategory(d->m_currentAccount, MyMoneyAccount()); } void KAccountsView::slotNewPayee(const QString& nameBase, QString& id) { KMyMoneyUtils::newPayee(nameBase, id); } void KAccountsView::slotAccountUnmapOnline() { Q_D(KAccountsView); // no account selected if (d->m_currentAccount.id().isEmpty()) return; // not a mapped account if (!d->m_currentAccount.hasOnlineMapping()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_currentAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_currentAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // Avoid showing an oline balance d->m_currentAccount.deletePair(QStringLiteral("lastStatementBalance")); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_currentAccount.deletePair(QStringLiteral("StatementKey")); MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", QString::fromLatin1(e.what()))); } } updateActions(d->m_currentAccount); } void KAccountsView::slotAccountMapOnline() { Q_D(KAccountsView); // no account selected if (d->m_currentAccount.id().isEmpty()) return; // already an account mapped if (d->m_currentAccount.hasOnlineMapping()) return; // check if user tries to map a brokerageAccount if (d->m_currentAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } if (!d->m_onlinePlugins) return; // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins->count()) { case 0: break; case 1: provider = d->m_onlinePlugins->begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins->constBegin(); it_p != d->m_onlinePlugins->constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins->constFind(provider.toLower()); if (it_p != d->m_onlinePlugins->constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_currentAccount, settings)) { settings["provider"] = provider.toLower(); MyMoneyAccount acc(d->m_currentAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", QString::fromLatin1(e.what()))); } } } updateActions(d->m_currentAccount); } void KAccountsView::slotAccountUpdateOnlineAll() { Q_D(KAccountsView); QList accList; MyMoneyFile::instance()->accountList(accList); QList mappedAccList; Q_FOREACH(auto account, accList) { if (account.hasOnlineMapping()) mappedAccList += account; } d->accountsUpdateOnline(mappedAccList); } void KAccountsView::slotAccountUpdateOnline() { Q_D(KAccountsView); // no account selected if (d->m_currentAccount.id().isEmpty()) return; // no online account mapped if (!d->m_currentAccount.hasOnlineMapping()) return; d->accountsUpdateOnline(QList { d->m_currentAccount } ); } diff --git a/kmymoney/views/kcategoriesview.cpp b/kmymoney/views/kcategoriesview.cpp index 4307f725a..7ecfa5881 100644 --- a/kmymoney/views/kcategoriesview.cpp +++ b/kmymoney/views/kcategoriesview.cpp @@ -1,496 +1,496 @@ /*************************************************************************** kcategoriesview.cpp - description ------------------- begin : Sun Jan 20 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kcategoriesview_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "kmymoneysettings.h" #include "knewaccountdlg.h" #include "kcategoryreassigndlg.h" #include "mymoneyschedule.h" #include "mymoneybudget.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneyenums.h" #include "storageenums.h" #include "menuenums.h" using namespace Icons; KCategoriesView::KCategoriesView(QWidget *parent) : KMyMoneyAccountsViewBase(*new KCategoriesViewPrivate(this), parent) { Q_D(KCategoriesView); d->ui->setupUi(this); connect(pActions[eMenu::Action::NewCategory], &QAction::triggered, this, &KCategoriesView::slotNewCategory); connect(pActions[eMenu::Action::EditCategory], &QAction::triggered, this, &KCategoriesView::slotEditCategory); connect(pActions[eMenu::Action::DeleteCategory], &QAction::triggered, this, &KCategoriesView::slotDeleteCategory); } KCategoriesView::~KCategoriesView() { } void KCategoriesView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KCategoriesView); QTimer::singleShot(0, d->ui->m_accountTree, SLOT(setFocus())); } break; default: break; } } void KCategoriesView::refresh() { Q_D(KCategoriesView); if (!isVisible()) { d->m_needsRefresh = true; return; } d->m_needsRefresh = false; d->m_proxyModel->invalidate(); - d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts()); + d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts() && !KMyMoneySettings::showAllAccounts()); // reinitialize the default state of the hidden categories label d->m_haveUnusedCategories = false; d->ui->m_hiddenCategories->hide(); d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneySettings::hideUnusedCategory()); } void KCategoriesView::showEvent(QShowEvent * event) { Q_D(KCategoriesView); if (!d->m_proxyModel) d->init(); emit customActionRequested(View::Categories, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); } void KCategoriesView::updateActions(const MyMoneyObject& obj) { Q_D(KCategoriesView); if (typeid(obj) != typeid(MyMoneyAccount) && (obj.id().isEmpty() && d->m_currentCategory.id().isEmpty())) // do not disable actions that were already disabled)) return; const auto& acc = static_cast(obj); if (d->m_currentCategory.id().isEmpty() && acc.id().isEmpty()) return; switch (acc.accountType()) { case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Expense: { const auto file = MyMoneyFile::instance(); auto b = file->isStandardAccount(acc.id()) ? false : true; pActions[eMenu::Action::EditCategory]->setEnabled(b); // enable delete action, if category/account itself is not referenced // by any object except accounts, because we want to allow // deleting of sub-categories. Also, we allow transactions, schedules and budgets // to be present because we can re-assign them during the delete process QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); skip.setBit((int)eStorage::Reference::Transaction); skip.setBit((int)eStorage::Reference::Account); skip.setBit((int)eStorage::Reference::Schedule); skip.setBit((int)eStorage::Reference::Budget); pActions[eMenu::Action::DeleteCategory]->setEnabled(b && !file->isReferenced(acc, skip)); d->m_currentCategory = acc; break; } default: pActions[eMenu::Action::EditCategory]->setEnabled(false); pActions[eMenu::Action::DeleteCategory]->setEnabled(false); d->m_currentCategory = MyMoneyAccount(); break; } } /** * The view is notified that an unused income expense account has been hidden. */ void KCategoriesView::slotUnusedIncomeExpenseAccountHidden() { Q_D(KCategoriesView); d->m_haveUnusedCategories = true; d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories); } void KCategoriesView::slotProfitChanged(const MyMoneyMoney &profit) { Q_D(KCategoriesView); d->netBalProChanged(profit, d->ui->m_totalProfitsLabel, View::Categories); } void KCategoriesView::slotShowCategoriesMenu(const MyMoneyAccount& acc) { Q_UNUSED(acc); pMenus[eMenu::Menu::Category]->exec(QCursor::pos()); } void KCategoriesView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch(intent) { case eView::Intent::UpdateActions: updateActions(obj); break; case eView::Intent::OpenContextMenu: slotShowCategoriesMenu(static_cast(obj)); break; default: break; } } void KCategoriesView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch (intent) { case eView::Intent::UpdateProfit: if (variant.count() == 1) slotProfitChanged(variant.first().value()); break; default: break; } } void KCategoriesView::slotNewCategory() { Q_D(KCategoriesView); MyMoneyAccount parent; MyMoneyAccount account; // Preselect the parent account by looking at the current selected account/category if (!d->m_currentCategory.id().isEmpty() && d->m_currentCategory.isIncomeExpense()) { try { parent = MyMoneyFile::instance()->account(d->m_currentCategory.id()); } catch (const MyMoneyException &) { } } KNewAccountDlg::createCategory(account, parent); } void KCategoriesView::slotEditCategory() { Q_D(KCategoriesView); if (d->m_currentCategory.id().isEmpty()) return; const auto file = MyMoneyFile::instance(); if (file->isStandardAccount(d->m_currentCategory.id())) return; switch (d->m_currentCategory.accountType()) { case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Expense: break; default: return; } // set a status message so that the application can't be closed until the editing is done // slotStatusMsg(caption); QPointer dlg = new KNewAccountDlg(d->m_currentCategory, true, true, 0, i18n("Edit category '%1'", d->m_currentCategory.name())); dlg->setOpeningBalanceShown(false); dlg->setOpeningDateShown(false); if (dlg && dlg->exec() == QDialog::Accepted) { try { MyMoneyFileTransaction ft; auto account = dlg->account(); auto parent = dlg->parentAccount(); // we need to modify first, as reparent would override all other changes file->modifyAccount(account); if (account.parentAccountId() != parent.id()) file->reparentAccount(account, parent); ft.commit(); // reload the account object as it might have changed in the meantime emit selectByObject(account, eView::Intent::None); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to modify category '%1'. Cause: %2", d->m_currentCategory.name(), QString::fromLatin1(e.what()))); } } delete dlg; // ready(); } void KCategoriesView::slotDeleteCategory() { Q_D(KCategoriesView); if (d->m_currentCategory.id().isEmpty()) return; // need an account ID const auto file = MyMoneyFile::instance(); // can't delete standard accounts or account which still have transactions assigned if (file->isStandardAccount(d->m_currentCategory.id())) return; // check if the account is referenced by a transaction or schedule QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); skip.setBit((int)eStorage::Reference::Account); skip.setBit((int)eStorage::Reference::Institution); skip.setBit((int)eStorage::Reference::Payee); skip.setBit((int)eStorage::Reference::Tag); skip.setBit((int)eStorage::Reference::Security); skip.setBit((int)eStorage::Reference::Currency); skip.setBit((int)eStorage::Reference::Price); const auto hasReference = file->isReferenced(d->m_currentCategory, skip); // if we get here and still have transactions referencing the account, we // need to check with the user to possibly re-assign them to a different account auto needAskUser = true; MyMoneyFileTransaction ft; if (hasReference) { // show transaction reassignment dialog needAskUser = false; auto dlg = new KCategoryReassignDlg(this); auto categoryId = dlg->show(d->m_currentCategory); delete dlg; // and kill the dialog if (categoryId.isEmpty()) return; // the user aborted the dialog, so let's abort as well auto newCategory = file->account(categoryId); try { { // KMSTATUS(i18n("Adjusting transactions...")); /* d->m_currentCategory.id() is the old id, categoryId the new one Now search all transactions and schedules that reference d->m_currentCategory.id() and replace that with categoryId. */ // get the list of all transactions that reference the old account MyMoneyTransactionFilter filter(d->m_currentCategory.id()); filter.setReportAllSplits(false); QList tlist; QList::iterator it_t; file->transactionList(tlist, filter); // slotStatusProgressBar(0, tlist.count()); // int cnt = 0; for (it_t = tlist.begin(); it_t != tlist.end(); ++it_t) { // slotStatusProgressBar(++cnt, 0); MyMoneyTransaction t = (*it_t); if (t.replaceId(categoryId, d->m_currentCategory.id())) file->modifyTransaction(t); } // slotStatusProgressBar(tlist.count(), 0); } // now fix all schedules { // KMSTATUS(i18n("Adjusting scheduled transactions...")); QList slist = file->scheduleList(d->m_currentCategory.id()); QList::iterator it_s; // int cnt = 0; // slotStatusProgressBar(0, slist.count()); for (it_s = slist.begin(); it_s != slist.end(); ++it_s) { // slotStatusProgressBar(++cnt, 0); MyMoneySchedule sch = (*it_s); if (sch.replaceId(categoryId, d->m_currentCategory.id())) { file->modifySchedule(sch); } } // slotStatusProgressBar(slist.count(), 0); } // now fix all budgets { // KMSTATUS(i18n("Adjusting budgets...")); QList blist = file->budgetList(); QList::const_iterator it_b; for (it_b = blist.constBegin(); it_b != blist.constEnd(); ++it_b) { if ((*it_b).hasReferenceTo(d->m_currentCategory.id())) { MyMoneyBudget b = (*it_b); MyMoneyBudget::AccountGroup fromBudget = b.account(d->m_currentCategory.id()); MyMoneyBudget::AccountGroup toBudget = b.account(categoryId); toBudget += fromBudget; b.setAccount(toBudget, categoryId); b.removeReference(d->m_currentCategory.id()); file->modifyBudget(b); } } // slotStatusProgressBar(blist.count(), 0); } } catch (MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to exchange category %1 with category %2. Reason: %3", d->m_currentCategory.name(), newCategory.name(), QString::fromLatin1(e.what()))); // slotStatusProgressBar(-1, -1); return; } // slotStatusProgressBar(-1, -1); } // retain the account name for a possible later usage in the error message box // since the account removal notifies the views the selected account can be changed // so we make sure by doing this that we display the correct name in the error message auto selectedAccountName = d->m_currentCategory.name(); // at this point, we must not have a reference to the account // to be deleted anymore // special handling for categories to allow deleting of empty subcategories { // open a compound statement here to be able to declare variables // which would otherwise not work within a case label. // case A - only a single, unused category without subcats selected if (d->m_currentCategory.accountList().isEmpty()) { if (!needAskUser || (KMessageBox::questionYesNo(this, i18n("Do you really want to delete category %1?", selectedAccountName)) == KMessageBox::Yes)) { try { file->removeAccount(d->m_currentCategory); d->m_currentCategory.clearId(); emit selectByObject(d->m_currentCategory, eView::Intent::None); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete category %1. Cause: %2", selectedAccountName, QString::fromLatin1(e.what()))); } } return; } // case B - we have some subcategories, maybe the user does not want to // delete them all, but just the category itself? auto parentAccount = file->account(d->m_currentCategory.parentAccountId()); QStringList accountsToReparent; int result = KMessageBox::questionYesNoCancel(this, i18n("Do you want to delete category %1 with all its sub-categories or only " "the category itself? If you only delete the category itself, all its sub-categories " "will be made sub-categories of %2.", selectedAccountName, parentAccount.name()), QString(), KGuiItem(i18n("Delete all")), KGuiItem(i18n("Just the category"))); if (result == KMessageBox::Cancel) return; // cancel pressed? ok, no delete then... // "No" means "Just the category" and that means we need to reparent all subaccounts bool need_confirmation = false; // case C - User only wants to delete the category itself if (result == KMessageBox::No) accountsToReparent = d->m_currentCategory.accountList(); else { // case D - User wants to delete all subcategories, now check all subcats of // d->m_currentCategory and remember all that cannot be deleted and // must be "reparented" foreach (const auto accountID, d->m_currentCategory.accountList()) { // reparent account if a transaction is assigned if (file->transactionCount(accountID) != 0) accountsToReparent.push_back(accountID); else if (!file->account(accountID).accountList().isEmpty()) { // or if we have at least one sub-account that is used for transactions if (!file->hasOnlyUnusedAccounts(file->account(accountID).accountList())) { accountsToReparent.push_back(accountID); //qDebug() << "subaccount not empty"; } } } if (!accountsToReparent.isEmpty()) need_confirmation = true; } if (!accountsToReparent.isEmpty() && need_confirmation) { if (KMessageBox::questionYesNo(this, i18n("

Some sub-categories of category %1 cannot " "be deleted, because they are still used. They will be made sub-categories of %2. Proceed?

", selectedAccountName, parentAccount.name())) != KMessageBox::Yes) { return; // user gets wet feet... } } // all good, now first reparent selected sub-categories try { auto parent = file->account(d->m_currentCategory.parentAccountId()); for (QStringList::const_iterator it = accountsToReparent.constBegin(); it != accountsToReparent.constEnd(); ++it) { auto child = file->account(*it); file->reparentAccount(child, parent); } // reload the account because the sub-account list might have changed d->m_currentCategory = file->account(d->m_currentCategory.id()); // now recursively delete remaining sub-categories file->removeAccountList(d->m_currentCategory.accountList()); // don't forget to update d->m_currentCategory, because we still have a copy of // the old account list, which is no longer valid d->m_currentCategory = file->account(d->m_currentCategory.id()); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete a sub-category of category %1. Reason: %2", selectedAccountName, QString::fromLatin1(e.what()))); return; } } // the category/account is deleted after the switch try { file->removeAccount(d->m_currentCategory); d->m_currentCategory.clearId(); emit selectByObject(MyMoneyAccount(), eView::Intent::None); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete category '%1'. Cause: %2", selectedAccountName, QString::fromLatin1(e.what()))); } } diff --git a/kmymoney/views/kinstitutionsview.cpp b/kmymoney/views/kinstitutionsview.cpp index f915429c2..cfcf6af12 100644 --- a/kmymoney/views/kinstitutionsview.cpp +++ b/kmymoney/views/kinstitutionsview.cpp @@ -1,240 +1,240 @@ /*************************************************************************** kinstitutionsview.cpp ------------------- copyright : (C) 2007 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kinstitutionsview_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneysettings.h" #include "mymoneyexception.h" #include "knewbankdlg.h" #include "menuenums.h" using namespace Icons; KInstitutionsView::KInstitutionsView(QWidget *parent) : KMyMoneyAccountsViewBase(*new KInstitutionsViewPrivate(this), parent) { Q_D(KInstitutionsView); d->ui->setupUi(this); connect(pActions[eMenu::Action::NewInstitution], &QAction::triggered, this, &KInstitutionsView::slotNewInstitution); connect(pActions[eMenu::Action::EditInstitution], &QAction::triggered, this, &KInstitutionsView::slotEditInstitution); connect(pActions[eMenu::Action::DeleteInstitution], &QAction::triggered, this, &KInstitutionsView::slotDeleteInstitution); } KInstitutionsView::~KInstitutionsView() { } void KInstitutionsView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KInstitutionsView); QTimer::singleShot(0, d->ui->m_accountTree, SLOT(setFocus())); } break; case eView::Action::EditInstitution: slotEditInstitution(); break; default: break; } } void KInstitutionsView::refresh() { Q_D(KInstitutionsView); if (!isVisible()) { d->m_needsRefresh = true; return; } d->m_needsRefresh = false; d->m_proxyModel->invalidate(); d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); - d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts()); + d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts() && !KMyMoneySettings::showAllAccounts()); } void KInstitutionsView::showEvent(QShowEvent * event) { Q_D(KInstitutionsView); if (!d->m_proxyModel) d->init(); emit customActionRequested(View::Institutions, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); } void KInstitutionsView::updateActions(const MyMoneyObject& obj) { Q_D(KInstitutionsView); if (typeid(obj) != typeid(MyMoneyInstitution) || (obj.id().isEmpty() && d->m_currentInstitution.id().isEmpty())) // do not disable actions that were already disabled return; const auto& inst = static_cast(obj); pActions[eMenu::Action::NewInstitution]->setEnabled(true); auto b = inst.id().isEmpty() ? false : true; pActions[eMenu::Action::EditInstitution]->setEnabled(b); pActions[eMenu::Action::DeleteInstitution]->setEnabled(b && !MyMoneyFile::instance()->isReferenced(inst)); d->m_currentInstitution = inst; } void KInstitutionsView::slotNetWorthChanged(const MyMoneyMoney &netWorth) { Q_D(KInstitutionsView); d->netBalProChanged(netWorth, d->ui->m_totalProfitsLabel, View::Institutions); } void KInstitutionsView::slotNewInstitution() { Q_D(KInstitutionsView); d->m_currentInstitution.clearId(); QPointer dlg = new KNewBankDlg(d->m_currentInstitution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->m_currentInstitution = dlg->institution(); const auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(d->m_currentInstitution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Cannot add institution: %1", QString::fromLatin1(e.what()))); } } delete dlg; } void KInstitutionsView::slotShowInstitutionsMenu(const MyMoneyInstitution& inst) { Q_UNUSED(inst); pMenus[eMenu::Menu::Institution]->exec(QCursor::pos()); } void KInstitutionsView::slotEditInstitution() { Q_D(KInstitutionsView); // make sure the selected object has an id if (d->m_currentInstitution.id().isEmpty()) return; try { const auto file = MyMoneyFile::instance(); //grab a pointer to the view, regardless of it being a account or institution view. auto institution = file->institution(d->m_currentInstitution.id()); // bankSuccess is not checked anymore because d->m_file->institution will throw anyway QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { file->modifyInstitution(dlg->institution()); ft.commit(); emit selectByObject(dlg->institution(), eView::Intent::None); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store institution: %1", QString::fromLatin1(e.what()))); } } delete dlg; } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to edit institution: %1", QString::fromLatin1(e.what()))); } } void KInstitutionsView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch(intent) { case eView::Intent::UpdateActions: updateActions(obj); break; case eView::Intent::OpenContextMenu: slotShowInstitutionsMenu(static_cast(obj)); break; default: break; } } void KInstitutionsView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch (intent) { case eView::Intent::UpdateNetWorth: if (variant.count() == 1) slotNetWorthChanged(variant.first().value()); break; default: break; } } void KInstitutionsView::slotDeleteInstitution() { Q_D(KInstitutionsView); const auto file = MyMoneyFile::instance(); try { auto institution = file->institution(d->m_currentInstitution.id()); if ((KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the institution %1?

", institution.name()))) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { file->removeInstitution(institution); emit selectByObject(MyMoneyInstitution(), eView::Intent::None); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", QString::fromLatin1(e.what()))); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", QString::fromLatin1(e.what()))); } }