diff --git a/kmymoney/dialogs/kmymoneypricedlg.cpp b/kmymoney/dialogs/kmymoneypricedlg.cpp index 44eac7f86..75d7230e0 100644 --- a/kmymoney/dialogs/kmymoneypricedlg.cpp +++ b/kmymoney/dialogs/kmymoneypricedlg.cpp @@ -1,397 +1,397 @@ /* * Copyright 2004-2017 Thomas Baumgart * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneypricedlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kmymoneypricedlg.h" #include "ui_kupdatestockpricedlg.h" #include "kupdatestockpricedlg.h" #include "kcurrencycalculator.h" #include "mymoneyprice.h" #include "kequitypriceupdatedlg.h" #include "kmymoneycurrencyselector.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "mymoneyexception.h" #include "kmymoneyutils.h" #include "kpricetreeitem.h" #include "icons/icons.h" using namespace Icons; // duplicated eMenu namespace from menuenums.h for consistency // there shouldn't be any clash, because we don't need menuenums.h here namespace eMenu { enum class Action { // ************* // The price menu // ************* NewPrice, DeletePrice, UpdatePrice, EditPrice }; inline uint qHash(const Action key, uint seed) { return ::qHash(static_cast(key), seed); } } class KMyMoneyPriceDlgPrivate { Q_DISABLE_COPY(KMyMoneyPriceDlgPrivate) Q_DECLARE_PUBLIC(KMyMoneyPriceDlg) public: explicit KMyMoneyPriceDlgPrivate(KMyMoneyPriceDlg *qq) : q_ptr(qq), ui(new Ui::KMyMoneyPriceDlg), m_currentItem(nullptr), m_searchWidget(nullptr) { } ~KMyMoneyPriceDlgPrivate() { delete ui; } int editPrice() { Q_Q(KMyMoneyPriceDlg); int rc = QDialog::Rejected; auto item = ui->m_priceList->currentItem(); if (item) { MyMoneySecurity from(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().from())); MyMoneySecurity to(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().to())); signed64 fract = MyMoneyMoney::precToDenom(from.pricePrecision()); QPointer calc = new KCurrencyCalculator(from, to, MyMoneyMoney::ONE, item->data(0, Qt::UserRole).value().rate(to.id()), item->data(0, Qt::UserRole).value().date(), fract, q); calc->setupPriceEditor(); rc = calc->exec(); delete calc; } return rc; } KMyMoneyPriceDlg *q_ptr; Ui::KMyMoneyPriceDlg *ui; QTreeWidgetItem* m_currentItem; /** * Search widget for the list */ KTreeWidgetSearchLineWidget* m_searchWidget; QMap m_stockNameMap; }; KMyMoneyPriceDlg::KMyMoneyPriceDlg(QWidget* parent) : QDialog(parent), d_ptr(new KMyMoneyPriceDlgPrivate(this)) { Q_D(KMyMoneyPriceDlg); d->ui->setupUi(this); // create the searchline widget // and insert it into the existing layout d->m_searchWidget = new KTreeWidgetSearchLineWidget(this, d->ui->m_priceList); d->m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); d->ui->m_listLayout->insertWidget(0, d->m_searchWidget); d->ui->m_priceList->header()->setSortIndicator(0, Qt::AscendingOrder); d->ui->m_priceList->header()->setStretchLastSection(true); d->ui->m_priceList->setContextMenuPolicy(Qt::CustomContextMenu); d->ui->m_deleteButton->setIcon(Icons::get(Icon::EditDelete)); d->ui->m_newButton->setIcon(Icons::get(Icon::DocumentNew)); d->ui->m_editButton->setIcon(Icons::get(Icon::DocumentEdit)); - d->ui->m_onlineQuoteButton->setIcon(Icons::get(Icon::ToolUpdatePrices)); + d->ui->m_onlineQuoteButton->setIcon(Icons::get(Icon::InvestmentOnlinePriceAll)); connect(d->ui->m_editButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotEditPrice); connect(d->ui->m_deleteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotDeletePrice); connect(d->ui->m_newButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotNewPrice); connect(d->ui->m_priceList, &QTreeWidget::itemSelectionChanged, this, &KMyMoneyPriceDlg::slotSelectPrice); connect(d->ui->m_onlineQuoteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotOnlinePriceUpdate); connect(d->ui->m_priceList, &QWidget::customContextMenuRequested, this, &KMyMoneyPriceDlg::slotShowPriceMenu); connect(d->ui->m_showAllPrices, &QAbstractButton::toggled, this, &KMyMoneyPriceDlg::slotLoadWidgets); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneyPriceDlg::slotLoadWidgets); slotLoadWidgets(); slotSelectPrice(); } KMyMoneyPriceDlg::~KMyMoneyPriceDlg() { Q_D(KMyMoneyPriceDlg); delete d; } void KMyMoneyPriceDlg::slotLoadWidgets() { Q_D(KMyMoneyPriceDlg); auto file = MyMoneyFile::instance(); //clear the list and disable the sorting while it loads the widgets, for performance d->ui->m_priceList->setSortingEnabled(false); d->ui->m_priceList->clear(); d->m_stockNameMap.clear(); //load the currencies for investments, which we'll need later QList accList; file->accountList(accList); QList::const_iterator acc_it; for (acc_it = accList.constBegin(); acc_it != accList.constEnd(); ++acc_it) { if ((*acc_it).isInvest()) { if (d->m_stockNameMap.contains((*acc_it).currencyId())) { d->m_stockNameMap[(*acc_it).currencyId()] = QString(d->m_stockNameMap.value((*acc_it).currencyId()) + ", " + (*acc_it).name()); } else { d->m_stockNameMap[(*acc_it).currencyId()] = (*acc_it).name(); } } } //get the price list MyMoneyPriceList list = file->priceList(); MyMoneyPriceList::ConstIterator it_allPrices; for (it_allPrices = list.constBegin(); it_allPrices != list.constEnd(); ++it_allPrices) { MyMoneyPriceEntries::ConstIterator it_priceItem; if (d->ui->m_showAllPrices->isChecked()) { for (it_priceItem = (*it_allPrices).constBegin(); it_priceItem != (*it_allPrices).constEnd(); ++it_priceItem) { loadPriceItem(*it_priceItem); } } else { //if it doesn't show all prices, it only shows the most recent occurrence for each price if ((*it_allPrices).count() > 0) { //the prices for each currency are ordered by date in ascending order //it gets the last item of the item, which is supposed to be the most recent price it_priceItem = (*it_allPrices).constEnd(); --it_priceItem; loadPriceItem(*it_priceItem); } } } //reenable sorting and sort by the commodity column d->ui->m_priceList->setSortingEnabled(true); d->ui->m_priceList->sortByColumn(KPriceTreeItem::ePriceCommodity); //update the search widget so the list gets refreshed correctly if it was being filtered if (!d->m_searchWidget->searchLine()->text().isEmpty()) d->m_searchWidget->searchLine()->updateSearch(d->m_searchWidget->searchLine()->text()); } QTreeWidgetItem* KMyMoneyPriceDlg::loadPriceItem(const MyMoneyPrice& basePrice) { Q_D(KMyMoneyPriceDlg); MyMoneySecurity from, to; auto price = MyMoneyPrice(basePrice); auto priceTreeItem = new KPriceTreeItem(d->ui->m_priceList); if (!price.isValid()) price = MyMoneyFile::instance()->price(price.from(), price.to(), price.date()); if (price.isValid()) { QString priceBase = price.to(); from = MyMoneyFile::instance()->security(price.from()); to = MyMoneyFile::instance()->security(price.to()); if (!to.isCurrency()) { from = MyMoneyFile::instance()->security(price.to()); to = MyMoneyFile::instance()->security(price.from()); priceBase = price.from(); } priceTreeItem->setData(KPriceTreeItem::ePriceCommodity, Qt::UserRole, QVariant::fromValue(price)); priceTreeItem->setText(KPriceTreeItem::ePriceCommodity, (from.isCurrency()) ? from.id() : from.tradingSymbol()); priceTreeItem->setText(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id())); priceTreeItem->setToolTip(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id())); priceTreeItem->setText(KPriceTreeItem::ePriceCurrency, to.id()); priceTreeItem->setText(KPriceTreeItem::ePriceDate, QLocale().toString(price.date(), QLocale::ShortFormat)); priceTreeItem->setData(KPriceTreeItem::ePriceDate, KPriceTreeItem::OrderRole, QVariant(price.date())); priceTreeItem->setText(KPriceTreeItem::ePricePrice, price.rate(priceBase).formatMoney("", from.pricePrecision())); priceTreeItem->setTextAlignment(KPriceTreeItem::ePricePrice, Qt::AlignRight | Qt::AlignVCenter); priceTreeItem->setData(KPriceTreeItem::ePricePrice, KPriceTreeItem::OrderRole, QVariant::fromValue(price.rate(priceBase))); priceTreeItem->setText(KPriceTreeItem::ePriceSource, price.source()); } return priceTreeItem; } void KMyMoneyPriceDlg::slotSelectPrice() { Q_D(KMyMoneyPriceDlg); QTreeWidgetItem* item = 0; if (d->ui->m_priceList->selectedItems().count() > 0) { item = d->ui->m_priceList->selectedItems().at(0); } d->m_currentItem = item; d->ui->m_editButton->setEnabled(item != 0); bool deleteEnabled = (item != 0); //if one of the selected entries is a default, then deleting is disabled QList itemsList = d->ui->m_priceList->selectedItems(); QList::const_iterator item_it; for (item_it = itemsList.constBegin(); item_it != itemsList.constEnd(); ++item_it) { MyMoneyPrice price = (*item_it)->data(0, Qt::UserRole).value(); if (price.source() == "KMyMoney") deleteEnabled = false; } d->ui->m_deleteButton->setEnabled(deleteEnabled); // Modification of automatically added entries is not allowed // Multiple entries cannot be edited at once if (item) { MyMoneyPrice price = item->data(0, Qt::UserRole).value(); if (price.source() == "KMyMoney" || itemsList.count() > 1) d->ui->m_editButton->setEnabled(false); // emit selectObject(price); } } void KMyMoneyPriceDlg::slotNewPrice() { Q_D(KMyMoneyPriceDlg); QPointer dlg = new KUpdateStockPriceDlg(this); try { auto item = d->ui->m_priceList->currentItem(); if (item) { MyMoneySecurity security; security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().from()); dlg->ui->m_security->setSecurity(security); security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().to()); dlg->ui->m_currency->setSecurity(security); } if (dlg->exec()) { MyMoneyPrice price(dlg->ui->m_security->security().id(), dlg->ui->m_currency->security().id(), dlg->date(), MyMoneyMoney::ONE, QString()); QTreeWidgetItem* p = loadPriceItem(price); d->ui->m_priceList->setCurrentItem(p, true); // If the user cancels the following operation, we delete the new item // and re-select any previously selected one if (d->editPrice() == Rejected) { delete p; if (item) d->ui->m_priceList->setCurrentItem(item, true); } } } catch (...) { delete dlg; throw; } delete dlg; } void KMyMoneyPriceDlg::slotEditPrice() { Q_D(KMyMoneyPriceDlg); d->editPrice(); } void KMyMoneyPriceDlg::slotDeletePrice() { Q_D(KMyMoneyPriceDlg); QList listItems = d->ui->m_priceList->selectedItems(); if (listItems.count() > 0) { if (KMessageBox::questionYesNo(this, i18np("Do you really want to delete the selected price entry?", "Do you really want to delete the selected price entries?", listItems.count()), i18n("Delete price information"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeletePrice") == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { QList::const_iterator price_it; for (price_it = listItems.constBegin(); price_it != listItems.constEnd(); ++price_it) { MyMoneyFile::instance()->removePrice((*price_it)->data(0, Qt::UserRole).value()); } ft.commit(); } catch (const MyMoneyException &) { qDebug("Cannot delete price"); } } } } void KMyMoneyPriceDlg::slotOnlinePriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == Accepted && dlg) dlg->storePrices(); delete dlg; } void KMyMoneyPriceDlg::slotShowPriceMenu(const QPoint& p) { Q_D(KMyMoneyPriceDlg); auto item = d->ui->m_priceList->itemAt(p); if (item) { d->ui->m_priceList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); const auto price = item->data(0, Qt::UserRole).value(); const auto cond1 = !price.from().isEmpty() && price.source() != QLatin1String("KMyMoney"); const auto cond2 = cond1 && MyMoneyFile::instance()->security(price.from()).isCurrency(); auto menu = new QMenu; typedef void(KMyMoneyPriceDlg::*KMyMoneyPriceDlgFunc)(); struct actionInfo { eMenu::Action action; KMyMoneyPriceDlgFunc callback; QString text; Icon icon; bool enabled; }; const QVector actionInfos { {eMenu::Action::NewPrice, &KMyMoneyPriceDlg::slotNewPrice, i18n("New price..."), Icon::DocumentNew, true}, {eMenu::Action::EditPrice, &KMyMoneyPriceDlg::slotEditPrice, i18n("Edit price..."), Icon::DocumentEdit, cond1}, {eMenu::Action::UpdatePrice, &KMyMoneyPriceDlg::slotOnlinePriceUpdate, i18n("Online Price Update..."), Icon::PriceUpdate, cond2}, {eMenu::Action::DeletePrice, &KMyMoneyPriceDlg::slotDeletePrice, i18n("Delete price..."), Icon::EditDelete, cond1} }; QList LUTActions; for (const auto& info : actionInfos) { auto a = new QAction(Icons::get(info.icon), info.text, nullptr); // WARNING: no empty Icon::Empty here a->setEnabled(info.enabled); connect(a, &QAction::triggered, this, info.callback); LUTActions.append(a); } menu->addSection(i18nc("Menu header","Price options")); menu->addActions(LUTActions); menu->exec(QCursor::pos()); } } diff --git a/kmymoney/dialogs/kmymoneysplittable.cpp b/kmymoney/dialogs/kmymoneysplittable.cpp index 83ae370e1..ccd8586b4 100644 --- a/kmymoney/dialogs/kmymoneysplittable.cpp +++ b/kmymoney/dialogs/kmymoneysplittable.cpp @@ -1,1137 +1,1137 @@ /* * Copyright 2008-2018 Thomas Baumgart * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneysplittable.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "mymoneyprice.h" #include "amountedit.h" #include "kmymoneycategory.h" #include "kmymoneyaccountselector.h" #include "kmymoneylineedit.h" #include "mymoneysecurity.h" #include "kmymoneysettings.h" #include "kmymoneymvccombo.h" #include "mymoneytag.h" #include "kmymoneytagcombo.h" #include "ktagcontainer.h" #include "kcurrencycalculator.h" #include "mymoneyutils.h" #include "mymoneytracer.h" #include "mymoneyexception.h" #include "icons.h" #include "mymoneyenums.h" using namespace Icons; class KMyMoneySplitTablePrivate { Q_DISABLE_COPY(KMyMoneySplitTablePrivate) public: KMyMoneySplitTablePrivate() : m_currentRow(0), m_maxRows(0), m_precision(2), m_contextMenu(nullptr), m_contextMenuDelete(nullptr), m_contextMenuDuplicate(nullptr), m_editCategory(0), m_editTag(0), m_editMemo(0), m_editAmount(0) { } ~KMyMoneySplitTablePrivate() { } /// the currently selected row (will be printed as selected) int m_currentRow; /// the number of rows filled with data int m_maxRows; MyMoneyTransaction m_transaction; MyMoneyAccount m_account; MyMoneySplit m_split; MyMoneySplit m_hiddenSplit; /** * This member keeps the precision for the values */ int m_precision; /** * This member keeps a pointer to the context menu */ QMenu* m_contextMenu; /// keeps the QAction of the delete entry in the context menu QAction* m_contextMenuDelete; /// keeps the QAction of the duplicate entry in the context menu QAction* m_contextMenuDuplicate; /** * This member contains a pointer to the input widget for the category. * The widget will be created and destroyed dynamically in createInputWidgets() * and destroyInputWidgets(). */ QPointer m_editCategory; /** * This member contains a pointer to the tag widget for the memo. */ QPointer m_editTag; /** * This member contains a pointer to the input widget for the memo. * The widget will be created and destroyed dynamically in createInputWidgets() * and destroyInputWidgets(). */ QPointer m_editMemo; /** * This member contains a pointer to the input widget for the amount. * The widget will be created and destroyed dynamically in createInputWidgets() * and destroyInputWidgets(). */ QPointer m_editAmount; /** * This member keeps the tab order for the above widgets */ QWidgetList m_tabOrderWidgets; QPointer m_registerButtonFrame; QPointer m_registerEnterButton; QPointer m_registerCancelButton; QMap m_priceInfo; }; KMyMoneySplitTable::KMyMoneySplitTable(QWidget *parent) : QTableWidget(parent), d_ptr(new KMyMoneySplitTablePrivate) { Q_D(KMyMoneySplitTable); // used for custom coloring with the help of the application's stylesheet setObjectName(QLatin1String("splittable")); // setup the transactions table setRowCount(1); setColumnCount(4); QStringList labels; labels << i18n("Category") << i18n("Memo") << i18n("Tag") << i18n("Amount"); setHorizontalHeaderLabels(labels); setSelectionMode(QAbstractItemView::SingleSelection); setSelectionBehavior(QAbstractItemView::SelectRows); int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); setContentsMargins(0, top, right, bottom); setFont(KMyMoneySettings::listCellFontEx()); setAlternatingRowColors(true); verticalHeader()->hide(); horizontalHeader()->setSectionsMovable(false); horizontalHeader()->setFont(KMyMoneySettings::listHeaderFontEx()); KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTable"); QByteArray columns; columns = grp.readEntry("HeaderState", columns); horizontalHeader()->restoreState(columns); horizontalHeader()->setStretchLastSection(true); setShowGrid(KMyMoneySettings::showGrid()); setEditTriggers(QAbstractItemView::NoEditTriggers); // setup the context menu d->m_contextMenu = new QMenu(this); d->m_contextMenu->setTitle(i18n("Split Options")); - d->m_contextMenu->setIcon(Icons::get(Icon::ViewFinancialTransfer)); + d->m_contextMenu->setIcon(Icons::get(Icon::Transaction)); d->m_contextMenu->addAction(Icons::get(Icon::DocumentEdit), i18n("Edit..."), this, SLOT(slotStartEdit())); d->m_contextMenuDuplicate = d->m_contextMenu->addAction(Icons::get(Icon::EditCopy), i18nc("To duplicate a split", "Duplicate"), this, SLOT(slotDuplicateSplit())); d->m_contextMenuDelete = d->m_contextMenu->addAction(Icons::get(Icon::EditDelete), i18n("Delete..."), this, SLOT(slotDeleteSplit())); connect(this, &QAbstractItemView::clicked, this, static_cast(&KMyMoneySplitTable::slotSetFocus)); connect(this, &KMyMoneySplitTable::transactionChanged, this, &KMyMoneySplitTable::slotUpdateData); installEventFilter(this); } KMyMoneySplitTable::~KMyMoneySplitTable() { Q_D(KMyMoneySplitTable); auto grp = KSharedConfig::openConfig()->group("SplitTable"); QByteArray columns = horizontalHeader()->saveState(); grp.writeEntry("HeaderState", columns); grp.sync(); delete d; } int KMyMoneySplitTable::currentRow() const { Q_D(const KMyMoneySplitTable); return d->m_currentRow; } void KMyMoneySplitTable::setup(const QMap& priceInfo, int precision) { Q_D(KMyMoneySplitTable); d->m_priceInfo = priceInfo; d->m_precision = precision; } bool KMyMoneySplitTable::eventFilter(QObject *o, QEvent *e) { Q_D(KMyMoneySplitTable); // MYMONEYTRACER(tracer); QKeyEvent *k = static_cast(e); bool rc = false; int row = currentRow(); int lines = viewport()->height() / rowHeight(0); if (e->type() == QEvent::KeyPress && !isEditMode()) { rc = true; switch (k->key()) { case Qt::Key_Up: if (row) slotSetFocus(model()->index(row - 1, 0)); break; case Qt::Key_Down: if (row < d->m_transaction.splits().count() - 1) slotSetFocus(model()->index(row + 1, 0)); break; case Qt::Key_Home: slotSetFocus(model()->index(0, 0)); break; case Qt::Key_End: slotSetFocus(model()->index(d->m_transaction.splits().count() - 1, 0)); break; case Qt::Key_PageUp: if (lines) { while (lines-- > 0 && row) --row; slotSetFocus(model()->index(row, 0)); } break; case Qt::Key_PageDown: if (row < d->m_transaction.splits().count() - 1) { while (lines-- > 0 && row < d->m_transaction.splits().count() - 1) ++row; slotSetFocus(model()->index(row, 0)); } break; case Qt::Key_Delete: slotDeleteSplit(); break; case Qt::Key_Return: case Qt::Key_Enter: if (row < d->m_transaction.splits().count() - 1 && KMyMoneySettings::enterMovesBetweenFields()) { slotStartEdit(); } else emit returnPressed(); break; case Qt::Key_Escape: emit escapePressed(); break; case Qt::Key_F2: slotStartEdit(); break; default: rc = true; // duplicate split if (Qt::Key_C == k->key() && Qt::ControlModifier == k->modifiers()) { slotDuplicateSplit(); // new split } else if (Qt::Key_Insert == k->key() && Qt::ControlModifier == k->modifiers()) { slotSetFocus(model()->index(d->m_transaction.splits().count() - 1, 0)); slotStartEdit(); } else if (k->text()[ 0 ].isPrint()) { KMyMoneyCategory* cat = createEditWidgets(false); if (cat) { KMyMoneyLineEdit *le = qobject_cast(cat->lineEdit()); if (le) { // make sure, the widget receives the key again // and does not select the text this time le->setText(k->text()); le->end(false); le->deselect(); le->skipSelectAll(true); le->setFocus(); } } } break; } } else if (e->type() == QEvent::KeyPress && isEditMode()) { bool terminate = true; rc = true; switch (k->key()) { // suppress the F2 functionality to start editing in inline edit mode case Qt::Key_F2: // suppress the cursor movement in inline edit mode case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_PageUp: case Qt::Key_PageDown: break; case Qt::Key_Return: case Qt::Key_Enter: // we cannot call the slot directly, as it destroys the caller of // this method :-( So we let the event handler take care of calling // the respective slot using a timeout. For a KLineEdit derived object // it could be, that at this point the user selected a value from // a completion list. In this case, we close the completion list and // do not end editing of the transaction. if (o->inherits("KLineEdit")) { if (auto le = dynamic_cast(o)) { KCompletionBox* box = le->completionBox(false); if (box && box->isVisible()) { terminate = false; le->completionBox(false)->hide(); } } } // in case we have the 'enter moves focus between fields', we need to simulate // a TAB key when the object 'o' points to the category or memo field. if (KMyMoneySettings::enterMovesBetweenFields()) { if (o == d->m_editCategory->lineEdit() || o == d->m_editMemo || o == d->m_editTag) { terminate = false; QKeyEvent evt(e->type(), Qt::Key_Tab, k->modifiers(), QString(), k->isAutoRepeat(), k->count()); QApplication::sendEvent(o, &evt); } } if (terminate) { QTimer::singleShot(0, this, SLOT(slotEndEditKeyboard())); } break; case Qt::Key_Escape: // we cannot call the slot directly, as it destroys the caller of // this method :-( So we let the event handler take care of calling // the respective slot using a timeout. QTimer::singleShot(0, this, SLOT(slotCancelEdit())); break; default: rc = false; break; } } else if (e->type() == QEvent::KeyRelease && !isEditMode()) { // for some reason, we only see a KeyRelease event of the Menu key // here. In other locations (e.g. Register::eventFilter()) we see // a KeyPress event. Strange. (ipwizard - 2008-05-10) switch (k->key()) { case Qt::Key_Menu: // if the very last entry is selected, the delete // operation is not available otherwise it is d->m_contextMenuDelete->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenuDuplicate->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenu->exec(QCursor::pos()); rc = true; break; default: break; } } // if the event has not been processed here, forward it to // the base class implementation if it's not a key event if (rc == false) { if (e->type() != QEvent::KeyPress && e->type() != QEvent::KeyRelease) { rc = QTableWidget::eventFilter(o, e); } } return rc; } void KMyMoneySplitTable::slotSetFocus(const QModelIndex& index) { slotSetFocus(index, Qt::LeftButton); } void KMyMoneySplitTable::slotSetFocus(const QModelIndex& index, int button) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); auto row = index.row(); // adjust row to used area if (row > d->m_transaction.splits().count() - 1) row = d->m_transaction.splits().count() - 1; if (row < 0) row = 0; // make sure the row will be on the screen scrollTo(model()->index(row, 0)); if (isEditMode()) { // in edit mode? if (isEditSplitValid() && KMyMoneySettings::focusChangeIsEnter()) endEdit(false/*keyboard driven*/, false/*set focus to next row*/); else slotCancelEdit(); } if (button == Qt::LeftButton) { // left mouse button if (row != currentRow()) { // setup new current row and update visible selection selectRow(row); slotUpdateData(d->m_transaction); } } else if (button == Qt::RightButton) { // context menu is only available when cursor is on // an existing transaction or the first line after this area if (row == index.row()) { // setup new current row and update visible selection selectRow(row); slotUpdateData(d->m_transaction); // if the very last entry is selected, the delete // operation is not available otherwise it is d->m_contextMenuDelete->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenuDuplicate->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenu->exec(QCursor::pos()); } } } void KMyMoneySplitTable::mousePressEvent(QMouseEvent* e) { slotSetFocus(indexAt(e->pos()), e->button()); } /* turn off QTable behaviour */ void KMyMoneySplitTable::mouseReleaseEvent(QMouseEvent* /* e */) { } void KMyMoneySplitTable::mouseDoubleClickEvent(QMouseEvent *e) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); int col = columnAt(e->pos().x()); slotSetFocus(model()->index(rowAt(e->pos().y()), col), e->button()); createEditWidgets(false); QLineEdit* editWidget = 0; //krazy:exclude=qmethods switch (col) { case 0: editWidget = d->m_editCategory->lineEdit(); break; case 1: editWidget = d->m_editMemo; break; case 2: d->m_editTag->tagCombo()->setFocus(); break; case 3: editWidget = d->m_editAmount; break; default: break; } if (editWidget) { editWidget->setFocus(); editWidget->selectAll(); } } void KMyMoneySplitTable::selectRow(int row) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); if (row > d->m_maxRows) row = d->m_maxRows; d->m_currentRow = row; QTableWidget::selectRow(row); QList list = getSplits(d->m_transaction); if (row < list.count()) d->m_split = list[row]; else d->m_split = MyMoneySplit(); } void KMyMoneySplitTable::setRowCount(int irows) { QTableWidget::setRowCount(irows); // determine row height according to the edit widgets // we use the category widget as the base QFontMetrics fm(KMyMoneySettings::listCellFontEx()); int height = fm.lineSpacing() + 6; #if 0 // recalculate row height hint KMyMoneyCategory cat; height = qMax(cat.sizeHint().height(), height); #endif verticalHeader()->setUpdatesEnabled(false); for (auto i = 0; i < irows; ++i) verticalHeader()->resizeSection(i, height); verticalHeader()->setUpdatesEnabled(true); } void KMyMoneySplitTable::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s, const MyMoneyAccount& acc) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); d->m_transaction = t; d->m_account = acc; d->m_hiddenSplit = s; selectRow(0); slotUpdateData(d->m_transaction); } MyMoneyTransaction KMyMoneySplitTable::transaction() const { Q_D(const KMyMoneySplitTable); return d->m_transaction; } QList KMyMoneySplitTable::getSplits(const MyMoneyTransaction& t) const { Q_D(const KMyMoneySplitTable); // get list of splits QList list = t.splits(); // and ignore the one that should be hidden QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { if ((*it).id() == d->m_hiddenSplit.id()) { list.erase(it); break; } } return list; } void KMyMoneySplitTable::slotUpdateData(const MyMoneyTransaction& t) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); unsigned long numRows = 0; QTableWidgetItem* textItem; QList list = getSplits(t); updateTransactionTableSize(); // fill the part that is used by transactions QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { QString colText; MyMoneyMoney value = (*it).value(); if (!(*it).accountId().isEmpty()) { try { colText = MyMoneyFile::instance()->accountToCategory((*it).accountId()); } catch (const MyMoneyException &) { qDebug("Unexpected exception in KMyMoneySplitTable::slotUpdateData()"); } } QString amountTxt = value.formatMoney(d->m_account.fraction()); if (value == MyMoneyMoney::autoCalc) { amountTxt = i18n("will be calculated"); } if (colText.isEmpty() && (*it).memo().isEmpty() && value.isZero()) amountTxt.clear(); unsigned width = fontMetrics().width(amountTxt); AmountEdit* valfield = new AmountEdit(); valfield->setMinimumWidth(width); width = valfield->minimumSizeHint().width(); delete valfield; textItem = item(numRows, 0); if (textItem) textItem->setText(colText); else setItem(numRows, 0, new QTableWidgetItem(colText)); textItem = item(numRows, 1); if (textItem) textItem->setText((*it).memo()); else setItem(numRows, 1, new QTableWidgetItem((*it).memo())); QList tl = (*it).tagIdList(); QStringList tagNames; if (!tl.isEmpty()) { for (int i = 0; i < tl.size(); i++) tagNames.append(MyMoneyFile::instance()->tag(tl[i]).name()); } setItem(numRows, 2, new QTableWidgetItem(tagNames.join(", "))); textItem = item(numRows, 3); if (textItem) textItem->setText(amountTxt); else setItem(numRows, 3, new QTableWidgetItem(amountTxt)); item(numRows, 3)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); ++numRows; } // now clean out the remainder of the table while (numRows < static_cast(rowCount())) { for (auto i = 0 ; i < 4; ++i) { textItem = item(numRows, i); if (textItem) textItem->setText(""); else setItem(numRows, i, new QTableWidgetItem("")); } item(numRows, 3)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); ++numRows; } } void KMyMoneySplitTable::updateTransactionTableSize() { Q_D(KMyMoneySplitTable); // get current size of transactions table int tableHeight = height(); int splitCount = d->m_transaction.splits().count() - 1; if (splitCount < 0) splitCount = 0; // see if we need some extra lines to fill the current size with the grid int numExtraLines = (tableHeight / rowHeight(0)) - splitCount; if (numExtraLines < 2) numExtraLines = 2; setRowCount(splitCount + numExtraLines); d->m_maxRows = splitCount; } void KMyMoneySplitTable::resizeEvent(QResizeEvent* ev) { QTableWidget::resizeEvent(ev); if (!isEditMode()) { // update the size of the transaction table only if a split is not being edited // otherwise the height of the editors would be altered in an undesired way updateTransactionTableSize(); } } void KMyMoneySplitTable::slotDuplicateSplit() { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); QList list = getSplits(d->m_transaction); if (d->m_currentRow < list.count()) { MyMoneySplit split = list[d->m_currentRow]; split.clearId(); try { d->m_transaction.addSplit(split); emit transactionChanged(d->m_transaction); } catch (const MyMoneyException &e) { qDebug("Cannot duplicate split: %s", e.what()); } } } void KMyMoneySplitTable::slotDeleteSplit() { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); QList list = getSplits(d->m_transaction); if (d->m_currentRow < list.count()) { if (KMessageBox::warningContinueCancel(this, i18n("You are about to delete the selected split. " "Do you really want to continue?"), i18n("KMyMoney") ) == KMessageBox::Continue) { try { d->m_transaction.removeSplit(list[d->m_currentRow]); // if we removed the last split, select the previous if (d->m_currentRow && d->m_currentRow == list.count() - 1) selectRow(d->m_currentRow - 1); else selectRow(d->m_currentRow); emit transactionChanged(d->m_transaction); } catch (const MyMoneyException &e) { qDebug("Cannot remove split: %s", e.what()); } } } } KMyMoneyCategory* KMyMoneySplitTable::slotStartEdit() { MYMONEYTRACER(tracer); return createEditWidgets(true); } void KMyMoneySplitTable::slotEndEdit() { endEdit(false); } void KMyMoneySplitTable::slotEndEditKeyboard() { endEdit(true); } void KMyMoneySplitTable::endEdit(bool keyboardDriven, bool setFocusToNextRow) { Q_D(KMyMoneySplitTable); auto file = MyMoneyFile::instance(); MYMONEYTRACER(tracer); MyMoneySplit s1 = d->m_split; if (!isEditSplitValid()) { KMessageBox::information(this, i18n("You need to assign a category to this split before it can be entered."), i18n("Enter split"), "EnterSplitWithEmptyCategory"); d->m_editCategory->setFocus(); return; } bool needUpdate = false; if (d->m_editCategory->selectedItem() != d->m_split.accountId()) { s1.setAccountId(d->m_editCategory->selectedItem()); needUpdate = true; } if (d->m_editMemo->text() != d->m_split.memo()) { s1.setMemo(d->m_editMemo->text()); needUpdate = true; } if (d->m_editTag->selectedTags() != d->m_split.tagIdList()) { s1.setTagIdList(d->m_editTag->selectedTags()); needUpdate = true; } if (d->m_editAmount->value() != d->m_split.value()) { s1.setValue(d->m_editAmount->value()); needUpdate = true; } if (needUpdate) { if (!s1.value().isZero()) { MyMoneyAccount cat = file->account(s1.accountId()); if (cat.currencyId() != d->m_transaction.commodity()) { MyMoneySecurity fromCurrency, toCurrency; MyMoneyMoney fromValue, toValue; fromCurrency = file->security(d->m_transaction.commodity()); toCurrency = file->security(cat.currencyId()); // determine the fraction required for this category int fract = toCurrency.smallestAccountFraction(); if (cat.accountType() == eMyMoney::Account::Type::Cash) fract = toCurrency.smallestCashFraction(); // display only positive values to the user fromValue = s1.value().abs(); // if we had a price info in the beginning, we use it here if (d->m_priceInfo.find(cat.currencyId()) != d->m_priceInfo.end()) { toValue = (fromValue * d->m_priceInfo[cat.currencyId()]).convert(fract); } // if the shares are still 0, we need to change that if (toValue.isZero()) { const MyMoneyPrice &price = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id()); // if the price is valid calculate the shares. If it is invalid // assume a conversion rate of 1.0 if (price.isValid()) { toValue = (price.rate(toCurrency.id()) * fromValue).convert(fract); } else { toValue = fromValue; } } // now present all that to the user QPointer calc = new KCurrencyCalculator(fromCurrency, toCurrency, fromValue, toValue, d->m_transaction.postDate(), fract, this); if (calc->exec() == QDialog::Rejected) { delete calc; return; } else { s1.setShares((s1.value() * calc->price()).convert(fract)); delete calc; } } else { s1.setShares(s1.value()); } } else s1.setShares(s1.value()); d->m_split = s1; try { if (d->m_split.id().isEmpty()) { d->m_transaction.addSplit(d->m_split); } else { d->m_transaction.modifySplit(d->m_split); } emit transactionChanged(d->m_transaction); } catch (const MyMoneyException &e) { qDebug("Cannot add/modify split: %s", e.what()); } } this->setFocus(); destroyEditWidgets(); if (setFocusToNextRow) { slotSetFocus(model()->index(currentRow() + 1, 0)); } // if we still have more splits, we start editing right away // in case we have selected 'enter moves between fields' if (keyboardDriven && currentRow() < d->m_transaction.splits().count() - 1 && KMyMoneySettings::enterMovesBetweenFields()) { slotStartEdit(); } } void KMyMoneySplitTable::slotCancelEdit() { Q_D(const KMyMoneySplitTable); MYMONEYTRACER(tracer); if (isEditMode()) { /* * Prevent asking to add a new category which happens if the user entered any text * caused by emitting signals in KMyMoneyCombo::focusOutEvent() on focus out event. * (see bug 344409) */ if (d->m_editCategory) d->m_editCategory->lineEdit()->setText(QString()); destroyEditWidgets(); this->setFocus(); } } bool KMyMoneySplitTable::isEditMode() const { Q_D(const KMyMoneySplitTable); // while the edit widgets exist we're in edit mode return d->m_editAmount || d->m_editMemo || d->m_editCategory || d->m_editTag; } bool KMyMoneySplitTable::isEditSplitValid() const { Q_D(const KMyMoneySplitTable); return isEditMode() && !(d->m_editCategory && d->m_editCategory->selectedItem().isEmpty()); } void KMyMoneySplitTable::destroyEditWidgets() { MYMONEYTRACER(tracer); Q_D(KMyMoneySplitTable); emit editFinished(); disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneySplitTable::slotLoadEditWidgets); destroyEditWidget(d->m_currentRow, 0); destroyEditWidget(d->m_currentRow, 1); destroyEditWidget(d->m_currentRow, 2); destroyEditWidget(d->m_currentRow, 3); destroyEditWidget(d->m_currentRow + 1, 0); } void KMyMoneySplitTable::destroyEditWidget(int r, int c) { if (QWidget* cw = cellWidget(r, c)) cw->hide(); removeCellWidget(r, c); } KMyMoneyCategory* KMyMoneySplitTable::createEditWidgets(bool setFocus) { MYMONEYTRACER(tracer); emit editStarted(); Q_D(KMyMoneySplitTable); auto cellFont = KMyMoneySettings::listCellFontEx(); d->m_tabOrderWidgets.clear(); // create the widgets d->m_editAmount = new AmountEdit; d->m_editAmount->setFont(cellFont); d->m_editAmount->setCalculatorButtonVisible(true); d->m_editAmount->setPrecision(d->m_precision); d->m_editCategory = new KMyMoneyCategory(); d->m_editCategory->setPlaceholderText(i18n("Category")); d->m_editCategory->setFont(cellFont); connect(d->m_editCategory, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createCategory(QString,QString&))); connect(d->m_editCategory, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); d->m_editMemo = new KMyMoneyLineEdit(0, false, Qt::AlignLeft | Qt::AlignVCenter); d->m_editMemo->setPlaceholderText(i18n("Memo")); d->m_editMemo->setFont(cellFont); d->m_editTag = new KTagContainer; d->m_editTag->tagCombo()->setPlaceholderText(i18n("Tag")); d->m_editTag->tagCombo()->setFont(cellFont); d->m_editTag->loadTags(MyMoneyFile::instance()->tagList()); connect(d->m_editTag->tagCombo(), SIGNAL(createItem(QString,QString&)), this, SIGNAL(createTag(QString,QString&))); connect(d->m_editTag->tagCombo(), SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); // create buttons for the mouse users d->m_registerButtonFrame = new QFrame(this); d->m_registerButtonFrame->setContentsMargins(0, 0, 0, 0); d->m_registerButtonFrame->setAutoFillBackground(true); QHBoxLayout* l = new QHBoxLayout(d->m_registerButtonFrame); l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); d->m_registerEnterButton = new QPushButton(Icons::get(Icon::DialogOK) , QString(), d->m_registerButtonFrame); d->m_registerCancelButton = new QPushButton(Icons::get(Icon::DialogCancel) , QString(), d->m_registerButtonFrame); l->addWidget(d->m_registerEnterButton); l->addWidget(d->m_registerCancelButton); l->addStretch(2); connect(d->m_registerEnterButton.data(), &QAbstractButton::clicked, this, &KMyMoneySplitTable::slotEndEdit); connect(d->m_registerCancelButton.data(), &QAbstractButton::clicked, this, &KMyMoneySplitTable::slotCancelEdit); // setup tab order addToTabOrder(d->m_editCategory); addToTabOrder(d->m_editMemo); addToTabOrder(d->m_editTag); addToTabOrder(d->m_editAmount); addToTabOrder(d->m_registerEnterButton); addToTabOrder(d->m_registerCancelButton); if (!d->m_split.accountId().isEmpty()) { d->m_editCategory->setSelectedItem(d->m_split.accountId()); } else { // check if the transaction is balanced or not. If not, // assign the remainder to the amount. MyMoneyMoney diff; QList list = d->m_transaction.splits(); QList::ConstIterator it_s; for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if (!(*it_s).accountId().isEmpty()) diff += (*it_s).value(); } d->m_split.setValue(-diff); } QList t = d->m_split.tagIdList(); if (!t.isEmpty()) { for (int i = 0; i < t.size(); i++) d->m_editTag->addTagWidget(t[i]); } d->m_editMemo->loadText(d->m_split.memo()); // don't allow automatically calculated values to be modified if (d->m_split.value() == MyMoneyMoney::autoCalc) { d->m_editAmount->setEnabled(false); d->m_editAmount->setText("will be calculated"); } else d->m_editAmount->setValue(d->m_split.value()); setCellWidget(d->m_currentRow, 0, d->m_editCategory); setCellWidget(d->m_currentRow, 1, d->m_editMemo); setCellWidget(d->m_currentRow, 2, d->m_editTag); setCellWidget(d->m_currentRow, 3, d->m_editAmount); setCellWidget(d->m_currentRow + 1, 0, d->m_registerButtonFrame); // load e.g. the category widget with the account list slotLoadEditWidgets(); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneySplitTable::slotLoadEditWidgets); foreach (QWidget* w, d->m_tabOrderWidgets) { if (w) { w->installEventFilter(this); } } if (setFocus) { d->m_editCategory->lineEdit()->setFocus(); d->m_editCategory->lineEdit()->selectAll(); } // resize the rows so the added edit widgets would fit appropriately resizeRowsToContents(); return d->m_editCategory; } void KMyMoneySplitTable::slotLoadEditWidgets() { Q_D(KMyMoneySplitTable); // reload category widget auto categoryId = d->m_editCategory->selectedItem(); AccountSet aSet; aSet.addAccountGroup(eMyMoney::Account::Type::Asset); aSet.addAccountGroup(eMyMoney::Account::Type::Liability); aSet.addAccountGroup(eMyMoney::Account::Type::Income); aSet.addAccountGroup(eMyMoney::Account::Type::Expense); if (KMyMoneySettings::expertMode()) aSet.addAccountGroup(eMyMoney::Account::Type::Equity); // remove the accounts with invalid types at this point aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep); aSet.removeAccountType(eMyMoney::Account::Type::Investment); aSet.removeAccountType(eMyMoney::Account::Type::Stock); aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket); aSet.load(d->m_editCategory->selector()); // if an account is specified then remove it from the widget so that the user // cannot create a transfer with from and to account being the same account if (!d->m_account.id().isEmpty()) d->m_editCategory->selector()->removeItem(d->m_account.id()); if (!categoryId.isEmpty()) d->m_editCategory->setSelectedItem(categoryId); } void KMyMoneySplitTable::addToTabOrder(QWidget* w) { Q_D(KMyMoneySplitTable); if (w) { while (w->focusProxy()) w = w->focusProxy(); d->m_tabOrderWidgets.append(w); } } bool KMyMoneySplitTable::focusNextPrevChild(bool next) { MYMONEYTRACER(tracer); Q_D(KMyMoneySplitTable); auto rc = false; if (isEditMode()) { QWidget *w = 0; w = qApp->focusWidget(); int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", w->className()); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", w->className()); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' as focus", w->className()); w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason); rc = true; } } } else rc = QTableWidget::focusNextPrevChild(next); return rc; } diff --git a/kmymoney/dialogs/knewbankdlg.cpp b/kmymoney/dialogs/knewbankdlg.cpp index b5bee9070..995710de1 100644 --- a/kmymoney/dialogs/knewbankdlg.cpp +++ b/kmymoney/dialogs/knewbankdlg.cpp @@ -1,262 +1,262 @@ /* * Copyright 2000-2002 Michael Edwardes * Copyright 2017 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "knewbankdlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_knewbankdlg.h" #include "mymoneyinstitution.h" #include "kmymoneyutils.h" #include "icons.h" #include class KNewBankDlgPrivate { Q_DISABLE_COPY(KNewBankDlgPrivate) public: KNewBankDlgPrivate() : ui(new Ui::KNewBankDlg) { m_iconLoadTimer.setSingleShot(true); } ~KNewBankDlgPrivate() { delete ui; } Ui::KNewBankDlg* ui; MyMoneyInstitution m_institution; QTimer m_iconLoadTimer; QPointer m_favIconJob; QIcon m_favIcon; QString m_iconName; QUrl m_url; }; KNewBankDlg::KNewBankDlg(MyMoneyInstitution& institution, QWidget *parent) : QDialog(parent), d_ptr(new KNewBankDlgPrivate) { Q_D(KNewBankDlg); d->ui->setupUi(this); d->m_institution = institution; setModal(true); d->ui->nameEdit->setFocus(); d->ui->nameEdit->setText(institution.name()); d->ui->cityEdit->setText(institution.city()); d->ui->streetEdit->setText(institution.street()); d->ui->postcodeEdit->setText(institution.postcode()); d->ui->telephoneEdit->setText(institution.telephone()); d->ui->sortCodeEdit->setText(institution.sortcode()); d->ui->bicEdit->setText(institution.value(QStringLiteral("bic"))); d->ui->urlEdit->setText(institution.value(QStringLiteral("url"))); if (!institution.value(QStringLiteral("icon")).isEmpty()) { d->m_favIcon = Icons::loadIconFromApplicationCache(institution.value(QStringLiteral("icon"))); } if (!d->m_favIcon.isNull()) { d->ui->iconButton->setEnabled(true); d->ui->iconButton->setIcon(d->m_favIcon); } d->ui->messageWidget->hide(); connect(d->ui->buttonBox, &QDialogButtonBox::accepted, this, &KNewBankDlg::okClicked); connect(d->ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(d->ui->nameEdit, &QLineEdit::textChanged, this, &KNewBankDlg::institutionNameChanged); connect(d->ui->urlEdit, &QLineEdit::textChanged, this, &KNewBankDlg::slotUrlChanged); connect(&d->m_iconLoadTimer, &QTimer::timeout, this, &KNewBankDlg::slotLoadIcon); connect(d->ui->iconButton, &QToolButton::pressed, this, [=] { QUrl url; url.setUrl(QString::fromLatin1("https://%1/").arg(d->ui->urlEdit->text())); QDesktopServices::openUrl(url); }); institutionNameChanged(d->ui->nameEdit->text()); slotUrlChanged(d->ui->urlEdit->text()); auto requiredFields = new KMandatoryFieldGroup(this); requiredFields->setOkButton(d->ui->buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present requiredFields->add(d->ui->nameEdit); } void KNewBankDlg::institutionNameChanged(const QString &_text) { Q_D(KNewBankDlg); d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!_text.isEmpty()); } KNewBankDlg::~KNewBankDlg() { Q_D(KNewBankDlg); delete d; } void KNewBankDlg::okClicked() { Q_D(KNewBankDlg); if (d->ui->nameEdit->text().isEmpty()) { KMessageBox::information(this, i18n("The institution name field is empty. Please enter the name."), i18n("Adding New Institution")); d->ui->nameEdit->setFocus(); return; } d->m_institution.setName(d->ui->nameEdit->text()); d->m_institution.setTown(d->ui->cityEdit->text()); d->m_institution.setStreet(d->ui->streetEdit->text()); d->m_institution.setPostcode(d->ui->postcodeEdit->text()); d->m_institution.setTelephone(d->ui->telephoneEdit->text()); d->m_institution.setSortcode(d->ui->sortCodeEdit->text()); d->m_institution.setValue(QStringLiteral("bic"), d->ui->bicEdit->text()); d->m_institution.setValue(QStringLiteral("url"), d->ui->urlEdit->text()); d->m_institution.deletePair(QStringLiteral("icon")); if (d->ui->iconButton->isEnabled()) { d->m_institution.setValue(QStringLiteral("icon"), d->m_iconName); Icons::storeIconInApplicationCache(d->m_iconName, d->m_favIcon); } accept(); } const MyMoneyInstitution& KNewBankDlg::institution() { Q_D(KNewBankDlg); return d->m_institution; } void KNewBankDlg::newInstitution(MyMoneyInstitution& institution) { institution.clearId(); QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { institution = dlg->institution(); KMyMoneyUtils::newInstitution(institution); } delete dlg; } void KNewBankDlg::slotUrlChanged(const QString& newUrl) { Q_D(KNewBankDlg); // remove a possible leading protocol since we only provide https for now QRegularExpression protocol(QStringLiteral("^[a-zA-Z]+://(?.*)"), QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch matcher = protocol.match(newUrl); if (matcher.hasMatch()) { d->ui->urlEdit->setText(matcher.captured(QStringLiteral("url"))); d->ui->messageWidget->setText(QLatin1String("The protocol part has been removed by KMyMoney because it is fixed to https.")); d->ui->messageWidget->setMessageType(KMessageWidget::Information); d->ui->messageWidget->animatedShow(); } d->m_iconLoadTimer.start(200); } void KNewBankDlg::slotLoadIcon() { Q_D(KNewBankDlg); // if currently a check is running, retry later if (d->m_favIconJob) { d->m_iconLoadTimer.start(200); return; } const auto path = d->ui->urlEdit->text(); QRegularExpression urlRe(QStringLiteral("^(.*\\.)?[^\\.]{2,}\\.[a-z]{2,}"), QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch matcher = urlRe.match(path); d->ui->iconButton->setEnabled(false); if (matcher.hasMatch()) { d->ui->iconButton->setEnabled(true); d->m_url = QUrl(QString::fromLatin1("https://%1").arg(path)); KIO::Scheduler::checkSlaveOnHold(true); d->m_favIconJob = new KIO::FavIconRequestJob(d->m_url); connect(d->m_favIconJob, &KIO::FavIconRequestJob::result, this, &KNewBankDlg::slotIconLoaded); // we force to end the job after 1 second to avoid blocking this mechanism in case the thing fails QTimer::singleShot(1000, this, &KNewBankDlg::killIconLoad); } } void KNewBankDlg::killIconLoad() { Q_D(KNewBankDlg); if (d->m_favIconJob) { d->m_favIconJob->kill(); d->m_favIconJob->deleteLater(); } } void KNewBankDlg::slotIconLoaded(KJob* job) { Q_D(KNewBankDlg); switch(job->error()) { case ECONNREFUSED: // There is an answer from the server, but no favicon. In case we // already have one, we keep it d->ui->iconButton->setEnabled(true); - d->m_favIcon = Icons::get(Icons::Icon::ViewBank); - d->m_iconName = QStringLiteral("enum:ViewBank"); + d->m_favIcon = Icons::get(Icons::Icon::Bank); + d->m_iconName = QStringLiteral("enum:Bank"); break; case 0: // There is an answer from the server, and the favicon is found d->ui->iconButton->setEnabled(true); d->m_favIcon = QIcon(dynamic_cast(job)->iconFile()); d->m_iconName = QStringLiteral("favicon:%1").arg(d->m_url.host()); break; default: // There is problem with the URL from qDebug() << "KIO::FavIconRequestJob error" << job->error(); // intentional fall through case EALREADY: // invalid URL, no server response d->ui->iconButton->setEnabled(false); d->m_favIcon = QIcon(); d->m_iconName.clear(); break; } d->ui->iconButton->setIcon(d->m_favIcon); } diff --git a/kmymoney/dialogs/settings/ksettingskmymoney.cpp b/kmymoney/dialogs/settings/ksettingskmymoney.cpp index 885af3539..92c01d255 100644 --- a/kmymoney/dialogs/settings/ksettingskmymoney.cpp +++ b/kmymoney/dialogs/settings/ksettingskmymoney.cpp @@ -1,82 +1,82 @@ /* * Copyright 2014-2016 Christian Dávid * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ksettingskmymoney.h" #include #include #include "ksettingsgeneral.h" #include "ksettingsregister.h" #include "ksettingscolors.h" #include "ksettingsfonts.h" #include "ksettingsicons.h" #include "ksettingsschedules.h" #include "ksettingsonlinequotes.h" #include "ksettingshome.h" #include "ksettingsplugins.h" #include "icons.h" using namespace Icons; KSettingsKMyMoney::KSettingsKMyMoney(QWidget *parent, const QString &name, KCoreConfigSkeleton *config) : KConfigDialog(parent, name, config) { // create the pages ... const auto generalPage = new KSettingsGeneral(); const auto registerPage = new KSettingsRegister(); const auto homePage = new KSettingsHome(); const auto schedulesPage = new KSettingsSchedules(); const auto colorsPage = new KSettingsColors(); const auto fontsPage = new KSettingsFonts(); const auto iconsPage = new KSettingsIcons(); const auto onlineQuotesPage = new KSettingsOnlineQuotes(); const auto pluginsPage = new KSettingsPlugins(); addPage(generalPage, i18nc("General settings", "General"), Icons::get(Icon::PreferencesGeneral).name()); - addPage(homePage, i18n("Home"), Icons::get(Icon::ViewHome).name()); - addPage(registerPage, i18nc("Ledger view settings", "Ledger"), Icons::get(Icon::ViewFinancialList).name()); - addPage(schedulesPage, QString(i18n("Scheduled\ntransactions")).replace(QLatin1Char('\n'), QString::fromUtf8("\xe2\x80\xa8")), Icons::get(Icon::ViewSchedules).name()); + addPage(homePage, i18n("Home"), Icons::get(Icon::Home).name()); + addPage(registerPage, i18nc("Ledger view settings", "Ledger"), Icons::get(Icon::Ledger).name()); + addPage(schedulesPage, QString(i18n("Scheduled\ntransactions")).replace(QLatin1Char('\n'), QString::fromUtf8("\xe2\x80\xa8")), Icons::get(Icon::Schedule).name()); addPage(onlineQuotesPage, i18n("Online Quotes"), Icons::get(Icon::PreferencesNetwork).name()); - addPage(colorsPage, i18n("Colors"), Icons::get(Icon::PreferencesColor).name()); - addPage(fontsPage, i18n("Fonts"), Icons::get(Icon::PreferencesFont).name()); - addPage(iconsPage, i18n("Icons"), Icons::get(Icon::PreferencesIcon).name()); - addPage(pluginsPage, i18n("Plugins"), Icons::get(Icon::PreferencesPlugin).name(), QString(), false); + addPage(colorsPage, i18n("Colors"), Icons::get(Icon::PreferencesColors).name()); + addPage(fontsPage, i18n("Fonts"), Icons::get(Icon::PreferencesFonts).name()); + addPage(iconsPage, i18n("Icons"), Icons::get(Icon::PreferencesIcons).name()); + addPage(pluginsPage, i18n("Plugins"), Icons::get(Icon::PreferencesPlugins).name(), QString(), false); setHelp("details.settings", "kmymoney"); connect(this, &KConfigDialog::rejected, schedulesPage, &KSettingsSchedules::slotResetRegion); connect(this, &KConfigDialog::rejected, iconsPage, &KSettingsIcons::slotResetTheme); connect(this, &KConfigDialog::settingsChanged, generalPage, &KSettingsGeneral::slotUpdateEquitiesVisibility); auto defaultButton = button(QDialogButtonBox::RestoreDefaults); auto applyButton = button(QDialogButtonBox::Apply); connect(this, &KConfigDialog::accepted, pluginsPage, &KSettingsPlugins::slotSavePluginConfiguration); connect(applyButton, &QPushButton::clicked, pluginsPage, &KSettingsPlugins::slotSavePluginConfiguration); connect(defaultButton, &QPushButton::clicked, pluginsPage, &KSettingsPlugins::slotResetToDefaults); connect(pluginsPage, &KSettingsPlugins::changed, this, &KSettingsKMyMoney::slotPluginsChanged); connect(pluginsPage, &KSettingsPlugins::settingsChanged, this, &KConfigDialog::settingsChanged); } void KSettingsKMyMoney::slotPluginsChanged(bool changed) { auto applyButton = button(QDialogButtonBox::Apply); applyButton->setEnabled(changed); } diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account_add.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-add.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account_add.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-add.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_asset.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-asset.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_asset.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-asset.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_cash.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-cash.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_cash.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-cash.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_checking.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-checking.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_checking.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-checking.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_closed.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-closed.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_closed.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-closed.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_credit-card.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-credit-card.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_credit-card.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-credit-card.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_expense.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-expense.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_expense.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-expense.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_income.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-income.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_income.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-income.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_investments.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-investments.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_investments.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-investments.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_liability.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-liability.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_liability.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-liability.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_loan.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-loan.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_loan.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-loan.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_savings.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-savings.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-account-types_savings.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-account-types-savings.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-forcast.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-forecast.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-forcast.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-forecast.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-hide_categories.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-hide-categories.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-hide_categories.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-hide-categories.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-hide_reconciled.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-hide-reconciled.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-hide_reconciled.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-hide-reconciled.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-institution_add.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-institution-add.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-institution_add.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-institution-add.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-onlinebanking.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-online-banking.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-onlinebanking.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-online-banking.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-personal_data.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-personal-data.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-personal_data.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-personal-data.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-transaction_find.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-transaction-find.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-transaction_find.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-transaction-find.svgz diff --git a/kmymoney/icons/Tango/scalable/actions/sc-actions-view_info.svgz b/kmymoney/icons/Tango/scalable/actions/sc-actions-view-info.svgz similarity index 100% rename from kmymoney/icons/Tango/scalable/actions/sc-actions-view_info.svgz rename to kmymoney/icons/Tango/scalable/actions/sc-actions-view-info.svgz diff --git a/kmymoney/icons/hicolor/16x16/actions/16-actions-pay_edit.png b/kmymoney/icons/hicolor/16x16/actions/16-actions-pay-edit.png similarity index 100% rename from kmymoney/icons/hicolor/16x16/actions/16-actions-pay_edit.png rename to kmymoney/icons/hicolor/16x16/actions/16-actions-pay-edit.png diff --git a/kmymoney/icons/hicolor/16x16/actions/16-actions-personal_data.png b/kmymoney/icons/hicolor/16x16/actions/16-actions-personal-data.png similarity index 100% rename from kmymoney/icons/hicolor/16x16/actions/16-actions-personal_data.png rename to kmymoney/icons/hicolor/16x16/actions/16-actions-personal-data.png diff --git a/kmymoney/icons/hicolor/16x16/actions/16-actions-transaction_export.png b/kmymoney/icons/hicolor/16x16/actions/16-actions-transaction-export.png similarity index 100% rename from kmymoney/icons/hicolor/16x16/actions/16-actions-transaction_export.png rename to kmymoney/icons/hicolor/16x16/actions/16-actions-transaction-export.png diff --git a/kmymoney/icons/hicolor/16x16/actions/16-actions-transaction_import.png b/kmymoney/icons/hicolor/16x16/actions/16-actions-transaction-import.png similarity index 100% rename from kmymoney/icons/hicolor/16x16/actions/16-actions-transaction_import.png rename to kmymoney/icons/hicolor/16x16/actions/16-actions-transaction-import.png diff --git a/kmymoney/icons/hicolor/16x16/actions/16-actions-update_prices.png b/kmymoney/icons/hicolor/16x16/actions/16-actions-update-prices.png similarity index 100% rename from kmymoney/icons/hicolor/16x16/actions/16-actions-update_prices.png rename to kmymoney/icons/hicolor/16x16/actions/16-actions-update-prices.png diff --git a/kmymoney/icons/hicolor/22x22/actions/22-actions-pay_edit.png b/kmymoney/icons/hicolor/22x22/actions/22-actions-pay-edit.png similarity index 100% rename from kmymoney/icons/hicolor/22x22/actions/22-actions-pay_edit.png rename to kmymoney/icons/hicolor/22x22/actions/22-actions-pay-edit.png diff --git a/kmymoney/icons/hicolor/22x22/actions/22-actions-personal_data.png b/kmymoney/icons/hicolor/22x22/actions/22-actions-personal-data.png similarity index 100% rename from kmymoney/icons/hicolor/22x22/actions/22-actions-personal_data.png rename to kmymoney/icons/hicolor/22x22/actions/22-actions-personal-data.png diff --git a/kmymoney/icons/hicolor/22x22/actions/22-actions-set_as.png b/kmymoney/icons/hicolor/22x22/actions/22-actions-set-as.png similarity index 100% rename from kmymoney/icons/hicolor/22x22/actions/22-actions-set_as.png rename to kmymoney/icons/hicolor/22x22/actions/22-actions-set-as.png diff --git a/kmymoney/icons/hicolor/22x22/actions/22-actions-transaction_export.png b/kmymoney/icons/hicolor/22x22/actions/22-actions-transaction-export.png similarity index 100% rename from kmymoney/icons/hicolor/22x22/actions/22-actions-transaction_export.png rename to kmymoney/icons/hicolor/22x22/actions/22-actions-transaction-export.png diff --git a/kmymoney/icons/hicolor/22x22/actions/22-actions-transaction_import.png b/kmymoney/icons/hicolor/22x22/actions/22-actions-transaction-import.png similarity index 100% rename from kmymoney/icons/hicolor/22x22/actions/22-actions-transaction_import.png rename to kmymoney/icons/hicolor/22x22/actions/22-actions-transaction-import.png diff --git a/kmymoney/icons/hicolor/22x22/actions/22-actions-update_prices.png b/kmymoney/icons/hicolor/22x22/actions/22-actions-update-prices.png similarity index 100% rename from kmymoney/icons/hicolor/22x22/actions/22-actions-update_prices.png rename to kmymoney/icons/hicolor/22x22/actions/22-actions-update-prices.png diff --git a/kmymoney/icons/icons.cpp b/kmymoney/icons/icons.cpp index 18bff0d92..54543f35c 100644 --- a/kmymoney/icons/icons.cpp +++ b/kmymoney/icons/icons.cpp @@ -1,460 +1,555 @@ /*************************************************************************** icons.cpp ------------------- begin : Sun Jun 25 2017 copyright : (C) 2017 by Łukasz Wojniłowicz + (C) 2020 by Dawid Wróbel ***************************************************************************/ /*************************************************************************** * * * 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 "icons.h" #include #include #include #include #include #include -#include #include #include namespace Icons { QHash sStandardIcons; uint qHash(const Icon key, uint seed) { return ::qHash(static_cast(key), seed); } + uint qHash(const IconSet key, uint seed) + { + return ::qHash(static_cast(key), seed); + } + struct iconDescription { - Icon baseIcon; - Icon overlayIcon; - Qt::Corner corner; + Icon baseIcon; + Icon overlayIcon; + Qt::Corner corner; + }; + + const QHash > iconMappings{ + {Icon::AccountClosed, + {{IconSet::Common, QStringLiteral("account-types-closed")}, + {IconSet::Oxygen, QStringLiteral("dialog-close")}, + {IconSet::Breeze, QStringLiteral("dialog-close")}}}, + {Icon::ArrowDown, + {{IconSet::Common, QStringLiteral("arrow-down")}, + {IconSet::Tango, QStringLiteral("go-down")}}}, + {Icon::ArrowLeft, + {{IconSet::Common, QStringLiteral("arrow-left")}, + {IconSet::Tango, QStringLiteral("go-previous")}}}, + {Icon::ArrowRight, + {{IconSet::Common, QStringLiteral("arrow-right")}, + {IconSet::Tango, QStringLiteral("go-next")}}}, + {Icon::ArrowUp, + {{IconSet::Common, QStringLiteral("arrow-up")}, + {IconSet::Tango, QStringLiteral("go-up")}}}, + {Icon::Budget, + {{IconSet::Common, QStringLiteral("budget")}, + {IconSet::Oxygen, QStringLiteral("view-time-schedule-calculus")}, + {IconSet::Breeze, QStringLiteral("view-time-schedule-calculus")}}}, + {Icon::Calculator, + {{IconSet::Common, QStringLiteral("accessories-calculator")}}}, + {Icon::Configure, + {{IconSet::Common, QStringLiteral("configure")}, + {IconSet::Tango, QStringLiteral("preferences-system")}}}, + {Icon::DialogCancel, + {{IconSet::Common, QStringLiteral("dialog-cancel")}, + {IconSet::Tango, QStringLiteral("stop")}}}, + {Icon::DialogClose, {{IconSet::Common, QStringLiteral("dialog-close")}}}, + {Icon::DialogError, {{IconSet::Common, QStringLiteral("dialog-error")}}}, + {Icon::DialogInformation, + {{IconSet::Common, QStringLiteral("dialog-information")}}}, + {Icon::DialogOK, + {{IconSet::Common, QStringLiteral("dialog-ok")}, + {IconSet::Tango, QStringLiteral("finish")}}}, + {Icon::DialogOKApply, + {{IconSet::Common, QStringLiteral("dialog-ok-apply")}}}, + {Icon::DialogWarning, + {{IconSet::Common, QStringLiteral("dialog-warning")}}}, + {Icon::DocumentClose, + {{IconSet::Common, QStringLiteral("document-close")}, + {IconSet::Tango, QStringLiteral("stop")}}}, + {Icon::DocumentEdit, + {{IconSet::Common, QStringLiteral("document-edit")}, + {IconSet::Tango, QStringLiteral("text-editor")}}}, + {Icon::DocumentExport, + {{IconSet::Common, QStringLiteral("format-indent-more")}, + {IconSet::Oxygen, QStringLiteral("document-export")}, + {IconSet::Breeze, QStringLiteral("document-export")}}}, + {Icon::DocumentImport, + {{IconSet::Common, QStringLiteral("format-indent-less")}, + {IconSet::Oxygen, QStringLiteral("document-import")}, + {IconSet::Breeze, QStringLiteral("document-import")}}}, + {Icon::DocumentNew, {{IconSet::Common, QStringLiteral("document-new")}}}, + {Icon::DocumentOpen, + {{IconSet::Common, QStringLiteral("document-open")}}}, + {Icon::DocumentProperties, + {{IconSet::Common, QStringLiteral("document-properties")}}}, + {Icon::DocumentSave, + {{IconSet::Common, QStringLiteral("document-save")}}}, + {Icon::Download, + {{IconSet::Breeze, QStringLiteral("edit-download")}, + {IconSet::Common, QStringLiteral("go-down")}, + {IconSet::Oxygen, QStringLiteral("download")}}}, + {Icon::EditClear, {{IconSet::Common, QStringLiteral("edit-clear")}}}, + {Icon::EditCopy, {{IconSet::Common, QStringLiteral("edit-copy")}}}, + {Icon::EditDelete, {{IconSet::Common, QStringLiteral("edit-delete")}}}, + {Icon::Find, {{IconSet::Common, QStringLiteral("edit-find")}}}, + {Icon::EditRename, + {{IconSet::Common, QStringLiteral("edit-rename")}, + {IconSet::Tango, QStringLiteral("text-editor")}}}, + {Icon::EditUndo, {{IconSet::Common, QStringLiteral("edit-undo")}}}, + {Icon::Folder, {{IconSet::Common, QStringLiteral("folder")}}}, + {Icon::GoTo, {{IconSet::Common, QStringLiteral("go-jump")}}}, + {Icon::Help, {{IconSet::Common, QStringLiteral("help-contents")}}}, + {Icon::HideCategories, + {{IconSet::Common, QStringLiteral("hide-categories")}}}, + {Icon::HideReconciled, + {{IconSet::Common, QStringLiteral("hide-reconciled")}}}, + {Icon::KMyMoney, {{IconSet::Common, QStringLiteral("kmymoney")}}}, + {Icon::KeyEnter, + {{IconSet::Common, QStringLiteral("input-keyboard")}, + {IconSet::Oxygen, QStringLiteral("key-enter")}, + {IconSet::Breeze, QStringLiteral("key-enter")}}}, + {Icon::ListAdd, {{IconSet::Common, QStringLiteral("list-add")}}}, + {Icon::ListAddTag, {{IconSet::Common, QStringLiteral("list-add-tag")}}}, + {Icon::ListAddUser, + {{IconSet::Common, QStringLiteral("list-add-user")}}}, + {Icon::ListCollapse, + {{IconSet::Common, QStringLiteral("zoom-out")}, + {IconSet::Tango, QStringLiteral("list-remove")}}}, + {Icon::ListExpand, + {{IconSet::Common, QStringLiteral("zoom-in")}, + {IconSet::Tango, QStringLiteral("list-add")}}}, + {Icon::ListRemoveTag, + {{IconSet::Common, QStringLiteral("list-remove-tag")}}}, + {Icon::ListRemoveUser, + {{IconSet::Common, QStringLiteral("list-remove-user")}}}, + {Icon::MailMessage, + {{IconSet::Common, QStringLiteral("internet-mail")}, + {IconSet::Oxygen, QStringLiteral("mail-message")}, + {IconSet::Breeze, QStringLiteral("mail-message")}}}, + {Icon::MailMessageNew, + {{IconSet::Common, QStringLiteral("mail-message-new")}}}, + {Icon::MailReceive, {{IconSet::Common, QStringLiteral("mail-receive")}}}, + {Icon::MapOnlineAccount, + {{IconSet::Common, QStringLiteral("news-subscribe")}}}, + {Icon::Merge, + {{IconSet::Common, QStringLiteral("reconcile")}, + {IconSet::Oxygen, QStringLiteral("merge")}, + {IconSet::Breeze, QStringLiteral("merge")}}}, + {Icon::NewSchedule, + {{IconSet::Common, QStringLiteral("appointment-new")}}}, + {Icon::OfficeChartLine, + {{IconSet::Common, QStringLiteral("account-types-investments")}, + {IconSet::Oxygen, QStringLiteral("office-chart-line")}, + {IconSet::Breeze, QStringLiteral("office-chart-line")}, + {IconSet::Tango, QStringLiteral("report-line")}}}, + {Icon::OpenDatabase, {{IconSet::Common, QStringLiteral("server-database")}}}, + {Icon::Pause, + {{IconSet::Common, QStringLiteral("media-playback-pause")}}}, + {Icon::PayeeMerge, {{IconSet::Common, QStringLiteral("merge")}}}, + {Icon::PayeeRename, + {{IconSet::Common, QStringLiteral("user-properties")}, + {IconSet::Oxygen, QStringLiteral("payee-rename")}}}, + {Icon::PerformanceTest, + {{IconSet::Common, QStringLiteral("fork")}, + {IconSet::Breeze, QStringLiteral("speedometer")}}}, + {Icon::PreferencesColors, + {{IconSet::Common, QStringLiteral("preferences-desktop-color")}}}, + {Icon::PreferencesFonts, + {{IconSet::Common, QStringLiteral("preferences-desktop-font")}}}, + {Icon::PreferencesGeneral, + {{IconSet::Common, QStringLiteral("system-run")}, + {IconSet::Tango, QStringLiteral("media-playback-start")}}}, + {Icon::PreferencesIcons, + {{IconSet::Common, QStringLiteral("preferences-desktop-icon")}}}, + {Icon::PreferencesNetwork, + {{IconSet::Common, QStringLiteral("preferences-system-network")}}}, + {Icon::PreferencesPlugins, + {{IconSet::Common, QStringLiteral("network-disconnect")}}}, + {Icon::Reconcile, + {{IconSet::Common, QStringLiteral("reconcile")}, + {IconSet::Oxygen, QStringLiteral("merge")}, + {IconSet::Breeze, QStringLiteral("merge")}}}, + {Icon::Reconciled, + {{IconSet::Common, QStringLiteral("reconciled")}, + {IconSet::Oxygen, QStringLiteral("flag-green")}, + {IconSet::Breeze, QStringLiteral("flag-green")}}}, + {Icon::Refresh, + {{IconSet::Breeze, QStringLiteral("view-refresh")}, + {IconSet::Oxygen, QStringLiteral("refresh")}}}, + {Icon::Report, + {{IconSet::Common, QStringLiteral("application-vnd.oasis.opendocument.spreadsheet")}}}, + {Icon::Reverse, {{IconSet::Common, QStringLiteral("reverse")}}}, + {Icon::SeekForward, + {{IconSet::Common, QStringLiteral("media-seek-forward")}}}, + {Icon::SkipForward, + {{IconSet::Common, QStringLiteral("media-skip-forward")}}}, + {Icon::SortAscending, + {{IconSet::Common, QStringLiteral("go-up")}, + {IconSet::Oxygen, QStringLiteral("view-sort-ascending")}, + {IconSet::Breeze, QStringLiteral("view-sort-ascending")}}}, + {Icon::SortDescending, + {{IconSet::Common, QStringLiteral("go-down")}, + {IconSet::Oxygen, QStringLiteral("view-sort-descending")}, + {IconSet::Breeze, QStringLiteral("view-sort-descending")}}}, + {Icon::Split, + {{IconSet::Common, QStringLiteral("transaction-split")}, + {IconSet::Oxygen, QStringLiteral("split")}, + {IconSet::Breeze, QStringLiteral("split")}}}, + {Icon::TagRename, {{IconSet::Common, QStringLiteral("tag-rename")}}}, + {Icon::TaskAccepted, + {{IconSet::Common, QStringLiteral("task-accepted")}}}, + {Icon::TaskComplete, + {{IconSet::Common, QStringLiteral("task-complete")}}}, + {Icon::TaskOngoing, {{IconSet::Common, QStringLiteral("task-ongoing")}}}, + {Icon::TaskReject, {{IconSet::Common, QStringLiteral("task-reject")}}}, + {Icon::Warning, + {{IconSet::Common, QStringLiteral("dialog-warning")}}}, + {Icon::Tip, + {{IconSet::Common, QStringLiteral("info")}, + {IconSet::Oxygen, QStringLiteral("ktip")}}}, + {Icon::Unknown, {{IconSet::Common, QStringLiteral("unknown")}}}, + {Icon::UnmapOnlineAccount, + {{IconSet::Common, QStringLiteral("news-unsubscribe")}}}, + {Icon::UserProperties, + {{IconSet::Common, QStringLiteral("system-users")}, + {IconSet::Oxygen, QStringLiteral("user-properties")}, + {IconSet::Breeze, QStringLiteral("user-properties")}}}, + {Icon::Accounts, + {{IconSet::Common, QStringLiteral("account")}, + {IconSet::Oxygen, QStringLiteral("view-bank-account")}, + {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, + {Icon::Asset, + {{IconSet::Common, QStringLiteral("account-types-asset")}, + {IconSet::Oxygen, QStringLiteral("view-bank-account")}, + {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, + {Icon::Bank, + {{IconSet::Common, QStringLiteral("bank")}, + {IconSet::Oxygen, QStringLiteral("view-bank")}, + {IconSet::Breeze, QStringLiteral("view-bank")}}}, + {Icon::BankAccount, + {{IconSet::Common, QStringLiteral("account")}, + {IconSet::Oxygen, QStringLiteral("view-bank-account")}, + {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, + {Icon::Calendar, + {{IconSet::Common, QStringLiteral("view-calendar")}}}, + {Icon::CalendarDay, + {{IconSet::Common, QStringLiteral("office-calendar")}, + {IconSet::Oxygen, QStringLiteral("view-calendar-day")}, + {IconSet::Breeze, QStringLiteral("view-calendar-day")}}}, + {Icon::Cash, + {{IconSet::Common, QStringLiteral("account-types-cash")}}}, + {Icon::Checking, + {{IconSet::Common, QStringLiteral("account-types-checking")}, + {IconSet::Oxygen, QStringLiteral("view-bank-account-checking")}, + {IconSet::Breeze, QStringLiteral("view-bank-account-checking")}}}, + {Icon::Close, {{IconSet::Common, QStringLiteral("view-close")}}}, + {Icon::CreditCard, + {{IconSet::Breeze, QStringLiteral("skrooge_credit_card")}, + {IconSet::Common, QStringLiteral("account-types-credit-card")}, + {IconSet::Oxygen, QStringLiteral("view-credit-card-account")}}}, + {Icon::Currencies, + {{IconSet::Common, QStringLiteral("view-currency-list")}}}, + {Icon::Equity, + {{IconSet::Common, QStringLiteral("account")}, + {IconSet::Oxygen, QStringLiteral("view-bank-account")}, + {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, + {Icon::Expense, + {{IconSet::Breeze, QStringLiteral("view-categories-expenditures")}, + {IconSet::Common, QStringLiteral("account-types-expense")}, + {IconSet::Oxygen, QStringLiteral("view-expenses-categories")}}}, + {Icon::Filter, {{IconSet::Common, QStringLiteral("view-filter")}}}, + {Icon::FinancialCategories, + {{IconSet::Common, QStringLiteral("categories")}, + {IconSet::Oxygen, QStringLiteral("view-categories")}, + {IconSet::Breeze, QStringLiteral("view-categories")}, + {IconSet::Oxygen, QStringLiteral("view-financial-categories")}}}, + {Icon::Ledger, + {{IconSet::Common, QStringLiteral("ledger")}, + {IconSet::Oxygen, QStringLiteral("view-financial-list")}}}, + {Icon::Transaction, + {{IconSet::Common, QStringLiteral("ledger")}, + {IconSet::Oxygen, QStringLiteral("view-financial-transfer")}}}, + {Icon::Forecast, + {{IconSet::Common, QStringLiteral("forecast")}, + {IconSet::Oxygen, QStringLiteral("view-financial-forecast")}}}, + {Icon::Home, + {{IconSet::Common, QStringLiteral("home")}, + {IconSet::Oxygen, QStringLiteral("go-home")}, + {IconSet::Breeze, QStringLiteral("go-home")}}}, + {Icon::Income, + {{IconSet::Breeze, QStringLiteral("view-categories-incomes")}, + {IconSet::Common, QStringLiteral("account-types-income")}, + {IconSet::Oxygen, QStringLiteral("view-income-categories")}}}, + {Icon::Institution, + {{IconSet::Common, QStringLiteral("institution")}, + {IconSet::Oxygen, QStringLiteral("view-bank")}, + {IconSet::Breeze, QStringLiteral("view-bank")}}}, + {Icon::Institutions, + {{IconSet::Common, QStringLiteral("institution")}, + {IconSet::Oxygen, QStringLiteral("view-bank")}, + {IconSet::Breeze, QStringLiteral("view-bank")}}}, + {Icon::Investment, + {{IconSet::Common, QStringLiteral("investment")}, + {IconSet::Oxygen, QStringLiteral("view-investment")}}}, + {Icon::Investments, + {{IconSet::Common, QStringLiteral("investment")}, + {IconSet::Oxygen, QStringLiteral("view-investment")}}}, + {Icon::Liability, + {{IconSet::Common, QStringLiteral("account-types-liability")}, + {IconSet::Oxygen, QStringLiteral("view-loan")}}}, + {Icon::Loan, + {{IconSet::Common, QStringLiteral("account-types-loan")}, + {IconSet::Oxygen, QStringLiteral("view-loan")}}}, + {Icon::LoanAsset, + {{IconSet::Common, QStringLiteral("account-types-loan")}, + {IconSet::Oxygen, QStringLiteral("view-loan-asset")}}}, + {Icon::OnlineJobOutbox, + {{IconSet::Common, QStringLiteral("online-banking")}}}, + {Icon::Payees, + {{IconSet::Common, QStringLiteral("payee")}, + {IconSet::Oxygen, QStringLiteral("system-users")}, + {IconSet::Breeze, QStringLiteral("system-users")}}}, + {Icon::Reports, + {{IconSet::Common, QStringLiteral("report")}, + {IconSet::Oxygen, QStringLiteral("office-chart-bar")}, + {IconSet::Breeze, QStringLiteral("office-chart-bar")}}}, + {Icon::Savings, + {{IconSet::Common, QStringLiteral("account-types-savings")}, + {IconSet::Oxygen, QStringLiteral("view-bank-account-savings")}, + {IconSet::Breeze, QStringLiteral("view-bank-account-savings")}}}, + {Icon::Schedule, + {{IconSet::Common, QStringLiteral("schedule")}, + {IconSet::Oxygen, QStringLiteral("view-pim-calendar")}, + {IconSet::Breeze, QStringLiteral("view-pim-calendar")}}}, + {Icon::Stock, + {{IconSet::Common, QStringLiteral("account-types-investments")}, + {IconSet::Oxygen, QStringLiteral("view-stock-account")}}}, + {Icon::Tags, + {{IconSet::Common, QStringLiteral("bookmark-new")}, + {IconSet::Oxygen, QStringLiteral("mail-tagged")}, + {IconSet::Breeze, QStringLiteral("mail-tagged")}}}, + {Icon::TransactionDetails, + {{IconSet::Common, QStringLiteral("edit-find")}, + {IconSet::Oxygen, QStringLiteral("zoom-in")}, + {IconSet::Breeze, QStringLiteral("zoom-in")}}}, + {Icon::UpcomingEvents, + {{IconSet::Common, QStringLiteral("view-calendar-upcoming-events")}}}, + {Icon::ZoomIn, {{IconSet::Common, QStringLiteral("zoom-in")}}}, + {Icon::ZoomOut, {{IconSet::Common, QStringLiteral("zoom-out")}}}, + {Icon::Visibility, {{IconSet::Common, QStringLiteral("visibility")}}}, + {Icon::NoVisibility, {{IconSet::Common, QStringLiteral("hint")}}}, + {Icon::SelectAll, {{IconSet::Common, QStringLiteral("edit-select-all")}}} }; const QHash sComposedIcons { - {Icon::EditFindTransaction, {Icon::ViewFinancialTransfer, Icon::EditFind, Qt::BottomRightCorner}}, - {Icon::InstitutionNew, {Icon::ViewBank, Icon::ListAdd, Qt::BottomRightCorner}}, - {Icon::InstitutionEdit, {Icon::ViewBank, Icon::DocumentEdit, Qt::BottomRightCorner}}, - {Icon::InstitutionDelete, {Icon::ViewBank, Icon::EditDelete, Qt::BottomRightCorner}}, - {Icon::AccountNew, {Icon::ViewBankAccount, Icon::ListAdd, Qt::TopRightCorner}}, + {Icon::InstitutionNew, {Icon::Bank, Icon::ListAdd, Qt::BottomRightCorner}}, + {Icon::InstitutionEdit, {Icon::Bank, Icon::DocumentEdit, Qt::BottomRightCorner}}, + {Icon::InstitutionDelete, {Icon::Bank, Icon::EditDelete, Qt::BottomRightCorner}}, + {Icon::AccountNew, {Icon::BankAccount, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::AccountFinishReconciliation, {Icon::Merge, Icon::DialogOK, Qt::BottomRightCorner}}, - {Icon::AccountEdit, {Icon::ViewBankAccount, Icon::DocumentEdit, Qt::BottomRightCorner}}, - {Icon::AccountDelete, {Icon::ViewBankAccount, Icon::EditDelete, Qt::BottomRightCorner}}, - {Icon::AccountClose, {Icon::ViewBankAccount, Icon::DialogClose, Qt::BottomRightCorner}}, - {Icon::AccountReopen, {Icon::ViewBankAccount, Icon::DialogOK, Qt::BottomRightCorner}}, - {Icon::AccountUpdateMenu, {Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}}, - {Icon::AccountUpdate, {Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}}, - {Icon::AccountUpdateAll, {Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}}, - {Icon::AccountCreditTransfer, {Icon::ViewBankAccount, Icon::MailMessageNew, Qt::BottomRightCorner}}, - {Icon::CategoryNew, {Icon::ViewFinancialCategories, Icon::ListAdd, Qt::TopRightCorner}}, - {Icon::CategoryEdit, {Icon::ViewFinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner}}, - {Icon::CategoryDelete, {Icon::ViewFinancialCategories, Icon::EditDelete, Qt::BottomRightCorner}}, - {Icon::ToolUpdatePrices, {Icon::ViewInvestments, Icon::Download, Qt::BottomRightCorner}}, - {Icon::TransactionNew, {Icon::ViewFinancialTransfer, Icon::ListAdd, Qt::TopRightCorner}}, - {Icon::TransactionEdit, {Icon::ViewFinancialTransfer, Icon::DocumentEdit, Qt::BottomRightCorner}}, - {Icon::TransactionMatch, {Icon::ViewFinancialTransfer, Icon::DocumentImport, Qt::BottomRightCorner}}, - {Icon::TransactionAccept, {Icon::ViewFinancialTransfer, Icon::DialogOKApply, Qt::BottomRightCorner}}, - {Icon::InvestmentNew, {Icon::ViewInvestments, Icon::ListAdd, Qt::TopRightCorner}}, - {Icon::InvestmentEdit, {Icon::ViewInvestments, Icon::DocumentEdit, Qt::BottomRightCorner}}, - {Icon::InvestmentDelete, {Icon::ViewInvestments, Icon::EditDelete, Qt::BottomRightCorner}}, - {Icon::InvestmentOnlinePrice, {Icon::ViewInvestments, Icon::Download, Qt::BottomRightCorner}}, + {Icon::AccountEdit, {Icon::BankAccount, Icon::DocumentEdit, Qt::BottomRightCorner}}, + {Icon::AccountDelete, {Icon::BankAccount, Icon::EditDelete, Qt::BottomRightCorner}}, + {Icon::AccountClose, {Icon::BankAccount, Icon::DialogClose, Qt::BottomRightCorner}}, + {Icon::AccountReopen, {Icon::BankAccount, Icon::DialogOK, Qt::BottomRightCorner}}, + {Icon::AccountUpdate, {Icon::BankAccount, Icon::Download, Qt::BottomRightCorner}}, + {Icon::AccountUpdateAll, {Icon::BankAccount, Icon::Download, Qt::BottomRightCorner}}, + {Icon::AccountCreditTransfer, {Icon::BankAccount, Icon::MailMessageNew, Qt::BottomRightCorner}}, + {Icon::CategoryNew, {Icon::FinancialCategories, Icon::ListAdd, Qt::TopRightCorner}}, + {Icon::CategoryEdit, {Icon::FinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner}}, + {Icon::CategoryDelete, {Icon::FinancialCategories, Icon::EditDelete, Qt::BottomRightCorner}}, + {Icon::TransactionNew, {Icon::Transaction, Icon::ListAdd, Qt::TopRightCorner}}, + {Icon::TransactionEdit, {Icon::Transaction, Icon::DocumentEdit, Qt::BottomRightCorner}}, + {Icon::TransactionMatch, {Icon::Transaction, Icon::DocumentImport, Qt::BottomRightCorner}}, + {Icon::TransactionAccept, {Icon::Transaction, Icon::DialogOKApply, Qt::BottomRightCorner}}, + {Icon::InvestmentNew, {Icon::Investment, Icon::ListAdd, Qt::TopRightCorner}}, + {Icon::InvestmentEdit, {Icon::Investment, Icon::DocumentEdit, Qt::BottomRightCorner}}, + {Icon::InvestmentDelete, {Icon::Investment, Icon::EditDelete, Qt::BottomRightCorner}}, + {Icon::InvestmentOnlinePrice, {Icon::Investment, Icon::Download, Qt::BottomRightCorner}}, + {Icon::InvestmentOnlinePriceAll, {Icon::Investment, Icon::Download, Qt::BottomRightCorner}}, {Icon::BudgetNew, {Icon::Budget, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::BudgetRename, {Icon::Budget, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::BudgetDelete, {Icon::Budget, Icon::EditDelete, Qt::BottomRightCorner}}, {Icon::BudgetCopy, {Icon::Budget, Icon::EditCopy, Qt::BottomRightCorner}}, - {Icon::PriceUpdate, {Icon::ViewCurrencyList, Icon::Download, Qt::BottomRightCorner}} + {Icon::PriceUpdate, {Icon::Currencies, Icon::Download, Qt::BottomRightCorner}} }; - QHash getCommonNames(); - QHash getKDENames(); - QHash getOxygenNames(); - QHash getBreezeNames(); - QHash getTangoNames(); - - QHash getCommonNames() - { - return { - {Icon::ListCollapse, QStringLiteral("zoom-out")}, - {Icon::ListExpand, QStringLiteral("zoom-in")}, - {Icon::ListAdd, QStringLiteral("list-add")}, - {Icon::ListRemoveTag, QStringLiteral("list-remove-tag")}, - {Icon::ListAddTag, QStringLiteral("list-add-tag")}, - {Icon::ListRemoveUser, QStringLiteral("list-remove-user")}, - {Icon::ListAddUser, QStringLiteral("list-add-user")}, - {Icon::NewSchedule, QStringLiteral("appointment-new")}, - {Icon::KeyEnter, QStringLiteral("input-keyboard")}, - {Icon::GoTo, QStringLiteral("go-jump")}, - {Icon::EditUndo, QStringLiteral("edit-undo")}, - {Icon::EditFind, QStringLiteral("edit-find")}, - {Icon::EditRename, QStringLiteral("edit-rename")}, - {Icon::EditCopy, QStringLiteral("edit-copy")}, - {Icon::EditDelete, QStringLiteral("edit-delete")}, - {Icon::EditClear, QStringLiteral("edit-clear")}, - {Icon::DialogClose, QStringLiteral("dialog-close")}, - {Icon::DialogCancel, QStringLiteral("dialog-cancel")}, - {Icon::DialogOK, QStringLiteral("dialog-ok")}, - {Icon::DialogOKApply, QStringLiteral("dialog-ok-apply")}, - {Icon::DialogWarning, QStringLiteral("dialog-warning")}, - {Icon::DialogError, QStringLiteral("dialog-error")}, - {Icon::DialogInformation, QStringLiteral("dialog-information")}, - {Icon::DocumentClose, QStringLiteral("document-close")}, - {Icon::DocumentOpen, QStringLiteral("document-open")}, - {Icon::DocumentSave, QStringLiteral("document-save")}, - {Icon::DocumentImport, QStringLiteral("format-indent-less")}, - {Icon::DocumentNew, QStringLiteral("document-new")}, - {Icon::DocumentEdit, QStringLiteral("document-edit")}, - {Icon::DocumentProperties, QStringLiteral("document-properties")}, - {Icon::DocumentImport, QStringLiteral("format-indent-less")}, - {Icon::DocumentExport, QStringLiteral("format-indent-more")}, - {Icon::UnmapOnlineAccount, QStringLiteral("news-unsubscribe")}, - {Icon::MapOnlineAccount, QStringLiteral("news-subscribe")}, - {Icon::OfficeChartLine, QStringLiteral("account-types-investments")}, - {Icon::SkipForward, QStringLiteral("media-skip-forward")}, - {Icon::SeekForward, QStringLiteral("media-seek-forward")}, - {Icon::Pause, QStringLiteral("media-playback-pause")}, - {Icon::Reconcile, QStringLiteral("reconcile")}, - {Icon::Merge, QStringLiteral("reconcile")}, - {Icon::PerformanceTest, QStringLiteral("fork")}, - {Icon::ViewEquity, QStringLiteral("account")}, - {Icon::ViewExpense, QStringLiteral("account-types-expense")}, - {Icon::ViewIncome, QStringLiteral("account-types-income")}, - {Icon::ViewCash, QStringLiteral("account-types-cash")}, - {Icon::ViewCreditCard, QStringLiteral("account-types-credit-card")}, - {Icon::ViewLoan, QStringLiteral("account-types-loan")}, - {Icon::ViewLoanAsset, QStringLiteral("account-types-loan")}, - {Icon::ViewSaving, QStringLiteral("account-types-savings")}, - {Icon::ViewChecking, QStringLiteral("account-types-checking")}, - {Icon::ViewStock, QStringLiteral("account-types-investments")}, - {Icon::ViewLiability, QStringLiteral("account-types-liability")}, - {Icon::ViewAsset, QStringLiteral("account-types-asset")}, - {Icon::ViewOutbox, QStringLiteral("online-banking")}, - {Icon::ViewForecast, QStringLiteral("forecast")}, - {Icon::ViewBudgets, QStringLiteral("budget")}, - {Icon::ViewReports, QStringLiteral("report")}, - {Icon::ViewInvestments, QStringLiteral("investment")}, - {Icon::ViewLedgers, QStringLiteral("ledger")}, - {Icon::ViewPayees, QStringLiteral("payee")}, - {Icon::ViewTags, QStringLiteral("bookmark-new")}, - {Icon::ViewCategories, QStringLiteral("categories")}, - {Icon::ViewSchedules, QStringLiteral("schedule")}, - {Icon::ViewAccounts, QStringLiteral("account")}, - {Icon::ViewInstitutions, QStringLiteral("institution")}, - {Icon::ViewHome, QStringLiteral("home")}, - {Icon::ViewClose, QStringLiteral("view-close")}, - {Icon::ViewBank, QStringLiteral("bank")}, - {Icon::ViewBankAccount, QStringLiteral("account")}, - {Icon::Budget, QStringLiteral("budget")}, - {Icon::ViewCalendar, QStringLiteral("view-calendar")}, - {Icon::ViewCurrencyList, QStringLiteral("view-currency-list")}, - {Icon::ViewUpcominEvents, QStringLiteral("view-calendar-upcoming-events")}, - {Icon::ViewCalendarDay, QStringLiteral("office-calendar")}, - {Icon::ViewFinancialTransfer, QStringLiteral("ledger")}, - {Icon::ViewFinancialCategories, QStringLiteral("categories")}, - {Icon::ViewFinancialList, QStringLiteral("ledger")}, - {Icon::ViewFilter, QStringLiteral("view-filter")}, - {Icon::SortAscending, QStringLiteral("go-up")}, - {Icon::SortDescending, QStringLiteral("go-down")}, - {Icon::HideCategories, QStringLiteral("hide-categories")}, - {Icon::HideReconciled, QStringLiteral("hide-reconciled")}, - {Icon::ViewTransactionDetail, QStringLiteral("edit-find")}, - {Icon::ZoomOut, QStringLiteral("zoom-out")}, - {Icon::ZoomIn, QStringLiteral("zoom-in")}, - {Icon::DocumentProperties, QStringLiteral("document-properties")}, - {Icon::TagRename, QStringLiteral("tag-rename")}, - {Icon::PayeeMerge, QStringLiteral("merge")}, - {Icon::PayeeRename, QStringLiteral("user-properties")}, - {Icon::UserProperties, QStringLiteral("system-users")}, - {Icon::Calculator, QStringLiteral("accessories-calculator")}, - {Icon::MailReceive, QStringLiteral("mail-receive")}, - {Icon::MailMessageNew, QStringLiteral("mail-message-new")}, - {Icon::MailMessage, QStringLiteral("internet-mail")}, - {Icon::OpenDatabase, QStringLiteral("svn-update")}, - {Icon::Split, QStringLiteral("transaction-split")}, - {Icon::Download, QStringLiteral("go-down")}, - {Icon::Tip, QStringLiteral("info")}, - {Icon::KMyMoney, QStringLiteral("kmymoney")}, - {Icon::Reconciled, QStringLiteral("reconciled")}, - {Icon::AccountClosed, QStringLiteral("account-types-closed")}, - {Icon::Unknown, QStringLiteral("unknown")}, - {Icon::Configure, QStringLiteral("configure")}, - {Icon::Report, QStringLiteral("application-vnd.oasis.opendocument.spreadsheet")}, - {Icon::ArrowUp, QStringLiteral("arrow-up")}, - {Icon::ArrowDown, QStringLiteral("arrow-down")}, - {Icon::ArrowLeft, QStringLiteral("arrow-left")}, - {Icon::ArrowRight, QStringLiteral("arrow-right")}, - {Icon::PreferencesGeneral, QStringLiteral("system-run")}, - {Icon::TaskAttention, QStringLiteral("task-attention")}, - {Icon::TaskOngoing, QStringLiteral("task-ongoing")}, - {Icon::TaskComplete, QStringLiteral("task-complete")}, - {Icon::TaskReject, QStringLiteral("task-reject")}, - {Icon::TaskAccepted, QStringLiteral("task-accepted")}, - {Icon::Help, QStringLiteral("help-contents")}, - {Icon::PreferencesNetwork, QStringLiteral("preferences-system-network")}, - {Icon::PreferencesColor, QStringLiteral("preferences-desktop-color")}, - {Icon::PreferencesFont, QStringLiteral("preferences-desktop-font")}, - {Icon::PreferencesIcon, QStringLiteral("preferences-desktop-icon")}, - {Icon::PreferencesPlugin, QStringLiteral("network-disconnect")}, - {Icon::Folder, QStringLiteral("folder")}, - {Icon::Reverse, QStringLiteral("reverse")}, - }; - } - - QHash getKDENames() + KMM_ICONS_EXPORT void setUpMappings(const QString& themeName) { - return { - {Icon::ViewHome, QStringLiteral("go-home")}, - {Icon::KeyEnter, QStringLiteral("key-enter")}, - {Icon::Split, QStringLiteral("split")}, - {Icon::Reconcile, QStringLiteral("merge")}, - {Icon::OfficeChartLine, QStringLiteral("office-chart-line")}, - {Icon::Merge, QStringLiteral("merge")}, - {Icon::ViewEquity, QStringLiteral("view-bank-account")}, - {Icon::ViewSaving, QStringLiteral("view-bank-account-savings")}, - {Icon::ViewChecking, QStringLiteral("view-bank-account-checking")}, - {Icon::ViewAsset, QStringLiteral("view-bank-account")}, - {Icon::ViewBank, QStringLiteral("view-bank")}, - {Icon::ViewBankAccount, QStringLiteral("view-bank-account")}, - {Icon::Budget, QStringLiteral("view-time-schedule-calculus")}, - {Icon::ViewBudgets, QStringLiteral("view-time-schedule-calculus")}, - {Icon::ViewCalendarDay, QStringLiteral("view-calendar-day")}, - {Icon::ViewTransactionDetail, QStringLiteral("zoom-in")}, - {Icon::ViewReports, QStringLiteral("office-chart-bar")}, - {Icon::ViewPayees, QStringLiteral("system-users")}, - {Icon::ViewTags, QStringLiteral("mail-tagged")}, - {Icon::ViewSchedules, QStringLiteral("view-pim-calendar")}, - {Icon::ViewAccounts, QStringLiteral("view-bank-account")}, - {Icon::ViewInstitutions, QStringLiteral("view-bank")}, - {Icon::ViewCategories, QStringLiteral("view-categories")}, - {Icon::ViewFinancialCategories, QStringLiteral("view-categories")}, - {Icon::UserProperties, QStringLiteral("user-properties")}, - {Icon::SortAscending, QStringLiteral("view-sort-ascending")}, - {Icon::SortDescending, QStringLiteral("view-sort-descending")}, - {Icon::Reconciled, QStringLiteral("flag-green")}, - {Icon::AccountClosed, QStringLiteral("dialog-close")}, - {Icon::MailMessage, QStringLiteral("mail-message")}, - {Icon::DocumentImport, QStringLiteral("document-import")}, - {Icon::DocumentExport, QStringLiteral("document-export")} - }; - } - - QHash getOxygenNames() - { - return { - {Icon::Download, QStringLiteral("download")}, - {Icon::Tip, QStringLiteral("ktip")}, - {Icon::ViewExpense, QStringLiteral("view-expenses-categories")}, - {Icon::ViewIncome, QStringLiteral("view-income-categories")}, - {Icon::ViewCreditCard, QStringLiteral("view-credit-card-account")}, - {Icon::ViewLoan, QStringLiteral("view-loan")}, - {Icon::ViewLoanAsset, QStringLiteral("view-loan-asset")}, - {Icon::ViewStock, QStringLiteral("view-stock-account")}, - {Icon::ViewLiability, QStringLiteral("view-loan")}, - {Icon::ViewForecast, QStringLiteral("view-financial-forecast")}, - {Icon::ViewInvestments, QStringLiteral("view-investment")}, - {Icon::ViewLedgers, QStringLiteral("view-financial-list")}, - {Icon::ViewCategories, QStringLiteral("view-financial-categories")}, - {Icon::ViewFinancialCategories, QStringLiteral("view-financial-categories")}, - {Icon::ViewFinancialTransfer, QStringLiteral("view-financial-transfer")}, - {Icon::ViewFinancialList, QStringLiteral("view-financial-list")}, - {Icon::Refresh, QStringLiteral("refresh")}, - }; - } - - QHash getBreezeNames() - { - return { - {Icon::ViewExpense, QStringLiteral("view-categories-expenditures")}, - {Icon::ViewIncome, QStringLiteral("view-categories-incomes")}, - {Icon::ViewCreditCard, QStringLiteral("skrooge_credit_card")}, - {Icon::Download, QStringLiteral("edit-download")}, - {Icon::Refresh, QStringLiteral("view-refresh")} - }; - } - - QHash getTangoNames() - { - return { - {Icon::OfficeChartLine, QStringLiteral("report-line")}, - {Icon::ListCollapse, QStringLiteral("list-remove")}, - {Icon::ListExpand, QStringLiteral("list-add")}, - {Icon::DocumentEdit, QStringLiteral("text-editor")}, - {Icon::DialogCancel, QStringLiteral("stop")}, - {Icon::DialogOK, QStringLiteral("finish")}, - {Icon::EditRename, QStringLiteral("text-editor")}, - {Icon::DocumentClose, QStringLiteral("stop")}, - {Icon::Configure, QStringLiteral("preferences-system")}, - {Icon::ArrowUp, QStringLiteral("go-up")}, - {Icon::ArrowDown, QStringLiteral("go-down")}, - {Icon::ArrowLeft, QStringLiteral("go-previous")}, - {Icon::ArrowRight, QStringLiteral("go-next")}, - {Icon::PreferencesGeneral, QStringLiteral("media-playback-start")}, - {Icon::TaskAttention, QStringLiteral("dialog-warning")} - }; - } - - KMM_ICONS_EXPORT void setIconThemeNames(const QString &_themeName) - { - sStandardIcons = getCommonNames(); - auto hasIconsResource = false; - -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - hasIconsResource = true; -#endif - - QStringList kdeThemes {QStringLiteral("oxygen"), QStringLiteral("breeze"), QStringLiteral("breeze-dark")}; - QHash iconNames; - - if (kdeThemes.contains(_themeName) || hasIconsResource) { // on Craft build system there is breeze icon theme, but it's in no way discoverable - iconNames = getKDENames(); - for (auto it = iconNames.cbegin(); it != iconNames.cend(); ++it) - sStandardIcons.insert(it.key(), it.value()); + for (auto iconDef = iconMappings.cbegin(); iconDef != iconMappings.cend(); ++iconDef) { + const auto icon = iconDef.key(); + for (auto mapping = iconDef.value().cbegin(); mapping != iconDef->cend(); ++mapping) + { + switch (mapping.key()) + { + case (IconSet::Oxygen): + if (themeName.contains(QStringLiteral("oxygen"), Qt::CaseInsensitive)) { + sStandardIcons.insert(icon, mapping.value()); + continue; + } + break; + case (IconSet::Tango): + if (themeName.contains(QStringLiteral("tango"), Qt::CaseInsensitive)) { + sStandardIcons.insert(icon, mapping.value()); + continue; + } + break; + case (IconSet::Breeze): + if (themeName.contains(QStringLiteral("breeze"), Qt::CaseInsensitive)) { + sStandardIcons.insert(icon, mapping.value()); + continue; + } + break; + case (IconSet::Common): + sStandardIcons.insert(icon, mapping.value()); + break; + } + } } - - // get icon replacements for specific theme - if (_themeName == kdeThemes.at(0)) - iconNames = getOxygenNames(); - else if (_themeName == kdeThemes.at(1) || _themeName == kdeThemes.at(2) || hasIconsResource) - iconNames = getBreezeNames(); - else if (_themeName == QLatin1String("Tango")) - iconNames = getTangoNames(); - else - return; - - for (auto it = iconNames.cbegin(); it != iconNames.cend(); ++it) - sStandardIcons.insert(it.key(), it.value()); } /** * This method overlays an icon over another one, to get a composite one * eg. an icon to add accounts */ QIcon overlayIcon(iconDescription description, const int size = 64) { const auto iconName = sStandardIcons[description.baseIcon]; const auto overlayName = sStandardIcons[description.overlayIcon]; const auto corner = description.corner; QPixmap pxIcon; QString kyIcon = iconName + overlayName; // If found in the cache, return quickly if (QPixmapCache::find(kyIcon, pxIcon)) return pxIcon; // try to retrieve the main icon from cache if (!QPixmapCache::find(iconName, pxIcon)) { pxIcon = QIcon::fromTheme(iconName).pixmap(size); QPixmapCache::insert(iconName, pxIcon); } if (overlayName.isEmpty()) // call from MyMoneyAccount::accountPixmap can have no overlay icon, so handle that appropriately return pxIcon; QPainter pixmapPainter(&pxIcon); QPixmap pxOverlay = QIcon::fromTheme(overlayName).pixmap(size); int x, y; switch (corner) { case Qt::TopLeftCorner: x = 0; y = 0; break; case Qt::TopRightCorner: x = pxIcon.width() / 2; y = 0; break; case Qt::BottomLeftCorner: x = 0; y = pxIcon.height() / 2; break; case Qt::BottomRightCorner: default: x = pxIcon.width() / 2; y = pxIcon.height() / 2; break; } pixmapPainter.drawPixmap(x, y, pxIcon.width() / 2, pxIcon.height() / 2, pxOverlay); //save for later use QPixmapCache::insert(kyIcon, pxIcon); return pxIcon; } KMM_ICONS_EXPORT QIcon get(Icon icon) { if (sComposedIcons.contains(icon)) return overlayIcon(sComposedIcons[icon]); return QIcon::fromTheme(sStandardIcons[icon]); } QString iconCacheDir() { - const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); if (QDir::root().mkpath(cachePath)) { return cachePath; } return QString(); } KMM_ICONS_EXPORT bool storeIconInApplicationCache(const QString& name, const QIcon& icon) { // split the icon name from the type QRegularExpression iconPath(QStringLiteral("^(?[a-zA-Z]+):(?.+)"), QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch matcher = iconPath.match(name); if (matcher.hasMatch()) { if (matcher.captured(QStringLiteral("type")).compare(QLatin1String("enum")) == 0) { return true; } else { const QString cacheDir = iconCacheDir(); if (!cacheDir.isEmpty()) { return icon.pixmap(16).save(QString::fromLatin1("%1/%2-%3").arg(cacheDir, matcher.captured(QStringLiteral("type")), matcher.captured(QStringLiteral("name"))), "PNG"); } } } return false; } KMM_ICONS_EXPORT QIcon loadIconFromApplicationCache(const QString& name) { const QHash sEnumIcons { - { QStringLiteral("ViewBank"), Icon::ViewBank }, + { QStringLiteral("Bank"), Icon::Bank }, }; // split the icon name from the type QRegularExpression iconPath(QStringLiteral("^(?[a-zA-Z]+):(?.+)"), QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch matcher = iconPath.match(name); if (matcher.hasMatch()) { if (matcher.captured(QStringLiteral("type")).compare(QLatin1String("enum")) == 0) { // type is enum, so we use our own set of icons const QString iconName = matcher.captured(QStringLiteral("name")); if (sEnumIcons.contains(iconName)) { return get(sEnumIcons[iconName]); } } else { // otherwise, we use the type as part of the filename const QString cacheDir = iconCacheDir(); if (!cacheDir.isEmpty()) { const QString filename = QString::fromLatin1("%1/%2-%3").arg(cacheDir, matcher.captured(QStringLiteral("type")), matcher.captured(QStringLiteral("name"))); if (QFile::exists(filename)) { return QIcon(filename); } } } } return QIcon(); } } diff --git a/kmymoney/icons/icons.h b/kmymoney/icons/icons.h index 56b883b9e..b1439d823 100644 --- a/kmymoney/icons/icons.h +++ b/kmymoney/icons/icons.h @@ -1,130 +1,135 @@ /*************************************************************************** icons.h ------------------- begin : Sun Jun 25 2017 copyright : (C) 2017 by Łukasz Wojniłowicz + (C) 2020 by Dawid Wróbel + ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef ICONS_H #define ICONS_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes class QString; class QIcon; namespace Icons { +enum class IconSet { Common, Oxygen, Tango, Breeze }; + enum class Icon { OpenDatabase, Merge, Reconcile, Split, Tip, PerformanceTest, Calculator, UserProperties, DocumentProperties, ZoomIn, ZoomOut, Pause, SeekForward, SkipForward, HideReconciled, HideCategories, - ViewHome, ViewInstitutions, - ViewAccounts, ViewCategories, - ViewSchedules, ViewTags, - ViewPayees, ViewLedgers, - ViewInvestments, ViewReports, - ViewBudgets, ViewForecast, - ViewOutbox, ViewFilter, - ViewLoan, ViewStock, - ViewChecking, - ViewSaving, - ViewLoanAsset, ViewCreditCard, - ViewCash, ViewEquity, - ViewIncome, ViewExpense, - ViewAsset, ViewLiability, - ViewUpcominEvents, ViewCalendarDay, - ViewFinancialList, ViewBankAccount, - ViewCurrencyList, ViewFinancialCategories, - ViewFinancialTransfer, ViewBank, - Budget, ViewCalendar, - ViewTransactionDetail, ViewClose, + Home, Institution, Institutions, + Accounts, + Schedule, Tags, + Payees, + Investment, Investments, Reports, + Budget, Forecast, + OnlineJobOutbox, Filter, + Loan, Stock, + Checking, + Savings, + LoanAsset, CreditCard, + Cash, Equity, + Income, Expense, + Asset, Liability, + UpcomingEvents, CalendarDay, + Ledger, BankAccount, + Currencies, FinancialCategories, + Transaction, Bank, + Calendar, + TransactionDetails, Close, DialogOK, DialogClose, DialogCancel, DialogOKApply, DialogError, DialogWarning, DialogInformation, ListExpand, ListCollapse, ListAdd, ListAddUser, ListRemoveUser, ListAddTag, ListRemoveTag, GoTo, KeyEnter, Download, TagRename, EditDelete, EditCopy, EditRename, - EditFind, EditUndo, EditClear, + Find, EditUndo, EditClear, DocumentEdit, DocumentNew, DocumentSave, DocumentClose, DocumentOpen, DocumentImport, DocumentExport, OfficeChartLine, MailMessageNew, MailMessage, MailReceive, MapOnlineAccount, UnmapOnlineAccount, NewSchedule, KMyMoney, PayeeRename, PayeeMerge, Configure, Reconciled, AccountClosed, Unknown, Report, Refresh, PreferencesGeneral, SortAscending, SortDescending, ArrowUp, ArrowDown, ArrowRight, ArrowLeft, - TaskAttention, TaskComplete, + Warning, TaskComplete, TaskReject, TaskAccepted, TaskOngoing, Help, Folder, - PreferencesFont, PreferencesColor, PreferencesIcon, - PreferencesNetwork, PreferencesPlugin, - Empty, EditFindTransaction, + PreferencesFonts, PreferencesColors, PreferencesIcons, + PreferencesNetwork, PreferencesPlugins, + Empty, InstitutionNew, InstitutionEdit, InstitutionDelete, AccountNew, AccountEdit, AccountDelete, AccountClose, AccountReopen, - AccountUpdateMenu, AccountUpdate, AccountUpdateAll, + AccountUpdate, AccountUpdateAll, AccountCreditTransfer, AccountFinishReconciliation, CategoryNew, CategoryEdit, CategoryDelete, TransactionNew, TransactionEdit, TransactionMatch, TransactionAccept, InvestmentNew, InvestmentEdit, InvestmentDelete, InvestmentOnlinePrice, BudgetNew, BudgetRename, BudgetDelete, BudgetCopy, - PriceUpdate, ToolUpdatePrices, Reverse + PriceUpdate, InvestmentOnlinePriceAll, Reverse, Visibility, NoVisibility, + SelectAll }; -KMM_ICONS_EXPORT void setIconThemeNames(const QString &_themeName); +KMM_ICONS_EXPORT void setUpMappings(const QString & themeName); KMM_ICONS_EXPORT QIcon get(Icons::Icon icon); /** * return an icon from the application local cache or an icon provided * by the application. The @a name is formatted as @c type:iconName. * The following types are supported * * - enum * - favicon * * @sa storeIconInApplicationCache(const QString& name, const QIcon& icon) */ KMM_ICONS_EXPORT QIcon loadIconFromApplicationCache(const QString& name); /** * store the @a icon in the applications local cache directory under the given @a name. * The @a name is formatted as @c type:iconName. * The icon will be stored in the file "type-iconName". * * @sa loadIconFromApplicationCache(const QString& name) */ KMM_ICONS_EXPORT bool storeIconInApplicationCache(const QString& name, const QIcon& icon); } #endif diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index a0cce8611..ca7ea5b32 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,3673 +1,3674 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002-2020 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 #include "kmymoney.h" // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_HOLIDAYS #include #include #endif #ifdef ENABLE_ACTIVITIES #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneysettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/knewbankdlg.h" #include "dialogs/ksaveasquestion.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/ktemplateexportdlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/kmymoneyaccountselector.h" #include "widgets/kmymoneypayeecombo.h" #include "widgets/amountedit.h" #include "widgets/kmymoneymvccombo.h" #include "views/kmymoneyview.h" #include "models/models.h" #include "models/accountsmodel.h" #include "models/equitiesmodel.h" #include "models/securitiesmodel.h" #ifdef ENABLE_UNFINISHEDFEATURES #include "models/ledgermodel.h" #endif #include "mymoney/mymoneyobject.h" #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyinstitution.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneyaccountloan.h" #include "mymoney/mymoneysecurity.h" #include "mymoney/mymoneypayee.h" #include "mymoney/mymoneyprice.h" #include "mymoney/mymoneytag.h" #include "mymoney/mymoneybudget.h" #include "mymoney/mymoneyreport.h" #include "mymoney/mymoneysplit.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/mymoneytransactionfilter.h" #include "mymoneyexception.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmappinterface.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "kmymoneyplugin.h" #include "tasks/credittransfer.h" #include "icons/icons.h" #include "misc/webconnect.h" #include "storage/mymoneystoragemgr.h" #include "imymoneystorageformat.h" #include "transactioneditor.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" #include "storageenums.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "viewenums.h" #include "menuenums.h" #include "kmymoneyenums.h" #include "platformtools.h" #include "kmm_printer.h" #ifdef ENABLE_SQLCIPHER #include "sqlcipher/sqlite3.h" #endif #ifdef KMM_DEBUG #include "mymoney/storage/mymoneystoragedump.h" #include "mymoneytracer.h" #endif using namespace Icons; using namespace eMenu; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: explicit Private(KMyMoneyApp *app) : q(app), m_backupState(backupStateE::BACKUP_IDLE), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(nullptr), m_startDialog(false), m_progressBar(nullptr), m_statusLabel(nullptr), m_autoSaveEnabled(true), m_autoSaveTimer(nullptr), m_progressTimer(nullptr), m_autoSavePeriod(0), m_inAutoSaving(false), m_recentFiles(nullptr), #ifdef ENABLE_HOLIDAYS m_holidayRegion(nullptr), #endif #ifdef ENABLE_ACTIVITIES m_activityResourceInstance(nullptr), #endif m_applicationIsReady(true), m_webConnect(new WebConnect(app)) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); struct storageInfo { eKMyMoney::StorageType type {eKMyMoney::StorageType::None}; bool isOpened {false}; QUrl url; }; storageInfo m_storageInfo; /** * The public interface. */ KMyMoneyApp * const q; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; KRecentFilesAction* m_recentFiles; #ifdef ENABLE_HOLIDAYS // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif #ifdef ENABLE_ACTIVITIES KActivities::ResourceInstance * m_activityResourceInstance; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; WebConnect* m_webConnect; // methods void consistencyCheck(bool alwaysDisplayResults); static void setThemedCSS(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const { auto file = MyMoneyFile::instance(); if (_acc.name() != name) { MyMoneyAccount acc(_acc); acc.setName(name); file->modifyAccount(acc); } } /** * This method updates names of currencies from file to localized names */ void updateCurrencyNames() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QList storedCurrencies = MyMoneyFile::instance()->currencyList(); QList availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); QStringList currencyIDs; foreach (auto currency, availableCurrencies) currencyIDs.append(currency.id()); try { foreach (auto currency, storedCurrencies) { int i = currencyIDs.indexOf(currency.id()); if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { currency.setName(availableCurrencies.at(i).name()); file->modifyCurrency(currency); } } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s updating currency names", e.what()); } } void updateAccountNames() { // make sure we setup the name of the base accounts in translated form try { MyMoneyFileTransaction ft; const auto file = MyMoneyFile::instance(); checkAccountName(file->asset(), i18n("Asset")); checkAccountName(file->liability(), i18n("Liability")); checkAccountName(file->income(), i18n("Income")); checkAccountName(file->expense(), i18n("Expense")); checkAccountName(file->equity(), i18n("Equity")); ft.commit(); } catch (const MyMoneyException &) { } } void ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } bool applyFileFixes() { const auto blocked = MyMoneyFile::instance()->blockSignals(true); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("General Options"); // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true if (grp.readEntry("SkipFix", false) != true) { MyMoneyFileTransaction ft; try { // Check if we have to modify the file before we allow to work with it auto s = MyMoneyFile::instance()->storage(); while (s->fileFixVersion() < s->currentFixVersion()) { qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); switch (s->fileFixVersion()) { case 0: fixFile_0(); s->setFileFixVersion(1); break; case 1: fixFile_1(); s->setFileFixVersion(2); break; case 2: fixFile_2(); s->setFileFixVersion(3); break; case 3: fixFile_3(); s->setFileFixVersion(4); break; case 4: fixFile_4(); s->setFileFixVersion(5); break; // add new levels above. Don't forget to increase currentFixVersion() for all // the storage backends this fix applies to default: throw MYMONEYEXCEPTION(QString::fromLatin1("Unknown fix level in input file")); } } ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } } else { qDebug("Skipping automatic transaction fix!"); } MyMoneyFile::instance()->blockSignals(blocked); return true; } void connectStorageToModels() { const auto file = MyMoneyFile::instance(); const auto accountsModel = Models::instance()->accountsModel(); q->connect(file, &MyMoneyFile::objectAdded, accountsModel, &AccountsModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, accountsModel, &AccountsModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, accountsModel, &AccountsModel::slotObjectRemoved); q->connect(file, &MyMoneyFile::balanceChanged, accountsModel, &AccountsModel::slotBalanceOrValueChanged); q->connect(file, &MyMoneyFile::valueChanged, accountsModel, &AccountsModel::slotBalanceOrValueChanged); const auto institutionsModel = Models::instance()->institutionsModel(); q->connect(file, &MyMoneyFile::objectAdded, institutionsModel, &InstitutionsModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, institutionsModel, &InstitutionsModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, institutionsModel, &InstitutionsModel::slotObjectRemoved); q->connect(file, &MyMoneyFile::balanceChanged, institutionsModel, &AccountsModel::slotBalanceOrValueChanged); q->connect(file, &MyMoneyFile::valueChanged, institutionsModel, &AccountsModel::slotBalanceOrValueChanged); const auto equitiesModel = Models::instance()->equitiesModel(); q->connect(file, &MyMoneyFile::objectAdded, equitiesModel, &EquitiesModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, equitiesModel, &EquitiesModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, equitiesModel, &EquitiesModel::slotObjectRemoved); q->connect(file, &MyMoneyFile::balanceChanged, equitiesModel, &EquitiesModel::slotBalanceOrValueChanged); q->connect(file, &MyMoneyFile::valueChanged, equitiesModel, &EquitiesModel::slotBalanceOrValueChanged); const auto securitiesModel = Models::instance()->securitiesModel(); q->connect(file, &MyMoneyFile::objectAdded, securitiesModel, &SecuritiesModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, securitiesModel, &SecuritiesModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, securitiesModel, &SecuritiesModel::slotObjectRemoved); #ifdef ENABLE_UNFINISHEDFEATURES const auto ledgerModel = Models::instance()->ledgerModel(); q->connect(file, &MyMoneyFile::objectAdded, ledgerModel, &LedgerModel::slotAddTransaction); q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifyTransaction); q->connect(file, &MyMoneyFile::objectRemoved, ledgerModel, &LedgerModel::slotRemoveTransaction); q->connect(file, &MyMoneyFile::objectAdded, ledgerModel, &LedgerModel::slotAddSchedule); q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifySchedule); q->connect(file, &MyMoneyFile::objectRemoved, ledgerModel, &LedgerModel::slotRemoveSchedule); #endif } void disconnectStorageFromModels() { const auto file = MyMoneyFile::instance(); q->disconnect(file, nullptr, Models::instance()->accountsModel(), nullptr); q->disconnect(file, nullptr, Models::instance()->institutionsModel(), nullptr); q->disconnect(file, nullptr, Models::instance()->equitiesModel(), nullptr); q->disconnect(file, nullptr, Models::instance()->securitiesModel(), nullptr); #ifdef ENABLE_UNFINISHEDFEATURES q->disconnect(file, nullptr, Models::instance()->ledgerModel(), nullptr); #endif } bool askAboutSaving() { const auto isFileNotSaved = q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->isEnabled(); const auto isNewFileNotSaved = m_storageInfo.isOpened && m_storageInfo.url.isEmpty(); auto fileNeedsToBeSaved = false; if (isFileNotSaved && KMyMoneySettings::autoSaveOnClose()) { fileNeedsToBeSaved = true; } else if (isFileNotSaved || isNewFileNotSaved) { switch (KMessageBox::warningYesNoCancel(q, i18n("The file has been changed, save it?"))) { case KMessageBox::ButtonCode::Yes: fileNeedsToBeSaved = true; break; case KMessageBox::ButtonCode::No: fileNeedsToBeSaved = false; break; case KMessageBox::ButtonCode::Cancel: default: return false; break; } } if (fileNeedsToBeSaved) { if (isFileNotSaved) return q->slotFileSave(); else if (isNewFileNotSaved) return q->slotFileSaveAs(); } return true; } /** * This method attaches an empty storage object to the MyMoneyFile * object. It calls removeStorage() to remove a possibly attached * storage object. */ void newStorage() { removeStorage(); auto file = MyMoneyFile::instance(); file->attachStorage(new MyMoneyStorageMgr); } /** * This method removes an attached storage from the MyMoneyFile * object. */ void removeStorage() { auto file = MyMoneyFile::instance(); auto p = file->storage(); if (p) { file->detachStorage(p); delete p; } } /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency() { auto file = MyMoneyFile::instance(); // check if we have a base currency. If not, we need to select one QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", e.what()); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(q); // connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); dlg->exec(); delete dlg; } try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", e.what()); } if (!baseId.isEmpty()) { // check that all accounts have a currency QList list; file->accountList(list); QList::Iterator it; // don't forget those standard accounts list << file->asset(); list << file->liability(); list << file->income(); list << file->expense(); list << file->equity(); for (it = list.begin(); it != list.end(); ++it) { QString cid; try { if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); } catch (const MyMoneyException &e) { qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what(); } if (cid.isEmpty()) { (*it).setCurrencyId(baseId); MyMoneyFileTransaction ft; try { file->modifyAccount(*it); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), e.what()); } } } } } /** * Call this to see if the MyMoneyFile contains any unsaved data. * * @retval true if any data has been modified but not saved * @retval false otherwise */ bool dirty() { if (!m_storageInfo.isOpened) return false; return MyMoneyFile::instance()->dirty(); } /* DO NOT ADD code to this function or any of it's called ones. Instead, create a new function, fixFile_n, and modify the initializeStorage() logic above to call it */ void fixFile_4() { auto file = MyMoneyFile::instance(); QList currencies = file->currencyList(); static const QStringList symbols = { QStringLiteral("XAU"), QStringLiteral("XPD"), QStringLiteral("XPT"), QStringLiteral("XAG") }; foreach(auto currency, currencies) { if (symbols.contains(currency.id())) { if (currency.smallestAccountFraction() != currency.smallestCashFraction()) { currency.setSmallestAccountFraction(currency.smallestCashFraction()); file->modifyCurrency(currency); } } } } void fixFile_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void fixFile_2() { auto file = MyMoneyFile::instance(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); // scan the transactions and modify transactions with two splits // which reference an account and a category to have the memo text // of the account. auto count = 0; foreach (const auto transaction, transactionList) { if (transaction.splitCount() == 2) { QString accountId; QString categoryId; QString accountMemo; QString categoryMemo; foreach (const auto split, transaction.splits()) { auto acc = file->account(split.accountId()); if (acc.isIncomeExpense()) { categoryId = split.id(); categoryMemo = split.memo(); } else { accountId = split.id(); accountMemo = split.memo(); } } if (!accountId.isEmpty() && !categoryId.isEmpty() && accountMemo != categoryMemo) { MyMoneyTransaction t(transaction); MyMoneySplit s(t.splitById(categoryId)); s.setMemo(accountMemo); t.modifySplit(s); file->modifyTransaction(t); ++count; } } } qDebug("%d transactions fixed in fixFile_2", count); } void fixFile_1() { // we need to fix reports. If the account filter list contains // investment accounts, we need to add the stock accounts to the list // as well if we don't have the expert mode enabled if (!KMyMoneySettings::expertMode()) { try { QList reports = MyMoneyFile::instance()->reportList(); QList::iterator it_r; for (it_r = reports.begin(); it_r != reports.end(); ++it_r) { QStringList list; (*it_r).accounts(list); QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == eMyMoney::Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } if (!missing.isEmpty()) { (*it_r).addAccount(missing); MyMoneyFile::instance()->modifyReport(*it_r); } } } catch (const MyMoneyException &) { } } } #if 0 if (!m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; m_accountsView->selectedItems(list); // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected if (!KMyMoneySettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.begin(); it_a != list.end(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } list += missing; } m_filter.addAccount(list); } #endif void fixFile_0() { /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the * file, it is really a warning. So I'm going to print a debug warning, and * then go track them down when I see them to figure out how they got saved * out needing fixing anyway. */ auto file = MyMoneyFile::instance(); QList accountList; file->accountList(accountList); QList::Iterator it_a; QList scheduleList = file->scheduleList(); QList::Iterator it_s; MyMoneyAccount equity = file->equity(); MyMoneyAccount asset = file->asset(); bool equityListEmpty = equity.accountList().count() == 0; for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { if ((*it_a).accountType() == eMyMoney::Account::Type::Loan || (*it_a).accountType() == eMyMoney::Account::Type::AssetLoan) { fixLoanAccount_0(*it_a); } // until early before 0.8 release, the equity account was not saved to // the file. If we have an equity account with no sub-accounts but // find and equity account that has equity() as it's parent, we reparent // this account. Need to move it to asset() first, because otherwise // MyMoneyFile::reparent would act as NOP. if (equityListEmpty && (*it_a).accountType() == eMyMoney::Account::Type::Equity) { if ((*it_a).parentAccountId() == equity.id()) { auto acc = *it_a; // tricky, force parent account to be empty so that we really // can re-parent it acc.setParentAccountId(QString()); file->reparentAccount(acc, equity); qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); } } } for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { fixSchedule_0(*it_s); } fixTransactions_0(); } void fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; try { bool updated = false; // Check if the splits contain valid data and set it to // be valid. for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) { // the first split is always the account on which this transaction operates // and if the transaction commodity is not set, we take this if (it_s == splitList.constBegin() && t.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; try { auto acc = MyMoneyFile::instance()->account((*it_s).accountId()); t.setCommodity(acc.currencyId()); updated = true; } catch (const MyMoneyException &) { } } // make sure the account exists. If not, remove the split try { MyMoneyFile::instance()->account((*it_s).accountId()); } catch (const MyMoneyException &) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; t.removeSplit(*it_s); updated = true; } if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; MyMoneySplit split = *it_s; split.setReconcileDate(QDate()); split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); t.modifySplit(split); updated = true; } // the schedule logic used to operate only on the value field. // This is now obsolete. if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) { MyMoneySplit split = *it_s; split.setShares(split.value()); t.modifySplit(split); updated = true; } } // If there have been changes, update the schedule and // the engine data. if (updated) { sched.setTransaction(t); MyMoneyFile::instance()->modifySchedule(sched); } } catch (const MyMoneyException &e) { qWarning("Unable to update broken schedule: %s", e.what()); } } void fixLoanAccount_0(MyMoneyAccount acc) { if (acc.value("final-payment").isEmpty() || acc.value("term").isEmpty() || acc.value("periodic-payment").isEmpty() || acc.value("loan-amount").isEmpty() || acc.value("interest-calculation").isEmpty() || acc.value("schedule").isEmpty() || acc.value("fixed-interest").isEmpty()) { KMessageBox::information(q, i18n("

The account \"%1\" was previously created as loan account but some information is missing.

The new loan wizard will be started to collect all relevant information.

Please use KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.

" , acc.name()), i18n("Account problem")); throw MYMONEYEXCEPTION_CSTRING("Fix LoanAccount0 not supported anymore"); } } void fixTransactions_0() { auto file = MyMoneyFile::instance(); QList scheduleList = file->scheduleList(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); QList::Iterator it_x; QStringList interestAccounts; KMSTATUS(i18n("Fix transactions")); q->slotStatusProgressBar(0, scheduleList.count() + transactionList.count()); int cnt = 0; // scan the schedules to find interest accounts for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) { MyMoneyTransaction t = (*it_x).transaction(); QList::ConstIterator it_s; QStringList accounts; bool hasDuplicateAccounts = false; foreach (const auto split, t.splits()) { if (accounts.contains(split.accountId())) { hasDuplicateAccounts = true; qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId(); } else { accounts << split.accountId(); } if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { if (interestAccounts.contains(split.accountId()) == 0) { interestAccounts << split.accountId(); } } } if (hasDuplicateAccounts) { fixDuplicateAccounts_0(t); } ++cnt; if (!(cnt % 10)) q->slotStatusProgressBar(cnt); } // scan the transactions and modify loan transactions for (auto& transaction : transactionList) { QString defaultAction; QList splits = transaction.splits(); QStringList accounts; // check if base commodity is set. if not, set baseCurrency if (transaction.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency"; transaction.setCommodity(file->baseCurrency().id()); file->modifyTransaction(transaction); } bool isLoan = false; // Determine default action if (transaction.splitCount() == 2) { // check for transfer int accountCount = 0; MyMoneyMoney val; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); if (acc.accountGroup() == eMyMoney::Account::Type::Asset || acc.accountGroup() == eMyMoney::Account::Type::Liability) { val = split.value(); accountCount++; if (acc.accountType() == eMyMoney::Account::Type::Loan || acc.accountType() == eMyMoney::Account::Type::AssetLoan) isLoan = true; } else break; } if (accountCount == 2) { if (isLoan) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer); } else { if (val.isNegative()) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); } } isLoan = false; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); MyMoneyMoney val = split.value(); if (acc.accountGroup() == eMyMoney::Account::Type::Asset || acc.accountGroup() == eMyMoney::Account::Type::Liability) { if (!val.isPositive()) { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); break; } else { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); break; } } } #if 0 // Check for correct actions in transactions referencing credit cards bool needModify = false; // The action fields are actually not used anymore in the ledger view logic // so we might as well skip this whole thing here! for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) { auto acc = file->account((*it_s).accountId()); MyMoneyMoney val = (*it_s).value(); if (acc.accountType() == Account::Type::CreditCard) { if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; } } // (Ace) Extended the #endif down to cover this conditional, because as-written // it will ALWAYS be skipped. if (needModify == true) { for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { (*it_s).setAction(defaultAction); transaction.modifySplit(*it_s); file->modifyTransaction(transaction); } splits = transaction.splits(); // update local copy qDebug("Fixed credit card assignment in %s", transaction.id().data()); } #endif // Check for correct assignment of ActionInterest in all splits // and check if there are any duplicates in this transactions for (auto& split : splits) { MyMoneyAccount splitAccount = file->account(split.accountId()); if (!accounts.contains(split.accountId())) { accounts << split.accountId(); } // if this split references an interest account, the action // must be of type ActionInterest if (interestAccounts.contains(split.accountId())) { if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest"; split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } // if it does not reference an interest account, it must not be // of type ActionInterest } else { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest"; split.setAction(defaultAction); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } } // check that for splits referencing an account that has // the same currency as the transactions commodity the value // and shares field are the same. if (transaction.commodity() == splitAccount.currencyId() && split.value() != split.shares()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value"; split.setShares(split.value()); transaction.modifySplit(split); file->modifyTransaction(transaction); } // fix the shares and values to have the correct fraction if (!splitAccount.isInvest()) { try { int fract = splitAccount.fraction(); if (split.shares() != split.shares().convert(fract)) { qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id())); split.setShares(split.shares().convert(fract)); split.setValue(split.value().convert(fract)); transaction.modifySplit(split); file->modifyTransaction(transaction); } } catch (const MyMoneyException &) { qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); } } } ++cnt; if (!(cnt % 10)) q->slotStatusProgressBar(cnt); } q->slotStatusProgressBar(-1, -1); } void fixDuplicateAccounts_0(MyMoneyTransaction& t) { qDebug("Duplicate account in transaction %s", qPrintable(t.id())); } /** * This method is used to update the caption of the application window. * It sets the caption to "filename [modified] - KMyMoney". * * @param skipActions if true, the actions will not be updated. This * is usually onyl required by some early calls when * these widgets are not yet created (the default is false). */ void updateCaption(); void updateActions(); bool canFileSaveAs() const; bool canUpdateAllAccounts() const; void fileAction(eKMyMoney::FileAction action); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney-" + QString::number(platformTools::processId()), QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); #ifdef ENABLE_SQLCIPHER /* Issues: * 1) libsqlite3 loads implicitly before libsqlcipher * thus making the second one loaded but non-functional, * 2) libsqlite3 gets linked into kmymoney target implicitly * and it's not possible to unload or unlink it explicitly * * Solution: * Use e.g. dummy sqlite3_key call, so that libsqlcipher gets loaded implicitly before libsqlite3 * thus making the first one functional. * * Additional info: * 1) loading libsqlcipher explicitly doesn't solve the issue, * 2) using sqlite3_key only in sqlstorage plugin doesn't solve the issue, * 3) in a separate, minimal test case, loading libsqlite3 explicitly * with QLibrary::ExportExternalSymbolsHint makes libsqlcipher non-functional */ sqlite3_key(nullptr, nullptr, 0); #endif // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setThemedCSS(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); QFrame* frame = new QFrame; frame->setFrameStyle(QFrame::NoFrame); // values for margin (11) and spacing(6) taken from KDialog implementation QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(6); initIcons(); initStatusBar(); pActions = initActions(); pMenus = initMenus(); d->m_myMoneyView = new KMyMoneyView; layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg); connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar); // Initialize kactivities resource instance #ifdef ENABLE_ACTIVITIES d->m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this); #endif const auto viewActions = d->m_myMoneyView->actionsToBeConnected(); actionCollection()->addActions(viewActions.values()); for (auto it = viewActions.cbegin(); it != viewActions.cend(); ++it) pActions.insert(it.key(), it.value()); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, pPlugins, this, guiFactory()); onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended); d->m_myMoneyView->setOnlinePlugins(pPlugins.online); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(pActions[Action::ViewTransactionDetail], &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); d->m_backupState = BACKUP_IDLE; QLocale locale; for (auto const& weekDay: locale.weekdays()) { d->m_processingDays.setBit(static_cast(weekDay)); } d->m_autoSaveTimer = new QTimer(this); d->m_progressTimer = new QTimer(this); connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone())); // connect the WebConnect server connect(d->m_webConnect, &WebConnect::gotUrl, this, &KMyMoneyApp::webConnectUrl); // setup the initial configuration slotUpdateConfiguration(QString()); // kickstart date change timer slotDateChanged(); d->fileAction(eKMyMoney::FileAction::Closed); } KMyMoneyApp::~KMyMoneyApp() { // delete cached objects since they are in the way // when unloading the plugins onlineJobAdministration::instance()->clearCaches(); // we need to unload all plugins before we destroy anything else KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, pPlugins, this, guiFactory()); d->removeStorage(); #ifdef ENABLE_HOLIDAYS delete d->m_holidayRegion; #endif #ifdef ENABLE_ACTIVITIES delete d->m_activityResourceInstance; #endif // destroy printer object KMyMoneyPrinter::cleanup(); // make sure all settings are written to disk KMyMoneySettings::self()->save(); delete d; } QUrl KMyMoneyApp::lastOpenedURL() { QUrl url = d->m_startDialog ? QUrl() : d->m_storageInfo.url; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } QHash KMyMoneyApp::initMenus() { QHash lutMenus; const QHash menuNames { {Menu::Institution, QStringLiteral("institution_context_menu")}, {Menu::Account, QStringLiteral("account_context_menu")}, {Menu::Schedule, QStringLiteral("schedule_context_menu")}, {Menu::Category, QStringLiteral("category_context_menu")}, {Menu::Tag, QStringLiteral("tag_context_menu")}, {Menu::Payee, QStringLiteral("payee_context_menu")}, {Menu::Investment, QStringLiteral("investment_context_menu")}, {Menu::Transaction, QStringLiteral("transaction_context_menu")}, {Menu::MoveTransaction, QStringLiteral("transaction_move_menu")}, {Menu::MarkTransaction, QStringLiteral("transaction_mark_menu")}, {Menu::MarkTransactionContext, QStringLiteral("transaction_context_mark_menu")}, {Menu::OnlineJob, QStringLiteral("onlinejob_context_menu")} }; for (auto it = menuNames.cbegin(); it != menuNames.cend(); ++it) lutMenus.insert(it.key(), qobject_cast(factory()->container(it.value(), this))); return lutMenus; } QHash KMyMoneyApp::initActions() { auto aC = actionCollection(); /* Look-up table for all custom and standard actions. It's required for: 1) building QList with QActions to be added to ActionCollection 2) adding custom features to QActions like e.g. keyboard shortcut */ QHash lutActions; // ************* // Adding standard actions // ************* KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC); KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC); d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC); KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC); KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC); KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC); lutActions.insert(Action::Print, KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC)); KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC); // ************* // Adding all actions // ************* { // struct for creating useless (unconnected) QAction struct actionInfo { Action action; QString name; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileBackup, QStringLiteral("file_backup"), i18n("Backup..."), Icon::Empty}, {Action::FileImportStatement, QStringLiteral("file_import_statement"), i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, QStringLiteral("file_import_template"), i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, QStringLiteral("file_export_template"), i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, QStringLiteral("view_personal_data"), i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump"), i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, QStringLiteral("view_file_info"), i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* - {Action::EditFindTransaction, QStringLiteral("edit_find_transaction"), i18n("Find transaction..."), Icon::EditFindTransaction}, + {Action::EditFindTransaction, QStringLiteral("edit_find_transaction"), i18n("Find transaction..."), Icon::Find}, // ************* // The View menu // ************* - {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail"), i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, + {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail"), i18n("Show Transaction Detail"), Icon::TransactionDetails}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories"), i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts"), i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::NewInstitution, QStringLiteral("institution_new"), i18n("New institution..."), Icon::InstitutionNew}, {Action::EditInstitution, QStringLiteral("institution_edit"), i18n("Edit institution..."), Icon::InstitutionEdit}, {Action::DeleteInstitution, QStringLiteral("institution_delete"), i18n("Delete institution..."), Icon::InstitutionDelete}, // ***************** // The accounts menu // ***************** {Action::NewAccount, QStringLiteral("account_new"), i18n("New account..."), Icon::AccountNew}, - {Action::OpenAccount, QStringLiteral("account_open"), i18n("Open ledger"), Icon::ViewFinancialList}, + {Action::OpenAccount, QStringLiteral("account_open"), i18n("Open ledger"), Icon::Ledger}, {Action::StartReconciliation, QStringLiteral("account_reconcile"), i18n("Reconcile..."), Icon::Reconcile}, - {Action::FinishReconciliation, QStringLiteral("account_reconcile_finish"), i18nc("Finish reconciliation", "Finish"), Icon::AccountFinishReconciliation}, + {Action::FinishReconciliation, QStringLiteral("account_reconcile_finish"), i18nc("Finish reconciliation", "Finish"), Icon::AccountFinishReconciliation}, {Action::PostponeReconciliation, QStringLiteral("account_reconcile_postpone"), i18n("Postpone reconciliation"), Icon::Pause}, {Action::EditAccount, QStringLiteral("account_edit"), i18n("Edit account..."), Icon::AccountEdit}, {Action::DeleteAccount, QStringLiteral("account_delete"), i18n("Delete account..."), Icon::AccountDelete}, {Action::CloseAccount, QStringLiteral("account_close"), i18n("Close account"), Icon::AccountClose}, {Action::ReopenAccount, QStringLiteral("account_reopen"), i18n("Reopen account"), Icon::AccountReopen}, - {Action::ReportAccountTransactions, QStringLiteral("account_transaction_report"), i18n("Transaction report"), Icon::ViewFinancialList}, + {Action::ReportAccountTransactions, QStringLiteral("account_transaction_report"), i18n("Transaction report"), Icon::Ledger}, {Action::ChartAccountBalance, QStringLiteral("account_chart"), i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::MapOnlineAccount, QStringLiteral("account_online_map"), i18n("Map account..."), Icon::MapOnlineAccount}, {Action::UnmapOnlineAccount, QStringLiteral("account_online_unmap"), i18n("Unmap account..."), Icon::UnmapOnlineAccount}, {Action::UpdateAccount, QStringLiteral("account_online_update"), i18n("Update account..."), Icon::AccountUpdate}, {Action::UpdateAllAccounts, QStringLiteral("account_online_update_all"), i18n("Update all accounts..."), Icon::AccountUpdateAll}, - {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"), Icon::AccountCreditTransfer}, + {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"), Icon::AccountCreditTransfer}, // ******************* // The categories menu // ******************* {Action::NewCategory, QStringLiteral("category_new"), i18n("New category..."), Icon::CategoryNew}, {Action::EditCategory, QStringLiteral("category_edit"), i18n("Edit category..."), Icon::CategoryEdit}, {Action::DeleteCategory, QStringLiteral("category_delete"), i18n("Delete category..."), Icon::CategoryDelete}, // ************** // The tools menu // ************** - {Action::ToolCurrencies, QStringLiteral("tools_currency_editor"), i18n("Currencies..."), Icon::ViewCurrencyList}, + {Action::ToolCurrencies, QStringLiteral("tools_currency_editor"), i18n("Currencies..."), Icon::Currencies}, {Action::ToolPrices, QStringLiteral("tools_price_editor"), i18n("Prices..."), Icon::Empty}, - {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices"), i18n("Update Stock and Currency Prices..."), Icon::ToolUpdatePrices}, + {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices"), i18n("Update Stock and Currency Prices..."), Icon::InvestmentOnlinePriceAll}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check"), i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, QStringLiteral("tools_performancetest"), i18n("Performance-Test"), Icon::PerformanceTest}, {Action::ToolCalculator, QStringLiteral("tools_kcalc"), i18n("Calculator..."), Icon::Calculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages"), i18n("Enable all messages"), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, QStringLiteral("help_show_tip"), i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::NewTransaction, QStringLiteral("transaction_new"), i18nc("New transaction button", "New"), Icon::TransactionNew}, {Action::EditTransaction, QStringLiteral("transaction_edit"), i18nc("Edit transaction button", "Edit"), Icon::TransactionEdit}, {Action::EnterTransaction, QStringLiteral("transaction_enter"), i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::EditSplits, QStringLiteral("transaction_editsplits"), i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::CancelTransaction, QStringLiteral("transaction_cancel"), i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::DeleteTransaction, QStringLiteral("transaction_delete"), i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::DuplicateTransaction, QStringLiteral("transaction_duplicate"), i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::AddReversingTransaction, QStringLiteral("transaction_add_reversing"), i18nc("Add reversing transaction", "Add reversing"),Icon::Reverse}, {Action::MatchTransaction, QStringLiteral("transaction_match"), i18nc("Button text for match transaction", "Match"),Icon::TransactionMatch}, {Action::AcceptTransaction, QStringLiteral("transaction_accept"), i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::TransactionAccept}, {Action::ToggleReconciliationFlag, QStringLiteral("transaction_mark_toggle"), i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::MarkCleared, QStringLiteral("transaction_mark_cleared"), i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::MarkReconciled, QStringLiteral("transaction_mark_reconciled"), i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::MarkNotReconciled, QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, - {Action::SelectAllTransactions, QStringLiteral("transaction_select_all"), i18nc("Select all transactions", "Select all"), Icon::Empty}, + {Action::SelectAllTransactions, QStringLiteral("transaction_select_all"), i18nc("Select all transactions", "Select all"), Icon::SelectAll}, {Action::GoToAccount, QStringLiteral("transaction_goto_account"), i18n("Go to account"), Icon::GoTo}, {Action::GoToPayee, QStringLiteral("transaction_goto_payee"), i18n("Go to payee"), Icon::GoTo}, {Action::NewScheduledTransaction, QStringLiteral("transaction_create_schedule"), i18n("Create scheduled transaction..."), Icon::NewSchedule}, {Action::AssignTransactionsNumber, QStringLiteral("transaction_assign_number"), i18n("Assign next number"), Icon::Empty}, - {Action::CombineTransactions, QStringLiteral("transaction_combine"), i18nc("Combine transactions", "Combine"), Icon::Empty}, + {Action::CombineTransactions, QStringLiteral("transaction_combine"), i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::CopySplits, QStringLiteral("transaction_copy_splits"), i18n("Copy splits"), Icon::Empty}, //Investment {Action::NewInvestment, QStringLiteral("investment_new"), i18n("New investment..."), Icon::InvestmentNew}, {Action::EditInvestment, QStringLiteral("investment_edit"), i18n("Edit investment..."), Icon::InvestmentEdit}, {Action::DeleteInvestment, QStringLiteral("investment_delete"), i18n("Delete investment..."), Icon::InvestmentDelete}, {Action::UpdatePriceOnline, QStringLiteral("investment_online_price_update"), i18n("Online price update..."), Icon::InvestmentOnlinePrice}, {Action::UpdatePriceManually, QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::NewSchedule, QStringLiteral("schedule_new"), i18n("New scheduled transaction"), Icon::NewSchedule}, {Action::EditSchedule, QStringLiteral("schedule_edit"), i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::DeleteSchedule, QStringLiteral("schedule_delete"), i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::DuplicateSchedule, QStringLiteral("schedule_duplicate"), i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::EnterSchedule, QStringLiteral("schedule_enter"), i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::SkipSchedule, QStringLiteral("schedule_skip"), i18n("Skip next transaction..."), Icon::SeekForward}, //Payees {Action::NewPayee, QStringLiteral("payee_new"), i18n("New payee"), Icon::ListAddUser}, {Action::RenamePayee, QStringLiteral("payee_rename"), i18n("Rename payee"), Icon::PayeeRename}, {Action::DeletePayee, QStringLiteral("payee_delete"), i18n("Delete payee"), Icon::ListRemoveUser}, {Action::MergePayee, QStringLiteral("payee_merge"), i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::NewTag, QStringLiteral("tag_new"), i18n("New tag"), Icon::ListAddTag}, {Action::RenameTag, QStringLiteral("tag_rename"), i18n("Rename tag"), Icon::TagRename}, {Action::DeleteTag, QStringLiteral("tag_delete"), i18n("Delete tag"), Icon::ListRemoveTag}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard"), i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, QStringLiteral("debug_traces"), i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, QStringLiteral("debug_timers"), i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::DeleteOnlineJob, QStringLiteral("onlinejob_delete"), i18n("Remove credit transfer"), Icon::EditDelete}, {Action::EditOnlineJob, QStringLiteral("onlinejob_edit"), i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::LogOnlineJob, QStringLiteral("onlinejob_log"), i18n("Show log"), Icon::Empty}, }; for (const auto& info : actionInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(info.name); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(Icons::get(info.icon)); a->setEnabled(false); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } { // List with slots that get connected here. Other slots get connected in e.g. appropriate views typedef void(KMyMoneyApp::*KMyMoneyAppFunc)(); const QHash actionConnections { // ************* // The File menu // ************* // {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase}, // {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts}, // ************** // The tools menu // ************** {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest}, // {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay}, // *************************** // Actions w/o main menu entry // *************************** //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers}, }; for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection) connect(lutActions[connection.key()], &QAction::triggered, this, connection.value()); } // ************* // Setting some of added actions checkable // ************* { // Some actions are checkable, // so set them here const QVector checkableActions { Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories, #ifdef KMM_DEBUG Action::DebugTraces, Action::DebugTimers, #endif Action::ViewShowAll }; for (const auto& it : checkableActions) { lutActions[it]->setCheckable(true); lutActions[it]->setEnabled(true); } } // ************* // Setting actions that are always enabled // ************* { const QVector alwaysEnabled { Action::HelpShow, Action::SettingsAllMessages, Action::ToolPerformance, Action::ToolCalculator }; for (const auto& action : alwaysEnabled) { lutActions[action]->setEnabled(true); } } // ************* // Setting keyboard shortcuts for some of added actions // ************* { const QVector> actionShortcuts { {qMakePair(Action::EditFindTransaction, Qt::CTRL + Qt::Key_F)}, {qMakePair(Action::ViewTransactionDetail, Qt::CTRL + Qt::Key_T)}, {qMakePair(Action::ViewHideReconciled, Qt::CTRL + Qt::Key_R)}, {qMakePair(Action::ViewHideCategories, Qt::CTRL + Qt::Key_U)}, {qMakePair(Action::ViewShowAll, Qt::CTRL + Qt::SHIFT + Qt::Key_A)}, {qMakePair(Action::StartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::NewTransaction, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::ToggleReconciliationFlag, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::MarkCleared, Qt::CTRL + Qt::ALT+ Qt::Key_Space)}, {qMakePair(Action::MarkReconciled, Qt::CTRL + Qt::SHIFT + Qt::Key_Space)}, {qMakePair(Action::SelectAllTransactions, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::AssignTransactionsNumber, Qt::CTRL + Qt::SHIFT + Qt::Key_N)} }; for(const auto& it : actionShortcuts) aC->setDefaultShortcut(lutActions[it.first], it.second); } // ************* // Misc settings // ************* connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); // Setup transaction detail switch lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory()); lutActions[Action::ViewShowAll]->setChecked(KMyMoneySettings::showAllAccounts()); // ************* // Adding actions to ActionCollection // ************* actionCollection()->addActions(lutActions.values()); // ************************ // Currently unused actions // ************************ #if 0 new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back"); new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward"); action("go_back")->setEnabled(false); action("go_forward")->setEnabled(false); #endif // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); // reconnect about app entry to dialog with full credits information auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp))); aboutApp->disconnect(); connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits); QMenu *menuContainer; menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentImport)); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentExport)); return lutActions; } #ifdef KMM_DEBUG void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); foreach (const auto it, list) std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl; } #endif bool KMyMoneyApp::isActionToggled(const Action _a) { return pActions[_a]->isChecked(); } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel; statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar; statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::initIcons() { #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) const auto appDataIconsLocation = QStringLiteral("kmymoney/icons"); #else const auto appDataIconsLocation = QStringLiteral("icons"); #endif const QString customIconRelativePath = appDataIconsLocation + QStringLiteral("/hicolor/16x16/actions/account-add.png"); #ifndef IS_APPIMAGE // find where our custom icons were installed based on an custom icon that we know should exist after installation auto customIconAbsolutePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, customIconRelativePath); if (customIconAbsolutePath.isEmpty()) { qWarning("Custom icons were not found in any of the following QStandardPaths::AppDataLocation:"); for (const auto &standardPath : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)) qWarning() << standardPath; } #else // according to https://docs.appimage.org/packaging-guide/ingredients.html#open-source-applications // QStandardPaths::AppDataLocation is unreliable on AppImages, so apply workaround here in case we fail to find icons QString customIconAbsolutePath; const auto appImageAppDataLocation = QString("%1%2%3").arg(QCoreApplication::applicationDirPath(), QString("/../share/kmymoney/"), customIconRelativePath); if (QFile::exists(appImageAppDataLocation )) { customIconAbsolutePath = appImageAppDataLocation ; } else { qWarning("Custom icons were not found in the following location:"); qWarning() << appImageAppDataLocation ; } #endif // add our custom icons path to icons search path if (!customIconAbsolutePath.isEmpty()) { customIconAbsolutePath.chop(customIconRelativePath.length()); customIconAbsolutePath.append(appDataIconsLocation); auto paths = QIcon::themeSearchPaths(); paths.append(customIconAbsolutePath); QIcon::setThemeSearchPaths(paths); } #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - QString themeName = QLatin1Literal("system"); // using QIcon::setThemeName on Craft build system causes icons to disappear + auto themeName = QStringLiteral("breeze"); // only breeze is available for craft packages #else - QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants -#endif - if (!themeName.isEmpty() && themeName != QLatin1Literal("system")) // if it isn't default theme then set it + auto themeName = KMyMoneySettings::iconsTheme(); // get theme user wants + if (!themeName.isEmpty() && themeName != QStringLiteral("system")) // if it isn't default theme then set it QIcon::setThemeName(themeName); - setIconThemeNames(QIcon::themeName()); // get whatever theme user ends up with and hope our icon names will fit that theme +#endif + + setUpMappings(themeName); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked()); KConfigGroup toolbarGrp = d->m_config->group("mainToolBar"); toolBar("mainToolBar")->saveSettings(toolbarGrp); d->m_recentFiles->saveEntries(d->m_config->group("Recent Files")); } void KMyMoneyApp::readOptions() { KConfigGroup grp = d->m_config->group("General Options"); pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions()); pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } #ifdef KMM_DEBUG void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); d->updateCaption(); } #endif bool KMyMoneyApp::queryClose() { if (!isReady()) return false; if (!slotFileClose()) return false; saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KMyMoneyApp::slotFileInfoDialog() { QPointer dlg = new KMyMoneyFileInfoDlg(0); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPerformanceTest() { // dump performance report to stderr int measurement[2]; QTime timer; MyMoneyAccount acc; qDebug("--- Starting performance tests ---"); // AccountList // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; timer.start(); for (int i = 0; i < 1000; ++i) { QList list; MyMoneyFile::instance()->accountList(list); measurement[i != 0] = timer.elapsed(); } std::cerr << "accountList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of asset account(s) // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of asset account // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "totalBalance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of expense account(s) // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of expense account // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); timer.start(); for (int i = 0; i < 1000; ++i) { MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] = timer.elapsed(); } std::cerr << "totalBalance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // transaction list // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { list = MyMoneyFile::instance()->transactionList(filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // transaction list // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { MyMoneyFile::instance()->transactionList(list, filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList(list)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // MyMoneyFile::instance()->preloadCache(); } bool KMyMoneyApp::isDatabase() { return (d->m_storageInfo.isOpened && ((d->m_storageInfo.type == eKMyMoney::StorageType::SQL))); } bool KMyMoneyApp::isNativeFile() { return (d->m_storageInfo.isOpened && (d->m_storageInfo.type == eKMyMoney::StorageType::SQL || d->m_storageInfo.type == eKMyMoney::StorageType::XML)); } bool KMyMoneyApp::fileOpen() const { return d->m_storageInfo.isOpened; } KMyMoneyAppCallback KMyMoneyApp::progressCallback() { return &KMyMoneyApp::progressCallback; } void KMyMoneyApp::consistencyCheck(bool alwaysDisplayResult) { d->consistencyCheck(alwaysDisplayResult); } bool KMyMoneyApp::isImportableFile(const QUrl &url) { bool result = false; // Iterate through the plugins and see if there's a loaded plugin who can handle it QMap::const_iterator it_plugin = pPlugins.importer.constBegin(); while (it_plugin != pPlugins.importer.constEnd()) { if ((*it_plugin)->isMyFormat(url.toLocalFile())) { result = true; break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == pPlugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } bool KMyMoneyApp::isFileOpenedInAnotherInstance(const QUrl &url) { const auto instances = instanceList(); #ifdef KMM_DBUS // check if there are other instances which might have this file open for (const auto& instance : instances) { QDBusInterface remoteApp(instance, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) qDebug("D-Bus error while calling app->filename()"); else if (reply.value() == url.url()) return true; } #else Q_UNUSED(url) #endif return false; } void KMyMoneyApp::slotShowTransactionDetail() { } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneySettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneySettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { KMyMoneySettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG void KMyMoneyApp::slotFileFileInfo() { if (!d->m_storageInfo.isOpened) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } QFile g("kmymoney.dump"); g.open(QIODevice::WriteOnly); QDataStream st(&g); MyMoneyStorageDump dumper; dumper.writeStream(st, MyMoneyFile::instance()->storage()); g.close(); } void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = pActions[Action::DebugTimers]->isChecked(); } QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = d->m_statusLabel->text(); d->m_applicationIsReady = false; QString currentMessage = text; if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) { d->m_applicationIsReady = true; currentMessage = i18nc("Application is ready to use", "Ready."); } statusBar()->clearMessage(); d->m_statusLabel->setText(currentMessage); return previousMessage; } void KMyMoneyApp::ready() { slotStatusMsg(QString()); } bool KMyMoneyApp::isReady() { return d->m_applicationIsReady; } void KMyMoneyApp::slotStatusProgressBar(int current, int total) { if (total == -1 && current == -1) { // reset if (d->m_progressTimer) { d->m_progressTimer->start(500); // remove from screen in 500 msec d->m_progressBar->setValue(d->m_progressBar->maximum()); } } else if (total != 0) { // init d->m_progressTimer->stop(); d->m_progressBar->setMaximum(total); d->m_progressBar->setValue(0); d->m_progressBar->show(); d->m_lastUpdate = QTime::currentTime(); } else { // update const auto currentTime = QTime::currentTime(); // only process painting if last update is at least 200 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 200) { d->m_progressBar->setValue(current); d->m_lastUpdate = currentTime; } } } void KMyMoneyApp::slotStatusProgressDone() { d->m_progressTimer->stop(); d->m_progressBar->reset(); d->m_progressBar->hide(); d->m_progressBar->setValue(0); } void KMyMoneyApp::progressCallback(int current, int total, const QString& msg) { if (!msg.isEmpty()) kmymoney->slotStatusMsg(msg); kmymoney->slotStatusProgressBar(current, total); } void KMyMoneyApp::slotFileViewPersonal() { if (!d->m_storageInfo.isOpened) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } KMSTATUS(i18n("Viewing personal data...")); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPayee user = file->user(); QPointer editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(), user.city(), user.state(), user.postcode(), user.telephone(), user.email(), this, i18n("Edit Personal Data")); if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) { user.setName(editPersonalDataDlg->userName()); user.setAddress(editPersonalDataDlg->userStreet()); user.setCity(editPersonalDataDlg->userTown()); user.setState(editPersonalDataDlg->userCountry()); user.setPostcode(editPersonalDataDlg->userPostcode()); user.setTelephone(editPersonalDataDlg->userTelephone()); user.setEmail(editPersonalDataDlg->userEmail()); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", QString::fromLatin1(e.what()))); } } delete editPersonalDataDlg; } void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); QPointer dlg = new KLoadTemplateDlg(); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { // import the account templates QList templates = dlg->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(progressCallback); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to import template(s)"), QString::fromLatin1(e.what())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name(); QDir templatesDir(savePath); if (!templatesDir.exists()) templatesDir.mkpath(savePath); QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath, i18n("KMyMoney template files (*.kmt);;All files (*)")); // // If there is no file extension, then append a .kmt at the end of the file name. // If there is a file extension, make sure it is .kmt, delete any others. // if (!newName.isEmpty()) { // find last . delimiter int nLoc = newName.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = newName.left(nLoc + 1); strExt = newName.right(newName.length() - (nLoc + 1)); if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) { strTemp.append("kmt"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmt"); } if (okToWriteFile(QUrl::fromLocalFile(newName))) { QPointer dlg = new KTemplateExportDlg(this); if (dlg->exec() == QDialog::Accepted && dlg) { MyMoneyTemplate templ; templ.setTitle(dlg->title()); templ.setShortDescription(dlg->shortDescription()); templ.setLongDescription(dlg->longDescription()); templ.exportTemplate(progressCallback); templ.saveTemplate(QUrl::fromLocalFile(newName)); } delete dlg; } } } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; if (KMyMoneyUtils::fileExists(url)) { if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) reallySaveFile = false; } return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it auto dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneySettings::self()); connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName) { if(dialogName.compare(QLatin1String("Plugins")) == 0) { KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, pPlugins, this, guiFactory()); actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(d->canFileSaveAs()); onlineJobAdministration::instance()->updateActions(); onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended); d->m_myMoneyView->setOnlinePlugins(pPlugins.online); d->updateActions(); d->m_myMoneyView->slotRefreshViews(); return; } MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); #ifdef ENABLE_UNFINISHEDFEATURES LedgerSeparator::setFirstFiscalDate(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); #endif d->m_myMoneyView->updateViewType(); // update the sql storage module settings // MyMoneyStorageSql::setStartDate(KMyMoneySettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneySettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneySettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneySettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneySettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setThemedCSS(); } void KMyMoneyApp::slotBackupFile() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } if (d->m_storageInfo.url.isEmpty()) return; if (!d->m_storageInfo.url.isLocalFile()) { KMessageBox::sorry(this, i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_storageInfo.url.url()), i18n("Local files only")); return; } QPointer backupDlg = new KBackupDlg(this); int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBoxChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->mountPoint(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); #ifdef Q_OS_WIN d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); #else // If we don't have to mount a device, we just issue // a dummy command to start the copy operation d->m_proc.setProgram("true"); d->m_proc.start(); #endif } } delete backupDlg; } void KMyMoneyApp::slotBackupMount() { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } bool KMyMoneyApp::slotBackupWriteFile() { QFileInfo fi(d->m_storageInfo.url.fileName()); QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix(); QString backupfile = d->m_mountpoint + '/' + d->m_storageInfo.url.fileName(); KMyMoneyUtils::appendCorrectFileExt(backupfile, today); #ifdef Q_OS_WIN // on windows, a leading slash is a problem if a drive letter follows // eg. "/Z:/path". In case we detect such a pattern, we simply remove // the leading slash const QRegularExpression re(QStringLiteral("/(?\\w+:/.+)"), QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption ); const auto match = re.match(backupfile); if (match.hasMatch() && !match.captured(QStringLiteral("path")).isEmpty()) { backupfile = match.captured(QStringLiteral("path")); } #endif // check if file already exists and ask what to do QFile f(backupfile); if (f.exists()) { int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace"))); if (answer == KMessageBox::Cancel) { return false; } } progressCallback(50, 0, i18n("Writing %1", backupfile)); d->m_proc.clearProgram(); #ifdef Q_OS_WIN d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y"; d->m_proc << QDir::toNativeSeparators(d->m_storageInfo.url.toLocalFile()) << "+" << "nul" << QDir::toNativeSeparators(backupfile); #else d->m_proc << "cp" << "-f"; d->m_proc << d->m_storageInfo.url.toLocalFile() << backupfile; #endif d->m_backupState = BACKUP_COPYING; qDebug() << "Backup cmd:" << d->m_proc.program(); d->m_proc.start(); return true; } void KMyMoneyApp::slotBackupUnmount() { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } void KMyMoneyApp::slotBackupFinish() { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } void KMyMoneyApp::slotBackupHandleEvents() { switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_ignoreBackupExitCode || (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) { d->m_ignoreBackupExitCode = false; d->m_backupResult = 0; if (!slotBackupWriteFile()) { d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); slotBackupFinish(); } } else { qDebug("copy exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } slotBackupFinish(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotViewSelected(View view) { KMyMoneySettings::setLastViewSelected(static_cast(view)); } void KMyMoneyApp::slotGenerateSql() { // QPointer editor = new KGenerateSqlDlg(this); // editor->setObjectName("Generate Database SQL"); // editor->exec(); // delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneySettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", QString::fromLatin1(e.what()))); } } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewInvestmentWizard::newInvestment(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewAccountDlg::newCategory(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account) { KNewAccountDlg::newCategory(account, MyMoneyAccount()); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard::newAccount(account); } void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { MyMoneyFile* file = MyMoneyFile::instance(); // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION_CSTRING("Transaction for schedule has less than 2 splits!"); } MyMoneyFileTransaction ft; try { file->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.accountType() == eMyMoney::Account::Type::Loan || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) { newAccount.setValue("schedule", newSchedule.id()); file->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what()))); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what()))); } } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst) { MyMoneyAccount src(_src); src.setInstitutionId(_dst.id()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(src); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to institution %2. Reason: %3

", src.name(), _dst.name(), QString::fromLatin1(e.what()))); } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst) { MyMoneyAccount src(_src); MyMoneyAccount dst(_dst); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->reparentAccount(src, dst); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to %2. Reason: %3

", src.name(), dst.name(), QString::fromLatin1(e.what()))); } } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { KEditScheduleDlg::newSchedule(_t, occurrence); } void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { KMyMoneyUtils::newPayee(newnameBase, id); } void KMyMoneyApp::slotNewFeature() { } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; foreach (const auto split, t.splits()) { stockAccountId = split.accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = split; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; foreach (const auto sAccount, toInvAcc.accountList()) { if (MyMoneyFile::instance()->account(sAccount).currencyId() == stockSecurityId) { newStockAccountId = sAccount; break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); if (auto menu = dynamic_cast(w)) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } void KMyMoneyApp::Private::updateCaption() { auto caption = m_storageInfo.url.isEmpty() && m_myMoneyView && m_storageInfo.isOpened ? i18n("Untitled") : m_storageInfo.url.fileName(); #ifdef KMM_DEBUG caption += QString(" (%1 x %2)").arg(q->width()).arg(q->height()); #endif q->setCaption(caption, MyMoneyFile::instance()->dirty()); } void KMyMoneyApp::Private::updateActions() { const QVector actions { Action::FilePersonalData, Action::FileInformation, Action::FileImportTemplate, Action::FileExportTemplate, #ifdef KMM_DEBUG Action::FileDump, #endif Action::EditFindTransaction, Action::NewCategory, Action::ToolCurrencies, Action::ToolPrices, Action::ToolUpdatePrices, Action::ToolConsistency, Action::ToolPerformance, Action::NewAccount, Action::NewInstitution, Action::NewSchedule }; for (const auto &action : actions) pActions[action]->setEnabled(m_storageInfo.isOpened); pActions[Action::FileBackup]->setEnabled(m_storageInfo.isOpened && m_storageInfo.type == eKMyMoney::StorageType::XML); auto aC = q->actionCollection(); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(canFileSaveAs()); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(m_storageInfo.isOpened); pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts()); } bool KMyMoneyApp::Private::canFileSaveAs() const { return (m_storageInfo.isOpened && (!pPlugins.storage.isEmpty() && !(pPlugins.storage.count() == 1 && pPlugins.storage.first()->storageType() == eKMyMoney::StorageType::GNC))); } void KMyMoneyApp::slotDataChanged() { d->fileAction(eKMyMoney::FileAction::Changed); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); dlg->exec(); delete dlg; } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); } void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult) { KMSTATUS(i18n("Running consistency check...")); MyMoneyFileTransaction ft; try { m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck(); ft.commit(); } catch (const MyMoneyException &e) { m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what())); // always display the result if the check failed alwaysDisplayResult = true; } // in case the consistency check was OK, we get a single line as result // in all erroneous cases, we get more than one line and force the // display of them. if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) { QString msg = i18n("The consistency check has found no issues in your data. Details are presented below."); if (m_consistencyCheckResult.size() > 1) msg = i18n("The consistency check has found some issues in your data. Details are presented below. Those issues that could not be corrected automatically need to be solved by the user."); // install a context menu for the list after the dialog is displayed QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu())); KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result")); } // this data is no longer needed m_consistencyCheckResult.clear(); } void KMyMoneyApp::Private::copyConsistencyCheckResults() { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n"))); } void KMyMoneyApp::Private::saveConsistencyCheckResults() { QUrl fileUrl = QFileDialog::getSaveFileUrl(q); if (!fileUrl.isEmpty()) { QFile file(fileUrl.toLocalFile()); if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) { QTextStream out(&file); out << m_consistencyCheckResult.join(QLatin1String("\n")); file.close(); } } } void KMyMoneyApp::Private::setThemedCSS() { const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")}; const QString rcDir("/html/"); QStringList defaultCSSDirs; #ifndef IS_APPIMAGE defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory); #else // according to https://docs.appimage.org/packaging-guide/ingredients.html#open-source-applications // QStandardPaths::AppDataLocation is unreliable on AppImages, so apply workaround here in case we fail to find icons // watch out for QStringBuilder here; for yet unknown reason it causes segmentation fault on startup const auto appImageAppDataLocation = QString("%1%2%3").arg(QCoreApplication::applicationDirPath(), QString("/../share/kmymoney"), rcDir); if (QFile::exists(appImageAppDataLocation + CSSnames.first())) { defaultCSSDirs.append(appImageAppDataLocation); } else { qWarning("CSS file was not found in the following location:"); qWarning() << appImageAppDataLocation; } #endif // scan the list of directories to find the ones that really // contains all files we look for QString defaultCSSDir; foreach (const auto dir, defaultCSSDirs) { defaultCSSDir = dir; foreach (const auto CSSname, CSSnames) { QFileInfo fileInfo(defaultCSSDir + CSSname); if (!fileInfo.exists()) { defaultCSSDir.clear(); break; } } if (!defaultCSSDir.isEmpty()) { break; } } // make sure we have the local directory where the themed version is stored const QString themedCSSDir = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + rcDir; QDir().mkpath(themedCSSDir); foreach (const auto CSSname, CSSnames) { const QString defaultCSSFilename = defaultCSSDir + CSSname; QFileInfo fileInfo(defaultCSSFilename); if (fileInfo.exists()) { const QString themedCSSFilename = themedCSSDir + CSSname; QFile::remove(themedCSSFilename); if (QFile::copy(defaultCSSFilename, themedCSSFilename)) { QFile cssFile (themedCSSFilename); if (cssFile.open(QIODevice::ReadWrite)) { QTextStream cssStream(&cssFile); auto cssText = cssStream.readAll(); cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive); cssText.replace(QLatin1String("WindowText"), KMyMoneySettings::schemeColor(SchemeColor::WindowText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Window"), KMyMoneySettings::schemeColor(SchemeColor::WindowBackground).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("HighlightText"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Highlight"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlight).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("black"), KMyMoneySettings::schemeColor(SchemeColor::ListGrid).name(), Qt::CaseSensitive); cssStream.seek(0); cssStream << cssText; cssFile.close(); } } } } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneySettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneySettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != eDialogs::ScheduleResultCode::Ignore && rc != eDialogs::ScheduleResultCode::Cancel) { rc = d->m_myMoneyView->enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == eDialogs::ScheduleResultCode::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = eDialogs::ScheduleResultCode::Enter; } } } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = kconfig->group("General Options"); //write path entry, no error handling since its void. grp.writeEntry("LastUsedDirectory", directory); } } void KMyMoneyApp::writeLastUsedFile(const QString& fileName) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // write path entry, no error handling since its void. // use a standard string, as fileName could contain a protocol // e.g. file:/home/thb/.... grp.writeEntry("LastUsedFile", fileName); } } QString KMyMoneyApp::readLastUsedDir() const { QString str; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); //read path entry. Second parameter is the default if the setting is not found, which will be the default document path. str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); // if the path stored is empty, we use the default nevertheless if (str.isEmpty()) str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } return str; } QString KMyMoneyApp::readLastUsedFile() const { QString str; // get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // read filename entry. str = grp.readEntry("LastUsedFile", ""); } return str; } QString KMyMoneyApp::filename() const { return d->m_storageInfo.url.url(); } QUrl KMyMoneyApp::filenameURL() const { return d->m_storageInfo.url; } void KMyMoneyApp::writeFilenameURL(const QUrl &url) { d->m_storageInfo.url = url; } void KMyMoneyApp::addToRecentFiles(const QUrl& url) { d->m_recentFiles->addUrl(url); } QTimer* KMyMoneyApp::autosaveTimer() { return d->m_autoSaveTimer; } WebConnect* KMyMoneyApp::webConnect() const { return d->m_webConnect; } QList KMyMoneyApp::instanceList() const { QList list; #ifdef KMM_DBUS QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (reply.isValid()) { QStringList apps = reply.value(); QStringList::ConstIterator it; // build a list of service names of all running kmymoney applications without this one for (it = apps.constBegin(); it != apps.constEnd(); ++it) { // please change this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somewhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { uint thisProcPid = platformTools::processId(); if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::webConnectUrl(const QUrl url) { QMetaObject::invokeMethod(this, "webConnect", Qt::QueuedConnection, Q_ARG(QString, url.toLocalFile()), Q_ARG(QByteArray, QByteArray())); } void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id) { // // Web connect attempts to go through the known importers and see if the file // can be importing using that method. If so, it will import it using that // plugin // Q_UNUSED(asn_id) d->m_importUrlsQueue.enqueue(sourceUrl); // only start processing if this is the only import so far if (d->m_importUrlsQueue.count() == 1) { MyMoneyStatementReader::clearResultMessages(); auto statementCount = 0; while (!d->m_importUrlsQueue.isEmpty()) { ++statementCount; // get the value of the next item from the queue // but leave it on the queue for now QString url = d->m_importUrlsQueue.head(); // Bring this window to the forefront. This method was suggested by // Lubos Lunak of the KDE core development team. //KStartupInfo::setNewStartupId(this, asn_id); // Make sure we have an open file if (! d->m_storageInfo.isOpened && KMessageBox::warningContinueCancel(this, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) slotFileOpen(); // only continue if the user really did open a file. if (d->m_storageInfo.isOpened) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = pPlugins.importer.constBegin(); while (it_plugin != pPlugins.importer.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { if (!(*it_plugin)->import(url) && !(*it_plugin)->lastError().isEmpty()) { KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error")); } break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == pPlugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url)) MyMoneyStatementReader::importStatement(url, false, progressCallback); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } KMyMoneyUtils::showStatementImportResult(MyMoneyStatementReader::resultMessages(), statementCount); } } void KMyMoneyApp::slotEnableMessages() { KMessageBox::enableAllMessages(); KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages")); } void KMyMoneyApp::createInterfaces() { // Sets up the plugin interface KMyMoneyPlugin::pluginInterfaces().appInterface = new KMyMoneyPlugin::KMMAppInterface(this, this); KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((static_cast(dt.secsTo(nextDay)) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef ENABLE_HOLIDAYS //since the cost of updating the cache is now not negligible //check whether the region has been modified if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) { // Delete the previous holidayRegion before creating a new one. delete d->m_holidayRegion; // Create a new holidayRegion. d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion); //clear and update the holiday cache preloadHolidays(); } #else Q_UNUSED(holidayRegion); #endif } bool KMyMoneyApp::isProcessingDate(const QDate& date) const { if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; #ifdef ENABLE_HOLIDAYS if (!d->m_holidayRegion || !d->m_holidayRegion->isValid()) return true; //check first whether it's already in cache if (d->m_holidayMap.contains(date)) { return d->m_holidayMap.value(date); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef ENABLE_HOLIDAYS //clear the cache before loading d->m_holidayMap.clear(); // only do this if it is a valid region if (d->m_holidayRegion && d->m_holidayRegion->isValid()) { // load holidays for the forecast days plus 1 cycle, to be on the safe side auto forecastDays = KMyMoneySettings::forecastDays() + KMyMoneySettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); // look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1)) d->m_holidayMap.insert(holidayDate, (*holiday_it).dayType() == KHolidays::Holiday::Workday); } // prefill cache with all values of the forecast period for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) { // if it is not a processing day, set it to false if (!d->m_processingDays.testBit(date.dayOfWeek())) { d->m_holidayMap.insert(date, false); } else if (!d->m_holidayMap.contains(date)) { // if it is not a holiday nor a weekend, it is a processing day d->m_holidayMap.insert(date, true); } } } #endif } bool KMyMoneyApp::slotFileNew() { KMSTATUS(i18n("Creating new document...")); if (!slotFileClose()) return false; NewUserWizard::Wizard wizard; if (wizard.exec() != QDialog::Accepted) return false; d->m_storageInfo.isOpened = true; d->m_storageInfo.type = eKMyMoney::StorageType::None; d->m_storageInfo.url = QUrl(); try { auto storage = new MyMoneyStorageMgr; MyMoneyFile::instance()->attachStorage(storage); MyMoneyFileTransaction ft; auto file = MyMoneyFile::instance(); // store the user info file->setUser(wizard.user()); // create and setup base currency file->addCurrency(wizard.baseCurrency()); file->setBaseCurrency(wizard.baseCurrency()); // create a possible institution MyMoneyInstitution inst = wizard.institution(); if (inst.name().length()) { file->addInstitution(inst); } // create a possible checking account auto acc = wizard.account(); if (acc.name().length()) { acc.setInstitutionId(inst.id()); MyMoneyAccount asset = file->asset(); file->addAccount(acc, asset); // create possible opening balance transaction if (!wizard.openingBalance().isZero()) { file->createOpeningBalanceTransaction(acc, wizard.openingBalance()); } } // import the account templates for (auto &tmpl : wizard.templates()) tmpl.importTemplate(progressCallback); ft.commit(); KMyMoneySettings::setFirstTimeRun(false); d->fileAction(eKMyMoney::FileAction::Opened); if (actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->isEnabled()) slotFileSaveAs(); } catch (const MyMoneyException & e) { slotFileClose(); d->removeStorage(); KMessageBox::detailedError(this, i18n("Couldn't create a new file."), e.what()); return false; } if (wizard.startSettingsAfterFinished()) slotSettings(); return true; } void KMyMoneyApp::slotFileOpen() { KMSTATUS(i18n("Open a file.")); const QVector desiredFileExtensions {eKMyMoney::StorageType::XML, eKMyMoney::StorageType::GNC}; QString fileExtensions; for (const auto &extension : desiredFileExtensions) { for (const auto &plugin : pPlugins.storage) { if (plugin->storageType() == extension) { fileExtensions += plugin->fileExtension() + QLatin1String(";;"); break; } } } if (fileExtensions.isEmpty()) { KMessageBox::error(this, i18n("Couldn't find any plugin for opening storage.")); return; } fileExtensions.append(i18n("All files (*)")); QPointer dialog = new QFileDialog(this, QString(), readLastUsedDir(), fileExtensions); dialog->setFileMode(QFileDialog::ExistingFile); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (dialog->exec() == QDialog::Accepted && dialog != nullptr) slotFileOpenRecent(dialog->selectedUrls().first()); delete dialog; } bool KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { KMSTATUS(i18n("Loading file...")); if (!url.isValid()) throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); if (isFileOpenedInAnotherInstance(url)) { KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); return false; } if (url.scheme() != QLatin1String("sql") && !KMyMoneyUtils::fileExists(url)) { KMessageBox::sorry(this, i18n("

%1 is either an invalid filename or the file does not exist. You can open another file or create a new one.

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); return false; } if (d->m_storageInfo.isOpened) if (!slotFileClose()) return false; // open the database d->m_storageInfo.type = eKMyMoney::StorageType::None; for (auto &plugin : pPlugins.storage) { try { if (auto pStorage = plugin->open(url)) { MyMoneyFile::instance()->attachStorage(pStorage); d->m_storageInfo.type = plugin->storageType(); if (plugin->storageType() != eKMyMoney::StorageType::GNC) { d->m_storageInfo.url = plugin->openUrl(); writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); /* Don't use url variable after KRecentFilesAction::addUrl * as it might delete it. * More in API reference to this method */ d->m_recentFiles->addUrl(url); } d->m_storageInfo.isOpened = true; break; } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Cannot open file as requested."), QString::fromLatin1(e.what())); return false; } } if(d->m_storageInfo.type == eKMyMoney::StorageType::None) { KMessageBox::error(this, i18n("Could not read your data source. Please check the KMyMoney settings that the necessary plugin is enabled.")); return false; } d->fileAction(eKMyMoney::FileAction::Opened); return true; } bool KMyMoneyApp::slotFileSave() { KMSTATUS(i18n("Saving file...")); for (const auto& plugin : pPlugins.storage) { if (plugin->storageType() == d->m_storageInfo.type) { d->consistencyCheck(false); try { if (plugin->save(d->m_storageInfo.url)) { d->fileAction(eKMyMoney::FileAction::Saved); return true; } return false; } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what()); return false; } } } KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); return false; } bool KMyMoneyApp::slotFileSaveAs() { KMSTATUS(i18n("Saving file as....")); QVector availableFileTypes; for (const auto& plugin : pPlugins.storage) { switch (plugin->storageType()) { case eKMyMoney::StorageType::GNC: break; default: availableFileTypes.append(plugin->storageType()); break; } } auto chosenFileType = eKMyMoney::StorageType::None; switch (availableFileTypes.count()) { case 0: KMessageBox::error(this, i18n("Couldn't find any plugin for saving storage.")); return false; case 1: chosenFileType = availableFileTypes.first(); break; default: { QPointer dlg = new KSaveAsQuestion(availableFileTypes, this); auto rc = dlg->exec(); if (dlg) { auto fileType = dlg->fileType(); delete dlg; if (rc != QDialog::Accepted) return false; chosenFileType = fileType; } } } for (const auto &plugin : pPlugins.storage) { if (chosenFileType == plugin->storageType()) { try { d->consistencyCheck(false); if (plugin->saveAs()) { d->fileAction(eKMyMoney::FileAction::Saved); d->m_storageInfo.type = plugin->storageType(); return true; } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what()); } } } return false; } bool KMyMoneyApp::slotFileClose() { if (!d->m_storageInfo.isOpened) return true; if (!d->askAboutSaving()) return false; d->fileAction(eKMyMoney::FileAction::Closing); d->removeStorage(); d->m_storageInfo = KMyMoneyApp::Private::storageInfo(); d->fileAction(eKMyMoney::FileAction::Closed); return true; } void KMyMoneyApp::slotFileQuit() { // don't modify the status message here as this will prevent quit from working!! // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 bool quitApplication = true; QList memberList = KMainWindow::memberList(); if (!memberList.isEmpty()) { QList::const_iterator w_it = memberList.constBegin(); for (; w_it != memberList.constEnd(); ++w_it) { // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, // the window and the application stay open. if (!(*w_it)->close()) { quitApplication = false; break; } } } // We will only quit if all windows were processed and not cancelled if (quitApplication) { QCoreApplication::quit(); } } void KMyMoneyApp::Private::fileAction(eKMyMoney::FileAction action) { switch(action) { case eKMyMoney::FileAction::Opened: q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); updateAccountNames(); updateCurrencyNames(); selectBaseCurrency(); // setup the standard precision AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); applyFileFixes(); Models::instance()->fileOpened(); connectStorageToModels(); // inform everyone about new data MyMoneyFile::instance()->forceDataChanged(); // Enable save in case the fix changed the contents q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(dirty()); updateActions(); m_myMoneyView->slotFileOpened(); onlineJobAdministration::instance()->updateActions(); m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened); m_myMoneyView->slotRefreshViews(); onlineJobAdministration::instance()->updateOnlineTaskProperties(); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); #ifdef ENABLE_ACTIVITIES { // make sure that we don't store the DB password in activity QUrl url(m_storageInfo.url); url.setPassword(QString()); m_activityResourceInstance->setUri(url); } #endif // start the check for scheduled transactions that need to be // entered as soon as the event loop becomes active. QMetaObject::invokeMethod(q, "slotCheckSchedules", Qt::QueuedConnection); // make sure to catch view activations connect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected); break; case eKMyMoney::FileAction::Saved: q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); m_autoSaveTimer->stop(); break; case eKMyMoney::FileAction::Closing: disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); // make sure to not catch view activations anymore disconnect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected); m_myMoneyView->slotFileClosed(); // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) Models::instance()->fileClosed(); break; case eKMyMoney::FileAction::Closed: q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); disconnectStorageFromModels(); q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened); updateActions(); break; case eKMyMoney::FileAction::Changed: q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(true && !m_storageInfo.url.isEmpty()); // As this method is called every time the MyMoneyFile instance // notifies a modification, it's the perfect place to start the timer if needed if (m_autoSaveEnabled && !m_autoSaveTimer->isActive()) { m_autoSaveTimer->setSingleShot(true); m_autoSaveTimer->start(m_autoSavePeriod * 60 * 1000); //miliseconds } pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts()); break; default: break; } updateCaption(); } KMStatus::KMStatus(const QString &text) : m_prevText(kmymoney->slotStatusMsg(text)) { } KMStatus::~KMStatus() { kmymoney->slotStatusMsg(m_prevText); } void KMyMoneyApp::Private::unlinkStatementXML() { QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); } } diff --git a/kmymoney/kmymoneyui.rc b/kmymoney/kmymoneyui.rc index f08fa80b5..d8a13e6d0 100644 --- a/kmymoney/kmymoneyui.rc +++ b/kmymoney/kmymoneyui.rc @@ -1,257 +1,257 @@ &Import &Export - + - + &Institution - + &Account - + &Category &Transaction Mark transaction as... Mark transaction T&ools Account options Category options Institution options Payee options Tag options Investment options Scheduled transactions options Transaction options Move transaction to... Select account Mark transaction as... Mark transaction Credit transfer options Main Toolbar diff --git a/kmymoney/kmymoneyutils.cpp b/kmymoney/kmymoneyutils.cpp index e6d1a6a62..e11aa3ec4 100644 --- a/kmymoney/kmymoneyutils.cpp +++ b/kmymoney/kmymoneyutils.cpp @@ -1,844 +1,844 @@ /*************************************************************************** kmymoneyutils.cpp - description ------------------- begin : Wed Feb 5 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net 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 "kmymoneyutils.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneyexception.h" #include "mymoneytransactionfilter.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneyprice.h" #include "mymoneystatement.h" #include "mymoneyforecast.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "kmymoneysettings.h" #include "icons.h" #include "storageenums.h" #include "mymoneyenums.h" #include "kmymoneyplugin.h" using namespace Icons; KMyMoneyUtils::KMyMoneyUtils() { } KMyMoneyUtils::~KMyMoneyUtils() { } const QString KMyMoneyUtils::occurrenceToString(const eMyMoney::Schedule::Occurrence occurrence) { return i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(occurrence).toLatin1()); } const QString KMyMoneyUtils::paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType) { return i18nc("Scheduled Transaction payment type", MyMoneySchedule::paymentMethodToString(paymentType).toLatin1()); } const QString KMyMoneyUtils::weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption) { return i18n(MyMoneySchedule::weekendOptionToString(weekendOption).toLatin1()); } const QString KMyMoneyUtils::scheduleTypeToString(eMyMoney::Schedule::Type type) { return i18nc("Scheduled transaction type", MyMoneySchedule::scheduleTypeToString(type).toLatin1()); } KGuiItem KMyMoneyUtils::scheduleNewGuiItem() { KGuiItem splitGuiItem(i18n("&New Schedule..."), Icons::get(Icon::DocumentNew), i18n("Create a new schedule."), i18n("Use this to create a new schedule.")); return splitGuiItem; } KGuiItem KMyMoneyUtils::accountsFilterGuiItem() { KGuiItem splitGuiItem(i18n("&Filter"), - Icons::get(Icon::ViewFilter), + Icons::get(Icon::Filter), i18n("Filter out accounts"), i18n("Use this to filter out accounts")); return splitGuiItem; } const char* homePageItems[] = { I18N_NOOP("Payments"), I18N_NOOP("Preferred accounts"), I18N_NOOP("Payment accounts"), I18N_NOOP("Favorite reports"), I18N_NOOP("Forecast (schedule)"), I18N_NOOP("Net worth forecast"), I18N_NOOP("Forecast (history)"), // unused, s.a. KSettingsHome::slotLoadItems() I18N_NOOP("Assets and Liabilities"), I18N_NOOP("Budget"), I18N_NOOP("CashFlow"), // insert new items above this comment 0 }; const QString KMyMoneyUtils::homePageItemToString(const int idx) { QString rc; if (abs(idx) > 0 && abs(idx) < static_cast(sizeof(homePageItems) / sizeof(homePageItems[0]))) { rc = i18n(homePageItems[abs(idx-1)]); } return rc; } int KMyMoneyUtils::stringToHomePageItem(const QString& txt) { int idx = 0; for (idx = 0; homePageItems[idx] != 0; ++idx) { if (txt == i18n(homePageItems[idx])) return idx + 1; } return 0; } bool KMyMoneyUtils::appendCorrectFileExt(QString& str, const QString& strExtToUse) { bool rc = false; if (!str.isEmpty()) { //find last . deliminator int nLoc = str.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = str.left(nLoc + 1); strExt = str.right(str.length() - (nLoc + 1)); if (strExt.indexOf(strExtToUse, 0, Qt::CaseInsensitive) == -1) { // if the extension given contains a period, we remove ours if (strExtToUse.indexOf('.') != -1) strTemp = strTemp.left(strTemp.length() - 1); //append extension to make complete file name strTemp.append(strExtToUse); str = strTemp; rc = true; } } else { str.append(QLatin1Char('.')); str.append(strExtToUse); rc = true; } } return rc; } void KMyMoneyUtils::checkConstants() { // TODO: port to kf5 #if 0 Q_ASSERT(static_cast(KLocale::ParensAround) == static_cast(MyMoneyMoney::ParensAround)); Q_ASSERT(static_cast(KLocale::BeforeQuantityMoney) == static_cast(MyMoneyMoney::BeforeQuantityMoney)); Q_ASSERT(static_cast(KLocale::AfterQuantityMoney) == static_cast(MyMoneyMoney::AfterQuantityMoney)); Q_ASSERT(static_cast(KLocale::BeforeMoney) == static_cast(MyMoneyMoney::BeforeMoney)); Q_ASSERT(static_cast(KLocale::AfterMoney) == static_cast(MyMoneyMoney::AfterMoney)); #endif } QString KMyMoneyUtils::variableCSS() { QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color(); QColor link = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color(); QString css; css += "\n"; return css; } QString KMyMoneyUtils::findResource(QStandardPaths::StandardLocation type, const QString& filename) { QLocale locale; QString country; QString localeName = locale.bcp47Name(); QString language = localeName; // extract language and country from the bcp47name QRegularExpression regExp(QLatin1String("(\\w+)_(\\w+)")); QRegularExpressionMatch match = regExp.match(localeName); if (match.hasMatch()) { language = match.captured(1); country = match.captured(2); } QString rc; // check that the placeholder is present and set things up if (filename.indexOf("%1") != -1) { /// @fixme somehow I have the impression, that language and country /// mappings to the filename are not correct. This certainly must /// be overhauled at some point in time (ipwizard, 2017-10-22) QString mask = filename.arg("_%1.%2"); rc = QStandardPaths::locate(type, mask.arg(country, language)); // search the given resource if (rc.isEmpty()) { mask = filename.arg("_%1"); rc = QStandardPaths::locate(type, mask.arg(language)); } if (rc.isEmpty()) { // qDebug(QString("html/home_%1.html not found").arg(country).toLatin1()); rc = QStandardPaths::locate(type, mask.arg(country)); } if (rc.isEmpty()) { rc = QStandardPaths::locate(type, filename.arg("")); } } else { rc = QStandardPaths::locate(type, filename); } if (rc.isEmpty()) { qWarning("No resource found for (%s,%s)", qPrintable(QStandardPaths::displayName(type)), qPrintable(filename)); } return rc; } const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t) { MyMoneySplit investmentAccountSplit; foreach (const auto split, t.splits()) { if (!split.accountId().isEmpty()) { auto acc = MyMoneyFile::instance()->account(split.accountId()); if (acc.isInvest()) { return split; } // if we have a reference to an investment account, we remember it here if (acc.accountType() == eMyMoney::Account::Type::Investment) investmentAccountSplit = split; } } // if we haven't found a stock split, we see if we've seen // an investment account on the way. If so, we return it. if (!investmentAccountSplit.id().isEmpty()) return investmentAccountSplit; // if none was found, we return an empty split. return MyMoneySplit(); } KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t) { if (!stockSplit(t).id().isEmpty()) return InvestmentTransaction; if (t.splitCount() < 2) { return Unknown; } else if (t.splitCount() > 2) { // FIXME check for loan transaction here return SplitTransaction; } QString ida, idb; const auto & splits = t.splits(); if (splits.size() > 0) ida = splits[0].accountId(); if (splits.size() > 1) idb = splits[1].accountId(); if (ida.isEmpty() || idb.isEmpty()) return Unknown; MyMoneyAccount a, b; a = MyMoneyFile::instance()->account(ida); b = MyMoneyFile::instance()->account(idb); if ((a.accountGroup() == eMyMoney::Account::Type::Asset || a.accountGroup() == eMyMoney::Account::Type::Liability) && (b.accountGroup() == eMyMoney::Account::Type::Asset || b.accountGroup() == eMyMoney::Account::Type::Liability)) return Transfer; return Normal; } void KMyMoneyUtils::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap& balances) { try { MyMoneyForecast::calculateAutoLoan(schedule, transaction, balances); } catch (const MyMoneyException &e) { KMessageBox::detailedError(0, i18n("Unable to load schedule details"), QString::fromLatin1(e.what())); } } QString KMyMoneyUtils::nextCheckNumber(const MyMoneyAccount& acc) { return getAdjacentNumber(acc.value("lastNumberUsed"), 1); } QString KMyMoneyUtils::nextFreeCheckNumber(const MyMoneyAccount& acc) { auto file = MyMoneyFile::instance(); auto num = acc.value("lastNumberUsed"); if (num.isEmpty()) num = QStringLiteral("1"); // now check if this number has been used already if (file->checkNoUsed(acc.id(), num)) { // if a number has been entered which is immediately prior to // an existing number, the next new number produced would clash // so need to look ahead for free next number // we limit that to a number of tries which depends on the // number of splits in that account (we can't have more) MyMoneyTransactionFilter filter(acc.id()); QList transactions; file->transactionList(transactions, filter); const int maxNumber = transactions.count(); for (int i = 0; i < maxNumber; i++) { if (file->checkNoUsed(acc.id(), num)) { // increment and try again num = getAdjacentNumber(num); } else { // found a free number break; } } } return num; } QString KMyMoneyUtils::getAdjacentNumber(const QString& number, int offset) { // make sure the offset is either -1 or 1 offset = (offset >= 0) ? 1 : -1; QString num = number; // +-#1--+ +#2++-#3-++-#4--+ QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?")); if (exp.indexIn(num) != -1) { QString arg1 = exp.cap(1); QString arg2 = exp.cap(2); QString arg3 = QString::number(exp.cap(3).toULong() + offset); QString arg4 = exp.cap(4); num = QString("%1%2%3%4").arg(arg1, arg2, arg3, arg4); } else { num = QStringLiteral("1"); } return num; } quint64 KMyMoneyUtils::numericPart(const QString & num) { quint64 num64 = 0; QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?")); if (exp.indexIn(num) != -1) { // QString arg1 = exp.cap(1); QString arg2 = exp.cap(2); QString arg3 = QString::number(exp.cap(3).toULongLong()); // QString arg4 = exp.cap(4); num64 = QString("%2%3").arg(arg2, arg3).toULongLong(); } return num64; } QString KMyMoneyUtils::reconcileStateToString(eMyMoney::Split::State flag, bool text) { QString txt; if (text) { switch (flag) { case eMyMoney::Split::State::NotReconciled: txt = i18nc("Reconciliation state 'Not reconciled'", "Not reconciled"); break; case eMyMoney::Split::State::Cleared: txt = i18nc("Reconciliation state 'Cleared'", "Cleared"); break; case eMyMoney::Split::State::Reconciled: txt = i18nc("Reconciliation state 'Reconciled'", "Reconciled"); break; case eMyMoney::Split::State::Frozen: txt = i18nc("Reconciliation state 'Frozen'", "Frozen"); break; default: txt = i18nc("Unknown reconciliation state", "Unknown"); break; } } else { switch (flag) { case eMyMoney::Split::State::NotReconciled: break; case eMyMoney::Split::State::Cleared: txt = i18nc("Reconciliation flag C", "C"); break; case eMyMoney::Split::State::Reconciled: txt = i18nc("Reconciliation flag R", "R"); break; case eMyMoney::Split::State::Frozen: txt = i18nc("Reconciliation flag F", "F"); break; default: txt = i18nc("Flag for unknown reconciliation state", "?"); break; } } return txt; } MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule) { MyMoneyTransaction t = schedule.transaction(); try { if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) { calculateAutoLoan(schedule, t, QMap()); } } catch (const MyMoneyException &e) { qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), e.what()); } t.clearId(); t.setEntryDate(QDate()); return t; } KXmlGuiWindow* KMyMoneyUtils::mainWindow() { foreach (QWidget *widget, QApplication::topLevelWidgets()) { KXmlGuiWindow* result = dynamic_cast(widget); if (result) return result; } return 0; } void KMyMoneyUtils::updateWizardButtons(QWizard* wizard) { // setup text on buttons wizard->setButtonText(QWizard::NextButton, i18nc("Go to next page of the wizard", "&Next")); wizard->setButtonText(QWizard::BackButton, KStandardGuiItem::back().text()); // setup icons wizard->button(QWizard::FinishButton)->setIcon(KStandardGuiItem::ok().icon()); wizard->button(QWizard::CancelButton)->setIcon(KStandardGuiItem::cancel().icon()); wizard->button(QWizard::NextButton)->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon()); wizard->button(QWizard::BackButton)->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon()); } void KMyMoneyUtils::dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, eMyMoney::Split::InvestmentTransactionType& transactionType) { // collect the splits. split references the stock account and should already // be set up. assetAccountSplit references the corresponding asset account (maybe // empty), feeSplits is the list of all expenses and interestSplits // the list of all incomes assetAccountSplit = MyMoneySplit(); // set to none to check later if it was assigned auto file = MyMoneyFile::instance(); foreach (const auto tsplit, transaction.splits()) { auto acc = file->account(tsplit.accountId()); if (tsplit.id() == split.id()) { security = file->security(acc.currencyId()); } else if (acc.accountGroup() == eMyMoney::Account::Type::Expense) { feeSplits.append(tsplit); // feeAmount += tsplit.value(); } else if (acc.accountGroup() == eMyMoney::Account::Type::Income) { interestSplits.append(tsplit); // interestAmount += tsplit.value(); } else { if (assetAccountSplit == MyMoneySplit()) // first asset Account should be our requested brokerage account assetAccountSplit = tsplit; else if (tsplit.value().isNegative()) // the rest (if present) is handled as fee or interest feeSplits.append(tsplit); // and shouldn't be allowed to override assetAccountSplit else if (tsplit.value().isPositive()) interestSplits.append(tsplit); } } // determine transaction type if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::AddShares)) { transactionType = (!split.shares().isNegative()) ? eMyMoney::Split::InvestmentTransactionType::AddShares : eMyMoney::Split::InvestmentTransactionType::RemoveShares; } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)) { transactionType = (!split.value().isNegative()) ? eMyMoney::Split::InvestmentTransactionType::BuyShares : eMyMoney::Split::InvestmentTransactionType::SellShares; } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend)) { transactionType = eMyMoney::Split::InvestmentTransactionType::Dividend; } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend)) { transactionType = eMyMoney::Split::InvestmentTransactionType::ReinvestDividend; } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Yield)) { transactionType = eMyMoney::Split::InvestmentTransactionType::Yield; } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { transactionType = eMyMoney::Split::InvestmentTransactionType::SplitShares; } else if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::InterestIncome)) { transactionType = eMyMoney::Split::InvestmentTransactionType::InterestIncome; } else transactionType = eMyMoney::Split::InvestmentTransactionType::BuyShares; currency.setTradingSymbol("???"); try { currency = file->security(transaction.commodity()); } catch (const MyMoneyException &) { } } void KMyMoneyUtils::processPriceList(const MyMoneyStatement &st) { auto file = MyMoneyFile::instance(); QHash secBySymbol; QHash secByName; const auto securityList = file->securityList(); for (const auto& sec : securityList) { secBySymbol[sec.tradingSymbol()] = sec; secByName[sec.name()] = sec; } for (const auto& stPrice : st.m_listPrices) { auto currency = file->baseCurrency().id(); QString security; if (!stPrice.m_strCurrency.isEmpty()) { security = stPrice.m_strSecurity; currency = stPrice.m_strCurrency; } else if (secBySymbol.contains(stPrice.m_strSecurity)) { security = secBySymbol[stPrice.m_strSecurity].id(); currency = file->security(file->security(security).tradingCurrency()).id(); } else if (secByName.contains(stPrice.m_strSecurity)) { security = secByName[stPrice.m_strSecurity].id(); currency = file->security(file->security(security).tradingCurrency()).id(); } else return; MyMoneyPrice price(security, currency, stPrice.m_date, stPrice.m_amount, stPrice.m_sourceName.isEmpty() ? i18n("Prices Importer") : stPrice.m_sourceName); file->addPrice(price); } } void KMyMoneyUtils::deleteSecurity(const MyMoneySecurity& security, QWidget* parent) { QString msg, msg2; QString dontAsk, dontAsk2; if (security.isCurrency()) { msg = i18n("

Do you really want to remove the currency %1 from the file?

", security.name()); msg2 = i18n("

All exchange rates for currency %1 will be lost.

Do you still want to continue?

", security.name()); dontAsk = "DeleteCurrency"; dontAsk2 = "DeleteCurrencyRates"; } else { msg = i18n("

Do you really want to remove the %1 %2 from the file?

", MyMoneySecurity::securityTypeToString(security.securityType()), security.name()); msg2 = i18n("

All price quotes for %1 %2 will be lost.

Do you still want to continue?

", MyMoneySecurity::securityTypeToString(security.securityType()), security.name()); dontAsk = "DeleteSecurity"; dontAsk2 = "DeleteSecurityPrices"; } if (KMessageBox::questionYesNo(parent, msg, i18n("Delete security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk) == KMessageBox::Yes) { MyMoneyFileTransaction ft; auto file = MyMoneyFile::instance(); QBitArray skip((int)eStorage::Reference::Count); skip.fill(true); skip.clearBit((int)eStorage::Reference::Price); if (file->isReferenced(security, skip)) { if (KMessageBox::questionYesNo(parent, msg2, i18n("Delete prices"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontAsk2) == KMessageBox::Yes) { try { QString secID = security.id(); foreach (auto priceEntry, file->priceList()) { const MyMoneyPrice& price = priceEntry.first(); if (price.from() == secID || price.to() == secID) file->removePrice(price); } ft.commit(); ft.restart(); } catch (const MyMoneyException &) { qDebug("Cannot delete price"); return; } } else return; } try { if (security.isCurrency()) file->removeCurrency(security); else file->removeSecurity(security); ft.commit(); } catch (const MyMoneyException &) { } } } bool KMyMoneyUtils::fileExists(const QUrl &url) { bool fileExists = false; if (url.isValid()) { if (url.isLocalFile() || url.scheme().isEmpty()) { QFileInfo check_file(url.toLocalFile()); fileExists = check_file.exists() && check_file.isFile(); } else { short int detailLevel = 0; // Lowest level: file/dir/symlink/none KIO::StatJob* statjob = KIO::stat(url, KIO::StatJob::SourceSide, detailLevel); bool noerror = statjob->exec(); if (noerror) { // We want a file fileExists = !statjob->statResult().isDir(); } statjob->kill(); } } return fileExists; } QString KMyMoneyUtils::downloadFile(const QUrl &url) { QString filename; KIO::StoredTransferJob *transferjob = KIO::storedGet (url); // KJobWidgets::setWindow(transferjob, this); if (! transferjob->exec()) { KMessageBox::detailedError(nullptr, i18n("Error while loading file '%1'.", url.url()), transferjob->errorString(), i18n("File access error")); return filename; } QTemporaryFile file; file.setAutoRemove(false); file.open(); file.write(transferjob->data()); filename = file.fileName(); file.close(); return filename; } bool KMyMoneyUtils::newPayee(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Payee")) { // Ask the user if that is what he intended to do? const auto msg = i18n("Do you want to add %1 as payer/receiver?", newnameBase); if (KMessageBox::questionYesNo(nullptr, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->payeeByName(newname); newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyPayee p; p.setName(newname); MyMoneyFile::instance()->addPayee(p); id = p.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to add payee"), QString::fromLatin1(e.what())); doit = false; } } return doit; } void KMyMoneyUtils::newTag(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Tag")) { // Ask the user if that is what he intended to do? const auto msg = i18n("Do you want to add %1 as tag?", newnameBase); if (KMessageBox::questionYesNo(nullptr, msg, i18n("New tag"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewTag") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->tagByName(newname); newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyTag ta; ta.setName(newname); MyMoneyFile::instance()->addTag(ta); id = ta.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to add tag"), QString::fromLatin1(e.what())); } } } void KMyMoneyUtils::newInstitution(MyMoneyInstitution& institution) { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(nullptr, i18n("Cannot add institution: %1", QString::fromLatin1(e.what()))); } } QDebug KMyMoneyUtils::debug() { return qDebug() << QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss.zzz")); } MyMoneyForecast KMyMoneyUtils::forecast() { MyMoneyForecast forecast; // override object defaults with those of the application forecast.setForecastCycles(KMyMoneySettings::forecastCycles()); forecast.setAccountsCycle(KMyMoneySettings::forecastAccountCycle()); forecast.setHistoryStartDate(QDate::currentDate().addDays(-forecast.forecastCycles()*forecast.accountsCycle())); forecast.setHistoryEndDate(QDate::currentDate().addDays(-1)); forecast.setForecastDays(KMyMoneySettings::forecastDays()); forecast.setBeginForecastDay(KMyMoneySettings::beginForecastDay()); forecast.setForecastMethod(KMyMoneySettings::forecastMethod()); forecast.setHistoryMethod(KMyMoneySettings::historyMethod()); forecast.setIncludeFutureTransactions(KMyMoneySettings::includeFutureTransactions()); forecast.setIncludeScheduledTransactions(KMyMoneySettings::includeScheduledTransactions()); return forecast; } bool KMyMoneyUtils::canUpdateAllAccounts() { const auto file = MyMoneyFile::instance(); auto rc = false; if (!file->storageAttached()) return rc; QList accList; file->accountList(accList); QList::const_iterator it_a; auto it_p = pPlugins.online.constEnd(); for (it_a = accList.constBegin(); (it_p == pPlugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if ((*it_a).hasOnlineMapping()) { // check if provider is available it_p = pPlugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); if (it_p != pPlugins.online.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (!protocols.isEmpty()) { rc = true; break; } } } } return rc; } void KMyMoneyUtils::showStatementImportResult(const QStringList& resultMessages, uint statementCount) { KMessageBox::informationList(nullptr, i18np("One statement has been processed with the following results:", "%1 statements have been processed with the following results:", statementCount), !resultMessages.isEmpty() ? resultMessages : QStringList { i18np("No new transaction has been imported.", "No new transactions have been imported.", statementCount) }, i18n("Statement import statistics")); } QString KMyMoneyUtils::normalizeNumericString(const qreal& val, const QLocale& loc, const char f, const int prec) { return loc.toString(val, f, prec) .remove(loc.groupSeparator()) .remove(QRegularExpression("0+$")) .remove(QRegularExpression("\\" + loc.decimalPoint() + "$")); } diff --git a/kmymoney/models/accountsmodel.cpp b/kmymoney/models/accountsmodel.cpp index d36080690..08af9eb7d 100644 --- a/kmymoney/models/accountsmodel.cpp +++ b/kmymoney/models/accountsmodel.cpp @@ -1,1255 +1,1255 @@ /* * Copyright 2010-2014 Cristian Oneț * Copyright 2017-2018 Łukasz Wojniłowicz * Copyright 2020 Robert Szczesiak * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "accountsmodel.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneymoney.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "kmymoneysettings.h" #include "icons.h" #include "modelenums.h" #include "mymoneyenums.h" #include "viewenums.h" using namespace Icons; using namespace eAccountsModel; using namespace eMyMoney; class AccountsModelPrivate { Q_DECLARE_PUBLIC(AccountsModel) public: /** * The pimpl. */ AccountsModelPrivate(AccountsModel *qq) : q_ptr(qq), m_file(MyMoneyFile::instance()) { m_columns.append(Column::Account); } virtual ~AccountsModelPrivate() { } void init() { Q_Q(AccountsModel); QStringList headerLabels; for (const auto& column : qAsConst(m_columns)) headerLabels.append(q->getHeaderName(column)); q->setHorizontalHeaderLabels(headerLabels); } void loadPreferredAccount(const MyMoneyAccount &acc, QStandardItem *fromNode /*accounts' regular node*/, const int row, QStandardItem *toNode /*accounts' favourite node*/) { if (acc.value(QStringLiteral("PreferredAccount")) != QLatin1String("Yes")) return; auto favRow = toNode->rowCount(); if (auto favItem = itemFromAccountId(toNode, acc.id())) { favRow = favItem->row(); toNode->removeRow(favRow); } auto itemToClone = fromNode->child(row); if (itemToClone) toNode->insertRow(favRow, itemToClone->clone()); } /** * Load all the sub-accounts recursively. * * @param model The model in which to load the data. * @param accountsItem The item from the model of the parent account of the sub-accounts which are being loaded. * @param favoriteAccountsItem The item of the favorites accounts groups so favorite accounts can be added here also. * @param list The list of the account id's of the sub-accounts which are being loaded. * */ void loadSubaccounts(QStandardItem *node, QStandardItem *favoriteAccountsItem, const QStringList& subaccounts) { for (const auto& subaccStr : subaccounts) { const auto subacc = m_file->account(subaccStr); auto item = new QStandardItem(subacc.name()); // initialize first column of subaccount node->appendRow(item); // add subaccount row to node item->setEditable(false); item->setData(node->data((int)Role::DisplayOrder), (int)Role::DisplayOrder); // inherit display order role from node loadSubaccounts(item, favoriteAccountsItem, subacc.accountList()); // subaccount may have subaccounts as well // set the account data after the children have been loaded const auto row = item->row(); setAccountData(node, row, subacc, m_columns); // initialize rest of columns of subaccount loadPreferredAccount(subacc, node, row, favoriteAccountsItem); // add to favourites node if preferred } } /** * Note: this functions should only be called after the child account data has been set. */ void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) { QStandardItem *cell; auto getCell = [&, row](const auto column) { cell = node->child(row, column); // try to get QStandardItem if (!cell) { // it may be uninitialized cell = new QStandardItem; // so create one node->setChild(row, column, cell); // and add it under the node } }; auto colNum = m_columns.indexOf(Column::Account); if (colNum == -1) return; getCell(colNum); auto font = cell->data(Qt::FontRole).value(); // display the names of closed accounts with strikeout font if (account.isClosed() != font.strikeOut()) font.setStrikeOut(account.isClosed()); if (columns.contains(Column::Account)) { // setting account column cell->setData(account.name(), Qt::DisplayRole); // cell->setData(QVariant::fromValue(account), (int)Role::Account); // is set in setAccountBalanceAndValue cell->setData(QVariant(account.id()), (int)Role::ID); cell->setData(QVariant(account.value("PreferredAccount") == QLatin1String("Yes")), (int)Role::Favorite); cell->setData(QVariant(QIcon(account.accountPixmap(m_reconciledAccount.id().isEmpty() ? false : account.id() == m_reconciledAccount.id()))), Qt::DecorationRole); cell->setData(MyMoneyFile::instance()->accountToCategory(account.id(), true), (int)Role::FullName); cell->setData(font, Qt::FontRole); } // Type if (columns.contains(Column::Type)) { colNum = m_columns.indexOf(Column::Type); if (colNum != -1) { getCell(colNum); cell->setData(account.accountTypeToString(account.accountType()), Qt::DisplayRole); cell->setData(font, Qt::FontRole); } } // Account's number if (columns.contains(Column::AccountNumber)) { colNum = m_columns.indexOf(Column::AccountNumber); if (colNum != -1) { getCell(colNum); cell->setData(account.number(), Qt::DisplayRole); cell->setData(font, Qt::FontRole); } } // Account's sort code if (columns.contains(Column::AccountSortCode)) { colNum = m_columns.indexOf(Column::AccountSortCode); if (colNum != -1) { getCell(colNum); cell->setData(account.value("iban"), Qt::DisplayRole); cell->setData(font, Qt::FontRole); } } const auto checkMark = Icons::get(Icon::DialogOK); switch (account.accountType()) { case Account::Type::Income: case Account::Type::Expense: case Account::Type::Asset: case Account::Type::Liability: // Tax if (columns.contains(Column::Tax)) { colNum = m_columns.indexOf(Column::Tax); if (colNum != -1) { getCell(colNum); if (account.value("Tax").toLower() == "yes") cell->setData(checkMark, Qt::DecorationRole); else cell->setData(QIcon(), Qt::DecorationRole); } } // VAT Account if (columns.contains(Column::VAT)) { colNum = m_columns.indexOf(Column::VAT); if (colNum != -1) { getCell(colNum); if (!account.value("VatAccount").isEmpty()) { const auto vatAccount = MyMoneyFile::instance()->account(account.value("VatAccount")); cell->setData(vatAccount.name(), Qt::DisplayRole); cell->setData(QVariant(Qt::AlignLeft | Qt::AlignVCenter), Qt::TextAlignmentRole); // VAT Rate } else if (!account.value("VatRate").isEmpty()) { const auto vatRate = MyMoneyMoney(account.value("VatRate")) * MyMoneyMoney(100, 1); cell->setData(QString::fromLatin1("%1 %").arg(vatRate.formatMoney(QString(), 1)), Qt::DisplayRole); cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); } else { cell->setData(QString(), Qt::DisplayRole); } } } // CostCenter if (columns.contains(Column::CostCenter)) { colNum = m_columns.indexOf(Column::CostCenter); if (colNum != -1) { getCell(colNum); if (account.isCostCenterRequired()) cell->setData(checkMark, Qt::DecorationRole); else cell->setData(QIcon(), Qt::DecorationRole); } } break; default: break; } // balance and value setAccountBalanceAndValue(node, row, account, columns); } void setInstitutionTotalValue(QStandardItem *node, const int row) { const auto colInstitution = m_columns.indexOf(Column::Account); auto itInstitution = node->child(row, colInstitution); const auto valInstitution = childrenTotalValue(itInstitution, true); itInstitution->setData(QVariant::fromValue(valInstitution ), (int)Role::TotalValue); const auto colTotalValue = m_columns.indexOf(Column::TotalValue); if (colTotalValue == -1) return; auto cell = node->child(row, colTotalValue); if (!cell) { cell = new QStandardItem; node->setChild(row, colTotalValue, cell); } const auto fontColor = KMyMoneySettings::schemeColor(valInstitution.isNegative() ? SchemeColor::Negative : SchemeColor::Positive); cell->setData(QVariant(fontColor), Qt::ForegroundRole); cell->setData(QVariant(itInstitution->data(Qt::FontRole).value()), Qt::FontRole); cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); cell->setData(MyMoneyUtils::formatMoney(valInstitution, m_file->baseCurrency()), Qt::DisplayRole); } void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) { QStandardItem *cell; auto getCell = [&, row](auto column) { cell = node->child(row, column); if (!cell) { cell = new QStandardItem; node->setChild(row, column, cell); } }; // setting account column auto colNum = m_columns.indexOf(Column::Account); if (colNum == -1) return; getCell(colNum); MyMoneyMoney accountBalance, accountValue, accountTotalValue; if (columns.contains(Column::Account)) { // update values only when requested accountBalance = balance(account); accountValue = value(account, accountBalance); accountTotalValue = childrenTotalValue(cell) + accountValue; cell->setData(QVariant::fromValue(account), (int)Role::Account); cell->setData(QVariant::fromValue(accountBalance), (int)Role::Balance); cell->setData(QVariant::fromValue(accountValue), (int)Role::Value); cell->setData(QVariant::fromValue(accountTotalValue), (int)Role::TotalValue); } else { // otherwise save up on tedious calculations accountBalance = cell->data((int)Role::Balance).value(); accountValue = cell->data((int)Role::Value).value(); accountTotalValue = cell->data((int)Role::TotalValue).value(); } const auto font = QVariant(cell->data(Qt::FontRole).value()); const auto alignment = QVariant(Qt::AlignRight | Qt::AlignVCenter); // setting total balance column if (columns.contains(Column::TotalBalance)) { colNum = m_columns.indexOf(Column::TotalBalance); if (colNum != -1) { const auto accountBalanceStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountBalance, m_file->security(account.currencyId()))); getCell(colNum); // only show the balance, if its a different security/currency if (m_file->security(account.currencyId()) != m_file->baseCurrency()) { cell->setData(accountBalanceStr, Qt::DisplayRole); } cell->setData(font, Qt::FontRole); cell->setData(alignment, Qt::TextAlignmentRole); } } // setting posted value column if (columns.contains(Column::PostedValue)) { colNum = m_columns.indexOf(Column::PostedValue); if (colNum != -1) { const auto accountValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountValue, m_file->baseCurrency())); getCell(colNum); const auto fontColor = KMyMoneySettings::schemeColor(accountValue.isNegative() ? SchemeColor::Negative : SchemeColor::Positive); cell->setData(QVariant(fontColor), Qt::ForegroundRole); cell->setData(accountValueStr, Qt::DisplayRole); cell->setData(font, Qt::FontRole); cell->setData(alignment, Qt::TextAlignmentRole); } } // setting total value column if (columns.contains(Column::TotalValue)) { colNum = m_columns.indexOf(Column::TotalValue); if (colNum != -1) { const auto accountTotalValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountTotalValue, m_file->baseCurrency())); getCell(colNum); const auto fontColor = KMyMoneySettings::schemeColor(accountTotalValue.isNegative() ? SchemeColor::Negative : SchemeColor::Positive); cell->setData(accountTotalValueStr, Qt::DisplayRole); cell->setData(font, Qt::FontRole); cell->setData(QVariant(fontColor), Qt::ForegroundRole); cell->setData(alignment, Qt::TextAlignmentRole); } } } /** * Compute the balance of the given account. * * @param account The account for which the balance is being computed. */ MyMoneyMoney balance(const MyMoneyAccount &account) { MyMoneyMoney balance; // a closed account has a zero balance by definition if (!account.isClosed()) { // account.balance() is not compatible with stock accounts if (account.isInvest()) balance = m_file->balance(account.id()); else balance = account.balance(); } // for income and liability accounts, we reverse the sign switch (account.accountGroup()) { case Account::Type::Income: case Account::Type::Liability: case Account::Type::Equity: balance = -balance; break; default: break; } return balance; } /** * Compute the value of the given account using the provided balance. * The value is defined as the balance of the account converted to the base currency. * * @param account The account for which the value is being computed. * @param balance The balance which should be used. * * @see balance */ MyMoneyMoney value(const MyMoneyAccount &account, const MyMoneyMoney &balance) { if (account.isClosed()) return MyMoneyMoney(); QList prices; MyMoneySecurity security = m_file->baseCurrency(); try { if (account.isInvest()) { security = m_file->security(account.currencyId()); prices += m_file->price(account.currencyId(), security.tradingCurrency()); if (security.tradingCurrency() != m_file->baseCurrency().id()) { MyMoneySecurity sec = m_file->security(security.tradingCurrency()); prices += m_file->price(sec.id(), m_file->baseCurrency().id()); } } else if (account.currencyId() != m_file->baseCurrency().id()) { security = m_file->security(account.currencyId()); prices += m_file->price(account.currencyId(), m_file->baseCurrency().id()); } } catch (const MyMoneyException &e) { qDebug() << Q_FUNC_INFO << " caught exception while adding " << account.name() << "[" << account.id() << "]: " << e.what(); } MyMoneyMoney value = balance; { QList::const_iterator it_p; QString securityID = account.currencyId(); for (it_p = prices.constBegin(); it_p != prices.constEnd(); ++it_p) { value = (value * (MyMoneyMoney::ONE / (*it_p).rate(securityID))).convertPrecision(m_file->security(securityID).pricePrecision()); if ((*it_p).from() == securityID) securityID = (*it_p).to(); else securityID = (*it_p).from(); } value = value.convert(m_file->baseCurrency().smallestAccountFraction()); } return value; } /** * Compute the total value of the child accounts of the given account. * Note that the value of the current account is not in this sum. Also, * before calling this function, the caller must make sure that the values * of all sub-account must be already in the model in the @ref Role::Value. * * @param index The index of the account in the model. * @see value */ MyMoneyMoney childrenTotalValue(const QStandardItem *node, const bool isInstitutionsModel = false) { MyMoneyMoney totalValue; if (!node) return totalValue; for (auto i = 0; i < node->rowCount(); ++i) { const auto childNode = node->child(i, (int)Column::Account); if (childNode->hasChildren()) totalValue += childrenTotalValue(childNode, isInstitutionsModel); const auto data = childNode->data((int)Role::Value); if (data.isValid()) { auto value = data.value(); if (isInstitutionsModel) { const auto account = childNode->data((int)Role::Account).value(); if (account.accountGroup() == Account::Type::Liability) value = -value; } totalValue += value; } } return totalValue; } /** * Function to get the item from an account id. * * @param parent The parent to localize the search in the child items of this parameter. * @param accountId Search based on this parameter. * * @return The item corresponding to the given account id, NULL if the account was not found. */ QStandardItem *itemFromAccountId(QStandardItem *parent, const QString &accountId) { auto const model = parent->model(); const auto list = model->match(model->index(0, 0, parent->index()), (int)Role::ID, QVariant(accountId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); if (!list.isEmpty()) return model->itemFromIndex(list.front()); // TODO: if not found at this item search for it in the model and if found reparent it. return nullptr; } /** * Function to get the item from an account id without knowing it's parent item. * Note that for the accounts which have two items in the model (favorite accounts) * the account item which is not the child of the favorite accounts item is always returned. * * @param model The model in which to search. * @param accountId Search based on this parameter. * * @return The item corresponding to the given account id, NULL if the account was not found. */ QStandardItem *itemFromAccountId(QStandardItemModel *model, const QString &accountId) { const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(accountId), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); for (const auto& index : list) { // always return the account which is not the child of the favorite accounts item if (index.parent().data((int)Role::ID).toString() != AccountsModel::favoritesAccountId) return model->itemFromIndex(index); } return nullptr; } AccountsModel *q_ptr; /** * Used to load the accounts data. */ MyMoneyFile *m_file; /** * Used to emit the @ref netWorthChanged signal. */ MyMoneyMoney m_lastNetWorth; /** * Used to emit the @ref profitChanged signal. */ MyMoneyMoney m_lastProfit; /** * Used to set the reconciliation flag. */ MyMoneyAccount m_reconciledAccount; QList m_columns; static const QString m_accountsModelConfGroup; static const QString m_accountsModelColumnSelection; }; const QString AccountsModelPrivate::m_accountsModelConfGroup = QStringLiteral("AccountsModel"); const QString AccountsModelPrivate::m_accountsModelColumnSelection = QStringLiteral("ColumnSelection"); const QString AccountsModel::favoritesAccountId(QStringLiteral("Favorites")); /** * The constructor is private so that only the @ref Models object can create such an object. */ AccountsModel::AccountsModel(QObject *parent) : QStandardItemModel(parent), d_ptr(new AccountsModelPrivate(this)) { Q_D(AccountsModel); d->init(); } AccountsModel::AccountsModel(AccountsModelPrivate &dd, QObject *parent) : QStandardItemModel(parent), d_ptr(&dd) { Q_D(AccountsModel); d->init(); } AccountsModel::~AccountsModel() { Q_D(AccountsModel); delete d; } /** * Perform the initial load of the model data * from the @ref MyMoneyFile. * */ void AccountsModel::load() { Q_D(AccountsModel); blockSignals(true); QStandardItem *rootItem = invisibleRootItem(); QFont font; font.setBold(true); // adding favourite accounts node auto favoriteAccountsItem = new QStandardItem(); favoriteAccountsItem->setEditable(false); rootItem->appendRow(favoriteAccountsItem); { QMap itemData; itemData[Qt::DisplayRole] = itemData[Qt::EditRole] = itemData[(int)Role::FullName] = i18n("Favorites"); itemData[Qt::FontRole] = font; - itemData[Qt::DecorationRole] = Icons::get(Icon::ViewBankAccount); + itemData[Qt::DecorationRole] = Icons::get(Icon::BankAccount); itemData[(int)Role::ID] = favoritesAccountId; itemData[(int)Role::DisplayOrder] = 0; this->setItemData(favoriteAccountsItem->index(), itemData); } // adding account categories (asset, liability, etc.) node const QVector categories { Account::Type::Asset, Account::Type::Liability, Account::Type::Income, Account::Type::Expense, Account::Type::Equity }; for (const auto category : categories) { MyMoneyAccount account; QString accountName; int displayOrder; switch (category) { case Account::Type::Asset: // Asset accounts account = d->m_file->asset(); accountName = i18n("Asset accounts"); displayOrder = 1; break; case Account::Type::Liability: // Liability accounts account = d->m_file->liability(); accountName = i18n("Liability accounts"); displayOrder = 2; break; case Account::Type::Income: // Income categories account = d->m_file->income(); accountName = i18n("Income categories"); displayOrder = 3; break; case Account::Type::Expense: // Expense categories account = d->m_file->expense(); accountName = i18n("Expense categories"); displayOrder = 4; break; case Account::Type::Equity: // Equity accounts account = d->m_file->equity(); accountName = i18n("Equity accounts"); displayOrder = 5; break; default: continue; } auto accountsItem = new QStandardItem(accountName); accountsItem->setEditable(false); rootItem->appendRow(accountsItem); { QMap itemData; itemData[Qt::DisplayRole] = accountName; itemData[(int)Role::FullName] = itemData[Qt::EditRole] = QVariant::fromValue(MyMoneyFile::instance()->accountToCategory(account.id(), true)); itemData[Qt::FontRole] = font; itemData[(int)Role::DisplayOrder] = displayOrder; this->setItemData(accountsItem->index(), itemData); } // adding accounts (specific bank/investment accounts) belonging to given accounts category const auto& accountList = account.accountList(); for (const auto& accStr : accountList) { const auto acc = d->m_file->account(accStr); auto item = new QStandardItem(acc.name()); accountsItem->appendRow(item); item->setEditable(false); auto subaccountsStr = acc.accountList(); // filter out stocks with zero balance if requested by user for (auto subaccStr = subaccountsStr.begin(); subaccStr != subaccountsStr.end();) { const auto subacc = d->m_file->account(*subaccStr); if (subacc.isInvest() && KMyMoneySettings::hideZeroBalanceEquities() && subacc.balance().isZero()) subaccStr = subaccountsStr.erase(subaccStr); else ++subaccStr; } // adding subaccounts (e.g. stocks under given investment account) belonging to given account d->loadSubaccounts(item, favoriteAccountsItem, subaccountsStr); const auto row = item->row(); d->setAccountData(accountsItem, row, acc, d->m_columns); d->loadPreferredAccount(acc, accountsItem, row, favoriteAccountsItem); } d->setAccountData(rootItem, accountsItem->row(), account, d->m_columns); } blockSignals(false); checkNetWorth(); checkProfit(); } QModelIndex AccountsModel::accountById(const QString& id) const { QModelIndexList accountList = match(index(0, 0), (int)Role::ID, id, 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); if(accountList.count() == 1) { return accountList.first(); } return QModelIndex(); } QList *AccountsModel::getColumns() { Q_D(AccountsModel); return &d->m_columns; } void AccountsModel::setColumnVisibility(const Column column, const bool show) { Q_D(AccountsModel); const auto ixCol = d->m_columns.indexOf(column); // get column index in our column's map if (!show && ixCol != -1) { // start removing column row by row from bottom to up d->m_columns.removeOne(column); // remove it from our column's map blockSignals(true); // block signals to not emit resources consuming dataChanged for (auto i = 0; i < rowCount(); ++i) { // recursive lambda function to remove cell belonging to unwanted column from all rows auto removeCellFromRow = [=](auto &&self, QStandardItem *item) -> bool { for(auto j = 0; j < item->rowCount(); ++j) { auto childItem = item->child(j); if (childItem->hasChildren()) self(self, childItem); childItem->removeColumn(ixCol); } return true; }; auto topItem = item(i); if (topItem->hasChildren()) removeCellFromRow(removeCellFromRow, topItem); topItem->removeColumn(ixCol); } blockSignals(false); // unblock signals, so model can update itself with new column removeColumn(ixCol); // remove column from invisible root item which triggers model's view update } else if (show && ixCol == -1) { // start inserting columns row by row from up to bottom (otherwise columns will be inserted automatically) auto model = qobject_cast(this); const auto isInstitutionsModel = model ? true : false; // if it's institution's model, then don't set any data on institution nodes auto newColPos = 0; for(; newColPos < d->m_columns.count(); ++newColPos) { if (d->m_columns.at(newColPos) > column) break; } d->m_columns.insert(newColPos, column); // insert columns according to enum order for cleanliness insertColumn(newColPos); setHorizontalHeaderItem(newColPos, new QStandardItem(getHeaderName(column))); blockSignals(true); for (auto i = 0; i < rowCount(); ++i) { // recursive lambda function to remove cell belonging to unwanted column from all rows auto addCellToRow = [&, newColPos](auto &&self, QStandardItem *item) -> bool { for(auto j = 0; j < item->rowCount(); ++j) { auto childItem = item->child(j); childItem->insertColumns(newColPos, 1); if (childItem->hasChildren()) self(self, childItem); d->setAccountData(item, j, childItem->data((int)Role::Account).value(), QList {column}); } return true; }; auto topItem = item(i); topItem->insertColumns(newColPos, 1); if (topItem->hasChildren()) addCellToRow(addCellToRow, topItem); if (isInstitutionsModel) d->setInstitutionTotalValue(invisibleRootItem(), i); else if (i !=0) // favourites node doesn't play well here, so exclude it from update d->setAccountData(invisibleRootItem(), i, topItem->data((int)Role::Account).value(), QList {column}); } blockSignals(false); } } QString AccountsModel::getHeaderName(const Column column) { switch(column) { case Column::Account: return i18n("Account"); case Column::Type: return i18n("Type"); case Column::Tax: return i18nc("Column heading for category in tax report", "Tax"); case Column::VAT: return i18nc("Column heading for VAT category", "VAT"); case Column::CostCenter: return i18nc("Column heading for Cost Center", "CC"); case Column::TotalBalance: return i18n("Total Balance"); case Column::PostedValue: return i18n("Posted Value"); case Column::TotalValue: return i18n("Total Value"); case Column::AccountNumber: return i18n("Number"); case Column::AccountSortCode: return i18nc("IBAN, SWIFT, etc.", "Sort Code"); default: return QString(); } } /** * Check if netWorthChanged should be emitted. */ void AccountsModel::checkNetWorth() { Q_D(AccountsModel); // compute the net worth QModelIndexList assetList = match(index(0, 0), (int)Role::ID, MyMoneyFile::instance()->asset().id(), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); QModelIndexList liabilityList = match(index(0, 0), (int)Role::ID, MyMoneyFile::instance()->liability().id(), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); MyMoneyMoney netWorth; if (!assetList.isEmpty() && !liabilityList.isEmpty()) { const auto assetValue = data(assetList.front(), (int)Role::TotalValue); const auto liabilityValue = data(liabilityList.front(), (int)Role::TotalValue); if (assetValue.isValid() && liabilityValue.isValid()) netWorth = assetValue.value() - liabilityValue.value(); } if (d->m_lastNetWorth != netWorth) { d->m_lastNetWorth = netWorth; emit netWorthChanged(QVariantList {QVariant::fromValue(d->m_lastNetWorth)}, eView::Intent::UpdateNetWorth); } } /** * Check if profitChanged should be emitted. */ void AccountsModel::checkProfit() { Q_D(AccountsModel); // compute the profit const auto incomeList = match(index(0, 0), (int)Role::ID, MyMoneyFile::instance()->income().id(), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); const auto expenseList = match(index(0, 0), (int)Role::ID, MyMoneyFile::instance()->expense().id(), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); MyMoneyMoney profit; if (!incomeList.isEmpty() && !expenseList.isEmpty()) { const auto incomeValue = data(incomeList.front(), (int)Role::TotalValue); const auto expenseValue = data(expenseList.front(), (int)Role::TotalValue); if (incomeValue.isValid() && expenseValue.isValid()) profit = incomeValue.value() - expenseValue.value(); } if (d->m_lastProfit != profit) { d->m_lastProfit = profit; emit profitChanged(QVariantList {QVariant::fromValue(d->m_lastProfit)}, eView::Intent::UpdateProfit); } } MyMoneyMoney AccountsModel::accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance) { Q_D(AccountsModel); return d->value(account, balance); } /** * This slot should be connected so that the model will be notified which account is being reconciled. */ void AccountsModel::slotReconcileAccount(const MyMoneyAccount &account, const QDate &reconciliationDate, const MyMoneyMoney &endingBalance) { Q_D(AccountsModel); Q_UNUSED(reconciliationDate) Q_UNUSED(endingBalance) if (d->m_reconciledAccount.id() != account.id()) { // first clear the flag of the old reconciliation account if (!d->m_reconciledAccount.id().isEmpty()) { const auto list = match(index(0, 0), (int)Role::ID, QVariant(d->m_reconciledAccount.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); for (const auto& index : list) setData(index, QVariant(QIcon(account.accountPixmap(false))), Qt::DecorationRole); } // then set the reconciliation flag of the new reconciliation account const auto list = match(index(0, 0), (int)Role::ID, QVariant(account.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); for (const auto& index : list) setData(index, QVariant(QIcon(account.accountPixmap(true))), Qt::DecorationRole); d->m_reconciledAccount = account; } } /** * Notify the model that an object has been added. An action is performed only if the object is an account. * */ void AccountsModel::slotObjectAdded(File::Object objType, const QString& id) { Q_D(AccountsModel); if (objType != File::Object::Account) return; const auto account = MyMoneyFile::instance()->account(id); auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId); auto parentAccountItem = d->itemFromAccountId(this, account.parentAccountId()); auto item = d->itemFromAccountId(parentAccountItem, account.id()); if (!item) { item = new QStandardItem(account.name()); parentAccountItem->appendRow(item); item->setEditable(false); } // load the sub-accounts if there are any - there could be sub accounts if this is an add operation // that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change d->loadSubaccounts(item, favoriteAccountsItem, account.accountList()); const auto row = item->row(); d->setAccountData(parentAccountItem, row, account, d->m_columns); d->loadPreferredAccount(account, parentAccountItem, row, favoriteAccountsItem); checkNetWorth(); checkProfit(); } /** * Notify the model that an object has been modified. An action is performed only if the object is an account. * */ void AccountsModel::slotObjectModified(File::Object objType, const QString& id) { Q_D(AccountsModel); if (objType != File::Object::Account) return; const auto account = MyMoneyFile::instance()->account(id); auto accountItem = d->itemFromAccountId(this, id); if (!accountItem) { qDebug() << "Unexpected null accountItem in AccountsModel::slotObjectModified"; return; } const auto oldAccount = accountItem->data((int)Role::Account).value(); if (oldAccount.parentAccountId() == account.parentAccountId()) { // the hierarchy did not change so update the account data auto parentAccountItem = accountItem->parent(); if (!parentAccountItem) parentAccountItem = this->invisibleRootItem(); const auto row = accountItem->row(); d->setAccountData(parentAccountItem, row, account, d->m_columns); // and the child of the favorite item if the account is a favorite account or it's favorite status has just changed if (auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId)) { if (account.value("PreferredAccount") == QLatin1String("Yes")) d->loadPreferredAccount(account, parentAccountItem, row, favoriteAccountsItem); else if (auto favItem = d->itemFromAccountId(favoriteAccountsItem, account.id())) favoriteAccountsItem->removeRow(favItem->row()); // it's not favorite anymore } } else { // this means that the hierarchy was changed - simulate this with a remove followed by and add operation slotObjectRemoved(File::Object::Account, oldAccount.id()); slotObjectAdded(File::Object::Account, id); } checkNetWorth(); checkProfit(); } /** * Notify the model that an object has been removed. An action is performed only if the object is an account. * */ void AccountsModel::slotObjectRemoved(File::Object objType, const QString& id) { if (objType != File::Object::Account) return; const auto list = match(index(0, 0), (int)Role::ID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); for (const auto& index : list) removeRow(index.row(), index.parent()); checkNetWorth(); checkProfit(); } /** * Notify the model that the account balance has been changed. */ void AccountsModel::slotBalanceOrValueChanged(const MyMoneyAccount &account) { Q_D(AccountsModel); auto itParent = d->itemFromAccountId(this, account.id()); // get node of account in model auto isTopLevel = false; // it could be top-level but we don't know it yet while (itParent && !isTopLevel) { // loop in which we set total values and balances from the bottom to the top auto itCurrent = itParent; const auto accCurrent = d->m_file->account(itCurrent->data((int)Role::Account).value().id()); if (accCurrent.id().isEmpty()) { // this is institution d->setInstitutionTotalValue(invisibleRootItem(), itCurrent->row()); break; // it's top-level node so nothing above that; } itParent = itCurrent->parent(); if (!itParent) { itParent = this->invisibleRootItem(); isTopLevel = true; } d->setAccountBalanceAndValue(itParent, itCurrent->row(), accCurrent, d->m_columns); } checkNetWorth(); checkProfit(); } /** * The pimpl of the @ref InstitutionsModel derived from the pimpl of the @ref AccountsModel. */ class InstitutionsModelPrivate : public AccountsModelPrivate { public: InstitutionsModelPrivate(InstitutionsModel *qq) : AccountsModelPrivate(qq) { } ~InstitutionsModelPrivate() override { } /** * Function to get the institution item from an institution id. * * @param model The model in which to look for the item. * @param institutionId Search based on this parameter. * * @return The item corresponding to the given institution id, NULL if the institution was not found. */ QStandardItem *institutionItemFromId(QStandardItemModel *model, const QString &institutionId) { const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(institutionId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); if (!list.isEmpty()) return model->itemFromIndex(list.front()); return nullptr; // this should rarely fail as we add all institutions early on } /** * Function to add the account item to it's corresponding institution item. * * @param model The model where to add the item. * @param account The account for which to create the item. * */ void loadInstitution(QStandardItemModel *model, const MyMoneyAccount &account) { if (!account.isAssetLiability() && !account.isInvest()) return; // we've got account but don't know under which institution it should be added, so we find it out auto idInstitution = account.institutionId(); if (account.isInvest()) { // if it's stock account then... const auto investmentAccount = m_file->account(account.parentAccountId()); // ...get investment account it's under and... idInstitution = investmentAccount.institutionId(); // ...get institution from investment account } auto itInstitution = institutionItemFromId(model, idInstitution); auto itAccount = itemFromAccountId(itInstitution, account.id()); // check if account already exists under institution // only stock accounts are added to their parent in the institutions view // this makes hierarchy maintenance a lot easier since the stock accounts // are the only ones that always have the same institution as their parent auto itInvestmentAccount = account.isInvest() ? itemFromAccountId(itInstitution, account.parentAccountId()) : nullptr; if (!itAccount) { itAccount = new QStandardItem(account.name()); if (itInvestmentAccount) // stock account nodes go under investment account nodes and... itInvestmentAccount->appendRow(itAccount); else if (itInstitution) // ...the rest goes under institution's node itInstitution->appendRow(itAccount); else return; itAccount->setEditable(false); } if (itInvestmentAccount) { setAccountData(itInvestmentAccount, itAccount->row(), account, m_columns); // set data for stock account node setAccountData(itInstitution, itInvestmentAccount->row(), m_file->account(account.parentAccountId()), m_columns); // set data for investment account node } else if (itInstitution) { setAccountData(itInstitution, itAccount->row(), account, m_columns); } } /** * Function to add an institution item to the model. * * @param model The model in which to add the item. * @param institution The institution object which should be represented by the item. * */ void addInstitutionItem(QStandardItemModel *model, const MyMoneyInstitution &institution) { QFont font; font.setBold(true); - auto itInstitution = new QStandardItem(Icons::get(Icon::ViewInstitutions), institution.name()); + auto itInstitution = new QStandardItem(Icons::get(Icon::Institution), institution.name()); itInstitution->setFont(font); itInstitution->setData(QVariant::fromValue(MyMoneyMoney()), (int)Role::TotalValue); itInstitution->setData(institution.id(), (int)Role::ID); itInstitution->setData(QVariant::fromValue(institution), (int)Role::Account); itInstitution->setData(6, (int)Role::DisplayOrder); itInstitution->setEditable(false); model->invisibleRootItem()->appendRow(itInstitution); setInstitutionTotalValue(model->invisibleRootItem(), itInstitution->row()); } }; /** * The institution model contains the accounts grouped by institution. * */ InstitutionsModel::InstitutionsModel(QObject *parent) : AccountsModel(*new InstitutionsModelPrivate(this), parent) { } InstitutionsModel::~InstitutionsModel() { } /** * Perform the initial load of the model data * from the @ref MyMoneyFile. * */ void InstitutionsModel::load() { Q_D(InstitutionsModel); // create items for all the institutions auto institutionList = d->m_file->institutionList(); MyMoneyInstitution none; none.setName(i18n("Accounts with no institution assigned")); institutionList.append(none); for (const auto& institution : institutionList) // add all known institutions as top-level nodes d->addInstitutionItem(this, institution); QList accountsList; QList stocksList; d->m_file->accountList(accountsList); for (const auto& account : accountsList) { // add account nodes under institution nodes... if (account.isInvest()) // ...but wait with stocks until investment accounts appear stocksList.append(account); else d->loadInstitution(this, account); } for (const auto& stock : stocksList) { if (!(KMyMoneySettings::hideZeroBalanceEquities() && stock.balance().isZero())) d->loadInstitution(this, stock); } for (auto i = 0 ; i < rowCount(); ++i) d->setInstitutionTotalValue(invisibleRootItem(), i); } /** * Notify the model that an object has been added. An action is performed only if the object is an account or an institution. * */ void InstitutionsModel::slotObjectAdded(File::Object objType, const QString& id) { Q_D(InstitutionsModel); if (objType == File::Object::Institution) { // if an institution was added then add the item which will represent it const auto institution = MyMoneyFile::instance()->institution(id); d->addInstitutionItem(this, institution); } if (objType != File::Object::Account) return; // if an account was added then add the item which will represent it only for real accounts const auto account = MyMoneyFile::instance()->account(id); // nothing to do for root accounts and categories if (account.parentAccountId().isEmpty() || account.isIncomeExpense()) return; // load the account into the institution d->loadInstitution(this, account); // load the investment sub-accounts if there are any - there could be sub-accounts if this is an add operation // that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change const auto& sAccounts = account.accountList(); if (!sAccounts.isEmpty()) { QList subAccounts; d->m_file->accountList(subAccounts, sAccounts); for (const auto& subAccount : subAccounts) { if (subAccount.isInvest()) { d->loadInstitution(this, subAccount); } } } } /** * Notify the model that an object has been modified. An action is performed only if the object is an account or an institution. * */ void InstitutionsModel::slotObjectModified(File::Object objType, const QString& id) { Q_D(InstitutionsModel); if (objType == File::Object::Institution) { // if an institution was modified then modify the item which represents it const auto institution = MyMoneyFile::instance()->institution(id); if (auto institutionItem = d->institutionItemFromId(this, id)) { institutionItem->setData(institution.name(), Qt::DisplayRole); institutionItem->setData(QVariant::fromValue(institution), (int)Role::Account); institutionItem->setIcon(MyMoneyInstitution::pixmap()); } } if (objType != File::Object::Account) return; // if an account was modified then modify the item which represents it const auto account = MyMoneyFile::instance()->account(id); // nothing to do for root accounts, categories and equity accounts since they don't have a representation in this model if (account.parentAccountId().isEmpty() || account.isIncomeExpense() || account.accountType() == Account::Type::Equity) return; auto accountItem = d->itemFromAccountId(this, account.id()); const auto oldAccount = accountItem->data((int)Role::Account).value(); if (oldAccount.institutionId() == account.institutionId()) { // the hierarchy did not change so update the account data d->setAccountData(accountItem->parent(), accountItem->row(), account, d->m_columns); } else { // this means that the hierarchy was changed - simulate this with a remove followed by and add operation slotObjectRemoved(File::Object::Account, oldAccount.id()); slotObjectAdded(File::Object::Account, id); } } /** * Notify the model that an object has been removed. An action is performed only if the object is an account or an institution. * */ void InstitutionsModel::slotObjectRemoved(File::Object objType, const QString& id) { Q_D(InstitutionsModel); if (objType == File::Object::Institution) { // if an institution was removed then remove the item which represents it if (auto itInstitution = d->institutionItemFromId(this, id)) removeRow(itInstitution->row(), itInstitution->index().parent()); } if (objType != File::Object::Account) return; // if an account was removed then remove the item which represents it and recompute the institution's value auto itAccount = d->itemFromAccountId(this, id); if (!itAccount) return; // this could happen if the account isIncomeExpense const auto account = itAccount->data((int)Role::Account).value(); if (auto itInstitution = d->itemFromAccountId(this, account.institutionId())) { AccountsModel::slotObjectRemoved(objType, id); d->setInstitutionTotalValue(invisibleRootItem(), itInstitution->row()); } } diff --git a/kmymoney/mymoney/mymoneyaccount.cpp b/kmymoney/mymoney/mymoneyaccount.cpp index adb4188ae..2e16e9b82 100644 --- a/kmymoney/mymoney/mymoneyaccount.cpp +++ b/kmymoney/mymoney/mymoneyaccount.cpp @@ -1,627 +1,627 @@ /* * Copyright 2000-2002 Michael Edwardes * Copyright 2001 Felix Rodriguez * Copyright 2002-2003 Kevin Tambascio * Copyright 2006-2017 Thomas Baumgart * Copyright 2006 Ace Jones * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mymoneyaccount.h" #include "mymoneyaccount_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneyexception.h" #include "mymoneysplit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyinstitution.h" #include "mymoneypayee.h" #include "payeeidentifier/payeeidentifiertyped.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" #include "icons/icons.h" using namespace Icons; MyMoneyAccount::MyMoneyAccount() : MyMoneyObject(*new MyMoneyAccountPrivate), MyMoneyKeyValueContainer() { } MyMoneyAccount::MyMoneyAccount(const QString &id): MyMoneyObject(*new MyMoneyAccountPrivate, id), MyMoneyKeyValueContainer() { } MyMoneyAccount::MyMoneyAccount(const MyMoneyAccount& other) : MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), other.id()), MyMoneyKeyValueContainer(other) { } MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& other) : MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), id), MyMoneyKeyValueContainer(other) { } MyMoneyAccount::~MyMoneyAccount() { } void MyMoneyAccount::touch() { setLastModified(QDate::currentDate()); } eMyMoney::Account::Type MyMoneyAccount::accountType() const { Q_D(const MyMoneyAccount); return d->m_accountType; } void MyMoneyAccount::setAccountType(const Account::Type type) { Q_D(MyMoneyAccount); d->m_accountType = type; } QString MyMoneyAccount::institutionId() const { Q_D(const MyMoneyAccount); return d->m_institution; } void MyMoneyAccount::setInstitutionId(const QString& id) { Q_D(MyMoneyAccount); d->m_institution = id; } QString MyMoneyAccount::name() const { Q_D(const MyMoneyAccount); return d->m_name; } void MyMoneyAccount::setName(const QString& name) { Q_D(MyMoneyAccount); d->m_name = name; } QString MyMoneyAccount::number() const { Q_D(const MyMoneyAccount); return d->m_number; } void MyMoneyAccount::setNumber(const QString& number) { Q_D(MyMoneyAccount); d->m_number = number; } QString MyMoneyAccount::description() const { Q_D(const MyMoneyAccount); return d->m_description; } void MyMoneyAccount::setDescription(const QString& desc) { Q_D(MyMoneyAccount); d->m_description = desc; } QDate MyMoneyAccount::openingDate() const { Q_D(const MyMoneyAccount); return d->m_openingDate; } void MyMoneyAccount::setOpeningDate(const QDate& date) { Q_D(MyMoneyAccount); d->m_openingDate = date; } QDate MyMoneyAccount::lastReconciliationDate() const { Q_D(const MyMoneyAccount); return d->m_lastReconciliationDate; } void MyMoneyAccount::setLastReconciliationDate(const QDate& date) { Q_D(MyMoneyAccount); d->m_lastReconciliationDate = date; } QDate MyMoneyAccount::lastModified() const { Q_D(const MyMoneyAccount); return d->m_lastModified; } void MyMoneyAccount::setLastModified(const QDate& date) { Q_D(MyMoneyAccount); d->m_lastModified = date; } QString MyMoneyAccount::parentAccountId() const { Q_D(const MyMoneyAccount); return d->m_parentAccount; } void MyMoneyAccount::setParentAccountId(const QString& parent) { Q_D(MyMoneyAccount); d->m_parentAccount = parent; } QStringList MyMoneyAccount::accountList() const { Q_D(const MyMoneyAccount); return d->m_accountList; } int MyMoneyAccount::accountCount() const { Q_D(const MyMoneyAccount); return d->m_accountList.count(); } void MyMoneyAccount::addAccountId(const QString& account) { Q_D(MyMoneyAccount); if (!d->m_accountList.contains(account)) d->m_accountList += account; } void MyMoneyAccount::removeAccountIds() { Q_D(MyMoneyAccount); d->m_accountList.clear(); } void MyMoneyAccount::removeAccountId(const QString& account) { Q_D(MyMoneyAccount); const auto pos = d->m_accountList.indexOf(account); if (pos != -1) d->m_accountList.removeAt(pos); } bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const { Q_D(const MyMoneyAccount); auto d2 = static_cast(right.d_func()); return (MyMoneyKeyValueContainer::operator==(right) && MyMoneyObject::operator==(right) && (d->m_accountList == d2->m_accountList) && (d->m_accountType == d2->m_accountType) && (d->m_lastModified == d2->m_lastModified) && (d->m_lastReconciliationDate == d2->m_lastReconciliationDate) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) && ((d->m_number.length() == 0 && d2->m_number.length() == 0) || (d->m_number == d2->m_number)) && ((d->m_description.length() == 0 && d2->m_description.length() == 0) || (d->m_description == d2->m_description)) && (d->m_openingDate == d2->m_openingDate) && (d->m_parentAccount == d2->m_parentAccount) && (d->m_currencyId == d2->m_currencyId) && (d->m_institution == d2->m_institution)); } Account::Type MyMoneyAccount::accountGroup() const { Q_D(const MyMoneyAccount); switch (d->m_accountType) { case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::Currency: case Account::Type::Investment: case Account::Type::MoneyMarket: case Account::Type::CertificateDep: case Account::Type::AssetLoan: case Account::Type::Stock: return Account::Type::Asset; case Account::Type::CreditCard: case Account::Type::Loan: return Account::Type::Liability; default: return d->m_accountType; } } QString MyMoneyAccount::currencyId() const { Q_D(const MyMoneyAccount); return d->m_currencyId; } QString MyMoneyAccount::tradingCurrencyId() const { const auto file = MyMoneyFile::instance(); // First, get the trading currency (formerly deep currency) auto deepcurrency = file->security(currencyId()); if (!deepcurrency.isCurrency()) deepcurrency = file->security(deepcurrency.tradingCurrency()); // Return the trading currency's ID return deepcurrency.id(); } bool MyMoneyAccount::isForeignCurrency() const { return (tradingCurrencyId() != MyMoneyFile::instance()->baseCurrency().id()); } void MyMoneyAccount::setCurrencyId(const QString& id) { Q_D(MyMoneyAccount); d->m_currencyId = id; } bool MyMoneyAccount::isAssetLiability() const { return accountGroup() == Account::Type::Asset || accountGroup() == Account::Type::Liability; } bool MyMoneyAccount::isIncomeExpense() const { return accountGroup() == Account::Type::Income || accountGroup() == Account::Type::Expense; } bool MyMoneyAccount::isLoan() const { return accountType() == Account::Type::Loan || accountType() == Account::Type::AssetLoan; } bool MyMoneyAccount::isInvest() const { return accountType() == Account::Type::Stock; } bool MyMoneyAccount::isLiquidAsset() const { return accountType() == Account::Type::Checkings || accountType() == Account::Type::Savings || accountType() == Account::Type::Cash; } bool MyMoneyAccount::isLiquidLiability() const { return accountType() == Account::Type::CreditCard; } bool MyMoneyAccount::isCostCenterRequired() const { return value("CostCenter").toLower() == QLatin1String("yes"); } void MyMoneyAccount::setCostCenterRequired(bool required) { if(required) { setValue("CostCenter", "yes"); } else { deletePair("CostCenter"); } } bool MyMoneyAccount::hasReferenceTo(const QString& id) const { Q_D(const MyMoneyAccount); return (id == d->m_institution) || (id == d->m_parentAccount) || (id == d->m_currencyId); } void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values) { Q_D(MyMoneyAccount); d->m_onlineBankingSettings = values; } MyMoneyKeyValueContainer MyMoneyAccount::onlineBankingSettings() const { Q_D(const MyMoneyAccount); return d->m_onlineBankingSettings; } void MyMoneyAccount::setClosed(bool closed) { if (closed) setValue("mm-closed", "yes"); else deletePair("mm-closed"); } bool MyMoneyAccount::isClosed() const { return !(value("mm-closed").isEmpty()); } int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const { Q_D(const MyMoneyAccount); int fraction; if (d->m_accountType == Account::Type::Cash) fraction = sec.smallestCashFraction(); else fraction = sec.smallestAccountFraction(); return fraction; } int MyMoneyAccount::fraction(const MyMoneySecurity& sec) { Q_D(MyMoneyAccount); if (d->m_accountType == Account::Type::Cash) d->m_fraction = sec.smallestCashFraction(); else d->m_fraction = sec.smallestAccountFraction(); return d->m_fraction; } int MyMoneyAccount::fraction() const { Q_D(const MyMoneyAccount); return d->m_fraction; } bool MyMoneyAccount::isCategory() const { Q_D(const MyMoneyAccount); return d->m_accountType == Account::Type::Income || d->m_accountType == Account::Type::Expense; } QString MyMoneyAccount::brokerageName() const { Q_D(const MyMoneyAccount); if (d->m_accountType == Account::Type::Investment) return QString("%1 (%2)").arg(d->m_name, i18nc("Brokerage (suffix for account names)", "Brokerage")); return d->m_name; } MyMoneyMoney MyMoneyAccount::balance() const { Q_D(const MyMoneyAccount); return d->m_balance; } void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse) { Q_D(MyMoneyAccount); if (s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { if (reverse) d->m_balance = d->m_balance / s.shares(); else d->m_balance = d->m_balance * s.shares(); } else { if (reverse) d->m_balance -= s.shares(); else d->m_balance += s.shares(); } } void MyMoneyAccount::setBalance(const MyMoneyMoney& val) { Q_D(MyMoneyAccount); d->m_balance = val; } QPixmap MyMoneyAccount::accountPixmap(const bool reconcileFlag, const int size) const { static const QHash accToIco { - {Account::Type::Asset, Icon::ViewAsset}, - {Account::Type::Investment, Icon::ViewStock}, - {Account::Type::Stock, Icon::ViewStock}, - {Account::Type::MoneyMarket, Icon::ViewStock}, - {Account::Type::Checkings, Icon::ViewChecking}, - {Account::Type::Savings, Icon::ViewSaving}, - {Account::Type::AssetLoan, Icon::ViewLoanAsset}, - {Account::Type::Loan, Icon::ViewLoan}, - {Account::Type::CreditCard, Icon::ViewCreditCard}, - {Account::Type::Asset, Icon::ViewAsset}, - {Account::Type::Cash, Icon::ViewCash}, - {Account::Type::Income, Icon::ViewIncome}, - {Account::Type::Expense, Icon::ViewExpense}, - {Account::Type::Equity, Icon::ViewEquity} + {Account::Type::Asset, Icon::Asset}, + {Account::Type::Investment, Icon::Stock}, + {Account::Type::Stock, Icon::Stock}, + {Account::Type::MoneyMarket, Icon::Stock}, + {Account::Type::Checkings, Icon::Checking}, + {Account::Type::Savings, Icon::Savings}, + {Account::Type::AssetLoan, Icon::LoanAsset}, + {Account::Type::Loan, Icon::Loan}, + {Account::Type::CreditCard, Icon::CreditCard}, + {Account::Type::Asset, Icon::Asset}, + {Account::Type::Cash, Icon::Cash}, + {Account::Type::Income, Icon::Income}, + {Account::Type::Expense, Icon::Expense}, + {Account::Type::Equity, Icon::Equity} }; - Icon ixIcon = accToIco.value(accountType(), Icon::ViewLiability); + Icon ixIcon = accToIco.value(accountType(), Icon::Liability); QString kyIcon = accountTypeToString(accountType()) + QString::number(size); QPixmap pxIcon; if (!QPixmapCache::find(kyIcon, &pxIcon)) { pxIcon = Icons::get(ixIcon).pixmap(size); // Qt::AA_UseHighDpiPixmaps (in Qt 5.7) doesn't return highdpi pixmap QPixmapCache::insert(kyIcon, pxIcon); } if (isClosed()) ixIcon = Icon::AccountClosed; else if (reconcileFlag) ixIcon = Icon::Reconciled; else if (hasOnlineMapping()) ixIcon = Icon::Download; else return pxIcon; QPixmap pxOverlay = Icons::get(ixIcon).pixmap(size); QPainter pxPainter(&pxIcon); const QSize szIcon = pxIcon.size(); pxPainter.drawPixmap(szIcon.width() / 2, szIcon.height() / 2, szIcon.width() / 2, szIcon.height() / 2, pxOverlay); return pxIcon; } QString MyMoneyAccount::accountTypeToString(const Account::Type accountType) { switch (accountType) { case Account::Type::Checkings: return i18nc("Account type", "Checking"); case Account::Type::Savings: return i18nc("Account type", "Savings"); case Account::Type::CreditCard: return i18nc("Account type", "Credit Card"); case Account::Type::Cash: return i18nc("Account type", "Cash"); case Account::Type::Loan: return i18nc("Account type", "Loan"); case Account::Type::CertificateDep: return i18nc("Account type", "Certificate of Deposit"); case Account::Type::Investment: return i18nc("Account type", "Investment"); case Account::Type::MoneyMarket: return i18nc("Account type", "Money Market"); case Account::Type::Asset: return i18nc("Account type", "Asset"); case Account::Type::Liability: return i18nc("Account type", "Liability"); case Account::Type::Currency: return i18nc("Account type", "Currency"); case Account::Type::Income: return i18nc("Account type", "Income"); case Account::Type::Expense: return i18nc("Account type", "Expense"); case Account::Type::AssetLoan: return i18nc("Account type", "Investment Loan"); case Account::Type::Stock: return i18nc("Account type", "Stock"); case Account::Type::Equity: return i18nc("Account type", "Equity"); default: return i18nc("Account type", "Unknown"); } } bool MyMoneyAccount::addReconciliation(const QDate& date, const MyMoneyMoney& amount) { Q_D(MyMoneyAccount); d->m_reconciliationHistory[date] = amount; QString history, sep; QMap::const_iterator it; for (it = d->m_reconciliationHistory.constBegin(); it != d->m_reconciliationHistory.constEnd(); ++it) { history += QString("%1%2:%3").arg(sep, it.key().toString(Qt::ISODate), (*it).toString()); sep = QLatin1Char(';'); } setValue("reconciliationHistory", history); return true; } QMap MyMoneyAccount::reconciliationHistory() { Q_D(MyMoneyAccount); // check if the internal history member is already loaded if (d->m_reconciliationHistory.count() == 0 && !value("reconciliationHistory").isEmpty()) { QStringList entries = value("reconciliationHistory").split(';'); foreach (const QString& entry, entries) { QStringList parts = entry.split(':'); QDate date = QDate::fromString(parts[0], Qt::ISODate); MyMoneyMoney amount(parts[1]); if (parts.count() == 2 && date.isValid()) { d->m_reconciliationHistory[date] = amount; } } } return d->m_reconciliationHistory; } /** * @todo Improve setting of country for nationalAccount */ QList< payeeIdentifier > MyMoneyAccount::payeeIdentifiers() const { QList< payeeIdentifier > list; MyMoneyFile* file = MyMoneyFile::instance(); const auto strIBAN = QStringLiteral("iban"); const auto strBIC = QStringLiteral("bic"); // Iban & Bic if (!value(strIBAN).isEmpty()) { payeeIdentifierTyped iban(new payeeIdentifiers::ibanBic); iban->setIban(value(strIBAN)); iban->setBic(file->institution(institutionId()).value(strBIC)); iban->setOwnerName(file->user().name()); list.append(iban); } // National Account number if (!number().isEmpty()) { payeeIdentifierTyped national(new payeeIdentifiers::nationalAccount); national->setAccountNumber(number()); national->setBankCode(file->institution(institutionId()).sortcode()); if (file->user().state().length() == 2) national->setCountry(file->user().state()); national->setOwnerName(file->user().name()); list.append(national); } return list; } bool MyMoneyAccount::hasOnlineMapping() const { Q_D(const MyMoneyAccount); return !d->m_onlineBankingSettings.value(QLatin1String("provider")).isEmpty(); } QString MyMoneyAccount::stdAccName(eMyMoney::Account::Standard stdAccID) { static const QHash stdAccNames { {eMyMoney::Account::Standard::Liability, QStringLiteral("AStd::Liability")}, {eMyMoney::Account::Standard::Asset, QStringLiteral("AStd::Asset")}, {eMyMoney::Account::Standard::Expense, QStringLiteral("AStd::Expense")}, {eMyMoney::Account::Standard::Income, QStringLiteral("AStd::Income")}, {eMyMoney::Account::Standard::Equity, QStringLiteral("AStd::Equity")}, }; return stdAccNames.value(stdAccID); } diff --git a/kmymoney/mymoney/mymoneyinstitution.cpp b/kmymoney/mymoney/mymoneyinstitution.cpp index 7b1c6a4b6..b9abe889c 100644 --- a/kmymoney/mymoney/mymoneyinstitution.cpp +++ b/kmymoney/mymoney/mymoneyinstitution.cpp @@ -1,263 +1,263 @@ /* * Copyright 2000-2001 Michael Edwardes * Copyright 2002-2017 Thomas Baumgart * Copyright 2003 Kevin Tambascio * Copyright 2004-2006 Ace Jones * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mymoneyinstitution.h" #include "mymoneyinstitution_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "icons.h" #include using namespace Icons; MyMoneyInstitution::MyMoneyInstitution() : MyMoneyObject(*new MyMoneyInstitutionPrivate), MyMoneyKeyValueContainer() { } MyMoneyInstitution::MyMoneyInstitution(const QString &id) : MyMoneyObject(*new MyMoneyInstitutionPrivate, id), MyMoneyKeyValueContainer() { } MyMoneyInstitution::MyMoneyInstitution(const QString& name, const QString& town, const QString& street, const QString& postcode, const QString& telephone, const QString& manager, const QString& sortcode) : MyMoneyObject(*new MyMoneyInstitutionPrivate), MyMoneyKeyValueContainer() { Q_D(MyMoneyInstitution); clearId(); d->m_name = name; d->m_town = town; d->m_street = street; d->m_postcode = postcode; d->m_telephone = telephone; d->m_manager = manager; d->m_sortcode = sortcode; } MyMoneyInstitution::MyMoneyInstitution(const MyMoneyInstitution& other) : MyMoneyObject(*new MyMoneyInstitutionPrivate(*other.d_func()), other.id()), MyMoneyKeyValueContainer(other) { } MyMoneyInstitution::MyMoneyInstitution(const QString& id, const MyMoneyInstitution& other) : MyMoneyObject(*new MyMoneyInstitutionPrivate(*other.d_func()), id), MyMoneyKeyValueContainer(other) { } MyMoneyInstitution::~MyMoneyInstitution() { } QString MyMoneyInstitution::manager() const { Q_D(const MyMoneyInstitution); return d->m_manager; } void MyMoneyInstitution::setManager(const QString& manager) { Q_D(MyMoneyInstitution); d->m_manager = manager; } QString MyMoneyInstitution::name() const { Q_D(const MyMoneyInstitution); return d->m_name; } void MyMoneyInstitution::setName(const QString& name) { Q_D(MyMoneyInstitution); d->m_name = name; } QString MyMoneyInstitution::postcode() const { Q_D(const MyMoneyInstitution); return d->m_postcode; } void MyMoneyInstitution::setPostcode(const QString& code) { Q_D(MyMoneyInstitution); d->m_postcode = code; } QString MyMoneyInstitution::street() const { Q_D(const MyMoneyInstitution); return d->m_street; } void MyMoneyInstitution::setStreet(const QString& street) { Q_D(MyMoneyInstitution); d->m_street = street; } QString MyMoneyInstitution::telephone() const { Q_D(const MyMoneyInstitution); return d->m_telephone; } void MyMoneyInstitution::setTelephone(const QString& tel) { Q_D(MyMoneyInstitution); d->m_telephone = tel; } QString MyMoneyInstitution::town() const { Q_D(const MyMoneyInstitution); return d->m_town; } void MyMoneyInstitution::setTown(const QString& town) { Q_D(MyMoneyInstitution); d->m_town = town; } QString MyMoneyInstitution::city() const { return town(); } void MyMoneyInstitution::setCity(const QString& town) { setTown(town); } QString MyMoneyInstitution::sortcode() const { Q_D(const MyMoneyInstitution); return d->m_sortcode; } void MyMoneyInstitution::setSortcode(const QString& code) { Q_D(MyMoneyInstitution); d->m_sortcode = code; } void MyMoneyInstitution::addAccountId(const QString& account) { Q_D(MyMoneyInstitution); // only add this account if it is not yet presently in the list if (d->m_accountList.contains(account) == 0) d->m_accountList.append(account); } QString MyMoneyInstitution::removeAccountId(const QString& account) { Q_D(MyMoneyInstitution); QString rc; auto pos = d->m_accountList.indexOf(account); if (pos != -1) { d->m_accountList.removeAt(pos); rc = account; } return rc; } QStringList MyMoneyInstitution::accountList() const { Q_D(const MyMoneyInstitution); return d->m_accountList; } /** * This method returns the number of accounts known to * this institution * @return number of accounts */ unsigned int MyMoneyInstitution::accountCount() const { Q_D(const MyMoneyInstitution); return d->m_accountList.count(); } bool MyMoneyInstitution::operator < (const MyMoneyInstitution& right) const { Q_D(const MyMoneyInstitution); auto d2 = static_cast(right.d_func()); return d->m_name < d2->m_name; } bool MyMoneyInstitution::operator == (const MyMoneyInstitution& right) const { Q_D(const MyMoneyInstitution); auto d2 = static_cast(right.d_func()); if (MyMoneyObject::operator==(right) && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) && ((d->m_town.length() == 0 && d2->m_town.length() == 0) || (d->m_town == d2->m_town)) && ((d->m_street.length() == 0 && d2->m_street.length() == 0) || (d->m_street == d2->m_street)) && ((d->m_postcode.length() == 0 && d2->m_postcode.length() == 0) || (d->m_postcode == d2->m_postcode)) && ((d->m_telephone.length() == 0 && d2->m_telephone.length() == 0) || (d->m_telephone == d2->m_telephone)) && ((d->m_sortcode.length() == 0 && d2->m_sortcode.length() == 0) || (d->m_sortcode == d2->m_sortcode)) && ((d->m_manager.length() == 0 && d2->m_manager.length() == 0) || (d->m_manager == d2->m_manager)) && (d->m_accountList == d2->m_accountList)) { return true; } else return false; } bool MyMoneyInstitution::hasReferenceTo(const QString& /* id */) const { return false; } QPixmap MyMoneyInstitution::pixmap(const int size) { QPixmap pxIcon; auto kyIcon = QString::fromLatin1("view_institution%1").arg(QString::number(size)); - if (!QPixmapCache::find(kyIcon, &pxIcon)) { - pxIcon = Icons::get(Icon::ViewInstitutions).pixmap(size); + if (!QPixmapCache::find(kyIcon, pxIcon)) { + pxIcon = Icons::get(Icon::Institution).pixmap(size); QPixmapCache::insert(kyIcon, pxIcon); } return pxIcon; } diff --git a/kmymoney/plugins/kbanking/kbanking.cpp b/kmymoney/plugins/kbanking/kbanking.cpp index c4880341f..247e34412 100644 --- a/kmymoney/plugins/kbanking/kbanking.cpp +++ b/kmymoney/plugins/kbanking/kbanking.cpp @@ -1,1527 +1,1526 @@ /* * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net * Copyright 2009 Cristian Onet onet.cristian@gmail.com * Copyright 2010-2019 Thomas Baumgart tbaumgart@kde.org * Copyright 2015 Christian David christian-david@web.de * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "kbanking.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include //! @todo remove @c #include #ifdef IS_APPIMAGE #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include -#include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Library Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/onlinejob.h" #include "kbaccountsettings.h" #include "kbmapaccount.h" #include "mymoneyfile.h" #include "onlinejobadministration.h" #include "kmymoneyview.h" #include "kbpickstartdate.h" #include "mymoneyinstitution.h" #include "mymoneytransactionfilter.h" #include "mymoneyexception.h" #include "mymoneysecurity.h" #include "gwenkdegui.h" #include "gwenhywfarqtoperators.h" #include "aqbankingkmmoperators.h" #include "mymoneystatement.h" #include "statementinterface.h" #include "viewinterface.h" #ifdef KMM_DEBUG #include "chiptandialog.h" #include "phototandialog.h" #include "phototan-demo.cpp" #endif class KBanking::Private { public: Private() : passwordCacheTimer(nullptr), jobList(), fileId() { QString gwenProxy = QString::fromLocal8Bit(qgetenv("GWEN_PROXY")); if (gwenProxy.isEmpty()) { std::unique_ptr cfg = std::unique_ptr(new KConfig("kioslaverc")); QRegExp exp("(\\w+://)?([^/]{2}.+:\\d+)"); QString proxy; KConfigGroup grp = cfg->group("Proxy Settings"); int type = grp.readEntry("ProxyType", 0); switch (type) { case 0: // no proxy break; case 1: // manual specified proxy = grp.readEntry("httpsProxy"); qDebug("KDE https proxy setting is '%s'", qPrintable(proxy)); if (exp.exactMatch(proxy)) { proxy = exp.cap(2); qDebug("Setting GWEN_PROXY to '%s'", qPrintable(proxy)); if (!qputenv("GWEN_PROXY", qPrintable(proxy))) { qDebug("Unable to setup GWEN_PROXY"); } } break; default: // other currently not supported qDebug("KDE proxy setting of type %d not supported", type); break; } } } QString libVersion(void (*version)(int*, int*, int*, int*)) { int major, minor, patch, build; version(&major, &minor, &patch, &build); return QString("%1.%2.%3.%4").arg(major).arg(minor).arg(patch).arg(build); } /** * KMyMoney asks for accounts over and over again which causes a lot of "Job not supported with this account" error messages. * This function filters messages with that string. */ static int gwenLogHook(GWEN_GUI* gui, const char* domain, GWEN_LOGGER_LEVEL level, const char* message) { Q_UNUSED(gui); Q_UNUSED(domain); Q_UNUSED(level); const char* messageToFilter = "Job not supported with this account"; if (strstr(message, messageToFilter) != 0) return 1; return 0; } QTimer *passwordCacheTimer; QMap jobList; QString fileId; }; KBanking::KBanking(QObject *parent, const QVariantList &args) : OnlinePluginExtended(parent, "kbanking") , d(new Private) , m_configAction(nullptr) , m_importAction(nullptr) , m_kbanking(nullptr) , m_accountSettings(nullptr) , m_statementCount(0) { Q_UNUSED(args) QString compileVersionSet = QLatin1String(GWENHYWFAR_VERSION_FULL_STRING "/" AQBANKING_VERSION_FULL_STRING); QString runtimeVersionSet = QString("%1/%2").arg(d->libVersion(&GWEN_Version), d->libVersion(&AB_Banking_GetVersion)); qDebug() << QString("Plugins: kbanking loaded, build with (%1), run with (%2)").arg(compileVersionSet, runtimeVersionSet); } KBanking::~KBanking() { delete d; qDebug("Plugins: kbanking unloaded"); } void KBanking::plug() { const auto componentName = QLatin1String("kbanking"); const auto rcFileName = QLatin1String("kbanking.rc"); m_kbanking = new KBankingExt(this, "KMyMoney"); d->passwordCacheTimer = new QTimer(this); d->passwordCacheTimer->setSingleShot(true); d->passwordCacheTimer->setInterval(60000); connect(d->passwordCacheTimer, &QTimer::timeout, this, &KBanking::slotClearPasswordCache); if (m_kbanking) { //! @todo when is gwenKdeGui deleted? gwenKdeGui *gui = new gwenKdeGui(); GWEN_Gui_SetGui(gui->getCInterface()); GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning); if (m_kbanking->init() == 0) { // Tell the host application to load my GUI component setComponentName(componentName, "KBanking"); #ifdef IS_APPIMAGE const QString rcFilePath = QString("%1/../share/kxmlgui5/%2/%3").arg(QCoreApplication::applicationDirPath(), componentName, rcFileName); setXMLFile(rcFilePath); const QString localRcFilePath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + QLatin1Char('/') + componentName + QLatin1Char('/') + rcFileName; setLocalXMLFile(localRcFilePath); #else setXMLFile(rcFileName); #endif // get certificate handling and dialog settings management AB_Gui_Extend(gui->getCInterface(), m_kbanking->getCInterface()); // create actions createActions(); // load protocol conversion list loadProtocolConversion(); GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Warning); GWEN_Gui_SetLogHookFn(GWEN_Gui_GetGui(), &KBanking::Private::gwenLogHook); } else { qWarning("Could not initialize KBanking online banking interface"); delete m_kbanking; m_kbanking = 0; } } } void KBanking::unplug() { d->passwordCacheTimer->deleteLater(); if (m_kbanking) { m_kbanking->fini(); delete m_kbanking; qDebug("Plugins: kbanking unplugged"); } } void KBanking::loadProtocolConversion() { if (m_kbanking) { m_protocolConversionMap = { {"aqhbci", "HBCI"}, {"aqofxconnect", "OFX"}, {"aqyellownet", "YellowNet"}, {"aqgeldkarte", "Geldkarte"}, {"aqdtaus", "DTAUS"} }; } } void KBanking::protocols(QStringList& protocolList) const { if (m_kbanking) { std::list list = m_kbanking->getActiveProviders(); std::list::iterator it; for (it = list.begin(); it != list.end(); ++it) { // skip the dummy if (*it == "aqnone") continue; QMap::const_iterator it_m; it_m = m_protocolConversionMap.find((*it).c_str()); if (it_m != m_protocolConversionMap.end()) protocolList << (*it_m); else protocolList << (*it).c_str(); } } } QWidget* KBanking::accountConfigTab(const MyMoneyAccount& acc, QString& name) { const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); name = i18n("Online settings"); if (m_kbanking) { m_accountSettings = new KBAccountSettings(acc, 0); m_accountSettings->loadUi(kvp); return m_accountSettings; } QLabel* label = new QLabel(i18n("KBanking module not correctly initialized"), 0); label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); return label; } MyMoneyKeyValueContainer KBanking::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); kvp["provider"] = objectName().toLower(); if (m_accountSettings) { m_accountSettings->loadKvp(kvp); } return kvp; } void KBanking::createActions() { QAction *settings_aqbanking = actionCollection()->addAction("settings_aqbanking"); settings_aqbanking->setText(i18n("Configure Aq&Banking...")); connect(settings_aqbanking, &QAction::triggered, this, &KBanking::slotSettings); QAction *file_import_aqbanking = actionCollection()->addAction("file_import_aqbanking"); file_import_aqbanking->setText(i18n("AqBanking importer...")); connect(file_import_aqbanking, &QAction::triggered, this, &KBanking::slotImport); Q_CHECK_PTR(viewInterface()); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action("file_import_aqbanking"), &QAction::setEnabled); #ifdef KMM_DEBUG QAction *openChipTanDialog = actionCollection()->addAction("open_chiptan_dialog"); openChipTanDialog->setText("Open ChipTan Dialog"); connect(openChipTanDialog, &QAction::triggered, [&](){ auto dlg = new chipTanDialog(); dlg->setHhdCode("0F04871100030333555414312C32331D"); dlg->setInfoText("

Test Graphic for debugging

The encoded data is

Account Number: 335554
Amount: 1,23

"); connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater); connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater); dlg->show(); }); QAction *openPhotoTanDialog = actionCollection()->addAction("open_phototan_dialog"); openPhotoTanDialog->setText("Open PhotoTan Dialog"); connect(openPhotoTanDialog, &QAction::triggered, [&](){ auto dlg = new photoTanDialog(); QImage img; img.loadFromData(photoTan, sizeof(photoTan), "PNG"); img = img.scaled(300, 300, Qt::KeepAspectRatio); dlg->setPicture(QPixmap::fromImage(img)); dlg->setInfoText("

Test Graphic for debugging

The encoded data is

unknown

"); connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater); connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater); dlg->show(); }); #endif } void KBanking::slotSettings() { if (m_kbanking) { GWEN_DIALOG* dlg = AB_Banking_CreateSetupDialog(m_kbanking->getCInterface()); if (dlg == NULL) { DBG_ERROR(0, "Could not create setup dialog."); return; } if (GWEN_Gui_ExecDialog(dlg, 0) == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); return; } GWEN_Dialog_free(dlg); } } bool KBanking::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { bool rc = false; if (m_kbanking && !acc.id().isEmpty()) { m_kbanking->askMapAccount(acc); // at this point, the account should be mapped // so we search it and setup the account reference in the KMyMoney object AB_ACCOUNT_SPEC* ab_acc; ab_acc = aqbAccount(acc); if (ab_acc) { MyMoneyAccount a(acc); setupAccountReference(a, ab_acc); settings = a.onlineBankingSettings(); rc = true; } } return rc; } AB_ACCOUNT_SPEC* KBanking::aqbAccount(const MyMoneyAccount& acc) const { if (m_kbanking == 0) { return 0; } // certainly looking for an expense or income account does not make sense at this point // so we better get out right away if (acc.isIncomeExpense()) { return 0; } AB_ACCOUNT_SPEC *ab_acc = AB_Banking_GetAccountSpecByAlias(m_kbanking->getCInterface(), m_kbanking->mappingId(acc).toUtf8().data()); // if the account is not found, we temporarily scan for the 'old' mapping (the one w/o the file id) // in case we find it, we setup the new mapping in addition on the fly. if (!ab_acc && acc.isAssetLiability()) { ab_acc = AB_Banking_GetAccountSpecByAlias(m_kbanking->getCInterface(), acc.id().toUtf8().data()); if (ab_acc) { qDebug("Found old mapping for '%s' but not new. Setup new mapping", qPrintable(acc.name())); m_kbanking->setAccountAlias(ab_acc, m_kbanking->mappingId(acc).toUtf8().constData()); // TODO at some point in time, we should remove the old mapping } } return ab_acc; } AB_ACCOUNT_SPEC* KBanking::aqbAccount(const QString& accountId) const { MyMoneyAccount account = MyMoneyFile::instance()->account(accountId); return aqbAccount(account); } QString KBanking::stripLeadingZeroes(const QString& s) const { QString rc(s); QRegExp exp("^(0*)([^0].*)"); if (exp.exactMatch(s)) { rc = exp.cap(2); } return rc; } void KBanking::setupAccountReference(const MyMoneyAccount& acc, AB_ACCOUNT_SPEC* ab_acc) { MyMoneyKeyValueContainer kvp; if (ab_acc) { QString accountNumber = stripLeadingZeroes(AB_AccountSpec_GetAccountNumber(ab_acc)); QString routingNumber = stripLeadingZeroes(AB_AccountSpec_GetBankCode(ab_acc)); QString val = QString("%1-%2-%3").arg(routingNumber, accountNumber).arg(AB_AccountSpec_GetType(ab_acc)); if (val != acc.onlineBankingSettings().value("kbanking-acc-ref")) { kvp.clear(); // make sure to keep our own previous settings const QMap& vals = acc.onlineBankingSettings().pairs(); QMap::const_iterator it_p; for (it_p = vals.begin(); it_p != vals.end(); ++it_p) { if (QString(it_p.key()).startsWith("kbanking-")) { kvp.setValue(it_p.key(), *it_p); } } kvp.setValue("kbanking-acc-ref", val); kvp.setValue("provider", objectName().toLower()); setAccountOnlineParameters(acc, kvp); } } else { // clear the connection setAccountOnlineParameters(acc, kvp); } } bool KBanking::accountIsMapped(const MyMoneyAccount& acc) { return aqbAccount(acc) != 0; } bool KBanking::updateAccount(const MyMoneyAccount& acc) { return updateAccount(acc, false); } bool KBanking::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { if (!m_kbanking) return false; bool rc = false; if (!acc.id().isEmpty()) { AB_TRANSACTION *job = 0; int rv; /* get AqBanking account */ AB_ACCOUNT_SPEC *ba = aqbAccount(acc); // Update the connection between the KMyMoney account and the AqBanking equivalent. // If the account is not found anymore ba == 0 and the connection is removed. setupAccountReference(acc, ba); if (!ba) { KMessageBox::error(0, i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", acc.name()), i18n("Account Not Mapped")); } else { bool enqueJob = true; if (acc.onlineBankingSettings().value("kbanking-txn-download") != "no") { /* create getTransactions job */ if (AB_AccountSpec_GetTransactionLimitsForCommand(ba, AB_Transaction_CommandGetTransactions)) { /* there are transaction limits for this job, so it is allowed */ job = AB_Transaction_new(); AB_Transaction_SetUniqueAccountId(job, AB_AccountSpec_GetUniqueId(ba)); AB_Transaction_SetCommand(job, AB_Transaction_CommandGetTransactions); } if (job) { int days = 0 /* TODO in AqBanking AB_JobGetTransactions_GetMaxStoreDays(job)*/; QDate qd; if (days > 0) { GWEN_DATE *dt; dt=GWEN_Date_CurrentDate(); GWEN_Date_SubDays(dt, days); qd = QDate(GWEN_Date_GetYear(dt), GWEN_Date_GetMonth(dt), GWEN_Date_GetDay(dt)); GWEN_Date_free(dt); } // get last statement request date from application account object // and start from a few days before if the date is valid QDate lastUpdate = QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate); if (lastUpdate.isValid()) lastUpdate = lastUpdate.addDays(-3); int dateOption = acc.onlineBankingSettings().value("kbanking-statementDate").toInt(); switch (dateOption) { case 0: // Ask user break; case 1: // No date qd = QDate(); break; case 2: // Last download qd = lastUpdate; break; case 3: // First possible // qd is already setup break; } // the pick start date option dialog is needed in // case the dateOption is 0 or the date option is > 1 // and the qd is invalid if (dateOption == 0 || (dateOption > 1 && !qd.isValid())) { QPointer psd = new KBPickStartDate(m_kbanking, qd, lastUpdate, acc.name(), lastUpdate.isValid() ? 2 : 3, 0, true); if (psd->exec() == QDialog::Accepted) { qd = psd->date(); } else { enqueJob = false; } delete psd; } if (enqueJob) { if (qd.isValid()) { GWEN_DATE *dt; dt=GWEN_Date_fromGregorian(qd.year(), qd.month(), qd.day()); AB_Transaction_SetFirstDate(job, dt); GWEN_Date_free(dt); } rv = m_kbanking->enqueueJob(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } } AB_Transaction_free(job); } } if (enqueJob) { /* create getBalance job */ if (AB_AccountSpec_GetTransactionLimitsForCommand(ba, AB_Transaction_CommandGetBalance)) { /* there are transaction limits for this job, so it is allowed */ job = AB_Transaction_new(); AB_Transaction_SetUniqueAccountId(job, AB_AccountSpec_GetUniqueId(ba)); AB_Transaction_SetCommand(job, AB_Transaction_CommandGetBalance); rv = m_kbanking->enqueueJob(job); AB_Transaction_free(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } else { rc = true; emit queueChanged(); } } } } } // make sure we have at least one job in the queue before sending it if (!moreAccounts && m_kbanking->getEnqueuedJobs().size() > 0) executeQueue(); return rc; } void KBanking::executeQueue() { if (m_kbanking && m_kbanking->getEnqueuedJobs().size() > 0) { AB_IMEXPORTER_CONTEXT *ctx; ctx = AB_ImExporterContext_new(); int rv = m_kbanking->executeQueue(ctx); if (!rv) { m_kbanking->importContext(ctx, 0); } else { DBG_ERROR(0, "Error: %d", rv); } AB_ImExporterContext_free(ctx); } } /** @todo improve error handling, e.g. by adding a .isValid to nationalTransfer * @todo use new onlineJob system */ void KBanking::sendOnlineJob(QList& jobs) { Q_CHECK_PTR(m_kbanking); m_onlineJobQueue.clear(); QList unhandledJobs; if (!jobs.isEmpty()) { foreach (onlineJob job, jobs) { if (sepaOnlineTransfer::name() == job.task()->taskName()) { onlineJobTyped typedJob(job); enqueTransaction(typedJob); job = typedJob; } else { job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "KBanking", "Cannot handle this request")); unhandledJobs.append(job); } m_onlineJobQueue.insert(m_kbanking->mappingId(job), job); } executeQueue(); } jobs = m_onlineJobQueue.values() + unhandledJobs; m_onlineJobQueue.clear(); } QStringList KBanking::availableJobs(QString accountId) { try { MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId); QString id = MyMoneyFile::instance()->value("kmm-id"); if(id != d->fileId) { d->jobList.clear(); d->fileId = id; } } catch (const MyMoneyException &) { // Exception usually means account was not found return QStringList(); } if(d->jobList.contains(accountId)) { return d->jobList[accountId]; } QStringList list; AB_ACCOUNT_SPEC* abAccount = aqbAccount(accountId); if (!abAccount) { return list; } // Check availableJobs // sepa transfer if (AB_AccountSpec_GetTransactionLimitsForCommand(abAccount, AB_Transaction_CommandSepaTransfer)) { list.append(sepaOnlineTransfer::name()); } d->jobList[accountId] = list; return list; } /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbJobDeleter { public: static void cleanup(AB_TRANSACTION* job) { AB_Transaction_free(job); } }; /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbAccountDeleter { public: static void cleanup(AB_ACCOUNT_SPEC* account) { AB_AccountSpec_free(account); } }; IonlineTaskSettings::ptr KBanking::settings(QString accountId, QString taskName) { AB_ACCOUNT_SPEC* abAcc = aqbAccount(accountId); if (abAcc == 0) return IonlineTaskSettings::ptr(); if (sepaOnlineTransfer::name() == taskName) { // Get limits for sepaonlinetransfer const AB_TRANSACTION_LIMITS *limits=AB_AccountSpec_GetTransactionLimitsForCommand(abAcc, AB_Transaction_CommandSepaTransfer); if (limits==NULL) return IonlineTaskSettings::ptr(); return AB_TransactionLimits_toSepaOnlineTaskSettings(limits).dynamicCast(); } return IonlineTaskSettings::ptr(); } bool KBanking::enqueTransaction(onlineJobTyped& job) { /* get AqBanking account */ const QString accId = job.constTask()->responsibleAccount(); AB_ACCOUNT_SPEC *abAccount = aqbAccount(accId); if (!abAccount) { job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Warning, "KBanking", i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", MyMoneyFile::instance()->account(accId).name()))); return false; } //setupAccountReference(acc, ba); // needed? if (AB_AccountSpec_GetTransactionLimitsForCommand(abAccount, AB_Transaction_CommandSepaTransfer)==NULL) { qDebug("AB_ERROR_OFFSET is %i", AB_ERROR_OFFSET); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "AqBanking", QString("Sepa credit transfers for account \"%1\" are not available.").arg(MyMoneyFile::instance()->account(accId).name()) ) ); return false; } AB_TRANSACTION *abJob = AB_Transaction_new(); /* command */ AB_Transaction_SetCommand(abJob, AB_Transaction_CommandSepaTransfer); // Origin Account AB_Transaction_SetUniqueAccountId(abJob, AB_AccountSpec_GetUniqueId(abAccount)); // Recipient payeeIdentifiers::ibanBic beneficiaryAcc = job.constTask()->beneficiaryTyped(); AB_Transaction_SetRemoteName(abJob, beneficiaryAcc.ownerName().toUtf8().constData()); AB_Transaction_SetRemoteIban(abJob, beneficiaryAcc.electronicIban().toUtf8().constData()); AB_Transaction_SetRemoteBic(abJob, beneficiaryAcc.fullStoredBic().toUtf8().constData()); // Origin Account AB_Transaction_SetLocalAccount(abJob, abAccount); // Purpose AB_Transaction_SetPurpose(abJob, job.constTask()->purpose().toUtf8().constData()); // Reference // AqBanking duplicates the string. This should be safe. AB_Transaction_SetEndToEndReference(abJob, job.constTask()->endToEndReference().toUtf8().constData()); // Other Fields AB_Transaction_SetTextKey(abJob, job.constTask()->textKey()); AB_Transaction_SetValue(abJob, AB_Value_fromMyMoneyMoney(job.constTask()->value())); /** @todo LOW remove Debug info */ AB_Transaction_SetStringIdForApplication(abJob, m_kbanking->mappingId(job).toUtf8().constData()); qDebug() << "Enqueue: " << m_kbanking->enqueueJob(abJob); AB_Transaction_free(abJob); //delete localAcc; return true; } void KBanking::startPasswordTimer() { if (d->passwordCacheTimer->isActive()) d->passwordCacheTimer->stop(); d->passwordCacheTimer->start(); } void KBanking::slotClearPasswordCache() { m_kbanking->clearPasswordCache(); } void KBanking::slotImport() { m_statementCount = 0; statementInterface()->resetMessages(); if (!m_kbanking->interactiveImport()) qWarning("Error on import dialog"); else statementInterface()->showMessages(m_statementCount); } bool KBanking::importStatement(const MyMoneyStatement& s) { m_statementCount++; return !statementInterface()->import(s).isEmpty(); } MyMoneyAccount KBanking::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void KBanking::setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps) const { return statementInterface()->setAccountOnlineParameters(acc, kvps); } KBankingExt::KBankingExt(KBanking* parent, const char* appname, const char* fname) : AB_Banking(appname, fname) , m_parent(parent) , _jobQueue(0) { m_sepaKeywords = {QString::fromUtf8("SEPA-BASISLASTSCHRIFT"), QString::fromUtf8("SEPA-ÜBERWEISUNG")}; QRegularExpression exp("(\\d+\\.\\d+\\.\\d+).*"); QRegularExpressionMatch match = exp.match(KAboutData::applicationData().version()); QByteArray regkey; const char *p = "\x08\x0f\x41\x0f\x58\x5b\x56\x4a\x09\x7b\x40\x0e\x5f\x2a\x56\x3f\x0e\x7f\x3f\x7d\x5b\x56\x56\x4b\x0a\x4d"; const char* q = appname; while (*p) { regkey += (*q++ ^ *p++) & 0xff; if (!*q) q = appname; } registerFinTs(regkey.data(), match.captured(1).remove(QLatin1Char('.')).left(5).toLatin1()); } int KBankingExt::init() { int rv = AB_Banking::init(); if (rv < 0) return rv; _jobQueue = AB_Transaction_List2_new(); return 0; } int KBankingExt::fini() { if (_jobQueue) { AB_Transaction_List2_freeAll(_jobQueue); _jobQueue = 0; } return AB_Banking::fini(); } int KBankingExt::executeQueue(AB_IMEXPORTER_CONTEXT *ctx) { m_parent->startPasswordTimer(); int rv = AB_Banking::executeJobs(_jobQueue, ctx); if (rv != 0) { qDebug() << "Sending queue by aqbanking got error no " << rv; } /** check result of each job */ AB_TRANSACTION_LIST2_ITERATOR* jobIter = AB_Transaction_List2_First(_jobQueue); if (jobIter) { AB_TRANSACTION* abJob = AB_Transaction_List2Iterator_Data(jobIter); while (abJob) { const char *stringIdForApp=AB_Transaction_GetStringIdForApplication(abJob); if (!(stringIdForApp && *stringIdForApp)) { qWarning("Executed AB_Job without KMyMoney id"); abJob = AB_Transaction_List2Iterator_Next(jobIter); continue; } QString jobIdent = QString::fromUtf8(stringIdForApp); onlineJob job = m_parent->m_onlineJobQueue.value(jobIdent); if (job.isNull()) { // It should not be possible that this will happen (only if AqBanking fails heavily). //! @todo correct exception text qWarning("Executed a job which was not in queue. Please inform the KMyMoney developers."); abJob = AB_Transaction_List2Iterator_Next(jobIter); continue; } AB_TRANSACTION_STATUS abStatus = AB_Transaction_GetStatus(abJob); if (abStatus == AB_Transaction_StatusSent || abStatus == AB_Transaction_StatusPending || abStatus == AB_Transaction_StatusAccepted || abStatus == AB_Transaction_StatusRejected || abStatus == AB_Transaction_StatusError || abStatus == AB_Transaction_StatusUnknown) job.setJobSend(); if (abStatus == AB_Transaction_StatusAccepted) job.setBankAnswer(eMyMoney::OnlineJob::sendingState::acceptedByBank); else if (abStatus == AB_Transaction_StatusError || abStatus == AB_Transaction_StatusRejected || abStatus == AB_Transaction_StatusUnknown) job.setBankAnswer(eMyMoney::OnlineJob::sendingState::sendingError); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Debug, "KBanking", "Job was processed")); m_parent->m_onlineJobQueue.insert(jobIdent, job); abJob = AB_Transaction_List2Iterator_Next(jobIter); } AB_Transaction_List2Iterator_free(jobIter); } AB_TRANSACTION_LIST2 *oldQ = _jobQueue; _jobQueue = AB_Transaction_List2_new(); AB_Transaction_List2_freeAll(oldQ); emit m_parent->queueChanged(); m_parent->startPasswordTimer(); return rv; } void KBankingExt::clearPasswordCache() { /* clear password DB */ GWEN_Gui_SetPasswordStatus(NULL, NULL, GWEN_Gui_PasswordStatus_Remove, 0); } std::list KBankingExt::getEnqueuedJobs() { AB_TRANSACTION_LIST2 *ll; std::list rl; ll = _jobQueue; if (ll && AB_Transaction_List2_GetSize(ll)) { AB_TRANSACTION *j; AB_TRANSACTION_LIST2_ITERATOR *it; it = AB_Transaction_List2_First(ll); assert(it); j = AB_Transaction_List2Iterator_Data(it); assert(j); while (j) { rl.push_back(j); j = AB_Transaction_List2Iterator_Next(it); } AB_Transaction_List2Iterator_free(it); } return rl; } int KBankingExt::enqueueJob(AB_TRANSACTION *j) { assert(_jobQueue); assert(j); AB_Transaction_Attach(j); AB_Transaction_List2_PushBack(_jobQueue, j); return 0; } int KBankingExt::dequeueJob(AB_TRANSACTION *j) { assert(_jobQueue); AB_Transaction_List2_Remove(_jobQueue, j); AB_Transaction_free(j); emit m_parent->queueChanged(); return 0; } void KBankingExt::transfer() { //m_parent->transfer(); } bool KBankingExt::askMapAccount(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); QString bankId; QString accountId; // extract some information about the bank. if we have a sortcode // (BLZ) we display it, otherwise the name is enough. try { const MyMoneyInstitution &bank = file->institution(acc.institutionId()); bankId = bank.name(); if (!bank.sortcode().isEmpty()) bankId = bank.sortcode(); } catch (const MyMoneyException &e) { // no bank assigned, we just leave the field empty } // extract account information. if we have an account number // we show it, otherwise the name will be displayed accountId = acc.number(); if (accountId.isEmpty()) accountId = acc.name(); // do the mapping. the return value of this method is either // true, when the user mapped the account or false, if he // decided to quit the dialog. So not really a great thing // to present some more information. KBMapAccount *w; w = new KBMapAccount(this, bankId.toUtf8().constData(), accountId.toUtf8().constData()); if (w->exec() == QDialog::Accepted) { AB_ACCOUNT_SPEC *a; a = w->getAccount(); assert(a); DBG_NOTICE(0, "Mapping application account \"%s\" to " "online account \"%s/%s\"", qPrintable(acc.name()), AB_AccountSpec_GetBankCode(a), AB_AccountSpec_GetAccountNumber(a)); // TODO remove the following line once we don't need backward compatibility setAccountAlias(a, acc.id().toUtf8().constData()); qDebug("Setup mapping to '%s'", acc.id().toUtf8().constData()); setAccountAlias(a, mappingId(acc).toUtf8().constData()); qDebug("Setup mapping to '%s'", mappingId(acc).toUtf8().constData()); delete w; return true; } delete w; return false; } QString KBankingExt::mappingId(const MyMoneyObject& object) const { QString id = MyMoneyFile::instance()->storageId() + QLatin1Char('-') + object.id(); // AqBanking does not handle the enclosing parens, so we remove it id.remove('{'); id.remove('}'); return id; } bool KBankingExt::interactiveImport() { AB_IMEXPORTER_CONTEXT *ctx; GWEN_DIALOG *dlg; int rv; ctx = AB_ImExporterContext_new(); dlg = AB_Banking_CreateImporterDialog(getCInterface(), ctx, NULL); if (dlg == NULL) { DBG_ERROR(0, "Could not create importer dialog."); AB_ImExporterContext_free(ctx); return false; } rv = GWEN_Gui_ExecDialog(dlg, 0); if (rv == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } if (!importContext(ctx, 0)) { DBG_ERROR(0, "Error on importContext"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return true; } void KBankingExt::_xaToStatement(MyMoneyStatement &ks, const MyMoneyAccount& acc, const AB_TRANSACTION *t) { QString s; QString memo; const char *p; const AB_VALUE *val; const GWEN_DATE *dt; const GWEN_DATE *startDate = 0; MyMoneyStatement::Transaction kt; unsigned long h; kt.m_fees = MyMoneyMoney(); // bank's transaction id p = AB_Transaction_GetFiId(t); if (p) kt.m_strBankID = QString("ID ") + QString::fromUtf8(p); // payee p = AB_Transaction_GetRemoteName(t); if (p) kt.m_strPayee = QString::fromUtf8(p); // memo p = AB_Transaction_GetPurpose(t); if (p && *p) { QString tmpMemo; s=QString::fromUtf8(p).trimmed(); tmpMemo=QString::fromUtf8(p).trimmed(); tmpMemo.replace('\n', ' '); memo=tmpMemo; } // in case we have some SEPA fields filled with information // we add them to the memo field p = AB_Transaction_GetEndToEndReference(t); if (p) { s += QString(", EREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("EREF: %1").arg(p)); } p = AB_Transaction_GetCustomerReference(t); if (p) { s += QString(", CREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CREF: %1").arg(p)); } p = AB_Transaction_GetMandateId(t); if (p) { s += QString(", MREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("MREF: %1").arg(p)); } p = AB_Transaction_GetCreditorSchemeId(t); if (p) { s += QString(", CRED: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CRED: %1").arg(p)); } p = AB_Transaction_GetOriginatorId(t); if (p) { s += QString(", DEBT: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("DEBT: %1").arg(p)); } const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); // check if we need the version with or without linebreaks if (kvp.value("kbanking-memo-removelinebreaks").compare(QLatin1String("no"))) { kt.m_strMemo = memo; } else { kt.m_strMemo = s; } // calculate the hash code and start with the payee info // and append the memo field h = MyMoneyTransaction::hash(kt.m_strPayee.trimmed()); h = MyMoneyTransaction::hash(s, h); // see, if we need to extract the payee from the memo field QString rePayee = kvp.value("kbanking-payee-regexp"); if (!rePayee.isEmpty() && kt.m_strPayee.isEmpty()) { QString reMemo = kvp.value("kbanking-memo-regexp"); QStringList exceptions = kvp.value("kbanking-payee-exceptions").split(';', QString::SkipEmptyParts); bool needExtract = true; QStringList::const_iterator it_s; for (it_s = exceptions.constBegin(); needExtract && it_s != exceptions.constEnd(); ++it_s) { QRegExp exp(*it_s, Qt::CaseInsensitive); if (exp.indexIn(kt.m_strMemo) != -1) { needExtract = false; } } if (needExtract) { QRegExp expPayee(rePayee, Qt::CaseInsensitive); QRegExp expMemo(reMemo, Qt::CaseInsensitive); if (expPayee.indexIn(kt.m_strMemo) != -1) { kt.m_strPayee = expPayee.cap(1); if (expMemo.indexIn(kt.m_strMemo) != -1) { kt.m_strMemo = expMemo.cap(1); } } } } kt.m_strPayee = kt.m_strPayee.trimmed(); // date dt = AB_Transaction_GetDate(t); if (!dt) dt = AB_Transaction_GetValutaDate(t); if (dt) { if (!startDate) startDate = dt; kt.m_datePosted = QDate(GWEN_Date_GetYear(dt), GWEN_Date_GetMonth(dt), GWEN_Date_GetDay(dt)); } else { DBG_WARN(0, "No date for transaction"); } // value val = AB_Transaction_GetValue(t); if (val) { if (ks.m_strCurrency.isEmpty()) { p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } else { p = AB_Value_GetCurrency(val); if (p) s = p; if (ks.m_strCurrency.toLower() != s.toLower()) { // TODO: handle currency difference DBG_ERROR(0, "Mixed currencies currently not allowed"); } } kt.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val)); // The initial implementation of this feature was based on // a denominator of 100. Since the denominator might be // different nowadays, we make sure to use 100 for the // duplicate detection QString tmpVal = kt.m_amount.formatMoney(100, false); tmpVal.remove(QRegExp("[,\\.]")); tmpVal += QLatin1String("/100"); h = MyMoneyTransaction::hash(tmpVal, h); } else { DBG_WARN(0, "No value for transaction"); } if (startDate) { QDate d(QDate(GWEN_Date_GetYear(startDate), GWEN_Date_GetMonth(startDate), GWEN_Date_GetDay(startDate))); if (!ks.m_dateBegin.isValid()) ks.m_dateBegin = d; else if (d < ks.m_dateBegin) ks.m_dateBegin = d; if (!ks.m_dateEnd.isValid()) ks.m_dateEnd = d; else if (d > ks.m_dateEnd) ks.m_dateEnd = d; } else { DBG_WARN(0, "No date in current transaction"); } // add information about remote account to memo in case we have something const char *remoteAcc = AB_Transaction_GetRemoteAccountNumber(t); const char *remoteBankCode = AB_Transaction_GetRemoteBankCode(t); if (remoteAcc && remoteBankCode) { kt.m_strMemo += QString("\n%1/%2").arg(remoteBankCode, remoteAcc); } // make hash value unique in case we don't have one already if (kt.m_strBankID.isEmpty()) { QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(kt.m_datePosted.toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); QMap::const_iterator it; it = m_hashMap.constFind(hash); if (it == m_hashMap.constEnd()) { m_hashMap[hash] = true; break; } ++idx; } kt.m_strBankID = QString("%1-%2").arg(acc.id()).arg(hash); } // store transaction ks.m_listTransactions += kt; } void KBankingExt::_slToStatement(MyMoneyStatement &ks, const MyMoneyAccount& acc, const AB_SECURITY *sy) { MyMoneyFile* file = MyMoneyFile::instance(); QString s; QString memo; const char *p; const AB_VALUE *val; const GWEN_TIME *ti; MyMoneyStatement::Security ksy; MyMoneyStatement::Price kpr; MyMoneyStatement::Transaction kt; // security name p = AB_Security_GetName(sy); if (p) ksy.m_strName = QString::fromUtf8(p); // security id p = AB_Security_GetUniqueId(sy); if (p) { ksy.m_strId = QString::fromUtf8(p); ksy.m_strSymbol = QString::fromUtf8(p); kpr.m_strSecurity = QString::fromUtf8(p); } // date ti = AB_Security_GetUnitPriceDate(sy); if (ti) { int year, month, day; if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) { kpr.m_date = QDate(year, month + 1, day); } } // value val = AB_Security_GetUnitPriceValue(sy); if (val) kpr.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val)); // generate dummy booking in case online balance does not match MyMoneySecurity security; MyMoneyAccount sacc; foreach (const auto sAccount, file->account(acc.id()).accountList()) { sacc=file->account(sAccount); security=file->security(sacc.currencyId()); if ((!ksy.m_strSymbol.isEmpty() && QString::compare(ksy.m_strSymbol, security.tradingSymbol(), Qt::CaseInsensitive) == 0) || (!ksy.m_strName.isEmpty() && QString::compare(ksy.m_strName, security.name(), Qt::CaseInsensitive) == 0)) { if (sacc.balance().toDouble() != AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy))) { qDebug("Creating dummy correction booking for '%s' with %f shares", qPrintable(security.tradingSymbol()), AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy))-sacc.balance().toDouble()); kt.m_fees = MyMoneyMoney(); kt.m_strMemo = "Dummy booking added by KMyMoney to reflect online balance - please adjust"; kt.m_datePosted = QDate::currentDate(); kt.m_strSymbol=security.tradingSymbol(); kt.m_strSecurity=security.name(); kt.m_strBrokerageAccount=acc.name(); kt.m_shares=MyMoneyMoney(AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy))-sacc.balance().toDouble()); if (AB_Value_GetValueAsDouble(AB_Security_GetUnits(sy)) > sacc.balance().toDouble()) kt.m_eAction = eMyMoney::Transaction::Action::Shrsin; else kt.m_eAction = eMyMoney::Transaction::Action::Shrsout; // store transaction ks.m_listTransactions += kt; } else qDebug("Online balance matches balance in KMyMoney account!"); } } // store security ks.m_listSecurities += ksy; // store prices ks.m_listPrices += kpr; } bool KBankingExt::importAccountInfo(AB_IMEXPORTER_CONTEXT *ctx, AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t /*flags*/) { const char *p; DBG_INFO(0, "Importing account..."); // account number MyMoneyStatement ks; p = AB_ImExporterAccountInfo_GetAccountNumber(ai); if (p) { ks.m_strAccountNumber = m_parent->stripLeadingZeroes(p); } p = AB_ImExporterAccountInfo_GetBankCode(ai); if (p) { ks.m_strRoutingNumber = m_parent->stripLeadingZeroes(p); } MyMoneyAccount kacc; if (!ks.m_strAccountNumber.isEmpty() || !ks.m_strRoutingNumber.isEmpty()) { kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2-%3").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber).arg(AB_ImExporterAccountInfo_GetAccountType(ai))); ks.m_accountId = kacc.id(); } // account name p = AB_ImExporterAccountInfo_GetAccountName(ai); if (p) ks.m_strAccountName = p; // account type switch (AB_ImExporterAccountInfo_GetAccountType(ai)) { case AB_AccountType_Bank: ks.m_eType = eMyMoney::Statement::Type::Savings; break; case AB_AccountType_CreditCard: ks.m_eType = eMyMoney::Statement::Type::CreditCard; break; case AB_AccountType_Checking: ks.m_eType = eMyMoney::Statement::Type::Checkings; break; case AB_AccountType_Savings: ks.m_eType = eMyMoney::Statement::Type::Savings; break; case AB_AccountType_Investment: ks.m_eType = eMyMoney::Statement::Type::Investment; break; case AB_AccountType_Cash: default: ks.m_eType = eMyMoney::Statement::Type::None; } // account status const AB_BALANCE *bal = AB_Balance_List_GetLatestByType(AB_ImExporterAccountInfo_GetBalanceList(ai), AB_Balance_TypeBooked); if (!bal) bal = AB_Balance_List_GetLatestByType(AB_ImExporterAccountInfo_GetBalanceList(ai), AB_Balance_TypeNoted); if (bal) { const AB_VALUE* val = AB_Balance_GetValue(bal); if (val) { DBG_INFO(0, "Importing balance"); ks.m_closingBalance = AB_Value_toMyMoneyMoney(val); p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } const GWEN_DATE* dt = AB_Balance_GetDate(bal); if (dt) { ks.m_dateEnd = QDate(GWEN_Date_GetYear(dt), GWEN_Date_GetMonth(dt) , GWEN_Date_GetDay(dt)); } else { DBG_WARN(0, "No date for balance"); } } else { DBG_WARN(0, "No account balance"); } // clear hash map m_hashMap.clear(); // get all securities const AB_SECURITY* s = AB_ImExporterContext_GetFirstSecurity(ctx); while (s) { qDebug("Found security '%s'", AB_Security_GetName(s)); _slToStatement(ks, kacc, s); s = AB_Security_List_Next(s); } // get all transactions const AB_TRANSACTION* t = AB_ImExporterAccountInfo_GetFirstTransaction(ai, AB_Transaction_TypeStatement, 0); while (t) { _xaToStatement(ks, kacc, t); t = AB_Transaction_List_FindNextByType(t, AB_Transaction_TypeStatement, 0); } // import them if (!m_parent->importStatement(ks)) { if (KMessageBox::warningYesNo(0, i18n("Error importing statement. Do you want to continue?"), i18n("Critical Error")) == KMessageBox::No) { DBG_ERROR(0, "User aborted"); return false; } } return true; } K_PLUGIN_FACTORY_WITH_JSON(KBankingFactory, "kbanking.json", registerPlugin();) #include "kbanking.moc" diff --git a/kmymoney/plugins/views/budget/kbudgetview.cpp b/kmymoney/plugins/views/budget/kbudgetview.cpp index 119294f3b..95f7b1998 100644 --- a/kmymoney/plugins/views/budget/kbudgetview.cpp +++ b/kmymoney/plugins/views/budget/kbudgetview.cpp @@ -1,539 +1,539 @@ /*************************************************************************** kbudgetview.cpp --------------- begin : Thu Jan 10 2006 copyright : (C) 2006 by Darren Gould email : darren_gould@gmx.de Alvaro Soliverez (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 "kbudgetview_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" using namespace Icons; KBudgetView::KBudgetView(QWidget *parent) : KMyMoneyAccountsViewBase(*new KBudgetViewPrivate(this), parent) { Q_D(KBudgetView); d->m_inSelection = false; } KBudgetView::KBudgetView(KBudgetViewPrivate &dd, QWidget *parent) : KMyMoneyAccountsViewBase(dd, parent) { } KBudgetView::~KBudgetView() { } void KBudgetView::showEvent(QShowEvent * event) { Q_D(KBudgetView); if (!d->m_proxyModel) d->init(); emit customActionRequested(View::Budget, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); } void KBudgetView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KBudgetView); QTimer::singleShot(0, d->ui->m_budgetList, SLOT(setFocus())); } break; default: break; } } void KBudgetView::refresh() { Q_D(KBudgetView); if (isVisible()) { if (d->m_inSelection) QTimer::singleShot(0, this, SLOT(refresh())); else { d->loadBudgets(); d->m_needsRefresh = false; } } else { d->m_needsRefresh = true; } } void KBudgetView::slotNewBudget() { Q_D(KBudgetView); d->askSave(); auto date = QDate::currentDate(); date.setDate(date.year(), KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); auto newname = i18n("Budget %1", date.year()); MyMoneyBudget budget; // make sure we have a unique name try { int i = 1; // Exception thrown when the name is not found while (1) { MyMoneyFile::instance()->budgetByName(newname); newname = i18n("Budget %1 %2", date.year(), i++); } } catch (const MyMoneyException &) { // all ok, the name is unique } MyMoneyFileTransaction ft; try { budget.setName(newname); budget.setBudgetStart(date); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add budget"), QString::fromLatin1(e.what())); } } void KBudgetView::slotDeleteBudget() { Q_D(KBudgetView); if (d->m_budgetList.isEmpty()) return; // shouldn't happen auto file = MyMoneyFile::instance(); // get confirmation from user QString prompt; if (d->m_budgetList.size() == 1) prompt = i18n("

Do you really want to remove the budget %1?

", d->m_budgetList.front().name()); else prompt = i18n("Do you really want to remove all selected budgets?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Budget")) == KMessageBox::No) return; try { MyMoneyFileTransaction ft; // now loop over all selected d->m_budgetList and remove them for (const auto& budget : d->m_budgetList) file->removeBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to remove budget."), QString::fromLatin1(e.what())); } } void KBudgetView::slotCopyBudget() { Q_D(KBudgetView); if (d->m_budgetList.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_budgetList.first(); budget.clearId(); budget.setName(i18n("Copy of %1", budget.name())); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add budget"), QString::fromLatin1(e.what())); } } } void KBudgetView::slotChangeBudgetYear() { Q_D(KBudgetView); if (d->m_budgetList.size() == 1) { QStringList years; int current = 0; bool haveCurrent = false; MyMoneyBudget budget = *(d->m_budgetList.begin()); for (int i = (QDate::currentDate().year() - 3); i < (QDate::currentDate().year() + 5); ++i) { years << QString::fromLatin1("%1").arg(i); if (i == budget.budgetStart().year()) { haveCurrent = true; } if (!haveCurrent) ++current; } if (!haveCurrent) current = 0; bool ok = false; auto yearString = QInputDialog::getItem(this, i18n("Select year"), i18n("Budget year"), years, current, false, &ok); if (ok) { int year = yearString.toInt(0, 0); QDate newYear = QDate(year, budget.budgetStart().month(), budget.budgetStart().day()); if (newYear != budget.budgetStart()) { MyMoneyFileTransaction ft; try { budget.setBudgetStart(newYear); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify budget."), QString::fromLatin1(e.what())); } } } } } void KBudgetView::slotBudgetForecast() { Q_D(KBudgetView); if (d->m_budgetList.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_budgetList.first(); bool calcBudget = budget.getaccounts().count() == 0; if (!calcBudget) { if (KMessageBox::warningContinueCancel(0, i18n("The current budget already contains data. Continuing will replace all current values of this budget."), i18nc("Warning message box", "Warning")) == KMessageBox::Continue) calcBudget = true; } if (calcBudget) { QDate historyStart; QDate historyEnd; QDate budgetStart; QDate budgetEnd; budgetStart = budget.budgetStart(); budgetEnd = budgetStart.addYears(1).addDays(-1); historyStart = budgetStart.addYears(-1); historyEnd = budgetEnd.addYears(-1); MyMoneyForecast forecast = KMyMoneyUtils::forecast(); forecast.createBudget(budget, historyStart, historyEnd, budgetStart, budgetEnd, true); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify budget."), QString::fromLatin1(e.what())); } } } void KBudgetView::slotResetBudget() { Q_D(KBudgetView); try { d->m_budget = MyMoneyFile::instance()->budget(d->m_budget.id()); d->loadAccounts(); const auto index = d->ui->m_accountTree->currentIndex(); if (index.isValid()) { const auto acc = d->ui->m_accountTree->model()->data(index, (int)eAccountsModel::Role::Account).value(); slotSelectAccount(acc, eView::Intent::None); } else { d->ui->m_budgetValue->clear(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to reset budget"), QString::fromLatin1(e.what())); } } void KBudgetView::slotUpdateBudget() { Q_D(KBudgetView); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyBudget(d->m_budget); ft.commit(); d->refreshHideUnusedButton(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify budget"), QString::fromLatin1(e.what())); } } void KBudgetView::slotStartRename() { Q_D(KBudgetView); QTreeWidgetItemIterator it_l(d->ui->m_budgetList, QTreeWidgetItemIterator::Selected); QTreeWidgetItem* it_v; if ((it_v = *it_l) != 0) { d->ui->m_budgetList->editItem(it_v, 0); } } void KBudgetView::slotOpenContextMenu(const QPoint&) { Q_D(KBudgetView); typedef void(KBudgetView::*KBudgetViewFunc)(); struct actionInfo { KBudgetViewFunc callback; QString text; Icon icon; bool enabled; }; const auto actionStates = d->actionStates(); const QVector actionInfos { {&KBudgetView::slotNewBudget, i18n("New budget"), Icon::BudgetNew, actionStates[eMenu::Action::NewBudget]}, {&KBudgetView::slotStartRename, i18n("Rename budget"), Icon::BudgetRename, actionStates[eMenu::Action::RenameBudget]}, {&KBudgetView::slotDeleteBudget, i18n("Delete budget"), Icon::BudgetDelete, actionStates[eMenu::Action::DeleteBudget]}, {&KBudgetView::slotCopyBudget, i18n("Copy budget"), Icon::BudgetCopy, actionStates[eMenu::Action::CopyBudget]}, - {&KBudgetView::slotChangeBudgetYear, i18n("Change budget year"), Icon::ViewCalendar, actionStates[eMenu::Action::ChangeBudgetYear]}, - {&KBudgetView::slotBudgetForecast, i18n("Budget based on forecast"), Icon::ViewForecast, actionStates[eMenu::Action::BudgetForecast]} + {&KBudgetView::slotChangeBudgetYear, i18n("Change budget year"), Icon::Calendar, actionStates[eMenu::Action::ChangeBudgetYear]}, + {&KBudgetView::slotBudgetForecast, i18n("Budget based on forecast"), Icon::Forecast, actionStates[eMenu::Action::BudgetForecast]} }; auto menu = new QMenu(i18nc("Menu header", "Budget options")); for (const auto& info : actionInfos) { auto a = menu->addAction(Icons::get(info.icon), info.text, this, info.callback); a->setEnabled(info.enabled); } menu->exec(QCursor::pos()); } void KBudgetView::slotItemChanged(QTreeWidgetItem* p, int col) { // if we don't have an item we actually don't care about it if (!p) return; auto pBudget = dynamic_cast(p); if (!pBudget) return; if (col == 1) { pBudget->setText(1, QString().setNum(pBudget->budget().budgetStart().year())); return; } // create a copy of the new name without leading and trailing whitespaces QString new_name = p->text(0).trimmed(); if (pBudget->budget().name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a budget with the new name try { // this function call will throw an exception, if the budget // hasn't been found. MyMoneyFile::instance()->budgetByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A budget with the name '%1' already exists. It is not advisable to have " "multiple budgets with the same identification name. Are you sure you would like " "to rename the budget?", new_name)) != KMessageBox::Yes) { p->setText(0, pBudget->budget().name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } MyMoneyBudget b = pBudget->budget(); b.setName(new_name); // don't use pBudget beyond this point as it will change due to call to modifyBudget pBudget = 0; MyMoneyFile::instance()->modifyBudget(b); // the above call to modifyBudget will reload the view so // all references and pointers to the view have to be // re-established. You cannot use pBudget beyond this point!!! ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify budget"), QString::fromLatin1(e.what())); } } else { pBudget->setText(0, new_name); } } void KBudgetView::slotSelectAccount(const MyMoneyObject &obj, eView::Intent intent) { Q_UNUSED(intent) Q_D(KBudgetView); d->ui->m_assignmentBox->setEnabled(false); if (typeid(obj) != typeid(MyMoneyAccount)) return; const MyMoneyAccount& acc = dynamic_cast(obj); d->ui->m_assignmentBox->setEnabled(true); if (d->m_budget.id().isEmpty()) return; QString id = acc.id(); d->ui->m_leAccounts->setText(MyMoneyFile::instance()->accountToCategory(id)); d->ui->m_cbBudgetSubaccounts->setChecked(d->m_budget.account(id).budgetSubaccounts()); d->ui->m_accountTotal->setValue(d->m_budget.account(id).totalBalance()); MyMoneyBudget::AccountGroup budgetAccount = d->m_budget.account(id); if (id != budgetAccount.id()) { budgetAccount.setBudgetLevel(eMyMoney::Budget::Level::Monthly); } d->ui->m_budgetValue->setBudgetValues(d->m_budget, budgetAccount); } void KBudgetView::slotBudgetedAmountChanged() { Q_D(KBudgetView); if (d->m_budget.id().isEmpty()) return; const auto indexes = d->ui->m_accountTree->selectionModel()->selectedIndexes(); if (indexes.empty()) return; QString accountID = indexes.front().data((int)eAccountsModel::Role::ID).toString(); MyMoneyBudget::AccountGroup accountGroup = d->m_budget.account(accountID); accountGroup.setId(accountID); d->ui->m_budgetValue->budgetValues(d->m_budget, accountGroup); d->m_budget.setAccount(accountGroup, accountID); d->m_budgetProxyModel->setBudget(d->m_budget); d->ui->m_accountTotal->setValue(accountGroup.totalBalance()); d->ui->m_updateButton->setEnabled(!(d->selectedBudget() == d->m_budget)); d->ui->m_resetButton->setEnabled(!(d->selectedBudget() == d->m_budget)); } void KBudgetView::cb_includesSubaccounts_clicked() { Q_D(KBudgetView); if (d->m_budget.id().isEmpty()) return; QModelIndexList indexes = d->ui->m_accountTree->selectionModel()->selectedIndexes(); if (!indexes.empty()) { QString accountID = indexes.front().data((int)eAccountsModel::Role::ID).toString(); // now, we get a reference to the accountgroup, to modify its attribute, // and then put the resulting account group instead of the original MyMoneyBudget::AccountGroup auxAccount = d->m_budget.account(accountID); auxAccount.setBudgetSubaccounts(d->ui->m_cbBudgetSubaccounts->isChecked()); // in case we turn the option on, we check that no subordinate account // has a budget. If we find some, we ask the user if he wants to move it // to the current account or leave things as they are if (d->ui->m_cbBudgetSubaccounts->isChecked()) { // TODO: asking the user needs to be added. So long, we assume yes if (1) { MyMoneyBudget::AccountGroup subAccount; if (d->collectSubBudgets(subAccount, indexes.front())) { // we found a sub-budget somewhere // so we add those figures found and // clear the subaccounts auxAccount += subAccount; d->clearSubBudgets(indexes.front()); } if (auxAccount.budgetLevel() == eMyMoney::Budget::Level::None) { MyMoneyBudget::PeriodGroup period; auxAccount.addPeriod(d->m_budget.budgetStart(), period); auxAccount.setBudgetLevel(eMyMoney::Budget::Level::Monthly); } } } d->m_budget.setAccount(auxAccount, accountID); d->m_budgetProxyModel->setBudget(d->m_budget); d->ui->m_budgetValue->setBudgetValues(d->m_budget, auxAccount); d->loadAccounts(); } } void KBudgetView::slotBudgetBalanceChanged(const MyMoneyMoney &balance) { Q_D(KBudgetView); d->netBalProChanged(balance, d->ui->m_balanceLabel, View::Budget); } void KBudgetView::slotSelectBudget() { Q_D(KBudgetView); d->askSave(); KBudgetListItem* item; QTreeWidgetItemIterator widgetIt = QTreeWidgetItemIterator(d->ui->m_budgetList); if (d->m_budget.id().isEmpty()) { item = dynamic_cast(*widgetIt); if (item) { d->ui->m_budgetList->blockSignals(true); d->ui->m_budgetList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); d->ui->m_budgetList->blockSignals(false); } } d->ui->m_accountTree->setEnabled(false); d->ui->m_assignmentBox->setEnabled(false); d->m_budget = MyMoneyBudget(); QTreeWidgetItemIterator it_l(d->ui->m_budgetList, QTreeWidgetItemIterator::Selected); item = dynamic_cast(*it_l); if (item) { d->m_budget = item->budget(); d->ui->m_accountTree->setEnabled(true); } d->refreshHideUnusedButton(); d->loadAccounts(); const auto index = d->ui->m_accountTree->currentIndex(); if (index.isValid()) { const auto acc = d->ui->m_accountTree->model()->data(index, (int)eAccountsModel::Role::Account).value(); slotSelectAccount(acc, eView::Intent::None); } else { d->ui->m_budgetValue->clear(); } d->m_budgetList.clear(); if (!d->m_budget.id().isEmpty()) d->m_budgetList << d->m_budget; d->actionStates(); d->updateButtonStates(); } void KBudgetView::slotHideUnused(bool toggled) { Q_D(KBudgetView); // make sure we show all items for an empty budget const auto prevState = !toggled; d->refreshHideUnusedButton(); if (prevState != d->ui->m_hideUnusedButton->isChecked()) d->m_budgetProxyModel->setHideUnusedIncomeExpenseAccounts(d->ui->m_hideUnusedButton->isChecked()); } diff --git a/kmymoney/plugins/views/forecast/kforecastview_p.h b/kmymoney/plugins/views/forecast/kforecastview_p.h index ae3172055..cd807b56c 100644 --- a/kmymoney/plugins/views/forecast/kforecastview_p.h +++ b/kmymoney/plugins/views/forecast/kforecastview_p.h @@ -1,1027 +1,1027 @@ /*************************************************************************** kforecastview.cpp ------------------- copyright : (C) 2007 by Alvaro Soliverez email : asoliverez@gmail.com (C) 2017 Ł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. * * * ***************************************************************************/ #ifndef KFORECASTVIEW_P_H #define KFORECASTVIEW_P_H #include "kforecastview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kforecastview.h" #include "forecastviewsettings.h" #include "kmymoneyviewbase_p.h" #include "mymoneymoney.h" #include "mymoneyforecast.h" #include "mymoneyprice.h" #include "mymoneyutils.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyexception.h" #include "mymoneysecurity.h" #include "kmymoneysettings.h" #include "mymoneybudget.h" #include "fixedcolumntreeview.h" #include "icons.h" #include "mymoneyenums.h" #include "kmymoneyutils.h" #include "kmymoneyplugin.h" #include "plugins/views/reports/reportsviewenums.h" using namespace Icons; typedef enum { SummaryView = 0, ListView, AdvancedView, BudgetView, ChartView, // insert new values above this line MaxViewTabs } ForecastViewTab; enum ForecastViewRoles { ForecastRole = Qt::UserRole, /**< The forecast is held in this role.*/ AccountRole = Qt::UserRole + 1, /**< The MyMoneyAccount is stored in this role in column 0.*/ AmountRole = Qt::UserRole + 2, /**< The amount.*/ ValueRole = Qt::UserRole + 3, /**< The value.*/ }; enum EForecastViewType { eSummary = 0, eDetailed, eAdvanced, eBudget, eUndefined }; class KForecastViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KForecastView) public: explicit KForecastViewPrivate(KForecastView *qq) : KMyMoneyViewBasePrivate(), q_ptr(qq), ui(new Ui::KForecastView), m_needLoad(true), m_totalItem(0), m_assetItem(0), m_liabilityItem(0), m_incomeItem(0), m_expenseItem(0), m_chartLayout(0), m_forecastChart(nullptr) { } ~KForecastViewPrivate() { delete ui; } void init() { Q_Q(KForecastView); m_needLoad = false; ui->setupUi(q); for (int i = 0; i < MaxViewTabs; ++i) m_needReload[i] = false; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Last Use Settings"); ui->m_tab->setCurrentIndex(grp.readEntry("KForecastView_LastType", 0)); - ui->m_forecastButton->setIcon(Icons::get(Icon::ViewForecast)); + ui->m_forecastButton->setIcon(Icons::get(Icon::Forecast)); q->connect(ui->m_tab, &QTabWidget::currentChanged, q, &KForecastView::slotTabChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KForecastView::refresh); q->connect(ui->m_forecastButton, &QAbstractButton::clicked, q, &KForecastView::slotManualForecast); ui->m_forecastList->setUniformRowHeights(true); ui->m_forecastList->setAllColumnsShowFocus(true); ui->m_summaryList->setAllColumnsShowFocus(true); ui->m_budgetList->setAllColumnsShowFocus(true); ui->m_advancedList->setAlternatingRowColors(true); q->connect(ui->m_forecastList, &QTreeWidget::itemExpanded, q, &KForecastView::itemExpanded); q->connect(ui->m_forecastList, &QTreeWidget::itemCollapsed, q, &KForecastView::itemCollapsed); q->connect(ui->m_summaryList, &QTreeWidget::itemExpanded, q, &KForecastView::itemExpanded); q->connect(ui->m_summaryList, &QTreeWidget::itemCollapsed, q, &KForecastView::itemCollapsed); q->connect(ui->m_budgetList, &QTreeWidget::itemExpanded, q, &KForecastView::itemExpanded); q->connect(ui->m_budgetList, &QTreeWidget::itemCollapsed, q, &KForecastView::itemCollapsed); m_chartLayout = ui->m_tabChart->layout(); m_chartLayout->setSpacing(6); loadForecastSettings(); } void loadForecast(ForecastViewTab tab) { if (m_needReload[tab]) { switch (tab) { case ListView: loadListView(); break; case SummaryView: loadSummaryView(); break; case AdvancedView: loadAdvancedView(); break; case BudgetView: loadBudgetView(); break; case ChartView: loadChartView(); break; default: break; } m_needReload[tab] = false; } } void loadListView() { MyMoneyForecast forecast = KMyMoneyUtils::forecast(); const auto file = MyMoneyFile::instance(); //get the settings from current page forecast.setForecastDays(ui->m_forecastDays->value()); forecast.setAccountsCycle(ui->m_accountsCycle->value()); forecast.setBeginForecastDay(ui->m_beginDay->value()); forecast.setForecastCycles(ui->m_forecastCycles->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); forecast.doForecast(); ui->m_forecastList->clear(); ui->m_forecastList->setColumnCount(0); ui->m_forecastList->setIconSize(QSize(22, 22)); ui->m_forecastList->setSortingEnabled(true); ui->m_forecastList->sortByColumn(0, Qt::AscendingOrder); //add columns QStringList headerLabels; headerLabels << i18n("Account"); //add cycle interval columns headerLabels << i18nc("Today's forecast", "Current"); for (int i = 1; i <= forecast.forecastDays(); ++i) { QDate forecastDate = QDate::currentDate().addDays(i); headerLabels << QLocale().toString(forecastDate, QLocale::LongFormat); } //add variation columns headerLabels << i18n("Total variation"); //set the columns ui->m_forecastList->setHeaderLabels(headerLabels); //add default rows addTotalRow(ui->m_forecastList, forecast); addAssetLiabilityRows(forecast); //load asset and liability forecast accounts loadAccounts(forecast, file->asset(), m_assetItem, eDetailed); loadAccounts(forecast, file->liability(), m_liabilityItem, eDetailed); adjustHeadersAndResizeToContents(ui->m_forecastList); // add the fixed column only if the horizontal scroll bar is visible m_fixedColumnView.reset(ui->m_forecastList->horizontalScrollBar()->isVisible() ? new FixedColumnTreeView(ui->m_forecastList) : 0); } void loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, QTreeWidgetItem* parentItem, int forecastType) { QMap nameIdx; const auto file = MyMoneyFile::instance(); QTreeWidgetItem *forecastItem = 0; //Get all accounts of the right type to calculate forecast const auto accList = account.accountList(); if (accList.isEmpty()) return; foreach (const auto sAccount, accList) { auto subAccount = file->account(sAccount); //only add the account if it is a forecast account or the parent of a forecast account if (includeAccount(forecast, subAccount)) { nameIdx[subAccount.id()] = subAccount.id(); } } QMap::ConstIterator it_nc; for (it_nc = nameIdx.constBegin(); it_nc != nameIdx.constEnd(); ++it_nc) { const MyMoneyAccount subAccount = file->account(*it_nc); MyMoneySecurity currency; if (subAccount.isInvest()) { MyMoneySecurity underSecurity = file->security(subAccount.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(subAccount.currencyId()); } forecastItem = new QTreeWidgetItem(parentItem); forecastItem->setText(0, subAccount.name()); forecastItem->setIcon(0, subAccount.accountPixmap()); forecastItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); forecastItem->setData(0, AccountRole, QVariant::fromValue(subAccount)); forecastItem->setExpanded(true); switch (forecastType) { case eSummary: updateSummary(forecastItem); break; case eDetailed: updateDetailed(forecastItem); break; case EForecastViewType::eBudget: updateBudget(forecastItem); break; default: break; } loadAccounts(forecast, subAccount, forecastItem, forecastType); } } void loadSummaryView() { MyMoneyForecast forecast = KMyMoneyUtils::forecast(); QList accList; const auto file = MyMoneyFile::instance(); //get the settings from current page forecast.setForecastDays(ui->m_forecastDays->value()); forecast.setAccountsCycle(ui->m_accountsCycle->value()); forecast.setBeginForecastDay(ui->m_beginDay->value()); forecast.setForecastCycles(ui->m_forecastCycles->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); forecast.doForecast(); //add columns QStringList headerLabels; headerLabels << i18n("Account"); headerLabels << i18nc("Today's forecast", "Current"); //if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle qint64 daysToBeginDay; if (QDate::currentDate() < forecast.beginForecastDate()) { daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); } else { daysToBeginDay = forecast.accountsCycle(); } for (auto i = 0; ((i*forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { auto intervalDays = ((i * forecast.accountsCycle()) + daysToBeginDay); headerLabels << i18np("1 day", "%1 days", intervalDays); } //add variation columns headerLabels << i18n("Total variation"); ui->m_summaryList->clear(); //set the columns ui->m_summaryList->setHeaderLabels(headerLabels); ui->m_summaryList->setIconSize(QSize(22, 22)); ui->m_summaryList->setSortingEnabled(true); ui->m_summaryList->sortByColumn(0, Qt::AscendingOrder); //add default rows addTotalRow(ui->m_summaryList, forecast); addAssetLiabilityRows(forecast); loadAccounts(forecast, file->asset(), m_assetItem, eSummary); loadAccounts(forecast, file->liability(), m_liabilityItem, eSummary); adjustHeadersAndResizeToContents(ui->m_summaryList); //Add comments to the advice list ui->m_adviceText->clear(); //Get all accounts of the right type to calculate forecast m_nameIdx.clear(); accList = forecast.accountList(); QList::const_iterator accList_t = accList.constBegin(); for (; accList_t != accList.constEnd(); ++accList_t) { MyMoneyAccount acc = *accList_t; if (m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there m_nameIdx[acc.id()] = acc.id(); } } QMap::ConstIterator it_nc; for (it_nc = m_nameIdx.constBegin(); it_nc != m_nameIdx.constEnd(); ++it_nc) { const MyMoneyAccount& acc = file->account(*it_nc); MyMoneySecurity currency; //change currency to deep currency if account is an investment if (acc.isInvest()) { MyMoneySecurity underSecurity = file->security(acc.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(acc.currencyId()); } //Check if the account is going to be below zero or below the minimal balance in the forecast period QString minimumBalance = acc.value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); //Check if the account is going to be below minimal balance auto dropMinimum = forecast.daysToMinimumBalance(acc); //Check if the account is going to be below zero in the future auto dropZero = forecast.daysToZeroBalance(acc); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if (dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch (dropMinimum) { case 0: msg = QString("").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name()); msg += i18n("The balance of %1 is below the minimum balance %2 today.", acc.name(), MyMoneyUtils::formatMoney(minBalance, acc, currency)); msg += QString(""); break; default: msg = QString("").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name()); msg += i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", "The balance of %2 will drop below the minimum balance %3 in %1 days.", dropMinimum - 1, acc.name(), MyMoneyUtils::formatMoney(minBalance, acc, currency)); msg += QString(""); } if (!msg.isEmpty()) { ui->m_adviceText->append(msg); } } // a drop below zero is always shown msg.clear(); switch (dropZero) { case -1: break; case 0: if (acc.accountGroup() == eMyMoney::Account::Type::Asset) { msg = QString("").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name()); msg += i18n("The balance of %1 is below %2 today.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); msg += QString(""); break; } if (acc.accountGroup() == eMyMoney::Account::Type::Liability) { msg = i18n("The balance of %1 is above %2 today.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); break; } break; default: if (acc.accountGroup() == eMyMoney::Account::Type::Asset) { msg = QString("").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name()); msg += i18np("The balance of %2 will drop below %3 in %1 day.", "The balance of %2 will drop below %3 in %1 days.", dropZero, acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); msg += QString(""); break; } if (acc.accountGroup() == eMyMoney::Account::Type::Liability) { msg = i18np("The balance of %2 will raise above %3 in %1 day.", "The balance of %2 will raise above %3 in %1 days.", dropZero, acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); break; } } if (!msg.isEmpty()) { ui->m_adviceText->append(msg); } //advice about trends msg.clear(); MyMoneyMoney accCycleVariation = forecast.accountCycleVariation(acc); if (accCycleVariation < MyMoneyMoney()) { msg = QString("").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name()); msg += i18n("The account %1 is decreasing %2 per cycle.", acc.name(), MyMoneyUtils::formatMoney(accCycleVariation, acc, currency)); msg += QString(""); } if (!msg.isEmpty()) { ui->m_adviceText->append(msg); } } ui->m_adviceText->show(); } void loadAdvancedView() { const auto file = MyMoneyFile::instance(); QList accList; MyMoneySecurity baseCurrency = file->baseCurrency(); MyMoneyForecast forecast = KMyMoneyUtils::forecast(); qint64 daysToBeginDay; //get the settings from current page forecast.setForecastDays(ui->m_forecastDays->value()); forecast.setAccountsCycle(ui->m_accountsCycle->value()); forecast.setBeginForecastDay(ui->m_beginDay->value()); forecast.setForecastCycles(ui->m_forecastCycles->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); forecast.doForecast(); //Get all accounts of the right type to calculate forecast m_nameIdx.clear(); accList = forecast.accountList(); QList::const_iterator accList_t = accList.constBegin(); for (; accList_t != accList.constEnd(); ++accList_t) { MyMoneyAccount acc = *accList_t; if (m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there m_nameIdx[acc.id()] = acc.id(); } } //clear the list, including columns ui->m_advancedList->clear(); ui->m_advancedList->setColumnCount(0); ui->m_advancedList->setIconSize(QSize(22, 22)); QStringList headerLabels; //add first column of both lists headerLabels << i18n("Account"); //if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle if (QDate::currentDate() < forecast.beginForecastDate()) { daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); } else { daysToBeginDay = forecast.accountsCycle(); } //add columns for (auto i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { headerLabels << i18n("Min Bal %1", i); headerLabels << i18n("Min Date %1", i); } for (auto i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { headerLabels << i18n("Max Bal %1", i); headerLabels << i18n("Max Date %1", i); } headerLabels << i18nc("Average balance", "Average"); ui->m_advancedList->setHeaderLabels(headerLabels); QTreeWidgetItem *advancedItem = 0; QMap::ConstIterator it_nc; for (it_nc = m_nameIdx.constBegin(); it_nc != m_nameIdx.constEnd(); ++it_nc) { const MyMoneyAccount& acc = file->account(*it_nc); QString amount; MyMoneyMoney amountMM; MyMoneySecurity currency; //change currency to deep currency if account is an investment if (acc.isInvest()) { MyMoneySecurity underSecurity = file->security(acc.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(acc.currencyId()); } advancedItem = new QTreeWidgetItem(ui->m_advancedList, advancedItem, false); advancedItem->setText(0, acc.name()); advancedItem->setIcon(0, acc.accountPixmap()); auto it_c = 1; // iterator for the columns of the listview //get minimum balance list QList minBalanceList = forecast.accountMinimumBalanceDateList(acc); QList::Iterator t_min; for (t_min = minBalanceList.begin(); t_min != minBalanceList.end() ; ++t_min) { QDate minDate = *t_min; amountMM = forecast.forecastBalance(acc, minDate); amount = MyMoneyUtils::formatMoney(amountMM, acc, currency); advancedItem->setText(it_c, amount); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } it_c++; QString dateString = QLocale().toString(minDate, QLocale::ShortFormat); advancedItem->setText(it_c, dateString); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } it_c++; } //get maximum balance list QList maxBalanceList = forecast.accountMaximumBalanceDateList(acc); QList::Iterator t_max; for (t_max = maxBalanceList.begin(); t_max != maxBalanceList.end() ; ++t_max) { QDate maxDate = *t_max; amountMM = forecast.forecastBalance(acc, maxDate); amount = MyMoneyUtils::formatMoney(amountMM, acc, currency); advancedItem->setText(it_c, amount); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } it_c++; QString dateString = QLocale().toString(maxDate, QLocale::ShortFormat); advancedItem->setText(it_c, dateString); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } it_c++; } //get average balance amountMM = forecast.accountAverageBalance(acc); amount = MyMoneyUtils::formatMoney(amountMM, acc, currency); advancedItem->setText(it_c, amount); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } it_c++; } // make sure all data is shown adjustHeadersAndResizeToContents(ui->m_advancedList); ui->m_advancedList->show(); } void loadBudgetView() { const auto file = MyMoneyFile::instance(); MyMoneyForecast forecast = KMyMoneyUtils::forecast(); //get the settings from current page and calculate this year based on last year QDate historyEndDate = QDate(QDate::currentDate().year() - 1, 12, 31); QDate historyStartDate = historyEndDate.addDays(-ui->m_accountsCycle->value() * ui->m_forecastCycles->value()); QDate forecastStartDate = QDate(QDate::currentDate().year(), 1, 1); QDate forecastEndDate = QDate::currentDate().addDays(ui->m_forecastDays->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); MyMoneyBudget budget; forecast.createBudget(budget, historyStartDate, historyEndDate, forecastStartDate, forecastEndDate, false); ui->m_budgetList->clear(); ui->m_budgetList->setIconSize(QSize(22, 22)); ui->m_budgetList->setSortingEnabled(true); ui->m_budgetList->sortByColumn(0, Qt::AscendingOrder); //add columns QStringList headerLabels; headerLabels << i18n("Account"); { forecastStartDate = forecast.forecastStartDate(); forecastEndDate = forecast.forecastEndDate(); //add cycle interval columns QDate f_date = forecastStartDate; for (; f_date <= forecastEndDate; f_date = f_date.addMonths(1)) { headerLabels << QDate::longMonthName(f_date.month()); } } //add total column headerLabels << i18nc("Total balance", "Total"); //set the columns ui->m_budgetList->setHeaderLabels(headerLabels); //add default rows addTotalRow(ui->m_budgetList, forecast); addIncomeExpenseRows(forecast); //load income and expense budget accounts loadAccounts(forecast, file->income(), m_incomeItem, EForecastViewType::eBudget); loadAccounts(forecast, file->expense(), m_expenseItem, EForecastViewType::eBudget); adjustHeadersAndResizeToContents(ui->m_budgetList); } void loadChartView() { if (m_forecastChart) delete m_forecastChart; if (const auto reportsPlugin = pPlugins.data.value("reportsview", nullptr)) { const QString args = QString::number(ui->m_comboDetail->currentIndex()) + ';' + QString::number(ui->m_forecastDays->value()) + ';' + QString::number(ui->m_tab->width()) + ';' + QString::number(ui->m_tab->height()); const auto variantReport = reportsPlugin->requestData(args, eWidgetPlugin::WidgetType::NetWorthForecastWithArgs); if (!variantReport.isNull()) m_forecastChart = variantReport.value(); else m_forecastChart = new QLabel(i18n("No data provided by reports plugin for this chart.")); } else { m_forecastChart = new QLabel(i18n("Enable reports plugin to see this chart.")); } m_chartLayout->addWidget(m_forecastChart); } void loadForecastSettings() { //fill the settings controls ui->m_forecastDays->setValue(KMyMoneySettings::forecastDays()); ui->m_accountsCycle->setValue(KMyMoneySettings::forecastAccountCycle()); ui->m_beginDay->setValue(KMyMoneySettings::beginForecastDay()); ui->m_forecastCycles->setValue(KMyMoneySettings::forecastCycles()); ui->m_historyMethod->setId(ui->radioButton11, 0); // simple moving avg ui->m_historyMethod->setId(ui->radioButton12, 1); // weighted moving avg ui->m_historyMethod->setId(ui->radioButton13, 2); // linear regression ui->m_historyMethod->button(KMyMoneySettings::historyMethod())->setChecked(true); switch (KMyMoneySettings::forecastMethod()) { case 0: ui->m_forecastMethod->setText(i18nc("Scheduled method", "Scheduled")); ui->m_forecastCycles->setDisabled(true); ui->m_historyMethodGroupBox->setDisabled(true); break; case 1: ui->m_forecastMethod->setText(i18nc("History-based method", "History")); ui->m_forecastCycles->setEnabled(true); ui->m_historyMethodGroupBox->setEnabled(true); break; default: ui->m_forecastMethod->setText(i18nc("Unknown forecast method", "Unknown")); break; } } void addAssetLiabilityRows(const MyMoneyForecast& forecast) { const auto file = MyMoneyFile::instance(); m_assetItem = new QTreeWidgetItem(m_totalItem); m_assetItem->setText(0, file->asset().name()); m_assetItem->setIcon(0, file->asset().accountPixmap()); m_assetItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_assetItem->setData(0, AccountRole, QVariant::fromValue(file->asset())); m_assetItem->setExpanded(true); m_liabilityItem = new QTreeWidgetItem(m_totalItem); m_liabilityItem->setText(0, file->liability().name()); m_liabilityItem->setIcon(0, file->liability().accountPixmap()); m_liabilityItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_liabilityItem->setData(0, AccountRole, QVariant::fromValue(file->liability())); m_liabilityItem->setExpanded(true); } void addIncomeExpenseRows(const MyMoneyForecast& forecast) { const auto file = MyMoneyFile::instance(); m_incomeItem = new QTreeWidgetItem(m_totalItem); m_incomeItem->setText(0, file->income().name()); m_incomeItem->setIcon(0, file->income().accountPixmap()); m_incomeItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_incomeItem->setData(0, AccountRole, QVariant::fromValue(file->income())); m_incomeItem->setExpanded(true); m_expenseItem = new QTreeWidgetItem(m_totalItem); m_expenseItem->setText(0, file->expense().name()); m_expenseItem->setIcon(0, file->expense().accountPixmap()); m_expenseItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_expenseItem->setData(0, AccountRole, QVariant::fromValue(file->expense())); m_expenseItem->setExpanded(true); } void addTotalRow(QTreeWidget* forecastList, const MyMoneyForecast& forecast) { const auto file = MyMoneyFile::instance(); m_totalItem = new QTreeWidgetItem(forecastList); QFont font; font.setBold(true); m_totalItem->setFont(0, font); m_totalItem->setText(0, i18nc("Total balance", "Total")); m_totalItem->setIcon(0, file->asset().accountPixmap()); m_totalItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_totalItem->setData(0, AccountRole, QVariant::fromValue(file->asset())); m_totalItem->setExpanded(true); } bool includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc) { const auto file = MyMoneyFile::instance(); if (forecast.isForecastAccount(acc)) return true; foreach (const auto sAccount, acc.accountList()) { auto account = file->account(sAccount); if (includeAccount(forecast, account)) return true; } return false; } void adjustHeadersAndResizeToContents(QTreeWidget *widget) { QSize sizeHint(0, widget->sizeHintForRow(0)); QTreeWidgetItem *header = widget->headerItem(); for (int i = 0; i < header->columnCount(); ++i) { if (i > 0) { header->setData(i, Qt::TextAlignmentRole, Qt::AlignRight); // make sure that the row height stays the same even when the column that has icons is not visible if (m_totalItem) { m_totalItem->setSizeHint(i, sizeHint); } } widget->resizeColumnToContents(i); } } void setNegative(QTreeWidgetItem *item, bool isNegative) { if (isNegative) { for (int i = 0; i < item->columnCount(); ++i) { item->setForeground(i, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } } } void showAmount(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount, const MyMoneySecurity& security) { item->setText(column, MyMoneyUtils::formatMoney(amount, security)); item->setTextAlignment(column, Qt::AlignRight | Qt::AlignVCenter); item->setFont(column, item->font(0)); if (amount.isNegative()) { item->setForeground(column, KMyMoneySettings::schemeColor(SchemeColor::Negative)); } } void adjustParentValue(QTreeWidgetItem *item, int column, const MyMoneyMoney& value) { if (!item) return; item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value() + value)); item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value().convert(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()))); // if the entry has no children, // or it is the top entry // or it is currently not open // we need to display the value of it if (item->childCount() == 0 || !item->parent() || (!item->isExpanded() && item->childCount() > 0) || (item->parent() && !item->parent()->parent())) { if (item->childCount() > 0) item->setText(column, " "); MyMoneyMoney amount = item->data(column, ValueRole).value(); showAmount(item, column, amount, MyMoneyFile::instance()->baseCurrency()); } // now make sure, the upstream accounts also get notified about the value change adjustParentValue(item->parent(), column, value); } void setValue(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount, const QDate& forecastDate) { MyMoneyAccount account = item->data(0, AccountRole).value(); //calculate the balance in base currency for the total row if (account.currencyId() != MyMoneyFile::instance()->baseCurrency().id()) { const auto file = MyMoneyFile::instance(); const auto curPrice = file->price(account.tradingCurrencyId(), file->baseCurrency().id(), forecastDate); const auto curRate = curPrice.rate(file->baseCurrency().id()); auto baseAmountMM = amount * curRate; auto value = baseAmountMM.convert(file->baseCurrency().smallestAccountFraction()); item->setData(column, ValueRole, QVariant::fromValue(value)); adjustParentValue(item->parent(), column, value); } else { item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value() + amount)); adjustParentValue(item->parent(), column, amount); } } void setAmount(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount) { item->setData(column, AmountRole, QVariant::fromValue(amount)); item->setTextAlignment(column, Qt::AlignRight | Qt::AlignVCenter); } void updateSummary(QTreeWidgetItem *item) { MyMoneyMoney amountMM; auto it_c = 1; // iterator for the columns of the listview const auto file = MyMoneyFile::instance(); qint64 daysToBeginDay; MyMoneyForecast forecast = item->data(0, ForecastRole).value(); if (QDate::currentDate() < forecast.beginForecastDate()) { daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); } else { daysToBeginDay = forecast.accountsCycle(); } MyMoneyAccount account = item->data(0, AccountRole).value(); MyMoneySecurity currency; if (account.isInvest()) { MyMoneySecurity underSecurity = file->security(account.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(account.currencyId()); } //add current balance column QDate summaryDate = QDate::currentDate(); amountMM = forecast.forecastBalance(account, summaryDate); //calculate the balance in base currency for the total row setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, summaryDate); showAmount(item, it_c, amountMM, currency); it_c++; //iterate through all other columns for (summaryDate = QDate::currentDate().addDays(daysToBeginDay); summaryDate <= forecast.forecastEndDate(); summaryDate = summaryDate.addDays(forecast.accountsCycle()), ++it_c) { amountMM = forecast.forecastBalance(account, summaryDate); //calculate the balance in base currency for the total row setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, summaryDate); showAmount(item, it_c, amountMM, currency); } //calculate and add variation per cycle setNegative(item, forecast.accountTotalVariation(account).isNegative()); setAmount(item, it_c, forecast.accountTotalVariation(account)); setValue(item, it_c, forecast.accountTotalVariation(account), forecast.forecastEndDate()); showAmount(item, it_c, forecast.accountTotalVariation(account), currency); } void updateDetailed(QTreeWidgetItem *item) { MyMoneyMoney vAmountMM; const auto file = MyMoneyFile::instance(); MyMoneyAccount account = item->data(0, AccountRole).value(); MyMoneySecurity currency; if (account.isInvest()) { MyMoneySecurity underSecurity = file->security(account.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(account.currencyId()); } int it_c = 1; // iterator for the columns of the listview MyMoneyForecast forecast = item->data(0, ForecastRole).value(); for (QDate forecastDate = QDate::currentDate(); forecastDate <= forecast.forecastEndDate(); ++it_c, forecastDate = forecastDate.addDays(1)) { MyMoneyMoney amountMM = forecast.forecastBalance(account, forecastDate); //calculate the balance in base currency for the total row setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, forecastDate); showAmount(item, it_c, amountMM, currency); } //calculate and add variation per cycle vAmountMM = forecast.accountTotalVariation(account); setAmount(item, it_c, vAmountMM); setValue(item, it_c, vAmountMM, forecast.forecastEndDate()); showAmount(item, it_c, vAmountMM, currency); } void updateBudget(QTreeWidgetItem *item) { MyMoneySecurity currency; MyMoneyMoney tAmountMM; MyMoneyForecast forecast = item->data(0, ForecastRole).value(); const auto file = MyMoneyFile::instance(); int it_c = 1; // iterator for the columns of the listview QDate forecastDate = forecast.forecastStartDate(); MyMoneyAccount account = item->data(0, AccountRole).value(); if (account.isInvest()) { MyMoneySecurity underSecurity = file->security(account.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(account.currencyId()); } //iterate columns for (; forecastDate <= forecast.forecastEndDate(); forecastDate = forecastDate.addMonths(1), ++it_c) { MyMoneyMoney amountMM; amountMM = forecast.forecastBalance(account, forecastDate); if (account.accountType() == eMyMoney::Account::Type::Expense) amountMM = -amountMM; tAmountMM += amountMM; setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, forecastDate); showAmount(item, it_c, amountMM, currency); } //set total column setAmount(item, it_c, tAmountMM); setValue(item, it_c, tAmountMM, forecast.forecastEndDate()); showAmount(item, it_c, tAmountMM, currency); } /** * Get the list of prices for an account * This is used later to create an instance of KMyMoneyAccountTreeForecastItem * */ // QList getAccountPrices(const MyMoneyAccount& acc) // { // const auto file = MyMoneyFile::instance(); // QList prices; // MyMoneySecurity security = file->baseCurrency(); // try { // if (acc.isInvest()) { // security = file->security(acc.currencyId()); // if (security.tradingCurrency() != file->baseCurrency().id()) { // MyMoneySecurity sec = file->security(security.tradingCurrency()); // prices += file->price(sec.id(), file->baseCurrency().id()); // } // } else if (acc.currencyId() != file->baseCurrency().id()) { // if (acc.currencyId() != file->baseCurrency().id()) { // security = file->security(acc.currencyId()); // prices += file->price(acc.currencyId(), file->baseCurrency().id()); // } // } // } catch (const MyMoneyException &e) { // qDebug() << Q_FUNC_INFO << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e.what(); // } // return prices; // } KForecastView *q_ptr; Ui::KForecastView *ui; bool m_needReload[MaxViewTabs]; /** * This member holds the load state of page */ bool m_needLoad; QTreeWidgetItem* m_totalItem; QTreeWidgetItem* m_assetItem; QTreeWidgetItem* m_liabilityItem; QTreeWidgetItem* m_incomeItem; QTreeWidgetItem* m_expenseItem; QLayout* m_chartLayout; QWidget *m_forecastChart; QScopedPointer m_fixedColumnView; QMap m_nameIdx; }; #endif diff --git a/kmymoney/plugins/views/onlinejoboutbox/onlinejobmodel.cpp b/kmymoney/plugins/views/onlinejoboutbox/onlinejobmodel.cpp index 0547dca4c..7617ba485 100644 --- a/kmymoney/plugins/views/onlinejoboutbox/onlinejobmodel.cpp +++ b/kmymoney/plugins/views/onlinejoboutbox/onlinejobmodel.cpp @@ -1,281 +1,281 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2013-2015 Christian Dávid * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "onlinejobmodel.h" #include #include #include #include "mymoneyobject.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyutils.h" #include "onlinetasks/interfaces/tasks/onlinetask.h" #include "onlinetasks/interfaces/tasks/credittransfer.h" #include "mymoney/onlinejobtyped.h" #include "payeeidentifier.h" #include "payeeidentifiertyped.h" #include "payeeidentifier/ibanbic/ibanbic.h" #include "icons/icons.h" #include "mymoneyexception.h" #include "mymoneyenums.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" using namespace Icons; onlineJobModel::onlineJobModel(QObject *parent) : QAbstractTableModel(parent), m_jobIdList(QStringList()) { MyMoneyFile *const file = MyMoneyFile::instance(); connect(file, &MyMoneyFile::objectAdded, this, &onlineJobModel::slotObjectAdded); connect(file, &MyMoneyFile::objectModified, this, &onlineJobModel::slotObjectModified); connect(file, &MyMoneyFile::objectRemoved, this, &onlineJobModel::slotObjectRemoved); } void onlineJobModel::load() { unload(); beginInsertRows(QModelIndex(), 0, 0); foreach (const onlineJob job, MyMoneyFile::instance()->onlineJobList()) { m_jobIdList.append(job.id()); } endInsertRows(); } void onlineJobModel::unload() { if (!m_jobIdList.isEmpty()) { beginResetModel(); m_jobIdList.clear(); endResetModel(); } } int onlineJobModel::rowCount(const QModelIndex & parent) const { if (parent.isValid()) return 0; return m_jobIdList.count(); } int onlineJobModel::columnCount(const QModelIndex & parent) const { if (parent.isValid()) return 0; return 4; } QVariant onlineJobModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (section) { case columns::ColAccount: return i18n("Account"); case columns::ColAction: return i18n("Action"); case columns::ColDestination: return i18n("Destination"); case columns::ColValue: return i18n("Value"); } } return QVariant(); } /** * @todo LOW improve speed * @todo use now onlineJob system */ QVariant onlineJobModel::data(const QModelIndex & index, int role) const { if (index.parent().isValid()) return QVariant(); Q_ASSERT(m_jobIdList.length() > index.row()); onlineJob job; try { job = MyMoneyFile::instance()->getOnlineJob(m_jobIdList[index.row()]); } catch (const MyMoneyException &) { return QVariant(); } // id of MyMoneyObject if (role == roles::OnlineJobId) return QVariant::fromValue(job.id()); else if (role == roles::OnlineJobRole) return QVariant::fromValue(job); // If job is null, display an error message and exit if (job.isNull()) { if (index.column() == columns::ColAction) { switch (role) { case Qt::DisplayRole: return i18n("Not able to display this job."); case Qt::ToolTipRole: return i18n("Could not find a plugin to display this job or it does not contain any data."); } } return QVariant(); } // Show general information if (index.column() == columns::ColAccount) { // Account column if (role == Qt::DisplayRole) { return QVariant::fromValue(job.responsibleMyMoneyAccount().name()); } else if (role == Qt::DecorationRole) { if (job.isLocked()) return Icons::get(Icon::TaskOngoing); switch (job.bankAnswerState()) { case eMyMoney::OnlineJob::sendingState::acceptedByBank: return Icons::get(Icon::TaskComplete); case eMyMoney::OnlineJob::sendingState::sendingError: case eMyMoney::OnlineJob::sendingState::abortedByUser: case eMyMoney::OnlineJob::sendingState::rejectedByBank: return Icons::get(Icon::TaskReject); case eMyMoney::OnlineJob::sendingState::noBankAnswer: break; } if (job.sendDate().isValid()) { return Icons::get(Icon::TaskAccepted); } else if (!job.isValid()) { - return Icons::get(Icon::TaskAttention); + return Icons::get(Icon::Warning); } } else if (role == Qt::ToolTipRole) { if (job.isLocked()) return i18n("Job is being processed at the moment."); switch (job.bankAnswerState()) { case eMyMoney::OnlineJob::sendingState::acceptedByBank: return i18nc("Arg 1 is a date/time", "This job was accepted by the bank on %1.", job.bankAnswerDate().toString(Qt::DefaultLocaleShortDate)); case eMyMoney::OnlineJob::sendingState::sendingError: return i18nc("Arg 1 is a date/time", "Sending this job failed (tried on %1).", job.sendDate().toString(Qt::DefaultLocaleShortDate)); case eMyMoney::OnlineJob::sendingState::abortedByUser: return i18n("Sending this job was manually aborted."); case eMyMoney::OnlineJob::sendingState::rejectedByBank: return i18nc("Arg 1 is a date/time", "The bank rejected this job on %1.", job.bankAnswerDate().toString(Qt::DefaultLocaleShortDate)); case eMyMoney::OnlineJob::sendingState::noBankAnswer: if (job.sendDate().isValid()) return i18nc("Arg 1 is a date/time", "The bank accepted this job on %1.", job.sendDate().toString(Qt::DefaultLocaleShortDate)); else if (!job.isValid()) return i18n("This job needs further editing and cannot be sent therefore."); else return i18n("This job is ready for sending."); } } return QVariant(); } else if (index.column() == columns::ColAction) { if (role == Qt::DisplayRole) return QVariant::fromValue(job.task()->jobTypeName()); return QVariant(); } // Show credit transfer data try { onlineJobTyped transfer(job); if (index.column() == columns::ColValue) { if (role == Qt::DisplayRole) return QVariant::fromValue(MyMoneyUtils::formatMoney(transfer.task()->value(), transfer.task()->currency())); if (role == Qt::TextAlignmentRole) return int (Qt::AlignVCenter | Qt::AlignRight); } else if (index.column() == columns::ColDestination) { if (role == Qt::DisplayRole) { const payeeIdentifierTyped ibanBic(transfer.constTask()->beneficiary()); return QVariant(ibanBic->ownerName()); } } } catch (const MyMoneyException &) { } return QVariant(); } void onlineJobModel::reloadAll() { emit dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, columnCount() - 1)); } /** * This method removes the rows from MyMoneyFile. */ bool onlineJobModel::removeRow(int row, const QModelIndex& parent) { if (parent.isValid()) return false; Q_ASSERT(m_jobIdList.count() < row); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction transaction; const onlineJob job = file->getOnlineJob(m_jobIdList[row]); file->removeOnlineJob(job); transaction.commit(); return true; } /** * This method removes the rows from MyMoneyFile. */ bool onlineJobModel::removeRows(int row, int count, const QModelIndex & parent) { if (parent.isValid()) return false; Q_ASSERT(m_jobIdList.count() > row); Q_ASSERT(m_jobIdList.count() >= (row + count)); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction transaction; for (int i = 0; i < count; ++i) { const onlineJob job = file->getOnlineJob(m_jobIdList[row+i]); file->removeOnlineJob(job); } transaction.commit(); return true; } void onlineJobModel::slotObjectAdded(eMyMoney::File::Object objType, const QString& id) { if (Q_LIKELY(objType != eMyMoney::File::Object::OnlineJob)) return; beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_jobIdList.append(id); endInsertRows(); } void onlineJobModel::slotObjectModified(eMyMoney::File::Object objType, const QString& id) { if (Q_LIKELY(objType != eMyMoney::File::Object::OnlineJob)) return; int row = m_jobIdList.indexOf(id); if (row != -1) emit dataChanged(index(row, 0), index(row, columnCount() - 1)); } void onlineJobModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id) { if (Q_LIKELY(objType != eMyMoney::File::Object::OnlineJob)) return; int row = m_jobIdList.indexOf(id); if (row != -1) { m_jobIdList.removeAll(id); beginRemoveRows(QModelIndex(), row, row); endRemoveRows(); } } diff --git a/kmymoney/plugins/views/reports/kreportsview_p.h b/kmymoney/plugins/views/reports/kreportsview_p.h index 54a3da98e..bedc119be 100644 --- a/kmymoney/plugins/views/reports/kreportsview_p.h +++ b/kmymoney/plugins/views/reports/kreportsview_p.h @@ -1,1497 +1,1497 @@ /*************************************************************************** kreportsview_p.h - description ------------------- begin : Sat Mar 27 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jones (C) 2017 Łukasz Wojniłowicz 2018 Michael Kiefer ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KREPORTSVIEW_P_H #define KREPORTSVIEW_P_H #include "kreportsview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WEBENGINE #include #else #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_reportcontrol.h" #include "kmymoneyviewbase_p.h" #include "kreportconfigurationfilterdlg.h" #include "mymoneyfile.h" #include "mymoneyreport.h" #include "mymoneyexception.h" #include "kmymoneysettings.h" #include "querytable.h" #include "objectinfotable.h" #include "icons/icons.h" #include #include "tocitem.h" #include "tocitemgroup.h" #include "tocitemreport.h" #include "kreportchartview.h" #include "pivottable.h" #include "reporttable.h" #include "reportcontrolimpl.h" #include "mymoneyenums.h" #include "kmm_printer.h" using namespace reports; using namespace eMyMoney; using namespace Icons; #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" /** * Helper class for KReportView. * * This is the widget which displays a single report in the TabWidget that comprises this view. * * @author Ace Jones */ class KReportTab: public QWidget { private: #ifdef ENABLE_WEBENGINE QWebEngineView *m_tableView; #else KWebView *m_tableView; #endif reports::KReportChartView *m_chartView; ReportControl *m_control; QVBoxLayout *m_layout; MyMoneyReport m_report; bool m_deleteMe; bool m_chartEnabled; bool m_showingChart; bool m_needReload; bool m_isChartViewValid; bool m_isTableViewValid; QPointer m_table; /** * Users character set encoding. */ QByteArray m_encoding; public: KReportTab(QTabWidget* parent, const MyMoneyReport& report, const KReportsView *eventHandler); ~KReportTab(); const MyMoneyReport& report() const { return m_report; } void print(); void toggleChart(); /** * Updates information about plotted chart in report's data */ void updateDataRange(); void copyToClipboard(); void saveAs(const QString& filename, bool includeCSS = false); void updateReport(); QString createTable(const QString& links = QString()); const ReportControl* control() const { return m_control; } bool isReadyToDelete() const { return m_deleteMe; } void setReadyToDelete(bool f) { m_deleteMe = f; } void modifyReport(const MyMoneyReport& report) { m_report = report; } void showEvent(QShowEvent * event) final override; void loadTab(); protected: void wheelEvent(QWheelEvent *event) override; }; /** * Helper class for KReportView. * * This is a named list of reports, which will be one section * in the list of default reports * * @author Ace Jones */ class ReportGroup: public QList { private: QString m_name; ///< the title of the group in non-translated form QString m_title; ///< the title of the group in i18n-ed form public: ReportGroup() {} ReportGroup(const QString& name, const QString& title): m_name(name), m_title(title) {} const QString& name() const { return m_name; } const QString& title() const { return m_title; } }; /** * KReportTab Implementation */ KReportTab::KReportTab(QTabWidget* parent, const MyMoneyReport& report, const KReportsView* eventHandler): QWidget(parent), #ifdef ENABLE_WEBENGINE m_tableView(new QWebEngineView(this)), #else m_tableView(new KWebView(this)), #endif m_chartView(new KReportChartView(this)), m_control(new ReportControl(this)), m_layout(new QVBoxLayout(this)), m_report(report), m_deleteMe(false), m_chartEnabled(false), m_showingChart(report.isChartByDefault()), m_needReload(true), m_isChartViewValid(false), m_isTableViewValid(false), m_table(0) { m_layout->setSpacing(6); m_tableView->setPage(new MyQWebEnginePage(m_tableView)); m_tableView->setZoomFactor(KMyMoneySettings::zoomFactor()); //set button icons m_control->ui->buttonChart->setIcon(Icons::get(Icon::OfficeChartLine)); m_control->ui->buttonClose->setIcon(Icons::get(Icon::DocumentClose)); m_control->ui->buttonConfigure->setIcon(Icons::get(Icon::Configure)); m_control->ui->buttonCopy->setIcon(Icons::get(Icon::EditCopy)); m_control->ui->buttonDelete->setIcon(Icons::get(Icon::EditDelete)); m_control->ui->buttonExport->setIcon(Icons::get(Icon::DocumentExport)); m_control->ui->buttonNew->setIcon(Icons::get(Icon::DocumentNew)); m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_chartView->hide(); m_tableView->hide(); m_layout->addWidget(m_control); m_layout->addWidget(m_tableView); m_layout->addWidget(m_chartView); m_layout->setStretch(1, 10); m_layout->setStretch(2, 10); connect(m_control->ui->buttonChart, &QAbstractButton::clicked, eventHandler, &KReportsView::slotToggleChart); connect(m_control->ui->buttonConfigure, &QAbstractButton::clicked, eventHandler, &KReportsView::slotConfigure); connect(m_control->ui->buttonNew, &QAbstractButton::clicked, eventHandler, &KReportsView::slotDuplicate); connect(m_control->ui->buttonCopy, &QAbstractButton::clicked, eventHandler, &KReportsView::slotCopyView); connect(m_control->ui->buttonExport, &QAbstractButton::clicked, eventHandler, &KReportsView::slotSaveView); connect(m_control->ui->buttonDelete, &QAbstractButton::clicked, eventHandler, &KReportsView::slotDelete); connect(m_control->ui->buttonClose, &QAbstractButton::clicked, eventHandler, &KReportsView::slotCloseCurrent); #ifdef ENABLE_WEBENGINE connect(m_tableView->page(), &QWebEnginePage::urlChanged, eventHandler, &KReportsView::slotOpenUrl); #else m_tableView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); connect(m_tableView->page(), &KWebPage::linkClicked, eventHandler, &KReportsView::slotOpenUrl); #endif // if this is a default report, then you can't delete it! if (report.id().isEmpty()) m_control->ui->buttonDelete->setEnabled(false); int tabNr = parent->addTab(this, Icons::get(Icon::Report), report.name()); parent->setTabEnabled(tabNr, true); parent->setCurrentIndex(tabNr); // get users character set encoding m_encoding = QTextCodec::codecForLocale()->name(); } KReportTab::~KReportTab() { delete m_table; } void KReportTab::wheelEvent(QWheelEvent* event) { // Zoom text on Ctrl + Scroll if (event->modifiers() & Qt::CTRL) { if (!m_showingChart) { qreal factor = m_tableView->zoomFactor(); if (event->delta() > 0) factor += 0.1; else if (event->delta() < 0) factor -= 0.1; m_tableView->setZoomFactor(factor); event->accept(); return; } } } void KReportTab::print() { if (m_tableView) { auto printer = KMyMoneyPrinter::startPrint(); if (printer != nullptr) { if (m_showingChart) { QPainter painter(printer); m_chartView->paint(&painter, painter.window()); QFont font = painter.font(); font.setPointSizeF(font.pointSizeF() * 0.8); painter.setFont(font); QLocale locale; painter.drawText(0, 0, QDate::currentDate().toString(locale.dateFormat(QLocale::ShortFormat))); /// @todo extract url from KMyMoneyApp QUrl file; if (file.isValid()) { painter.drawText(0, painter.window().height(), file.toLocalFile()); } } else { #ifdef ENABLE_WEBENGINE m_tableView->page()->print(printer, [=] (bool) {}); #else m_tableView->print(printer); #endif } } } } void KReportTab::copyToClipboard() { QMimeData* pMimeData = new QMimeData(); pMimeData->setHtml(m_table->renderReport(QLatin1String("html"), m_encoding, m_report.name(), true)); QApplication::clipboard()->setMimeData(pMimeData); } void KReportTab::saveAs(const QString& filename, bool includeCSS) { QFile file(filename); if (file.open(QIODevice::WriteOnly)) { if (QFileInfo(filename).suffix().toLower() == QLatin1String("csv")) { QTextStream(&file) << m_table->renderReport(QLatin1String("csv"), m_encoding, QString()); } else { QString table = m_table->renderReport(QLatin1String("html"), m_encoding, m_report.name(), includeCSS); QTextStream stream(&file); stream << table; } file.close(); } } void KReportTab::loadTab() { m_needReload = true; if (isVisible()) { m_needReload = false; updateReport(); } } void KReportTab::showEvent(QShowEvent * event) { if (m_needReload) { m_needReload = false; updateReport(); } QWidget::showEvent(event); } void KReportTab::updateReport() { m_isChartViewValid = false; m_isTableViewValid = false; // reload the report from the engine. It might have // been changed by the user try { // Don't try to reload default reports from the engine if (!m_report.id().isEmpty()) m_report = MyMoneyFile::instance()->report(m_report.id()); } catch (const MyMoneyException &) { } delete m_table; m_table = 0; if (m_report.reportType() == eMyMoney::Report::ReportType::PivotTable) { m_table = new PivotTable(m_report); m_chartEnabled = true; } else if (m_report.reportType() == eMyMoney::Report::ReportType::QueryTable) { m_table = new QueryTable(m_report); m_chartEnabled = false; } else if (m_report.reportType() == eMyMoney::Report::ReportType::InfoTable) { m_table = new ObjectInfoTable(m_report); m_chartEnabled = false; } m_control->ui->buttonChart->setEnabled(m_chartEnabled); m_showingChart = !m_showingChart; toggleChart(); } void KReportTab::toggleChart() { // for now it will just SHOW the chart. In the future it actually has to toggle it. if (m_showingChart) { if (!m_isTableViewValid) { m_tableView->setHtml(m_table->renderReport(QLatin1String("html"), m_encoding, m_report.name()), QUrl("file://")); // workaround for access permission to css file } m_isTableViewValid = true; m_tableView->show(); m_chartView->hide(); m_control->ui->buttonChart->setText(i18n("Chart")); m_control->ui->buttonChart->setToolTip(i18n("Show the chart version of this report")); m_control->ui->buttonChart->setIcon(Icons::get(Icon::OfficeChartLine)); } else { if (!m_isChartViewValid) m_table->drawChart(*m_chartView); m_isChartViewValid = true; m_tableView->hide(); m_chartView->show(); m_control->ui->buttonChart->setText(i18n("Report")); m_control->ui->buttonChart->setToolTip(i18n("Show the report version of this chart")); - m_control->ui->buttonChart->setIcon(Icons::get(Icon::ViewFinancialList)); + m_control->ui->buttonChart->setIcon(Icons::get(Icon::Ledger)); } m_showingChart = ! m_showingChart; } void KReportTab::updateDataRange() { QList grids = m_chartView->coordinatePlane()->gridDimensionsList(); // get dimensions of plotted graph if (grids.isEmpty()) return; QChar separator = locale().groupSeparator(); QChar decimalPoint = locale().decimalPoint(); int precision = m_report.yLabelsPrecision(); QList> dims; // create list of dimension values in string and qreal // get qreal values dims.append(qMakePair(QString(), grids.at(1).start)); dims.append(qMakePair(QString(), grids.at(1).end)); dims.append(qMakePair(QString(), grids.at(1).stepWidth)); dims.append(qMakePair(QString(), grids.at(1).subStepWidth)); // convert qreal values to string variables for (int i = 0; i < 4; ++i) { if (i > 2) ++precision; if (precision == 0) dims[i].first = locale().toString(qRound(dims.at(i).second)); else dims[i].first = locale().toString(dims.at(i).second, 'f', precision).remove(separator).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + decimalPoint + "$")); } // save string variables in report's data m_report.setDataRangeStart(dims.at(0).first); m_report.setDataRangeEnd(dims.at(1).first); m_report.setDataMajorTick(dims.at(2).first); m_report.setDataMinorTick(dims.at(3).first); } class KReportsViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KReportsView) public: explicit KReportsViewPrivate(KReportsView *qq): q_ptr(qq), m_needLoad(true), m_reportListView(nullptr), m_reportTabWidget(nullptr), m_listTab(nullptr), m_listTabLayout(nullptr), m_tocTreeWidget(nullptr), m_columnsAlreadyAdjusted(false) { } ~KReportsViewPrivate() { } void init() { Q_Q(KReportsView); m_needLoad = false; auto vbox = new QVBoxLayout(q); q->setLayout(vbox); vbox->setSpacing(6); vbox->setMargin(0); // build reports toc setColumnsAlreadyAdjusted(false); m_reportTabWidget = new QTabWidget(q); vbox->addWidget(m_reportTabWidget); m_reportTabWidget->setTabsClosable(true); m_listTab = new QWidget(m_reportTabWidget); m_listTabLayout = new QVBoxLayout(m_listTab); m_listTabLayout->setSpacing(6); m_tocTreeWidget = new QTreeWidget(m_listTab); // report-group items have only 1 column (name of group), // report items have 2 columns (report name and comment) m_tocTreeWidget->setColumnCount(2); // headers QStringList headers; headers << i18n("Reports") << i18n("Comment"); m_tocTreeWidget->setHeaderLabels(headers); m_tocTreeWidget->setAlternatingRowColors(true); m_tocTreeWidget->setSortingEnabled(true); m_tocTreeWidget->sortByColumn(0, Qt::AscendingOrder); // for report group items: // doubleclick toggles the expand-state, m_tocTreeWidget->setExpandsOnDoubleClick(false); m_tocTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); m_tocTreeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); m_listTabLayout->addWidget(m_tocTreeWidget); m_reportTabWidget->addTab(m_listTab, i18n("Reports")); q->connect(m_reportTabWidget, &QTabWidget::tabCloseRequested, q, &KReportsView::slotClose); q->connect(m_tocTreeWidget, &QTreeWidget::itemDoubleClicked, q, &KReportsView::slotItemDoubleClicked); q->connect(m_tocTreeWidget, &QWidget::customContextMenuRequested, q, &KReportsView::slotListContextMenu); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KReportsView::refresh); } void restoreTocExpandState(QMap& expandStates) { for (auto i = 0; i < m_tocTreeWidget->topLevelItemCount(); ++i) { QTreeWidgetItem* item = m_tocTreeWidget->topLevelItem(i); if (item) { QString itemLabel = item->text(0); if (expandStates.contains(itemLabel)) { item->setExpanded(expandStates[itemLabel]); } else { item->setExpanded(false); } } } } /** * Display a dialog to confirm report deletion */ int deleteReportDialog(const QString &reportName) { Q_Q(KReportsView); return KMessageBox::warningContinueCancel(q, i18n("Are you sure you want to delete report %1? There is no way to recover it.", reportName), i18n("Delete Report?")); } void addReportTab(const MyMoneyReport& report) { Q_Q(KReportsView); new KReportTab(m_reportTabWidget, report, q); } void loadView() { // remember the id of the current selected item QTreeWidgetItem* item = m_tocTreeWidget->currentItem(); QString selectedItem = (item) ? item->text(0) : QString(); // save expand states of all top-level items QMap expandStates; for (int i = 0; i < m_tocTreeWidget->topLevelItemCount(); ++i) { item = m_tocTreeWidget->topLevelItem(i); if (item) { QString itemLabel = item->text(0); if (item->isExpanded()) { expandStates.insert(itemLabel, true); } else { expandStates.insert(itemLabel, false); } } } // find the item visible on top QTreeWidgetItem* visibleTopItem = m_tocTreeWidget->itemAt(0, 0); // text of column 0 identifies the item visible on top QString visibleTopItemText; bool visibleTopItemFound = true; if (visibleTopItem == NULL) { visibleTopItemFound = false; } else { // this assumes, that all item-texts in column 0 are unique, // no matter, whether the item is a report- or a group-item visibleTopItemText = visibleTopItem->text(0); } // turn off updates to avoid flickering during reload //m_reportListView->setUpdatesEnabled(false); // // Rebuild the list page // m_tocTreeWidget->clear(); // Default Reports QList defaultreports; defaultReports(defaultreports); QList::const_iterator it_group = defaultreports.constBegin(); // the item to be set as current item QTreeWidgetItem* currentItem = 0L; // group number, this will be used as sort key for reportgroup items // we have: // 1st some default groups // 2nd a chart group // 3rd maybe a favorite group // 4th maybe an orphan group (for old reports) int defaultGroupNo = 1; int chartGroupNo = defaultreports.size() + 1; // group for diagrams QString groupName = I18N_NOOP("Charts"); TocItemGroup* chartTocItemGroup = new TocItemGroup(m_tocTreeWidget, chartGroupNo, i18n(groupName.toLatin1().data())); m_allTocItemGroups.insert(groupName, chartTocItemGroup); while (it_group != defaultreports.constEnd()) { groupName = (*it_group).name(); TocItemGroup* defaultTocItemGroup = new TocItemGroup(m_tocTreeWidget, defaultGroupNo++, i18n(groupName.toLatin1().data())); m_allTocItemGroups.insert(groupName, defaultTocItemGroup); if (groupName == selectedItem) { currentItem = defaultTocItemGroup; } QList::const_iterator it_report = (*it_group).begin(); while (it_report != (*it_group).end()) { MyMoneyReport report = *it_report; report.setGroup(groupName); TocItemReport* reportTocItemReport = new TocItemReport(defaultTocItemGroup, report); if (report.name() == selectedItem) { currentItem = reportTocItemReport; } // ALSO place it into the Charts list if it's displayed as a chart by default if (report.isChartByDefault()) { new TocItemReport(chartTocItemGroup, report); } ++it_report; } ++it_group; } // group for custom (favorite) reports int favoriteGroupNo = chartGroupNo + 1; groupName = I18N_NOOP("Favorite Reports"); TocItemGroup* favoriteTocItemGroup = new TocItemGroup(m_tocTreeWidget, favoriteGroupNo, i18n(groupName.toLatin1().data())); m_allTocItemGroups.insert(groupName, favoriteTocItemGroup); TocItemGroup* orphanTocItemGroup = 0; QList customreports = MyMoneyFile::instance()->reportList(); QList::const_iterator it_report = customreports.constBegin(); while (it_report != customreports.constEnd()) { MyMoneyReport report = *it_report; groupName = (*it_report).group(); // If this report is in a known group, place it there // KReportGroupListItem* groupnode = groupitems[(*it_report).group()]; TocItemGroup* groupNode = m_allTocItemGroups[groupName]; if (groupNode) { new TocItemReport(groupNode, report); } else { // otherwise, place it in the orphanage if (!orphanTocItemGroup) { // group for orphaned reports int orphanGroupNo = favoriteGroupNo + 1; groupName = I18N_NOOP("Old Customized Reports"); orphanTocItemGroup = new TocItemGroup(m_tocTreeWidget, orphanGroupNo, i18n(groupName.toLatin1().data())); m_allTocItemGroups.insert(groupName, orphanTocItemGroup); } new TocItemReport(orphanTocItemGroup, report); } // ALSO place it into the Favorites list if it's a favorite if ((*it_report).isFavorite()) { new TocItemReport(favoriteTocItemGroup, report); } // ALSO place it into the Charts list if it's displayed as a chart by default if ((*it_report).isChartByDefault()) { new TocItemReport(chartTocItemGroup, report); } ++it_report; } // // Go through the tabs to set their update flag or delete them if needed // int index = 1; while (index < m_reportTabWidget->count()) { // TODO: Find some way of detecting the file is closed and kill these tabs!! if (auto tab = dynamic_cast(m_reportTabWidget->widget(index))) { if (tab->isReadyToDelete() /* || ! reports.count() */) { delete tab; --index; } else { tab->loadTab(); } } ++index; } if (visibleTopItemFound) { // try to find the visibleTopItem that we had at the start of this method // intentionally not using 'Qt::MatchCaseSensitive' here // to avoid 'item not found' if someone corrected a typo only QList visibleTopItemList = m_tocTreeWidget->findItems(visibleTopItemText, Qt::MatchFixedString | Qt::MatchRecursive); if (visibleTopItemList.isEmpty()) { // the item could not be found, it was deleted or renamed visibleTopItemFound = false; } else { visibleTopItem = visibleTopItemList.at(0); if (visibleTopItem == NULL) { visibleTopItemFound = false; } } } // adjust column widths, // but only the first time when the view is loaded, // maybe the user sets other column widths later, // so don't disturb him if (columnsAlreadyAdjusted()) { // restore expand states of all top-level items restoreTocExpandState(expandStates); // restore current item m_tocTreeWidget->setCurrentItem(currentItem); // try to scroll to the item visible on top // when this method started if (visibleTopItemFound) { m_tocTreeWidget->scrollToItem(visibleTopItem); } else { m_tocTreeWidget->scrollToTop(); } return; } // avoid flickering m_tocTreeWidget->setUpdatesEnabled(false); // expand all top-level items m_tocTreeWidget->expandAll(); // resize columns m_tocTreeWidget->resizeColumnToContents(0); m_tocTreeWidget->resizeColumnToContents(1); // restore expand states of all top-level items restoreTocExpandState(expandStates); // restore current item m_tocTreeWidget->setCurrentItem(currentItem); // try to scroll to the item visible on top // when this method started if (visibleTopItemFound) { m_tocTreeWidget->scrollToItem(visibleTopItem); } else { m_tocTreeWidget->scrollToTop(); } setColumnsAlreadyAdjusted(true); m_tocTreeWidget->setUpdatesEnabled(true); } void defaultReports(QList& groups) { { ReportGroup list("Income and Expenses", i18n("Income and Expenses")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentMonth, eMyMoney::Report::DetailLevel::All, i18n("Income and Expenses This Month"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Income and Expenses This Year"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Years), TransactionFilter::Date::All, eMyMoney::Report::DetailLevel::All, i18n("Income and Expenses By Year"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last12Months, eMyMoney::Report::DetailLevel::Top, i18n("Income and Expenses Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.back().setChartDataLabels(false); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last12Months, eMyMoney::Report::DetailLevel::Top, i18n("Income and Expenses Bar Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartType(eMyMoney::Report::ChartType::StackedBar); list.back().setChartDataLabels(false); list.back().setNegExpenses(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::Group, i18n("Income and Expenses Pie Chart"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartType(eMyMoney::Report::ChartType::Pie); list.back().setShowingRowTotals(false); groups.push_back(list); } { ReportGroup list("Net Worth", i18n("Net Worth")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::Top, i18n("Net Worth By Month"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Today, eMyMoney::Report::DetailLevel::Top, i18n("Net Worth Today"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Years), TransactionFilter::Date::All, eMyMoney::Report::DetailLevel::Top, i18n("Net Worth By Year"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Next7Days, eMyMoney::Report::DetailLevel::Top, i18n("7-day Cash Flow Forecast"), i18n("Default Report") )); list.back().setIncludingSchedules(true); list.back().setColumnsAreDays(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last12Months, eMyMoney::Report::DetailLevel::Total, i18n("Net Worth Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Institution, eMyMoney::Report::QueryColumn::None, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::Top, i18n("Account Balances by Institution"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountType, eMyMoney::Report::QueryColumn::None, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::Top, i18n("Account Balances by Type"), i18n("Default Report") )); groups.push_back(list); } { ReportGroup list("Transactions", i18n("Transactions")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Account, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Tag | eMyMoney::Report::QueryColumn::Balance, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Account"), i18n("Default Report") )); //list.back().setConvertCurrency(false); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Category, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Account | eMyMoney::Report::QueryColumn::Tag, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Category"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Payee, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Tag, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Payee"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Tag, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Category, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Tag"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Month, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Tag, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Month"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Week, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Tag, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Week"), i18n("Default Report") )); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Account, eMyMoney::Report::QueryColumn::Loan, TransactionFilter::Date::All, eMyMoney::Report::DetailLevel::All, i18n("Loan Transactions"), i18n("Default Report") )); list.back().setLoansOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountReconcile, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Balance, TransactionFilter::Date::Last3Months, eMyMoney::Report::DetailLevel::All, i18n("Transactions by Reconciliation Status"), i18n("Default Report") )); groups.push_back(list); } { ReportGroup list("CashFlow", i18n("Cash Flow")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::CashFlow, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Account, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Cash Flow Transactions This Month"), i18n("Default Report") )); groups.push_back(list); } { ReportGroup list("Investments", i18n("Investments")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::TopAccount, eMyMoney::Report::QueryColumn::Action | eMyMoney::Report::QueryColumn::Shares | eMyMoney::Report::QueryColumn::Price, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Transactions"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountByTopAccount, eMyMoney::Report::QueryColumn::Shares | eMyMoney::Report::QueryColumn::Price, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Holdings by Account"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::EquityType, eMyMoney::Report::QueryColumn::Shares | eMyMoney::Report::QueryColumn::Price, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Holdings by Type"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountByTopAccount, eMyMoney::Report::QueryColumn::Performance, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Performance by Account"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::EquityType, eMyMoney::Report::QueryColumn::Performance, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Performance by Type"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountByTopAccount, eMyMoney::Report::QueryColumn::CapitalGain, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Capital Gains by Account"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::EquityType, eMyMoney::Report::QueryColumn::CapitalGain, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Investment Capital Gains by Type"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Today, eMyMoney::Report::DetailLevel::All, i18n("Investment Holdings Pie"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Pie); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last12Months, eMyMoney::Report::DetailLevel::All, i18n("Investment Worth Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last12Months, eMyMoney::Report::DetailLevel::All, i18n("Investment Price Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.back().setIncludingBudgetActuals(false); list.back().setIncludingPrice(true); list.back().setConvertCurrency(true); list.back().setChartDataLabels(false); list.back().setSkipZero(true); list.back().setShowingColumnTotals(false); list.back().setShowingRowTotals(false); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last12Months, eMyMoney::Report::DetailLevel::All, i18n("Investment Moving Average Price Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.back().setIncludingBudgetActuals(false); list.back().setIncludingAveragePrice(true); list.back().setMovingAverageDays(10); list.back().setConvertCurrency(true); list.back().setChartDataLabels(false); list.back().setShowingColumnTotals(false); list.back().setShowingRowTotals(false); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last30Days, eMyMoney::Report::DetailLevel::All, i18n("Investment Moving Average"), i18n("Default Report") )); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.back().setIncludingBudgetActuals(false); list.back().setIncludingMovingAverage(true); list.back().setMovingAverageDays(10); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Last30Days, eMyMoney::Report::DetailLevel::All, i18n("Investment Moving Average vs Actual"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.back().setIncludingBudgetActuals(true); list.back().setIncludingMovingAverage(true); list.back().setMovingAverageDays(10); groups.push_back(list); } { ReportGroup list("Taxes", i18n("Taxes")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Category, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Account, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Tax Transactions by Category"), i18n("Default Report") )); list.back().setTax(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Payee, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Account, TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Tax Transactions by Payee"), i18n("Default Report") )); list.back().setTax(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Category, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Payee | eMyMoney::Report::QueryColumn::Account, TransactionFilter::Date::LastFiscalYear, eMyMoney::Report::DetailLevel::All, i18n("Tax Transactions by Category Last Fiscal Year"), i18n("Default Report") )); list.back().setTax(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Payee, eMyMoney::Report::QueryColumn::Number | eMyMoney::Report::QueryColumn::Category | eMyMoney::Report::QueryColumn::Account, TransactionFilter::Date::LastFiscalYear, eMyMoney::Report::DetailLevel::All, i18n("Tax Transactions by Payee Last Fiscal Year"), i18n("Default Report") )); list.back().setTax(true); groups.push_back(list); } { ReportGroup list("Budgeting", i18n("Budgeting")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::BudgetActual, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::YearToDate, eMyMoney::Report::DetailLevel::All, i18n("Budgeted vs. Actual This Year"), i18n("Default Report") )); list.back().setShowingRowTotals(true); list.back().setBudget("Any", true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::BudgetActual, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::YearToMonth, eMyMoney::Report::DetailLevel::All, i18n("Budgeted vs. Actual This Year (YTM)"), i18n("Default Report") )); list.back().setShowingRowTotals(true); list.back().setBudget("Any", true); // in case we're in January, we show the last year if (QDate::currentDate().month() == 1) { list.back().setDateFilter(TransactionFilter::Date::LastYear); } list.push_back(MyMoneyReport( eMyMoney::Report::RowType::BudgetActual, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentMonth, eMyMoney::Report::DetailLevel::All, i18n("Monthly Budgeted vs. Actual"), i18n("Default Report") )); list.back().setBudget("Any", true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::BudgetActual, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentYear, eMyMoney::Report::DetailLevel::All, i18n("Yearly Budgeted vs. Actual"), i18n("Default Report") )); list.back().setBudget("Any", true); list.back().setShowingRowTotals(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Budget, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentMonth, eMyMoney::Report::DetailLevel::All, i18n("Monthly Budget"), i18n("Default Report") )); list.back().setBudget("Any", false); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Budget, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentYear, eMyMoney::Report::DetailLevel::All, i18n("Yearly Budget"), i18n("Default Report") )); list.back().setBudget("Any", false); list.back().setShowingRowTotals(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::BudgetActual, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentYear, eMyMoney::Report::DetailLevel::Group, i18n("Yearly Budgeted vs Actual Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setBudget("Any", true); list.back().setChartType(eMyMoney::Report::ChartType::Line); groups.push_back(list); } { ReportGroup list("Forecast", i18n("Forecast")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Next12Months, eMyMoney::Report::DetailLevel::Top, i18n("Forecast By Month"), i18n("Default Report") )); list.back().setIncludingForecast(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::NextQuarter, eMyMoney::Report::DetailLevel::Top, i18n("Forecast Next Quarter"), i18n("Default Report") )); list.back().setColumnsAreDays(true); list.back().setIncludingForecast(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::ExpenseIncome, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::CurrentYear, eMyMoney::Report::DetailLevel::Top, i18n("Income and Expenses Forecast This Year"), i18n("Default Report") )); list.back().setIncludingForecast(true); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AssetLiability, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Next3Months, eMyMoney::Report::DetailLevel::Total, i18n("Net Worth Forecast Graph"), i18n("Default Report") )); list.back().setColumnsAreDays(true); list.back().setIncludingForecast(true); list.back().setChartByDefault(true); list.back().setChartCHGridLines(false); list.back().setChartSVGridLines(false); list.back().setChartType(eMyMoney::Report::ChartType::Line); groups.push_back(list); } { ReportGroup list("Information", i18n("General Information")); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Schedule, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Next12Months, eMyMoney::Report::DetailLevel::All, i18n("Schedule Information"), i18n("Default Report") )); list.back().setDetailLevel(eMyMoney::Report::DetailLevel::All); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::Schedule, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Next12Months, eMyMoney::Report::DetailLevel::All, i18n("Schedule Summary Information"), i18n("Default Report") )); list.back().setDetailLevel(eMyMoney::Report::DetailLevel::Top); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountInfo, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Today, eMyMoney::Report::DetailLevel::All, i18n("Account Information"), i18n("Default Report") )); list.back().setConvertCurrency(false); list.push_back(MyMoneyReport( eMyMoney::Report::RowType::AccountLoanInfo, static_cast(eMyMoney::Report::ColumnType::Months), TransactionFilter::Date::Today, eMyMoney::Report::DetailLevel::All, i18n("Loan Information"), i18n("Default Report") )); list.back().setConvertCurrency(false); groups.push_back(list); } } bool columnsAlreadyAdjusted() const { return m_columnsAlreadyAdjusted; } void setColumnsAlreadyAdjusted(bool adjusted) { m_columnsAlreadyAdjusted = adjusted; } KReportsView *q_ptr; /** * This member holds the load state of page */ bool m_needLoad; QListWidget* m_reportListView; QTabWidget* m_reportTabWidget; QWidget* m_listTab; QVBoxLayout* m_listTabLayout; QTreeWidget* m_tocTreeWidget; QMap m_allTocItemGroups; QString m_selectedExportFilter; bool m_columnsAlreadyAdjusted; MyMoneyAccount m_currentAccount; }; #endif diff --git a/kmymoney/views/khomeview_p.h b/kmymoney/views/khomeview_p.h index 8e69b0fa5..1752e14b4 100644 --- a/kmymoney/views/khomeview_p.h +++ b/kmymoney/views/khomeview_p.h @@ -1,1859 +1,1859 @@ /*************************************************************************** khomeview_p.h - description ------------------- begin : Tue Jan 22 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. * * * ***************************************************************************/ #ifndef KHOMEVIEW_P_H #define KHOMEVIEW_P_H #include "khomeview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WEBENGINE #include #else #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyviewbase_p.h" #include "mymoneyutils.h" #include "kmymoneyutils.h" #include "kwelcomepage.h" #include "kmymoneysettings.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyprice.h" #include "mymoneyreport.h" #include "mymoneymoney.h" #include "mymoneyforecast.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "icons.h" #include "kmymoneywebpage.h" #include "mymoneyschedule.h" #include "mymoneysecurity.h" #include "mymoneyexception.h" #include "kmymoneyplugin.h" #include "mymoneyenums.h" #include "menuenums.h" #include "plugins/views/reports/reportsviewenums.h" #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" using namespace Icons; using namespace eMyMoney; /** * @brief Converts a QPixmap to an data URI scheme * * According to RFC 2397 * * @param pixmap Source to convert * @return full data URI */ QString QPixmapToDataUri(const QPixmap& pixmap) { QImage image(pixmap.toImage()); QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer return QLatin1String("data:image/png;base64,") + QString(byteArray.toBase64()); } bool accountNameLess(const MyMoneyAccount &acc1, const MyMoneyAccount &acc2) { return acc1.name().localeAwareCompare(acc2.name()) < 0; } class KHomeViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KHomeView) public: explicit KHomeViewPrivate(KHomeView *qq) : KMyMoneyViewBasePrivate(), q_ptr(qq), m_view(nullptr), m_showAllSchedules(false), m_needLoad(true), m_netWorthGraphLastValidSize(400, 300), m_scrollBarPos(0) { } ~KHomeViewPrivate() { // if user wants to remember the font size, store it here if (KMyMoneySettings::rememberZoomFactor() && m_view) { KMyMoneySettings::setZoomFactor(m_view->zoomFactor()); KMyMoneySettings::self()->save(); } } /** * Definition of bitmap used as argument for showAccounts(). */ enum paymentTypeE { Preferred = 1, ///< show preferred accounts Payment = 2 ///< show payment accounts }; void init() { Q_Q(KHomeView); m_needLoad = false; auto vbox = new QVBoxLayout(q); q->setLayout(vbox); vbox->setSpacing(6); vbox->setMargin(0); #ifdef ENABLE_WEBENGINE m_view = new QWebEngineView(q); #else m_view = new KWebView(q); #endif m_view->setPage(new MyQWebEnginePage(m_view)); vbox->addWidget(m_view); #ifdef ENABLE_WEBENGINE q->connect(m_view->page(), &QWebEnginePage::urlChanged, q, &KHomeView::slotOpenUrl); #else m_view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); q->connect(m_view->page(), &KWebPage::linkClicked, q, &KHomeView::slotOpenUrl); #endif q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KHomeView::refresh); } /** * Print an account and its balance and limit */ void showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) { MyMoneyFile* file = MyMoneyFile::instance(); QString tmp; MyMoneySecurity currency = file->currency(acc.currencyId()); QString amount; QString amountToMinBal; //format amounts amount = MyMoneyUtils::formatMoney(value, acc, currency); amount.replace(QChar(' '), " "); if (showMinBal) { amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency); amountToMinBal.replace(QChar(' '), " "); } QString cellStatus, pathOK, pathTODO, pathNotOK; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { //show account's online-status pathOK = QPixmapToDataUri(Icons::get(Icon::DialogOKApply).pixmap(QSize(16,16))); pathTODO = QPixmapToDataUri(Icons::get(Icon::MailReceive).pixmap(QSize(16,16))); pathNotOK = QPixmapToDataUri(Icons::get(Icon::DialogCancel).pixmap(QSize(16,16))); if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) cellStatus = '-'; else if (file->hasMatchingOnlineBalance(acc)) { if (file->hasNewerTransaction(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate))) cellStatus = QString("").arg(pathTODO); else cellStatus = QString("").arg(pathOK); } else cellStatus = QString("").arg(pathNotOK); tmp = QString("%1").arg(cellStatus); } tmp += QString("") + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + ""; int countNotMarked = 0, countCleared = 0, countNotReconciled = 0; QString countStr; if (KMyMoneySettings::showCountOfUnmarkedTransactions() || KMyMoneySettings::showCountOfNotReconciledTransactions()) countNotMarked = m_transactionStats[acc.id()][(int)Split::State::NotReconciled]; if (KMyMoneySettings::showCountOfClearedTransactions() || KMyMoneySettings::showCountOfNotReconciledTransactions()) countCleared = m_transactionStats[acc.id()][(int)Split::State::Cleared]; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) countNotReconciled = countNotMarked + countCleared; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) { if (countNotMarked) countStr = QString("%1").arg(countNotMarked); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneySettings::showCountOfClearedTransactions()) { if (countCleared) countStr = QString("%1").arg(countCleared); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneySettings::showCountOfNotReconciledTransactions()) { if (countNotReconciled) countStr = QString("%1").arg(countNotReconciled); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneySettings::showDateOfLastReconciliation()) { const auto lastReconciliationDate = acc.lastReconciliationDate().toString(Qt::SystemLocaleShortDate).replace(QChar(' '), " "); tmp += QString("%1").arg(lastReconciliationDate); } //show account balance tmp += QString("%1").arg(showColoredAmount(amount, value.isNegative())); //show minimum balance column if requested if (showMinBal) { //if it is an investment, show minimum balance empty if (acc.accountType() == Account::Type::Investment) { tmp += QString(" "); } else { //show minimum balance entry tmp += QString("%1").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); } } // qDebug("accountEntry = '%s'", tmp.toLatin1()); m_html += tmp; } void showAccountEntry(const MyMoneyAccount& acc) { const auto file = MyMoneyFile::instance(); MyMoneyMoney value; bool showLimit = KMyMoneySettings::showLimitInfo(); if (acc.accountType() == Account::Type::Investment) { //investment accounts show the balances of all its subaccounts value = investmentBalance(acc); //investment accounts have no minimum balance showAccountEntry(acc, value, MyMoneyMoney(), showLimit); } else { //get balance for normal accounts value = file->balance(acc.id(), QDate::currentDate()); if (acc.currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price(acc.tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); auto baseValue = value * curRate; baseValue = baseValue.convert(file->baseCurrency().smallestAccountFraction()); m_total += baseValue; } else { m_total += value; } //if credit card or checkings account, show maximum credit if (acc.accountType() == Account::Type::CreditCard || acc.accountType() == Account::Type::Checkings) { QString maximumCredit = acc.value("maxCreditAbsolute"); if (maximumCredit.isEmpty()) { maximumCredit = acc.value("minBalanceAbsolute"); } MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); showAccountEntry(acc, value, value - maxCredit, showLimit); } else { //otherwise use minimum balance QString minimumBalance = acc.value("minBalanceAbsolute"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); showAccountEntry(acc, value, value - minBalance, showLimit); } } } /** * @param acc the investment account * @return the balance in the currency of the investment account */ MyMoneyMoney investmentBalance(const MyMoneyAccount& acc) { auto file = MyMoneyFile::instance(); auto value = file->balance(acc.id(), QDate::currentDate()); foreach (const auto accountID, acc.accountList()) { auto stock = file->account(accountID); if (!stock.isClosed()) { try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id(), QDate::currentDate()); MyMoneySecurity security = file->security(stock.currencyId()); const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); val = (balance * price.rate(security.tradingCurrency())).convertPrecision(security.pricePrecision()); // adjust value of security to the currency of the account MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); val = val.convert(acc.fraction()); value += val; } catch (const MyMoneyException &e) { qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what()))); } } } return value; } /** * Print text in the color set for negative numbers, if @p amount is negative * abd @p isNegative is true */ QString showColoredAmount(const QString& amount, bool isNegative) { if (isNegative) { //if negative, get the settings for negative numbers return QString("%2").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name(), amount); } //if positive, return the same string return amount; } /** * Run the forecast */ void doForecast() { //clear m_accountList because forecast is about to changed m_accountList.clear(); //reinitialize the object m_forecast = KMyMoneyUtils::forecast(); //If forecastDays lower than accountsCycle, adjust to the first cycle if (m_forecast.accountsCycle() > m_forecast.forecastDays()) m_forecast.setForecastDays(m_forecast.accountsCycle()); //Get all accounts of the right type to calculate forecast m_forecast.doForecast(); } /** * Calculate the forecast balance after a payment has been made */ MyMoneyMoney forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) { //if paymentDate before or equal to currentDate set it to current date plus 1 //so we get to accumulate forecast balance correctly if (paymentDate <= QDate::currentDate()) paymentDate = QDate::currentDate().addDays(1); //check if the account is already there if (m_accountList.find(acc.id()) == m_accountList.end() || m_accountList[acc.id()].find(paymentDate) == m_accountList[acc.id()].end()) { if (paymentDate == QDate::currentDate()) { m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate); } else { m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); } } m_accountList[acc.id()][paymentDate] = m_accountList[acc.id()][paymentDate] + payment; return m_accountList[acc.id()][paymentDate]; } void loadView() { Q_Q(KHomeView); m_view->setZoomFactor(KMyMoneySettings::zoomFactor()); QList list; if (MyMoneyFile::instance()->storage()) { MyMoneyFile::instance()->accountList(list); } if (list.isEmpty()) { m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://")); } else { // preload transaction statistics m_transactionStats = MyMoneyFile::instance()->countTransactionsWithSpecificReconciliationState(); // keep current location on page m_scrollBarPos = 0; #ifndef ENABLE_WEBENGINE m_scrollBarPos = m_view->page()->mainFrame()->scrollBarValue(Qt::Vertical); #endif //clear the forecast flag so it will be reloaded m_forecast.setForecastDone(false); const QString filename = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "html/kmymoney.css"); QString header = QString("\n\n").arg(QUrl::fromLocalFile(filename).url()); header += KMyMoneyUtils::variableCSS(); header += "\n"; QString footer = "\n"; m_html.clear(); m_html += header; m_html += QString("
%1
").arg(i18n("Your Financial Summary")); QStringList settings = KMyMoneySettings::listOfItems(); QStringList::ConstIterator it; for (it = settings.constBegin(); it != settings.constEnd(); ++it) { int option = (*it).toInt(); if (option > 0) { switch (option) { case 1: // payments showPayments(); break; case 2: // preferred accounts showAccounts(Preferred, i18n("Preferred Accounts")); break; case 3: // payment accounts // Check if preferred accounts are shown separately if (settings.contains("2")) { showAccounts(static_cast(Payment | Preferred), i18n("Payment Accounts")); } else { showAccounts(Payment, i18n("Payment Accounts")); } break; case 4: // favorite reports showFavoriteReports(); break; case 5: // forecast showForecast(); break; case 6: // net worth graph over all accounts showNetWorthGraph(); break; case 7: // forecast (history) - currently unused break; case 8: // assets and liabilities showAssetsLiabilities(); break; case 9: // budget showBudget(); break; case 10: // cash flow summary showCashFlowSummary(); break; } m_html += "
 
\n"; } } m_html += "
"; m_html += link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend(); m_html += "
"; m_html += "
"; m_html += footer; m_view->setHtml(m_html, QUrl("file://")); #ifndef ENABLE_WEBENGINE if (m_scrollBarPos) { QMetaObject::invokeMethod(q, "slotAdjustScrollPos", Qt::QueuedConnection); } #endif } } void showNetWorthGraph() { Q_Q(KHomeView); // Adjust the size QSize netWorthGraphSize = q->size(); netWorthGraphSize -= QSize(80, 30); m_netWorthGraphLastValidSize = netWorthGraphSize; m_html += QString("
%1
\n
 
\n").arg(i18n("Net Worth Forecast")); m_html += QString(""); m_html += QString(""); if (const auto reportsPlugin = pPlugins.data.value(QStringLiteral("reportsview"), nullptr)) { const auto variantReport = reportsPlugin->requestData(QString(), eWidgetPlugin::WidgetType::NetWorthForecast); if (!variantReport.isNull()) { auto report = variantReport.value(); report->resize(m_netWorthGraphLastValidSize); m_html += QString("").arg(QPixmapToDataUri(report->grab())); delete report; } } else { m_html += QString("").arg(i18n("Enable reports plugin to see this chart.")); } m_html += QString(""); m_html += QString("
\"Networth\"
%1
"); } void showPayments() { MyMoneyFile* file = MyMoneyFile::instance(); QList overdues; QList schedule; int i = 0; //if forecast has not been executed yet, do it. if (!m_forecast.isForecastDone()) doForecast(); schedule = file->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate::currentDate(), QDate::currentDate().addMonths(1), false); overdues = file->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), true); if (schedule.empty() && overdues.empty()) return; // HACK // Remove the finished schedules QList::Iterator d_it; //regular schedules d_it = schedule.begin(); while (d_it != schedule.end()) { if ((*d_it).isFinished()) { d_it = schedule.erase(d_it); continue; } ++d_it; } //overdue schedules d_it = overdues.begin(); while (d_it != overdues.end()) { if ((*d_it).isFinished()) { d_it = overdues.erase(d_it); continue; } ++d_it; } m_html += "
"; m_html += QString("
%1
\n").arg(i18n("Payments")); if (!overdues.isEmpty()) { m_html += "
 
\n"; qSort(overdues); QList::Iterator it; QList::Iterator it_f; m_html += ""; m_html += QString("\n").arg(showColoredAmount(i18n("Overdue payments"), true)); m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; for (it = overdues.begin(); it != overdues.end(); ++it) { // determine number of overdue payments int cnt = (*it).transactionsRemainingUntil(QDate::currentDate().addDays(-1)); m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it, cnt); m_html += ""; } m_html += "
%1
"; m_html += i18n("Date"); m_html += ""; m_html += i18n("Schedule"); m_html += ""; m_html += i18n("Account"); m_html += ""; m_html += i18n("Amount"); m_html += ""; m_html += i18n("Balance after"); m_html += "
"; } if (!schedule.isEmpty()) { qSort(schedule); // Extract todays payments if any QList todays; QList::Iterator t_it; for (t_it = schedule.begin(); t_it != schedule.end();) { if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { todays.append(*t_it); (*t_it).setNextDueDate((*t_it).nextPayment(QDate::currentDate())); // if adjustedNextDueDate is still currentDate then remove it from // scheduled payments if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { t_it = schedule.erase(t_it); continue; } } ++t_it; } if (todays.count() > 0) { m_html += "
 
\n"; m_html += ""; m_html += QString("\n").arg(i18n("Today's due payments")); m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; for (t_it = todays.begin(); t_it != todays.end(); ++t_it) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*t_it); m_html += ""; } m_html += "
%1
"; m_html += i18n("Date"); m_html += ""; m_html += i18n("Schedule"); m_html += ""; m_html += i18n("Account"); m_html += ""; m_html += i18n("Amount"); m_html += ""; m_html += i18n("Balance after"); m_html += "
"; } if (!schedule.isEmpty()) { m_html += "
 
\n"; QList::Iterator it; m_html += ""; m_html += QString("\n").arg(i18n("Future payments")); m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; // show all or the first 6 entries int cnt; cnt = (m_showAllSchedules) ? -1 : 6; bool needMoreLess = m_showAllSchedules; QDate lastDate = QDate::currentDate().addMonths(1); qSort(schedule); do { it = schedule.begin(); if (it == schedule.end()) break; // if the next due date is invalid (schedule is finished) // we remove it from the list QDate nextDate = (*it).nextDueDate(); if (!nextDate.isValid()) { schedule.erase(it); continue; } if (nextDate > lastDate) break; if (cnt == 0) { needMoreLess = true; break; } // in case we've shown the current recurrence as overdue, // we don't show it here again, but keep the schedule // as it might show up later in the list again if (!(*it).isOverdue()) { if (cnt > 0) --cnt; m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it); m_html += ""; // for single occurrence we have reported everything so we // better get out of here. if ((*it).occurrence() == Schedule::Occurrence::Once) { schedule.erase(it); continue; } } // if nextPayment returns an invalid date, setNextDueDate will // just skip it, resulting in a loop // we check the resulting date and erase the schedule if invalid if (!((*it).nextPayment((*it).nextDueDate())).isValid()) { schedule.erase(it); continue; } (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); qSort(schedule); } while (1); if (needMoreLess) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); m_html += ""; m_html += ""; } m_html += "
%1
"; m_html += i18n("Date"); m_html += ""; m_html += i18n("Schedule"); m_html += ""; m_html += i18n("Account"); m_html += ""; m_html += i18n("Amount"); m_html += ""; m_html += i18n("Balance after"); m_html += "
"; if (m_showAllSchedules) { m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18nc("Less...", "Show fewer schedules on the list") + linkend(); } else { m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18nc("More...", "Show more schedules on the list") + linkend(); } m_html += "
"; } } m_html += "
"; } void showPaymentEntry(const MyMoneySchedule& sched, int cnt = 1) { QString tmp; MyMoneyFile* file = MyMoneyFile::instance(); try { MyMoneyAccount acc = sched.account(); if (!acc.id().isEmpty()) { MyMoneyTransaction t = sched.transaction(); // only show the entry, if it is still active if (!sched.isFinished()) { MyMoneySplit sp = t.splitByAccount(acc.id(), true); QString pathEnter = QPixmapToDataUri(Icons::get(Icon::KeyEnter).pixmap(QSize(16,16))); QString pathSkip = QPixmapToDataUri(Icons::get(Icon::SkipForward).pixmap(QSize(16, 16))); //show payment date tmp = QString("") + QLocale().toString(sched.adjustedNextDueDate(), QLocale::ShortFormat) + ""; if (!pathEnter.isEmpty()) tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("").arg(pathEnter) + linkend(); if (!pathSkip.isEmpty()) tmp += " " + link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("").arg(pathSkip) + linkend(); tmp += QString(" "); tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); //show quantity of payments overdue if any if (cnt > 1) tmp += i18np(" (%1 payment)", " (%1 payments)", cnt); //show account of the main split tmp += ""; tmp += QString(file->account(acc.id()).name()); //show amount of the schedule tmp += ""; const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId()); MyMoneyMoney payment = MyMoneyMoney(sp.value(t.commodity(), acc.currencyId()) * cnt); QString amount = MyMoneyUtils::formatMoney(payment, acc, currency); amount.replace(QChar(' '), " "); tmp += showColoredAmount(amount, payment.isNegative()); tmp += ""; //show balance after payments tmp += ""; QDate paymentDate = QDate(sched.adjustedNextDueDate()); MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate); QString balance = MyMoneyUtils::formatMoney(balanceAfter, acc, currency); balance.replace(QChar(' '), " "); tmp += showColoredAmount(balance, balanceAfter.isNegative()); tmp += ""; // qDebug("paymentEntry = '%s'", tmp.toLatin1()); m_html += tmp; } } } catch (const MyMoneyException &e) { qDebug("Unable to display schedule entry: %s", e.what()); } } void showAccounts(paymentTypeE type, const QString& header) { MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); QList accounts; auto showClosedAccounts = KMyMoneySettings::showAllAccounts(); // get list of all accounts file->accountList(accounts); for (QList::Iterator it = accounts.begin(); it != accounts.end();) { bool removeAccount = false; if (!(*it).isClosed() || showClosedAccounts) { switch ((*it).accountType()) { case Account::Type::Expense: case Account::Type::Income: // never show a category account // Note: This might be different in a future version when // the homepage also shows category based information removeAccount = true; break; // Asset and Liability accounts are only shown if they // have the preferred flag set case Account::Type::Asset: case Account::Type::Liability: case Account::Type::Investment: // if preferred accounts are requested, then keep in list if ((*it).value("PreferredAccount") != "Yes" || (type & Preferred) == 0) { removeAccount = true; } break; // Check payment accounts. If payment and preferred is selected, // then always show them. If only payment is selected, then // show only if preferred flag is not set. case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::CreditCard: switch (type & (Payment | Preferred)) { case Payment: if ((*it).value("PreferredAccount") == "Yes") removeAccount = true; break; case Preferred: if ((*it).value("PreferredAccount") != "Yes") removeAccount = true; break; case Payment | Preferred: break; default: removeAccount = true; break; } break; // filter all accounts that are not used on homepage views default: removeAccount = true; break; } } else if ((*it).isClosed() || (*it).isInvest()) { // don't show if closed or a stock account removeAccount = true; } if (removeAccount) it = accounts.erase(it); else ++it; } if (!accounts.isEmpty()) { // sort the accounts by name qStableSort(accounts.begin(), accounts.end(), accountNameLess); QString tmp; int i = 0; tmp = "
" + header + "
\n
 
\n"; m_html += tmp; m_html += ""; m_html += ""; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader = QPixmapToDataUri(Icons::get(Icon::Download).pixmap(QSize(16,16))); m_html += QString("").arg(pathStatusHeader); } m_html += ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += QString("").arg(i18nc("Header not marked", "!M")); if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += QString("").arg(i18nc("Header cleared", "C")); if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += QString("").arg(i18nc("Header not reconciled", "!R")); if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += QString("").arg(i18n("Last Reconciled")); m_html += ""; //only show limit info if user chose to do so if (KMyMoneySettings::showLimitInfo()) { m_html += ""; } m_html += ""; m_total = 0; QList::const_iterator it_m; for (it_m = accounts.constBegin(); it_m != accounts.constEnd(); ++it_m) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showAccountEntry(*it_m); m_html += ""; } m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); QString amount = m_total.formatMoney(file->baseCurrency().tradingSymbol(), prec); if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) m_html += ""; m_html += QString("").arg(i18n("Total")); if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += ""; m_html += QString("").arg(showColoredAmount(amount, m_total.isNegative())); m_html += "
"; m_html += i18n("Account"); m_html += "%1%1%1%1"; m_html += i18n("Current Balance"); m_html += ""; m_html += i18n("To Minimum Balance / Maximum Credit"); m_html += "
%1%1
"; } } void showFavoriteReports() { QList reports = MyMoneyFile::instance()->reportList(); if (!reports.isEmpty()) { bool firstTime = 1; int row = 0; QList::const_iterator it_report = reports.constBegin(); while (it_report != reports.constEnd()) { if ((*it_report).isFavorite()) { if (firstTime) { m_html += QString("
%1
\n
 
\n").arg(i18n("Favorite Reports")); m_html += ""; m_html += ""; firstTime = false; } m_html += QString("") .arg(row++ & 0x01 ? "even" : "odd") .arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id()))) .arg((*it_report).name()) .arg(linkend()) .arg((*it_report).comment()); } ++it_report; } if (!firstTime) m_html += "
"; m_html += i18n("Report"); m_html += ""; m_html += i18n("Comment"); m_html += "
%2%3%4%5
"; } } void showForecast() { MyMoneyFile* file = MyMoneyFile::instance(); QList accList; //if forecast has not been executed yet, do it. if (!m_forecast.isForecastDone()) doForecast(); accList = m_forecast.accountList(); if (accList.count() > 0) { // sort the accounts by name qStableSort(accList.begin(), accList.end(), accountNameLess); auto i = 0; auto colspan = 1; //get begin day auto beginDay = QDate::currentDate().daysTo(m_forecast.beginForecastDate()); //if begin day is today skip to next cycle if (beginDay == 0) beginDay = m_forecast.accountsCycle(); // Now output header m_html += QString("
%1
\n
 
\n").arg(i18ncp("Forecast days", "%1 Day Forecast", "%1 Day Forecast", m_forecast.forecastDays())); m_html += ""; m_html += ""; auto colWidth = 55 / (m_forecast.forecastDays() / m_forecast.accountsCycle()); for (i = 0; (i*m_forecast.accountsCycle() + beginDay) <= m_forecast.forecastDays(); ++i) { m_html += QString(""; colspan++; } m_html += ""; // Now output entries i = 0; QList::ConstIterator it_account; for (it_account = accList.constBegin(); it_account != accList.constEnd(); ++it_account) { //MyMoneyAccount acc = (*it_n); m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); m_html += QString(""; qint64 dropZero = -1; //account dropped below zero qint64 dropMinimum = -1; //account dropped below minimum balance QString minimumBalance = (*it_account).value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); MyMoneySecurity currency; MyMoneyMoney forecastBalance; //change account to deep currency if account is an investment if ((*it_account).isInvest()) { MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security((*it_account).currencyId()); } for (auto f = beginDay; f <= m_forecast.forecastDays(); f += m_forecast.accountsCycle()) { forecastBalance = m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); QString amount; amount = MyMoneyUtils::formatMoney(forecastBalance, *it_account, currency); amount.replace(QChar(' '), " "); m_html += QString("").arg(showColoredAmount(amount, forecastBalance.isNegative())); } m_html += ""; //Check if the account is going to be below zero or below the minimal balance in the forecast period //Check if the account is going to be below minimal balance dropMinimum = m_forecast.daysToMinimumBalance(*it_account); //Check if the account is going to be below zero in the future dropZero = m_forecast.daysToZeroBalance(*it_account); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if (dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch (dropMinimum) { case 0: msg = i18n("The balance of %1 is below the minimum balance %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; default: msg = i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", "The balance of %2 will drop below the minimum balance %3 in %1 days.", dropMinimum - 1, (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; } if (!msg.isEmpty()) { m_html += QString("").arg(msg).arg(colspan); } } // a drop below zero is always shown msg.clear(); switch (dropZero) { case -1: break; case 0: if ((*it_account).accountGroup() == Account::Type::Asset) { msg = i18n("The balance of %1 is below %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == Account::Type::Liability) { msg = i18n("The balance of %1 is above %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } break; default: if ((*it_account).accountGroup() == Account::Type::Asset) { msg = i18np("The balance of %2 will drop below %3 in %1 day.", "The balance of %2 will drop below %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == Account::Type::Liability) { msg = i18np("The balance of %2 will raise above %3 in %1 day.", "The balance of %2 will raise above %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } } if (!msg.isEmpty()) { m_html += QString("").arg(msg).arg(colspan); } } m_html += "
"; m_html += i18n("Account"); m_html += "").arg(colWidth); m_html += i18ncp("Forecast days", "%1 day", "%1 days", i * m_forecast.accountsCycle() + beginDay); m_html += "
") + link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "").arg(colWidth); m_html += QString("%1
%1
%1
"; } } QString link(const QString& view, const QString& query, const QString& _title = QString()) const { QString titlePart; QString title(_title); if (!title.isEmpty()) titlePart = QString(" title=\"%1\"").arg(title.replace(QLatin1Char(' '), " ")); return QString("").arg(view, query, titlePart); } QString linkend() const { return QStringLiteral(""); } void showAssetsLiabilities() { QList accounts; QList::ConstIterator it; QList assets; QList liabilities; MyMoneyMoney netAssets; MyMoneyMoney netLiabilities; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); int i = 0; // get list of all accounts file->accountList(accounts); for (it = accounts.constBegin(); it != accounts.constEnd();) { if (!(*it).isClosed()) { switch ((*it).accountType()) { // group all assets into one list but make sure that investment accounts always show up case Account::Type::Investment: assets << *it; break; case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::Asset: case Account::Type::AssetLoan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { assets << *it; } break; // group the liabilities into the other case Account::Type::CreditCard: case Account::Type::Liability: case Account::Type::Loan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { // Add it if we are not hiding zero balance liabilities, or the balance is not zero const auto value = MyMoneyFile::instance()->balance((*it).id(), QDate::currentDate()); if (!(KMyMoneySettings::hideZeroBalanceLiabilities() && value.isZero())) { liabilities << *it; } } break; default: break; } } ++it; } //only do it if we have assets or liabilities account if (assets.count() > 0 || liabilities.count() > 0) { // sort the accounts by name qStableSort(assets.begin(), assets.end(), accountNameLess); qStableSort(liabilities.begin(), liabilities.end(), accountNameLess); QString statusHeader; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader; - pathStatusHeader = QPixmapToDataUri(Icons::get(Icon::ViewOutbox).pixmap(QSize(16,16))); + pathStatusHeader = QPixmapToDataUri(Icons::get(Icon::OnlineJobOutbox).pixmap(QSize(16, 16))); statusHeader = QString("").arg(pathStatusHeader); } //print header m_html += "
" + i18n("Assets and Liabilities Summary") + "
\n
 
\n"; m_html += ""; //column titles m_html += ""; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { m_html += ""; } m_html += ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += ""; m_html += ""; //intermediate row to separate both columns m_html += ""; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { m_html += ""; } m_html += ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += ""; m_html += ""; QString placeHolder_Status, placeHolder_Counts; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) placeHolder_Status = ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) placeHolder_Counts = ""; if (KMyMoneySettings::showCountOfClearedTransactions()) placeHolder_Counts += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) placeHolder_Counts += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) placeHolder_Counts += ""; //get asset and liability accounts QList::const_iterator asset_it = assets.constBegin(); QList::const_iterator liabilities_it = liabilities.constBegin(); for (; asset_it != assets.constEnd() || liabilities_it != liabilities.constEnd();) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //write an asset account if we still have any if (asset_it != assets.constEnd()) { MyMoneyMoney value; //investment accounts consolidate the balance of its subaccounts if ((*asset_it).accountType() == Account::Type::Investment) { value = investmentBalance(*asset_it); } else { value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); } //calculate balance for foreign currency accounts if ((*asset_it).currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price((*asset_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); auto baseValue = value * curRate; baseValue = baseValue.convert(10000); netAssets += baseValue; } else { netAssets += value; } //show the account without minimum balance showAccountEntry(*asset_it, value, MyMoneyMoney(), false); ++asset_it; } else { //write a white space if we don't m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } //leave the intermediate column empty m_html += ""; //write a liability account if (liabilities_it != liabilities.constEnd()) { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*liabilities_it).currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price((*liabilities_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); auto baseValue = value * curRate; baseValue = baseValue.convert(10000); netLiabilities += baseValue; } else { netLiabilities += value; } //show the account without minimum balance showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); ++liabilities_it; } else { //leave the space empty if we run out of liabilities m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } m_html += ""; } //calculate net worth MyMoneyMoney netWorth = netAssets + netLiabilities; //format assets, liabilities and net worth QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountAssets.replace(QChar(' '), " "); amountLiabilities.replace(QChar(' '), " "); amountNetWorth.replace(QChar(' '), " "); m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //print total for assets m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Assets")).arg(placeHolder_Counts).arg(showColoredAmount(amountAssets, netAssets.isNegative())); //leave the intermediate column empty m_html += ""; //print total liabilities m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Liabilities")).arg(placeHolder_Counts).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative())); m_html += ""; //print net worth m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Net Worth")).arg(placeHolder_Counts).arg(showColoredAmount(amountNetWorth, netWorth.isNegative())); m_html += ""; m_html += "
"; m_html += statusHeader; m_html += ""; m_html += i18n("Asset Accounts"); m_html += "!MC!R" + i18n("Last Reconciled") + ""; m_html += i18n("Current Balance"); m_html += ""; m_html += statusHeader; m_html += ""; m_html += i18n("Liability Accounts"); m_html += "!MC!R" + i18n("Last Reconciled") + ""; m_html += i18n("Current Balance"); m_html += "
%2%4%2%4
%2%4
"; m_html += "
"; } } void showBudget() { QVariant variantReport; if (const auto reportsPlugin = pPlugins.data.value(QStringLiteral("reportsview"), nullptr)) { variantReport = reportsPlugin->requestData(QString(), eWidgetPlugin::WidgetType::Budget); } if (!variantReport.isNull()) { m_html.append(variantReport.toString()); } else { m_html += "
" + i18n("Budget") + "
\n
 
\n"; m_html += ""; m_html += QString(""); m_html += QString("").arg(i18n("Enable reports plugin to see this chart.")); m_html += QString(""); m_html += QString("
%1
"); } } void showCashFlowSummary() { MyMoneyTransactionFilter filter; MyMoneyMoney incomeValue; MyMoneyMoney expenseValue; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); //set start and end of month dates QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); //Add total income and expenses for this month //get transactions for current month filter.setDateFilter(startOfMonth, endOfMonth); filter.setReportAllSplits(false); QList transactions = file->transactionList(filter); //if no transaction then skip and print total in zero if (transactions.size() > 0) { //get all transactions for this month foreach (const auto transaction, transactions) { //get the splits for each transaction foreach (const auto split, transaction.splits()) { if (!split.shares().isZero()) { auto repSplitAcc = file->account(split.accountId()); //only add if it is an income or expense if (repSplitAcc.isIncomeExpense()) { MyMoneyMoney value; //convert to base currency if necessary if (repSplitAcc.currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price(repSplitAcc.tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); value = (split.shares() * MyMoneyMoney::MINUS_ONE) * curRate; value = value.convert(10000); } else { value = (split.shares() * MyMoneyMoney::MINUS_ONE); } //store depending on account type if (repSplitAcc.accountType() == Account::Type::Income) { incomeValue += value; } else { expenseValue += value; } } } } } } //format income and expenses QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountIncome.replace(QChar(' '), " "); amountExpense.replace(QChar(' '), " "); //calculate schedules //Add all schedules for this month MyMoneyMoney scheduledIncome; MyMoneyMoney scheduledExpense; MyMoneyMoney scheduledLiquidTransfer; MyMoneyMoney scheduledOtherTransfer; //get overdues and schedules until the end of this month QList schedule = file->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), endOfMonth, false); //Remove the finished schedules QList::Iterator finished_it; for (finished_it = schedule.begin(); finished_it != schedule.end();) { if ((*finished_it).isFinished()) { finished_it = schedule.erase(finished_it); continue; } ++finished_it; } //add income and expenses QList::Iterator sched_it; for (sched_it = schedule.begin(); sched_it != schedule.end();) { QDate nextDate = (*sched_it).nextDueDate(); int cnt = 0; while (nextDate.isValid() && nextDate <= endOfMonth) { ++cnt; nextDate = (*sched_it).nextPayment(nextDate); // for single occurrence nextDate will not change, so we // better get out of here. if ((*sched_it).occurrence() == Schedule::Occurrence::Once) break; } MyMoneyAccount acc = (*sched_it).account(); if (!acc.id().isEmpty()) { MyMoneyTransaction transaction = (*sched_it).transaction(); // only show the entry, if it is still active MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); // take care of the autoCalc stuff if ((*sched_it).type() == Schedule::Type::LoanPayment) { nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); //make sure we have all 'starting balances' so that the autocalc works QMap balanceMap; foreach (const auto split, transaction.splits()) { acc = file->account(split.accountId()); // collect all overdues on the first day QDate schedDate = nextDate; if (QDate::currentDate() >= nextDate) schedDate = QDate::currentDate().addDays(1); balanceMap[acc.id()] += file->balance(acc.id(), QDate::currentDate()); } KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); } //go through the splits and assign to liquid or other transfers const QList splits = transaction.splits(); QList::const_iterator split_it; for (split_it = splits.constBegin(); split_it != splits.constEnd(); ++split_it) { if ((*split_it).accountId() != acc.id()) { auto repSplitAcc = file->account((*split_it).accountId()); //get the shares and multiply by the quantity of occurrences in the period MyMoneyMoney value = (*split_it).shares() * cnt; //convert to foreign currency if needed if (repSplitAcc.currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price(repSplitAcc.tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); value = value * curRate; value = value.convert(10000); } if ((repSplitAcc.isLiquidLiability() || repSplitAcc.isLiquidAsset()) && acc.accountGroup() != repSplitAcc.accountGroup()) { scheduledLiquidTransfer += value; } else if (repSplitAcc.isAssetLiability() && !repSplitAcc.isLiquidLiability() && !repSplitAcc.isLiquidAsset()) { scheduledOtherTransfer += value; } else if (repSplitAcc.isIncomeExpense()) { //income and expenses are stored as negative values if (repSplitAcc.accountType() == Account::Type::Income) scheduledIncome -= value; if (repSplitAcc.accountType() == Account::Type::Expense) scheduledExpense -= value; } } } } ++sched_it; } //format the currency strings QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountScheduledIncome.replace(QChar(' '), " "); amountScheduledExpense.replace(QChar(' '), " "); amountScheduledLiquidTransfer.replace(QChar(' '), " "); amountScheduledOtherTransfer.replace(QChar(' '), " "); //get liquid assets and liabilities QList accounts; QList::const_iterator account_it; MyMoneyMoney liquidAssets; MyMoneyMoney liquidLiabilities; // get list of all accounts file->accountList(accounts); for (account_it = accounts.constBegin(); account_it != accounts.constEnd();) { if (!(*account_it).isClosed()) { switch ((*account_it).accountType()) { //group all assets into one list case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: { MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance for foreign currency accounts if ((*account_it).currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price((*account_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); auto baseValue = value * curRate; liquidAssets += baseValue; liquidAssets = liquidAssets.convert(10000); } else { liquidAssets += value; } break; } //group the liabilities into the other case Account::Type::CreditCard: { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*account_it).currencyId() != file->baseCurrency().id()) { const auto curPrice = file->price((*account_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); const auto curRate = curPrice.rate(file->baseCurrency().id()); auto baseValue = value * curRate; liquidLiabilities += baseValue; liquidLiabilities = liquidLiabilities.convert(10000); } else { liquidLiabilities += value; } break; } default: break; } } ++account_it; } //calculate net worth MyMoneyMoney liquidWorth = liquidAssets + liquidLiabilities; //format assets, liabilities and net worth QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountLiquidAssets.replace(QChar(' '), " "); amountLiquidLiabilities.replace(QChar(' '), " "); amountLiquidWorth.replace(QChar(' '), " "); //show the summary m_html += "
" + i18n("Cash Flow Summary") + "
\n
 
\n"; //print header m_html += ""; //income and expense title m_html += ""; m_html += ""; //column titles m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; //add row with banding m_html += QString(""); //print current income m_html += QString("").arg(showColoredAmount(amountIncome, incomeValue.isNegative())); //print the scheduled income m_html += QString("").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative())); //print current expenses m_html += QString("").arg(showColoredAmount(amountExpense, expenseValue.isNegative())); //print the scheduled expenses m_html += QString("").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Income and Expenses of Current Month"); m_html += "
"; m_html += i18n("Income"); m_html += ""; m_html += i18n("Scheduled Income"); m_html += ""; m_html += i18n("Expenses"); m_html += ""; m_html += i18n("Scheduled Expenses"); m_html += "
%2%2%2%2
"; //print header of assets and liabilities m_html += "
 
\n"; m_html += ""; //assets and liabilities title m_html += ""; m_html += ""; //column titles m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; //add row with banding m_html += QString(""); //print current liquid assets m_html += QString("").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative())); //print the scheduled transfers m_html += QString("").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative())); //print current liabilities m_html += QString("").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative())); //print the scheduled transfers m_html += QString("").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Liquid Assets and Liabilities"); m_html += "
"; m_html += i18n("Liquid Assets"); m_html += ""; m_html += i18n("Transfers to Liquid Liabilities"); m_html += ""; m_html += i18n("Liquid Liabilities"); m_html += ""; m_html += i18n("Other Transfers"); m_html += "
%2%2%2%2
"; //final conclusion MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountProfit.replace(QChar(' '), " "); amountExpectedAsset.replace(QChar(' '), " "); amountExpectedLiabilities.replace(QChar(' '), " "); //print header of cash flow status m_html += "
 
\n"; m_html += ""; //income and expense title m_html += ""; m_html += ""; //column titles m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; //add row with banding m_html += QString(""); m_html += ""; //print expected assets m_html += QString("").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative())); //print expected liabilities m_html += QString("").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative())); //print expected profit m_html += QString("").arg(showColoredAmount(amountProfit, profitValue.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Cash Flow Status"); m_html += "
 "; m_html += i18n("Expected Liquid Assets"); m_html += ""; m_html += i18n("Expected Liquid Liabilities"); m_html += ""; m_html += i18n("Expected Profit/Loss"); m_html += "
 %2%2%2
"; m_html += "
"; } KHomeView *q_ptr; /** * daily balances of an account */ typedef QMap dailyBalances; #ifdef ENABLE_WEBENGINE QWebEngineView *m_view; #else KWebView *m_view; #endif QString m_html; bool m_showAllSchedules; bool m_needLoad; MyMoneyForecast m_forecast; MyMoneyMoney m_total; /** * Hold the last valid size of the net worth graph * for the times when the needed size can't be computed. */ QSize m_netWorthGraphLastValidSize; QMap< QString, QVector > m_transactionStats; /** * daily forecast balance of accounts */ QMap m_accountList; int m_scrollBarPos; }; #endif diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index da1433e32..6fe636162 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,866 +1,866 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 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 #include "kmymoneyview.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #ifdef ENABLE_UNFINISHEDFEATURES #include "simpleledgerview.h" #endif #include "kmymoneysettings.h" #include "kmymoneytitlelabel.h" #include "kcurrencyeditdlg.h" #include "mymoneyexception.h" #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h" #include "kinstitutionsview.h" #include "kpayeesview.h" #include "ktagsview.h" #include "kscheduledview.h" #include "kgloballedgerview.h" #include "kinvestmentview.h" #include "models.h" #include "accountsmodel.h" #include "equitiesmodel.h" #include "securitiesmodel.h" #include "icons.h" #include "onlinejobadministration.h" #include "kmymoneyaccounttreeview.h" #include "accountsviewproxymodel.h" #include "mymoneymoney.h" #include "mymoneyprice.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneyaccount.h" #include "mymoneyinstitution.h" #include "mymoneytag.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyreport.h" #include "kmymoneyplugin.h" #include "mymoneyenums.h" #include "menuenums.h" using namespace Icons; using namespace eMyMoney; typedef void(KMyMoneyView::*KMyMoneyViewFunc)(); KMyMoneyView::KMyMoneyView() : KPageWidget(nullptr), m_header(0) { // this is a workaround for the bug in KPageWidget that causes the header to be shown // for a short while during page switch which causes a kind of bouncing of the page's // content and if the page's content is at it's minimum size then during a page switch // the main window's size is also increased to fit the header that is shown for a sort // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) // in a grid layout so if we find it there remove it for good to avoid the described issues QGridLayout* gridLayout = qobject_cast(layout()); if (gridLayout) { QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout if (headerItem && qobject_cast(headerItem->widget()) != NULL) { gridLayout->removeItem(headerItem); // after we remove the KPageWidget standard header replace it with our own title label m_header = new KMyMoneyTitleLabel(this); m_header->setObjectName("titleLabel"); m_header->setMinimumSize(QSize(100, 30)); m_header->setRightImageFile("pics/titlelabel_background.png"); m_header->setVisible(KMyMoneySettings::showTitleBar()); gridLayout->addWidget(m_header, 1, 1); } } // newStorage(); m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit viewBases[View::Home] = new KHomeView; viewBases[View::Institutions] = new KInstitutionsView; viewBases[View::Accounts] = new KAccountsView; viewBases[View::Schedules] = new KScheduledView; viewBases[View::Categories] = new KCategoriesView; viewBases[View::Tags] = new KTagsView; viewBases[View::Payees] = new KPayeesView; viewBases[View::Ledgers] = new KGlobalLedgerView; viewBases[View::Investments] = new KInvestmentView; #ifdef ENABLE_UNFINISHEDFEATURES viewBases[View::NewLedgers] = new SimpleLedgerView; #endif struct viewInfo { View id; QString name; Icon icon; }; const QVector viewsInfo { - {View::Home, i18n("Home"), Icon::ViewHome}, - {View::Institutions, i18n("Institutions"), Icon::ViewInstitutions}, - {View::Accounts, i18n("Accounts"), Icon::ViewAccounts}, - {View::Schedules, i18n("Scheduled\ntransactions"), Icon::ViewSchedules}, - {View::Categories, i18n("Categories"), Icon::ViewCategories}, - {View::Tags, i18n("Tags"), Icon::ViewTags}, - {View::Payees, i18n("Payees"), Icon::ViewPayees}, - {View::Ledgers, i18n("Ledgers"), Icon::ViewLedgers}, - {View::Investments, i18n("Investments"), Icon::ViewInvestments}, + {View::Home, i18n("Home"), Icon::Home}, + {View::Institutions, i18n("Institutions"), Icon::Institutions}, + {View::Accounts, i18n("Accounts"), Icon::Accounts}, + {View::Schedules, i18n("Scheduled\ntransactions"), Icon::Schedule}, + {View::Categories, i18n("Categories"), Icon::FinancialCategories}, + {View::Tags, i18n("Tags"), Icon::Tags}, + {View::Payees, i18n("Payees"), Icon::Payees}, + {View::Ledgers, i18n("Ledgers"), Icon::Ledger}, + {View::Investments, i18n("Investments"), Icon::Investments}, #ifdef ENABLE_UNFINISHEDFEATURES {View::NewLedgers, i18n("New ledger"), Icon::DocumentProperties}, #endif }; for (const viewInfo& view : viewsInfo) { /* There is a bug in static int layoutText(QTextLayout *layout, int maxWidth) from kpageview_p.cpp from kwidgetsaddons. The method doesn't break strings that are too long. Following line workarounds this by using LINE SEPARATOR character which is accepted by QTextLayout::createLine().*/ viewFrames[view.id] = m_model->addPage(viewBases[view.id], QString(view.name).replace(QLatin1Char('\n'), QString::fromUtf8("\xe2\x80\xa8"))); viewFrames[view.id]->setIcon(Icons::get(view.icon)); connect(viewBases[view.id], &KMyMoneyViewBase::selectByObject, this, &KMyMoneyView::slotSelectByObject); connect(viewBases[view.id], &KMyMoneyViewBase::selectByVariant, this, &KMyMoneyView::slotSelectByVariant); connect(viewBases[view.id], &KMyMoneyViewBase::customActionRequested, this, &KMyMoneyView::slotCustomActionRequested); } connect(Models::instance()->accountsModel(), &AccountsModel::netWorthChanged, this, &KMyMoneyView::slotSelectByVariant); connect(Models::instance()->accountsModel(), &AccountsModel::profitChanged, this, &KMyMoneyView::slotSelectByVariant); connect(Models::instance()->institutionsModel(), &AccountsModel::netWorthChanged, this, &KMyMoneyView::slotSelectByVariant); connect(Models::instance()->institutionsModel(), &AccountsModel::profitChanged, this, &KMyMoneyView::slotSelectByVariant); //set the model setModel(m_model); setCurrentPage(viewFrames[View::Home]); connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); updateViewType(); } KMyMoneyView::~KMyMoneyView() { } void KMyMoneyView::slotFileOpened() { if (viewBases.contains(View::OnlineJobOutbox)) viewBases[View::OnlineJobOutbox]->executeCustomAction(eView::Action::InitializeAfterFileOpen); if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->executeCustomAction(eView::Action::InitializeAfterFileOpen); #ifdef ENABLE_UNFINISHEDFEATURES static_cast(viewBases[View::NewLedgers])->openFavoriteLedgers(); #endif // delay the switchToDefaultView call until the event loop is running QMetaObject::invokeMethod(this, "switchToDefaultView", Qt::QueuedConnection); slotObjectSelected(MyMoneyAccount()); // in order to enable update all accounts on file reload } void KMyMoneyView::slotFileClosed() { slotShowHomePage(); if (viewBases.contains(View::Home)) viewBases[View::Home]->executeCustomAction(eView::Action::CleanupBeforeFileClose); if (viewBases.contains(View::Reports)) viewBases[View::Reports]->executeCustomAction(eView::Action::CleanupBeforeFileClose); if (viewBases.contains(View::OnlineJobOutbox)) viewBases[View::OnlineJobOutbox]->executeCustomAction(eView::Action::CleanupBeforeFileClose); if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->executeCustomAction(eView::Action::CleanupBeforeFileClose); #ifdef ENABLE_UNFINISHEDFEATURES static_cast(viewBases[View::NewLedgers])->closeLedgers(); #endif pActions[eMenu::Action::Print]->setEnabled(false); pActions[eMenu::Action::AccountCreditTransfer]->setEnabled(false); pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(false); } void KMyMoneyView::slotShowHomePage() { showPageAndFocus(View::Home); } void KMyMoneyView::slotShowInstitutionsPage() { showPageAndFocus(View::Institutions); } void KMyMoneyView::slotShowAccountsPage() { showPageAndFocus(View::Accounts); } void KMyMoneyView::slotShowSchedulesPage() { showPageAndFocus(View::Schedules); } void KMyMoneyView::slotShowCategoriesPage() { showPageAndFocus(View::Categories); } void KMyMoneyView::slotShowTagsPage() { showPageAndFocus(View::Tags); } void KMyMoneyView::slotShowPayeesPage() { showPageAndFocus(View::Payees); } void KMyMoneyView::slotShowLedgersPage() { showPageAndFocus(View::Ledgers); } void KMyMoneyView::slotShowInvestmentsPage() { showPageAndFocus(View::Investments); } void KMyMoneyView::slotShowReportsPage() { showPageAndFocus(View::Reports); } void KMyMoneyView::slotShowBudgetPage() { showPageAndFocus(View::Budget); } void KMyMoneyView::slotShowForecastPage() { showPageAndFocus(View::Forecast); } void KMyMoneyView::slotShowOutboxPage() { showPageAndFocus(View::OnlineJobOutbox); } void KMyMoneyView::showTitleBar(bool show) { if (m_header) m_header->setVisible(show); } void KMyMoneyView::updateViewType() { // set the face type KPageView::FaceType faceType = KPageView::List; switch (KMyMoneySettings::viewType()) { case 0: faceType = KPageView::List; break; case 1: faceType = KPageView::Tree; break; case 2: faceType = KPageView::Tabbed; break; } if (faceType != KMyMoneyView::faceType()) { setFaceType(faceType); if (faceType == KPageView::Tree) { QList views = findChildren(); foreach (QTreeView * view, views) { if (view && (view->parent() == this)) { view->setRootIsDecorated(false); break; } } } } } void KMyMoneyView::slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show) { QVector proxyModels { static_cast(viewBases[View::Institutions])->getProxyModel(), static_cast(viewBases[View::Accounts])->getProxyModel(), static_cast(viewBases[View::Categories])->getProxyModel() }; if (viewBases.contains(View::Budget)) proxyModels.append(static_cast(viewBases[View::Budget])->getProxyModel()); for (auto i = proxyModels.count() - 1; i >= 0; --i) { // weed out unloaded views if (!proxyModels.at(i)) proxyModels.removeAt(i); } QString question; if (show) question = i18n("Do you want to show %1 column on every loaded view?", AccountsModel::getHeaderName(column)); else question = i18n("Do you want to hide %1 column on every loaded view?", AccountsModel::getHeaderName(column)); if (proxyModels.count() == 1 || // no need to ask what to do with other views because they aren't loaded KMessageBox::questionYesNo(this, question, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("ShowColumnOnEveryView")) == KMessageBox::Yes) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); foreach(AccountsViewProxyModel *proxyModel, proxyModels) { if (!proxyModel) continue; proxyModel->setColumnVisibility(column, show); proxyModel->invalidate(); } } else if(show) { // in case we need to show it, we have to make sure to set the visibility // in the base model as well. Otherwise, we don't see the column through the proxy model Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); } } void KMyMoneyView::setOnlinePlugins(QMap& plugins) { if (viewBases.contains(View::Accounts)) viewBases[View::Accounts]->slotSelectByVariant(QVariantList {QVariant::fromValue(static_cast(&plugins))}, eView::Intent::SetOnlinePlugins); if (viewBases.contains(View::OnlineJobOutbox)) viewBases[View::OnlineJobOutbox]->slotSelectByVariant(QVariantList {QVariant::fromValue(static_cast(&plugins))}, eView::Intent::SetOnlinePlugins); } eDialogs::ScheduleResultCode KMyMoneyView::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { return static_cast(viewBases[View::Schedules])->enterSchedule(schedule, autoEnter, extendedKeys); } void KMyMoneyView::addView(KMyMoneyViewBase* view, const QString& name, View idView) { auto isViewInserted = false; for (auto i = (int)idView; i < (int)View::None; ++i) { if (viewFrames.contains((View)i)) { viewFrames[idView] = m_model->insertPage(viewFrames[(View)i],view, name); isViewInserted = true; break; } } if (!isViewInserted) viewFrames[idView] = m_model->addPage(view, name); viewBases[idView] = view; connect(viewBases[idView], &KMyMoneyViewBase::selectByObject, this, &KMyMoneyView::slotSelectByObject); connect(viewBases[idView], &KMyMoneyViewBase::selectByVariant, this, &KMyMoneyView::slotSelectByVariant); connect(viewBases[idView], &KMyMoneyViewBase::customActionRequested, this, &KMyMoneyView::slotCustomActionRequested); - auto icon = Icon::ViewForecast; + auto icon = Icon::Forecast; switch (idView) { case View::Reports: - icon = Icon::ViewReports; + icon = Icon::Reports; break; case View::Budget: - icon = Icon::ViewBudgets; + icon = Icon::Budget; break; case View::Forecast: - icon = Icon::ViewForecast; + icon = Icon::Forecast; break; case View::OnlineJobOutbox: - icon = Icon::ViewOutbox; + icon = Icon::OnlineJobOutbox; break; default: break; } viewFrames[idView]->setIcon(Icons::get(icon)); } void KMyMoneyView::removeView(View idView) { if (!viewBases.contains(idView)) return; disconnect(viewBases[idView], &KMyMoneyViewBase::selectByObject, this, &KMyMoneyView::slotSelectByObject); disconnect(viewBases[idView], &KMyMoneyViewBase::selectByVariant, this, &KMyMoneyView::slotSelectByVariant); disconnect(viewBases[idView], &KMyMoneyViewBase::customActionRequested, this, &KMyMoneyView::slotCustomActionRequested); m_model->removePage(viewFrames[idView]); viewFrames.remove(idView); viewBases.remove(idView); } QHash KMyMoneyView::actionsToBeConnected() { using namespace eMenu; // add fast switching of main views through Ctrl + NUM_X struct pageInfo { Action view; KMyMoneyViewFunc callback; QString text; QKeySequence shortcut = QKeySequence(); }; const QVector pageInfos { {Action::ShowHomeView, &KMyMoneyView::slotShowHomePage, i18n("Show home page"), Qt::CTRL + Qt::Key_1}, {Action::ShowInstitutionsView, &KMyMoneyView::slotShowInstitutionsPage, i18n("Show institutions page"), Qt::CTRL + Qt::Key_2}, {Action::ShowAccountsView, &KMyMoneyView::slotShowAccountsPage, i18n("Show accounts page"), Qt::CTRL + Qt::Key_3}, {Action::ShowSchedulesView, &KMyMoneyView::slotShowSchedulesPage, i18n("Show scheduled transactions page"), Qt::CTRL + Qt::Key_4}, {Action::ShowCategoriesView, &KMyMoneyView::slotShowCategoriesPage, i18n("Show categories page"), Qt::CTRL + Qt::Key_5}, {Action::ShowTagsView, &KMyMoneyView::slotShowTagsPage, i18n("Show tags page"), }, {Action::ShowPayeesView, &KMyMoneyView::slotShowPayeesPage, i18n("Show payees page"), Qt::CTRL + Qt::Key_6}, {Action::ShowLedgersView, &KMyMoneyView::slotShowLedgersPage, i18n("Show ledgers page"), Qt::CTRL + Qt::Key_7}, {Action::ShowInvestmentsView, &KMyMoneyView::slotShowInvestmentsPage, i18n("Show investments page"), Qt::CTRL + Qt::Key_8}, {Action::ShowReportsView, &KMyMoneyView::slotShowReportsPage, i18n("Show reports page"), Qt::CTRL + Qt::Key_9}, {Action::ShowBudgetView, &KMyMoneyView::slotShowBudgetPage, i18n("Show budget page"), }, {Action::ShowForecastView, &KMyMoneyView::slotShowForecastPage, i18n("Show forecast page"), }, {Action::ShowOnlineJobOutboxView, &KMyMoneyView::slotShowOutboxPage, i18n("Show outbox page") } }; QHash lutActions; auto pageCount = 0; for (const pageInfo& info : pageInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(QString::fromLatin1("ShowPage%1").arg(QString::number(pageCount++))); a->setText(info.text); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.view, a); // store QAction's pointer for later processing if (!info.shortcut.isEmpty()) a->setShortcut(info.shortcut); } return lutActions; } bool KMyMoneyView::showPageHeader() const { return false; } void KMyMoneyView::showPageAndFocus(View idView) { if (viewFrames.contains(idView)) { showPage(idView); viewBases[idView]->executeCustomAction(eView::Action::SetDefaultFocus); } } void KMyMoneyView::showPage(View idView) { if (!viewFrames.contains(idView) || currentPage() == viewFrames[idView]) return; resetViewSelection(); setCurrentPage(viewFrames[idView]); } bool KMyMoneyView::canPrint() { return (MyMoneyFile::instance()->storageAttached() && ((viewFrames.contains(View::Reports) && viewFrames[View::Reports] == currentPage()) || (viewFrames.contains(View::Home) && viewFrames[View::Home] == currentPage())) ); } void KMyMoneyView::enableViewsIfFileOpen(bool fileOpen) { // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this Q_ASSERT_X(((int)(View::Home)+1) == (int)View::Institutions, "viewenums.h", "View::Home must be first and View::Institutions second entry"); for (auto i = (int)View::Institutions; i < (int)View::None; ++i) if (viewFrames.contains(View(i))) if (viewFrames[View(i)]->isEnabled() != fileOpen) viewFrames[View(i)]->setEnabled(fileOpen); emit viewStateChanged(fileOpen); } void KMyMoneyView::switchToDefaultView() { const auto idView = KMyMoneySettings::startLastViewSelected() ? static_cast(KMyMoneySettings::lastViewSelected()) : View::Home; // if we currently see a different page, then select the right one if (viewFrames.contains(idView) && viewFrames[idView] != currentPage()) showPage(idView); } void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) { showPage(View::Payees); static_cast(viewBases[View::Payees])->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(View::Tags); static_cast(viewBases[View::Tags])->slotSelectTagAndTransaction(tag, account, transaction); } void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) { Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); static_cast(viewBases[View::Ledgers])->slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); } void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) { if (viewFrames[View::Accounts] != currentPage()) showPage(View::Accounts); viewBases[View::Accounts]->show(); } void KMyMoneyView::slotRefreshViews() { showTitleBar(KMyMoneySettings::showTitleBar()); for (auto i = (int)View::Home; i < (int)View::None; ++i) if (viewBases.contains(View(i))) viewBases[View(i)]->executeCustomAction(eView::Action::Refresh); viewBases[View::Payees]->executeCustomAction(eView::Action::ClosePayeeIdentifierSource); } void KMyMoneyView::slotShowTransactionDetail(bool detailed) { KMyMoneySettings::setShowRegisterDetailed(detailed); slotRefreshViews(); } void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous) { // set the current page's title in the header if (m_header) m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); const auto view = currentPage(); // remember the selected view if there is a real change if (previous.isValid()) { QHash::const_iterator it; for(it = viewFrames.cbegin(); it != viewFrames.cend(); ++it) { if ((*it) == view) { emit viewActivated(it.key()); break; } } } if (viewBases.contains(View::Ledgers) && view != viewFrames.value(View::Ledgers)) viewBases[View::Ledgers]->executeCustomAction(eView::Action::DisableViewDepenedendActions); pActions[eMenu::Action::Print]->setEnabled(canPrint()); pActions[eMenu::Action::AccountCreditTransfer]->setEnabled(onlineJobAdministration::instance()->canSendCreditTransfer()); } void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { MyMoneyFileTransaction ft; try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION_CSTRING("Transaction for schedule has less than 2 splits!"); } // now search the split that does not have an account reference // and set it up to be the one of the account we just added // to the account pool. Note: the schedule code used to leave // this always the first split, but the loan code leaves it as // the second one. So I thought, searching is a good alternative .... foreach (const auto split, t.splits()) { if (split.accountId().isEmpty()) { MyMoneySplit s = split; s.setAccountId(newAccount.id()); t.modifySplit(s); break; } } newSchedule.setTransaction(t); MyMoneyFile::instance()->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.isLoan()) { newAccount.setValue("schedule", newSchedule.id()); MyMoneyFile::instance()->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add schedule: %1", QString::fromLatin1(e.what()))); } } } void KMyMoneyView::slotPrintView() { if (viewFrames.contains(View::Reports) && viewFrames[View::Reports] == currentPage()) viewBases[View::Reports]->executeCustomAction(eView::Action::Print); else if (viewFrames.contains(View::Home) && viewFrames[View::Home] == currentPage()) viewBases[View::Home]->executeCustomAction(eView::Action::Print); } void KMyMoneyView::resetViewSelection() { if (!MyMoneyFile::instance()->storageAttached()) return; slotObjectSelected(MyMoneyAccount()); slotObjectSelected(MyMoneyInstitution()); slotObjectSelected(MyMoneySchedule()); slotObjectSelected(MyMoneyTag()); slotSelectByVariant(QVariantList {QVariant::fromValue(KMyMoneyRegister::SelectedTransactions())}, eView::Intent::SelectRegisterTransactions); } void KMyMoneyView::slotOpenObjectRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); // check if we can open this account // currently it make's sense for asset and liability accounts if (!MyMoneyFile::instance()->isStandardAccount(acc.id())) if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->slotSelectByVariant(QVariantList {QVariant(acc.id()), QVariant(QString()) }, eView::Intent::ShowTransaction ); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { // const auto& inst = static_cast(obj); if (viewBases.contains(View::Institutions)) viewBases[View::Institutions]->executeCustomAction(eView::Action::EditInstitution); } else if (typeid(obj) == typeid(MyMoneySchedule)) { if (viewBases.contains(View::Schedules)) viewBases[View::Schedules]->executeCustomAction(eView::Action::EditSchedule); } else if (typeid(obj) == typeid(MyMoneyReport)) { // const auto& rep = static_cast(obj); showPage(View::Reports); if (viewBases.contains(View::Reports)) viewBases[View::Reports]->slotSelectByObject(obj, eView::Intent::OpenObject); } } void KMyMoneyView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch (intent) { case eView::Intent::None: slotObjectSelected(obj); break; case eView::Intent::SynchronizeAccountInInvestmentView: if (viewBases.contains(View::Investments)) viewBases[View::Investments]->slotSelectByObject(obj, intent); break; case eView::Intent::SynchronizeAccountInLedgersView: if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->slotSelectByObject(obj, intent); break; case eView::Intent::OpenObject: slotOpenObjectRequested(obj); break; case eView::Intent::OpenContextMenu: slotContextMenuRequested(obj); break; case eView::Intent::StartEnteringOverdueScheduledTransactions: if (viewBases.contains(View::Schedules)) viewBases[View::Schedules]->slotSelectByObject(obj, intent); break; case eView::Intent::FinishEnteringOverdueScheduledTransactions: if (viewBases.contains(View::Ledgers)) { showPage(View::Ledgers); viewBases[View::Ledgers]->slotSelectByObject(obj, intent); } break; default: break; } } void KMyMoneyView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch(intent) { case eView::Intent::ReportProgress: if (variant.count() == 2) emit statusProgress(variant.at(0).toInt(), variant.at(1).toInt()); break; case eView::Intent::ReportProgressMessage: if (variant.count() == 1) emit statusMsg(variant.first().toString()); break; case eView::Intent::UpdateNetWorth: if (viewBases.contains(View::Accounts)) viewBases[View::Accounts]->slotSelectByVariant(variant, intent); if (viewBases.contains(View::Institutions)) viewBases[View::Institutions]->slotSelectByVariant(variant, intent); break; case eView::Intent::UpdateProfit: if (viewBases.contains(View::Categories)) viewBases[View::Categories]->slotSelectByVariant(variant, intent); break; case eView::Intent::ShowTransaction: if (viewBases.contains(View::Ledgers)) { showPage(View::Ledgers); viewBases[View::Ledgers]->slotSelectByVariant(variant, intent); } break; case eView::Intent::ToggleColumn: if (variant.count() == 2) slotAccountTreeViewChanged(variant.at(0).value(), variant.at(1).value()); break; case eView::Intent::ShowPayee: if (viewBases.contains(View::Payees)) { showPage(View::Payees); viewBases[View::Payees]->slotSelectByVariant(variant, intent); } break; case eView::Intent::SelectRegisterTransactions: if (variant.count() == 1) { emit transactionsSelected(variant.at(0).value()); // for plugins if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->slotSelectByVariant(variant, intent); } break; case eView::Intent::AccountReconciled: if (variant.count() == 5) emit accountReconciled(variant.at(0).value(), variant.at(1).value(), variant.at(2).value(), variant.at(3).value(), variant.at(4).value>>()); // for plugins break; default: break; } } void KMyMoneyView::slotCustomActionRequested(View view, eView::Action action) { switch (action) { case eView::Action::AboutToShow: resetViewSelection(); break; case eView::Action::SwitchView: showPage(view); break; case eView::Action::ShowBalanceChart: if (viewBases.contains(View::Reports)) viewBases[View::Reports]->executeCustomAction(action); break; default: break; } } void KMyMoneyView::slotObjectSelected(const MyMoneyObject& obj) { // carrying some slots over to views isn't easy for all slots... // ...so calls to kmymoney still must be here if (typeid(obj) == typeid(MyMoneyAccount)) { QVector views {View::Investments, View::Categories, View::Accounts, View::Ledgers, View::Reports, View::OnlineJobOutbox}; for (const auto view : views) if (viewBases.contains(view)) viewBases[view]->slotSelectByObject(obj, eView::Intent::UpdateActions); // for plugin only const auto& acc = static_cast(obj); if (!acc.isIncomeExpense() && !MyMoneyFile::instance()->isStandardAccount(acc.id())) emit accountSelected(acc); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { viewBases[View::Institutions]->slotSelectByObject(obj, eView::Intent::UpdateActions); } else if (typeid(obj) == typeid(MyMoneySchedule)) { viewBases[View::Schedules]->slotSelectByObject(obj, eView::Intent::UpdateActions); } } void KMyMoneyView::slotContextMenuRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); if (acc.isInvest()) viewBases[View::Investments]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); else if (acc.isIncomeExpense()) viewBases[View::Categories]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); else viewBases[View::Accounts]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { viewBases[View::Institutions]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); } else if (typeid(obj) == typeid(MyMoneySchedule)) { viewBases[View::Schedules]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); } } diff --git a/kmymoney/views/kscheduledview_p.h b/kmymoney/views/kscheduledview_p.h index d2d1377c5..e6065f137 100644 --- a/kmymoney/views/kscheduledview_p.h +++ b/kmymoney/views/kscheduledview_p.h @@ -1,687 +1,687 @@ /*************************************************************************** kscheduledview_p.h - description ------------------- begin : Sun Jan 27 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net 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. * * * ***************************************************************************/ #ifndef KSCHEDULEDVIEW_P_H #define KSCHEDULEDVIEW_P_H #include "kscheduledview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kscheduledview.h" #include "kmymoneyviewbase_p.h" #include "kenterscheduledlg.h" #include "kbalancewarning.h" #include "transactioneditor.h" #include "kconfirmmanualenterdlg.h" #include "kmymoneymvccombo.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "mymoneyexception.h" #include "kscheduletreeitem.h" #include "ktreewidgetfilterlinewidget.h" #include "icons/icons.h" #include "mymoneyutils.h" #include "mymoneyaccount.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" #include "mymoneyschedule.h" #include "mymoneyfile.h" #include "mymoneypayee.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" #include "menuenums.h" #include "dialogenums.h" using namespace Icons; class KScheduledViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KScheduledView) public: explicit KScheduledViewPrivate(KScheduledView *qq) : KMyMoneyViewBasePrivate(), q_ptr(qq), ui(new Ui::KScheduledView), m_kaccPopup(nullptr), m_openBills(true), m_openDeposits(true), m_openTransfers(true), m_openLoans(true), m_needLoad(true), m_searchWidget(nullptr), m_balanceWarning(nullptr) { } ~KScheduledViewPrivate() { if(!m_needLoad) writeConfig(); delete ui; } void init() { Q_Q(KScheduledView); m_needLoad = false; ui->setupUi(q); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KTreeWidgetFilterLineWidget(q, ui->m_scheduleTree); ui->vboxLayout->insertWidget(1, m_searchWidget); //enable custom context menu ui->m_scheduleTree->setContextMenuPolicy(Qt::CustomContextMenu); ui->m_scheduleTree->setSelectionMode(QAbstractItemView::SingleSelection); readConfig(); q->connect(ui->m_qbuttonNew, &QAbstractButton::clicked, pActions[eMenu::Action::NewSchedule], &QAction::trigger); // attach popup to 'Filter...' button m_kaccPopup = new QMenu(q); ui->m_accountsCombo->setMenu(m_kaccPopup); q->connect(m_kaccPopup, &QMenu::triggered, q, &KScheduledView::slotAccountActivated); KGuiItem::assign(ui->m_qbuttonNew, KMyMoneyUtils::scheduleNewGuiItem()); KGuiItem::assign(ui->m_accountsCombo, KMyMoneyUtils::accountsFilterGuiItem()); q->connect(ui->m_scheduleTree, &QWidget::customContextMenuRequested, q, &KScheduledView::customContextMenuRequested); q->connect(ui->m_scheduleTree, &QTreeWidget::itemSelectionChanged, q, &KScheduledView::slotSetSelectedItem); q->connect(ui->m_scheduleTree, &QTreeWidget::itemDoubleClicked, q, &KScheduledView::slotListItemExecuted); q->connect(ui->m_scheduleTree, &QTreeWidget::itemExpanded, q, &KScheduledView::slotListViewExpanded); q->connect(ui->m_scheduleTree, &QTreeWidget::itemCollapsed, q, &KScheduledView::slotListViewCollapsed); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KScheduledView::refresh); } static bool accountNameLessThan(const MyMoneyAccount& acc1, const MyMoneyAccount& acc2) { return acc1.name().toLower() < acc2.name().toLower(); } void refreshSchedule(bool full, const QString& schedId) { Q_Q(KScheduledView); ui->m_scheduleTree->header()->setFont(KMyMoneySettings::listHeaderFontEx()); ui->m_scheduleTree->clear(); try { if (full) { try { m_kaccPopup->clear(); MyMoneyFile* file = MyMoneyFile::instance(); // extract a list of all accounts under the asset group // and sort them by name QList list; QStringList accountList = file->asset().accountList(); accountList.append(file->liability().accountList()); file->accountList(list, accountList, true); qStableSort(list.begin(), list.end(), accountNameLessThan); QList::ConstIterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { if (!(*it_a).isClosed()) { QAction* act; act = m_kaccPopup->addAction((*it_a).name()); act->setCheckable(true); act->setChecked(true); } } } catch (const MyMoneyException &e) { KMessageBox::detailedError(q, i18n("Unable to load accounts: "), e.what()); } } MyMoneyFile *file = MyMoneyFile::instance(); QList scheduledItems = file->scheduleList(); if (scheduledItems.count() == 0) return; //disable sorting for performance ui->m_scheduleTree->setSortingEnabled(false); KScheduleTreeItem *itemBills = new KScheduleTreeItem(ui->m_scheduleTree); - itemBills->setIcon(0, Icons::get(Icon::ViewExpense)); + itemBills->setIcon(0, Icons::get(Icon::Expense)); itemBills->setText(0, i18n("Bills")); itemBills->setData(0, KScheduleTreeItem::OrderRole, QVariant("0")); itemBills->setFirstColumnSpanned(true); itemBills->setFlags(Qt::ItemIsEnabled); QFont bold = itemBills->font(0); bold.setBold(true); itemBills->setFont(0, bold); KScheduleTreeItem *itemDeposits = new KScheduleTreeItem(ui->m_scheduleTree); - itemDeposits->setIcon(0, Icons::get(Icon::ViewIncome)); + itemDeposits->setIcon(0, Icons::get(Icon::Income)); itemDeposits->setText(0, i18n("Deposits")); itemDeposits->setData(0, KScheduleTreeItem::OrderRole, QVariant("1")); itemDeposits->setFirstColumnSpanned(true); itemDeposits->setFlags(Qt::ItemIsEnabled); itemDeposits->setFont(0, bold); KScheduleTreeItem *itemLoans = new KScheduleTreeItem(ui->m_scheduleTree); - itemLoans->setIcon(0, Icons::get(Icon::ViewLoan)); + itemLoans->setIcon(0, Icons::get(Icon::Loan)); itemLoans->setText(0, i18n("Loans")); itemLoans->setData(0, KScheduleTreeItem::OrderRole, QVariant("2")); itemLoans->setFirstColumnSpanned(true); itemLoans->setFlags(Qt::ItemIsEnabled); itemLoans->setFont(0, bold); KScheduleTreeItem *itemTransfers = new KScheduleTreeItem(ui->m_scheduleTree); - itemTransfers->setIcon(0, Icons::get(Icon::ViewFinancialTransfer)); + itemTransfers->setIcon(0, Icons::get(Icon::Transaction)); itemTransfers->setText(0, i18n("Transfers")); itemTransfers->setData(0, KScheduleTreeItem::OrderRole, QVariant("3")); itemTransfers->setFirstColumnSpanned(true); itemTransfers->setFlags(Qt::ItemIsEnabled); itemTransfers->setFont(0, bold); QList::Iterator it; QTreeWidgetItem *openItem = 0; for (it = scheduledItems.begin(); it != scheduledItems.end(); ++it) { MyMoneySchedule schedData = (*it); QTreeWidgetItem* item = 0; bool bContinue = true; QStringList::iterator accIt; for (accIt = m_filterAccounts.begin(); accIt != m_filterAccounts.end(); ++accIt) { if (*accIt == schedData.account().id()) { bContinue = false; // Filter it out break; } } if (!bContinue) continue; QTreeWidgetItem* parent = 0; switch (schedData.type()) { case eMyMoney::Schedule::Type::Any: // Should we display an error ? // We just sort it as bill and fall through here case eMyMoney::Schedule::Type::Bill: parent = itemBills; break; case eMyMoney::Schedule::Type::Deposit: parent = itemDeposits; break; case eMyMoney::Schedule::Type::Transfer: parent = itemTransfers; break; case eMyMoney::Schedule::Type::LoanPayment: parent = itemLoans; break; } if (parent) { if (!KMyMoneySettings::hideFinishedSchedules() || !schedData.isFinished()) { item = addScheduleItem(parent, schedData); if (schedData.id() == schedId) openItem = item; } } } if (openItem) { ui->m_scheduleTree->setCurrentItem(openItem); } // using a timeout is the only way, I got the 'ensureTransactionVisible' // working when coming from hidden form to visible form. I assume, this // has something to do with the delayed update of the display somehow. q->resize(q->width(), q->height() - 1); QTimer::singleShot(10, q, SLOT(slotTimerDone())); ui->m_scheduleTree->update(); // force repaint in case the filter is set m_searchWidget->searchLine()->updateSearch(QString()); if (m_openBills) itemBills->setExpanded(true); if (m_openDeposits) itemDeposits->setExpanded(true); if (m_openTransfers) itemTransfers->setExpanded(true); if (m_openLoans) itemLoans->setExpanded(true); } catch (const MyMoneyException &e) { KMessageBox::error(q, e.what()); } for (int i = 0; i < ui->m_scheduleTree->columnCount(); ++i) { ui->m_scheduleTree->resizeColumnToContents(i); } //reenable sorting after loading items ui->m_scheduleTree->setSortingEnabled(true); } void readConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Last Use Settings"); m_openBills = grp.readEntry("KScheduleView_openBills", true); m_openDeposits = grp.readEntry("KScheduleView_openDeposits", true); m_openTransfers = grp.readEntry("KScheduleView_openTransfers", true); m_openLoans = grp.readEntry("KScheduleView_openLoans", true); QByteArray columns; columns = grp.readEntry("KScheduleView_treeState", columns); ui->m_scheduleTree->header()->restoreState(columns); ui->m_scheduleTree->header()->setFont(KMyMoneySettings::listHeaderFontEx()); } void writeConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Last Use Settings"); grp.writeEntry("KScheduleView_openBills", m_openBills); grp.writeEntry("KScheduleView_openDeposits", m_openDeposits); grp.writeEntry("KScheduleView_openTransfers", m_openTransfers); grp.writeEntry("KScheduleView_openLoans", m_openLoans); QByteArray columns = ui->m_scheduleTree->header()->saveState(); grp.writeEntry("KScheduleView_treeState", columns); config->sync(); } QTreeWidgetItem* addScheduleItem(QTreeWidgetItem* parent, MyMoneySchedule& schedule) { KScheduleTreeItem* item = new KScheduleTreeItem(parent); item->setData(0, Qt::UserRole, QVariant::fromValue(schedule)); item->setData(0, KScheduleTreeItem::OrderRole, schedule.name()); if (!schedule.isFinished()) { if (schedule.isOverdue()) { - item->setIcon(0, Icons::get(Icon::ViewUpcominEvents)); + item->setIcon(0, Icons::get(Icon::UpcomingEvents)); QBrush brush = item->foreground(0); brush.setColor(Qt::red); for (int i = 0; i < ui->m_scheduleTree->columnCount(); ++i) { item->setForeground(i, brush); } } else { - item->setIcon(0, Icons::get(Icon::ViewCalendarDay)); + item->setIcon(0, Icons::get(Icon::CalendarDay)); } } else { item->setIcon(0, Icons::get(Icon::DialogClose)); QBrush brush = item->foreground(0); brush.setColor(Qt::darkGreen); for (int i = 0; i < ui->m_scheduleTree->columnCount(); ++i) { item->setForeground(i, brush); } } try { MyMoneyTransaction transaction = schedule.transaction(); MyMoneySplit s1 = (transaction.splits().size() < 1) ? MyMoneySplit() : transaction.splits()[0]; MyMoneySplit s2 = (transaction.splits().size() < 2) ? MyMoneySplit() : transaction.splits()[1]; MyMoneySplit split; MyMoneyAccount acc; switch (schedule.type()) { case eMyMoney::Schedule::Type::Deposit: if (s1.value().isNegative()) split = s2; else split = s1; break; case eMyMoney::Schedule::Type::LoanPayment: { auto found = false; foreach (const auto it_split, transaction.splits()) { acc = MyMoneyFile::instance()->account(it_split.accountId()); if (acc.accountGroup() == eMyMoney::Account::Type::Asset || acc.accountGroup() == eMyMoney::Account::Type::Liability) { if (acc.accountType() != eMyMoney::Account::Type::Loan && acc.accountType() != eMyMoney::Account::Type::AssetLoan) { split = it_split; found = true; break; } } } if (!found) { qWarning("Split for payment account not found in %s:%d.", __FILE__, __LINE__); } break; } default: if (!s1.value().isPositive()) split = s1; else split = s2; break; } acc = MyMoneyFile::instance()->account(split.accountId()); item->setText(0, schedule.name()); MyMoneySecurity currency = MyMoneyFile::instance()->currency(acc.currencyId()); QString accName = acc.name(); if (!accName.isEmpty()) { item->setText(1, accName); } else { item->setText(1, "---"); } item->setData(1, KScheduleTreeItem::OrderRole, QVariant(accName)); QString payeeName; if (!s1.payeeId().isEmpty()) { payeeName = MyMoneyFile::instance()->payee(s1.payeeId()).name(); item->setText(2, payeeName); } else { item->setText(2, "---"); } item->setData(2, KScheduleTreeItem::OrderRole, QVariant(payeeName)); MyMoneyMoney amount = split.shares().abs(); item->setData(3, Qt::UserRole, QVariant::fromValue(amount)); if (!accName.isEmpty()) { item->setText(3, QString("%1 ").arg(MyMoneyUtils::formatMoney(amount, acc, currency))); } else { //there are some cases where the schedule does not have an account //in those cases the account will not have a fraction //use base currency instead item->setText(3, QString("%1 ").arg(MyMoneyUtils::formatMoney(amount, MyMoneyFile::instance()->baseCurrency()))); } item->setTextAlignment(3, Qt::AlignRight | Qt::AlignVCenter); item->setData(3, KScheduleTreeItem::OrderRole, QVariant::fromValue(amount)); // Do the real next payment like ms-money etc QDate nextDueDate; if (schedule.isFinished()) { item->setText(4, i18nc("Finished schedule", "Finished")); } else { nextDueDate = schedule.adjustedNextDueDate(); item->setText(4, QLocale().toString(schedule.adjustedNextDueDate(), QLocale::ShortFormat)); } item->setData(4, KScheduleTreeItem::OrderRole, QVariant(nextDueDate)); item->setText(5, i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1())); item->setText(6, KMyMoneyUtils::paymentMethodToString(schedule.paymentType())); } catch (const MyMoneyException &e) { item->setText(0, "Error:"); item->setText(1, e.what()); } return item; } /** * This method allows to enter the next scheduled transaction of * the given schedule @a s. In case @a extendedKeys is @a true, * the given schedule can also be skipped or ignored. * If @a autoEnter is @a true and the schedule does not contain * an estimated value, the schedule is entered as is without further * interaction with the user. In all other cases, the user will * be presented a dialog and allowed to adjust the values for this * instance of the schedule. * * The transaction will be created and entered into the ledger * and the schedule updated. */ eDialogs::ScheduleResultCode enterSchedule(MyMoneySchedule& schedule, bool autoEnter = false, bool extendedKeys = false) { Q_Q(KScheduledView); auto rc = eDialogs::ScheduleResultCode::Cancel; if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(q, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); return rc; } QWidget* parent = QApplication::activeWindow(); QPointer dlg = new KEnterScheduleDlg(parent, schedule); qDebug() << "parent widget" << (void*) parent; try { QDate origDueDate = schedule.nextDueDate(); dlg->showExtendedKeys(extendedKeys); QPointer transactionEditor = dlg->startEdit(); if (transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); MyMoneyTransaction torig, taccepted; transactionEditor->createTransaction(torig, dlg->transaction(), schedule.transaction().splits().isEmpty() ? MyMoneySplit() : schedule.transaction().splits().front(), true); // force actions to be available no matter what (will be updated according to the state during // slotTransactionsEnter or slotTransactionsCancel) pActions[eMenu::Action::CancelTransaction]->setEnabled(true); pActions[eMenu::Action::EnterTransaction]->setEnabled(true); KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce; if (!autoEnter || !schedule.isFixed()) { for (; dlg != 0;) { rc = eDialogs::ScheduleResultCode::Cancel; if (dlg->exec() == QDialog::Accepted && dlg != 0) { rc = dlg->resultCode(); if (rc == eDialogs::ScheduleResultCode::Enter) { transactionEditor->createTransaction(taccepted, torig, torig.splits().isEmpty() ? MyMoneySplit() : torig.splits().front(), true); // make sure to suppress comparison of some data: postDate torig.setPostDate(taccepted.postDate()); if (torig != taccepted) { QPointer cdlg = new KConfirmManualEnterDlg(schedule, q); cdlg->loadTransactions(torig, taccepted); if (cdlg->exec() == QDialog::Accepted) { action = cdlg->action(); delete cdlg; break; } delete cdlg; // the user has chosen 'cancel' during confirmation, // we go back to the editor continue; } } else if (rc == eDialogs::ScheduleResultCode::Skip) { slotTransactionsCancel(transactionEditor, schedule); skipSchedule(schedule); } else { slotTransactionsCancel(transactionEditor, schedule); } } else { if (autoEnter) { if (KMessageBox::warningYesNo(q, i18n("Are you sure you wish to stop this scheduled transaction from being entered into the register?\n\nKMyMoney will prompt you again next time it starts unless you manually enter it later.")) == KMessageBox::No) { // the user has chosen 'No' for the above question, // we go back to the editor continue; } } slotTransactionsCancel(transactionEditor, schedule); } break; } } // if we still have the editor around here, the user did not cancel if ((transactionEditor != 0) && (dlg != 0)) { MyMoneyFileTransaction ft; try { MyMoneyTransaction t; // add the new transaction switch (action) { case KConfirmManualEnterDlg::UseOriginal: // setup widgets with original transaction data transactionEditor->setTransaction(dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front()); // and create a transaction based on that data taccepted = MyMoneyTransaction(); transactionEditor->createTransaction(taccepted, dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front(), true); break; case KConfirmManualEnterDlg::ModifyAlways: torig = taccepted; torig.setPostDate(origDueDate); schedule.setTransaction(torig); break; case KConfirmManualEnterDlg::ModifyOnce: break; } QString newId; q->connect(transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), m_balanceWarning.data(), SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (transactionEditor->enterTransactions(newId, false)) { if (!newId.isEmpty()) { t = MyMoneyFile::instance()->transaction(newId); schedule.setLastPayment(t.postDate()); } // in case the next due date is invalid, the schedule is finished // we mark it as such by setting the next due date to one day past the end QDate nextDueDate = schedule.nextPayment(origDueDate); if (!nextDueDate.isValid()) { schedule.setNextDueDate(schedule.endDate().addDays(1)); } else { schedule.setNextDueDate(nextDueDate); } MyMoneyFile::instance()->modifySchedule(schedule); rc = eDialogs::ScheduleResultCode::Enter; // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. delete transactionEditor; ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(q, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } delete transactionEditor; } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(q, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } delete dlg; } return rc; } void slotTransactionsCancel(TransactionEditor* editor, const MyMoneySchedule& schedule) { Q_Q(KScheduledView); // since we jump here via code, we have to make sure to react only // if the action is enabled if (pActions[eMenu::Action::CancelTransaction]->isEnabled()) { // make sure, we block the enter function pActions[eMenu::Action::EnterTransaction]->setEnabled(false); // qDebug("KMyMoneyApp::slotTransactionsCancel"); delete editor; emit q->selectByObject(schedule, eView::Intent::None); } } /** * This method allows to skip the next scheduled transaction of * the given schedule @a s. * */ void skipSchedule(MyMoneySchedule& schedule) { Q_Q(KScheduledView); if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); if (!schedule.isFinished()) { if (schedule.occurrence() != eMyMoney::Schedule::Occurrence::Once) { QDate next = schedule.nextDueDate(); if (!schedule.isFinished() && (KMessageBox::questionYesNo(q, i18n("Do you really want to skip the %1 transaction scheduled for %2?", schedule.name(), QLocale().toString(next, QLocale::ShortFormat)))) == KMessageBox::Yes) { MyMoneyFileTransaction ft; schedule.setLastPayment(next); schedule.setNextDueDate(schedule.nextPayment(next)); MyMoneyFile::instance()->modifySchedule(schedule); ft.commit(); } } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(q, i18n("Unable to skip scheduled transaction %1.", schedule.name()), e.what()); } } } KScheduledView *q_ptr; Ui::KScheduledView *ui; /// The selected schedule id in the list view. QMenu *m_kaccPopup; QStringList m_filterAccounts; bool m_openBills; bool m_openDeposits; bool m_openTransfers; bool m_openLoans; /** * This member holds the load state of page */ bool m_needLoad; /** * Search widget for the list */ KTreeWidgetSearchLineWidget* m_searchWidget; MyMoneySchedule m_currentSchedule; QScopedPointer m_balanceWarning; }; #endif diff --git a/kmymoney/widgets/kmymoneyaccountselector.cpp b/kmymoney/widgets/kmymoneyaccountselector.cpp index 906f458fb..91d9ba718 100644 --- a/kmymoney/widgets/kmymoneyaccountselector.cpp +++ b/kmymoney/widgets/kmymoneyaccountselector.cpp @@ -1,580 +1,580 @@ /* * Copyright 2003-2018 Thomas Baumgart * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneyaccountselector.h" #include "kmymoneyselector_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "icons/icons.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "widgetenums.h" using namespace Icons; using namespace eMyMoney; class KMyMoneyAccountSelectorPrivate : public KMyMoneySelectorPrivate { Q_DISABLE_COPY(KMyMoneyAccountSelectorPrivate) public: KMyMoneyAccountSelectorPrivate(KMyMoneyAccountSelector *qq) : KMyMoneySelectorPrivate(qq), m_allAccountsButton(0), m_noAccountButton(0), m_incomeCategoriesButton(0), m_expenseCategoriesButton(0) { } QPushButton* m_allAccountsButton; QPushButton* m_noAccountButton; QPushButton* m_incomeCategoriesButton; QPushButton* m_expenseCategoriesButton; QList m_typeList; QStringList m_accountList; }; KMyMoneyAccountSelector::KMyMoneyAccountSelector(QWidget *parent, Qt::WindowFlags flags, const bool createButtons) : KMyMoneySelector(*new KMyMoneyAccountSelectorPrivate(this), parent, flags) { Q_D(KMyMoneyAccountSelector); if (createButtons) { QVBoxLayout* buttonLayout = new QVBoxLayout(); buttonLayout->setSpacing(6); d->m_allAccountsButton = new QPushButton(this); d->m_allAccountsButton->setObjectName("m_allAccountsButton"); d->m_allAccountsButton->setText(i18nc("Select all accounts", "All")); buttonLayout->addWidget(d->m_allAccountsButton); d->m_incomeCategoriesButton = new QPushButton(this); d->m_incomeCategoriesButton->setObjectName("m_incomeCategoriesButton"); d->m_incomeCategoriesButton->setText(i18n("Income")); buttonLayout->addWidget(d->m_incomeCategoriesButton); d->m_expenseCategoriesButton = new QPushButton(this); d->m_expenseCategoriesButton->setObjectName("m_expenseCategoriesButton"); d->m_expenseCategoriesButton->setText(i18n("Expense")); buttonLayout->addWidget(d->m_expenseCategoriesButton); d->m_noAccountButton = new QPushButton(this); d->m_noAccountButton->setObjectName("m_noAccountButton"); d->m_noAccountButton->setText(i18nc("No account", "None")); buttonLayout->addWidget(d->m_noAccountButton); QSpacerItem* spacer = new QSpacerItem(0, 67, QSizePolicy::Minimum, QSizePolicy::Expanding); buttonLayout->addItem(spacer); d->m_layout->addLayout(buttonLayout); connect(d->m_allAccountsButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotSelectAllAccounts); connect(d->m_noAccountButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotDeselectAllAccounts); connect(d->m_incomeCategoriesButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotSelectIncomeCategories); connect(d->m_expenseCategoriesButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotSelectExpenseCategories); } } KMyMoneyAccountSelector::~KMyMoneyAccountSelector() { } void KMyMoneyAccountSelector::removeButtons() { Q_D(KMyMoneyAccountSelector); delete d->m_allAccountsButton; delete d->m_incomeCategoriesButton; delete d->m_expenseCategoriesButton; delete d->m_noAccountButton; } void KMyMoneyAccountSelector::slotSelectAllAccounts() { selectAllItems(true); } void KMyMoneyAccountSelector::slotDeselectAllAccounts() { selectAllItems(false); } void KMyMoneyAccountSelector::selectCategories(const bool income, const bool expense) { Q_D(KMyMoneyAccountSelector); QTreeWidgetItemIterator it_v(d->m_treeWidget); for (; *it_v != 0; ++it_v) { if ((*it_v)->text(0) == i18n("Income categories")) selectAllSubItems(*it_v, income); else if ((*it_v)->text(0) == i18n("Expense categories")) selectAllSubItems(*it_v, expense); } emit stateChanged(); } void KMyMoneyAccountSelector::slotSelectIncomeCategories() { selectCategories(true, false); } void KMyMoneyAccountSelector::slotSelectExpenseCategories() { selectCategories(false, true); } void KMyMoneyAccountSelector::setSelectionMode(QTreeWidget::SelectionMode mode) { Q_D(KMyMoneyAccountSelector); d->m_incomeCategoriesButton->setHidden(mode == QTreeWidget::MultiSelection); d->m_expenseCategoriesButton->setHidden(mode == QTreeWidget::MultiSelection); KMyMoneySelector::setSelectionMode(mode); } QStringList KMyMoneyAccountSelector::accountList(const QList& filterList) const { Q_D(const KMyMoneyAccountSelector); QStringList list; QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable); while (*it) { QVariant id = (*it)->data(0, (int)eWidgets::Selector::Role::Id); MyMoneyAccount acc = MyMoneyFile::instance()->account(id.toString()); if (filterList.count() == 0 || filterList.contains(acc.accountType())) list << id.toString(); it++; } return list; } QStringList KMyMoneyAccountSelector::accountList() const { return accountList(QList()); } bool KMyMoneyAccountSelector::match(const QRegExp& exp, QTreeWidgetItem* item) const { if (!item->flags().testFlag(Qt::ItemIsSelectable)) return false; return exp.indexIn(item->data(0, (int)eWidgets::Selector::Role::Key).toString().mid(1)) != -1; } bool KMyMoneyAccountSelector::contains(const QString& txt) const { Q_D(const KMyMoneyAccountSelector); QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable); QTreeWidgetItem* it_v; QString baseName = i18n("Asset") + '|' + i18n("Liability") + '|' + i18n("Income") + '|' + i18n("Expense") + '|' + i18n("Equity") + '|' + i18n("Security"); while ((it_v = *it) != 0) { QRegExp exp(QString("^(?:%1):%2$").arg(baseName).arg(QRegExp::escape(txt))); if (exp.indexIn(it_v->data(0, (int)eWidgets::Selector::Role::Key).toString().mid(1)) != -1) { return true; } it++; } return false; } class AccountSetPrivate { Q_DISABLE_COPY(AccountSetPrivate) public: AccountSetPrivate() : m_count(0) , m_file(MyMoneyFile::instance()) , m_favorites(0) , m_hideClosedAccounts(true) , m_showInvestments(false) { } int m_count; MyMoneyFile* m_file; QList m_typeList; QTreeWidgetItem* m_favorites; bool m_hideClosedAccounts; bool m_showInvestments; }; AccountSet::AccountSet() : d_ptr(new AccountSetPrivate) { } AccountSet::~AccountSet() { Q_D(AccountSet); delete d; } void AccountSet::setShowInvestments(bool show) { Q_D(AccountSet); d->m_showInvestments = show; } void AccountSet::addAccountGroup(Account::Type group) { Q_D(AccountSet); if (group == Account::Type::Asset) { d->m_typeList << Account::Type::Checkings; d->m_typeList << Account::Type::Savings; d->m_typeList << Account::Type::Cash; d->m_typeList << Account::Type::AssetLoan; d->m_typeList << Account::Type::CertificateDep; d->m_typeList << Account::Type::Investment; d->m_typeList << Account::Type::Stock; d->m_typeList << Account::Type::MoneyMarket; d->m_typeList << Account::Type::Asset; d->m_typeList << Account::Type::Currency; } else if (group == Account::Type::Liability) { d->m_typeList << Account::Type::CreditCard; d->m_typeList << Account::Type::Loan; d->m_typeList << Account::Type::Liability; } else if (group == Account::Type::Income) { d->m_typeList << Account::Type::Income; } else if (group == Account::Type::Expense) { d->m_typeList << Account::Type::Expense; } else if (group == Account::Type::Equity) { d->m_typeList << Account::Type::Equity; } } void AccountSet::addAccountType(Account::Type type) { Q_D(AccountSet); d->m_typeList << type; } void AccountSet::removeAccountType(Account::Type type) { Q_D(AccountSet); int index = d->m_typeList.indexOf(type); if (index != -1) { d->m_typeList.removeAt(index); } } void AccountSet::clear() { Q_D(AccountSet); d->m_typeList.clear(); } int AccountSet::load(KMyMoneyAccountSelector* selector) { Q_D(AccountSet); QStringList list; QStringList::ConstIterator it_l; int count = 0; int typeMask = 0; QString currentId; if (selector->selectionMode() == QTreeWidget::SingleSelection) { selector->selectedItems(list); if (!list.isEmpty()) currentId = list.first(); } if (d->m_typeList.contains(Account::Type::Checkings) || d->m_typeList.contains(Account::Type::Savings) || d->m_typeList.contains(Account::Type::Cash) || d->m_typeList.contains(Account::Type::AssetLoan) || d->m_typeList.contains(Account::Type::CertificateDep) || d->m_typeList.contains(Account::Type::Investment) || d->m_typeList.contains(Account::Type::Stock) || d->m_typeList.contains(Account::Type::MoneyMarket) || d->m_typeList.contains(Account::Type::Asset) || d->m_typeList.contains(Account::Type::Currency)) typeMask |= eDialogs::Category::asset; if (d->m_typeList.contains(Account::Type::CreditCard) || d->m_typeList.contains(Account::Type::Loan) || d->m_typeList.contains(Account::Type::Liability)) typeMask |= eDialogs::Category::liability; if (d->m_typeList.contains(Account::Type::Income)) typeMask |= eDialogs::Category::income; if (d->m_typeList.contains(Account::Type::Expense)) typeMask |= eDialogs::Category::expense; if (d->m_typeList.contains(Account::Type::Equity)) typeMask |= eDialogs::Category::equity; selector->clear(); QTreeWidget* lv = selector->listView(); d->m_count = 0; QString key; QTreeWidgetItem* after = 0; // create the favorite section first and sort it to the beginning key = QString("A%1").arg(i18n("Favorites")); d->m_favorites = selector->newItem(i18n("Favorites"), key); //get the account icon from cache or insert it if it is not there QPixmap accountPixmap; if (!QPixmapCache::find("account", accountPixmap)) { - QIcon icon = Icons::get(Icon::ViewBankAccount); + QIcon icon = Icons::get(Icon::BankAccount); if (!icon.availableSizes().isEmpty()) accountPixmap = icon.pixmap(icon.availableSizes().first()); QPixmapCache::insert("account", accountPixmap); } d->m_favorites->setIcon(0, QIcon(accountPixmap)); for (auto mask = 0x01; mask != eDialogs::Category::last; mask <<= 1) { QTreeWidgetItem* item = 0; if ((typeMask & mask & eDialogs::Category::asset) != 0) { ++d->m_count; key = QString("B%1").arg(i18n("Asset")); item = selector->newItem(i18n("Asset accounts"), key); item->setIcon(0, d->m_file->asset().accountPixmap()); list = d->m_file->asset().accountList(); } if ((typeMask & mask & eDialogs::Category::liability) != 0) { ++d->m_count; key = QString("C%1").arg(i18n("Liability")); item = selector->newItem(i18n("Liability accounts"), key); item->setIcon(0, d->m_file->liability().accountPixmap()); list = d->m_file->liability().accountList(); } if ((typeMask & mask & eDialogs::Category::income) != 0) { ++d->m_count; key = QString("D%1").arg(i18n("Income")); item = selector->newItem(i18n("Income categories"), key); item->setIcon(0, d->m_file->income().accountPixmap()); list = d->m_file->income().accountList(); if (selector->selectionMode() == QTreeWidget::MultiSelection) { selector->d_func()->m_incomeCategoriesButton->show(); } } if ((typeMask & mask & eDialogs::Category::expense) != 0) { ++d->m_count; key = QString("E%1").arg(i18n("Expense")); item = selector->newItem(i18n("Expense categories"), key); item->setIcon(0, d->m_file->expense().accountPixmap()); list = d->m_file->expense().accountList(); if (selector->selectionMode() == QTreeWidget::MultiSelection) { selector->d_func()->m_expenseCategoriesButton->show(); } } if ((typeMask & mask & eDialogs::Category::equity) != 0) { ++d->m_count; key = QString("F%1").arg(i18n("Equity")); item = selector->newItem(i18n("Equity accounts"), key); item->setIcon(0, d->m_file->equity().accountPixmap()); list = d->m_file->equity().accountList(); } if (!after) after = item; if (item != 0) { // scan all matching accounts found in the engine for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { const MyMoneyAccount& acc = d->m_file->account(*it_l); ++d->m_count; ++count; //this will include an account if it matches the account type and //if it is still open or it has been set to show closed accounts if (includeAccount(acc) && (!isHidingClosedAccounts() || !acc.isClosed())) { QString tmpKey; tmpKey = key + MyMoneyFile::AccountSeparator + acc.name(); QTreeWidgetItem* subItem = selector->newItem(item, acc.name(), tmpKey, acc.id()); subItem->setIcon(0, acc.accountPixmap()); if (acc.value("PreferredAccount") == "Yes" && d->m_typeList.contains(acc.accountType())) { selector->newItem(d->m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());; } if (acc.accountList().count() > 0) { subItem->setExpanded(true); count += loadSubAccounts(selector, subItem, tmpKey, acc.accountList()); } // the item is not selectable if it has been added only because a subaccount matches the type if (!d->m_typeList.contains(acc.accountType())) { selector->setSelectable(subItem, false); } subItem->sortChildren(1, Qt::AscendingOrder); } } item->sortChildren(1, Qt::AscendingOrder); } } d->m_favorites->sortChildren(1, Qt::AscendingOrder); lv->invisibleRootItem()->sortChildren(1, Qt::AscendingOrder); // if we don't have a favorite account or the selector is for multi-mode // we get rid of the favorite entry and subentries. if (d->m_favorites->childCount() == 0 || selector->selectionMode() == QTreeWidget::MultiSelection) { delete d->m_favorites; d->m_favorites = 0; } if (lv->itemAt(0, 0)) { if (currentId.isEmpty()) { lv->setCurrentItem(lv->itemAt(0, 0)); lv->clearSelection(); } else { selector->setSelected(currentId); } } selector->update(); return count; } int AccountSet::load(KMyMoneyAccountSelector* selector, const QString& baseName, const QList& accountIdList, const bool clear) { Q_D(AccountSet); int count = 0; QTreeWidgetItem* item = 0; d->m_typeList.clear(); if (clear) { d->m_count = 0; selector->clear(); } item = selector->newItem(baseName); ++d->m_count; QList::ConstIterator it; for (it = accountIdList.constBegin(); it != accountIdList.constEnd(); ++it) { const MyMoneyAccount& acc = d->m_file->account(*it); if (acc.isClosed()) continue; QString tmpKey; // the first character must be preset. Since we don't know any sort order here, we just use A tmpKey = QString("A%1%2%3").arg(baseName, MyMoneyFile::AccountSeparator, acc.name()); selector->newItem(item, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap()); ++d->m_count; ++count; } QTreeWidget* lv = selector->listView(); if (lv->itemAt(0, 0)) { lv->setCurrentItem(lv->itemAt(0, 0)); lv->clearSelection(); } selector->update(); return count; } int AccountSet::count() const { Q_D(const AccountSet); return d->m_count; } void AccountSet::setHideClosedAccounts(bool _bool) { Q_D(AccountSet); d->m_hideClosedAccounts = _bool; } bool AccountSet::isHidingClosedAccounts() const { Q_D(const AccountSet); return d->m_hideClosedAccounts; } int AccountSet::loadSubAccounts(KMyMoneyAccountSelector* selector, QTreeWidgetItem* parent, const QString& key, const QStringList& list) { Q_D(AccountSet); QStringList::ConstIterator it_l; int count = 0; for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { const MyMoneyAccount& acc = d->m_file->account(*it_l); // don't include stock accounts if not in expert mode if (acc.isInvest() && !d->m_showInvestments) continue; //this will include an account if it matches the account type and //if it is still open or it has been set to show closed accounts if (includeAccount(acc) && (!isHidingClosedAccounts() || !acc.isClosed())) { QString tmpKey; tmpKey = key + MyMoneyFile::AccountSeparator + acc.name(); ++count; ++d->m_count; QTreeWidgetItem* item = selector->newItem(parent, acc.name(), tmpKey, acc.id()); item->setIcon(0, acc.accountPixmap()); if (acc.value("PreferredAccount") == "Yes" && d->m_typeList.contains(acc.accountType())) { selector->newItem(d->m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap()); } if (acc.accountList().count() > 0) { item->setExpanded(true); count += loadSubAccounts(selector, item, tmpKey, acc.accountList()); } // the item is not selectable if it has been added only because a subaccount matches the type if (!d->m_typeList.contains(acc.accountType())) { selector->setSelectable(item, false); } item->sortChildren(1, Qt::AscendingOrder); } } return count; } bool AccountSet::includeAccount(const MyMoneyAccount& acc) { Q_D(AccountSet); if (d->m_typeList.contains(acc.accountType())) return true; foreach (const auto sAccount, acc.accountList()) if (includeAccount(d->m_file->account(sAccount))) return true; return false; } diff --git a/kmymoney/widgets/kmymoneycurrencyselector.cpp b/kmymoney/widgets/kmymoneycurrencyselector.cpp index 396375726..ba8f5ffb4 100644 --- a/kmymoney/widgets/kmymoneycurrencyselector.cpp +++ b/kmymoney/widgets/kmymoneycurrencyselector.cpp @@ -1,210 +1,210 @@ /* * Copyright 2004-2011 Thomas Baumgart * Copyright 2017 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneycurrencyselector.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "icons/icons.h" using namespace Icons; class KMyMoneySecuritySelectorPrivate { Q_DISABLE_COPY(KMyMoneySecuritySelectorPrivate) Q_DECLARE_PUBLIC(KMyMoneySecuritySelector) public: enum displayItemE { Symbol = 0, FullName }; enum displayTypeE { TypeCurrencies = 0x01, TypeSecurities = 0x02, TypeAll = 0x03 }; explicit KMyMoneySecuritySelectorPrivate(KMyMoneySecuritySelector *qq): q_ptr(qq), m_displayItem(FullName), m_selectedItemId(0), m_displayOnly(false), m_displayType(TypeAll) { } void selectDisplayItem(displayItemE item) { Q_Q(KMyMoneySecuritySelector); m_displayItem = item; q->update(QString()); } void setDisplayType(displayTypeE type) { m_displayType = type; } KMyMoneySecuritySelector *q_ptr; MyMoneySecurity m_currency; displayItemE m_displayItem; int m_selectedItemId; bool m_displayOnly; displayTypeE m_displayType; QList m_list; }; KMyMoneySecuritySelector::KMyMoneySecuritySelector(QWidget *parent) : KComboBox(parent), d_ptr(new KMyMoneySecuritySelectorPrivate(this)) { // update(QString()); } KMyMoneySecuritySelector::~KMyMoneySecuritySelector() { Q_D(KMyMoneySecuritySelector); delete d; } void KMyMoneySecuritySelector::update(const QString& id) { Q_D(KMyMoneySecuritySelector); MyMoneySecurity curr = MyMoneyFile::instance()->baseCurrency(); QString baseCurrency = curr.id(); if (!id.isEmpty()) curr = d->m_currency; this->clear(); d->m_list.clear(); if (d->m_displayType & KMyMoneySecuritySelectorPrivate::TypeCurrencies) d->m_list += MyMoneyFile::instance()->currencyList(); if (d->m_displayType & KMyMoneySecuritySelectorPrivate::TypeSecurities) d->m_list += MyMoneyFile::instance()->securityList(); // sort qSort(d->m_list); QList::ConstIterator it; // construct a transparent 16x16 pixmap static unsigned char empty_png[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xDB, 0x07, 0x08, 0x0B, 0x16, 0x09, 0xAA, 0xA8, 0x50, 0x21, 0x00, 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x38, 0xCB, 0x63, 0x60, 0x18, 0x05, 0xA3, 0x60, 0x14, 0x8C, 0x02, 0x08, 0x00, 0x00, 0x04, 0x10, 0x00, 0x01, 0x85, 0x3F, 0xAA, 0x72, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; QPixmap empty; empty.loadFromData(empty_png, sizeof(empty_png), 0, Qt::AutoColor); QIcon emptyIcon(empty); int itemId = 0; int m_selectedItemId = 0; for (it = d->m_list.constBegin(); it != d->m_list.constEnd(); ++it) { QString display; switch (d->m_displayItem) { default: case KMyMoneySecuritySelectorPrivate::FullName: if ((*it).isCurrency()) { display = QString("%2 (%1)").arg((*it).id()).arg((*it).name()); } else display = QString("%2 (%1)").arg((*it).tradingSymbol()).arg((*it).name()); break; break; case KMyMoneySecuritySelectorPrivate::Symbol: if ((*it).isCurrency()) display = (*it).id(); else display = (*it).tradingSymbol(); break; } if ((*it).id() == baseCurrency) { - insertItem(itemId, Icons::get(Icon::ViewBankAccount), display); + insertItem(itemId, Icons::get(Icon::BankAccount), display); } else { insertItem(itemId, emptyIcon, display); } if (curr.id() == (*it).id()) { m_selectedItemId = itemId; d->m_currency = (*it); } itemId++; } setCurrentIndex(m_selectedItemId); } const MyMoneySecurity& KMyMoneySecuritySelector::security() const { Q_D(const KMyMoneySecuritySelector); int index = currentIndex(); if ((0 <= index) && (index < d->m_list.size())) return d->m_list[index]; else return d->m_currency; } void KMyMoneySecuritySelector::setSecurity(const MyMoneySecurity& currency) { Q_D(KMyMoneySecuritySelector); d->m_currency = currency; update(QString("x")); } KMyMoneyCurrencySelector::KMyMoneyCurrencySelector(QWidget *parent) : KMyMoneySecuritySelector(parent) { Q_D(KMyMoneySecuritySelector); d->setDisplayType(KMyMoneySecuritySelectorPrivate::TypeCurrencies); } KMyMoneyCurrencySelector::~KMyMoneyCurrencySelector() { } diff --git a/kmymoney/widgets/kmymoneydateinput.cpp b/kmymoney/widgets/kmymoneydateinput.cpp index d34e926e5..fde749499 100644 --- a/kmymoney/widgets/kmymoneydateinput.cpp +++ b/kmymoney/widgets/kmymoneydateinput.cpp @@ -1,402 +1,402 @@ /* * Copyright 2000-2003 Michael Edwardes * Copyright 2001 Felix Rodriguez * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneydateinput.h" #include "kmymoneysettings.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "icons/icons.h" using namespace Icons; namespace { const int DATE_POPUP_TIMEOUT = 1500; const QDate INVALID_DATE = QDate(1800, 1, 1); } KMyMoney::OldDateEdit::OldDateEdit(const QDate& date, QWidget* parent) : QDateEdit(date, parent) , m_initialSection(QDateTimeEdit::DaySection) { } void KMyMoney::OldDateEdit::keyPressEvent(QKeyEvent* k) { if ((lineEdit()->text().isEmpty() || lineEdit()->selectedText() == lineEdit()->text()) && QChar(k->key()).isDigit()) { // the line edit is empty which means that the date was cleared // or the whole text is selected and a digit character was entered // (the same meaning as clearing the date) - in this case set the date // to the current date and let the editor do the actual work setDate(QDate::currentDate()); setSelectedSection(m_initialSection); // start as when focused in if the date was cleared } QDateEdit::keyPressEvent(k); } void KMyMoney::OldDateEdit::focusInEvent(QFocusEvent * event) { QDateEdit::focusInEvent(event); setSelectedSection(m_initialSection); } bool KMyMoney::OldDateEdit::event(QEvent* e) { // make sure that we keep the current date setting of a KMyMoneyDateInput object // across the QDateEdit::event(FocusOutEvent) bool rc; KMyMoneyDateInput* p = dynamic_cast(parentWidget()); if (e->type() == QEvent::FocusOut && p) { QDate d = p->date(); rc = QDateEdit::event(e); if (d.isValid()) d = p->date(); p->loadDate(d); } else { rc = QDateEdit::event(e); } return rc; } bool KMyMoney::OldDateEdit::focusNextPrevChild(bool next) { Q_UNUSED(next) return true; } struct KMyMoneyDateInput::Private { KMyMoney::OldDateEdit *m_dateEdit; KDatePicker *m_datePicker; QDate m_date; QDate m_prevDate; Qt::AlignmentFlag m_qtalignment; QWidget *m_dateFrame; QPushButton *m_dateButton; KPassivePopup *m_datePopup; }; KMyMoneyDateInput::KMyMoneyDateInput(QWidget *parent, Qt::AlignmentFlag flags) : QWidget(parent), d(new Private) { d->m_qtalignment = flags; d->m_date = QDate::currentDate(); QHBoxLayout *dateInputLayout = new QHBoxLayout(this); dateInputLayout->setSpacing(0); dateInputLayout->setContentsMargins(0, 0, 0, 0); d->m_dateEdit = new KMyMoney::OldDateEdit(d->m_date, this); dateInputLayout->addWidget(d->m_dateEdit, 3); setFocusProxy(d->m_dateEdit); d->m_dateEdit->installEventFilter(this); // To get d->m_dateEdit's FocusIn/Out and some KeyPress events // we use INVALID_DATE as a special value for multi transaction editing d->m_dateEdit->setMinimumDate(INVALID_DATE); d->m_dateEdit->setSpecialValueText(QLatin1String(" ")); d->m_datePopup = new KPassivePopup(d->m_dateEdit); d->m_datePopup->setObjectName("datePopup"); d->m_datePopup->setTimeout(DATE_POPUP_TIMEOUT); d->m_datePopup->setView(new QLabel(QLocale().toString(d->m_date), d->m_datePopup)); d->m_dateFrame = new QWidget(this); dateInputLayout->addWidget(d->m_dateFrame); QVBoxLayout *dateFrameVBoxLayout = new QVBoxLayout(d->m_dateFrame); dateFrameVBoxLayout->setMargin(0); dateFrameVBoxLayout->setContentsMargins(0, 0, 0, 0); d->m_dateFrame->setWindowFlags(Qt::Popup); d->m_dateFrame->hide(); d->m_dateEdit->setDisplayFormat(QLocale().dateFormat(QLocale::ShortFormat)); switch(KMyMoneySettings::initialDateFieldCursorPosition()) { case KMyMoneySettings::Day: d->m_dateEdit->setInitialSection(QDateTimeEdit::DaySection); break; case KMyMoneySettings::Month: d->m_dateEdit->setInitialSection(QDateTimeEdit::MonthSection); break; case KMyMoneySettings::Year: d->m_dateEdit->setInitialSection(QDateTimeEdit::YearSection); break; } d->m_datePicker = new KDatePicker(d->m_date, d->m_dateFrame); dateFrameVBoxLayout->addWidget(d->m_datePicker); // Let the date picker have a close button (Added in 3.1) d->m_datePicker->setCloseButton(true); // the next line is a try to add an icon to the button - d->m_dateButton = new QPushButton(Icons::get(Icon::ViewCalendarDay), QString(), this); + d->m_dateButton = new QPushButton(Icons::get(Icon::CalendarDay), QString(), this); dateInputLayout->addWidget(d->m_dateButton); connect(d->m_dateButton, &QAbstractButton::clicked, this, &KMyMoneyDateInput::toggleDatePicker); connect(d->m_dateEdit, &QDateTimeEdit::dateChanged, this, &KMyMoneyDateInput::slotDateChosenRef); connect(d->m_datePicker, &KDatePicker::dateSelected, this, &KMyMoneyDateInput::slotDateChosen); connect(d->m_datePicker, &KDatePicker::dateEntered, this, &KMyMoneyDateInput::slotDateChosen); connect(d->m_datePicker, &KDatePicker::dateSelected, d->m_dateFrame, &QWidget::hide); } void KMyMoneyDateInput::markAsBadDate(bool bad, const QColor& color) { // the next line knows a bit about the internals of QAbstractSpinBox QLineEdit* le = d->m_dateEdit->findChild(); //krazy:exclude=qclasses if (le) { QPalette palette = this->palette(); le->setPalette(palette); if (bad) { palette.setColor(foregroundRole(), color); le->setPalette(palette); } } } void KMyMoneyDateInput::showEvent(QShowEvent* event) { // don't forget the standard behaviour ;-) QWidget::showEvent(event); // If the widget is shown, the size must be fixed a little later // to be appropriate. I saw this in some other places and the only // way to solve this problem is to postpone the setup of the size // to the time when the widget is on the screen. QTimer::singleShot(50, this, SLOT(fixSize())); } void KMyMoneyDateInput::fixSize() { // According to a hint in the documentation of KDatePicker::sizeHint() // 28 pixels should be added in each direction to obtain a better // display of the month button. I decided, (22,14) is good // enough and save some space on the screen (ipwizard) d->m_dateFrame->setFixedSize(d->m_datePicker->sizeHint() + QSize(22, 14)); } KMyMoneyDateInput::~KMyMoneyDateInput() { delete d->m_dateFrame; delete d->m_datePopup; delete d; } void KMyMoneyDateInput::toggleDatePicker() { int w = d->m_dateFrame->width(); int h = d->m_dateFrame->height(); if (d->m_dateFrame->isVisible()) { d->m_dateFrame->hide(); } else { QPoint tmpPoint = mapToGlobal(d->m_dateButton->geometry().bottomRight()); // usually, the datepicker widget is shown underneath the d->m_dateEdit widget // if it does not fit on the screen, we show it above this widget if (tmpPoint.y() + h > QApplication::desktop()->height()) { tmpPoint.setY(tmpPoint.y() - h - d->m_dateButton->height()); } if ((d->m_qtalignment == Qt::AlignRight && tmpPoint.x() + w <= QApplication::desktop()->width()) || (tmpPoint.x() - w < 0)) { d->m_dateFrame->setGeometry(tmpPoint.x() - width(), tmpPoint.y(), w, h); } else { tmpPoint.setX(tmpPoint.x() - w); d->m_dateFrame->setGeometry(tmpPoint.x(), tmpPoint.y(), w, h); } if (d->m_date.isValid() && d->m_date != INVALID_DATE) { d->m_datePicker->setDate(d->m_date); } else { d->m_datePicker->setDate(QDate::currentDate()); } d->m_dateFrame->show(); } } /** Overriding QWidget::keyPressEvent * * increments/decrements the date upon +/- or Up/Down key input * sets the date to current date when the 'T' key is pressed */ void KMyMoneyDateInput::keyPressEvent(QKeyEvent * k) { QKeySequence today(i18nc("Enter todays date into date input widget", "T")); auto adjustDateSection = [&](int offset) { switch(d->m_dateEdit->currentSection()) { case QDateTimeEdit::DaySection: slotDateChosen(d->m_date.addDays(offset)); break; case QDateTimeEdit::MonthSection: slotDateChosen(d->m_date.addMonths(offset)); break; case QDateTimeEdit::YearSection: slotDateChosen(d->m_date.addYears(offset)); break; default: break; } }; switch (k->key()) { case Qt::Key_Equal: case Qt::Key_Plus: adjustDateSection(1); k->accept(); break; case Qt::Key_Minus: adjustDateSection(-1); k->accept(); break; default: if (today == QKeySequence(k->key()) || k->key() == Qt::Key_T) { slotDateChosen(QDate::currentDate()); k->accept(); } break; } k->ignore(); // signal that the key event was not handled } /** * This function receives all events that are sent to focusWidget(). * Some KeyPress events are intercepted and passed to keyPressEvent. * Otherwise they would be consumed by QDateEdit. */ bool KMyMoneyDateInput::eventFilter(QObject *, QEvent *e) { if (e->type() == QEvent::FocusIn) { #ifndef Q_OS_MAC d->m_datePopup->show(mapToGlobal(QPoint(0, height()))); #endif // select the date section, but we need to delay it a bit } else if (e->type() == QEvent::FocusOut) { #ifndef Q_OS_MAC d->m_datePopup->hide(); #endif } else if (e->type() == QEvent::KeyPress) { if (QKeyEvent *k = dynamic_cast(e)) { keyPressEvent(k); if (k->isAccepted()) return true; // signal that the key event was handled } } return false; // Don't filter the event } void KMyMoneyDateInput::slotDateChosenRef(const QDate& date) { if (date.isValid()) { emit dateChanged(date); d->m_date = date; #ifndef Q_OS_MAC QLabel *lbl = static_cast(d->m_datePopup->view()); lbl->setText(QLocale().toString(date)); lbl->adjustSize(); if (d->m_datePopup->isVisible() || hasFocus()) d->m_datePopup->show(mapToGlobal(QPoint(0, height()))); // Repaint #endif } } void KMyMoneyDateInput::slotDateChosen(QDate date) { if (date.isValid()) { // the next line implies a call to slotDateChosenRef() above d->m_dateEdit->setDate(date); } else { d->m_dateEdit->setDate(INVALID_DATE); } } QDate KMyMoneyDateInput::date() const { QDate rc = d->m_dateEdit->date(); if (rc == INVALID_DATE) rc = QDate(); return rc; } void KMyMoneyDateInput::setDate(QDate date) { slotDateChosen(date); } void KMyMoneyDateInput::loadDate(const QDate& date) { d->m_date = d->m_prevDate = date; blockSignals(true); slotDateChosen(date); blockSignals(false); } void KMyMoneyDateInput::resetDate() { setDate(d->m_prevDate); } void KMyMoneyDateInput::setMaximumDate(const QDate& max) { d->m_dateEdit->setMaximumDate(max); } QWidget* KMyMoneyDateInput::focusWidget() const { QWidget* w = d->m_dateEdit; while (w->focusProxy()) w = w->focusProxy(); return w; } /* void KMyMoneyDateInput::setRange(const QDate & min, const QDate & max) { d->m_dateEdit->setDateRange(min, max); } */ diff --git a/kmymoney/widgets/passwordtoggle.cpp b/kmymoney/widgets/passwordtoggle.cpp index bbe18b956..f9c5f454e 100644 --- a/kmymoney/widgets/passwordtoggle.cpp +++ b/kmymoney/widgets/passwordtoggle.cpp @@ -1,57 +1,65 @@ /* * Copyright 2019 Thomas Baumgart * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "passwordtoggle.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "icons/icons.h" + +using namespace Icons; + PasswordToggle::PasswordToggle(QLineEdit* parent) : QObject(parent) , m_lineEdit(parent) { - m_toggleAction = m_lineEdit->addAction(QIcon::fromTheme(QStringLiteral("visibility")), QLineEdit::TrailingPosition); + m_toggleAction = m_lineEdit->addAction(Icons::get(Icon::Visibility), QLineEdit::TrailingPosition); m_toggleAction->setVisible(false); m_toggleAction->setToolTip(i18n("Change the visibility of the password")); connect(m_lineEdit, &QLineEdit::textChanged, this, &PasswordToggle::toggleEchoModeAction); connect(m_toggleAction, &QAction::triggered, this, &PasswordToggle::toggleEchoMode); } void PasswordToggle::toggleEchoModeAction(const QString& text) { m_toggleAction->setVisible(!text.isEmpty()); } void PasswordToggle::toggleEchoMode() { if (m_lineEdit->echoMode() == QLineEdit::Password) { m_lineEdit->setEchoMode(QLineEdit::Normal); - m_toggleAction->setIcon(QIcon::fromTheme(QStringLiteral("hint"))); + m_toggleAction->setIcon(Icons::get(Icon::NoVisibility)); } else if (m_lineEdit->echoMode() == QLineEdit::Normal) { m_lineEdit->setEchoMode(QLineEdit::Password); - m_toggleAction->setIcon(QIcon::fromTheme(QStringLiteral("visibility"))); + m_toggleAction->setIcon(Icons::get(Icon::Visibility)); } } diff --git a/kmymoney/widgets/registersearchline.cpp b/kmymoney/widgets/registersearchline.cpp index 760506b9f..41c507d7d 100644 --- a/kmymoney/widgets/registersearchline.cpp +++ b/kmymoney/widgets/registersearchline.cpp @@ -1,253 +1,253 @@ /* * Copyright 2007-2018 Thomas Baumgart * Copyright 2017-2018 Ł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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "registersearchline.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyutils.h" #include "register.h" #include "registeritem.h" #include "registerfilter.h" #include "icons/icons.h" #include "widgetenums.h" using namespace eWidgets; using namespace KMyMoneyRegister; using namespace Icons; class RegisterSearchLine::RegisterSearchLinePrivate { public: RegisterSearchLinePrivate() : reg(0), combo(0), queuedSearches(0), status(eRegister::ItemState::Any) {} Register* reg; KComboBox* combo; QString search; int queuedSearches; eRegister::ItemState status; }; RegisterSearchLine::RegisterSearchLine(QWidget* parent, Register* reg) : KLineEdit(parent), d(new RegisterSearchLinePrivate) { setClearButtonEnabled(true); if (!parentWidget()->layout()) parentWidget()->setLayout(new QHBoxLayout); parentWidget()->layout()->addWidget(this); d->reg = reg; connect(this, &QLineEdit::textChanged, this, &RegisterSearchLine::queueSearch); QLabel* label = new QLabel(i18nc("label for status combo", "Stat&us"), parentWidget()); parentWidget()->layout()->addWidget(label); d->combo = new KComboBox(parentWidget()); parentWidget()->layout()->addWidget(d->combo); // don't change the order of the following lines unless updating // the case labels in RegisterSearchLine::itemMatches() at the same time d->combo->insertItem((int)eRegister::ItemState::Any, Icons::get(Icon::Unknown), i18n("Any status")); d->combo->insertItem((int)eRegister::ItemState::Imported, Icons::get(Icon::DocumentImport), i18n("Imported")); d->combo->insertItem((int)eRegister::ItemState::Matched, Icons::get(Icon::TransactionMatch), i18n("Matched")); - d->combo->insertItem((int)eRegister::ItemState::Erroneous, Icons::get(Icon::TaskAttention), i18n("Erroneous")); - d->combo->insertItem((int)eRegister::ItemState::Scheduled, Icons::get(Icon::ViewSchedules), i18n("Scheduled")); + d->combo->insertItem((int)eRegister::ItemState::Erroneous, Icons::get(Icon::Warning), i18n("Erroneous")); + d->combo->insertItem((int)eRegister::ItemState::Scheduled, Icons::get(Icon::Schedule), i18n("Scheduled")); d->combo->insertItem((int)eRegister::ItemState::NotMarked, i18n("Not marked")); d->combo->insertItem((int)eRegister::ItemState::NotReconciled, i18n("Not reconciled")); d->combo->insertItem((int)eRegister::ItemState::Cleared, i18nc("Reconciliation state 'Cleared'", "Cleared")); d->combo->setCurrentIndex((int)eRegister::ItemState::Any); connect(d->combo, static_cast(&QComboBox::activated), this, &RegisterSearchLine::slotStatusChanged); label->setBuddy(d->combo); if (reg) { connect(reg, &QObject::destroyed, this, &RegisterSearchLine::registerDestroyed); connect(reg, &Register::itemAdded, this, &RegisterSearchLine::itemAdded); } else { setEnabled(false); } } RegisterSearchLine::~RegisterSearchLine() { delete d; } void RegisterSearchLine::setRegister(Register* reg) { if (d->reg) { disconnect(d->reg, &QObject::destroyed, this, &RegisterSearchLine::registerDestroyed); disconnect(d->reg, &Register::itemAdded, this, &RegisterSearchLine::itemAdded); } d->reg = reg; if (reg) { connect(reg, &QObject::destroyed, this, &RegisterSearchLine::registerDestroyed); connect(reg, &Register::itemAdded, this, &RegisterSearchLine::itemAdded); } setEnabled(reg != 0); } void RegisterSearchLine::slotStatusChanged(int status) { d->status = static_cast(status); updateSearch(); } void RegisterSearchLine::queueSearch(const QString& search) { d->queuedSearches++; d->search = search; QTimer::singleShot(200, this, SLOT(activateSearch())); } void RegisterSearchLine::activateSearch() { --(d->queuedSearches); if (d->queuedSearches == 0) updateSearch(d->search); } void RegisterSearchLine::updateSearch(const QString& s) { if (!d->reg) return; d->search = s.isNull() ? text() : s; // keep track of the current focus item RegisterItem* focusItem = d->reg->focusItem(); bool enabled = d->reg->updatesEnabled(); d->reg->setUpdatesEnabled(false); bool scrollBarVisible = d->reg->verticalScrollBar()->isVisible(); RegisterFilter filter(d->search, d->status); RegisterItem* p = d->reg->firstItem(); for (; p; p = p->nextItem()) { p->setVisible(p->matches(filter)); } d->reg->suppressAdjacentMarkers(); d->reg->updateAlternate(); d->reg->setUpdatesEnabled(enabled); // if focus item is still visible, then make sure we have // it on screen if (focusItem && focusItem->isVisible()) { d->reg->update(); /* it's totally fine to call ensureFocusItemVisible instantly * while narrowing (by adding another letter) filtered results * because removing items from QTableWidget is fast * but while widening (by removing some letter) filtered results * QTableWidget lags and ensureFocusItemVisible() happens before * its update and focused item isn't made visible therefore */ QTimer::singleShot(500, d->reg, SLOT(ensureFocusItemVisible())); } // if the scrollbar's visibility changed, we need to resize the contents if (scrollBarVisible != d->reg->verticalScrollBar()->isVisible()) { d->reg->resize((int)eTransaction::Column::Detail); } } void RegisterSearchLine::itemAdded(RegisterItem* item) const { item->setVisible(item->matches(RegisterFilter(text(), d->status))); } void RegisterSearchLine::registerDestroyed() { d->reg = 0; setEnabled(false); } class RegisterSearchLineWidget::RegisterSearchLineWidgetPrivate { public: RegisterSearchLineWidgetPrivate() : reg(0), searchLine(0) {} Register* reg; RegisterSearchLine* searchLine; }; RegisterSearchLineWidget::RegisterSearchLineWidget(Register* reg, QWidget* parent) : QWidget(parent), d(new RegisterSearchLineWidgetPrivate) { d->reg = reg; createWidgets(); } RegisterSearchLineWidget::~RegisterSearchLineWidget() { delete d; } RegisterSearchLine* RegisterSearchLineWidget::createSearchLine(Register* reg) { if (!d->searchLine) d->searchLine = new RegisterSearchLine(this, reg); return d->searchLine; } void RegisterSearchLineWidget::createWidgets() { QHBoxLayout *searchLineLayout = new QHBoxLayout(this); searchLineLayout->setSpacing(0); searchLineLayout->setContentsMargins(0, 0, 0, 0); QLabel *label = new QLabel(i18nc("Filter widget label", "Fi<er:"), this); searchLineLayout->addWidget(label); d->searchLine = createSearchLine(d->reg); d->searchLine->show(); label->setBuddy(d->searchLine); label->show(); } RegisterSearchLine* RegisterSearchLineWidget::searchLine() const { return d->searchLine; } diff --git a/kmymoney/wizards/newuserwizard/kcurrencypage.cpp b/kmymoney/wizards/newuserwizard/kcurrencypage.cpp index ce90960a1..aa3abbe78 100644 --- a/kmymoney/wizards/newuserwizard/kcurrencypage.cpp +++ b/kmymoney/wizards/newuserwizard/kcurrencypage.cpp @@ -1,124 +1,124 @@ /*************************************************************************** kcurrencypage.cpp ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : 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 "kcurrencypage.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "icons/icons.h" #include "knewuserwizard.h" #include "knewuserwizard_p.h" #include "kaccountpage.h" #include "kaccountpage_p.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "ui_currency.h" #include "ui_kaccountpage.h" #include "wizardpage.h" using namespace Icons; namespace NewUserWizard { class CurrencyPagePrivate : public WizardPagePrivate { Q_DISABLE_COPY(CurrencyPagePrivate) public: CurrencyPagePrivate(QObject* parent) : WizardPagePrivate(parent) { } }; CurrencyPage::CurrencyPage(Wizard* wizard) : Currency(wizard), WizardPage(*new CurrencyPagePrivate(wizard), stepCount++, this, wizard) { QTreeWidgetItem *first = 0; QList list = MyMoneyFile::instance()->availableCurrencyList(); QList::const_iterator it; QString localCurrency(QLocale().currencySymbol(QLocale::CurrencyIsoCode)); QString baseCurrency = MyMoneyFile::instance()->storageAttached() ? MyMoneyFile::instance()->baseCurrency().id() : QString(); ui->m_currencyList->clear(); for (it = list.constBegin(); it != list.constEnd(); ++it) { QTreeWidgetItem* p = insertCurrency(*it); if ((*it).id() == baseCurrency) { first = p; - QIcon icon = Icons::get(Icon::ViewBankAccount); + QIcon icon = Icons::get(Icon::BankAccount); p->setIcon(0, icon); } else { p->setIcon(0, QIcon()); } if (!first && (*it).id() == localCurrency) first = p; } QTreeWidgetItemIterator itemsIt = QTreeWidgetItemIterator(ui->m_currencyList, QTreeWidgetItemIterator::All); if (first == 0) first = *itemsIt; if (first != 0) { ui->m_currencyList->setCurrentItem(first); ui->m_currencyList->setItemSelected(first, true); ui->m_currencyList->scrollToItem(first, QTreeView::PositionAtTop); } } CurrencyPage::~CurrencyPage() { } void CurrencyPage::enterPage() { ui->m_currencyList->setFocus(); } KMyMoneyWizardPage* CurrencyPage::nextPage() const { Q_D(const CurrencyPage); QString selCur = selectedCurrency(); QList currencies = MyMoneyFile::instance()->availableCurrencyList(); foreach (auto currency, currencies) { if (selCur == currency.id()) { d->m_wizard->d_func()->m_baseCurrency = currency; break; } } d->m_wizard->d_func()->m_accountPage->d_func()->ui->m_accountCurrencyLabel->setText(d->m_wizard->d_func()->m_baseCurrency.tradingSymbol()); return d->m_wizard->d_func()->m_accountPage; } }