diff --git a/kmymoney/dialogs/kmymoneysplittable.cpp b/kmymoney/dialogs/kmymoneysplittable.cpp index b9faa2497..3d6d6a27f 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 "kmymoneyedit.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->lineedit(); 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); KMyMoneyEdit* valfield = new KMyMoneyEdit(); 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 KMyMoneyEdit(0); d->m_editAmount->setFont(cellFont); d->m_editAmount->setResetButtonVisible(false); 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->loadText("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(); 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 426da4710..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::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/icons.cpp b/kmymoney/icons/icons.cpp index a53b323ba..750c20b87 100644 --- a/kmymoney/icons/icons.cpp +++ b/kmymoney/icons/icons.cpp @@ -1,558 +1,553 @@ /*************************************************************************** icons.cpp ------------------- begin : Sun Jun 25 2017 copyright : (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 "icons.h" #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; }; 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::EditFind, {{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")}}}, {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::ViewAccounts, + {Icon::Accounts, {{IconSet::Common, QStringLiteral("account")}, {IconSet::Oxygen, QStringLiteral("view-bank-account")}, {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, - {Icon::ViewAsset, + {Icon::Asset, {{IconSet::Common, QStringLiteral("account-types-asset")}, {IconSet::Oxygen, QStringLiteral("view-bank-account")}, {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, - {Icon::ViewBank, + {Icon::Bank, {{IconSet::Common, QStringLiteral("bank")}, {IconSet::Oxygen, QStringLiteral("view-bank")}, {IconSet::Breeze, QStringLiteral("view-bank")}}}, - {Icon::ViewBankAccount, + {Icon::BankAccount, {{IconSet::Common, QStringLiteral("account")}, {IconSet::Oxygen, QStringLiteral("view-bank-account")}, {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, - {Icon::ViewBudgets, - {{IconSet::Common, QStringLiteral("budget")}, - {IconSet::Oxygen, QStringLiteral("view-time-schedule-calculus")}, - {IconSet::Breeze, QStringLiteral("view-time-schedule-calculus")}}}, - {Icon::ViewCalendar, + {Icon::Calendar, {{IconSet::Common, QStringLiteral("view-calendar")}}}, - {Icon::ViewCalendarDay, + {Icon::CalendarDay, {{IconSet::Common, QStringLiteral("office-calendar")}, {IconSet::Oxygen, QStringLiteral("view-calendar-day")}, {IconSet::Breeze, QStringLiteral("view-calendar-day")}}}, - {Icon::ViewCash, + {Icon::Cash, {{IconSet::Common, QStringLiteral("account-types-cash")}}}, - {Icon::ViewCategories, - {{IconSet::Common, QStringLiteral("categories")}, - {IconSet::Oxygen, QStringLiteral("view-categories")}, - {IconSet::Breeze, QStringLiteral("view-categories")}, - {IconSet::Oxygen, QStringLiteral("view-financial-categories")}}}, - {Icon::ViewChecking, + {Icon::Checking, {{IconSet::Common, QStringLiteral("account-types-checking")}, {IconSet::Oxygen, QStringLiteral("view-bank-account-checking")}, {IconSet::Breeze, QStringLiteral("view-bank-account-checking")}}}, - {Icon::ViewClose, {{IconSet::Common, QStringLiteral("view-close")}}}, - {Icon::ViewCreditCard, + {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::ViewCurrencyList, + {Icon::Currencies, {{IconSet::Common, QStringLiteral("view-currency-list")}}}, - {Icon::ViewEquity, + {Icon::Equity, {{IconSet::Common, QStringLiteral("account")}, {IconSet::Oxygen, QStringLiteral("view-bank-account")}, {IconSet::Breeze, QStringLiteral("view-bank-account")}}}, - {Icon::ViewExpense, + {Icon::Expense, {{IconSet::Breeze, QStringLiteral("view-categories-expenditures")}, {IconSet::Common, QStringLiteral("account-types-expense")}, {IconSet::Oxygen, QStringLiteral("view-expenses-categories")}}}, - {Icon::ViewFilter, {{IconSet::Common, QStringLiteral("view-filter")}}}, - {Icon::ViewFinancialCategories, + {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::ViewFinancialList, + {Icon::Ledger, {{IconSet::Common, QStringLiteral("ledger")}, {IconSet::Oxygen, QStringLiteral("view-financial-list")}}}, - {Icon::ViewFinancialTransfer, + {Icon::Transaction, {{IconSet::Common, QStringLiteral("ledger")}, {IconSet::Oxygen, QStringLiteral("view-financial-transfer")}}}, - {Icon::ViewForecast, + {Icon::Forecast, {{IconSet::Common, QStringLiteral("forecast")}, {IconSet::Oxygen, QStringLiteral("view-financial-forecast")}}}, - {Icon::ViewHome, + {Icon::Home, {{IconSet::Common, QStringLiteral("home")}, {IconSet::Oxygen, QStringLiteral("go-home")}, {IconSet::Breeze, QStringLiteral("go-home")}}}, - {Icon::ViewIncome, + {Icon::Income, {{IconSet::Breeze, QStringLiteral("view-categories-incomes")}, {IconSet::Common, QStringLiteral("account-types-income")}, {IconSet::Oxygen, QStringLiteral("view-income-categories")}}}, - {Icon::ViewInstitutions, + {Icon::Institution, {{IconSet::Common, QStringLiteral("institution")}, {IconSet::Oxygen, QStringLiteral("view-bank")}, {IconSet::Breeze, QStringLiteral("view-bank")}}}, - {Icon::ViewInvestments, + {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::ViewLedgers, - {{IconSet::Common, QStringLiteral("ledger")}, - {IconSet::Oxygen, QStringLiteral("view-financial-list")}}}, - {Icon::ViewLiability, + {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::ViewLoan, + {Icon::Loan, {{IconSet::Common, QStringLiteral("account-types-loan")}, {IconSet::Oxygen, QStringLiteral("view-loan")}}}, - {Icon::ViewLoanAsset, + {Icon::LoanAsset, {{IconSet::Common, QStringLiteral("account-types-loan")}, {IconSet::Oxygen, QStringLiteral("view-loan-asset")}}}, - {Icon::ViewOutbox, + {Icon::OnlineJobOutbox, {{IconSet::Common, QStringLiteral("online-banking")}}}, - {Icon::ViewPayees, + {Icon::Payees, {{IconSet::Common, QStringLiteral("payee")}, {IconSet::Oxygen, QStringLiteral("system-users")}, {IconSet::Breeze, QStringLiteral("system-users")}}}, - {Icon::ViewReports, + {Icon::Reports, {{IconSet::Common, QStringLiteral("report")}, {IconSet::Oxygen, QStringLiteral("office-chart-bar")}, {IconSet::Breeze, QStringLiteral("office-chart-bar")}}}, - {Icon::ViewSaving, + {Icon::Savings, {{IconSet::Common, QStringLiteral("account-types-savings")}, {IconSet::Oxygen, QStringLiteral("view-bank-account-savings")}, {IconSet::Breeze, QStringLiteral("view-bank-account-savings")}}}, - {Icon::ViewSchedules, + {Icon::Schedule, {{IconSet::Common, QStringLiteral("schedule")}, {IconSet::Oxygen, QStringLiteral("view-pim-calendar")}, {IconSet::Breeze, QStringLiteral("view-pim-calendar")}}}, - {Icon::ViewStock, + {Icon::Stock, {{IconSet::Common, QStringLiteral("account-types-investments")}, {IconSet::Oxygen, QStringLiteral("view-stock-account")}}}, - {Icon::ViewTags, + {Icon::Tags, {{IconSet::Common, QStringLiteral("bookmark-new")}, {IconSet::Oxygen, QStringLiteral("mail-tagged")}, {IconSet::Breeze, QStringLiteral("mail-tagged")}}}, - {Icon::ViewTransactionDetail, + {Icon::TransactionDetails, {{IconSet::Common, QStringLiteral("edit-find")}, {IconSet::Oxygen, QStringLiteral("zoom-in")}, {IconSet::Breeze, QStringLiteral("zoom-in")}}}, - {Icon::ViewUpcominEvents, + {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::EditFindTransaction, {Icon::Transaction, Icon::EditFind, Qt::BottomRightCorner}}, + {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::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::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::InvestmentOnlinePriceAll, {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}} }; KMM_ICONS_EXPORT void setUpMappings(const QString& themeName) { 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; } } } } /** * 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 70f6a8ed3..b0c846741 100644 --- a/kmymoney/icons/icons.h +++ b/kmymoney/icons/icons.h @@ -1,133 +1,133 @@ /*************************************************************************** icons.h ------------------- begin : Sun Jun 25 2017 copyright : (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 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, 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, Warning, TaskComplete, TaskReject, TaskAccepted, TaskOngoing, Help, Folder, PreferencesFonts, PreferencesColors, PreferencesIcons, PreferencesNetwork, PreferencesPlugins, Empty, EditFindTransaction, InstitutionNew, InstitutionEdit, InstitutionDelete, AccountNew, AccountEdit, AccountDelete, AccountClose, AccountReopen, AccountUpdate, AccountUpdateAll, AccountCreditTransfer, AccountFinishReconciliation, CategoryNew, CategoryEdit, CategoryDelete, TransactionNew, TransactionEdit, TransactionMatch, TransactionAccept, InvestmentNew, InvestmentEdit, InvestmentDelete, InvestmentOnlinePrice, BudgetNew, BudgetRename, BudgetDelete, BudgetCopy, PriceUpdate, InvestmentOnlinePriceAll, Reverse, Visibility, NoVisibility, SelectAll }; 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 e5c5342e2..042bdc2ce 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,3679 +1,3679 @@ /*************************************************************************** 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 #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif #ifdef KF5Activities_FOUND #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/kmymoneyedit.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 KF5Holidays_FOUND m_holidayRegion(nullptr), #endif #ifdef KF5Activities_FOUND 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 KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif #ifdef KF5Activities_FOUND 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 KF5Activities_FOUND 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 KF5Holidays_FOUND delete d->m_holidayRegion; #endif #ifdef KF5Activities_FOUND 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}, // ************* // 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::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::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) auto themeName = QStringLiteral("breeze"); // only breeze is available for craft packages #else 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); #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 errneous 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 somwhere 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 KF5Holidays_FOUND //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 KF5Holidays_FOUND 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 KF5Holidays_FOUND // 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())); KMyMoneyEdit::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 KF5Activities_FOUND { // 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/kmymoneyutils.cpp b/kmymoney/kmymoneyutils.cpp index 89d6dcb2a..c36b41851 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 . delminator 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 our's 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 9ddd2575c..2eeb62b7b 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 compatable 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 woth 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 023d515f8..b395c1bce 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 f0ae76c39..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); + pxIcon = Icons::get(Icon::Institution).pixmap(size); QPixmapCache::insert(kyIcon, pxIcon); } return pxIcon; } 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/reports/kreportsview_p.h b/kmymoney/plugins/views/reports/kreportsview_p.h index 5e650b231..3ef4a8bef 100644 --- a/kmymoney/plugins/views/reports/kreportsview_p.h +++ b/kmymoney/plugins/views/reports/kreportsview_p.h @@ -1,1483 +1,1483 @@ /*************************************************************************** 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 ***************************************************************************/ /*************************************************************************** * * * 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 ploted 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 dimmensions of ploted 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::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 d7135d9be..e4bbea11a 100644 --- a/kmymoney/views/khomeview_p.h +++ b/kmymoney/views/khomeview_p.h @@ -1,1860 +1,1860 @@ /*************************************************************************** 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 #ifdef ENABLE_WEBENGINE #include #else #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #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 8f027c511..243cb53d4 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,868 +1,868 @@ /*************************************************************************** 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 #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 "amountedit.h" #include "onlinejobadministration.h" #include "kmymoneyaccounttreeview.h" #include "accountsviewproxymodel.h" #include "mymoneyprice.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneyaccount.h" #include "mymoneyinstitution.h" #include "mymoneytag.h" #include "kmymoneyedit.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/registersearchline.cpp b/kmymoney/widgets/registersearchline.cpp index 7de87c3e1..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::Warning), i18n("Erroneous")); - d->combo->insertItem((int)eRegister::ItemState::Scheduled, Icons::get(Icon::ViewSchedules), i18n("Scheduled")); + 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; } }