diff --git a/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp b/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp index 1299f499f..62325d1c8 100644 --- a/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp +++ b/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp @@ -1,667 +1,667 @@ /*************************************************************************** kreportconfigurationdlg.cpp - description ------------------- begin : Mon Jun 21 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 ***************************************************************************/ /*************************************************************************** * * * 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 "kreportconfigurationfilterdlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include #include #include #include #include "ui_kfindtransactiondlgdecl.h" KReportConfigurationFilterDlg::KReportConfigurationFilterDlg( MyMoneyReport report, QWidget *parent) : KFindTransactionDlg(parent, report.rowType() == MyMoneyReport::eAccount), m_tab2(0), m_tab3(0), m_tabChart(0), m_initialState(report), m_currentState(report) { // // Rework labeling // setWindowTitle(i18n("Report Configuration")); delete m_ui->TextLabel1; // // Rework the buttons // // the Apply button is always enabled disconnect(SIGNAL(selectionNotEmpty(bool))); enableButtonApply(true); setButtonGuiItem(KDialog::Apply, KStandardGuiItem::ok()); setButtonToolTip(KDialog::Apply, i18nc("@info:tooltip for report configuration apply button", "Apply the configuration changes to the report")); // // Add new tabs // m_tab1 = new kMyMoneyReportConfigTab1Decl(m_ui->m_criteriaTab); m_tab1->setObjectName("kMyMoneyReportConfigTab1"); m_ui->m_criteriaTab->insertTab(0, m_tab1, i18n("Report")); if (m_initialState.reportType() == MyMoneyReport::ePivotTable) { m_tab2 = new kMyMoneyReportConfigTab2Decl(m_ui->m_criteriaTab); m_tab2->setObjectName("kMyMoneyReportConfigTab2"); m_ui->m_criteriaTab->insertTab(1, m_tab2, i18n("Rows/Columns")); connect(m_tab2->findChild("m_comboRows"), SIGNAL(activated(int)), this, SLOT(slotRowTypeChanged(int))); connect(m_tab2->findChild("m_comboColumns"), SIGNAL(activated(int)), this, SLOT(slotColumnTypeChanged(int))); connect(m_tab2->findChild("m_comboRows"), SIGNAL(activated(int)), this, SLOT(slotUpdateColumnsCombo())); connect(m_tab2->findChild("m_comboColumns"), SIGNAL(activated(int)), this, SLOT(slotUpdateColumnsCombo())); //control the state of the includeTransfer check connect(m_ui->m_categoriesView, SIGNAL(stateChanged()), this, SLOT(slotUpdateCheckTransfers())); m_tabChart = new kMyMoneyReportConfigTabChartDecl(m_ui->m_criteriaTab); m_tabChart->setObjectName("kMyMoneyReportConfigTabChart"); m_ui->m_criteriaTab->insertTab(2, m_tabChart, i18n("Chart")); } else if (m_initialState.reportType() == MyMoneyReport::eQueryTable) { // eInvestmentHoldings is a special-case report, and you cannot configure the // rows & columns of that report. if (m_initialState.rowType() < MyMoneyReport::eAccountByTopAccount) { m_tab3 = new kMyMoneyReportConfigTab3Decl(m_ui->m_criteriaTab); m_tab3->setObjectName("kMyMoneyReportConfigTab3"); m_ui->m_criteriaTab->insertTab(1, m_tab3, i18n("Rows/Columns")); } } m_ui->m_criteriaTab->setCurrentIndex(m_ui->m_criteriaTab->indexOf(m_tab1)); m_ui->m_criteriaTab->setMinimumSize(500, 200); QList list = MyMoneyFile::instance()->budgetList(); QList::const_iterator it_b; for (it_b = list.constBegin(); it_b != list.constEnd(); ++it_b) { m_budgets.push_back(*it_b); } // // Now set up the widgets with proper values // slotReset(); } KReportConfigurationFilterDlg::~KReportConfigurationFilterDlg() { } void KReportConfigurationFilterDlg::slotSearch() { // setup the filter from the dialog widgets setupFilter(); // Copy the m_filter over to the filter part of m_currentConfig. m_currentState.assignFilter(m_filter); // Then extract the report properties m_currentState.setName(m_tab1->findChild("m_editName")->text()); m_currentState.setComment(m_tab1->findChild("m_editComment")->text()); m_currentState.setConvertCurrency(m_tab1->findChild("m_checkCurrency")->isChecked()); m_currentState.setFavorite(m_tab1->findChild("m_checkFavorite")->isChecked()); m_currentState.setSkipZero(m_tab1->findChild("m_skipZero")->isChecked()); if (m_tab2) { MyMoneyReport::EDetailLevel dl[4] = { MyMoneyReport::eDetailAll, MyMoneyReport::eDetailTop, MyMoneyReport::eDetailGroup, MyMoneyReport::eDetailTotal }; m_currentState.setDetailLevel(dl[m_tab2->findChild("m_comboDetail")->currentIndex()]); // modify the rowtype only if the widget is enabled if (m_tab2->findChild("m_comboRows")->isEnabled()) { MyMoneyReport::ERowType rt[2] = { MyMoneyReport::eExpenseIncome, MyMoneyReport::eAssetLiability }; m_currentState.setRowType(rt[m_tab2->findChild("m_comboRows")->currentIndex()]); } m_currentState.setShowingRowTotals(false); if (m_tab2->findChild("m_comboRows")->currentIndex() == 0) m_currentState.setShowingRowTotals(m_tab2->findChild("m_checkTotalColumn")->isChecked()); MyMoneyReport::Column::Type ct[6] = { MyMoneyReport::Column::Days, MyMoneyReport::Column::Weeks, MyMoneyReport::Column::Months, MyMoneyReport::Column::BiMonths, MyMoneyReport::Column::Quarters, MyMoneyReport::Column::Years }; bool dy[6] = { true, true, false, false, false, false }; m_currentState.setColumnType(ct[m_tab2->findChild("m_comboColumns")->currentIndex()]); //TODO (Ace) This should be implicit in the call above. MMReport needs fixin' m_currentState.setColumnsAreDays(dy[m_tab2->findChild("m_comboColumns")->currentIndex()]); m_currentState.setIncludingSchedules(m_tab2->findChild("m_checkScheduled")->isChecked()); m_currentState.setIncludingTransfers(m_tab2->findChild("m_checkTransfers")->isChecked()); m_currentState.setIncludingUnusedAccounts(m_tab2->findChild("m_checkUnused")->isChecked()); if (m_tab2->findChild("m_comboBudget")->isEnabled()) { m_currentState.setBudget(m_budgets[m_tab2->findChild("m_comboBudget")->currentItem()].id(), m_initialState.rowType() == MyMoneyReport::eBudgetActual); } else { m_currentState.setBudget(QString(), false); } //set moving average days if (m_tab2->findChild("m_movingAverageDays")->isEnabled()) { m_currentState.setMovingAverageDays(m_tab2->findChild("m_movingAverageDays")->value()); } } else if (m_tab3) { MyMoneyReport::ERowType rtq[8] = { MyMoneyReport::eCategory, MyMoneyReport::eTopCategory, MyMoneyReport::eTag, MyMoneyReport::ePayee, MyMoneyReport::eAccount, MyMoneyReport::eTopAccount, MyMoneyReport::eMonth, MyMoneyReport::eWeek }; m_currentState.setRowType(rtq[m_tab3->findChild("m_comboOrganizeBy")->currentIndex()]); - unsigned qc = MyMoneyReport::eQCnone; + unsigned qc = MyMoneyReport::QueryColumns::None; - if (m_currentState.queryColumns() & MyMoneyReport::eQCloan) + if (m_currentState.queryColumns() & MyMoneyReport::QueryColumns::Loan) // once a loan report, always a loan report - qc = MyMoneyReport::eQCloan; + qc = MyMoneyReport::QueryColumns::Loan; if (m_tab3->findChild("m_checkNumber")->isChecked()) - qc |= MyMoneyReport::eQCnumber; + qc |= MyMoneyReport::QueryColumns::Number; if (m_tab3->findChild("m_checkPayee")->isChecked()) - qc |= MyMoneyReport::eQCpayee; + qc |= MyMoneyReport::QueryColumns::Payee; if (m_tab3->findChild("m_checkTag")->isChecked()) - qc |= MyMoneyReport::eQCtag; + qc |= MyMoneyReport::QueryColumns::Tag; if (m_tab3->findChild("m_checkCategory")->isChecked()) - qc |= MyMoneyReport::eQCcategory; + qc |= MyMoneyReport::QueryColumns::Category; if (m_tab3->findChild("m_checkMemo")->isChecked()) - qc |= MyMoneyReport::eQCmemo; + qc |= MyMoneyReport::QueryColumns::Memo; if (m_tab3->findChild("m_checkAccount")->isChecked()) - qc |= MyMoneyReport::eQCaccount; + qc |= MyMoneyReport::QueryColumns::Account; if (m_tab3->findChild("m_checkReconciled")->isChecked()) - qc |= MyMoneyReport::eQCreconciled; + qc |= MyMoneyReport::QueryColumns::Reconciled; if (m_tab3->findChild("m_checkAction")->isChecked()) - qc |= MyMoneyReport::eQCaction; + qc |= MyMoneyReport::QueryColumns::Action; if (m_tab3->findChild("m_checkShares")->isChecked()) - qc |= MyMoneyReport::eQCshares; + qc |= MyMoneyReport::QueryColumns::Shares; if (m_tab3->findChild("m_checkPrice")->isChecked()) - qc |= MyMoneyReport::eQCprice; + qc |= MyMoneyReport::QueryColumns::Price; if (m_tab3->findChild("m_checkBalance")->isChecked()) - qc |= MyMoneyReport::eQCbalance; + qc |= MyMoneyReport::QueryColumns::Balance; - m_currentState.setQueryColumns(static_cast(qc)); + m_currentState.setQueryColumns(static_cast(qc)); m_currentState.setTax(m_tab3->findChild("m_checkTax")->isChecked()); m_currentState.setInvestmentsOnly(m_tab3->findChild("m_checkInvestments")->isChecked()); m_currentState.setLoansOnly(m_tab3->findChild("m_checkLoans")->isChecked()); m_currentState.setDetailLevel(m_tab3->findChild("m_checkHideSplitDetails")->isChecked() ? MyMoneyReport::eDetailNone : MyMoneyReport::eDetailAll); } if (m_tabChart) { MyMoneyReport::EChartType ct[5] = { MyMoneyReport::eChartLine, MyMoneyReport::eChartBar, MyMoneyReport::eChartStackedBar, MyMoneyReport::eChartPie, MyMoneyReport::eChartRing }; m_currentState.setChartType(ct[m_tabChart->findChild("m_comboType")->currentIndex()]); m_currentState.setChartGridLines(m_tabChart->findChild("m_checkGridLines")->isChecked()); m_currentState.setChartDataLabels(m_tabChart->findChild("m_checkValues")->isChecked()); m_currentState.setChartByDefault(m_tabChart->findChild("m_checkShowChart")->isChecked()); m_currentState.setChartLineWidth(m_tabChart->findChild("m_lineWidth")->value()); } // setup the date lock MyMoneyTransactionFilter::dateOptionE range = m_ui->m_dateRange->currentItem(); m_currentState.setDateFilter(range); done(true); } void KReportConfigurationFilterDlg::slotRowTypeChanged(int row) { m_tab2->findChild("m_checkTotalColumn")->setEnabled(row == 0); } void KReportConfigurationFilterDlg::slotColumnTypeChanged(int row) { if ((m_tab2->findChild("m_comboBudget")->isEnabled() && row < 2)) { m_tab2->findChild("m_comboColumns")->setCurrentItem(i18nc("@item the columns will display monthly data", "Monthly"), false); } } void KReportConfigurationFilterDlg::slotUpdateColumnsCombo() { const int monthlyIndex = 2; const int incomeExpenseIndex = 0; const bool isIncomeExpenseForecast = m_currentState.isIncludingForecast() && m_tab2->findChild("m_comboRows")->currentIndex() == incomeExpenseIndex; if (isIncomeExpenseForecast && m_tab2->findChild("m_comboColumns")->currentIndex() != monthlyIndex) { m_tab2->findChild("m_comboColumns")->setCurrentItem(i18nc("@item the columns will display monthly data", "Monthly"), false); } } void KReportConfigurationFilterDlg::slotReset() { // // Set up the widget from the initial filter // m_currentState = m_initialState; // // Report Properties // m_tab1->findChild("m_editName")->setText(m_initialState.name()); m_tab1->findChild("m_editComment")->setText(m_initialState.comment()); m_tab1->findChild("m_checkCurrency")->setChecked(m_initialState.isConvertCurrency()); m_tab1->findChild("m_checkFavorite")->setChecked(m_initialState.isFavorite()); if (m_initialState.isIncludingPrice() || m_initialState.isSkippingZero()) { m_tab1->findChild("m_skipZero")->setChecked(m_initialState.isSkippingZero()); } else { m_tab1->findChild("m_skipZero")->setEnabled(false); } if (m_tab2) { KComboBox *combo = m_tab2->findChild("m_comboDetail"); switch (m_initialState.detailLevel()) { case MyMoneyReport::eDetailNone: case MyMoneyReport::eDetailEnd: case MyMoneyReport::eDetailAll: combo->setCurrentItem(i18nc("All accounts", "All"), false); break; case MyMoneyReport::eDetailTop: combo->setCurrentItem(i18n("Top-Level"), false); break; case MyMoneyReport::eDetailGroup: combo->setCurrentItem(i18n("Groups"), false); break; case MyMoneyReport::eDetailTotal: combo->setCurrentItem(i18n("Totals"), false); break; } combo = m_tab2->findChild("m_comboRows"); switch (m_initialState.rowType()) { case MyMoneyReport::eExpenseIncome: case MyMoneyReport::eBudget: case MyMoneyReport::eBudgetActual: combo->setCurrentItem(i18n("Income & Expenses"), false); // income / expense break; default: combo->setCurrentItem(i18n("Assets & Liabilities"), false); // asset / liability break; } m_tab2->findChild("m_checkTotalColumn")->setChecked(m_initialState.isShowingRowTotals()); slotRowTypeChanged(combo->currentIndex()); combo = m_tab2->findChild("m_comboColumns"); if (m_initialState.isColumnsAreDays()) { switch (m_initialState.columnType()) { case MyMoneyReport::Column::NoColumns: case MyMoneyReport::Column::Days: combo->setCurrentItem(i18nc("@item the columns will display daily data", "Daily"), false); break; case MyMoneyReport::Column::Weeks: combo->setCurrentItem(i18nc("@item the columns will display weekly data", "Weekly"), false); break; default: break; } } else { switch (m_initialState.columnType()) { case MyMoneyReport::Column::NoColumns: case MyMoneyReport::Column::Months: combo->setCurrentItem(i18nc("@item the columns will display monthly data", "Monthly"), false); break; case MyMoneyReport::Column::BiMonths: combo->setCurrentItem(i18nc("@item the columns will display bi-monthly data", "Bi-Monthly"), false); break; case MyMoneyReport::Column::Quarters: combo->setCurrentItem(i18nc("@item the columns will display quarterly data", "Quarterly"), false); break; case MyMoneyReport::Column::Years: combo->setCurrentItem(i18nc("@item the columns will display yearly data", "Yearly"), false); break; default: break; } } //load budgets combo if (m_initialState.rowType() == MyMoneyReport::eBudget || m_initialState.rowType() == MyMoneyReport::eBudgetActual) { m_tab2->findChild("m_comboRows")->setEnabled(false); m_tab2->findChild("m_budgetFrame")->setEnabled(!m_budgets.empty()); int i = 0; for (QVector::const_iterator it_b = m_budgets.constBegin(); it_b != m_budgets.constEnd(); ++it_b) { m_tab2->findChild("m_comboBudget")->insertItem((*it_b).name(), i); //set the current selected item if ((m_initialState.budget() == "Any" && (*it_b).budgetStart().year() == QDate::currentDate().year()) || m_initialState.budget() == (*it_b).id()) m_tab2->findChild("m_comboBudget")->setCurrentItem(i); i++; } } //set moving average days spinbox QSpinBox *spinbox = m_tab2->findChild("m_movingAverageDays"); spinbox->setEnabled(m_initialState.isIncludingMovingAverage()); if (m_initialState.isIncludingMovingAverage()) { spinbox->setValue(m_initialState.movingAverageDays()); } m_tab2->findChild("m_checkScheduled")->setChecked(m_initialState.isIncludingSchedules()); m_tab2->findChild("m_checkTransfers")->setChecked(m_initialState.isIncludingTransfers()); m_tab2->findChild("m_checkUnused")->setChecked(m_initialState.isIncludingUnusedAccounts()); } else if (m_tab3) { KComboBox *combo = m_tab3->findChild("m_comboOrganizeBy"); switch (m_initialState.rowType()) { case MyMoneyReport::Column::NoColumns: case MyMoneyReport::eCategory: combo->setCurrentItem(i18n("Categories"), false); break; case MyMoneyReport::eTopCategory: combo->setCurrentItem(i18n("Top Categories"), false); break; case MyMoneyReport::eTag: combo->setCurrentItem(i18n("Tags"), false); break; case MyMoneyReport::ePayee: combo->setCurrentItem(i18n("Payees"), false); break; case MyMoneyReport::eAccount: combo->setCurrentItem(i18n("Accounts"), false); break; case MyMoneyReport::eTopAccount: combo->setCurrentItem(i18n("Top Accounts"), false); break; case MyMoneyReport::eMonth: combo->setCurrentItem(i18n("Month"), false); break; case MyMoneyReport::eWeek: combo->setCurrentItem(i18n("Week"), false); break; default: throw MYMONEYEXCEPTION("KReportConfigurationFilterDlg::slotReset(): QueryTable report has invalid rowtype"); } unsigned qc = m_initialState.queryColumns(); - m_tab3->findChild("m_checkNumber")->setChecked(qc & MyMoneyReport::eQCnumber); - m_tab3->findChild("m_checkPayee")->setChecked(qc & MyMoneyReport::eQCpayee); - m_tab3->findChild("m_checkTag")->setChecked(qc & MyMoneyReport::eQCtag); - m_tab3->findChild("m_checkCategory")->setChecked(qc & MyMoneyReport::eQCcategory); - m_tab3->findChild("m_checkMemo")->setChecked(qc & MyMoneyReport::eQCmemo); - m_tab3->findChild("m_checkAccount")->setChecked(qc & MyMoneyReport::eQCaccount); - m_tab3->findChild("m_checkReconciled")->setChecked(qc & MyMoneyReport::eQCreconciled); - m_tab3->findChild("m_checkAction")->setChecked(qc & MyMoneyReport::eQCaction); - m_tab3->findChild("m_checkShares")->setChecked(qc & MyMoneyReport::eQCshares); - m_tab3->findChild("m_checkPrice")->setChecked(qc & MyMoneyReport::eQCprice); - m_tab3->findChild("m_checkBalance")->setChecked(qc & MyMoneyReport::eQCbalance); + m_tab3->findChild("m_checkNumber")->setChecked(qc & MyMoneyReport::QueryColumns::Number); + m_tab3->findChild("m_checkPayee")->setChecked(qc & MyMoneyReport::QueryColumns::Payee); + m_tab3->findChild("m_checkTag")->setChecked(qc & MyMoneyReport::QueryColumns::Tag); + m_tab3->findChild("m_checkCategory")->setChecked(qc & MyMoneyReport::QueryColumns::Category); + m_tab3->findChild("m_checkMemo")->setChecked(qc & MyMoneyReport::QueryColumns::Memo); + m_tab3->findChild("m_checkAccount")->setChecked(qc & MyMoneyReport::QueryColumns::Account); + m_tab3->findChild("m_checkReconciled")->setChecked(qc & MyMoneyReport::QueryColumns::Reconciled); + m_tab3->findChild("m_checkAction")->setChecked(qc & MyMoneyReport::QueryColumns::Action); + m_tab3->findChild("m_checkShares")->setChecked(qc & MyMoneyReport::QueryColumns::Shares); + m_tab3->findChild("m_checkPrice")->setChecked(qc & MyMoneyReport::QueryColumns::Price); + m_tab3->findChild("m_checkBalance")->setChecked(qc & MyMoneyReport::QueryColumns::Balance); m_tab3->findChild("m_checkTax")->setChecked(m_initialState.isTax()); m_tab3->findChild("m_checkInvestments")->setChecked(m_initialState.isInvestmentsOnly()); m_tab3->findChild("m_checkLoans")->setChecked(m_initialState.isLoansOnly()); m_tab3->findChild("m_checkHideSplitDetails")->setChecked (m_initialState.detailLevel() == MyMoneyReport::eDetailNone); } if (m_tabChart) { KMyMoneyGeneralCombo* combo = m_tabChart->findChild("m_comboType"); switch (m_initialState.chartType()) { case MyMoneyReport::eChartNone: combo->setCurrentItem(MyMoneyReport::eChartLine); break; case MyMoneyReport::eChartLine: case MyMoneyReport::eChartBar: case MyMoneyReport::eChartStackedBar: case MyMoneyReport::eChartPie: case MyMoneyReport::eChartRing: combo->setCurrentItem(m_initialState.chartType()); break; case MyMoneyReport::eChartEnd: throw MYMONEYEXCEPTION("KReportConfigurationFilterDlg::slotReset(): Report has invalid charttype"); } m_tabChart->findChild("m_checkGridLines")->setChecked(m_initialState.isChartGridLines()); m_tabChart->findChild("m_checkValues")->setChecked(m_initialState.isChartDataLabels()); m_tabChart->findChild("m_checkShowChart")->setChecked(m_initialState.isChartByDefault()); m_tabChart->findChild("m_lineWidth")->setValue(m_initialState.chartLineWidth()); } // // Text Filter // QRegExp textfilter; if (m_initialState.textFilter(textfilter)) { m_ui->m_textEdit->setText(textfilter.pattern()); m_ui->m_caseSensitive->setChecked(Qt::CaseSensitive == textfilter.caseSensitivity()); m_ui->m_regExp->setChecked(QRegExp::RegExp == textfilter.patternSyntax()); m_ui->m_textNegate->setCurrentIndex(m_initialState.isInvertingText()); } // // Type & State Filters // int type; if (m_initialState.firstType(type)) m_ui->m_typeBox->setCurrentIndex(type); int state; if (m_initialState.firstState(state)) m_ui->m_stateBox->setCurrentIndex(state); // // Number Filter // QString nrFrom, nrTo; if (m_initialState.numberFilter(nrFrom, nrTo)) { if (nrFrom == nrTo) { m_ui->m_nrEdit->setEnabled(true); m_ui->m_nrFromEdit->setEnabled(false); m_ui->m_nrToEdit->setEnabled(false); m_ui->m_nrEdit->setText(nrFrom); m_ui->m_nrFromEdit->setText(QString()); m_ui->m_nrToEdit->setText(QString()); m_ui->m_nrButton->setChecked(true); m_ui->m_nrRangeButton->setChecked(false); } else { m_ui->m_nrEdit->setEnabled(false); m_ui->m_nrFromEdit->setEnabled(true); m_ui->m_nrToEdit->setEnabled(false); m_ui->m_nrEdit->setText(QString()); m_ui->m_nrFromEdit->setText(nrFrom); m_ui->m_nrToEdit->setText(nrTo); m_ui->m_nrButton->setChecked(false); m_ui->m_nrRangeButton->setChecked(true); } } else { m_ui->m_nrEdit->setEnabled(true); m_ui->m_nrFromEdit->setEnabled(false); m_ui->m_nrToEdit->setEnabled(false); m_ui->m_nrEdit->setText(QString()); m_ui->m_nrFromEdit->setText(QString()); m_ui->m_nrToEdit->setText(QString()); m_ui->m_nrButton->setChecked(true); m_ui->m_nrRangeButton->setChecked(false); } // // Amount Filter // MyMoneyMoney from, to; if (m_initialState.amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&); if (from == to) { m_ui->m_amountEdit->setEnabled(true); m_ui->m_amountFromEdit->setEnabled(false); m_ui->m_amountToEdit->setEnabled(false); m_ui->m_amountEdit->loadText(QString::number(from.toDouble())); m_ui->m_amountFromEdit->loadText(QString()); m_ui->m_amountToEdit->loadText(QString()); m_ui->m_amountButton->setChecked(true); m_ui->m_amountRangeButton->setChecked(false); } else { m_ui->m_amountEdit->setEnabled(false); m_ui->m_amountFromEdit->setEnabled(true); m_ui->m_amountToEdit->setEnabled(true); m_ui->m_amountEdit->loadText(QString()); m_ui->m_amountFromEdit->loadText(QString::number(from.toDouble())); m_ui->m_amountToEdit->loadText(QString::number(to.toDouble())); m_ui->m_amountButton->setChecked(false); m_ui->m_amountRangeButton->setChecked(true); } } else { m_ui->m_amountEdit->setEnabled(true); m_ui->m_amountFromEdit->setEnabled(false); m_ui->m_amountToEdit->setEnabled(false); m_ui->m_amountEdit->loadText(QString()); m_ui->m_amountFromEdit->loadText(QString()); m_ui->m_amountToEdit->loadText(QString()); m_ui->m_amountButton->setChecked(true); m_ui->m_amountRangeButton->setChecked(false); } // // Payees Filter // QStringList payees; if (m_initialState.payees(payees)) { if (payees.empty()) { m_ui->m_emptyPayeesButton->setChecked(true); } else { selectAllItems(m_ui->m_payeesView, false); selectItems(m_ui->m_payeesView, payees, true); } } else { selectAllItems(m_ui->m_payeesView, true); } // // Tags Filter // QStringList tags; if (m_initialState.tags(tags)) { if (tags.empty()) { m_ui->m_emptyTagsButton->setChecked(true); } else { selectAllItems(m_ui->m_tagsView, false); selectItems(m_ui->m_tagsView, tags, true); } } else { selectAllItems(m_ui->m_tagsView, true); } // // Accounts Filter // QStringList accounts; if (m_initialState.accounts(accounts)) { m_ui->m_accountsView->selectAllItems(false); m_ui->m_accountsView->selectItems(accounts, true); } else m_ui->m_accountsView->selectAllItems(true); // // Categories Filter // if (m_initialState.categories(accounts)) { m_ui->m_categoriesView->selectAllItems(false); m_ui->m_categoriesView->selectItems(accounts, true); } else m_ui->m_categoriesView->selectAllItems(true); // // Date Filter // // the following call implies a call to slotUpdateSelections, // that's why we call it last m_initialState.updateDateFilter(); QDate dateFrom, dateTo; if (m_initialState.dateFilter(dateFrom, dateTo)) { if (m_initialState.isUserDefined()) { m_ui->m_dateRange->setCurrentItem(MyMoneyTransactionFilter::userDefined); m_ui->m_fromDate->setDate(dateFrom); m_ui->m_toDate->setDate(dateTo); } else { m_ui->m_fromDate->setDate(dateFrom); m_ui->m_toDate->setDate(dateTo); KFindTransactionDlg::slotDateChanged(); } } else { m_ui->m_dateRange->setCurrentItem(MyMoneyTransactionFilter::allDates); slotDateRangeChanged(MyMoneyTransactionFilter::allDates); } slotRightSize(); } void KReportConfigurationFilterDlg::slotDateChanged() { if (m_ui->m_dateRange->currentItem() != MyMoneyTransactionFilter::userDefined) { KFindTransactionDlg::slotDateChanged(); } slotUpdateSelections(); } void KReportConfigurationFilterDlg::slotShowHelp() { KToolInvocation::invokeHelp("details.reports.config"); } //TODO Fix the reports and engine to include transfers even if categories are filtered - bug #1523508 void KReportConfigurationFilterDlg::slotUpdateCheckTransfers() { QCheckBox* cb = m_tab2->findChild("m_checkTransfers"); if (!m_ui->m_categoriesView->allItemsSelected()) { cb->setChecked(false); cb->setDisabled(true); } else { cb->setEnabled(true); } } diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index aaf3cc5a6..816dcc342 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7874 +1,7874 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoney.h" // for _getpid #ifdef Q_OS_WIN32 //krazy:exclude=cpp #include #else #if HAVE_SYS_TYPES_H #include #endif #if HAVE_UNISTD_H #include #endif #endif // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // only for performance tests #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 #include #include #include #include #include #include #include #include #ifdef HAVE_KDEPIMLIBS #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingsgeneral.h" #include "dialogs/settings/ksettingsregister.h" #include "dialogs/settings/ksettingsgpg.h" #include "dialogs/settings/ksettingscolors.h" #include "dialogs/settings/ksettingsfonts.h" #include "dialogs/settings/ksettingsschedules.h" #include "dialogs/settings/ksettingsonlinequotes.h" #include "dialogs/settings/ksettingshome.h" #include "dialogs/settings/ksettingsforecast.h" #include "dialogs/settings/ksettingsplugins.h" #include "dialogs/settings/ksettingsreports.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kexportdlg.h" #include "dialogs/kimportdlg.h" #include "dialogs/mymoneyqifprofileeditor.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/kfindtransactiondlg.h" #include "dialogs/knewbankdlg.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/knewfiledlg.h" #include "dialogs/kselectdatabasedlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/knewloanwizard.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/ktagreassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "dialogs/kmergetransactionsdlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kgeneratesqldlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/kgpgkeyselectiondlg.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/onlinejobmessagesview.h" #include "widgets/kmymoneymvccombo.h" #include "widgets/kmymoneycompletion.h" #include "views/kmymoneyview.h" #include "views/konlinejoboutbox.h" #include "models/onlinejobmessagesmodel.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/storage/mymoneystoragedump.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/onlinejob.h" #include "mymoney/onlinetransfer.h" #include "mymoney/onlinejobadministration.h" #include "converter/mymoneyqifwriter.h" #include "converter/mymoneyqifreader.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/pluginloader.h" #include "plugins/onlinepluginextended.h" #include #include #include "konlinetransferform.h" #include #include "kmymoneyutils.h" #define RECOVER_KEY_ID "59B0F826D2B08440" // define the default period to warn about an expiring recoverkey to 30 days // but allows to override this setting during build time #ifndef RECOVER_KEY_EXPIRATION_WARNING #define RECOVER_KEY_EXPIRATION_WARNING 30 #endif #define ID_STATUS_MSG 1 enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), m_ft(0), m_moveToAccountSelector(0), m_statementXMLindex(0), m_balanceWarning(0), m_collectingStatements(false), m_pluginLoader(0), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(0), m_progressBar(0), m_qifReader(0), m_smtReader(0), m_searchDlg(0), m_autoSaveTimer(0), m_progressTimer(0), m_inAutoSaving(false), m_transactionEditor(0), m_endingBalanceDlg(0), m_saveEncrypted(0), m_additionalKeyLabel(0), m_additionalKeyButton(0), m_recentFiles(0), #ifdef HAVE_KDEPIMLIBS m_holidayRegion(0), #endif m_applicationIsReady(true) { // 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 closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); /** * The public interface. */ KMyMoneyApp * const q; MyMoneyFileTransaction* m_ft; kMyMoneyAccountSelector* m_moveToAccountSelector; int m_statementXMLindex; KBalanceWarning* m_balanceWarning; bool m_collectingStatements; QStringList m_statementResults; KMyMoneyPlugin::PluginLoader* m_pluginLoader; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; QMap m_importerPlugins; QMap m_onlinePlugins; /** * 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; /** * This variable keeps the name of the backup file. */ QString m_backupFile; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; /// The URL of the file currently being edited when open. KUrl m_fileName; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; MyMoneyQifReader* m_qifReader; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; QObject* m_pluginInterface; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneyAccount m_selectedInvestment; MyMoneyInstitution m_selectedInstitution; MyMoneySchedule m_selectedSchedule; MyMoneySecurity m_selectedCurrency; MyMoneyPrice m_selectedPrice; QList m_selectedPayees; QList m_selectedTags; QList m_selectedBudgets; KMyMoneyRegister::SelectedTransactions m_selectedTransactions; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // pointer to the current transaction editor TransactionEditor* m_transactionEditor; // Reconciliation dialog KEndingBalanceDlg* m_endingBalanceDlg; // Pointer to the combo box used for key selection during // File/Save as KComboBox* m_saveEncrypted; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; QStringList m_additionalGpgKeys; QLabel* m_additionalKeyLabel; KPushButton* m_additionalKeyButton; KRecentFilesAction* m_recentFiles; #ifdef HAVE_KDEPIMLIBS // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; // methods void consistencyCheck(bool alwaysDisplayResults); void setCustomColors(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney", QDBusConnectionInterface::DontQueueService); // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KGlobal::config(); d->setCustomColors(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); updateCaption(true); QFrame* frame = new QFrame(this); 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); initStatusBar(); initActions(); initDynamicMenus(); d->m_myMoneyView = new KMyMoneyView(frame); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, SIGNAL(aboutToChangeView()), this, SLOT(slotResetSelections())); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connectActionsAndViews(); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); loadPlugins(); 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(action("view_show_transaction_detail"), SIGNAL(toggled(bool)), d->m_myMoneyView, SLOT(slotShowTransactionDetail(bool))); d->m_backupState = BACKUP_IDLE; int weekStart = KGlobal::locale()->workingWeekStartDay(); int weekEnd = KGlobal::locale()->workingWeekEndDay(); bool startFirst = (weekStart < weekEnd); for (int i = 0; i < 8; i++) { if (startFirst) d->m_processingDays.setBit(i, (i >= weekStart && i <= weekEnd)); else d->m_processingDays.setBit(i, (i >= weekStart || i <= weekEnd)); } 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())); // make sure, we get a note when the engine changes state connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotDataChanged())); // make sure we have a balance warning object d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(); // kickstart date change timer slotDateChanged(); connect(this, SIGNAL(fileLoaded(KUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties())); } KMyMoneyApp::~KMyMoneyApp() { delete d->m_searchDlg; delete d->m_qifReader; delete d->m_transactionEditor; delete d->m_endingBalanceDlg; delete d->m_moveToAccountSelector; #ifdef HAVE_KDEPIMLIBS delete d->m_holidayRegion; #endif delete d; } const KUrl KMyMoneyApp::lastOpenedURL() { KUrl url = d->m_startDialog ? KUrl() : d->m_fileName; if (!url.isValid()) { url = readLastUsedFile(); } ready(); return url; } void KMyMoneyApp::slotObjectDestroyed(QObject* o) { if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = 0; } } 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(); } } } void KMyMoneyApp::createTransactionMoveMenu() { if (!d->m_moveToAccountSelector) { QWidget* w = factory()->container("transaction_move_menu", this); KMenu *menu = dynamic_cast(w); if (menu) { QWidgetAction *accountSelectorAction = new QWidgetAction(menu); d->m_moveToAccountSelector = new kMyMoneyAccountSelector(menu, 0, false); d->m_moveToAccountSelector->setObjectName("transaction_move_menu_selector"); accountSelectorAction->setDefaultWidget(d->m_moveToAccountSelector); menu->addAction(accountSelectorAction); connect(d->m_moveToAccountSelector, SIGNAL(destroyed(QObject*)), this, SLOT(slotObjectDestroyed(QObject*))); connect(d->m_moveToAccountSelector, SIGNAL(itemSelected(QString)), this, SLOT(slotMoveToAccount(QString))); } } } void KMyMoneyApp::initDynamicMenus() { connect(this, SIGNAL(accountSelected(MyMoneyAccount)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateMoveToAccountMenu())); } void KMyMoneyApp::initActions() { // ************* // The File menu // ************* actionCollection()->addAction(KStandardAction::New, this, SLOT(slotFileNew())); actionCollection()->addAction(KStandardAction::Open, this, SLOT(slotFileOpen())); d->m_recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(KUrl)), actionCollection()); actionCollection()->addAction(KStandardAction::Save, this, SLOT(slotFileSave())); actionCollection()->addAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); actionCollection()->addAction(KStandardAction::Close, this, SLOT(slotFileClose())); actionCollection()->addAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionCollection()->addAction(KStandardAction::Print, this, SLOT(slotPrintView())); KAction *open_database = actionCollection()->addAction("open_database"); open_database->setText(i18n("Open database...")); connect(open_database, SIGNAL(triggered()), this, SLOT(slotOpenDatabase())); KAction *saveas_database = actionCollection()->addAction("saveas_database"); saveas_database->setText(i18n("Save as database...")); saveas_database->setIcon(KIcon("svn-update")); connect(saveas_database, SIGNAL(triggered()), this, SLOT(slotSaveAsDatabase())); KAction *file_backup = actionCollection()->addAction("file_backup"); file_backup->setText(i18n("Backup...")); file_backup->setIcon(KIcon("utilities-file-archiver")); connect(file_backup, SIGNAL(triggered()), this, SLOT(slotBackupFile())); KAction *file_import_qif = actionCollection()->addAction("file_import_qif"); file_import_qif->setText(i18n("QIF...")); connect(file_import_qif, SIGNAL(triggered()), this, SLOT(slotQifImport())); KAction *file_import_gnc = actionCollection()->addAction("file_import_gnc"); file_import_gnc->setText(i18n("GnuCash...")); connect(file_import_gnc, SIGNAL(triggered()), this, SLOT(slotGncImport())); KAction *file_import_statement = actionCollection()->addAction("file_import_statement"); file_import_statement->setText(i18n("Statement file...")); connect(file_import_statement, SIGNAL(triggered()), this, SLOT(slotStatementImport())); KAction *file_import_template = actionCollection()->addAction("file_import_template"); file_import_template->setText(i18n("Account Template...")); connect(file_import_template, SIGNAL(triggered()), this, SLOT(slotLoadAccountTemplates())); KAction *file_export_template = actionCollection()->addAction("file_export_template"); file_export_template->setText(i18n("Account Template...")); connect(file_export_template, SIGNAL(triggered()), this, SLOT(slotSaveAccountTemplates())); KAction *file_export_qif = actionCollection()->addAction("file_export_qif"); file_export_qif->setText(i18n("QIF...")); connect(file_export_qif, SIGNAL(triggered()), this, SLOT(slotQifExport())); KAction *view_personal_data = actionCollection()->addAction("view_personal_data"); view_personal_data->setText(i18n("Personal Data...")); view_personal_data->setIcon(KIcon("user-properties")); connect(view_personal_data, SIGNAL(triggered()), this, SLOT(slotFileViewPersonal())); #ifdef KMM_DEBUG KAction *file_dump = actionCollection()->addAction("file_dump"); file_dump->setText(i18n("Dump Memory")); connect(file_dump, SIGNAL(triggered()), this, SLOT(slotFileFileInfo())); #endif KAction *view_file_info = actionCollection()->addAction("view_file_info"); view_file_info->setText(i18n("File-Information...")); view_file_info->setIcon(KIcon("document-properties")); connect(view_file_info, SIGNAL(triggered()), this, SLOT(slotFileInfoDialog())); // ************* // The Edit menu // ************* KAction *edit_find_transaction = actionCollection()->addAction("edit_find_transaction"); edit_find_transaction->setText(i18n("Find transaction...")); edit_find_transaction->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "edit-find")); edit_find_transaction->setShortcut(KShortcut("Ctrl+F")); connect(edit_find_transaction, SIGNAL(triggered()), this, SLOT(slotFindTransaction())); // ************* // The View menu // ************* KToggleAction *view_show_transaction_detail = actionCollection()->add("view_show_transaction_detail"); view_show_transaction_detail->setIcon(KIcon("zoom-in")); view_show_transaction_detail->setText(i18n("Show Transaction Detail")); view_show_transaction_detail->setShortcut(KShortcut("Ctrl+T")); KToggleAction *view_hide_reconciled_transactions = actionCollection()->add("view_hide_reconciled_transactions"); view_hide_reconciled_transactions->setText(i18n("Hide reconciled transactions")); view_hide_reconciled_transactions->setIcon(KMyMoneyUtils::overlayIcon("merge", "view-close")); view_hide_reconciled_transactions->setShortcut(KShortcut("Ctrl+R")); connect(view_hide_reconciled_transactions, SIGNAL(triggered()), this, SLOT(slotHideReconciledTransactions())); KToggleAction *view_hide_unused_categories = actionCollection()->add("view_hide_unused_categories"); view_hide_unused_categories->setText(i18n("Hide unused categories")); view_hide_unused_categories->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "view-close")); view_hide_unused_categories->setShortcut(KShortcut("Ctrl+U")); connect(view_hide_unused_categories, SIGNAL(triggered()), this, SLOT(slotHideUnusedCategories())); KToggleAction *view_show_all_accounts = actionCollection()->add("view_show_all_accounts"); view_show_all_accounts->setText(i18n("Show all accounts")); view_show_all_accounts->setShortcut(KShortcut("Ctrl+Shift+A")); connect(view_show_all_accounts, SIGNAL(triggered()), this, SLOT(slotShowAllAccounts())); // ********************* // The institutions menu // ********************* KAction *institution_new = actionCollection()->addAction("institution_new"); institution_new->setText(i18n("New institution...")); institution_new->setIcon(KMyMoneyUtils::overlayIcon("view-bank", "list-add", Qt::TopRightCorner)); connect(institution_new, SIGNAL(triggered()), this, SLOT(slotInstitutionNew())); KAction *institution_edit = actionCollection()->addAction("institution_edit"); institution_edit->setText(i18n("Edit institution...")); institution_edit->setIcon(KMyMoneyUtils::overlayIcon("view-bank", "document-edit")); connect(institution_edit, SIGNAL(triggered()), this, SLOT(slotInstitutionEdit())); KAction *institution_delete = actionCollection()->addAction("institution_delete"); institution_delete->setText(i18n("Delete institution...")); institution_delete->setIcon(KMyMoneyUtils::overlayIcon("view-bank", "edit-delete")); connect(institution_delete, SIGNAL(triggered()), this, SLOT(slotInstitutionDelete())); // ***************** // The accounts menu // ***************** KAction *account_new = actionCollection()->addAction("account_new"); account_new->setText(i18n("New account...")); account_new->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "list-add", Qt::TopRightCorner)); connect(account_new, SIGNAL(triggered()), this, SLOT(slotAccountNew())); // note : action "category_new" is included in this menu but defined below KAction *account_open = actionCollection()->addAction("account_open"); account_open->setText(i18n("Open ledger")); account_open->setIcon(KIcon("view-financial-list")); connect(account_open, SIGNAL(triggered()), this, SLOT(slotAccountOpen())); KAction *account_reconcile = actionCollection()->addAction("account_reconcile"); account_reconcile->setText(i18n("Reconcile...")); account_reconcile->setIcon(KIcon("merge")); account_reconcile->setShortcut(KShortcut("Ctrl+Shift+R")); connect(account_reconcile, SIGNAL(triggered()), this, SLOT(slotAccountReconcileStart())); KAction *account_reconcile_finish = actionCollection()->addAction("account_reconcile_finish"); account_reconcile_finish->setText(i18nc("Finish reconciliation", "Finish")); account_reconcile_finish->setIcon(KMyMoneyUtils::overlayIcon("merge", "dialog-ok")); connect(account_reconcile_finish, SIGNAL(triggered()), this, SLOT(slotAccountReconcileFinish())); KAction *account_reconcile_postpone = actionCollection()->addAction("account_reconcile_postpone"); account_reconcile_postpone->setText(i18n("Postpone reconciliation")); account_reconcile_postpone->setIcon(KIcon("media-playback-pause")); connect(account_reconcile_postpone, SIGNAL(triggered()), this, SLOT(slotAccountReconcilePostpone())); KAction *account_edit = actionCollection()->addAction("account_edit"); account_edit->setText(i18n("Edit account...")); account_edit->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "document-edit")); connect(account_edit, SIGNAL(triggered()), this, SLOT(slotAccountEdit())); KAction *account_delete = actionCollection()->addAction("account_delete"); account_delete->setText(i18n("Delete account...")); account_delete->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "edit-delete")); connect(account_delete, SIGNAL(triggered()), this, SLOT(slotAccountDelete())); KAction *account_close = actionCollection()->addAction("account_close"); account_close->setText(i18n("Close account")); account_close->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "dialog-close")); connect(account_close, SIGNAL(triggered()), this, SLOT(slotAccountClose())); KAction *account_reopen = actionCollection()->addAction("account_reopen"); account_reopen->setText(i18n("Reopen account")); account_reopen->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "dialog-ok")); connect(account_reopen, SIGNAL(triggered()), this, SLOT(slotAccountReopen())); KAction *account_transaction_report = actionCollection()->addAction("account_transaction_report"); account_transaction_report->setText(i18n("Transaction report")); account_transaction_report->setIcon(KIcon("view-financial-list")); connect(account_transaction_report, SIGNAL(triggered()), this, SLOT(slotAccountTransactionReport())); KAction *account_chart = actionCollection()->addAction("account_chart"); account_chart->setText(i18n("Show balance chart...")); account_chart->setIcon(KIcon("office-chart-line")); connect(account_chart, SIGNAL(triggered()), this, SLOT(slotAccountChart())); KAction *account_online_map = actionCollection()->addAction("account_online_map"); account_online_map->setText(i18n("Map to online account...")); account_online_map->setIcon(KIcon("news-subscribe")); connect(account_online_map, SIGNAL(triggered()), this, SLOT(slotAccountMapOnline())); KAction *account_online_unmap = actionCollection()->addAction("account_online_unmap"); account_online_unmap->setText(i18n("Unmap account...")); account_online_unmap->setIcon(KIcon("news-unsubscribe")); connect(account_online_unmap, SIGNAL(triggered()), this, SLOT(slotAccountUnmapOnline())); KActionMenu* menu = new KActionMenu(KIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "download")), i18nc("Update online accounts menu", "Update"), this); actionCollection()->addAction("account_online_update_menu", menu); // activating the menu button is the same as selecting the current account connect(menu, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnline())); KAction *account_online_update = actionCollection()->addAction("account_online_update"); account_online_update->setText(i18n("Update account...")); account_online_update->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "download")); connect(account_online_update, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnline())); menu->addAction(account_online_update); KAction *account_online_update_all = actionCollection()->addAction("account_online_update_all"); account_online_update_all->setText(i18n("Update all accounts...")); account_online_update_all->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "download")); connect(account_online_update_all, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnlineAll())); menu->addAction(account_online_update_all); KAction *account_online_transfer = actionCollection()->addAction("account_online_new_credit_transfer"); account_online_transfer->setText(i18n("New credit transfer")); account_online_transfer->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "mail-message-new")); connect(account_online_transfer, SIGNAL(triggered()), this, SLOT(slotNewOnlineTransfer())); connect(onlineJobAdministration::instance(), SIGNAL(canSendCreditTransferChanged(bool)), account_online_transfer, SLOT(setEnabled(bool))); // ******************* // The categories menu // ******************* KAction *category_new = actionCollection()->addAction("category_new"); category_new->setText(i18n("New category...")); category_new->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "list-add", Qt::TopRightCorner)); connect(category_new, SIGNAL(triggered()), this, SLOT(slotCategoryNew())); KAction *category_edit = actionCollection()->addAction("category_edit"); category_edit->setText(i18n("Edit category...")); category_edit->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "document-edit")); connect(category_edit, SIGNAL(triggered()), this, SLOT(slotAccountEdit())); KAction *category_delete = actionCollection()->addAction("category_delete"); category_delete->setText(i18n("Delete category...")); category_delete->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "edit-delete")); connect(category_delete, SIGNAL(triggered()), this, SLOT(slotAccountDelete())); // ************** // The tools menu // ************** KAction *tools_qif_editor = actionCollection()->addAction("tools_qif_editor"); tools_qif_editor->setText(i18n("QIF Profile Editor...")); tools_qif_editor->setIcon(KIcon("document-properties")); connect(tools_qif_editor, SIGNAL(triggered()), this, SLOT(slotQifProfileEditor())); KAction *tools_currency_editor = actionCollection()->addAction("tools_currency_editor"); tools_currency_editor->setText(i18n("Currencies...")); tools_currency_editor->setIcon(KIcon("view-currency-list")); connect(tools_currency_editor, SIGNAL(triggered()), this, SLOT(slotCurrencyDialog())); KAction *tools_price_editor = actionCollection()->addAction("tools_price_editor"); tools_price_editor->setText(i18n("Prices...")); connect(tools_price_editor, SIGNAL(triggered()), this, SLOT(slotPriceDialog())); KAction *tools_update_prices = actionCollection()->addAction("tools_update_prices"); tools_update_prices->setText(i18n("Update Stock and Currency Prices...")); tools_update_prices->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "download")); connect(tools_update_prices, SIGNAL(triggered()), this, SLOT(slotEquityPriceUpdate())); KAction *tools_consistency_check = actionCollection()->addAction("tools_consistency_check"); tools_consistency_check->setText(i18n("Consistency Check")); connect(tools_consistency_check, SIGNAL(triggered()), this, SLOT(slotFileConsistencyCheck())); KAction *tools_performancetest = actionCollection()->addAction("tools_performancetest"); tools_performancetest->setText(i18n("Performance-Test")); tools_performancetest->setIcon(KIcon("fork")); connect(tools_performancetest, SIGNAL(triggered()), this, SLOT(slotPerformanceTest())); KAction *tools_generate_sql = actionCollection()->addAction("tools_generate_sql"); tools_generate_sql->setText(i18n("Generate Database SQL")); connect(tools_generate_sql, SIGNAL(triggered()), this, SLOT(slotGenerateSql())); KAction *tools_kcalc = actionCollection()->addAction("tools_kcalc"); tools_kcalc->setText(i18n("Calculator...")); tools_kcalc->setIcon(KIcon("accessories-calculator")); connect(tools_kcalc, SIGNAL(triggered()), this, SLOT(slotToolsStartKCalc())); // ***************** // The settings menu // ***************** actionCollection()->addAction(KStandardAction::Preferences, this, SLOT(slotSettings())); KAction *settings_enable_messages = actionCollection()->addAction("settings_enable_messages"); settings_enable_messages->setText(i18n("Enable all messages")); connect(settings_enable_messages, SIGNAL(triggered()), this, SLOT(slotEnableMessages())); KAction *settings_language = actionCollection()->addAction("settings_language"); settings_language->setText(i18n("KDE language settings...")); connect(settings_language, SIGNAL(triggered()), this, SLOT(slotKDELanguageSettings())); // ************* // The help menu // ************* KAction *help_show_tip = actionCollection()->addAction("help_show_tip"); help_show_tip->setText(i18n("&Show tip of the day")); help_show_tip->setIcon(KIcon("ktip")); connect(help_show_tip, SIGNAL(triggered()), this, SLOT(slotShowTipOfTheDay())); // *************************** // Actions w/o main menu entry // *************************** KAction *transaction_new = actionCollection()->addAction("transaction_new"); transaction_new->setText(i18nc("New transaction button", "New")); transaction_new->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "list-add", Qt::TopRightCorner)); transaction_new->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Insert)); connect(transaction_new, SIGNAL(triggered()), this, SLOT(slotTransactionsNew())); // we use Return as the same shortcut for Edit and Enter. Therefore, we don't allow // to change them (The standard KDE dialog complains anyway if you want to assign // the same shortcut to two actions) KAction *transaction_edit = actionCollection()->addAction("transaction_edit"); transaction_edit->setText(i18nc("Edit transaction button", "Edit")); transaction_edit->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "document-edit")); transaction_edit->setShortcutConfigurable(false); connect(transaction_edit, SIGNAL(triggered()), this, SLOT(slotTransactionsEdit())); KAction *transaction_enter = actionCollection()->addAction("transaction_enter"); transaction_enter->setText(i18nc("Enter transaction", "Enter")); transaction_enter->setIcon(KIcon("dialog-ok")); transaction_enter->setShortcutConfigurable(false); connect(transaction_enter, SIGNAL(triggered()), this, SLOT(slotTransactionsEnter())); KAction *transaction_editsplits = actionCollection()->addAction("transaction_editsplits"); transaction_editsplits->setText(i18nc("Edit split button", "Edit splits")); transaction_editsplits->setIcon(KIcon("split")); connect(transaction_editsplits, SIGNAL(triggered()), this, SLOT(slotTransactionsEditSplits())); KAction *transaction_cancel = actionCollection()->addAction("transaction_cancel"); transaction_cancel->setText(i18nc("Cancel transaction edit", "Cancel")); transaction_cancel->setIcon(KIcon("dialog-cancel")); connect(transaction_cancel, SIGNAL(triggered()), this, SLOT(slotTransactionsCancel())); KAction *transaction_delete = actionCollection()->addAction("transaction_delete"); transaction_delete->setText(i18nc("Delete transaction", "Delete")); transaction_delete->setIcon(KIcon("edit-delete")); connect(transaction_delete, SIGNAL(triggered()), this, SLOT(slotTransactionsDelete())); KAction *transaction_duplicate = actionCollection()->addAction("transaction_duplicate"); transaction_duplicate->setText(i18nc("Duplicate transaction", "Duplicate")); transaction_duplicate->setIcon(KIcon("edit-copy")); connect(transaction_duplicate, SIGNAL(triggered()), this, SLOT(slotTransactionDuplicate())); KAction *transaction_match = actionCollection()->addAction("transaction_match"); transaction_match->setText(i18nc("Button text for match transaction", "Match")); transaction_match->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "document-import")); connect(transaction_match, SIGNAL(triggered()), this, SLOT(slotTransactionMatch())); KAction *transaction_accept = actionCollection()->addAction("transaction_accept"); transaction_accept->setText(i18nc("Accept 'imported' and 'matched' transaction", "Accept")); transaction_accept->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "dialog-ok-apply")); connect(transaction_accept, SIGNAL(triggered()), this, SLOT(slotTransactionsAccept())); KAction *transaction_mark_toggle = actionCollection()->addAction("transaction_mark_toggle"); transaction_mark_toggle->setText(i18nc("Toggle reconciliation flag", "Toggle")); transaction_mark_toggle->setShortcut(KShortcut("Ctrl+Space")); connect(transaction_mark_toggle, SIGNAL(triggered()), this, SLOT(slotToggleReconciliationFlag())); KAction *transaction_mark_cleared = actionCollection()->addAction("transaction_mark_cleared"); transaction_mark_cleared->setText(i18nc("Mark transaction cleared", "Cleared")); transaction_mark_cleared->setShortcut(KShortcut("Ctrl+Alt+Space")); connect(transaction_mark_cleared, SIGNAL(triggered()), this, SLOT(slotMarkTransactionCleared())); KAction *transaction_mark_reconciled = actionCollection()->addAction("transaction_mark_reconciled"); transaction_mark_reconciled->setText(i18nc("Mark transaction reconciled", "Reconciled")); transaction_mark_reconciled->setShortcut(KShortcut("Ctrl+Shift+Space")); connect(transaction_mark_reconciled, SIGNAL(triggered()), this, SLOT(slotMarkTransactionReconciled())); KAction *transaction_mark_notreconciled = actionCollection()->addAction("transaction_mark_notreconciled"); transaction_mark_notreconciled->setText(i18nc("Mark transaction not reconciled", "Not reconciled")); connect(transaction_mark_notreconciled, SIGNAL(triggered()), this, SLOT(slotMarkTransactionNotReconciled())); KAction *transaction_select_all = actionCollection()->addAction("transaction_select_all"); transaction_select_all->setText(i18nc("Select all transactions", "Select all")); transaction_select_all->setShortcut(KShortcut("Ctrl+A")); connect(transaction_select_all, SIGNAL(triggered()), this, SIGNAL(selectAllTransactions())); KAction *transaction_goto_account = actionCollection()->addAction("transaction_goto_account"); transaction_goto_account->setText(i18n("Go to account")); transaction_goto_account->setIcon(KIcon("go-jump")); connect(transaction_goto_account, SIGNAL(triggered()), this, SLOT(slotTransactionGotoAccount())); KAction *transaction_goto_payee = actionCollection()->addAction("transaction_goto_payee"); transaction_goto_payee->setText(i18n("Go to payee")); transaction_goto_payee->setIcon(KIcon("go-jump")); connect(transaction_goto_payee, SIGNAL(triggered()), this, SLOT(slotTransactionGotoPayee())); KAction *transaction_create_schedule = actionCollection()->addAction("transaction_create_schedule"); transaction_create_schedule->setText(i18n("Create scheduled transaction...")); transaction_create_schedule->setIcon(KIcon("appointment-new")); connect(transaction_create_schedule, SIGNAL(triggered()), this, SLOT(slotTransactionCreateSchedule())); KAction *transaction_assign_number = actionCollection()->addAction("transaction_assign_number"); transaction_assign_number->setText(i18n("Assign next number")); transaction_assign_number->setShortcut(KShortcut("Ctrl+Shift+N")); connect(transaction_assign_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNumber())); KAction *transaction_combine = actionCollection()->addAction("transaction_combine"); transaction_combine->setText(i18nc("Combine transactions", "Combine")); connect(transaction_combine, SIGNAL(triggered()), this, SLOT(slotTransactionCombine())); KAction *transaction_copy_splits = actionCollection()->addAction("transaction_copy_splits"); transaction_copy_splits->setText(i18n("Copy splits")); connect(transaction_copy_splits, SIGNAL(triggered()), this, SLOT(slotTransactionCopySplits())); //Investment KAction *investment_new = actionCollection()->addAction("investment_new"); investment_new->setText(i18n("New investment...")); investment_new->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "list-add", Qt::TopRightCorner)); connect(investment_new, SIGNAL(triggered()), this, SLOT(slotInvestmentNew())); KAction *investment_edit = actionCollection()->addAction("investment_edit"); investment_edit->setText(i18n("Edit investment...")); investment_edit->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "document-edit")); connect(investment_edit, SIGNAL(triggered()), this, SLOT(slotInvestmentEdit())); KAction *investment_delete = actionCollection()->addAction("investment_delete"); investment_delete->setText(i18n("Delete investment...")); investment_delete->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "edit-delete")); connect(investment_delete, SIGNAL(triggered()), this, SLOT(slotInvestmentDelete())); KAction *investment_online_price_update = actionCollection()->addAction("investment_online_price_update"); investment_online_price_update->setText(i18n("Online price update...")); investment_online_price_update->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "download")); connect(investment_online_price_update, SIGNAL(triggered()), this, SLOT(slotOnlinePriceUpdate())); KAction *investment_manual_price_update = actionCollection()->addAction("investment_manual_price_update"); investment_manual_price_update->setText(i18n("Manual price update...")); connect(investment_manual_price_update, SIGNAL(triggered()), this, SLOT(slotManualPriceUpdate())); //Schedule KAction *schedule_new = actionCollection()->addAction("schedule_new"); schedule_new->setText(i18n("New scheduled transaction")); schedule_new->setIcon(KIcon("appointment-new")); connect(schedule_new, SIGNAL(triggered()), this, SLOT(slotScheduleNew())); KAction *schedule_edit = actionCollection()->addAction("schedule_edit"); schedule_edit->setText(i18n("Edit scheduled transaction")); schedule_edit->setIcon(KIcon("document-edit")); connect(schedule_edit, SIGNAL(triggered()), this, SLOT(slotScheduleEdit())); KAction *schedule_delete = actionCollection()->addAction("schedule_delete"); schedule_delete->setText(i18n("Delete scheduled transaction")); schedule_delete->setIcon(KIcon("edit-delete")); connect(schedule_delete, SIGNAL(triggered()), this, SLOT(slotScheduleDelete())); KAction *schedule_duplicate = actionCollection()->addAction("schedule_duplicate"); schedule_duplicate->setText(i18n("Duplicate scheduled transaction")); schedule_duplicate->setIcon(KIcon("edit-copy")); connect(schedule_duplicate, SIGNAL(triggered()), this, SLOT(slotScheduleDuplicate())); KAction *schedule_enter = actionCollection()->addAction("schedule_enter"); schedule_enter->setText(i18n("Enter next transaction...")); schedule_enter->setIcon(KIcon(KMyMoneyGlobalSettings::enterScheduleIcon())); connect(schedule_enter, SIGNAL(triggered()), this, SLOT(slotScheduleEnter())); KAction *schedule_skip = actionCollection()->addAction("schedule_skip"); schedule_skip->setText(i18n("Skip next transaction...")); schedule_skip->setIcon(KIcon("media-seek-forward")); connect(schedule_skip, SIGNAL(triggered()), this, SLOT(slotScheduleSkip())); //Payees KAction *payee_new = actionCollection()->addAction("payee_new"); payee_new->setText(i18n("New payee")); payee_new->setIcon(KIcon("list-add-user")); connect(payee_new, SIGNAL(triggered()), this, SLOT(slotPayeeNew())); KAction *payee_rename = actionCollection()->addAction("payee_rename"); payee_rename->setText(i18n("Rename payee")); payee_rename->setIcon(KIcon("user-properties")); connect(payee_rename, SIGNAL(triggered()), this, SIGNAL(payeeRename())); KAction *payee_delete = actionCollection()->addAction("payee_delete"); payee_delete->setText(i18n("Delete payee")); payee_delete->setIcon(KIcon("list-remove-user")); connect(payee_delete, SIGNAL(triggered()), this, SLOT(slotPayeeDelete())); KAction *payee_merge = actionCollection()->addAction("payee_merge"); payee_merge->setText(i18n("Merge payees")); payee_merge->setIcon(KIcon("merge")); connect(payee_merge, SIGNAL(triggered()), this, SLOT(slotPayeeMerge())); //Tags KAction *tag_new = actionCollection()->addAction("tag_new"); tag_new->setText(i18n("New tag")); tag_new->setIcon(KIcon("list-add-tag")); connect(tag_new, SIGNAL(triggered()), this, SLOT(slotTagNew())); KAction *tag_rename = actionCollection()->addAction("tag_rename"); tag_rename->setText(i18n("Rename tag")); tag_rename->setIcon(KIcon("tag-rename")); connect(tag_rename, SIGNAL(triggered()), this, SIGNAL(tagRename())); KAction *tag_delete = actionCollection()->addAction("tag_delete"); tag_delete->setText(i18n("Delete tag")); tag_delete->setIcon(KIcon("list-remove-tag")); connect(tag_delete, SIGNAL(triggered()), this, SLOT(slotTagDelete())); //Budget KAction *budget_new = actionCollection()->addAction("budget_new"); budget_new->setText(i18n("New budget")); budget_new->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "list-add", Qt::TopRightCorner)); connect(budget_new, SIGNAL(triggered()), this, SLOT(slotBudgetNew())); KAction *budget_rename = actionCollection()->addAction("budget_rename"); budget_rename->setText(i18n("Rename budget")); budget_rename->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "document-edit")); connect(budget_rename, SIGNAL(triggered()), this, SIGNAL(budgetRename())); KAction *budget_delete = actionCollection()->addAction("budget_delete"); budget_delete->setText(i18n("Delete budget")); budget_delete->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "edit-delete")); connect(budget_delete, SIGNAL(triggered()), this, SLOT(slotBudgetDelete())); KAction *budget_copy = actionCollection()->addAction("budget_copy"); budget_copy->setText(i18n("Copy budget")); budget_copy->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "edit-copy")); connect(budget_copy, SIGNAL(triggered()), this, SLOT(slotBudgetCopy())); KAction *budget_change_year = actionCollection()->addAction("budget_change_year"); budget_change_year->setText(i18n("Change budget year")); budget_change_year->setIcon(KIcon("view-calendar")); connect(budget_change_year, SIGNAL(triggered()), this, SLOT(slotBudgetChangeYear())); KAction *budget_forecast = actionCollection()->addAction("budget_forecast"); budget_forecast->setText(i18n("Budget based on forecast")); budget_forecast->setIcon(KIcon("view-financial-forecast")); connect(budget_forecast, SIGNAL(triggered()), this, SLOT(slotBudgetForecast())); // Currency actions KAction *currency_new = actionCollection()->addAction("currency_new"); currency_new->setText(i18n("New currency")); currency_new->setIcon(KIcon("document-new")); connect(currency_new, SIGNAL(triggered()), this, SLOT(slotCurrencyNew())); KAction *currency_rename = actionCollection()->addAction("currency_rename"); currency_rename->setText(i18n("Rename currency")); currency_rename->setIcon(KIcon("edit-rename")); connect(currency_rename, SIGNAL(triggered()), this, SIGNAL(currencyRename())); KAction *currency_delete = actionCollection()->addAction("currency_delete"); currency_delete->setText(i18n("Delete currency")); currency_delete->setIcon(KIcon("edit-delete")); connect(currency_delete, SIGNAL(triggered()), this, SLOT(slotCurrencyDelete())); KAction *currency_setbase = actionCollection()->addAction("currency_setbase"); currency_setbase->setText(i18n("Select as base currency")); currency_setbase->setIcon(KIcon("kmymoney")); connect(currency_setbase, SIGNAL(triggered()), this, SLOT(slotCurrencySetBase())); //price actions KAction *price_new = actionCollection()->addAction("price_new"); price_new->setText(i18n("New price...")); price_new->setIcon(KIcon("document-new")); connect(price_new, SIGNAL(triggered()), this, SIGNAL(priceNew())); KAction *price_edit = actionCollection()->addAction("price_edit"); price_edit->setText(i18n("Edit price...")); price_edit->setIcon(KIcon("document-edit")); connect(price_edit, SIGNAL(triggered()), this, SIGNAL(priceEdit())); KAction *price_update = actionCollection()->addAction("price_update"); price_update->setText(i18n("Online Price Update...")); price_update->setIcon(KMyMoneyUtils::overlayIcon("view-currency-list", "download")); connect(price_update, SIGNAL(triggered()), this, SIGNAL(priceOnlineUpdate())); KAction *price_delete = actionCollection()->addAction("price_delete"); price_delete->setText(i18n("Delete price...")); price_delete->setIcon(KIcon("edit-delete")); connect(price_delete, SIGNAL(triggered()), this, SIGNAL(priceDelete())); //debug actions #ifdef KMM_DEBUG KAction *new_user_wizard = actionCollection()->addAction("new_user_wizard"); new_user_wizard->setText(i18n("Test new feature")); new_user_wizard->setShortcut(KShortcut("Ctrl+G")); connect(new_user_wizard, SIGNAL(triggered()), this, SLOT(slotNewFeature())); KToggleAction *debug_traces = actionCollection()->add("debug_traces"); debug_traces->setText(i18n("Debug Traces")); connect(debug_traces, SIGNAL(triggered()), this, SLOT(slotToggleTraces())); #endif KToggleAction *debug_timers = actionCollection()->add("debug_timers"); debug_timers->setText(i18n("Debug Timers")); connect(debug_timers, SIGNAL(triggered()), this, SLOT(slotToggleTimers())); // onlineJob actions KAction* onlineJob_delete = actionCollection()->addAction("onlinejob_delete"); onlineJob_delete->setText(i18n("Remove credit transfer")); onlineJob_delete->setIcon(KIcon::fromTheme("edit-delete")); KAction* onlineJob_edit = actionCollection()->addAction("onlinejob_edit"); onlineJob_edit->setText(i18n("Edit credit transfer")); onlineJob_edit->setIcon(KIcon::fromTheme("document-edit")); KAction* onlineJob_log = actionCollection()->addAction("onlinejob_log"); onlineJob_log->setText(i18n("Show log")); connect(onlineJob_log, SIGNAL(triggered()), this, SLOT(slotOnlineJobLog())); // ************************ // 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 // Setup transaction detail switch toggleAction("view_show_transaction_detail")->setChecked(KMyMoneyGlobalSettings::showRegisterDetailed()); toggleAction("view_hide_reconciled_transactions")->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); toggleAction("view_hide_unused_categories")->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); toggleAction("view_show_all_accounts")->setChecked(false); // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); } void KMyMoneyApp::connectActionsAndViews() { KOnlineJobOutbox *const outbox = d->m_myMoneyView->getOnlineJobOutbox(); Q_CHECK_PTR(outbox); QAction *const onlineJob_delete = actionCollection()->action("onlinejob_delete"); Q_CHECK_PTR(onlineJob_delete); connect(onlineJob_delete, SIGNAL(triggered()), outbox, SLOT(slotRemoveJob())); QAction *const onlineJob_edit = actionCollection()->action("onlinejob_edit"); Q_CHECK_PTR(onlineJob_edit); connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob())); } void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); QList::const_iterator it; for (it = list.begin(); it != list.end(); ++it) { std::cout << qPrintable((*it)->objectName()) << ": " << qPrintable((*it)->text()) << std::endl; } } KToggleAction* KMyMoneyApp::toggleAction(const QString& actionName) const { static KToggleAction dummyAction(QString("Dummy"), 0); KToggleAction* p = dynamic_cast(actionCollection()->action(QString(actionName.toLatin1()))); if (!p) { qWarning("ToggleAction with name '%s' not found!", qPrintable(actionName)); p = &dummyAction; } return p; } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR statusBar()->insertItem("", ID_STATUS_MSG); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar(statusBar()); 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::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", toggleAction("options_show_statusbar")->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"); toggleAction("view_hide_reconciled_transactions")->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); toggleAction("view_hide_unused_categories")->setChecked(KMyMoneyGlobalSettings::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); } void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); updateCaption(true); } int KMyMoneyApp::askSaveOnClose() { int ans; if (KMyMoneyGlobalSettings::autoSaveOnClose()) { ans = KMessageBox::Yes; } else { ans = KMessageBox::warningYesNoCancel(this, i18n("The file has been changed, save it?")); } return ans; } bool KMyMoneyApp::queryClose() { if (!isReady()) return false; if (d->m_myMoneyView->dirty()) { int ans = askSaveOnClose(); if (ans == KMessageBox::Cancel) return false; else if (ans == KMessageBox::Yes) { bool saved = slotFileSave(); saveOptions(); return saved; } } if (d->m_myMoneyView->isDatabase()) slotFileClose(); // close off the database 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(); } void KMyMoneyApp::slotFileNew() { KMSTATUS(i18n("Creating new document...")); slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->newFile(); d->m_fileName = KUrl(); updateCaption(); // before we create the wizard, we need to preload the currencies MyMoneyFileTransaction ft; d->m_myMoneyView->loadDefaultCurrencies(); d->m_myMoneyView->loadAncientCurrencies(); ft.commit(); NewUserWizard::Wizard *wizard = new NewUserWizard::Wizard(); if (wizard->exec() == QDialog::Accepted) { MyMoneyFile* file = MyMoneyFile::instance(); ft.restart(); try { // store the user info file->setUser(wizard->user()); // setup base currency file->setBaseCurrency(wizard->baseCurrency()); // create a possible institution MyMoneyInstitution inst = wizard->institution(); if (inst.name().length()) { file->addInstitution(inst); } // import the account templates QList templates = wizard->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } // create a possible checking account MyMoneyAccount 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()); } } d->m_fileName = KUrl(wizard->url()); ft.commit(); KMyMoneyGlobalSettings::setFirstTimeRun(false); // FIXME This is a bit clumsy. We re-read the freshly // created file to be able to run through all the // fixup logic and then save it to keep the modified // flag off. slotFileSave(); d->m_myMoneyView->readFile(d->m_fileName); slotFileSave(); // now keep the filename in the recent files used list //KRecentFilesAction *p = dynamic_cast(action(KStandardAction::name(KStandardAction::OpenRecent))); //if(p) d->m_recentFiles->addUrl(d->m_fileName); writeLastUsedFile(d->m_fileName.url()); } catch (const MyMoneyException &) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } if (wizard->startSettingsAfterFinished()) slotSettings(); } else { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } delete wizard; updateCaption(); emit fileLoaded(d->m_fileName); } } KUrl KMyMoneyApp::selectFile(const QString& /*title*/, const QString& _path, const QString& mask, KFile::Mode mode, QWidget* widget) { KUrl url; QString path(_path); // if the path is not specified open the file dialog in the last used directory // 'kmymoney' is the keyword that identifies the last used directory in KFileDialog if (path.isEmpty()) path = "kfiledialog:///kmymoney-import"; QPointer dialog = new KFileDialog(KUrl(path), mask, this, widget); dialog->setMode(mode); if (dialog->exec() == QDialog::Accepted && dialog != 0) { url = dialog->selectedUrl(); } // in case we have an additional widget, we remove it from the // dialog, so that the caller can still access it. Therefore, it is // the callers responsibility to delete the object if (widget) widget->setParent(0); delete dialog; return url; } // General open void KMyMoneyApp::slotFileOpen() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new KFileDialog(KUrl("kfiledialog:///kmymoney-open"), i18n("*.kmy *.xml|KMyMoney files\n*|All files"), this); dialog->setMode(KFile::File | KFile::ExistingOnly); if (dialog->exec() == QDialog::Accepted && dialog != 0) { slotFileOpenRecent(dialog->selectedUrl()); } delete dialog; } void KMyMoneyApp::slotOpenDatabase() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite); if (!dialog->checkDrivers()) { delete dialog; return; } if (dialog->exec() == QDialog::Accepted && dialog != 0) { slotFileOpenRecent(dialog->selectedURL()); } delete dialog; } bool KMyMoneyApp::isImportableFile(const KUrl& 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 = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url.path())) { 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 == d->m_importerPlugins.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; } void KMyMoneyApp::slotFileOpenRecent(const KUrl& url) { KMSTATUS(i18n("Loading file...")); KUrl lastFile = d->m_fileName; // check if there are other instances which might have this file open QList list = instanceList(); QList::ConstIterator it; bool duplicate = false; for (it = list.constBegin(); duplicate == false && it != list.constEnd(); ++it) { QDBusInterface remoteApp(*it, "/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()) { duplicate = true; } } } if (!duplicate) { KUrl newurl = url; if ((newurl.protocol() == "sql")) { if (newurl.queryItem("driver") == "QMYSQL3") { // fix any old urls newurl.removeQueryItem("driver"); newurl.addQueryItem("driver", "QMYSQL"); } if (newurl.queryItem("driver") == "QSQLITE3") { newurl.removeQueryItem("driver"); newurl.addQueryItem("driver", "QSQLITE"); } // check if a password is needed. it may be if the URL came from the last/recent file list QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl); if (!dialog->checkDrivers()) { delete dialog; return; } // if we need to supply a password, then show the dialog // otherwise it isn't needed if ((newurl.queryItem("secure") == "yes") && newurl.pass().isEmpty()) { if (dialog->exec() == QDialog::Accepted && dialog != 0) newurl = dialog->selectedURL(); else { delete dialog; return; } } delete dialog; } if ((newurl.protocol() == "sql") || (newurl.isValid() && KIO::NetAccess::exists(newurl, KIO::NetAccess::SourceSide, this))) { slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { try { if (d->m_myMoneyView->readFile(newurl)) { if ((d->m_myMoneyView->isNativeFile())) { d->m_fileName = newurl; updateCaption(); d->m_recentFiles->addUrl(newurl.pathOrUrl()); writeLastUsedFile(newurl.pathOrUrl()); } else { d->m_fileName = KUrl(); // imported files have no filename } // Check the schedules slotCheckSchedules(); } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", e.what())); } updateCaption(); emit fileLoaded(d->m_fileName); } else { /*fileOpen failed - should we do something or maybe fileOpen puts out the message... - it does for database*/ } } else { // newurl invalid slotFileClose(); 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.pathOrUrl()), i18n("File not found")); } } else { // isDuplicate KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.pathOrUrl()), i18n("Duplicate open")); } } bool KMyMoneyApp::slotFileSave() { // if there's nothing changed, there's no need to save anything if (!d->m_myMoneyView->dirty()) return true; bool rc = false; KMSTATUS(i18n("Saving file...")); if (d->m_fileName.isEmpty()) return slotFileSaveAs(); d->consistencyCheck(false); /*if (myMoneyView->isDatabase()) { rc = myMoneyView->saveDatabase(m_fileName); // the 'save' function is no longer relevant for a database*/ setEnabled(false); rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); setEnabled(true); d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileSaveAsFilterChanged(const QString& filter) { if (!d->m_saveEncrypted) return; if (filter != "*.kmy") { d->m_saveEncrypted->setCurrentItem(0); d->m_saveEncrypted->setEnabled(false); } else { d->m_saveEncrypted->setEnabled(true); } } void KMyMoneyApp::slotManageGpgKeys() { QPointer dlg = new KGpgKeySelectionDlg(this); dlg->setKeys(d->m_additionalGpgKeys); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->m_additionalGpgKeys = dlg->keys(); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count())); } delete dlg; } void KMyMoneyApp::slotKeySelected(int idx) { int cnt = 0; if (idx != 0) { cnt = d->m_additionalGpgKeys.count(); } d->m_additionalKeyLabel->setEnabled(idx != 0); d->m_additionalKeyButton->setEnabled(idx != 0); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", cnt)); } bool KMyMoneyApp::slotFileSaveAs() { bool rc = false; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); KMSTATUS(i18n("Saving file with a new filename...")); QString prevDir = ""; // don't prompt file name if not a native file if (d->m_myMoneyView->isNativeFile()) prevDir = readLastUsedDir(); // fill the additional key list with the default d->m_additionalGpgKeys = KMyMoneyGlobalSettings::gpgRecipientList(); KVBox* vbox = new KVBox(); if (KGPGFile::GPGAvailable()) { KHBox* keyBox = new KHBox(vbox); new QLabel(i18n("Encryption key to be used"), keyBox); d->m_saveEncrypted = new KComboBox(keyBox); KHBox* labelBox = new KHBox(vbox); d->m_additionalKeyLabel = new QLabel(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count()), labelBox); d->m_additionalKeyButton = new KPushButton(i18n("Manage additional keys"), labelBox); connect(d->m_additionalKeyButton, SIGNAL(clicked()), this, SLOT(slotManageGpgKeys())); connect(d->m_saveEncrypted, SIGNAL(activated(int)), this, SLOT(slotKeySelected(int))); // fill the secret key list and combo box QStringList keyList; KGPGFile::secretKeyList(keyList); d->m_saveEncrypted->addItem(i18n("No encryption")); for (QStringList::iterator it = keyList.begin(); it != keyList.end(); ++it) { QStringList fields = (*it).split(':', QString::SkipEmptyParts); if (fields[0] != RECOVER_KEY_ID) { // replace parenthesis in name field with brackets QString name = fields[1]; name.replace('(', "["); name.replace(')', "]"); name = QString("%1 (0x%2)").arg(name).arg(fields[0]); d->m_saveEncrypted->addItem(name); if (name.contains(KMyMoneyGlobalSettings::gpgRecipient())) { d->m_saveEncrypted->setCurrentItem(name); } } } } // the following code is copied from KFileDialog::getSaveFileName, // adjust to our local needs (filetypes etc.) and // enhanced to show the d->m_saveEncrypted combo box bool specialDir = !prevDir.isEmpty() && prevDir.at(0) == QLatin1Char(':'); QPointer dlg = new KFileDialog(specialDir ? prevDir : QString(), QString("%1|%2\n").arg("*.kmy").arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + QString("%1|%2\n").arg("*.xml").arg(i18nc("XML (Filefilter)", "XML files")) + QString("%1|%2\n").arg("*.anon.xml").arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + QString("%1|%2\n").arg("*").arg(i18nc("All files (Filefilter)", "All files")), this, vbox); dlg->setOperationMode(KFileDialog::Saving); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotFileSaveAsFilterChanged(QString))); if (!specialDir) dlg->setSelection(prevDir); // may also be a filename dlg->setCaption(i18n("Save As")); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->consistencyCheck(false); KUrl newURL = dlg->selectedUrl(); // deleting the dialog will delete the combobox pointed to by d->m_saveEncrypted so get the key name here QString selectedKeyName; if (d->m_saveEncrypted && d->m_saveEncrypted->currentIndex() != 0) selectedKeyName = d->m_saveEncrypted->currentText(); d->m_saveEncrypted = 0; delete dlg; if (!newURL.isEmpty()) { QString newName = newURL.pathOrUrl(); // end of copy // 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("kmy", 0, Qt::CaseInsensitive) == -1) && (strExt.indexOf("xml", 0, Qt::CaseInsensitive) == -1)) { strTemp.append("kmy"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmy"); } if (okToWriteFile(newName)) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(newName); setEnabled(false); // If this is the anonymous file export, just save it, don't actually take the // name, or remember it! Don't even try to encrypt it if (newName.right(9).toLower() == ".anon.xml") { rc = d->m_myMoneyView->saveFile(newName); } else { d->m_fileName = newName; QString encryptionKeys; QRegExp keyExp(".* \\((.*)\\)"); if (keyExp.indexIn(selectedKeyName) != -1) { encryptionKeys = keyExp.cap(1); } if (!d->m_additionalGpgKeys.isEmpty()) { if (!encryptionKeys.isEmpty()) encryptionKeys += ','; encryptionKeys += d->m_additionalGpgKeys.join(","); } rc = d->m_myMoneyView->saveFile(newName, encryptionKeys); //write the directory used for this file as the default one for next time. writeLastUsedDir(newName); writeLastUsedFile(newName); } d->m_autoSaveTimer->stop(); setEnabled(true); } } } else { delete dlg; } updateCaption(); return rc; } bool KMyMoneyApp::slotSaveAsDatabase() { bool rc = false; KUrl oldUrl; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) { dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); oldUrl = d->m_fileName.isEmpty() ? lastOpenedURL() : d->m_fileName; } KMSTATUS(i18n("Saving file to database...")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); KUrl url = oldUrl; if (!dialog->checkDrivers()) { delete dialog; return (false); } while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { url = dialog->selectedURL(); // If the protocol is SQL for the old and new, and the hostname and database names match // Let the user know that the current database cannot be saved on top of itself. if (url.protocol() == "sql" && oldUrl.protocol() == "sql" && oldUrl.host() == url.host() && oldUrl.queryItem("driver") == url.queryItem("driver") && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { KMessageBox::sorry(this, i18n("Cannot save to current database.")); } else { try { rc = d->m_myMoneyView->saveAsDatabase(url); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot save to current database: %1", e.what())); } } } delete dialog; if (rc) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(url.pathOrUrl()); writeLastUsedFile(url.pathOrUrl()); } d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileCloseWindow() { KMSTATUS(i18n("Closing window...")); if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } close(); } void KMyMoneyApp::slotFileClose() { bool okToSelect = true; // check if transaction editor is open and ask user what he wants to do slotTransactionsCancelOrEnter(okToSelect); if (!okToSelect) return; // no update status here, as we might delete the status too early. if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } d->closeFile(); } 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 KMainWindow* w = 0; 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()) { w = (*w_it); break; } } // We will only quit if all windows were processed and not cancelled if (w == 0) kapp->quit(); } else kapp->quit(); } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneyGlobalSettings::setHideReconciledTransactions(toggleAction("view_hide_reconciled_transactions")->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneyGlobalSettings::setHideUnusedCategory(toggleAction("view_hide_unused_categories")->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(toggleAction("debug_traces")->isChecked() ? 1 : 0); } void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = toggleAction("debug_timers")->isChecked(); } const QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = statusBar()->itemText(ID_STATUS_MSG); 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(); statusBar()->changeItem(currentMessage, ID_STATUS_MSG); 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(); } else { // update QTime currentTime = QTime::currentTime(); // only process painting if last update is at least 250 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 250) { 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_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } KMSTATUS(i18n("Viewing personal data...")); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPayee user = file->user(); QPointer newFileDlg = new KNewFileDlg(user.name(), user.address(), user.city(), user.state(), user.postcode(), user.telephone(), user.email(), this, i18n("Edit Personal Data")); if (newFileDlg->exec() == QDialog::Accepted && newFileDlg != 0) { user.setName(newFileDlg->userNameText); user.setAddress(newFileDlg->userStreetText); user.setCity(newFileDlg->userTownText); user.setState(newFileDlg->userCountyText); user.setPostcode(newFileDlg->userPostcodeText); user.setTelephone(newFileDlg->userTelephoneText); user.setEmail(newFileDlg->userEmailText); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", e.what())); } } delete newFileDlg; } void KMyMoneyApp::slotFileFileInfo() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } QFile g("kmymoney.dump"); g.open(QIODevice::WriteOnly); QDataStream st(&g); MyMoneyStorageDump dumper; dumper.writeStream(st, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); } void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); int rc; QPointer dlg = new KLoadTemplateDlg(); if ((rc = 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(0, i18n("Error"), i18n("Unable to import template(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString locale = KGlobal::locale()->language() + "_" + KGlobal::locale()->country().toUpper(); QString savePath = KGlobal::dirs()->saveLocation("appdata", "templates/" + locale, true); QString newName = KFileDialog::getSaveFileName(savePath, i18n("*.kmt|KMyMoney template files\n" "*|All files"), this, i18n("Save as...")); // // 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(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(newName); } delete dlg; } } } void KMyMoneyApp::slotQifImport() { if (d->m_qifReader == 0) { // FIXME: the menu entry for qif import should be disabled here QPointer dlg = new KImportDlg(0); if (dlg->exec() == QDialog::Accepted && dlg != 0) { KMSTATUS(i18n("Importing file...")); d->m_qifReader = new MyMoneyQifReader; // remove all kmm-statement-#.txt files d->unlinkStatementXML(); connect(d->m_qifReader, SIGNAL(importFinished()), this, SLOT(slotQifImportFinished())); d->m_qifReader->setURL(dlg->filename()); d->m_qifReader->setProfile(dlg->profile()); d->m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); d->m_qifReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); d->m_ft = new MyMoneyFileTransaction(); d->m_collectingStatements = true; d->m_statementResults.clear(); if (!d->m_qifReader->startImport()) { // if the import failed to start make sure that slotQifImportFinished is called otherwise the application will be left disabled QTimer::singleShot(0, this, SLOT(slotQifImportFinished())); } } delete dlg; slotUpdateActions(); } } void KMyMoneyApp::slotQifImportFinished() { if (d->m_qifReader != 0) { d->m_qifReader->finishImport(); d->m_ft->commit(); d->m_collectingStatements = false; KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); #if 0 // fixme: re-enable the QIF import menu options if (d->m_qifReader->finishImport()) { if (verifyImportedData(d->m_qifReader->account())) { // keep the new data set, destroy the backup copy delete d->m_engineBackup; d->m_engineBackup = 0; } } if (d->m_engineBackup != 0) { // user cancelled, destroy the updated set and keep the backup copy IMyMoneyStorage* data = file->storage(); if (data != 0) { file->detachStorage(data); delete data; } file->attachStorage(d->m_engineBackup); d->m_engineBackup = 0; } #endif // update the views as they might still contain invalid data // from the import session. The same applies for the window caption d->m_myMoneyView->slotRefreshViews(); updateCaption(); delete d->m_qifReader; d->m_qifReader = 0; } delete d->m_ft; d->m_ft = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); slotUpdateActions(); } void KMyMoneyApp::slotGncImport() { if (d->m_myMoneyView->fileOpen()) { switch (KMessageBox::questionYesNoCancel(0, i18n("You cannot import GnuCash data into an existing file. Do you wish to save this file?"), PACKAGE)) { case KMessageBox::Yes: slotFileSave(); break; case KMessageBox::No: d->closeFile(); break; default: return; } } KMSTATUS(i18n("Importing a GnuCash file.")); QPointer dialog = new KFileDialog(KUrl("kfiledialog:///kmymoney-import"), i18n("*|GnuCash files\n*|All files"), this); dialog->setMode(KFile::File | KFile::ExistingOnly); if (dialog->exec() == QDialog::Accepted && dialog != 0) { // call the importer if (d->m_myMoneyView->readFile(dialog->selectedUrl())) { // imported files don't have a name d->m_fileName = KUrl(); updateCaption(); emit fileLoaded(d->m_fileName); } } delete dialog; } void KMyMoneyApp::slotAccountChart() { if (!d->m_selectedAccount.id().isEmpty()) { QPointer dlg = new KBalanceChartDlg(d->m_selectedAccount, this); dlg->exec(); delete dlg; } } // // KMyMoneyApp::slotStatementImport() is for testing only. The MyMoneyStatement // is not intended to be exposed to users in XML form. // void KMyMoneyApp::slotStatementImport() { bool result = false; KMSTATUS(i18n("Importing an XML Statement.")); QPointer dialog = new KFileDialog(KUrl("kfiledialog:///kmymoney-import"), i18n("*.xml|XML files\n*|All files"), this); dialog->setMode(KFile::Files | KFile::ExistingOnly); if (dialog->exec() == QDialog::Accepted && dialog != 0) { KUrl::List files = dialog->selectedUrls(); d->m_collectingStatements = (files.count() > 1); foreach (const KUrl& url, files) { qDebug("Processing '%s'", qPrintable(url.path())); result |= slotStatementImport(url.path()); } /* QFile f( dialog->selectedURL().path() ); f.open( QIODevice::ReadOnly ); QString error = "Unable to parse file"; QDomDocument* doc = new QDomDocument; if(doc->setContent(&f, FALSE)) { if ( doc->doctype().name() == "KMYMONEY-STATEMENT" ) { QDomElement rootElement = doc->documentElement(); if(!rootElement.isNull()) { QDomNode child = rootElement.firstChild(); if(!child.isNull() && child.isElement()) { MyMoneyStatement s; if ( s.read(child.toElement()) ) result = slotStatementImport(s); else error = "File does not contain any statements"; } } } else error = "File is not a KMyMoney Statement"; } delete doc; if ( !result ) { QMessageBox::critical( this, i18n("Critical Error"), i18n("Unable to read file %1: %2").arg( dialog->selectedURL().path()).arg(error), QMessageBox::Ok, 0 ); }*/ } delete dialog; if (!result) { // re-enable all standard widgets setEnabled(true); } } bool KMyMoneyApp::slotStatementImport(const QString& url) { bool result = false; MyMoneyStatement s; if (MyMoneyStatement::readXMLFile(s, url)) result = slotStatementImport(s); else KMessageBox::error(this, i18n("Error importing %1: This file is not a valid KMM statement file.", url), i18n("Invalid Statement")); return result; } bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s) { bool result = false; // keep a copy of the statement if (KMyMoneySettings::logImportedStatements()) { QString logFile = QString("%1/kmm-statement-%2.txt").arg(KMyMoneySettings::logPath()).arg(d->m_statementXMLindex++); MyMoneyStatement::writeXMLFile(s, logFile); } // we use an object on the heap here, so that we can check the presence // of it during slotUpdateActions() by looking at the pointer. d->m_smtReader = new MyMoneyStatementReader; d->m_smtReader->setAutoCreatePayee(true); d->m_smtReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); QStringList messages; result = d->m_smtReader->import(s, messages); bool transactionAdded = d->m_smtReader->anyTransactionAdded(); // get rid of the statement reader and tell everyone else // about the destruction by setting the pointer to zero delete d->m_smtReader; d->m_smtReader = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); if (!d->m_collectingStatements) KMessageBox::informationList(this, i18n("The statement has been processed with the following results:"), messages, i18n("Statement stats")); else if (transactionAdded) d->m_statementResults += messages; slotUpdateActions();// Re-enable menu items after import via plugin. return result; } void KMyMoneyApp::slotQifExport() { KMSTATUS(i18n("Exporting file...")); QPointer dlg = new KExportDlg(0); if (dlg->exec() == QDialog::Accepted && dlg != 0) { if (okToWriteFile(dlg->filename())) { MyMoneyQifWriter writer; connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), dlg->accountSelected(), dlg->categorySelected(), dlg->startDate(), dlg->endDate()); } } delete dlg; } bool KMyMoneyApp::okToWriteFile(const KUrl& url) { // check if the file exists and warn the user bool reallySaveFile = true; if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, this)) { if (KMessageBox::warningYesNo(this, QString("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.pathOrUrl()) + QString(""), 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 KConfigDialog* dlg = new KConfigDialog(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self()); // create the pages ... KSettingsGeneral* generalPage = new KSettingsGeneral(); KSettingsRegister* registerPage = new KSettingsRegister(); KSettingsHome* homePage = new KSettingsHome(); KSettingsSchedules* schedulesPage = new KSettingsSchedules(); KSettingsGpg* encryptionPage = new KSettingsGpg(); KSettingsColors* colorsPage = new KSettingsColors(); KSettingsFonts* fontsPage = new KSettingsFonts(); KSettingsOnlineQuotes* onlineQuotesPage = new KSettingsOnlineQuotes(); KSettingsForecast* forecastPage = new KSettingsForecast(); KSettingsPlugins* pluginsPage = new KSettingsPlugins(); KSettingsReports* reportsPage = new KSettingsReports(); dlg->addPage(generalPage, i18nc("General settings", "General"), "system-run"); dlg->addPage(homePage, i18n("Home"), "go-home"); dlg->addPage(registerPage, i18nc("Ledger view settings", "Ledger"), "view-financial-list"); dlg->addPage(schedulesPage, i18n("Scheduled transactions"), "view-pim-calendar"); dlg->addPage(onlineQuotesPage, i18n("Online Quotes"), "preferences-system-network"); dlg->addPage(reportsPage, i18nc("Report settings", "Reports"), "office-chart-bar"); dlg->addPage(forecastPage, i18nc("Forecast settings", "Forecast"), "view-financial-forecast"); dlg->addPage(encryptionPage, i18n("Encryption"), "kgpg"); dlg->addPage(colorsPage, i18n("Colors"), "preferences-desktop-color"); dlg->addPage(fontsPage, i18n("Fonts"), "preferences-desktop-font"); dlg->addPage(pluginsPage, i18n("Plugins"), "network-disconnect"); dlg->setHelp("details.settings", "kmymoney"); connect(dlg, SIGNAL(settingsChanged(QString)), this, SLOT(slotUpdateConfiguration())); connect(dlg, SIGNAL(cancelClicked()), schedulesPage, SLOT(slotResetRegion())); connect(dlg, SIGNAL(okClicked()), pluginsPage, SLOT(slotSavePlugins())); connect(dlg, SIGNAL(defaultClicked()), pluginsPage, SLOT(slotDefaultsPlugins())); dlg->show(); } void KMyMoneyApp::slotUpdateConfiguration() { MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); d->m_myMoneyView->updateViewType(); // update the sql storage module settings MyMoneyStorageSql::setPrecision(KMyMoneyGlobalSettings::pricePrecision()); MyMoneyStorageSql::setStartDate(KMyMoneyGlobalSettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneyGlobalSettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneyGlobalSettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneyGlobalSettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneyGlobalSettings::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->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setCustomColors(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(RECOVER_KEY_ID)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KGlobal::config(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } void KMyMoneyApp::slotBackupFile() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->m_myMoneyView->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_fileName.isEmpty()) return; if (!d->m_fileName.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_fileName.url()), i18n("Local files only")); return; } QPointer backupDlg = new KBackupDlg(this); #ifdef Q_OS_WIN backupDlg->mountCheckBox->setEnabled(false); #endif int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBox->isChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->txtMountPoint->text(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); } } 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_fileName.fileName()); QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix(); d->m_backupFile = d->m_mountpoint + '/' + d->m_fileName.fileName(); KMyMoneyUtils::appendCorrectFileExt(d->m_backupFile, today); // check if file already exists and ask what to do QFile f(d->m_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", d->m_backupFile)); d->m_backupState = BACKUP_COPYING; d->m_backupResult = !KIO::NetAccess::upload(d->m_fileName.toLocalFile(), d->m_backupFile, this); slotBackupHandleEvents(); return !d->m_backupResult; } 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_backupResult) { qDebug() << "copy to" << d->m_fileName << "okay"; if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up") + QString(" - %1").arg(d->m_backupFile), i18n("Backup")); slotBackupFinish(); } } else { QString errorString = KIO::NetAccess::lastErrorString(); qDebug() << "copy failed with" << errorString; KMessageBox::information(this, i18n("Error copying file to device") + QString(" - %1").arg(errorString), 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 %d!", d->m_backupState); progressCallback(-1, -1, QString()); ready(); break; } } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(this); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } void KMyMoneyApp::slotQifProfileEditor() { QPointer editor = new MyMoneyQifProfileEditor(true, this); editor->setObjectName("QIF Profile Editor"); editor->exec(); delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneyGlobalSettings::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::slotFindTransaction() { if (d->m_searchDlg == 0) { d->m_searchDlg = new KFindTransactionDlg(); connect(d->m_searchDlg, SIGNAL(destroyed()), this, SLOT(slotCloseSearchDialog())); connect(d->m_searchDlg, SIGNAL(transactionSelected(QString,QString)), d->m_myMoneyView, SLOT(slotLedgerSelected(QString,QString))); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KMyMoneyApp::slotCloseSearchDialog() { if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = 0; } void KMyMoneyApp::createInstitution(MyMoneyInstitution& institution) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Cannot add institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionNew() { MyMoneyInstitution institution; slotInstitutionNew(institution); } void KMyMoneyApp::slotInstitutionNew(MyMoneyInstitution& institution) { institution.clearId(); QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { institution = dlg->institution(); createInstitution(institution); } delete dlg; } void KMyMoneyApp::slotInstitutionEdit(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; // make sure the selected object has an id if (d->m_selectedInstitution.id().isEmpty()) return; try { MyMoneyFile* file = MyMoneyFile::instance(); //grab a pointer to the view, regardless of it being a account or institution view. MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); // bankSuccess is not checked anymore because d->m_file->institution will throw anyway QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { file->modifyInstitution(dlg->institution()); ft.commit(); slotSelectInstitution(file->institution(dlg->institution().id())); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store institution: %1", e.what())); } } delete dlg; } catch (const MyMoneyException &e) { if (!obj.id().isEmpty()) KMessageBox::information(this, i18n("Unable to edit institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionDelete() { MyMoneyFile *file = MyMoneyFile::instance(); try { MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); if ((KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the institution %1?

", institution.name()))) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { file->removeInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } const MyMoneyAccount& KMyMoneyApp::findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const { static MyMoneyAccount nullAccount; MyMoneyFile* file = MyMoneyFile::instance(); QList parents; try { // search by id if (!acc.id().isEmpty()) { return file->account(acc.id()); } // collect the parents. in case parent does not have an id, we scan the all top-level accounts if (parent.id().isEmpty()) { parents << file->asset(); parents << file->liability(); parents << file->income(); parents << file->expense(); parents << file->equity(); } else { parents << parent; } QList::const_iterator it_p; for (it_p = parents.constBegin(); it_p != parents.constEnd(); ++it_p) { MyMoneyAccount parentAccount = *it_p; // search by name (allow hierarchy) int pos; // check for ':' in the name and use it as separator for a hierarchy QString name = acc.name(); bool notFound = false; while ((pos = name.indexOf(MyMoneyFile::AccountSeparator)) != -1) { QString part = name.left(pos); QString remainder = name.mid(pos + 1); const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); // if account has not been found, continue with next top level parent if (existingAccount.id().isEmpty()) { notFound = true; break; } parentAccount = existingAccount; name = remainder; } if (notFound) continue; const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, name); if (!existingAccount.id().isEmpty()) { if (acc.accountType() != MyMoneyAccount::UnknownAccountType) { if (acc.accountType() != existingAccount.accountType()) continue; } return existingAccount; } } } catch (const MyMoneyException &e) { KMessageBox::error(0, i18n("Unable to find account: %1", e.what())); } return nullAccount; } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile* file = MyMoneyFile::instance(); // make sure we have a currency. If none is assigned, we assume base currency if (newAccount.currencyId().isEmpty()) newAccount.setCurrencyId(file->baseCurrency().id()); MyMoneyFileTransaction ft; try { int pos; // check for ':' in the name and use it as separator for a hierarchy while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeparator)) != -1) { QString part = newAccount.name().left(pos); QString remainder = newAccount.name().mid(pos + 1); const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); if (existingAccount.id().isEmpty()) { newAccount.setName(part); file->addAccount(newAccount, parentAccount); parentAccount = newAccount; } else { parentAccount = existingAccount; } newAccount.setParentAccountId(QString()); // make sure, there's no parent newAccount.clearId(); // and no id set for adding newAccount.removeAccountIds(); // and no sub-account ids newAccount.setName(remainder); } const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == MyMoneyAccount::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->addAccount(newAccount, parentAccount); // in case of a loan account, we add the initial payment if ((newAccount.accountType() == MyMoneyAccount::Loan || newAccount.accountType() == MyMoneyAccount::AssetLoan) && !newAccount.value("kmm-loan-payment-acc").isEmpty() && !newAccount.value("kmm-loan-payment-date").isEmpty()) { MyMoneyAccountLoan acc(newAccount); MyMoneyTransaction t; MyMoneySplit a, b; a.setAccountId(acc.id()); b.setAccountId(acc.value("kmm-loan-payment-acc").toLatin1()); a.setValue(acc.loanAmount()); if (acc.accountType() == MyMoneyAccount::Loan) a.setValue(-a.value()); a.setShares(a.value()); b.setValue(-a.value()); b.setShares(b.value()); a.setMemo(i18n("Loan payout")); b.setMemo(i18n("Loan payout")); t.setPostDate(QDate::fromString(acc.value("kmm-loan-payment-date"), Qt::ISODate)); newAccount.deletePair("kmm-loan-payment-acc"); newAccount.deletePair("kmm-loan-payment-date"); MyMoneyFile::instance()->modifyAccount(newAccount); t.addSplit(a); t.addSplit(b); file->addTransaction(t); file->createOpeningBalanceTransaction(newAccount, openingBal); // in case of an investment account we check if we should create // a brokerage account } else if (newAccount.accountType() == MyMoneyAccount::Investment && !brokerageAccount.name().isEmpty()) { file->addAccount(brokerageAccount, parentAccount); // set a link from the investment account to the brokerage account file->modifyAccount(newAccount); file->createOpeningBalanceTransaction(brokerageAccount, openingBal); } else file->createOpeningBalanceTransaction(newAccount, openingBal); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", e.what())); } } void KMyMoneyApp::slotCategoryNew(const QString& name, QString& id) { MyMoneyAccount account; account.setName(name); slotCategoryNew(account, MyMoneyFile::instance()->expense()); id = account.id(); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (KMessageBox::questionYesNo(this, QString("%1").arg(i18n("

The category %1 currently does not exist. Do you want to create it?

The parent account will default to %2 but can be changed in the following dialog.

", account.name(), parent.name())), i18n("Create category"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "CreateNewCategories") == KMessageBox::Yes) { createCategory(account, parent); } else { // 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 = KGlobal::config(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("CreateNewCategories")); } } } void KMyMoneyApp::slotCategoryNew() { MyMoneyAccount parent; MyMoneyAccount account; // Preselect the parent account by looking at the current selected account/category if (!d->m_selectedAccount.id().isEmpty() && d->m_selectedAccount.isIncomeExpense()) { MyMoneyFile* file = MyMoneyFile::instance(); try { parent = file->account(d->m_selectedAccount.id()); } catch (const MyMoneyException &) { } } createCategory(account, parent); } void KMyMoneyApp::createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (!parent.id().isEmpty()) { try { // make sure parent account exists MyMoneyFile::instance()->account(parent.id()); account.setParentAccountId(parent.id()); account.setAccountType(parent.accountType()); } catch (const MyMoneyException &) { } } QPointer dialog = new KNewAccountDlg(account, false, true, 0, i18n("Create a new Category")); dialog->setOpeningBalanceShown(false); dialog->setOpeningDateShown(false); if (dialog->exec() == QDialog::Accepted && dialog != 0) { MyMoneyAccount parentAccount, brokerageAccount; account = dialog->account(); parentAccount = dialog->parentAccount(); createAccount(account, parentAccount, brokerageAccount, MyMoneyMoney()); } delete dialog; } void KMyMoneyApp::slotAccountNew() { MyMoneyAccount acc; acc.setInstitutionId(d->m_selectedInstitution.id()); acc.setOpeningDate(KMyMoneyGlobalSettings::firstFiscalDate()); slotAccountNew(acc); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard* wizard = new NewAccountWizard::Wizard(); connect(wizard, SIGNAL(createInstitution(MyMoneyInstitution&)), this, SLOT(slotInstitutionNew(MyMoneyInstitution&))); connect(wizard, SIGNAL(createAccount(MyMoneyAccount&)), this, SLOT(slotAccountNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); connect(wizard, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); wizard->setAccount(account); if (wizard->exec() == QDialog::Accepted) { MyMoneyAccount acc = wizard->account(); if (!(acc == MyMoneyAccount())) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // create the account MyMoneyAccount parent = wizard->parentAccount(); file->addAccount(acc, parent); // tell the wizard about the account id which it // needs to create a possible schedule and transactions wizard->setAccount(acc); // store a possible conversion rate for the currency if (acc.currencyId() != file->baseCurrency().id()) { file->addPrice(wizard->conversionRate()); } // create the opening balance transaction if any file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); // create the payout transaction for loans if any MyMoneyTransaction payoutTransaction = wizard->payoutTransaction(); if (payoutTransaction.splits().count() > 0) { file->addTransaction(payoutTransaction); } // create a brokerage account if selected MyMoneyAccount brokerageAccount = wizard->brokerageAccount(); if (!(brokerageAccount == MyMoneyAccount())) { file->addAccount(brokerageAccount, parent); } // create a possible schedule MyMoneySchedule sch = wizard->schedule(); if (!(sch == MyMoneySchedule())) { MyMoneyFile::instance()->addSchedule(sch); if (acc.isLoan()) { MyMoneyAccountLoan accLoan = MyMoneyFile::instance()->account(acc.id()); accLoan.setSchedule(sch.id()); acc = accLoan; MyMoneyFile::instance()->modifyAccount(acc); } } ft.commit(); account = acc; } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to create account: %1", e.what())); } } } delete wizard; } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { QString dontShowAgain = "CreateNewInvestments"; if (KMessageBox::questionYesNo(this, QString("") + i18n("The security %1 currently does not exist as sub-account of %2. " "Do you want to create it?", account.name(), parent.name()) + QString(""), i18n("Create security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontShowAgain) == KMessageBox::Yes) { KNewInvestmentWizard dlg; dlg.setName(account.name()); if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(parent.id()); account = dlg.account(); } } else { // in case the user said no but turned on the don't show again selection, we will enable // the message no matter what. Otherwise, the user is not able to use this feature // in the future anymore. KMessageBox::enableMessage(dontShowAgain); } } void KMyMoneyApp::slotInvestmentNew() { KNewInvestmentWizard dlg; if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(d->m_selectedAccount.id()); } } void KMyMoneyApp::slotInvestmentEdit() { KNewInvestmentWizard dlg(d->m_selectedInvestment); if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(d->m_selectedAccount.id()); } } void KMyMoneyApp::slotInvestmentDelete() { if (KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the investment %1?

", d->m_selectedInvestment.name()), i18n("Delete investment"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeleteInvestment") == KMessageBox::Yes) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->removeAccount(d->m_selectedInvestment); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete investment: %1", e.what())); } } else { // 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 = KGlobal::config(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("DeleteInvestment")); } } } void KMyMoneyApp::slotOnlinePriceUpdate() { if (!d->m_selectedInvestment.id().isEmpty()) { QPointer dlg = new KEquityPriceUpdateDlg(0, d->m_selectedInvestment.currencyId()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { dlg->storePrices(); } delete dlg; } } void KMyMoneyApp::slotManualPriceUpdate() { if (!d->m_selectedInvestment.id().isEmpty()) { try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); MyMoneySecurity currency = MyMoneyFile::instance()->security(security.tradingCurrency()); const MyMoneyPrice &price = MyMoneyFile::instance()->price(security.id(), currency.id()); signed64 fract = MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision()); QPointer calc = new KCurrencyCalculator(security, currency, MyMoneyMoney::ONE, price.rate(currency.id()), price.date(), fract); calc->setupPriceEditor(); // The dialog takes care of adding the price if necessary calc->exec(); delete calc; } catch (const MyMoneyException &e) { qDebug("Error in price update: %s", qPrintable(e.what())); } } } 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("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() == MyMoneyAccount::Loan || newAccount.accountType() == MyMoneyAccount::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", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } } void KMyMoneyApp::slotAccountDelete() { if (d->m_selectedAccount.id().isEmpty()) return; // need an account ID MyMoneyFile* file = MyMoneyFile::instance(); // can't delete standard accounts or account which still have transactions assigned if (file->isStandardAccount(d->m_selectedAccount.id())) return; // check if the account is referenced by a transaction or schedule MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckAccount); skip.setBit(IMyMoneyStorage::RefCheckInstitution); skip.setBit(IMyMoneyStorage::RefCheckPayee); skip.setBit(IMyMoneyStorage::RefCheckTag); skip.setBit(IMyMoneyStorage::RefCheckSecurity); skip.setBit(IMyMoneyStorage::RefCheckCurrency); skip.setBit(IMyMoneyStorage::RefCheckPrice); bool hasReference = file->isReferenced(d->m_selectedAccount, skip); // make sure we only allow transactions in a 'category' (income/expense account) switch (d->m_selectedAccount.accountType()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: break; default: // if the account is still referenced if (hasReference) { return; } break; } // if we get here and still have transactions referencing the account, we // need to check with the user to possibly re-assign them to a different account bool needAskUser = true; bool exit = false; MyMoneyFileTransaction ft; if (hasReference) { // show transaction reassignment dialog needAskUser = false; KCategoryReassignDlg* dlg = new KCategoryReassignDlg(this); QString categoryId = dlg->show(d->m_selectedAccount); delete dlg; // and kill the dialog if (categoryId.isEmpty()) return; // the user aborted the dialog, so let's abort as well MyMoneyAccount newCategory = file->account(categoryId); try { { KMSTATUS(i18n("Adjusting transactions...")); /* d->m_selectedAccount.id() is the old id, categoryId the new one Now search all transactions and schedules that reference d->m_selectedAccount.id() and replace that with categoryId. */ // get the list of all transactions that reference the old account MyMoneyTransactionFilter filter(d->m_selectedAccount.id()); filter.setReportAllSplits(false); QList tlist; QList::iterator it_t; file->transactionList(tlist, filter); slotStatusProgressBar(0, tlist.count()); int cnt = 0; for (it_t = tlist.begin(); it_t != tlist.end(); ++it_t) { slotStatusProgressBar(++cnt, 0); MyMoneyTransaction t = (*it_t); if (t.replaceId(categoryId, d->m_selectedAccount.id())) file->modifyTransaction(t); } slotStatusProgressBar(tlist.count(), 0); } // now fix all schedules { KMSTATUS(i18n("Adjusting scheduled transactions...")); QList slist = file->scheduleList(d->m_selectedAccount.id()); QList::iterator it_s; int cnt = 0; slotStatusProgressBar(0, slist.count()); for (it_s = slist.begin(); it_s != slist.end(); ++it_s) { slotStatusProgressBar(++cnt, 0); MyMoneySchedule sch = (*it_s); if (sch.replaceId(categoryId, d->m_selectedAccount.id())) { file->modifySchedule(sch); } } slotStatusProgressBar(slist.count(), 0); } // now fix all budgets { KMSTATUS(i18n("Adjusting budgets...")); QList blist = file->budgetList(); QList::const_iterator it_b; for (it_b = blist.constBegin(); it_b != blist.constEnd(); ++it_b) { if ((*it_b).hasReferenceTo(d->m_selectedAccount.id())) { MyMoneyBudget b = (*it_b); MyMoneyBudget::AccountGroup fromBudget = b.account(d->m_selectedAccount.id()); MyMoneyBudget::AccountGroup toBudget = b.account(categoryId); toBudget += fromBudget; b.setAccount(toBudget, categoryId); b.removeReference(d->m_selectedAccount.id()); file->modifyBudget(b); } } slotStatusProgressBar(blist.count(), 0); } } catch (MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to exchange category %1 with category %2. Reason: %3", d->m_selectedAccount.name(), newCategory.name(), e.what())); exit = true; } slotStatusProgressBar(-1, -1); } if (exit) return; // retain the account name for a possible later usage in the error message box // since the account removal notifies the views the selected account can be changed // so we make sure by doing this that we display the correct name in the error message QString selectedAccountName = d->m_selectedAccount.name(); // at this point, we must not have a reference to the account // to be deleted anymore switch (d->m_selectedAccount.accountGroup()) { // special handling for categories to allow deleting of empty subcategories case MyMoneyAccount::Income: case MyMoneyAccount::Expense: { // open a compound statement here to be able to declare variables // which would otherwise not work within a case label. // case A - only a single, unused category without subcats selected if (d->m_selectedAccount.accountList().isEmpty()) { if (!needAskUser || (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to delete category %1?", selectedAccountName) + QString("")) == KMessageBox::Yes)) { try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Unable to delete category %1. Cause: %2", selectedAccountName, e.what()) + QString("")); } } return; } // case B - we have some subcategories, maybe the user does not want to // delete them all, but just the category itself? MyMoneyAccount parentAccount = file->account(d->m_selectedAccount.parentAccountId()); QStringList accountsToReparent; int result = KMessageBox::questionYesNoCancel(this, QString("") + i18n("Do you want to delete category %1 with all its sub-categories or only " "the category itself? If you only delete the category itself, all its sub-categories " "will be made sub-categories of %2.", selectedAccountName, parentAccount.name()) + QString(""), QString(), KGuiItem(i18n("Delete all")), KGuiItem(i18n("Just the category"))); if (result == KMessageBox::Cancel) return; // cancel pressed? ok, no delete then... // "No" means "Just the category" and that means we need to reparent all subaccounts bool need_confirmation = false; // case C - User only wants to delete the category itself if (result == KMessageBox::No) accountsToReparent = d->m_selectedAccount.accountList(); else { // case D - User wants to delete all subcategories, now check all subcats of // d->m_selectedAccount and remember all that cannot be deleted and // must be "reparented" for (QStringList::const_iterator it = d->m_selectedAccount.accountList().begin(); it != d->m_selectedAccount.accountList().end(); ++it) { // reparent account if a transaction is assigned if (file->transactionCount(*it) != 0) accountsToReparent.push_back(*it); else if (!file->account(*it).accountList().isEmpty()) { // or if we have at least one sub-account that is used for transactions if (!file->hasOnlyUnusedAccounts(file->account(*it).accountList())) { accountsToReparent.push_back(*it); //kDebug() << "subaccount not empty"; } } } if (!accountsToReparent.isEmpty()) need_confirmation = true; } if (!accountsToReparent.isEmpty() && need_confirmation) { if (KMessageBox::questionYesNo(this, i18n("

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

", selectedAccountName, parentAccount.name())) != KMessageBox::Yes) { return; // user gets wet feet... } } // all good, now first reparent selected sub-categories try { MyMoneyAccount parent = file->account(d->m_selectedAccount.parentAccountId()); for (QStringList::const_iterator it = accountsToReparent.constBegin(); it != accountsToReparent.constEnd(); ++it) { MyMoneyAccount child = file->account(*it); file->reparentAccount(child, parent); } // reload the account because the sub-account list might have changed d->m_selectedAccount = file->account(d->m_selectedAccount.id()); // now recursively delete remaining sub-categories file->removeAccountList(d->m_selectedAccount.accountList()); // don't forget to update d->m_selectedAccount, because we still have a copy of // the old account list, which is no longer valid d->m_selectedAccount = file->account(d->m_selectedAccount.id()); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Unable to delete a sub-category of category %1. Reason: %2", selectedAccountName, e.what()) + QString("")); return; } } break; // the category/account is deleted after the switch default: if (!d->m_selectedAccount.accountList().isEmpty()) return; // can't delete accounts which still have subaccounts if (KMessageBox::questionYesNo(this, i18n("

Do you really want to " "delete account %1?

", selectedAccountName)) != KMessageBox::Yes) { return; // ok, you don't want to? why did you click then, hmm? } } // switch; try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, e.what())); } } void KMyMoneyApp::slotAccountEdit() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { if (d->m_selectedAccount.accountType() != MyMoneyAccount::Loan && d->m_selectedAccount.accountType() != MyMoneyAccount::AssetLoan) { QString caption; bool category = false; switch (d->m_selectedAccount.accountGroup()) { default: caption = i18n("Edit account '%1'", d->m_selectedAccount.name()); break; case MyMoneyAccount::Expense: case MyMoneyAccount::Income: caption = i18n("Edit category '%1'", d->m_selectedAccount.name()); category = true; break; } // set a status message so that the application can't be closed until the editing is done slotStatusMsg(caption); QString tid = file->openingBalanceTransaction(d->m_selectedAccount); MyMoneyTransaction t; MyMoneySplit s0, s1; QPointer dlg = new KNewAccountDlg(d->m_selectedAccount, true, category, 0, caption); if (category) { dlg->setOpeningBalanceShown(false); dlg->setOpeningDateShown(false); tid.clear(); } else { if (!tid.isEmpty()) { try { t = file->transaction(tid); s0 = t.splitByAccount(d->m_selectedAccount.id()); s1 = t.splitByAccount(d->m_selectedAccount.id(), false); dlg->setOpeningBalance(s0.shares()); if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) { dlg->setOpeningBalance(-s0.shares()); } } catch (const MyMoneyException &e) { kDebug(2) << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n"; tid.clear(); } } } // check for online modules QMap::const_iterator it_plugin = d->m_onlinePlugins.constEnd(); const MyMoneyKeyValueContainer& kvp = d->m_selectedAccount.onlineBankingSettings(); if (!kvp["provider"].isEmpty()) { // if we have an online provider for this account, we need to check // that we have the corresponding plugin. If that exists, we ask it // to provide an additional tab for the account editor. it_plugin = d->m_onlinePlugins.constFind(kvp["provider"]); if (it_plugin != d->m_onlinePlugins.constEnd()) { QString name; QWidget *w = (*it_plugin)->accountConfigTab(d->m_selectedAccount, name); dlg->addTab(w, name); } } if (dlg != 0 && dlg->exec() == QDialog::Accepted) { try { MyMoneyFileTransaction ft; MyMoneyAccount account = dlg->account(); MyMoneyAccount parent = dlg->parentAccount(); if (it_plugin != d->m_onlinePlugins.constEnd()) { account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings())); } MyMoneyMoney bal = dlg->openingBalance(); if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) { bal = -bal; } // we need to modify first, as reparent would override all other changes file->modifyAccount(account); if (account.parentAccountId() != parent.id()) { file->reparentAccount(account, parent); } if (!tid.isEmpty() && dlg->openingBalance().isZero()) { file->removeTransaction(t); } else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) { s0.setShares(bal); s0.setValue(bal); t.modifySplit(s0); s1.setShares(-bal); s1.setValue(-bal); t.modifySplit(s1); t.setPostDate(account.openingDate()); file->modifyTransaction(t); } else if (tid.isEmpty() && !dlg->openingBalance().isZero()) { file->createOpeningBalanceTransaction(d->m_selectedAccount, bal); } ft.commit(); // reload the account object as it might have changed in the meantime slotSelectAccount(file->account(account.id())); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to modify account '%1'. Cause: %2", d->m_selectedAccount.name(), e.what())); } } delete dlg; ready(); } else { QPointer wizard = new KEditLoanWizard(d->m_selectedAccount); connect(wizard, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (wizard->exec() == QDialog::Accepted && wizard != 0) { MyMoneySchedule sch; try { MyMoneySchedule sch = file->schedule(d->m_selectedAccount.value("schedule").toLatin1()); } catch (const MyMoneyException &e) { qDebug() << "schedule" << d->m_selectedAccount.value("schedule").toLatin1() << "not found"; } if (!(d->m_selectedAccount == wizard->account()) || !(sch == wizard->schedule())) { MyMoneyFileTransaction ft; try { file->modifyAccount(wizard->account()); if (!sch.id().isEmpty()) { sch = wizard->schedule(); } try { file->schedule(sch.id()); file->modifySchedule(sch); ft.commit(); } catch (const MyMoneyException &) { try { if(sch.transaction().splitCount() >= 2) { file->addSchedule(sch); } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Cannot add schedule: '%s'", qPrintable(e.what())); } } } catch (const MyMoneyException &e) { qDebug("Unable to modify account %s: '%s'", qPrintable(d->m_selectedAccount.name()), qPrintable(e.what())); } } } delete wizard; } } } } QList > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount) { static const int NR_OF_STEPS_LIMIT = 300000; static const int PROGRESSBAR_STEPS = 1000; QList > result = transactions; KMSTATUS(i18n("Running automatic reconciliation")); int progressBarIndex = 0; kmymoney->slotStatusProgressBar(progressBarIndex, NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS); // optimize the most common case - all transactions should be cleared QListIterator > itTransactionSplitResult(result); MyMoneyMoney transactionsBalance; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); transactionsBalance += transactionSplit.second.shares(); } if (amount == transactionsBalance) { result = transactions; return result; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // only one transaction is uncleared itTransactionSplitResult.toFront(); int index = 0; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); if (transactionsBalance - transactionSplit.second.shares() == amount) { result.removeAt(index); return result; } index++; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // more than one transaction is uncleared - apply the algorithm result.clear(); const MyMoneySecurity &security = MyMoneyFile::instance()->security(account.currencyId()); double precision = 0.1 / account.fraction(security); QList sumList; sumList << MyMoneyMoney(); QMap > > sumToComponentsMap; // compute the possible matches QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); QListIterator itSum(sumList); QList tempList; while (itSum.hasNext()) { const MyMoneyMoney &sum = itSum.next(); QList > splitIds; splitIds << qMakePair(transactionSplit.first.id(), transactionSplit.second.id()); if (sumToComponentsMap.contains(sum)) { if (sumToComponentsMap.value(sum).contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { continue; } splitIds.append(sumToComponentsMap.value(sum)); } tempList << transactionSplit.second.shares() + sum; sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds; int size = sumToComponentsMap.size(); if (size % PROGRESSBAR_STEPS == 0) { kmymoney->slotStatusProgressBar(progressBarIndex++, 0); } if (size > NR_OF_STEPS_LIMIT) { return result; // it's taking too much resources abort the algorithm } } QList unionList; unionList.append(tempList); unionList.append(sumList); qSort(unionList); sumList.clear(); MyMoneyMoney smallestSumFromUnion = unionList.first(); sumList.append(smallestSumFromUnion); QListIterator itUnion(unionList); while (itUnion.hasNext()) { MyMoneyMoney sumFromUnion = itUnion.next(); if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) { smallestSumFromUnion = sumFromUnion; sumList.append(sumFromUnion); } } } kmymoney->slotStatusProgressBar(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0); if (sumToComponentsMap.contains(amount)) { QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); const QList > &splitIds = sumToComponentsMap.value(amount); if (splitIds.contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { result.append(transactionSplit); } } } #ifdef KMM_DEBUG qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ", qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size()); #endif kmymoney->slotStatusProgressBar(-1, -1); return result; } void KMyMoneyApp::slotAccountReconcileStart() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount account; // we cannot reconcile standard accounts if (!file->isStandardAccount(d->m_selectedAccount.id())) { // check if we can reconcile this account // it make's sense for asset and liability accounts try { // check if we have overdue schedules for this account QList schedules = file->scheduleList(d->m_selectedAccount.id(), MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); if (schedules.count() > 0) { if (KMessageBox::questionYesNo(this, i18n("KMyMoney has detected some overdue scheduled transactions for this account. Do you want to enter those scheduled transactions now?"), i18n("Scheduled transactions found")) == KMessageBox::Yes) { QMap skipMap; bool processedOne; KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter; do { processedOne = false; QList::const_iterator it_sch; for (it_sch = schedules.constBegin(); (rc != KMyMoneyUtils::Cancel) && (it_sch != schedules.constEnd()); ++it_sch) { MyMoneySchedule sch(*(it_sch)); // and enter it if it is not on the skip list if (skipMap.find((*it_sch).id()) == skipMap.end()) { rc = enterSchedule(sch, false, true); if (rc == KMyMoneyUtils::Ignore) { skipMap[(*it_sch).id()] = true; } } } // reload list (maybe this schedule needs to be added again) schedules = file->scheduleList(d->m_selectedAccount.id(), MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); } while (processedOne); } } account = file->account(d->m_selectedAccount.id()); // get rid of previous run. delete d->m_endingBalanceDlg; d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this); if (account.isAssetLiability()) { connect(d->m_endingBalanceDlg, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); connect(d->m_endingBalanceDlg, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) { if (KMyMoneyGlobalSettings::autoReconciliation()) { MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance(); MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance(); QDate endDate = d->m_endingBalanceDlg->statementDate(); QList > transactionList; MyMoneyTransactionFilter filter(account.id()); filter.addState(MyMoneyTransactionFilter::cleared); filter.addState(MyMoneyTransactionFilter::notReconciled); filter.setDateFilter(QDate(), endDate); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); QList > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance); if (!result.empty()) { QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?"); if (KMessageBox::questionYesNo(this, message, i18n("Automatic reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "AcceptAutomaticReconciliation") == KMessageBox::Yes) { // mark the transactions cleared KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions; d->m_selectedTransactions.clear(); QListIterator > itTransactionSplitResult(result); while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second)); } // mark all transactions in d->m_selectedTransactions as 'Cleared' markTransaction(MyMoneySplit::Cleared); d->m_selectedTransactions = oldSelection; } } } if (d->m_myMoneyView->startReconciliation(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance())) { // check if the user requests us to create interest // or charge transactions. MyMoneyTransaction ti = d->m_endingBalanceDlg->interestTransaction(); MyMoneyTransaction tc = d->m_endingBalanceDlg->chargeTransaction(); MyMoneyFileTransaction ft; try { if (ti != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(ti); } if (tc != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(tc); } ft.commit(); } catch (const MyMoneyException &e) { qWarning("interest transaction not stored: '%s'", qPrintable(e.what())); } // reload the account object as it might have changed in the meantime d->m_reconciliationAccount = file->account(account.id()); slotUpdateActions(); } } } } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotAccountReconcileFinish() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // retrieve list of all transactions that are not reconciled or cleared QList > transactionList; MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id()); filter.addState(MyMoneyTransactionFilter::cleared); filter.addState(MyMoneyTransactionFilter::notReconciled); filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate()); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate()); MyMoneyMoney actBalance, clearedBalance; actBalance = clearedBalance = balance; // walk the list of transactions to figure out the balance(s) QList >::const_iterator it; for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled) { clearedBalance -= (*it).second.shares(); } } if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) { QString message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n" "Are you sure you want to finish the reconciliation?"); if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No) return; } MyMoneyFileTransaction ft; // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); // only update the last statement balance here, if we haven't a newer one due // to download of online statements. if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty() || QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) { d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString()); // in case we override the last statement balance here, we have to make sure // that we don't show the online balance anymore, as it might be different d->m_reconciliationAccount.deletePair("lastImportedTransactionDate"); } d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate()); // keep a record of this reconciliation d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); d->m_reconciliationAccount.deletePair("lastReconciledBalance"); d->m_reconciliationAccount.deletePair("statementBalance"); d->m_reconciliationAccount.deletePair("statementDate"); try { // update the account data file->modifyAccount(d->m_reconciliationAccount); /* // collect the list of cleared splits for this account filter.clear(); filter.addAccount(d->m_reconciliationAccount.id()); filter.addState(MyMoneyTransactionFilter::cleared); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); */ // walk the list of transactions/splits and mark the cleared ones as reconciled QList >::iterator it; for (it = transactionList.begin(); it != transactionList.end(); ++it) { MyMoneySplit sp = (*it).second; // skip the ones that are not marked cleared if (sp.reconcileFlag() != MyMoneySplit::Cleared) continue; // always retrieve a fresh copy of the transaction because we // might have changed it already with another split MyMoneyTransaction t = file->transaction((*it).first.id()); sp.setReconcileFlag(MyMoneySplit::Reconciled); sp.setReconcileDate(d->m_endingBalanceDlg->statementDate()); t.modifySplit(sp); // update the engine ... file->modifyTransaction(t); // ... and the list (*it) = qMakePair(t, sp); } ft.commit(); // reload account data from engine as the data might have changed in the meantime d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); emit accountReconciled(d->m_reconciliationAccount, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->previousBalance(), d->m_endingBalanceDlg->endingBalance(), transactionList); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting cleared to reconcile"); } } // Turn off reconciliation mode d->m_reconciliationAccount = MyMoneyAccount(); slotUpdateActions(); } void KMyMoneyApp::slotAccountReconcilePostpone() { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString()); d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString()); d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate)); try { file->modifyAccount(d->m_reconciliationAccount); ft.commit(); d->m_reconciliationAccount = MyMoneyAccount(); slotUpdateActions(); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting last reconcile info into account"); ft.rollback(); d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); } } } void KMyMoneyApp::slotAccountOpen(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; MyMoneyFile* file = MyMoneyFile::instance(); QString id = d->m_selectedAccount.id(); // if the caller passed a non-empty object, we need to select that if (!obj.id().isEmpty()) { id = obj.id(); } // we cannot reconcile standard accounts if (!file->isStandardAccount(id)) { // check if we can open this account // currently it make's sense for asset and liability accounts try { MyMoneyAccount account = file->account(id); d->m_myMoneyView->slotLedgerSelected(account.id()); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::enableCloseAccountAction(const MyMoneyAccount& acc) { switch (canCloseAccount(acc)) { case KMyMoneyUtils::AccountCanClose: { action("account_close")->setEnabled(true); break; } case KMyMoneyUtils::AccountBalanceNonZero: { action("account_close")->setEnabled(false); action("account_close")->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); break; } case KMyMoneyUtils::AccountChildrenOpen: { action("account_close")->setEnabled(false); action("account_close")->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); break; } case KMyMoneyUtils::AccountScheduleReference: { action("account_close")->setEnabled(false); action("account_close")->setToolTip(i18n("This account is still included in an active schedule")); break; } } } KMyMoneyUtils::CanCloseAccountCodeE KMyMoneyApp::canCloseAccount(const MyMoneyAccount& acc) const { // balance must be zero if (!acc.balance().isZero()) return KMyMoneyUtils::AccountBalanceNonZero; // all children must be already closed QStringList::const_iterator it_a; for (it_a = acc.accountList().constBegin(); it_a != acc.accountList().constEnd(); ++it_a) { MyMoneyAccount a = MyMoneyFile::instance()->account(*it_a); if (!a.isClosed()) { return KMyMoneyUtils::AccountChildrenOpen; } } // there must be no unfinished schedule referencing the account QList list = MyMoneyFile::instance()->scheduleList(); QList::const_iterator it_l; for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { if ((*it_l).isFinished()) continue; if ((*it_l).hasReferenceTo(acc.id())) return KMyMoneyUtils::AccountScheduleReference; } return KMyMoneyUtils::AccountCanClose; } void KMyMoneyApp::slotAccountClose() { MyMoneyAccount a; if (!d->m_selectedInvestment.id().isEmpty()) a = d->m_selectedInvestment; else if (!d->m_selectedAccount.id().isEmpty()) a = d->m_selectedAccount; if (a.id().isEmpty()) return; // need an account ID MyMoneyFileTransaction ft; try { a.setClosed(true); MyMoneyFile::instance()->modifyAccount(a); ft.commit(); if (KMyMoneyGlobalSettings::hideClosedAccounts()) { KMessageBox::information(this, QString("") + i18n("You have closed this account. It remains in the system because you have transactions which still refer to it, but it is not shown in the views. You can make it visible again by going to the View menu and selecting Show all accounts or by deselecting the Do not show closed accounts setting.") + QString(""), i18n("Information"), "CloseAccountInfo"); } } catch (const MyMoneyException &) { } } void KMyMoneyApp::slotAccountReopen() { MyMoneyAccount a; if (!d->m_selectedInvestment.id().isEmpty()) a = d->m_selectedInvestment; else if (!d->m_selectedAccount.id().isEmpty()) a = d->m_selectedAccount; if (a.id().isEmpty()) return; // need an account ID MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { while (a.isClosed()) { a.setClosed(false); file->modifyAccount(a); a = file->account(a.parentAccountId()); } ft.commit(); } catch (const MyMoneyException &) { } } 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(), 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(), e.what())); } } void KMyMoneyApp::slotAccountTransactionReport() { // Generate a transaction report that contains transactions for only the // currently selected account. if (!d->m_selectedAccount.id().isEmpty()) { MyMoneyReport report( MyMoneyReport::eAccount, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("%1 YTD Account Transactions", d->m_selectedAccount.name()), i18n("Generated Report") ); report.setGroup(i18n("Transactions")); report.addAccount(d->m_selectedAccount.id()); d->m_myMoneyView->slotShowReport(report); } } void KMyMoneyApp::slotScheduleNew() { slotScheduleNew(MyMoneyTransaction()); } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, MyMoneySchedule::occurrenceE occurrence) { MyMoneySchedule schedule; schedule.setOccurrence(occurrence); // if the schedule is based on an existing transaction, // we take the post date and project it to the next // schedule in a month. if (_t != MyMoneyTransaction()) { MyMoneyTransaction t(_t); schedule.setTransaction(t); if (occurrence != MyMoneySchedule::OCCUR_ONCE) schedule.setNextDueDate(schedule.nextPayment(t.postDate())); } QPointer dlg = new KEditScheduleDlg(schedule, this); TransactionEditor* transactionEditor = dlg->startEdit(); if (transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { schedule = dlg->schedule(); MyMoneyFile::instance()->addSchedule(schedule); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to add scheduled transaction: %1", e.what()), i18n("Add scheduled transaction")); } } } delete transactionEditor; delete dlg; } void KMyMoneyApp::slotScheduleEdit() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); KEditScheduleDlg* sched_dlg = 0; KEditLoanWizard* loan_wiz = 0; switch (schedule.type()) { case MyMoneySchedule::TYPE_BILL: case MyMoneySchedule::TYPE_DEPOSIT: case MyMoneySchedule::TYPE_TRANSFER: sched_dlg = new KEditScheduleDlg(schedule, this); d->m_transactionEditor = sched_dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart()); if (sched_dlg->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = sched_dlg->schedule(); // Check whether the new Schedule Date // is at or before the lastPaymentDate // If it is, ask the user whether to clear the // lastPaymentDate const QDate& next = sched.nextDueDate(); const QDate& last = sched.lastPayment(); if (next.isValid() && last.isValid() && next <= last) { // Entered a date effectively no later // than previous payment. Date would be // updated automatically so we probably // want to clear it. Let's ask the user. if (KMessageBox::questionYesNo(this, QString("") + i18n("You have entered a scheduled transaction date of %1. Because the scheduled transaction was last paid on %2, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset. Do you want to reset the last payment date?", KGlobal::locale()->formatDate(next, KLocale::ShortDate), KGlobal::locale()->formatDate(last, KLocale::ShortDate)) + QString(""), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) { sched.setLastPayment(QDate()); } } MyMoneyFile::instance()->modifySchedule(sched); // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } deleteTransactionEditor(); } delete sched_dlg; break; case MyMoneySchedule::TYPE_LOANPAYMENT: loan_wiz = new KEditLoanWizard(schedule.account(2)); connect(loan_wiz, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(loan_wiz, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (loan_wiz->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule()); MyMoneyFile::instance()->modifyAccount(loan_wiz->account()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } delete loan_wiz; break; case MyMoneySchedule::TYPE_ANY: break; } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDelete() { if (!d->m_selectedSchedule.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); QString msg = i18n("

Are you sure you want to delete the scheduled transaction %1?

", d->m_selectedSchedule.name()); if (sched.type() == MyMoneySchedule::TYPE_LOANPAYMENT) { msg += QString(" "); msg += i18n("In case of loan payments it is currently not possible to recreate the scheduled transaction."); } if (KMessageBox::questionYesNo(this, msg) == KMessageBox::No) return; MyMoneyFile::instance()->removeSchedule(sched); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to remove scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("schedule_duplicate")->isEnabled()) { MyMoneySchedule sch = d->m_selectedSchedule; sch.clearId(); sch.setLastPayment(QDate()); sch.setName(i18nc("Copy of scheduled transaction name", "Copy of %1", sch.name())); // make sure that we set a valid next due date if the original next due date is invalid if (!sch.nextDueDate().isValid()) sch.setNextDueDate(QDate::currentDate()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addSchedule(sch); ft.commit(); // select the new schedule in the view if (!d->m_selectedSchedule.id().isEmpty()) d->m_myMoneyView->slotScheduleSelected(sch.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to duplicate scheduled transaction: '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleSkip() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); skipSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::skipSchedule(MyMoneySchedule& schedule) { if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); if (!schedule.isFinished()) { if (schedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { QDate next = schedule.nextDueDate(); if (!schedule.isFinished() && (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to skip the %1 transaction scheduled for %2?", schedule.name(), KGlobal::locale()->formatDate(next, KLocale::ShortDate)) + QString(""))) == KMessageBox::Yes) { MyMoneyFileTransaction ft; schedule.setLastPayment(next); schedule.setNextDueDate(schedule.nextPayment(next)); MyMoneyFile::instance()->modifySchedule(schedule); ft.commit(); } } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, QString("") + i18n("Unable to skip scheduled transaction %1.", schedule.name()) + QString(""), e.what()); } } } void KMyMoneyApp::slotScheduleEnter() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); enterSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } KMyMoneyUtils::EnterScheduleResultCodeE KMyMoneyApp::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Cancel; if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); return rc; } QPointer dlg = new KEnterScheduleDlg(this, schedule); try { QDate origDueDate = schedule.nextDueDate(); dlg->showExtendedKeys(extendedKeys); d->m_transactionEditor = dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); MyMoneyTransaction torig, taccepted; d->m_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) kmymoney->action("transaction_cancel")->setEnabled(true); kmymoney->action("transaction_enter")->setEnabled(true); KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce; if (!autoEnter || !schedule.isFixed()) { for (; dlg != 0;) { rc = KMyMoneyUtils::Cancel; if (dlg->exec() == QDialog::Accepted && dlg != 0) { rc = dlg->resultCode(); if (rc == KMyMoneyUtils::Enter) { d->m_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, this); 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 == KMyMoneyUtils::Skip) { slotTransactionsCancel(); skipSchedule(schedule); } else { slotTransactionsCancel(); } } else { if (autoEnter) { if (KMessageBox::warningYesNo(this, 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(); } break; } } // if we still have the editor around here, the user did not cancel if ((d->m_transactionEditor != 0) && (dlg != 0)) { MyMoneyFileTransaction ft; try { MyMoneyTransaction t; // add the new transaction switch (action) { case KConfirmManualEnterDlg::UseOriginal: // setup widgets with original transaction data d->m_transactionEditor->setTransaction(dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front()); // and create a transaction based on that data taccepted = MyMoneyTransaction(); d->m_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; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId, false)) { if (!newId.isEmpty()) { MyMoneyTransaction 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 = KMyMoneyUtils::Enter; // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } deleteTransactionEditor(); } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } delete dlg; } return rc; } bool KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Payee")) { // Ask the user if that is what he intended to do? QString msg = QString("") + i18n("Do you want to add %1 as payer/receiver?", newnameBase) + QString(""); if (KMessageBox::questionYesNo(this, 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 = KGlobal::config(); 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("%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(this, i18n("Unable to add payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); doit = false; } } return doit; } void KMyMoneyApp::slotPayeeNew() { QString id; slotPayeeNew(i18n("New Payee"), id); // the callbacks should have made sure, that the payees view has been // updated already. So we search for the id in the list of items // and select it. emit payeeCreated(id); } bool KMyMoneyApp::payeeInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotPayeeDelete() { if (d->m_selectedPayees.isEmpty()) return; // shouldn't happen // get confirmation from user QString prompt; if (d->m_selectedPayees.size() == 1) prompt = i18n("

Do you really want to remove the payee %1?

", d->m_selectedPayees.front().name()); else prompt = i18n("Do you really want to remove all selected payees?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Payee")) == KMessageBox::No) return; payeeReassign(KPayeeReassignDlg::TypeDelete); } void KMyMoneyApp::slotPayeeMerge() { if (d->m_selectedPayees.size() < 1) return; // shouldn't happen if (KMessageBox::questionYesNo(this, i18n("

Do you really want to merge the selected payees?"), i18n("Merge Payees")) == KMessageBox::No) return; if (payeeReassign(KPayeeReassignDlg::TypeMerge)) // clean selection since we just deleted the selected payees slotSelectPayees(QList()); } bool KMyMoneyApp::payeeReassign(int type) { if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount)) return false; MyMoneyFile * file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { // create a transaction filter that contains all payees selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedPayees.constBegin(); it != d->m_selectedPayees.constEnd(); ++it) { f.addPayee((*it).id()); } // request a list of all transactions that still use the payees in question QList translist = file->transactionList(f); // kDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees"; // now get a list of all schedules that make use of one of the payees QList all_schedules = file->scheduleList(); QList used_schedules; for (QList::ConstIterator it = all_schedules.constBegin(); it != all_schedules.constEnd(); ++it) { // loop over all splits in the transaction of the schedule for (QList::ConstIterator s_it = (*it).transaction().splits().constBegin(); s_it != (*it).transaction().splits().constEnd(); ++s_it) { // is the payee in the split to be deleted? if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { used_schedules.push_back(*it); // remember this schedule break; } } } // kDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees"; // and a list of all loan accounts that references one of the payees QList allAccounts; QList usedAccounts; file->accountList(allAccounts); foreach (const MyMoneyAccount &account, allAccounts) { if (account.isLoan()) { MyMoneyAccountLoan loanAccount(account); foreach (const MyMoneyPayee &payee, d->m_selectedPayees) { if (loanAccount.hasReferenceTo(payee.id())) { usedAccounts.append(account); } } } } MyMoneyPayee newPayee; bool addToMatchList = false; // if at least one payee is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) { // first create list with all non-selected payees QList remainingPayees; if (type == KPayeeReassignDlg::TypeMerge) { remainingPayees = d->m_selectedPayees; } else { remainingPayees = file->payeeList(); QList::iterator it_p; for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) { if (d->m_selectedPayees.contains(*it_p)) { it_p = remainingPayees.erase(it_p); } else { ++it_p; } } } // show error message if no payees remain if (remainingPayees.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. " "Currently you have all payees selected. However, at least one payee must remain so " "that the transaction/scheduled transaction or loan account can be reassigned.")); return false; } // show transaction reassignment dialog KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast(type), this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString payee_id = dlg->show(remainingPayees); addToMatchList = dlg->addToMatchList(); delete dlg; // and kill the dialog if (payee_id.isEmpty()) return false; // the user aborted the dialog, so let's abort as well // try to get selected payee. If not possible and we are merging payees, // then we create a new one try { newPayee = file->payee(payee_id); } catch (const MyMoneyException &e) { if (type == KPayeeReassignDlg::TypeMerge) { // it's ok to use payee_id for both arguments since the first is const, // so it's garantee not to change its content if (!slotPayeeNew(payee_id, payee_id)) return false; // the user aborted the dialog, so let's abort as well newPayee = file->payee(payee_id); } else { return false; } } // TODO : check if we have a report that explicitively uses one of our payees // and issue an appropriate warning try { QList::iterator s_it; // now loop over all transactions and reassign payee for (QList::iterator it = translist.begin(); it != translist.end(); ++it) { // create a copy of the splits list in the transaction QList splits = (*it).splits(); // loop over all splits for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { // if the split is assigned to one of the selected payees, we need to modify it if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { (*s_it).setPayeeId(payee_id); // first modify payee in current split // then modify the split in our local copy of the transaction list (*it).modifySplit(*s_it); // this does not modify the list object 'splits'! } } // for - Splits file->modifyTransaction(*it); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign payees for (QList::iterator it = used_schedules.begin(); it != used_schedules.end(); ++it) { // create copy of transaction in current schedule MyMoneyTransaction trans = (*it).transaction(); // create copy of lists of splits QList splits = trans.splits(); for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { (*s_it).setPayeeId(payee_id); trans.modifySplit(*s_it); // does not modify the list object 'splits'! } } // for - Splits // store transaction in current schedule (*it).setTransaction(trans); file->modifySchedule(*it); // modify the schedule in the MyMoney engine } // for - Schedules // reassign the payees in the loans that reference the deleted payees foreach (const MyMoneyAccount &account, usedAccounts) { MyMoneyAccountLoan loanAccount(account); loanAccount.setPayee(payee_id); file->modifyAccount(loanAccount); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reassign payee of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { // if !translist.isEmpty() if (type == KPayeeReassignDlg::TypeMerge) { KMessageBox::sorry(this, i18n("Nothing to merge."), i18n("Merge Payees")); return false; } } bool ignorecase; QStringList payeeNames; MyMoneyPayee::payeeMatchType matchType = newPayee.matchData(ignorecase, payeeNames); QStringList deletedPayeeNames; // now loop over all selected payees and remove them for (QList::iterator it = d->m_selectedPayees.begin(); it != d->m_selectedPayees.end(); ++it) { if (newPayee.id() != (*it).id()) { if (addToMatchList) { deletedPayeeNames << (*it).name(); } file->removePayee(*it); } } // if we initially have no matching turned on, we just ignore the case (default) if (matchType == MyMoneyPayee::matchDisabled) ignorecase = true; // update the destination payee if this was requested by the user if (addToMatchList && deletedPayeeNames.count() > 0) { // add new names to the list // TODO: it would be cool to somehow shrink the list to make better use // of regular expressions at this point. For now, we leave this task // to the user himeself. QStringList::const_iterator it_n; for (it_n = deletedPayeeNames.constBegin(); it_n != deletedPayeeNames.constEnd(); ++it_n) { if (matchType == MyMoneyPayee::matchKey) { // make sure we really need it and it is not caught by an existing regexp QStringList::const_iterator it_k; for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) { QRegExp exp(*it_k, ignorecase ? Qt::CaseInsensitive : Qt::CaseSensitive); if (exp.indexIn(*it_n) != -1) break; } if (it_k == payeeNames.constEnd()) payeeNames << QRegExp::escape(*it_n); } else if (payeeNames.contains(*it_n) == 0) payeeNames << QRegExp::escape(*it_n); } // and update the payee in the engine context // make sure to turn on matching for this payee in the right mode newPayee.setMatchData(MyMoneyPayee::matchKey, ignorecase, payeeNames); file->modifyPayee(newPayee); } ft.commit(); // If we just deleted the payees, they sure don't exist anymore slotSelectPayees(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to remove payee(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } return true; } void KMyMoneyApp::slotTagNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Tag")) { // Ask the user if that is what he intended to do? QString msg = QString("") + i18n("Do you want to add %1 as tag?", newnameBase) + QString(""); if (KMessageBox::questionYesNo(this, 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 = KGlobal::config(); 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("%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(this, i18n("Unable to add tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotTagNew() { QString id; slotTagNew(i18n("New Tag"), id); // the callbacks should have made sure, that the tags view has been // updated already. So we search for the id in the list of items // and select it. emit tagCreated(id); } bool KMyMoneyApp::tagInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotTagDelete() { if (d->m_selectedTags.isEmpty()) return; // shouldn't happen MyMoneyFile * file = MyMoneyFile::instance(); // first create list with all non-selected tags QList remainingTags = file->tagList(); QList::iterator it_ta; for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) { if (d->m_selectedTags.contains(*it_ta)) { it_ta = remainingTags.erase(it_ta); } else { ++it_ta; } } // get confirmation from user QString prompt; if (d->m_selectedTags.size() == 1) prompt = i18n("

Do you really want to remove the tag %1?

", d->m_selectedTags.front().name()); else prompt = i18n("Do you really want to remove all selected tags?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { // create a transaction filter that contains all tags selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedTags.constBegin(); it != d->m_selectedTags.constEnd(); ++it) { f.addTag((*it).id()); } // request a list of all transactions that still use the tags in question QList translist = file->transactionList(f); // kDebug() << "[KTagsView::slotDeleteTag] " << translist.count() << " transaction still assigned to tags"; // now get a list of all schedules that make use of one of the tags QList all_schedules = file->scheduleList(); QList used_schedules; for (QList::ConstIterator it = all_schedules.constBegin(); it != all_schedules.constEnd(); ++it) { // loop over all splits in the transaction of the schedule for (QList::ConstIterator s_it = (*it).transaction().splits().constBegin(); s_it != (*it).transaction().splits().constEnd(); ++s_it) { for (int i = 0; i < (*s_it).tagIdList().size(); i++) { // is the tag in the split to be deleted? if (tagInList(d->m_selectedTags, (*s_it).tagIdList()[i])) { used_schedules.push_back(*it); // remember this schedule break; } } } } // kDebug() << "[KTagsView::slotDeleteTag] " << used_schedules.count() << " schedules use one of the selected tags"; MyMoneyTag newTag; // if at least one tag is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty()) { // show error message if no tags remain //FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction if (remainingTags.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. " "Currently you have all tags selected. However, at least one tag must remain so " "that the transaction/scheduled transaction can be reassigned.")); return; } // show transaction reassignment dialog KTagReassignDlg * dlg = new KTagReassignDlg(this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString tag_id = dlg->show(remainingTags); delete dlg; // and kill the dialog if (tag_id.isEmpty()) //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one. return; // the user aborted the dialog, so let's abort as well newTag = file->tag(tag_id); // TODO : check if we have a report that explicitively uses one of our tags // and issue an appropriate warning try { QList::iterator s_it; // now loop over all transactions and reassign tag for (QList::iterator it = translist.begin(); it != translist.end(); ++it) { // create a copy of the splits list in the transaction QList splits = (*it).splits(); // loop over all splits for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { QList tagIdList = (*s_it).tagIdList(); for (int i = 0; i < tagIdList.size(); i++) { // if the split is assigned to one of the selected tags, we need to modify it if (tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } (*s_it).setTagIdList(tagIdList); // first modify tag list in current split // then modify the split in our local copy of the transaction list (*it).modifySplit(*s_it); // this does not modify the list object 'splits'! } // for - Splits file->modifyTransaction(*it); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign tags for (QList::iterator it = used_schedules.begin(); it != used_schedules.end(); ++it) { // create copy of transaction in current schedule MyMoneyTransaction trans = (*it).transaction(); // create copy of lists of splits QList splits = trans.splits(); for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { QList tagIdList = (*s_it).tagIdList(); for (int i = 0; i < tagIdList.size(); i++) { if (tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } (*s_it).setTagIdList(tagIdList); trans.modifySplit(*s_it); // does not modify the list object 'splits'! } // for - Splits // store transaction in current schedule (*it).setTransaction(trans); file->modifySchedule(*it); // modify the schedule in the MyMoney engine } // for - Schedules } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reassign tag of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } // if !translist.isEmpty() // now loop over all selected tags and remove them for (QList::iterator it = d->m_selectedTags.begin(); it != d->m_selectedTags.end(); ++it) { file->removeTag(*it); } ft.commit(); // If we just deleted the tags, they sure don't exist anymore slotSelectTags(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to remove tag(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotCurrencyNew() { QString sid = KInputDialog::getText(i18n("New currency"), i18n("Enter ISO 4217 code for the new currency"), QString(), 0, 0, 0, 0, ">AAA"); if (!sid.isEmpty()) { QString id(sid); MyMoneySecurity currency(id, i18n("New currency")); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addCurrency(currency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot create new currency. %1", e.what()), i18n("New currency")); } emit currencyCreated(id); } } void KMyMoneyApp::slotCurrencyUpdate(const QString ¤cyId, const QString& currencyName, const QString& currencyTradingSymbol) { MyMoneyFile* file = MyMoneyFile::instance(); try { if (currencyName != d->m_selectedCurrency.name() || currencyTradingSymbol != d->m_selectedCurrency.tradingSymbol()) { MyMoneySecurity currency = file->currency(currencyId); currency.setName(currencyName); currency.setTradingSymbol(currencyTradingSymbol); MyMoneyFileTransaction ft; try { file->modifyCurrency(currency); d->m_selectedCurrency = currency; ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency")); } } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency")); } } void KMyMoneyApp::slotCurrencyDelete() { if (!d->m_selectedCurrency.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->removeCurrency(d->m_selectedCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot delete currency %1. %2", d->m_selectedCurrency.name(), e.what()), i18n("Delete currency")); } } } void KMyMoneyApp::slotCurrencySetBase() { if (!d->m_selectedCurrency.id().isEmpty()) { if (d->m_selectedCurrency.id() != MyMoneyFile::instance()->baseCurrency().id()) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(d->m_selectedCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", d->m_selectedCurrency.name(), e.what()), i18n("Set base currency")); } } } } void KMyMoneyApp::slotBudgetNew() { QDate date = QDate::currentDate(); date.setYMD(date.year(), 1, 1); QString 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(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotBudgetDelete() { if (d->m_selectedBudgets.isEmpty()) return; // shouldn't happen MyMoneyFile * file = MyMoneyFile::instance(); // get confirmation from user QString prompt; if (d->m_selectedBudgets.size() == 1) prompt = i18n("

Do you really want to remove the budget %1?

", d->m_selectedBudgets.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; MyMoneyFileTransaction ft; try { // now loop over all selected budgets and remove them for (QList::iterator it = d->m_selectedBudgets.begin(); it != d->m_selectedBudgets.end(); ++it) { file->removeBudget(*it); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to remove budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotBudgetCopy() { if (d->m_selectedBudgets.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_selectedBudgets.first(); budget.clearId(); budget.setName(i18n("Copy of %1", budget.name())); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotBudgetChangeYear() { if (d->m_selectedBudgets.size() == 1) { QStringList years; int current = 0; bool haveCurrent = false; MyMoneyBudget budget = *(d->m_selectedBudgets.begin()); for (int i = (QDate::currentDate().year() - 3); i < (QDate::currentDate().year() + 5); ++i) { years << QString("%1").arg(i); if (i == budget.budgetStart().year()) { haveCurrent = true; } if (!haveCurrent) ++current; } if (!haveCurrent) current = 0; bool ok = false; QString yearString = KInputDialog::getItem(i18n("Select year"), i18n("Budget year"), years, current, false, &ok, this); if (ok) { int year = yearString.toInt(0, 0); QDate newYear = QDate(year, 1, 1); if (newYear != budget.budgetStart()) { MyMoneyFileTransaction ft; try { budget.setBudgetStart(newYear); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } } } void KMyMoneyApp::slotBudgetForecast() { if (d->m_selectedBudgets.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_selectedBudgets.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 = KMyMoneyGlobalSettings::forecast(); forecast.createBudget(budget, historyStart, historyEnd, budgetStart, budgetEnd, true); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotKDELanguageSettings() { KMessageBox::information(this, i18n("Please be aware that changes made in the following dialog affect all KDE applications not only KMyMoney."), i18nc("Warning message box", "Warning"), "LanguageSettingsWarning"); QStringList args; args << "language"; QString error; int pid; KToolInvocation::kdeinitExec("kcmshell4", args, &error, &pid); } void KMyMoneyApp::slotNewFeature() { } void KMyMoneyApp::slotTransactionsDelete() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (!kmymoney->action("transaction_delete")->isEnabled()) return; if (d->m_selectedTransactions.isEmpty()) return; if (d->m_selectedTransactions.warnLevel() == 1) { if (KMessageBox::warningContinueCancel(0, i18n("At least one split of the selected transactions has been reconciled. " "Do you wish to delete the transactions anyway?"), i18n("Transaction already reconciled")) == KMessageBox::Cancel) return; } QString msg = i18np("Do you really want to delete the selected transaction?", "Do you really want to delete all %1 selected transactions?", d->m_selectedTransactions.count()); if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) { KMSTATUS(i18n("Deleting transactions")); doDeleteTransactions(); } } void KMyMoneyApp::slotTransactionDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_duplicate")->isEnabled()) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int i = 0; int cnt = d->m_selectedTransactions.count(); KMSTATUS(i18n("Duplicating transactions")); slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; MyMoneyTransaction lt; try { for (it_t = list.begin(); it_t != list.end(); ++it_t) { MyMoneyTransaction t = (*it_t).transaction(); QList::iterator it_s; // wipe out any reconciliation information for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { (*it_s).setReconcileFlag(MyMoneySplit::NotReconciled); (*it_s).setReconcileDate(QDate()); (*it_s).setBankID(QString()); } // clear invalid data t.setEntryDate(QDate()); t.clearId(); // and set the post date to today t.setPostDate(QDate::currentDate()); MyMoneyFile::instance()->addTransaction(t); lt = t; slotStatusProgressBar(i++, 0); } ft.commit(); // select the new transaction in the ledger if (!d->m_selectedAccount.id().isEmpty()) d->m_myMoneyView->slotLedgerSelected(d->m_selectedAccount.id(), lt.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to duplicate transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } // switch off the progress bar slotStatusProgressBar(-1, -1); } } void KMyMoneyApp::doDeleteTransactions() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { it_t = list.begin(); while (it_t != list.end()) { // only remove those transactions that do not reference a closed account if (!file->referencesClosedAccount((*it_t).transaction())) { file->removeTransaction((*it_t).transaction()); // remove all those references in the list of selected transactions // that refer to the same transaction we just removed so that we // will not be caught by an exception later on (see bko #285310) KMyMoneyRegister::SelectedTransactions::iterator it_td = it_t; ++it_td; while (it_td != list.end()) { if ((*it_t).transaction().id() == (*it_td).transaction().id()) { it_td = list.erase(it_td); i++; // bump count of deleted transactions } else { ++it_td; } } } // need to ensure "nextCheckNumber" is still correct MyMoneyAccount acc = file->account((*it_t).split().accountId()); // the "lastNumberUsed" might have been the txn number deleted // so adjust it QString deletedNum = (*it_t).split().number(); // decrement deletedNum and set new "lastNumberUsed" QString num = KMyMoneyUtils::getAdjacentNumber(deletedNum, -1); acc.setValue("lastNumberUsed", num); file->modifyAccount(acc); list.erase(it_t); it_t = list.begin(); slotStatusProgressBar(i++, 0); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to delete transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } slotStatusProgressBar(-1, -1); } void KMyMoneyApp::slotTransactionsNew() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_new")->isEnabled()) { if (d->m_myMoneyView->createNewTransaction()) { d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) { // in case we entered a new transaction before and used a payee, // we reuse it here. Save the text to the edit widget, select it // so that hitting any character will start entering another payee. payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId); payeeEdit->lineEdit()->selectAll(); } if (d->m_transactionEditor) { connect(d->m_transactionEditor, SIGNAL(statusProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); connect(d->m_transactionEditor, SIGNAL(statusMsg(QString)), this, SLOT(slotStatusMsg(QString))); connect(d->m_transactionEditor, SIGNAL(scheduleTransaction(MyMoneyTransaction,MyMoneySchedule::occurrenceE)), this, SLOT(slotScheduleNew(MyMoneyTransaction,MyMoneySchedule::occurrenceE))); } slotUpdateActions(); } } } } void KMyMoneyApp::slotTransactionsEdit() { // qDebug("KMyMoneyApp::slotTransactionsEdit()"); // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_edit")->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); slotUpdateActions(); } } void KMyMoneyApp::deleteTransactionEditor() { // make sure, we don't use the transaction editor pointer // anymore from now on TransactionEditor* p = d->m_transactionEditor; d->m_transactionEditor = 0; delete p; } void KMyMoneyApp::slotTransactionsEditSplits() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_editsplits")->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); slotUpdateActions(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { QString id; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); d->m_transactionEditor->enterTransactions(id); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } deleteTransactionEditor(); slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsCancel() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_cancel")->isEnabled()) { // make sure, we block the enter function action("transaction_enter")->setEnabled(false); // qDebug("KMyMoneyApp::slotTransactionsCancel"); deleteTransactionEditor(); slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsEnter() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_enter")->isEnabled()) { // disable the action while we process it to make sure it's processed only once since // d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents // we could end up here twice which will cause a crash slotUpdateActions() will enable the action again kmymoney->action("transaction_enter")->setEnabled(false); if (d->m_transactionEditor) { QString accountId = d->m_selectedAccount.id(); QString newId; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId)) { KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !newId.isEmpty()) { d->m_lastPayeeEnteredId = payeeEdit->selectedItem(); } deleteTransactionEditor(); } if (!newId.isEmpty()) { d->m_myMoneyView->slotLedgerSelected(accountId, newId); } } slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsCancelOrEnter(bool& okToSelect) { static bool oneTime = false; if (!oneTime) { oneTime = true; QString dontShowAgain = "CancelOrEditTransaction"; // qDebug("KMyMoneyApp::slotCancelOrEndEdit"); if (d->m_transactionEditor) { if (KMyMoneyGlobalSettings::focusChangeIsEnter() && kmymoney->action("transaction_enter")->isEnabled()) { slotTransactionsEnter(); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } } else { // okToSelect is preset to true if a cancel of the dialog is useful and false if it is not int rc; KGuiItem noGuiItem = KStandardGuiItem::save(); KGuiItem yesGuiItem = KStandardGuiItem::discard(); KGuiItem cancelGuiItem = KStandardGuiItem::cont(); // if the transaction can't be entered make sure that it can't be entered by pressing no either if (!kmymoney->action("transaction_enter")->isEnabled()) { noGuiItem.setEnabled(false); noGuiItem.setToolTip(kmymoney->action("transaction_enter")->toolTip()); } if (okToSelect == true) { rc = KMessageBox::warningYesNoCancel(0, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain); } else { rc = KMessageBox::warningYesNo(0, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain); } switch (rc) { case KMessageBox::Yes: slotTransactionsCancel(); break; case KMessageBox::No: slotTransactionsEnter(); // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } break; case KMessageBox::Cancel: // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); okToSelect = false; break; } } } oneTime = false; } } void KMyMoneyApp::slotToggleReconciliationFlag() { markTransaction(MyMoneySplit::Unknown); } void KMyMoneyApp::slotMarkTransactionCleared() { markTransaction(MyMoneySplit::Cleared); } void KMyMoneyApp::slotMarkTransactionReconciled() { markTransaction(MyMoneySplit::Reconciled); } void KMyMoneyApp::slotMarkTransactionNotReconciled() { markTransaction(MyMoneySplit::NotReconciled); } void KMyMoneyApp::markTransaction(MyMoneySplit::reconcileFlagE flag) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // turn on signals before we modify the last entry in the list cnt--; MyMoneyFile::instance()->blockSignals(cnt != 0); // get a fresh copy MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); MyMoneySplit sp = t.splitById((*it_t).split().id()); if (sp.reconcileFlag() != flag) { if (flag == MyMoneySplit::Unknown) { if (d->m_reconciliationAccount.id().isEmpty()) { // in normal mode we cycle through all states switch (sp.reconcileFlag()) { case MyMoneySplit::NotReconciled: sp.setReconcileFlag(MyMoneySplit::Cleared); break; case MyMoneySplit::Cleared: sp.setReconcileFlag(MyMoneySplit::Reconciled); break; case MyMoneySplit::Reconciled: sp.setReconcileFlag(MyMoneySplit::NotReconciled); break; default: break; } } else { // in reconciliation mode we skip the reconciled state switch (sp.reconcileFlag()) { case MyMoneySplit::NotReconciled: sp.setReconcileFlag(MyMoneySplit::Cleared); break; case MyMoneySplit::Cleared: sp.setReconcileFlag(MyMoneySplit::NotReconciled); break; default: break; } } } else { sp.setReconcileFlag(flag); } t.modifySplit(sp); MyMoneyFile::instance()->modifyTransaction(t); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionsAccept() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // reload transaction in case it got changed during the course of this loop MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); if (t.isImported()) { t.setImported(false); if (!d->m_selectedAccount.id().isEmpty()) { QList list = t.splits(); QList::const_iterator it_s; for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { if ((*it_s).reconcileFlag() == MyMoneySplit::NotReconciled) { MyMoneySplit s = (*it_s); s.setReconcileFlag(MyMoneySplit::Cleared); t.modifySplit(s); } } } } MyMoneyFile::instance()->modifyTransaction(t); } if ((*it_t).split().isMatched()) { // reload split in case it got changed during the course of this loop MyMoneySplit s = t.splitById((*it_t).split().id()); TransactionMatcher matcher(d->m_selectedAccount); matcher.accept(t, s); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to accept transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionGotoAccount() { if (!d->m_accountGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides // d->m_accountGoto while calling slotUpdateActions() QString accountId = d->m_accountGoto; d->m_myMoneyView->slotLedgerSelected(accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionGotoPayee() { if (!d->m_payeeGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides // d->m_payeeGoto and d->m_selectedAccount while calling slotUpdateActions() QString payeeId = d->m_payeeGoto; QString accountId = d->m_selectedAccount.id(); d->m_myMoneyView->slotPayeeSelected(payeeId, accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionCreateSchedule() { if (d->m_selectedTransactions.count() == 1) { // make sure to have the current selected split as first split in the schedule MyMoneyTransaction t = d->m_selectedTransactions[0].transaction(); MyMoneySplit s = d->m_selectedTransactions[0].split(); QString splitId = s.id(); s.clearId(); s.setReconcileFlag(MyMoneySplit::NotReconciled); s.setReconcileDate(QDate()); t.removeSplits(); t.addSplit(s); const QList& splits = d->m_selectedTransactions[0].transaction().splits(); QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).id() != splitId) { MyMoneySplit s0 = (*it_s); s0.clearId(); s0.setReconcileFlag(MyMoneySplit::NotReconciled); s0.setReconcileDate(QDate()); t.addSplit(s0); } } slotScheduleNew(t); } } void KMyMoneyApp::slotTransactionAssignNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextNumber(); } void KMyMoneyApp::slotTransactionCombine() { qDebug("slotTransactionCombine() not implemented yet"); } void KMyMoneyApp::slotTransactionCopySplits() { MyMoneyFile* file = MyMoneyFile::instance(); if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; KMyMoneyRegister::SelectedTransaction selectedSourceTransaction; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: selectedSourceTransaction = st; multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { MyMoneyFileTransaction ft; try { const MyMoneyTransaction& sourceTransaction = selectedSourceTransaction.transaction(); const MyMoneySplit& sourceSplit = selectedSourceTransaction.split(); foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { MyMoneyTransaction t = st.transaction(); // don't process the source transaction if (sourceTransaction.id() == t.id()) { continue; } const MyMoneySplit& baseSplit = st.split(); if (t.splitCount() == 1) { foreach (const MyMoneySplit& split, sourceTransaction.splits()) { // Don't copy the source split, as we already have that // as part of the destination transaction if (split.id() == sourceSplit.id()) { continue; } MyMoneySplit sp(split); // clear the ID and reconciliation state sp.clearId(); sp.setReconcileFlag(MyMoneySplit::NotReconciled); sp.setReconcileDate(QDate()); // in case it is a simple transaction consisting of two splits, // we can adjust the share and value part of the second split we // just created. We need to keep a possible price in mind in case // of different currencies if (sourceTransaction.splitCount() == 2) { sp.setValue(-baseSplit.value()); sp.setShares(-(baseSplit.shares() * baseSplit.price())); } t.addSplit(sp); } file->modifyTransaction(t); } } ft.commit(); } catch (const MyMoneyException &) { qDebug() << "transactionCopySplits() failed"; } } } } void KMyMoneyApp::slotMoveToAccount(const QString& id) { // close the menu, if it is still open QWidget* w = factory()->container("transaction_context_menu", this); if (w && w->isVisible()) { w->close(); } if (!d->m_selectedTransactions.isEmpty()) { MyMoneyFileTransaction ft; try { KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { d->moveInvestmentTransaction(d->m_selectedAccount.id(), id, (*it_t).transaction()); } else { QList::const_iterator it_s; bool changed = false; MyMoneyTransaction t = (*it_t).transaction(); for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { MyMoneySplit s = (*it_s); s.setAccountId(id); t.modifySplit(s); changed = true; } } if (changed) { MyMoneyFile::instance()->modifyTransaction(t); } } } ft.commit(); } catch (const MyMoneyException &) { } } } // 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; for (QList::const_iterator it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { stockAccountId = (*it_s).accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = *it_s; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; QStringList accountList = toInvAcc.accountList(); for (QStringList::const_iterator it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) { if (MyMoneyFile::instance()->account((*it_a)).currencyId() == stockSecurityId) { newStockAccountId = (*it_a); 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::slotUpdateMoveToAccountMenu() { createTransactionMoveMenu(); // in case we were not able to create the selector, we // better get out of here. Anything else would cause // a crash later on (accountSet.load) if (!d->m_moveToAccountSelector) return; if (!d->m_selectedAccount.id().isEmpty()) { AccountSet accountSet; if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { accountSet.addAccountType(MyMoneyAccount::Investment); } else if (d->m_selectedAccount.isAssetLiability()) { accountSet.addAccountType(MyMoneyAccount::Checkings); accountSet.addAccountType(MyMoneyAccount::Savings); accountSet.addAccountType(MyMoneyAccount::Cash); accountSet.addAccountType(MyMoneyAccount::AssetLoan); accountSet.addAccountType(MyMoneyAccount::CertificateDep); accountSet.addAccountType(MyMoneyAccount::MoneyMarket); accountSet.addAccountType(MyMoneyAccount::Asset); accountSet.addAccountType(MyMoneyAccount::Currency); accountSet.addAccountType(MyMoneyAccount::CreditCard); accountSet.addAccountType(MyMoneyAccount::Loan); accountSet.addAccountType(MyMoneyAccount::Liability); } else if (d->m_selectedAccount.isIncomeExpense()) { accountSet.addAccountType(MyMoneyAccount::Income); accountSet.addAccountType(MyMoneyAccount::Expense); } accountSet.load(d->m_moveToAccountSelector); // remove those accounts that we currently reference KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { QList::const_iterator it_s; for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { d->m_moveToAccountSelector->removeItem((*it_s).accountId()); } } // remove those accounts from the list that are denominated // in a different currency QStringList list = d->m_moveToAccountSelector->accountList(); QList::const_iterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.currencyId() != d->m_selectedAccount.currencyId()) d->m_moveToAccountSelector->removeItem((*it_a)); } } } void KMyMoneyApp::slotTransactionMatch() { // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed QString transactionActionText = action("transaction_match")->text(); transactionActionText.remove('&'); if (transactionActionText == i18nc("Button text for match transaction", "Match")) transactionMatch(); else transactionUnmatch(); } void KMyMoneyApp::transactionUnmatch() { KMyMoneyRegister::SelectedTransactions::const_iterator it; MyMoneyFileTransaction ft; try { for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).split().isMatched()) { TransactionMatcher matcher(d->m_selectedAccount); matcher.unmatch((*it).transaction(), (*it).split()); } } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to unmatch the selected transactions"), e.what()); } } void KMyMoneyApp::transactionMatch() { if (d->m_selectedTransactions.count() != 2) return; MyMoneyTransaction startMatchTransaction; MyMoneyTransaction endMatchTransaction; MyMoneySplit startSplit; MyMoneySplit endSplit; KMyMoneyRegister::SelectedTransactions::const_iterator it; KMyMoneyRegister::SelectedTransactions toBeDeleted; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) { if (endMatchTransaction.id().isEmpty()) { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } else { //This is a second imported transaction, we still want to merge startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } } else if (!(*it).split().isMatched()) { if (startMatchTransaction.id().isEmpty()) { startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } else { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } } } #if 0 KMergeTransactionsDlg dlg(d->m_selectedAccount); dlg.addTransaction(startMatchTransaction); dlg.addTransaction(endMatchTransaction); if (dlg.exec() == QDialog::Accepted) #endif { MyMoneyFileTransaction ft; try { if (startMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No manually entered transaction selected for matching")); if (endMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No imported transaction selected for matching")); TransactionMatcher matcher(d->m_selectedAccount); matcher.match(startMatchTransaction, startSplit, endMatchTransaction, endSplit, true); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to match the selected transactions"), e.what()); } } } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); KMenu *menu = dynamic_cast(w); if (menu) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotShowTransactionContextMenu() { if (d->m_selectedTransactions.isEmpty() && d->m_selectedSchedule != MyMoneySchedule()) { showContextMenu("schedule_context_menu"); } else { showContextMenu("transaction_context_menu"); } } void KMyMoneyApp::slotShowInvestmentContextMenu() { showContextMenu("investment_context_menu"); } void KMyMoneyApp::slotShowScheduleContextMenu() { showContextMenu("schedule_context_menu"); } void KMyMoneyApp::slotShowAccountContextMenu(const MyMoneyObject& obj) { // qDebug("KMyMoneyApp::slotShowAccountContextMenu"); if (typeid(obj) != typeid(MyMoneyAccount)) return; const MyMoneyAccount& acc = dynamic_cast(obj); // if the selected account is actually a stock account, we // call the right slot instead if (acc.isInvest()) { showContextMenu("investment_context_menu"); } else if (acc.isIncomeExpense()) { showContextMenu("category_context_menu"); } else { showContextMenu("account_context_menu"); } } void KMyMoneyApp::slotShowInstitutionContextMenu(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; showContextMenu("institution_context_menu"); } void KMyMoneyApp::slotShowPayeeContextMenu() { showContextMenu("payee_context_menu"); } void KMyMoneyApp::slotShowTagContextMenu() { showContextMenu("tag_context_menu"); } void KMyMoneyApp::slotShowBudgetContextMenu() { showContextMenu("budget_context_menu"); } void KMyMoneyApp::slotShowCurrencyContextMenu() { showContextMenu("currency_context_menu"); } void KMyMoneyApp::slotShowPriceContextMenu() { showContextMenu("price_context_menu"); } void KMyMoneyApp::slotShowOnlineJobContextMenu() { showContextMenu("onlinejob_context_menu"); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } void KMyMoneyApp::updateCaption(bool skipActions) { QString caption; caption = d->m_fileName.fileName(); if (caption.isEmpty() && d->m_myMoneyView && d->m_myMoneyView->fileOpen()) caption = i18n("Untitled"); // MyMoneyFile::instance()->dirty() throws an exception, if // there's no storage object available. In this case, we // assume that the storage object is not changed. Actually, // this can only happen if we are newly created early on. bool modified; try { modified = MyMoneyFile::instance()->dirty(); } catch (const MyMoneyException &) { modified = false; skipActions = true; } #ifdef KMM_DEBUG caption += QString(" (%1 x %2)").arg(width()).arg(height()); #endif setCaption(caption, modified); if (!skipActions) { d->m_myMoneyView->enableViewsIfFileOpen(); slotUpdateActions(); } } void KMyMoneyApp::slotUpdateActions() { MyMoneyFile* file = MyMoneyFile::instance(); bool fileOpen = d->m_myMoneyView->fileOpen(); bool modified = file->dirty(); QWidget* w; action("open_database")->setEnabled(true); action("saveas_database")->setEnabled(fileOpen); action("file_save")->setEnabled(modified && !d->m_myMoneyView->isDatabase()); action("file_save_as")->setEnabled(fileOpen); action("file_close")->setEnabled(fileOpen); action("view_personal_data")->setEnabled(fileOpen); action("file_backup")->setEnabled(fileOpen && !d->m_myMoneyView->isDatabase()); action("file_print")->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); #ifdef KMM_DEBUG action("view_file_info")->setEnabled(fileOpen); action("file_dump")->setEnabled(fileOpen); #endif action("edit_find_transaction")->setEnabled(fileOpen); bool importRunning = (d->m_qifReader != 0) || (d->m_smtReader != 0); action("file_export_qif")->setEnabled(fileOpen && !importRunning); action("file_import_qif")->setEnabled(fileOpen && !importRunning); action("file_import_gnc")->setEnabled(!importRunning); action("file_import_template")->setEnabled(fileOpen && !importRunning); action("file_export_template")->setEnabled(fileOpen && !importRunning); action("tools_currency_editor")->setEnabled(fileOpen); action("tools_price_editor")->setEnabled(fileOpen); action("tools_update_prices")->setEnabled(fileOpen); action("tools_consistency_check")->setEnabled(fileOpen); action("account_new")->setEnabled(fileOpen); action("account_reconcile")->setEnabled(false); action("account_reconcile_finish")->setEnabled(false); action("account_reconcile_postpone")->setEnabled(false); action("account_edit")->setEnabled(false); action("account_delete")->setEnabled(false); action("account_open")->setEnabled(false); action("account_close")->setEnabled(false); action("account_reopen")->setEnabled(false); action("account_transaction_report")->setEnabled(false); action("account_online_map")->setEnabled(false); action("account_online_update")->setEnabled(false); action("account_online_update_all")->setEnabled(false); action("account_online_unmap")->setEnabled(false); action("account_online_new_credit_transfer")->setEnabled(onlineJobAdministration::instance()->canSendCreditTransfer()); action("account_chart")->setEnabled(false); action("category_new")->setEnabled(fileOpen); action("category_edit")->setEnabled(false); action("category_delete")->setEnabled(false); action("institution_new")->setEnabled(fileOpen); action("institution_edit")->setEnabled(false); action("institution_delete")->setEnabled(false); action("investment_new")->setEnabled(false); action("investment_edit")->setEnabled(false); action("investment_delete")->setEnabled(false); action("investment_online_price_update")->setEnabled(false); action("investment_manual_price_update")->setEnabled(false); action("schedule_edit")->setEnabled(false); action("schedule_delete")->setEnabled(false); action("schedule_enter")->setEnabled(false); action("schedule_skip")->setEnabled(false); action("payee_delete")->setEnabled(false); action("payee_rename")->setEnabled(false); action("payee_merge")->setEnabled(false); action("tag_delete")->setEnabled(false); action("tag_rename")->setEnabled(false); action("budget_delete")->setEnabled(false); action("budget_rename")->setEnabled(false); action("budget_change_year")->setEnabled(false); action("budget_new")->setEnabled(true); action("budget_copy")->setEnabled(false); action("budget_forecast")->setEnabled(false); QString tooltip = i18n("Create a new transaction"); action("transaction_new")->setEnabled(fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)); action("transaction_new")->setToolTip(tooltip); action("transaction_edit")->setEnabled(false); action("transaction_editsplits")->setEnabled(false); action("transaction_enter")->setEnabled(false); action("transaction_cancel")->setEnabled(false); action("transaction_delete")->setEnabled(false); action("transaction_match")->setEnabled(false); action("transaction_match")->setText(i18nc("Button text for match transaction", "Match")); action("transaction_accept")->setEnabled(false); action("transaction_duplicate")->setEnabled(false); action("transaction_mark_toggle")->setEnabled(false); action("transaction_mark_cleared")->setEnabled(false); action("transaction_mark_reconciled")->setEnabled(false); action("transaction_mark_notreconciled")->setEnabled(false); action("transaction_goto_account")->setEnabled(false); action("transaction_goto_payee")->setEnabled(false); action("transaction_assign_number")->setEnabled(false); action("transaction_create_schedule")->setEnabled(false); action("transaction_combine")->setEnabled(false); action("transaction_select_all")->setEnabled(false); action("transaction_copy_splits")->setEnabled(false); action("schedule_new")->setEnabled(fileOpen); action("schedule_edit")->setEnabled(false); action("schedule_delete")->setEnabled(false); action("schedule_duplicate")->setEnabled(false); action("schedule_enter")->setEnabled(false); action("schedule_skip")->setEnabled(false); action("currency_new")->setEnabled(fileOpen); action("currency_rename")->setEnabled(false); action("currency_delete")->setEnabled(false); action("currency_setbase")->setEnabled(false); action("price_new")->setEnabled(fileOpen); action("price_edit")->setEnabled(false); action("price_delete")->setEnabled(false); action("price_update")->setEnabled(false); w = factory()->container("transaction_move_menu", this); if (w) w->setEnabled(false); w = factory()->container("transaction_mark_menu", this); if (w) w->setEnabled(false); w = factory()->container("transaction_context_mark_menu", this); if (w) w->setEnabled(false); // FIXME for now it's always on, but we should only allow it, if we // can select at least a single transaction action("transaction_select_all")->setEnabled(true); if (!d->m_selectedTransactions.isEmpty()) { // enable 'delete transaction' only if at least one of the // selected transactions does not reference a closed account bool enable = false; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) { enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction()); } action("transaction_delete")->setEnabled(enable); if (!d->m_transactionEditor) { tooltip = i18n("Duplicate the current selected transactions"); action("transaction_duplicate")->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); action("transaction_duplicate")->setToolTip(tooltip); if (d->m_myMoneyView->canEditTransactions(d->m_selectedTransactions, tooltip)) { action("transaction_edit")->setEnabled(true); // editing splits is allowed only if we have one transaction selected if (d->m_selectedTransactions.count() == 1) { action("transaction_editsplits")->setEnabled(true); } if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != MyMoneyAccount::Investment) { action("transaction_create_schedule")->setEnabled(d->m_selectedTransactions.count() == 1); } } action("transaction_edit")->setToolTip(tooltip); if (!d->m_selectedAccount.isClosed()) { w = factory()->container("transaction_move_menu", this); if (w) w->setEnabled(true); } w = factory()->container("transaction_mark_menu", this); if (w) w->setEnabled(true); w = factory()->container("transaction_context_mark_menu", this); if (w) w->setEnabled(true); // Allow marking the transaction if at least one is selected action("transaction_mark_cleared")->setEnabled(true); action("transaction_mark_reconciled")->setEnabled(true); action("transaction_mark_notreconciled")->setEnabled(true); action("transaction_mark_toggle")->setEnabled(true); if (!d->m_accountGoto.isEmpty()) action("transaction_goto_account")->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) action("transaction_goto_payee")->setEnabled(true); // Matching is enabled as soon as one regular and one imported transaction is selected int matchedCount = 0; int importedCount = 0; KMyMoneyRegister::SelectedTransactions::const_iterator it; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) ++importedCount; if ((*it).split().isMatched()) ++matchedCount; } if (d->m_selectedTransactions.count() == 2 /* && action("transaction_edit")->isEnabled() */) { action("transaction_match")->setEnabled(true); } if (importedCount != 0 || matchedCount != 0) action("transaction_accept")->setEnabled(true); if (matchedCount != 0) { action("transaction_match")->setEnabled(true); action("transaction_match")->setText(i18nc("Button text for unmatch transaction", "Unmatch")); action("transaction_match")->setIcon(QIcon("process-stop")); } if (d->m_selectedTransactions.count() > 1) { action("transaction_combine")->setEnabled(true); } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { action("transaction_copy_splits")->setEnabled(true); } } } else { action("transaction_assign_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); action("transaction_new")->setEnabled(false); action("transaction_delete")->setEnabled(false); QString reason; action("transaction_enter")->setEnabled(d->m_transactionEditor->isComplete(reason)); //FIXME: Port to KDE4 // the next line somehow worked in KDE3 but does not have // any influence under KDE4 /// Works for me when 'reason' is set. Allan action("transaction_enter")->setToolTip(reason); action("transaction_cancel")->setEnabled(true); } } QList accList; file->accountList(accList); QList::const_iterator it_a; QMap::const_iterator it_p = d->m_onlinePlugins.constEnd(); for (it_a = accList.constBegin(); (it_p == d->m_onlinePlugins.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if (!(*it_a).onlineBankingSettings().value("provider").isEmpty()) { // check if provider is available it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { action("account_online_update_all")->setEnabled(true); action("account_online_update_menu")->setEnabled(true); } } } } MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { switch (d->m_selectedAccount.accountGroup()) { case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Equity: action("account_transaction_report")->setEnabled(true); action("account_edit")->setEnabled(true); action("account_delete")->setEnabled(!file->isReferenced(d->m_selectedAccount)); action("account_open")->setEnabled(true); if (d->m_selectedAccount.accountGroup() != MyMoneyAccount::Equity) { if (d->m_reconciliationAccount.id().isEmpty()) { action("account_reconcile")->setEnabled(true); action("account_reconcile")->setToolTip(i18n("Reconcile")); } else { QString tip; tip = i18n("Reconcile - disabled because you are currently reconciling %1", d->m_reconciliationAccount.name()); action("account_reconcile")->setToolTip(tip); if (!d->m_transactionEditor) { action("account_reconcile_finish")->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); action("account_reconcile_postpone")->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); } } } if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) action("investment_new")->setEnabled(true); if (d->m_selectedAccount.isClosed()) action("account_reopen")->setEnabled(true); else enableCloseAccountAction(d->m_selectedAccount); if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) { action("account_online_unmap")->setEnabled(true); // check if provider is available QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { action("account_online_update")->setEnabled(true); action("account_online_update_menu")->setEnabled(true); } } } else { action("account_online_map")->setEnabled(d->m_onlinePlugins.count() > 0); } action("account_chart")->setEnabled(true); break; case MyMoneyAccount::Income : case MyMoneyAccount::Expense : action("category_edit")->setEnabled(true); // enable delete action, if category/account itself is not referenced // by any object except accounts, because we want to allow // deleting of sub-categories. Also, we allow transactions, schedules and budgets // to be present because we can re-assign them during the delete process skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); skip.setBit(IMyMoneyStorage::RefCheckAccount); skip.setBit(IMyMoneyStorage::RefCheckSchedule); skip.setBit(IMyMoneyStorage::RefCheckBudget); action("category_delete")->setEnabled(!file->isReferenced(d->m_selectedAccount, skip)); action("account_open")->setEnabled(true); break; default: break; } } } if (!d->m_selectedInstitution.id().isEmpty()) { action("institution_edit")->setEnabled(true); action("institution_delete")->setEnabled(!file->isReferenced(d->m_selectedInstitution)); } if (!d->m_selectedInvestment.id().isEmpty()) { action("investment_edit")->setEnabled(true); action("investment_delete")->setEnabled(!file->isReferenced(d->m_selectedInvestment)); action("investment_manual_price_update")->setEnabled(true); try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); if (!security.value("kmm-online-source").isEmpty()) action("investment_online_price_update")->setEnabled(true); } catch (const MyMoneyException &e) { qDebug("Error retrieving security for investment %s: %s", qPrintable(d->m_selectedInvestment.name()), qPrintable(e.what())); } if (d->m_selectedInvestment.isClosed()) action("account_reopen")->setEnabled(true); else enableCloseAccountAction(d->m_selectedInvestment); } if (!d->m_selectedSchedule.id().isEmpty()) { action("schedule_edit")->setEnabled(true); action("schedule_duplicate")->setEnabled(true); action("schedule_delete")->setEnabled(!file->isReferenced(d->m_selectedSchedule)); if (!d->m_selectedSchedule.isFinished()) { action("schedule_enter")->setEnabled(true); // a schedule with a single occurrence cannot be skipped if (d->m_selectedSchedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { action("schedule_skip")->setEnabled(true); } } } if (d->m_selectedPayees.count() >= 1) { action("payee_rename")->setEnabled(d->m_selectedPayees.count() == 1); action("payee_merge")->setEnabled(d->m_selectedPayees.count() > 1); action("payee_delete")->setEnabled(true); } if (d->m_selectedTags.count() >= 1) { action("tag_rename")->setEnabled(d->m_selectedTags.count() == 1); action("tag_delete")->setEnabled(true); } if (d->m_selectedBudgets.count() >= 1) { action("budget_delete")->setEnabled(true); if (d->m_selectedBudgets.count() == 1) { action("budget_change_year")->setEnabled(true); action("budget_copy")->setEnabled(true); action("budget_rename")->setEnabled(true); action("budget_forecast")->setEnabled(true); } } if (!d->m_selectedCurrency.id().isEmpty()) { action("currency_rename")->setEnabled(true); // no need to check each transaction. accounts are enough in this case skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); action("currency_delete")->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); if (d->m_selectedCurrency.id() != file->baseCurrency().id()) action("currency_setbase")->setEnabled(true); } if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != "KMyMoney") { action("price_edit")->setEnabled(true); action("price_delete")->setEnabled(true); //enable online update if it is a currency MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedPrice.from()); action("price_update")->setEnabled(security.isCurrency()); } } void KMyMoneyApp::slotResetSelections() { slotSelectAccount(); slotSelectInstitution(); slotSelectInvestment(); slotSelectSchedule(); slotSelectCurrency(); slotSelectPrice(); slotSelectPayees(QList()); slotSelectTags(QList()); slotSelectBudget(QList()); slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); slotUpdateActions(); } void KMyMoneyApp::slotSelectCurrency(const MyMoneySecurity& currency) { d->m_selectedCurrency = currency; slotUpdateActions(); emit currencySelected(d->m_selectedCurrency); } void KMyMoneyApp::slotSelectPrice(const MyMoneyPrice& price) { d->m_selectedPrice = price; slotUpdateActions(); emit priceSelected(d->m_selectedPrice); } void KMyMoneyApp::slotSelectBudget(const QList& list) { d->m_selectedBudgets = list; slotUpdateActions(); emit budgetSelected(d->m_selectedBudgets); } void KMyMoneyApp::slotSelectPayees(const QList& list) { d->m_selectedPayees = list; slotUpdateActions(); emit payeesSelected(d->m_selectedPayees); } void KMyMoneyApp::slotSelectTags(const QList& list) { d->m_selectedTags = list; slotUpdateActions(); emit tagsSelected(d->m_selectedTags); } void KMyMoneyApp::slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list) { // list can either contain a list of transactions or a single selected scheduled transaction // in the latter case, the transaction id is actually the one of the schedule. In order // to differentiate between the two, we just ask for the schedule. If we don't find one - because // we passed the id of a real transaction - then we know that fact. We use the schedule here, // because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips // to the backend which we would have to do if we check for the transaction. d->m_selectedTransactions.clear(); d->m_selectedSchedule = MyMoneySchedule(); d->m_accountGoto.clear(); d->m_payeeGoto.clear(); if (!list.isEmpty() && !list.first().isScheduled()) { d->m_selectedTransactions = list; if (list.count() == 1) { const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); if (!sp.payeeId().isEmpty()) { try { MyMoneyPayee payee = MyMoneyFile::instance()->payee(sp.payeeId()); if (!payee.name().isEmpty()) { d->m_payeeGoto = payee.id(); QString name = payee.name(); name.replace(QRegExp("&(?!&)"), "&&"); action("transaction_goto_payee")->setText(i18n("Go to '%1'", name)); } } catch (const MyMoneyException &) { } } try { QList::const_iterator it_s; const MyMoneyTransaction& t = d->m_selectedTransactions[0].transaction(); // search the first non-income/non-expense accunt and use it for the 'goto account' const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if ((*it_s).id() != sp.id()) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (!acc.isIncomeExpense()) { // for stock accounts we show the portfolio account if (acc.isInvest()) { acc = MyMoneyFile::instance()->account(acc.parentAccountId()); } d->m_accountGoto = acc.id(); QString name = acc.name(); name.replace(QRegExp("&(?!&)"), "&&"); action("transaction_goto_account")->setText(i18n("Go to '%1'", name)); break; } } } } catch (const MyMoneyException &) { } } slotUpdateActions(); emit transactionsSelected(d->m_selectedTransactions); } else if (!list.isEmpty()) { slotSelectSchedule(MyMoneyFile::instance()->schedule(list.first().scheduleId())); } else { slotUpdateActions(); } // make sure, we show some neutral menu entry if we don't have an object if (d->m_payeeGoto.isEmpty()) action("transaction_goto_payee")->setText(i18n("Go to payee")); if (d->m_accountGoto.isEmpty()) action("transaction_goto_account")->setText(i18n("Go to account")); } void KMyMoneyApp::slotSelectInstitution(const MyMoneyObject& institution) { if (typeid(institution) != typeid(MyMoneyInstitution)) return; d->m_selectedInstitution = dynamic_cast(institution); // qDebug("slotSelectInstitution('%s')", d->m_selectedInstitution.name().data()); slotUpdateActions(); emit institutionSelected(d->m_selectedInstitution); } void KMyMoneyApp::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; d->m_selectedAccount = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (!acc.isInvest()) d->m_selectedAccount = acc; // qDebug("slotSelectAccount('%s')", d->m_selectedAccount.name().data()); slotUpdateActions(); emit accountSelected(d->m_selectedAccount); } void KMyMoneyApp::slotSelectInvestment(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; // qDebug("slotSelectInvestment('%s')", account.name().data()); d->m_selectedInvestment = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (acc.isInvest()) d->m_selectedInvestment = acc; slotUpdateActions(); emit investmentSelected(d->m_selectedInvestment); } void KMyMoneyApp::slotSelectSchedule(const MyMoneySchedule& schedule) { // qDebug("slotSelectSchedule('%s')", schedule.name().data()); d->m_selectedSchedule = schedule; slotUpdateActions(); emit scheduleSelected(d->m_selectedSchedule); } void KMyMoneyApp::slotDataChanged() { // 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 (d->m_autoSaveEnabled && !d->m_autoSaveTimer->isActive()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); //miliseconds } updateCaption(); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneySecurity)), this, SLOT(slotSelectCurrency(MyMoneySecurity))); connect(dlg, SIGNAL(openContextMenu(MyMoneySecurity)), this, SLOT(slotShowCurrencyContextMenu())); connect(this, SIGNAL(currencyRename()), dlg, SLOT(slotStartRename())); connect(dlg, SIGNAL(updateCurrency(QString,QString,QString)), this, SLOT(slotCurrencyUpdate(QString,QString,QString))); connect(this, SIGNAL(currencyCreated(QString)), dlg, SLOT(slotSelectCurrency(QString))); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotCurrencySetBase())); connect(dlg, SIGNAL(deleteCurrency(MyMoneySecurity)), this, SLOT(slotCurrencyDelete())); connect(dlg, SIGNAL(newCurrency()), this, SLOT(slotCurrencyNew())); dlg->exec(); delete dlg; slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneyPrice)), this, SLOT(slotSelectPrice(MyMoneyPrice))); connect(dlg, SIGNAL(openContextMenu(MyMoneyPrice)), this, SLOT(slotShowPriceContextMenu())); connect(this, SIGNAL(priceNew()), dlg, SLOT(slotNewPrice())); connect(this, SIGNAL(priceEdit()), dlg, SLOT(slotEditPrice())); connect(this, SIGNAL(priceDelete()), dlg, SLOT(slotDeletePrice())); connect(this, SIGNAL(priceOnlineUpdate()), dlg, SLOT(slotOnlinePriceUpdate())); dlg->exec(); } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); updateCaption(); } 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() { QPointer dialog = new KFileDialog(KUrl("kfiledialog:///kmymoney-consistency-check"), QString(), q); dialog->setMode(KFile::File); dialog->setOperationMode(KFileDialog::Saving); if (dialog->exec() == QDialog::Accepted && dialog != 0) { QFile file(dialog->selectedUrl().toLocalFile()); if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) { QTextStream out(&file); out << m_consistencyCheckResult.join(QLatin1String("\n")); file.close(); } } } void KMyMoneyApp::Private::setCustomColors() { // setup the kmymoney custom colors if needed if (KMyMoneyGlobalSettings::useSystemColors()) { qApp->setStyleSheet(QString()); } else { qApp->setStyleSheet("QTreeView, QTableView#register, QTableView#m_register, QTableView#splittable, QListView { background-color: " + KMyMoneyGlobalSettings::listBGColor().name() + ';' + "alternate-background-color: " + KMyMoneyGlobalSettings::listColor().name() + ';' + "background-clip: content;}"); } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneyGlobalSettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneyGlobalSettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != KMyMoneyUtils::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 != KMyMoneyUtils::Ignore && rc != KMyMoneyUtils::Cancel) { rc = enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == KMyMoneyUtils::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = KMyMoneyUtils::Enter; } } updateCaption(); } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = KGlobal::config(); 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 = KGlobal::config(); 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 = KGlobal::config(); 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", KGlobalSettings::documentPath()); // if the path stored is empty, we use the default nevertheless if (str.isEmpty()) str = KGlobalSettings::documentPath(); } return str; } QString KMyMoneyApp::readLastUsedFile() const { QString str; // get global config object for our app. KSharedConfigPtr kconfig = KGlobal::config(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // read filename entry. str = grp.readEntry("LastUsedFile", ""); } return str; } const QString KMyMoneyApp::filename() const { return d->m_fileName.url(); } const QList KMyMoneyApp::instanceList() const { QList list; 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 shange 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) { #ifdef _MSC_VER uint thisProcPid = _getpid(); #else uint thisProcPid = getpid(); #endif 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())); } return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } 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 // d->m_importUrlsQueue.enqueue(sourceUrl); // only start processing if this is the only import so far if (d->m_importUrlsQueue.count() == 1) { while (!d->m_importUrlsQueue.isEmpty()) { // 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_myMoneyView->fileOpen() && KMessageBox::warningContinueCancel(kmymoney, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) kmymoney->slotFileOpen(); // only continue if the user really did open a file. if (d->m_myMoneyView->fileOpen()) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { QList statements; if (!(*it_plugin)->import(url)) { 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 == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url)) slotStatementImport(url); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } } } 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, and load the plugins d->m_pluginInterface = new QObject(this); new KMyMoneyPlugin::KMMViewInterface(this, d->m_myMoneyView, d->m_pluginInterface); new KMyMoneyPlugin::KMMStatementInterface(this, d->m_pluginInterface); new KMyMoneyPlugin::KMMImportInterface(this, d->m_pluginInterface); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::loadPlugins() { d->m_pluginLoader = new KMyMoneyPlugin::PluginLoader(this); connect(d->m_pluginLoader, SIGNAL(plug(KPluginInfo*)), this, SLOT(slotPluginPlug(KPluginInfo*))); connect(d->m_pluginLoader, SIGNAL(unplug(KPluginInfo*)), this, SLOT(slotPluginUnplug(KPluginInfo*))); d->m_pluginLoader->loadPlugins(); } void KMyMoneyApp::slotPluginPlug(KPluginInfo* info) { KMyMoneyPlugin::Plugin* plugin = d->m_pluginLoader->getPluginFromInfo(info); // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for extended online plugin KMyMoneyPlugin::OnlinePluginExtended* ope = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // plug the plugin guiFactory()->addClient(plugin); if (op) d->m_onlinePlugins[plugin->objectName()] = op; if (ope) onlineJobAdministration::instance()->addPlugin(plugin->objectName(), ope); if (ip) d->m_importerPlugins[plugin->objectName()] = ip; slotUpdateActions(); } void KMyMoneyApp::slotPluginUnplug(KPluginInfo* info) { KMyMoneyPlugin::Plugin* plugin = d->m_pluginLoader->getPluginFromInfo(info); // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // unplug the plugin guiFactory()->removeClient(plugin); if (op) d->m_onlinePlugins.remove(plugin->objectName()); if (ip) d->m_importerPlugins.remove(plugin->objectName()); slotUpdateActions(); } 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->m_myMoneyView->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((dt.secsTo(nextDay) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } const MyMoneyAccount& KMyMoneyApp::account(const QString& key, const QString& value) const { QList list; QList::const_iterator it_a; MyMoneyFile::instance()->accountList(list); QString accId; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { // search in the account's kvp container const QString& accountKvpValue = (*it_a).value(key); // search in the account's online settings kvp container const QString& onlineSettingsKvpValue = (*it_a).onlineBankingSettings().value(key); if (accountKvpValue.contains(value) || onlineSettingsKvpValue.contains(value)) { if(accId.isEmpty()) { accId = (*it_a).id(); } } if (accountKvpValue == value || onlineSettingsKvpValue == value) { accId = (*it_a).id(); break; } } // return the account found or an empty element return MyMoneyFile::instance()->account(accId); } void KMyMoneyApp::setAccountOnlineParameters(const MyMoneyAccount& _acc, const MyMoneyKeyValueContainer& kvps) { MyMoneyFileTransaction ft; try { MyMoneyAccount acc = MyMoneyFile::instance()->account(_acc.id()); acc.setOnlineBankingSettings(kvps); MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to setup online parameters for account '%1'", _acc.name()), e.what()); } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_selectedAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_selectedAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_selectedAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_selectedAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", e.what())); } } } void KMyMoneyApp::slotAccountMapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // already an account mapped if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; // check if user tries to map a brokerageAccount if (d->m_selectedAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins.count()) { case 0: break; case 1: provider = d->m_onlinePlugins.begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins.constBegin(); it_p != d->m_onlinePlugins.constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins.constFind(provider); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider; MyMoneyAccount acc(d->m_selectedAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", e.what())); } } } } void KMyMoneyApp::slotAccountUpdateOnlineAll() { QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; d->m_statementResults.clear(); d->m_collectingStatements = true; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if ((*it_a).onlineBankingSettings().value("provider").isEmpty() || d->m_onlinePlugins.find((*it_a).onlineBankingSettings().value("provider")) == d->m_onlinePlugins.end()) { it_a = accList.erase(it_a); } else ++it_a; } action("account_online_update")->setEnabled(false); action("account_online_update_menu")->setEnabled(false); action("account_online_update_all")->setEnabled(false); // now work on the remaining list of accounts int cnt = accList.count() - 1; for (it_a = accList.begin(); it_a != accList.end(); ++it_a) { it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); (*it_p)->updateAccount(*it_a, cnt != 0); --cnt; } d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotAccountUpdateOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // no online account mapped if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; action("account_online_update")->setEnabled(false); action("account_online_update_menu")->setEnabled(false); action("account_online_update_all")->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it d->m_collectingStatements = true; d->m_statementResults.clear(); (*it_p)->updateAccount(d->m_selectedAccount); d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); } // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotNewOnlineTransfer() { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); if (!d->m_selectedAccount.id().isEmpty()) { transferForm->setCurrentAccount(d->m_selectedAccount.id()); } connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotEditOnlineJob(const QString jobId) { try { const onlineJob constJob = MyMoneyFile::instance()->getOnlineJob(jobId); slotEditOnlineJob(constJob); } catch (MyMoneyException&) { // Prevent a crash in very rare cases } } void KMyMoneyApp::slotEditOnlineJob(onlineJob job) { try { slotEditOnlineJob(onlineJobTyped(job)); } catch (MyMoneyException&) { } } void KMyMoneyApp::slotEditOnlineJob(const onlineJobTyped job) { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); transferForm->setOnlineJob(job); connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotOnlineJobSave(onlineJob job) { MyMoneyFileTransaction fileTransaction; if (job.id() == MyMoneyObject::emptyId()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); } /** @todo when onlineJob queue is used, continue here */ void KMyMoneyApp::slotOnlineJobSend(onlineJob job) { MyMoneyFileTransaction fileTransaction; if (job.id() == MyMoneyObject::emptyId()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); QList jobList; jobList.append(job); slotOnlineJobSend(jobList); } void KMyMoneyApp::slotOnlineJobSend(QList jobs) { MyMoneyFile *const kmmFile = MyMoneyFile::instance(); QMultiMap jobsByPlugin; // Sort jobs by online plugin & lock them foreach (onlineJob job, jobs) { Q_ASSERT(job.id() != MyMoneyObject::emptyId()); // find the provider const MyMoneyAccount originAcc = job.responsibleMyMoneyAccount(); job.setLock(); job.addJobMessage(onlineJobMessage(onlineJobMessage::debug, "KMyMoneyApp::slotOnlineJobSend", "Added to queue for plugin '" + originAcc.onlineBankingSettings().value("provider") + '\'')); MyMoneyFileTransaction fileTransaction; kmmFile->modifyOnlineJob(job); fileTransaction.commit(); jobsByPlugin.insert(originAcc.onlineBankingSettings().value("provider"), job); } // Send onlineJobs to plugins QList usedPlugins = jobsByPlugin.keys(); std::sort(usedPlugins.begin(), usedPlugins.end()); const QList::iterator newEnd = std::unique(usedPlugins.begin(), usedPlugins.end()); usedPlugins.erase(newEnd, usedPlugins.end()); foreach (const QString& pluginKey, usedPlugins) { QMap::const_iterator it_p = d->m_onlinePlugins.constFind(pluginKey); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it KMyMoneyPlugin::OnlinePluginExtended *pluginExt = dynamic_cast< KMyMoneyPlugin::OnlinePluginExtended* >(*it_p); if (pluginExt == 0) { qWarning("Job given for plugin which is not an extended plugin"); continue; } //! @fixme remove debug message qDebug() << "Sending " << jobsByPlugin.count(pluginKey) << " job(s) to online plugin " << pluginKey; QList jobsToExecute = jobsByPlugin.values(pluginKey); QList executedJobs = jobsToExecute; pluginExt->sendOnlineJob(executedJobs); // Save possible changes of the online job and remove lock MyMoneyFileTransaction fileTransaction; foreach (onlineJob job, executedJobs) { fileTransaction.restart(); job.setLock(false); kmmFile->modifyOnlineJob(job); fileTransaction.commit(); } if (Q_UNLIKELY(executedJobs.size() != jobsToExecute.size())) { // OnlinePlugin did not return all jobs qWarning() << "Error saving send online tasks. After restart you should see at minimum all successfully executed jobs marked send. Imperfect plugin: " << pluginExt->objectName(); } } else { qWarning() << "Error, got onlineJob for an account without online plugin."; /** @FIXME can this actually happen? */ } } } void KMyMoneyApp::slotOnlineJobLog() { QStringList jobIds = d->m_myMoneyView->getOnlineJobOutbox()->selectedOnlineJobs(); slotOnlineJobLog(jobIds); } void KMyMoneyApp::slotOnlineJobLog(const QStringList& onlineJobIds) { onlineJobMessagesView *const dialog = new onlineJobMessagesView(); onlineJobMessagesModel *const model = new onlineJobMessagesModel(dialog); model->setOnlineJob(MyMoneyFile::instance()->getOnlineJob(onlineJobIds.first())); dialog->setModel(model); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); // Note: Objects are not deleted here, Qt's parent-child system has to do that. } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef HAVE_KDEPIMLIBS //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 { #ifdef HAVE_KDEPIMLIBS if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; 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, true); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else Q_UNUSED(date); return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef HAVE_KDEPIMLIBS //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 int forecastDays = KMyMoneyGlobalSettings::forecastDays() + KMyMoneyGlobalSettings::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) { d->m_holidayMap.insert((*holiday_it).date(), false); } 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 } 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])); } m_statementXMLindex = 0; } void KMyMoneyApp::Private::closeFile() { q->slotSelectAccount(); q->slotSelectInstitution(); q->slotSelectInvestment(); q->slotSelectSchedule(); q->slotSelectCurrency(); q->slotSelectBudget(QList()); q->slotSelectPayees(QList()); q->slotSelectTags(QList()); q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(m_reconciliationAccount); m_myMoneyView->closeFile(); m_fileName = KUrl(); q->updateCaption(); // just create a new balance warning object delete m_balanceWarning; m_balanceWarning = new KBalanceWarning(q); emit q->fileLoaded(m_fileName); } diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp index 223cd0d06..6f33f70a3 100644 --- a/kmymoney/mymoney/mymoneyreport.cpp +++ b/kmymoney/mymoney/mymoneyreport.cpp @@ -1,869 +1,868 @@ /*************************************************************************** mymoneyreport.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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 "mymoneyreport.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "reportdebug.h" const QStringList MyMoneyReport::Column::kTypeText = QString("none,months,bimonths,quarters,4,5,6,weeks,8,9,10,11,years").split(','); +// if you add names here, don't forget to update the bitmap for QueryColumns::Type +// and shift the bit for QueryColumns::end one position to the left +const QStringList MyMoneyReport::QueryColumns::kText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance").split(','); const QStringList MyMoneyReport::kRowTypeText = QString("none,assetliability,expenseincome,category,topcategory,account,tag,payee,month,week,topaccount,topaccount-account,equitytype,accounttype,institution,budget,budgetactual,schedule,accountinfo,accountloaninfo,accountreconcile,cashflow").split(','); -// if you add names here, don't forget to update the bitmap for EQueryColumns -// and shift the bit for eQCend one position to the left -const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance").split(','); - const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport }; const QStringList MyMoneyReport::kDetailLevelText = QString("none,all,top,group,total,invalid").split(','); const QStringList MyMoneyReport::kChartTypeText = QString("none,line,bar,pie,ring,stackedbar,invalid").split(','); // This should live in mymoney/mymoneytransactionfilter.h const QStringList kTypeText = QString("all,payments,deposits,transfers,none").split(','); const QStringList kStateText = QString("all,notreconciled,cleared,reconciled,frozen,none").split(','); const QStringList kDateLockText = QString("alldates,untiltoday,currentmonth,currentyear,monthtodate,yeartodate,yeartomonth,lastmonth,lastyear,last7days,last30days,last3months,last6months,last12months,next7days,next30days,next3months,next6months,next12months,userdefined,last3tonext3months,last11Months,currentQuarter,lastQuarter,nextQuarter,currentFiscalYear,lastFiscalYear,today,next18months").split(','); const QStringList kAccountTypeText = QString("unknown,checkings,savings,cash,creditcard,loan,certificatedep,investment,moneymarket,asset,liability,currency,income,expense,assetloan,stock,equity,invalid").split(','); MyMoneyReport::MyMoneyReport() : m_name("Unconfigured Pivot Table Report"), m_detailLevel(eDetailNone), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(kTypeArray[eExpenseIncome]), m_rowType(eExpenseIncome), m_columnType(Column::Months), m_columnsAreDays(false), - m_queryColumns(eQCnone), + m_queryColumns(QueryColumns::None), m_dateLock(userDefined), m_accountGroupFilter(false), m_chartType(eChartLine), m_chartDataLabels(true), m_chartGridLines(true), m_chartByDefault(false), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { m_chartLineWidth = m_lineWidth; } MyMoneyReport::MyMoneyReport(const QString& id, const MyMoneyReport& right) : MyMoneyObject(id), m_movingAverageDays(0), m_currentDateColumn(0) { *this = right; setId(id); } MyMoneyReport::MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment) : m_name(_name), m_comment(_comment), m_detailLevel(_ss), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(kTypeArray[_rt]), m_rowType(_rt), m_columnType(Column::Months), m_columnsAreDays(false), - m_queryColumns(eQCnone), + m_queryColumns(QueryColumns::None), m_dateLock(_dl), m_accountGroupFilter(false), m_chartType(eChartLine), m_chartDataLabels(true), m_chartGridLines(true), m_chartByDefault(false), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { //set initial values m_chartLineWidth = m_lineWidth; //set report type if (m_reportType == ePivotTable) m_columnType = static_cast(_ct); if (m_reportType == eQueryTable) - m_queryColumns = static_cast(_ct); + m_queryColumns = static_cast(_ct); setDateFilter(_dl); //throw exception if the type is inconsistent if ((_rt > static_cast(sizeof(kTypeArray) / sizeof(kTypeArray[0]))) || (m_reportType == eNoReport)) throw MYMONEYEXCEPTION("Invalid report type"); //add the corresponding account groups if (_rt == MyMoneyReport::eAssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); m_showRowTotals = true; } if (_rt == MyMoneyReport::eAccount) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::AssetLoan); addAccountGroup(MyMoneyAccount::Cash); addAccountGroup(MyMoneyAccount::Checkings); addAccountGroup(MyMoneyAccount::CreditCard); if (KMyMoneyGlobalSettings::expertMode()) addAccountGroup(MyMoneyAccount::Equity); addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Liability); addAccountGroup(MyMoneyAccount::Loan); addAccountGroup(MyMoneyAccount::Savings); addAccountGroup(MyMoneyAccount::Stock); m_showRowTotals = true; } if (_rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); m_showRowTotals = true; } //FIXME take this out once we have sorted out all issues regarding budget of assets and liabilities -- asoliverez@gmail.com if (_rt == MyMoneyReport::eBudget || _rt == MyMoneyReport::eBudgetActual) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } if (_rt == MyMoneyReport::eAccountInfo) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } //cash flow reports show splits for all account groups if (_rt == MyMoneyReport::eCashFlow) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } #ifdef DEBUG_REPORTS QDebug dbg = qDebug(); dbg << _name << toString(_rt) << toString(m_reportType); foreach(const MyMoneyAccount::accountTypeE accountType, m_accountGroups) dbg << MyMoneyAccount::accountTypeToString(accountType); if (m_accounts.size() > 0) dbg << m_accounts; #endif } MyMoneyReport::MyMoneyReport(const QDomElement& node) : MyMoneyObject(node), m_currentDateColumn(0) { // properly initialize the object before reading it *this = MyMoneyReport(); if (!read(node)) clearId(); } void MyMoneyReport::clear() { m_accountGroupFilter = false; m_accountGroups.clear(); MyMoneyTransactionFilter::clear(); } void MyMoneyReport::validDateRange(QDate& _db, QDate& _de) { _db = fromDate(); _de = toDate(); // if either begin or end date are invalid we have one of the following // possible date filters: // // a) begin date not set - first transaction until given end date // b) end date not set - from given date until last transaction // c) both not set - first transaction until last transaction // // If there is no transaction in the engine at all, we use the current // year as the filter criteria. if (!_db.isValid() || !_de.isValid()) { QList list = MyMoneyFile::instance()->transactionList(*this); QDate tmpBegin, tmpEnd; if (!list.isEmpty()) { qSort(list); // try to use the post dates tmpBegin = list.front().postDate(); tmpEnd = list.back().postDate(); // if the post dates are not valid try the entry dates if (!tmpBegin.isValid()) tmpBegin = list.front().entryDate(); if (!tmpEnd.isValid()) tmpEnd = list.back().entryDate(); } // make sure that we leave this function with valid dates no mather what if (!tmpBegin.isValid() || !tmpEnd.isValid() || tmpBegin > tmpEnd) { tmpBegin = QDate(QDate::currentDate().year(), 1, 1); // the first date in the file tmpEnd = QDate(QDate::currentDate().year(), 12, 31); // the last date in the file } if (!_db.isValid()) _db = tmpBegin; if (!_de.isValid()) _de = tmpEnd; } if (_db > _de) _db = _de; } void MyMoneyReport::setRowType(ERowType _rt) { m_rowType = _rt; m_reportType = kTypeArray[_rt]; m_accountGroupFilter = false; m_accountGroups.clear(); if (_rt == MyMoneyReport::eAssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } if (_rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } } bool MyMoneyReport::accountGroups(QList& list) const { bool result = m_accountGroupFilter; if (result) { QList::const_iterator it_group = m_accountGroups.begin(); while (it_group != m_accountGroups.end()) { list += (*it_group); ++it_group; } } return result; } void MyMoneyReport::addAccountGroup(MyMoneyAccount::accountTypeE type) { if (!m_accountGroups.isEmpty() && type != MyMoneyAccount::UnknownAccountType) { if (m_accountGroups.contains(type)) return; } m_accountGroupFilter = true; if (type != MyMoneyAccount::UnknownAccountType) m_accountGroups.push_back(type); } bool MyMoneyReport::includesAccountGroup(MyMoneyAccount::accountTypeE type) const { bool result = (! m_accountGroupFilter) || (isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome) || m_accountGroups.contains(type); return result; } bool MyMoneyReport::includes(const MyMoneyAccount& acc) const { bool result = false; if (includesAccountGroup(acc.accountGroup())) { switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: if (isTax()) result = (acc.value("Tax") == "Yes") && includesCategory(acc.id()); else result = includesCategory(acc.id()); break; case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: if (isLoansOnly()) result = acc.isLoan() && includesAccount(acc.id()); else if (isInvestmentsOnly()) result = acc.isInvest() && includesAccount(acc.id()); else if (isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome) // If transfers are included, ONLY include this account if it is NOT // included in the report itself!! result = ! includesAccount(acc.id()); else result = includesAccount(acc.id()); break; default: result = includesAccount(acc.id()); } } return result; } void MyMoneyReport::write(QDomElement& e, QDomDocument *doc, bool anonymous) const { // No matter what changes, be sure to have a 'type' attribute. Only change // the major type if it becomes impossible to maintain compatibility with // older versions of the program as new features are added to the reports. // Feel free to change the minor type every time a change is made here. writeBaseXML(*doc, e); if (anonymous) { e.setAttribute("name", m_id); e.setAttribute("comment", QString(m_comment).fill('x')); } else { e.setAttribute("name", m_name); e.setAttribute("comment", m_comment); } e.setAttribute("group", m_group); e.setAttribute("convertcurrency", m_convertCurrency); e.setAttribute("favorite", m_favorite); e.setAttribute("tax", m_tax); e.setAttribute("investments", m_investments); e.setAttribute("loans", m_loans); e.setAttribute("rowtype", kRowTypeText[m_rowType]); e.setAttribute("datelock", kDateLockText[m_dateLock]); e.setAttribute("includeschedules", m_includeSchedules); e.setAttribute("columnsaredays", m_columnsAreDays); e.setAttribute("includestransfers", m_includeTransfers); if (!m_budgetId.isEmpty()) e.setAttribute("budget", m_budgetId); e.setAttribute("includesactuals", m_includeBudgetActuals); e.setAttribute("includeunused", m_includeUnusedAccounts); e.setAttribute("includesforecast", m_includeForecast); e.setAttribute("includesprice", m_includePrice); e.setAttribute("includesaverageprice", m_includeAveragePrice); e.setAttribute("mixedtime", m_mixedTime); e.setAttribute("includesmovingaverage", m_includeMovingAverage); if (m_includeMovingAverage) e.setAttribute("movingaveragedays", m_movingAverageDays); if (m_chartType < 0 || m_chartType >= kChartTypeText.size()) { qDebug("m_chartType out of bounds with %d on report of type %d. Default to none.", m_chartType, m_reportType); e.setAttribute("charttype", kChartTypeText[0]); } else { e.setAttribute("charttype", kChartTypeText[m_chartType]); } e.setAttribute("chartdatalabels", m_chartDataLabels); e.setAttribute("chartgridlines", m_chartGridLines); e.setAttribute("chartbydefault", m_chartByDefault); e.setAttribute("chartlinewidth", m_chartLineWidth); e.setAttribute("skipZero", m_skipZero); if (m_reportType == ePivotTable) { e.setAttribute("type", "pivottable 1.15"); e.setAttribute("detail", kDetailLevelText[m_detailLevel]); e.setAttribute("columntype", Column::kTypeText[m_columnType]); e.setAttribute("showrowtotals", m_showRowTotals); } else if (m_reportType == eQueryTable) { e.setAttribute("type", "querytable 1.14"); QStringList columns; unsigned qc = m_queryColumns; - unsigned it_qc = eQCbegin; + unsigned it_qc = QueryColumns::Begin; unsigned index = 1; - while (it_qc != eQCend) { + while (it_qc != QueryColumns::End) { if (qc & it_qc) - columns += kQueryColumnsText[index]; + columns += QueryColumns::kText[index]; it_qc *= 2; index++; } e.setAttribute("querycolumns", columns.join(",")); } else if (m_reportType == eInfoTable) { e.setAttribute("type", "infotable 1.0"); e.setAttribute("detail", kDetailLevelText[m_detailLevel]); e.setAttribute("showrowtotals", m_showRowTotals); } // // Text Filter // QRegExp textfilter; if (textFilter(textfilter)) { QDomElement f = doc->createElement("TEXT"); f.setAttribute("pattern", textfilter.pattern()); f.setAttribute("casesensitive", (textfilter.caseSensitivity() == Qt::CaseSensitive) ? 1 : 0); f.setAttribute("regex", (textfilter.patternSyntax() == QRegExp::Wildcard) ? 1 : 0); f.setAttribute("inverttext", m_invertText); e.appendChild(f); } // // Type & State Filters // QList typelist; if (types(typelist) && ! typelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_type = typelist.constBegin(); while (it_type != typelist.constEnd()) { QDomElement p = doc->createElement("TYPE"); p.setAttribute("type", kTypeText[*it_type]); e.appendChild(p); ++it_type; } } QList statelist; if (states(statelist) && ! statelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_state = statelist.constBegin(); while (it_state != statelist.constEnd()) { QDomElement p = doc->createElement("STATE"); p.setAttribute("state", kStateText[*it_state]); e.appendChild(p); ++it_state; } } // // Number Filter // QString nrFrom, nrTo; if (numberFilter(nrFrom, nrTo)) { QDomElement f = doc->createElement("NUMBER"); f.setAttribute("from", nrFrom); f.setAttribute("to", nrTo); e.appendChild(f); } // // Amount Filter // MyMoneyMoney from, to; if (amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&); QDomElement f = doc->createElement("AMOUNT"); f.setAttribute("from", from.toString()); f.setAttribute("to", to.toString()); e.appendChild(f); } // // Payees Filter // QStringList payeelist; if (payees(payeelist)) { if (payeelist.empty()) { QDomElement p = doc->createElement("PAYEE"); e.appendChild(p); } else { // iterate over payees, and add each one QStringList::const_iterator it_payee = payeelist.constBegin(); while (it_payee != payeelist.constEnd()) { QDomElement p = doc->createElement("PAYEE"); p.setAttribute("id", *it_payee); e.appendChild(p); ++it_payee; } } } // // Tags Filter // QStringList taglist; if (tags(taglist)) { if (taglist.empty()) { QDomElement p = doc->createElement("TAG"); e.appendChild(p); } else { // iterate over tags, and add each one QStringList::const_iterator it_tag = taglist.constBegin(); while (it_tag != taglist.constEnd()) { QDomElement p = doc->createElement("TAG"); p.setAttribute("id", *it_tag); e.appendChild(p); ++it_tag; } } } // // Account Groups Filter // QList accountgrouplist; if (accountGroups(accountgrouplist)) { // iterate over accounts, and add each one QList::const_iterator it_group = accountgrouplist.constBegin(); while (it_group != accountgrouplist.constEnd()) { QDomElement p = doc->createElement("ACCOUNTGROUP"); p.setAttribute("group", kAccountTypeText[*it_group]); e.appendChild(p); ++it_group; } } // // Accounts Filter // QStringList accountlist; if (accounts(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement("ACCOUNT"); p.setAttribute("id", *it_account); e.appendChild(p); ++it_account; } } // // Categories Filter // accountlist.clear(); if (categories(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement("CATEGORY"); p.setAttribute("id", *it_account); e.appendChild(p); ++it_account; } } // // Date Filter // if (m_dateLock == userDefined) { QDate dateFrom, dateTo; if (dateFilter(dateFrom, dateTo)) { QDomElement f = doc->createElement("DATES"); if (dateFrom.isValid()) f.setAttribute("from", dateFrom.toString(Qt::ISODate)); if (dateTo.isValid()) f.setAttribute("to", dateTo.toString(Qt::ISODate)); e.appendChild(f); } } } bool MyMoneyReport::read(const QDomElement& e) { // The goal of this reading method is 100% backward AND 100% forward // compatibility. Any report ever created with any version of KMyMoney // should be able to be loaded by this method (as long as it's one of the // report types supported in this version, of course) bool result = false; if ( "REPORT" == e.tagName() && ( (e.attribute("type").indexOf("pivottable 1.") == 0) || (e.attribute("type").indexOf("querytable 1.") == 0) || (e.attribute("type").indexOf("infotable 1.") == 0) ) ) { result = true; clear(); int i; m_name = e.attribute("name"); m_comment = e.attribute("comment", "Extremely old report"); //set report type if (!e.attribute("type").indexOf("pivottable")) { m_reportType = MyMoneyReport::ePivotTable; } else if (!e.attribute("type").indexOf("querytable")) { m_reportType = MyMoneyReport::eQueryTable; } else if (!e.attribute("type").indexOf("infotable")) { m_reportType = MyMoneyReport::eInfoTable; } else { m_reportType = MyMoneyReport::eNoReport; } // Removed the line that screened out loading reports that are called // "Default Report". It's possible for the user to change the comment // to this, and we'd hate for it to break as a result. m_group = e.attribute("group"); m_id = e.attribute("id"); //check for reports with older settings which didn't have the detail attribute if (e.hasAttribute("detail")) { i = kDetailLevelText.indexOf(e.attribute("detail", "all")); if (i != -1) m_detailLevel = static_cast(i); } else if (e.attribute("showsubaccounts", "0").toUInt()) { //set to show all accounts m_detailLevel = eDetailAll; } else { //set to show the top level account instead m_detailLevel = eDetailTop; } m_convertCurrency = e.attribute("convertcurrency", "1").toUInt(); m_favorite = e.attribute("favorite", "0").toUInt(); m_tax = e.attribute("tax", "0").toUInt(); m_investments = e.attribute("investments", "0").toUInt(); m_loans = e.attribute("loans", "0").toUInt(); m_includeSchedules = e.attribute("includeschedules", "0").toUInt(); m_columnsAreDays = e.attribute("columnsaredays", "0").toUInt(); m_includeTransfers = e.attribute("includestransfers", "0").toUInt(); if (e.hasAttribute("budget")) m_budgetId = e.attribute("budget"); m_includeBudgetActuals = e.attribute("includesactuals", "0").toUInt(); m_includeUnusedAccounts = e.attribute("includeunused", "0").toUInt(); m_includeForecast = e.attribute("includesforecast", "0").toUInt(); m_includePrice = e.attribute("includesprice", "0").toUInt(); m_includeAveragePrice = e.attribute("includesaverageprice", "0").toUInt(); m_mixedTime = e.attribute("mixedtime", "0").toUInt(); m_includeMovingAverage = e.attribute("includesmovingaverage", "0").toUInt(); m_skipZero = e.attribute("skipZero", "0").toUInt(); if (m_includeMovingAverage) m_movingAverageDays = e.attribute("movingaveragedays", "1").toUInt(); //only load chart data if it is a pivot table m_chartType = static_cast(0); if (m_reportType == ePivotTable) { i = kChartTypeText.indexOf(e.attribute("charttype")); if (i >= 0) m_chartType = static_cast(i); // if it is invalid, set to first type if (m_chartType >= eChartEnd) m_chartType = eChartLine; m_chartDataLabels = e.attribute("chartdatalabels", "1").toUInt(); m_chartGridLines = e.attribute("chartgridlines", "1").toUInt(); m_chartByDefault = e.attribute("chartbydefault", "0").toUInt(); m_chartLineWidth = e.attribute("chartlinewidth", QString(m_lineWidth)).toUInt(); } else { m_chartDataLabels = true; m_chartGridLines = true; m_chartByDefault = false; m_chartLineWidth = 1; } QString datelockstr = e.attribute("datelock", "userdefined"); // Handle the pivot 1.2/query 1.1 case where the values were saved as // numbers bool ok = false; i = datelockstr.toUInt(&ok); if (!ok) { i = kDateLockText.indexOf(datelockstr); if (i == -1) i = userDefined; } setDateFilter(static_cast(i)); i = kRowTypeText.indexOf(e.attribute("rowtype", "expenseincome")); if (i != -1) { setRowType(static_cast(i)); // recent versions of KMyMoney always showed a total column for // income/expense reports. We turn it on for backward compatibility // here. If the total column is turned off, the flag will be reset // in the next step if (i == eExpenseIncome) m_showRowTotals = true; } if (e.hasAttribute("showrowtotals")) m_showRowTotals = e.attribute("showrowtotals").toUInt(); i = Column::kTypeText.indexOf(e.attribute("columntype", "months")); if (i != -1) setColumnType(static_cast(i)); unsigned qc = 0; QStringList columns = e.attribute("querycolumns", "none").split(','); QStringList::const_iterator it_column = columns.constBegin(); while (it_column != columns.constEnd()) { - i = kQueryColumnsText.indexOf(*it_column); + i = QueryColumns::kText.indexOf(*it_column); if (i > 0) qc |= (1 << (i - 1)); ++it_column; } - setQueryColumns(static_cast(qc)); + setQueryColumns(static_cast(qc)); QDomNode child = e.firstChild(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); if ("TEXT" == c.tagName() && c.hasAttribute("pattern")) { setTextFilter(QRegExp(c.attribute("pattern"), c.attribute("casesensitive", "1").toUInt() ? Qt::CaseSensitive : Qt::CaseInsensitive, c.attribute("regex", "1").toUInt() ? QRegExp::Wildcard : QRegExp::RegExp), c.attribute("inverttext", "0").toUInt()); } if ("TYPE" == c.tagName() && c.hasAttribute("type")) { i = kTypeText.indexOf(c.attribute("type")); if (i != -1) addType(i); } if ("STATE" == c.tagName() && c.hasAttribute("state")) { i = kStateText.indexOf(c.attribute("state")); if (i != -1) addState(i); } if ("NUMBER" == c.tagName()) { setNumberFilter(c.attribute("from"), c.attribute("to")); } if ("AMOUNT" == c.tagName()) { setAmountFilter(MyMoneyMoney(c.attribute("from", "0/100")), MyMoneyMoney(c.attribute("to", "0/100"))); } if ("DATES" == c.tagName()) { QDate from, to; if (c.hasAttribute("from")) from = QDate::fromString(c.attribute("from"), Qt::ISODate); if (c.hasAttribute("to")) to = QDate::fromString(c.attribute("to"), Qt::ISODate); MyMoneyTransactionFilter::setDateFilter(from, to); } if ("PAYEE" == c.tagName()) { addPayee(c.attribute("id")); } if ("TAG" == c.tagName()) { addTag(c.attribute("id")); } if ("CATEGORY" == c.tagName() && c.hasAttribute("id")) { addCategory(c.attribute("id")); } if ("ACCOUNT" == c.tagName() && c.hasAttribute("id")) { addAccount(c.attribute("id")); } if ("ACCOUNTGROUP" == c.tagName() && c.hasAttribute("group")) { i = kAccountTypeText.indexOf(c.attribute("group")); if (i != -1) addAccountGroup(static_cast(i)); } child = child.nextSibling(); } } return result; } void MyMoneyReport::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el = document.createElement("REPORT"); write(el, &document, false); parent.appendChild(el); } bool MyMoneyReport::hasReferenceTo(const QString& id) const { QStringList list; // collect all ids accounts(list); categories(list); payees(list); tags(list); return (list.contains(id) > 0); } int MyMoneyReport::m_lineWidth = 2; void MyMoneyReport::setLineWidth(int width) { m_lineWidth = width; } QString MyMoneyReport::toString(ERowType type) { switch(type) { case eNoRows : return "eNoRows"; case eAssetLiability : return "eAssetLiability"; case eExpenseIncome : return "eExpenseIncome"; case eCategory : return "eCategory"; case eTopCategory : return "eTopCategory"; case eAccount : return "eAccount"; case eTag : return "eTag"; case ePayee : return "ePayee"; case eMonth : return "eMonth"; case eWeek : return "eWeek"; case eTopAccount : return "eTopAccount"; case eAccountByTopAccount: return "eAccountByTopAccount"; case eEquityType : return "eEquityType"; case eAccountType : return "eAccountType"; case eInstitution : return "eInstitution"; case eBudget : return "eBudget"; case eBudgetActual : return "eBudgetActual"; case eSchedule : return "eSchedule"; case eAccountInfo : return "eAccountInfo"; case eAccountLoanInfo : return "eAccountLoanInfo"; case eAccountReconcile : return "eAccountReconcile"; case eCashFlow : return "eCashFlow"; default : return "undefined"; } } QString MyMoneyReport::toString(MyMoneyReport::EReportType type) { switch(type) { case eNoReport: return "eNoReport"; case ePivotTable: return "ePivotTable"; case eQueryTable: return "eQueryTable"; case eInfoTable: return "eInfoTable"; default: return "undefined"; } } diff --git a/kmymoney/mymoney/mymoneyreport.h b/kmymoney/mymoney/mymoneyreport.h index 6ce13199a..8e5c34089 100644 --- a/kmymoney/mymoney/mymoneyreport.h +++ b/kmymoney/mymoney/mymoneyreport.h @@ -1,716 +1,719 @@ /*************************************************************************** mymoneyreport.h ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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 MYMONEYREPORT_H #define MYMONEYREPORT_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include class QDomElement; class QDomDocument; // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include /** * This class defines a report within the MyMoneyEngine. The report class * contains all the configuration parameters needed to run a report, plus * XML serialization. * * A report is a transactionfilter, so any report can specify which * transactions it's interested down to the most minute level of detail. * It extends the transactionfilter by providing identification (name, * comments, group type, etc) as well as layout information (what kind * of layout should be used, how the rows & columns should be presented, * currency converted, etc.) * * As noted above, this class only provides a report DEFINITION. The * generation and presentation of the report itself are left to higher * level classes. * * @author Ace Jones */ class KMM_MYMONEY_EXPORT MyMoneyReport: public MyMoneyObject, public MyMoneyTransactionFilter { public: // When adding a new row type, be sure to add a corresponding entry in kTypeArray enum ERowType { eNoRows = 0, eAssetLiability, eExpenseIncome, eCategory, eTopCategory, eAccount, eTag, ePayee, eMonth, eWeek, eTopAccount, eAccountByTopAccount, eEquityType, eAccountType, eInstitution, eBudget, eBudgetActual, eSchedule, eAccountInfo, eAccountLoanInfo, eAccountReconcile, eCashFlow}; enum EReportType { eNoReport = 0, ePivotTable, eQueryTable, eInfoTable }; class Column { public: enum Type { NoColumns = 0, Days = 1, Months = 1, BiMonths = 2, Quarters = 3, Weeks = 7, Years = 12 }; static const QStringList kTypeText; }; - // if you add bits to this bitmask, start with the value currently assigned to eQCend and update its value afterwards - // also don't forget to add column names to kQueryColumnsText in mymoneyreport.cpp - enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCend = 0x2000 }; + // if you add bits to this bitmask, start with the value currently assigned to end and update its value afterwards + // also don't forget to add column names to QueryColumns::kText in mymoneyreport.cpp + class QueryColumns { + public: + enum Type { None = 0x0, Begin = 0x1, Number = 0x1, Payee = 0x2, Category = 0x4, Tag = 0x8, Memo = 0x10, Account = 0x20, Reconciled = 0x40, Action = 0x80, Shares = 0x100, Price = 0x200, Performance = 0x400, Loan = 0x800, Balance = 0x1000, End = 0x2000 }; + static const QStringList kText; + }; enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd }; enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd }; static const QStringList kRowTypeText; - static const QStringList kQueryColumnsText; static const QStringList kDetailLevelText; static const QStringList kChartTypeText; static const EReportType kTypeArray[]; public: MyMoneyReport(); MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment); MyMoneyReport(const QString& id, const MyMoneyReport& right); /** * This constructor creates an object based on the data found in the * QDomElement referenced by @p node. If problems arise, the @p id of * the object is cleared (see MyMoneyObject::clearId()). */ MyMoneyReport(const QDomElement& node); // Simple get operations const QString& name() const { return m_name; } bool isShowingRowTotals() const { return (m_showRowTotals); } EReportType reportType() const { return m_reportType; } ERowType rowType() const { return m_rowType; } Column::Type columnType() const { return m_columnType; } bool isRunningSum() const { return (m_rowType == eAssetLiability); } bool isConvertCurrency() const { return m_convertCurrency; } unsigned columnPitch() const { return static_cast(m_columnType); } bool isShowingColumnTotals() const { return m_convertCurrency; } const QString& comment() const { return m_comment; } - EQueryColumns queryColumns() const { + QueryColumns::Type queryColumns() const { return m_queryColumns; } const QString& group() const { return m_group; } bool isFavorite() const { return m_favorite; } bool isTax() const { return m_tax; } bool isInvestmentsOnly() const { return m_investments; } bool isLoansOnly() const { return m_loans; } EDetailLevel detailLevel() const { return m_detailLevel; } EChartType chartType() const { return m_chartType; } bool isChartDataLabels() const { return m_chartDataLabels; } bool isChartGridLines() const { return m_chartGridLines; } bool isChartByDefault() const { return m_chartByDefault; } uint chartLineWidth() const { return m_chartLineWidth; } bool isIncludingSchedules() const { return m_includeSchedules; } bool isColumnsAreDays() const { return m_columnsAreDays; } bool isIncludingTransfers() const { return m_includeTransfers; } bool isIncludingUnusedAccounts() const { return m_includeUnusedAccounts; } bool hasBudget() const { return !m_budgetId.isEmpty(); } const QString& budget() const { return m_budgetId; } bool isIncludingBudgetActuals() const { return m_includeBudgetActuals; } bool isIncludingForecast() const { return m_includeForecast; } bool isIncludingMovingAverage() const { return m_includeMovingAverage; } int movingAverageDays() const { return m_movingAverageDays; } bool isIncludingPrice() const { return m_includePrice; } bool isIncludingAveragePrice() const { return m_includeAveragePrice; } bool isUserDefined() const { return m_dateLock == userDefined; } bool isMixedTime() const { return m_mixedTime; } int currentDateColumn() const { return m_currentDateColumn; } /** * @see #m_skipZero */ bool isSkippingZero() const { return m_skipZero; } // Simple set operations void setName(const QString& _s) { m_name = _s; } void setConvertCurrency(bool _f) { m_convertCurrency = _f; } void setRowType(ERowType _rt); void setColumnType(Column::Type _ct) { m_columnType = _ct; } void setComment(const QString& _comment) { m_comment = _comment; } void setGroup(const QString& _group) { m_group = _group; } void setFavorite(bool _f) { m_favorite = _f; } - void setQueryColumns(EQueryColumns _qc) { + void setQueryColumns(QueryColumns::Type _qc) { m_queryColumns = _qc; } void setTax(bool _f) { m_tax = _f; } void setInvestmentsOnly(bool _f) { m_investments = _f; if (_f) m_loans = false; } void setLoansOnly(bool _f) { m_loans = _f; if (_f) m_investments = false; } void setDetailLevel(EDetailLevel _detail) { m_detailLevel = _detail; } void setChartType(EChartType _type) { m_chartType = _type; } void setChartDataLabels(bool _f) { m_chartDataLabels = _f; } void setChartGridLines(bool _f) { m_chartGridLines = _f; } void setChartByDefault(bool _f) { m_chartByDefault = _f; } void setChartLineWidth(uint _f) { m_chartLineWidth = _f; } void setIncludingSchedules(bool _f) { m_includeSchedules = _f; } void setColumnsAreDays(bool _f) { m_columnsAreDays = _f; } void setIncludingTransfers(bool _f) { m_includeTransfers = _f; } void setIncludingUnusedAccounts(bool _f) { m_includeUnusedAccounts = _f; } void setShowingRowTotals(bool _f) { m_showRowTotals = _f; } void setIncludingBudgetActuals(bool _f) { m_includeBudgetActuals = _f; } void setIncludingForecast(bool _f) { m_includeForecast = _f; } void setIncludingMovingAverage(bool _f) { m_includeMovingAverage = _f; } void setMovingAverageDays(int _days) { m_movingAverageDays = _days; } void setIncludingPrice(bool _f) { m_includePrice = _f; } void setIncludingAveragePrice(bool _f) { m_includeAveragePrice = _f; } void setMixedTime(bool _f) { m_mixedTime = _f; } void setCurrentDateColumn(int _f) { m_currentDateColumn = _f; } /** * @see #m_skipZero */ void setSkipZero(int _f) { m_skipZero = _f; } /** * Sets the budget used for this report * * @param _budget The ID of the budget to use, or an empty string * to indicate a budget is NOT included * @param _fa Whether to display actual data alongside the budget. * Setting to false means the report displays ONLY the budget itself. * @warning For now, the budget ID is ignored. The budget id is * simply checked for any non-empty string, and if so, hasBudget() * will return true. */ void setBudget(const QString& _budget, bool _fa = true) { m_budgetId = _budget; m_includeBudgetActuals = _fa; } /** * This method allows you to clear the underlying transaction filter */ void clear(); /** * This method allows you to set the underlying transaction filter * * @param _filter The filter which should replace the existing transaction * filter. */ void assignFilter(const MyMoneyTransactionFilter& _filter) { MyMoneyTransactionFilter::operator=(_filter); } /** * Set the underlying date filter and LOCK that filter to the specified * range. For example, if @p _u is "CurrentMonth", this report should always * be updated to the current month no matter when the report is run. * * This updating is not entirely automatic, you should update it yourself by * calling updateDateFilter. * * @param _u The date range constant (MyMoneyTransactionFilter::dateRangeE) * which this report should be locked to. */ void setDateFilter(dateOptionE _u) { m_dateLock = _u; if (_u != userDefined) MyMoneyTransactionFilter::setDateFilter(_u); } /** * Set the underlying date filter using the start and end dates provided. * Note that this does not LOCK to any range like setDateFilter(unsigned) * above. It is just a reimplementation of the MyMoneyTransactionFilter * version. * * @param _db The inclusive begin date of the date range * @param _de The inclusive end date of the date range */ void setDateFilter(const QDate& _db, const QDate& _de) { MyMoneyTransactionFilter::setDateFilter(_db, _de); } /** * Set the underlying date filter using the 'date lock' property. * * Always call this function before executing the report to be sure that * the date filters properly match the plain-language 'date lock'. * * For example, if the report is date-locked to "Current Month", and the * last time you loaded or ran the report was in August, but it's now * September, this function will update the date range to be September, * as is proper. */ void updateDateFilter() { if (m_dateLock != userDefined) MyMoneyTransactionFilter::setDateFilter(m_dateLock); } /** * Retrieves a VALID beginning & ending date for this report. * * The underlying date filter can return en empty QDate() for either the * begin or end date or both. This is typically unacceptable for reports, * which need the REAL begin and end date. * * This function gets the underlying date filter range, and if either is * an empty QDate(), it determines the missing date from looking at all * the transactions which match the underlying filter, and returning the * date of the first or last transaction (as appropriate). * * @param _db The inclusive begin date of the date range * @param _de The inclusive end date of the date range */ void validDateRange(QDate& _db, QDate& _de); /** * This method turns on the account group filter and adds the * @p type to the list of allowed groups. * * Note that account group filtering is handled differently * than all the filters of the underlying class. This filter * is meant to be applied to individual splits of matched * transactions AFTER the underlying filter is used to find * the matching transactions. * * @param type the account group to add to the allowed groups list */ void addAccountGroup(MyMoneyAccount::accountTypeE type); /** * This method returns whether an account group filter has been set, * and if so, it returns all the account groups set in the filter. * * @param list list to append account groups into * @return return true if an account group filter has been set */ bool accountGroups(QList& list) const; /** * This method returns whether the specified account group * is allowed by the account groups filter. * * @param type group to append account groups into * @return return true if an account group filter has been set */ bool includesAccountGroup(MyMoneyAccount::accountTypeE type) const; /** * This method is used to test whether a specific account * passes the accountGroup test and either the Account or * Category test, depending on which sort of Account it is. * * The m_tax and m_investments properties are also considered. * * @param acc the account in question * @return true if account is in filter set, false otherwise */ bool includes(const MyMoneyAccount& acc) const; /** * This method writes this report to the DOM element @p e, * within the DOM document @p doc. * * @param e The element which should be populated with info from this report * @param doc The document which we can use to create new sub-elements * if needed * @param anonymous Whether the sensitive parts of the report should be * masked */ void write(QDomElement& e, QDomDocument *doc, bool anonymous = false) const; /** * This method reads a report from the DOM element @p e, and * populates this report with the results. * * @param e The element from which the report should be read * * @return bool True if a report was successfully loaded from the * element @p e. If false is returned, the contents of this report * object are undefined. */ bool read(const QDomElement& e); /** * This method creates a QDomElement for the @p document * under the parent node @p parent. (This version overwrites the * MMObject base class.) * * @param document reference to QDomDocument * @param parent reference to QDomElement parent node */ virtual void writeXML(QDomDocument& document, QDomElement& parent) const; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ virtual bool hasReferenceTo(const QString& id) const; /** * This method allows to modify the default lineWidth for graphs. * The default is 2. */ static void setLineWidth(int width); /** * Return row type as string. * * @param type type to get string for * @return row type converted to string */ static QString toString(ERowType type); /** * Return report type as string. * * @param type report type to get string for * @return report type converted to string */ static QString toString(EReportType type); private: /** * The user-assigned name of the report */ QString m_name; /** * The user-assigned comment for the report, in case they want to make * additional notes for themselves about the report. */ QString m_comment; /** * Where to group this report amongst the others in the UI view. This * should be assigned by the UI system. */ QString m_group; /** * How much detail to show in the accounts */ enum EDetailLevel m_detailLevel; /** * Whether to convert all currencies to the base currency of the file (true). * If this is false, it's up to the report generator to decide how to handle * the currency. */ bool m_convertCurrency; /** * Whether this is one of the users' favorite reports */ bool m_favorite; /** * Whether this report should only include categories marked as "Tax"="Yes" */ bool m_tax; /** * Whether this report should only include investment accounts */ bool m_investments; /** * Whether this report should only include loan accounts * Applies only to querytable reports. Mutually exclusive with * m_investments. */ bool m_loans; /** * What sort of algorithm should be used to run the report */ enum EReportType m_reportType; /** * What sort of values should show up on the ROWS of this report */ enum ERowType m_rowType; /** * What sort of values should show up on the COLUMNS of this report, * in the case of a 'PivotTable' report. Really this is used more as a * QUANTITY of months or days. Whether it's months or days is determined * by m_columnsAreDays. */ Column::Type m_columnType; /** * Whether the base unit of columns of this report is days. Only applies to * 'PivotTable' reports. If false, then columns are months or multiples thereof. */ bool m_columnsAreDays; /** * What sort of values should show up on the COLUMNS of this report, * in the case of a 'QueryTable' report */ - enum EQueryColumns m_queryColumns; + enum QueryColumns::Type m_queryColumns; /** * The plain-language description of what the date range should be locked * to. 'userDefined' means NO locking, in any other case, the report * will be adjusted to match the date lock. So if the date lock is * 'currentMonth', the start and end dates of the underlying filter will * be updated to whatever the current month is. This updating happens * automatically when the report is loaded, and should also be done * manually by calling updateDateFilter() before generating the report */ dateOptionE m_dateLock; /** * Which account groups should be included in the report. This filter * is applied to the individual splits AFTER a transaction has been * matched using the underlying filter. */ QList m_accountGroups; /** * Whether an account group filter has been set (see m_accountGroups) */ bool m_accountGroupFilter; /** * What format should be used to draw this report as a chart */ enum EChartType m_chartType; /** * Whether the value of individual data points should be drawn on the chart */ bool m_chartDataLabels; /** * Whether grid lines should be drawn on the chart */ bool m_chartGridLines; /** * Whether this report should be shown as a chart by default (otherwise it * should be shown as a textual report) */ bool m_chartByDefault; /** * Width of the chart lines */ uint m_chartLineWidth; /** * Whether to include scheduled transactions */ bool m_includeSchedules; /** * Whether to include transfers. Only applies to Income/Expense reports */ bool m_includeTransfers; /** * The id of the budget associated with this report. */ QString m_budgetId; /** * Whether this report should print the actual data to go along with * the budget. This is only valid if the report has a budget. */ bool m_includeBudgetActuals; /** * Whether this report should include all accounts and not only * accounts with transactions. */ bool m_includeUnusedAccounts; /** * Whether this report should include columns for row totals */ bool m_showRowTotals; /** * Whether this report should include forecast balance */ bool m_includeForecast; /** * Whether this report should include moving average */ bool m_includeMovingAverage; /** * The amount of days that spans each moving average */ int m_movingAverageDays; /** * Whether this report should include prices */ bool m_includePrice; /** * Whether this report should include moving average prices */ bool m_includeAveragePrice; /** * Make the actual and forecast lines display as one */ bool m_mixedTime; /** * This stores the column for the current date * This value is calculated dinamically and thus it is not saved in the file */ int m_currentDateColumn; /** * This member keeps the current setting for line graphs lineWidth. * @sa setLineWidth() */ static int m_lineWidth; /** * This option is for investments reports only which * show prices instead of balances as all other reports do. *

* Select this option to include prices for the given period (week, month, * quarter, ...) only. *

*

* If this option is off the last existing price is shown for a period, if * it is on, in a table the value is '0' shown and in a chart a linear * interpolation for the missing values will be performed. *
Example: *
There are prices for January and March, but there is no price for * February. *

    *
  • OFF: shows the price for February as the last price of * January *
  • ON: in a table the value is '0', in a chart a linear * interpolation for the February-price will be performed * (so it makes a kind of average-value using the January- and the * March-price in the chart) *
*

*/ bool m_skipZero; }; /** * Make it possible to hold @ref MyMoneyReport objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyReport) #endif // MYMONEYREPORT_H diff --git a/kmymoney/reports/querytable.cpp b/kmymoney/reports/querytable.cpp index 78dfafa97..6d5334560 100644 --- a/kmymoney/reports/querytable.cpp +++ b/kmymoney/reports/querytable.cpp @@ -1,1571 +1,1571 @@ /*************************************************************************** querytable.cpp ------------------- begin : Fri Jul 23 2004 copyright : (C) 2004-2005 by Ace Jones (C) 2007 Sascha Pfau ***************************************************************************/ /**************************************************************************** Contains code from the func_xirr and related methods of financial.cpp - KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under GPLv2 or later. *****************************************************************************/ /*************************************************************************** * * * 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 "querytable.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneytransaction.h" #include "mymoneyreport.h" #include "mymoneyexception.h" #include "kmymoneyutils.h" #include "reportaccount.h" #include "reportdebug.h" #include "kmymoneyglobalsettings.h" namespace reports { // **************************************************************************** // // CashFlowListItem implementation // // Cash flow analysis tools for investment reports // // **************************************************************************** QDate CashFlowListItem::m_sToday = QDate::currentDate(); MyMoneyMoney CashFlowListItem::NPV(double _rate) const { double T = static_cast(m_sToday.daysTo(m_date)) / 365.0; MyMoneyMoney result(m_value.toDouble() / pow(1 + _rate, T), 100); //kDebug(2) << "CashFlowListItem::NPV( " << _rate << " ) == " << result; return result; } // **************************************************************************** // // CashFlowList implementation // // Cash flow analysis tools for investment reports // // **************************************************************************** CashFlowListItem CashFlowList::mostRecent() const { CashFlowList dupe(*this); qSort(dupe); //kDebug(2) << " CashFlowList::mostRecent() == " << dupe.back().date().toString(Qt::ISODate); return dupe.back(); } MyMoneyMoney CashFlowList::NPV(double _rate) const { MyMoneyMoney result; const_iterator it_cash = begin(); while (it_cash != end()) { result += (*it_cash).NPV(_rate); ++it_cash; } //kDebug(2) << "CashFlowList::NPV( " << _rate << " ) == " << result << "------------------------" << endl; return result; } double CashFlowList::calculateXIRR() const { double resultRate = 0.00001; double resultZero = 0.00000; //if ( args.count() > 2 ) // resultRate = calc->conv()->asFloat ( args[2] ).asFloat(); // check pairs and count >= 2 and guess > -1.0 //if ( args[0].count() != args[1].count() || args[1].count() < 2 || resultRate <= -1.0 ) // return Value::errorVALUE(); // define max epsilon static const double maxEpsilon = 1e-5; // max number of iterations static const int maxIter = 50; // Newton's method - try to find a res, with a accuracy of maxEpsilon double rateEpsilon, newRate, resultValue; int i = 0; bool contLoop; do { resultValue = xirrResult(resultRate); double resultDerive = xirrResultDerive(resultRate); //check what happens if xirrResultDerive is zero //Don't know if it is correct to dismiss the result if (resultDerive != 0) { newRate = resultRate - resultValue / resultDerive; } else { newRate = resultRate - resultValue; } rateEpsilon = fabs(newRate - resultRate); resultRate = newRate; contLoop = (rateEpsilon > maxEpsilon) && (fabs(resultValue) > maxEpsilon); } while (contLoop && (++i < maxIter)); if (contLoop) return resultZero; return resultRate; } double CashFlowList::xirrResult(double& rate) const { QDate date; double r = rate + 1.0; double res = 0.00000;//back().value().toDouble(); QList::const_iterator list_it = begin(); while (list_it != end()) { double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0; MyMoneyMoney val = (* list_it).value(); if (e_i < 0) { res += val.toDouble() * pow(r, -e_i); } else { res += val.toDouble() / pow(r, e_i); } ++list_it; } return res; } double CashFlowList::xirrResultDerive(double& rate) const { QDate date; double r = rate + 1.0; double res = 0.00000; QList::const_iterator list_it = begin(); while (list_it != end()) { double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0; MyMoneyMoney val = (* list_it).value(); res -= e_i * val.toDouble() / pow(r, e_i + 1.0); ++list_it; } return res; } double CashFlowList::IRR() const { double result = 0.0; // set 'today', which is the most recent of all dates in the list CashFlowListItem::setToday(mostRecent().date()); result = calculateXIRR(); return result; } MyMoneyMoney CashFlowList::total() const { MyMoneyMoney result; const_iterator it_cash = begin(); while (it_cash != end()) { result += (*it_cash).value(); ++it_cash; } return result; } void CashFlowList::dumpDebug() const { const_iterator it_item = begin(); while (it_item != end()) { kDebug(2) << (*it_item).date().toString(Qt::ISODate) << " " << (*it_item).value().toString(); ++it_item; } } // **************************************************************************** // // QueryTable implementation // // **************************************************************************** /** * TODO * * - Collapse 2- & 3- groups when they are identical * - Way more test cases (especially splits & transfers) * - Option to collapse splits * - Option to exclude transfers * */ QueryTable::QueryTable(const MyMoneyReport& _report): ListTable(_report) { // separated into its own method to allow debugging (setting breakpoints // directly in ctors somehow does not work for me (ipwizard)) // TODO: remove the init() method and move the code back to the ctor init(); } void QueryTable::init() { switch (m_config.rowType()) { case MyMoneyReport::eAccountByTopAccount: case MyMoneyReport::eEquityType: case MyMoneyReport::eAccountType: case MyMoneyReport::eInstitution: constructAccountTable(); m_columns = "account"; break; case MyMoneyReport::eAccount: constructTransactionTable(); m_columns = "accountid,postdate"; break; case MyMoneyReport::ePayee: case MyMoneyReport::eTag: case MyMoneyReport::eMonth: case MyMoneyReport::eWeek: constructTransactionTable(); m_columns = "postdate,account"; break; case MyMoneyReport::eCashFlow: constructSplitsTable(); m_columns = "postdate"; break; default: constructTransactionTable(); m_columns = "postdate"; } // Sort the data to match the report definition m_subtotal = "value"; switch (m_config.rowType()) { case MyMoneyReport::eCashFlow: m_group = "categorytype,topcategory,category"; break; case MyMoneyReport::eCategory: m_group = "categorytype,topcategory,category"; break; case MyMoneyReport::eTopCategory: m_group = "categorytype,topcategory"; break; case MyMoneyReport::eTopAccount: m_group = "topaccount,account"; break; case MyMoneyReport::eAccount: m_group = "account"; break; case MyMoneyReport::eAccountReconcile: m_group = "account,reconcileflag"; break; case MyMoneyReport::ePayee: m_group = "payee"; break; case MyMoneyReport::eTag: m_group = "tag"; break; case MyMoneyReport::eMonth: m_group = "month"; break; case MyMoneyReport::eWeek: m_group = "week"; break; case MyMoneyReport::eAccountByTopAccount: m_group = "topaccount"; break; case MyMoneyReport::eEquityType: m_group = "equitytype"; break; case MyMoneyReport::eAccountType: m_group = "type"; break; case MyMoneyReport::eInstitution: m_group = "institution,topaccount"; break; default: throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type"); } QString sort; switch (m_config.rowType()) { case MyMoneyReport::eMonth: case MyMoneyReport::eWeek: sort = m_group + "sort," + m_columns + ",id,rank"; break; default: sort = m_group + ',' + m_columns + ",id,rank"; break; } switch (m_config.rowType()) { case MyMoneyReport::eAccountByTopAccount: case MyMoneyReport::eEquityType: case MyMoneyReport::eAccountType: case MyMoneyReport::eInstitution: m_columns = "account"; break; default: m_columns = "postdate"; } unsigned qc = m_config.queryColumns(); - if (qc & MyMoneyReport::eQCnumber) + if (qc & MyMoneyReport::QueryColumns::Number) m_columns += ",number"; - if (qc & MyMoneyReport::eQCpayee) + if (qc & MyMoneyReport::QueryColumns::Payee) m_columns += ",payee"; - if (qc & MyMoneyReport::eQCtag) + if (qc & MyMoneyReport::QueryColumns::Tag) m_columns += ",tag"; - if (qc & MyMoneyReport::eQCcategory) + if (qc & MyMoneyReport::QueryColumns::Category) m_columns += ",category"; - if (qc & MyMoneyReport::eQCaccount) + if (qc & MyMoneyReport::QueryColumns::Account) m_columns += ",account"; - if (qc & MyMoneyReport::eQCreconciled) + if (qc & MyMoneyReport::QueryColumns::Reconciled) m_columns += ",reconcileflag"; - if (qc & MyMoneyReport::eQCmemo) + if (qc & MyMoneyReport::QueryColumns::Memo) m_columns += ",memo"; - if (qc & MyMoneyReport::eQCaction) + if (qc & MyMoneyReport::QueryColumns::Action) m_columns += ",action"; - if (qc & MyMoneyReport::eQCshares) + if (qc & MyMoneyReport::QueryColumns::Shares) m_columns += ",shares"; - if (qc & MyMoneyReport::eQCprice) + if (qc & MyMoneyReport::QueryColumns::Price) m_columns += ",price"; - if (qc & MyMoneyReport::eQCperformance) + if (qc & MyMoneyReport::QueryColumns::Performance) m_columns += ",startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment"; - if (qc & MyMoneyReport::eQCloan) { + if (qc & MyMoneyReport::QueryColumns::Loan) { m_columns += ",payment,interest,fees"; m_postcolumns = "balance"; } - if (qc & MyMoneyReport::eQCbalance) + if (qc & MyMoneyReport::QueryColumns::Balance) m_postcolumns = "balance"; TableRow::setSortCriteria(sort); qSort(m_rows); } void QueryTable::constructTransactionTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); MyMoneyReport report(m_config); report.setReportAllSplits(false); report.setConsiderCategory(true); bool use_transfers; bool use_summary; bool hide_details; bool tag_special_case = false; switch (m_config.rowType()) { case MyMoneyReport::eCategory: case MyMoneyReport::eTopCategory: use_summary = false; use_transfers = false; hide_details = false; break; case MyMoneyReport::ePayee: use_summary = false; use_transfers = false; hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone); break; case MyMoneyReport::eTag: use_summary = false; use_transfers = false; hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone); tag_special_case = true; break; default: use_summary = true; use_transfers = true; hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone); break; } // support for opening and closing balances QMap accts; //get all transactions for this report QList transactions = file->transactionList(report); for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) { TableRow qA, qS; QDate pd; QList tagIdListCache; qA["id"] = qS["id"] = (* it_transaction).id(); qA["entrydate"] = qS["entrydate"] = (* it_transaction).entryDate().toString(Qt::ISODate); qA["postdate"] = qS["postdate"] = (* it_transaction).postDate().toString(Qt::ISODate); qA["commodity"] = qS["commodity"] = (* it_transaction).commodity(); pd = (* it_transaction).postDate(); qA["monthsort"] = qS["monthsort"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); qA["weeksort"] = qS["weeksort"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); qA["month"] = qS["month"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::DefaultLocaleLongDate)); qA["week"] = qS["week"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::DefaultLocaleLongDate)); qA["currency"] = qS["currency"] = ""; if ((* it_transaction).commodity() != file->baseCurrency().id()) { if (!report.isConvertCurrency()) { qA["currency"] = qS["currency"] = (*it_transaction).commodity(); } } // to handle splits, we decide on which account to base the split // (a reference point or point of view so to speak). here we take the // first account that is a stock account or loan account (or the first account // that is not an income or expense account if there is no stock or loan account) // to be the account (qA) that will have the sub-item "split" entries. we add // one transaction entry (qS) for each subsequent entry in the split. const QList& splits = (*it_transaction).splits(); QList::const_iterator myBegin, it_split; for (it_split = splits.begin(), myBegin = splits.end(); it_split != splits.end(); ++it_split) { ReportAccount splitAcc = (* it_split).accountId(); // always put split with a "stock" account if it exists if (splitAcc.isInvest()) break; // prefer to put splits with a "loan" account if it exists if (splitAcc.isLoan()) myBegin = it_split; if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) { myBegin = it_split; } } // select our "reference" split if (it_split == splits.end()) { it_split = myBegin; } else { myBegin = it_split; } // skip this transaction if we didn't find a valid base account - see the above description // for the base account's description - if we don't find it avoid a crash by skipping the transaction if (myBegin == splits.end()) continue; // if the split is still unknown, use the first one. I have seen this // happen with a transaction that has only a single split referencing an income or expense // account and has an amount and value of 0. Such a transaction will fall through // the above logic and leave 'it_split' pointing to splits.end() which causes the remainder // of this to end in an infinite loop. if (it_split == splits.end()) { it_split = splits.begin(); } // for "loan" reports, the loan transaction gets special treatment. // the splits of a loan transaction are placed on one line in the // reference (loan) account (qA). however, we process the matching // split entries (qS) normally. bool loan_special_case = false; - if (m_config.queryColumns() & MyMoneyReport::eQCloan) { + if (m_config.queryColumns() & MyMoneyReport::QueryColumns::Loan) { ReportAccount splitAcc = (*it_split).accountId(); loan_special_case = splitAcc.isLoan(); } bool include_me = true; bool transaction_text = false; //indicates whether a text should be considered as a match for the transaction or for a split only QString a_fullname = ""; QString a_memo = ""; int pass = 1; QString myBeginCurrency = (file->account((*myBegin).accountId())).currencyId(); //currency of the main split do { MyMoneyMoney xr; ReportAccount splitAcc = (* it_split).accountId(); //get fraction for account int fraction = splitAcc.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = splitAcc.institutionId(); QString payee = (*it_split).payeeId(); const QList tagIdList = (*it_split).tagIdList(); //convert to base currency if (m_config.isConvertCurrency()) { xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate()) * splitAcc.baseCurrencyPrice((*it_transaction).postDate())).reduce(); } else { xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate())).reduce(); } if (splitAcc.isInvest()) { // use the institution of the parent for stock accounts institution = splitAcc.parent().institutionId(); MyMoneyMoney shares = (*it_split).shares(); qA["action"] = (*it_split).action(); qA["shares"] = shares.isZero() ? "" : (*it_split).shares().toString(); qA["price"] = shares.isZero() ? "" : xr.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative()) qA["action"] = "Sell"; qA["investaccount"] = splitAcc.parent().name(); } if (it_split == myBegin) { include_me = m_config.includes(splitAcc); a_fullname = splitAcc.fullName(); a_memo = (*it_split).memo(); transaction_text = m_config.match(&(*it_split)); qA["price"] = xr.toString(); qA["account"] = splitAcc.name(); qA["accountid"] = splitAcc.id(); qA["topaccount"] = splitAcc.topParentName(); qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qA["payee"] = payee.isEmpty() ? i18n("[Empty Payee]") : file->payee(payee).name().simplified(); if (tag_special_case) { tagIdListCache = tagIdList; } else { QString delimiter = ""; for (int i = 0; i < tagIdList.size(); i++) { qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified(); delimiter = ", "; } } qA["reconciledate"] = (*it_split).reconcileDate().toString(Qt::ISODate); qA["reconcileflag"] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); qA["number"] = (*it_split).number(); qA["memo"] = a_memo; qA["value"] = (((*it_split).shares()) * xr).convert(fraction).toString(); qS["reconciledate"] = qA["reconciledate"]; qS["reconcileflag"] = qA["reconcileflag"]; qS["number"] = qA["number"]; qS["topcategory"] = splitAcc.topParentName(); qS["categorytype"] = i18n("Transfer"); // only include the configured accounts if (include_me) { if (loan_special_case) { // put the principal amount in the "value" column and convert to lowest fraction qA["value"] = ((-(*it_split).shares()) * xr).convert(fraction).toString(); qA["rank"] = '0'; qA["split"] = ""; } else { if ((splits.count() > 2) && use_summary) { // add the "summarized" split transaction // this is the sub-total of the split detail // convert to lowest fraction qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); qA["rank"] = '0'; qA["category"] = i18n("[Split Transaction]"); qA["topcategory"] = i18nc("Split transaction", "Split"); qA["categorytype"] = i18nc("Split transaction", "Split"); m_rows += qA; } } // track accts that will need opening and closing balances //FIXME in some cases it will show the opening and closing //balances but no transactions if the splits are all filtered out -- asoliverez accts.insert(splitAcc.id(), splitAcc); } } else { if (include_me) { if (loan_special_case) { MyMoneyMoney value = ((-(* it_split).shares()) * xr).convert(fraction); if ((*it_split).action() == MyMoneySplit::ActionAmortization) { // put the payment in the "payment" column and convert to lowest fraction qA["payment"] = value.toString(); } else if ((*it_split).action() == MyMoneySplit::ActionInterest) { // put the interest in the "interest" column and convert to lowest fraction qA["interest"] = value.toString(); } else if (splits.count() > 2) { // [dv: This comment carried from the original code. I am // not exactly clear on what it means or why we do this.] // Put the initial pay-in nowhere (that is, ignore it). This // is dangerous, though. The only way I can tell the initial // pay-in apart from fees is if there are only 2 splits in // the transaction. I wish there was a better way. } else { // accumulate everything else in the "fees" column MyMoneyMoney n0 = MyMoneyMoney(qA["fees"]); qA["fees"] = (n0 + value).toString(); } // we don't add qA here for a loan transaction. we'll add one // qA afer all of the split components have been processed. // (see below) } //--- special case to hide split transaction details else if (hide_details && (splits.count() > 2)) { // essentially, don't add any qA entries } //--- default case includes all transaction details else { //this is when the splits are going to be shown as children of the main split if ((splits.count() > 2) && use_summary) { qA["value"] = ""; //convert to lowest fraction qA["split"] = ((-(*it_split).shares()) * xr).convert(fraction).toString(); qA["rank"] = '1'; } else { //this applies when the transaction has only 2 splits, or each split is going to be //shown separately, eg. transactions by category qA["split"] = ""; // multiply by currency and convert to lowest fraction // but only for income and expense // transfers are dealt with somewhere else below if (splitAcc.isIncomeExpense()) { // if the currency of the split is different from the currency of the main split, then convert to the currency of the main split MyMoneyMoney ieXr(xr); if (!m_config.isConvertCurrency() && splitAcc.currency().id() != myBeginCurrency) { ieXr = (xr * splitAcc.foreignCurrencyPrice(myBeginCurrency, (*it_transaction).postDate())).reduce(); } qA["value"] = ((-(*it_split).shares()) * ieXr).convert(fraction).toString(); } qA["rank"] = '0'; } qA ["memo"] = (*it_split).memo(); // if different from base currency and not converting // show the currency of the split if (splitAcc.currencyId() != file->baseCurrency().id()) { if (!report.isConvertCurrency()) { qS["currency"] = splitAcc.currencyId(); } } else { qS["currency"] = ""; } if (! splitAcc.isIncomeExpense()) { qA["category"] = ((*it_split).shares().isNegative()) ? i18n("Transfer from %1", splitAcc.fullName()) : i18n("Transfer to %1", splitAcc.fullName()); qA["topcategory"] = splitAcc.topParentName(); qA["categorytype"] = i18n("Transfer"); } else { qA ["category"] = splitAcc.fullName(); qA ["topcategory"] = splitAcc.topParentName(); qA ["categorytype"] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup()); } if (use_transfers || (splitAcc.isIncomeExpense() && m_config.includes(splitAcc))) { //if it matches the text of the main split of the transaction or //it matches this particular split, include it //otherwise, skip it //if the filter is "does not contain" exclude the split if it does not match //even it matches the whole split if ((m_config.isInvertingText() && m_config.match(&(*it_split))) || (!m_config.isInvertingText() && (transaction_text || m_config.match(&(*it_split))))) { if (tag_special_case) { if (!tagIdListCache.size()) qA["tag"] = i18n("[No Tag]"); else for (int i = 0; i < tagIdListCache.size(); i++) { qA["tag"] = file->tag(tagIdListCache[i]).name().simplified(); m_rows += qA; } } else { m_rows += qA; } } } } } if (m_config.includes(splitAcc) && use_transfers) { if (! splitAcc.isIncomeExpense()) { //multiply by currency and convert to lowest fraction qS["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); qS["rank"] = '0'; qS["account"] = splitAcc.name(); qS["accountid"] = splitAcc.id(); qS["topaccount"] = splitAcc.topParentName(); qS["category"] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", a_fullname) : i18n("Transfer from %1", a_fullname); qS["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qS["memo"] = (*it_split).memo().isEmpty() ? a_memo : (*it_split).memo(); //FIXME-ALEX When is used this? I can't find in which condition we arrive here... maybe this code is useless? QString delimiter = ""; for (int i = 0; i < tagIdList.size(); i++) { qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified(); delimiter = '+'; } qS["payee"] = payee.isEmpty() ? qA["payee"] : file->payee(payee).name().simplified(); //check the specific split against the filter for text and amount //TODO this should be done at the engine, but I have no clear idea how -- asoliverez //if the filter is "does not contain" exclude the split if it does not match //even it matches the whole split if ((m_config.isInvertingText() && m_config.match(&(*it_split))) || (!m_config.isInvertingText() && (transaction_text || m_config.match(&(*it_split))))) { m_rows += qS; // track accts that will need opening and closing balances accts.insert(splitAcc.id(), splitAcc); } } } } ++it_split; // look for wrap-around if (it_split == splits.end()) it_split = splits.begin(); // but terminate if this transaction has only a single split if (splits.count() < 2) break; //check if there have been more passes than there are splits //this is to prevent infinite loops in cases of data inconsistency -- asoliverez ++pass; if (pass > splits.count()) break; } while (it_split != myBegin); if (loan_special_case) { m_rows += qA; } } // now run through our accts list and add opening and closing balances switch (m_config.rowType()) { case MyMoneyReport::eAccount: case MyMoneyReport::eTopAccount: break; // case MyMoneyReport::eCategory: // case MyMoneyReport::eTopCategory: // case MyMoneyReport::ePayee: // case MyMoneyReport::eMonth: // case MyMoneyReport::eWeek: default: return; } QDate startDate, endDate; report.validDateRange(startDate, endDate); QString strStartDate = startDate.toString(Qt::ISODate); QString strEndDate = endDate.toString(Qt::ISODate); startDate = startDate.addDays(-1); QMap::const_iterator it_account, accts_end; for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) { TableRow qA; ReportAccount account = (* it_account); //get fraction for account int fraction = account.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = account.institutionId(); // use the institution of the parent for stock accounts if (account.isInvest()) institution = account.parent().institutionId(); MyMoneyMoney startBalance, endBalance, startPrice, endPrice; MyMoneyMoney startShares, endShares; //get price and convert currency if necessary if (m_config.isConvertCurrency()) { startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce(); endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce(); } else { startPrice = account.deepCurrencyPrice(startDate).reduce(); endPrice = account.deepCurrencyPrice(endDate).reduce(); } startShares = file->balance(account.id(), startDate); endShares = file->balance(account.id(), endDate); //get starting and ending balances startBalance = startShares * startPrice; endBalance = endShares * endPrice; //starting balance // don't show currency if we're converting or if it's not foreign qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id(); qA["accountid"] = account.id(); qA["account"] = account.name(); qA["topaccount"] = account.topParentName(); qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qA["rank"] = "-2"; qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); if (account.isInvest()) { qA["shares"] = startShares.toString(); } qA["postdate"] = strStartDate; qA["balance"] = startBalance.convert(fraction).toString(); qA["value"].clear(); qA["id"] = 'A'; m_rows += qA; //ending balance qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); if (account.isInvest()) { qA["shares"] = endShares.toString(); } qA["postdate"] = strEndDate; qA["balance"] = endBalance.toString(); qA["id"] = 'Z'; m_rows += qA; } } void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result) const { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneySecurity security = file->security(account.currencyId()); result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType()); //get fraction depending on type of account int fraction = account.currency().smallestAccountFraction(); // // Calculate performance // // The following columns are created: // Account, Value on , Buys, Sells, Income, Value on , Return% MyMoneyReport report = m_config; QDate startingDate; QDate endingDate; MyMoneyMoney price; report.validDateRange(startingDate, endingDate); startingDate = startingDate.addDays(-1); //calculate starting balance if (m_config.isConvertCurrency()) { price = account.deepCurrencyPrice(startingDate) * account.baseCurrencyPrice(startingDate); } else { price = account.deepCurrencyPrice(startingDate); } //work around if there is no price for the starting balance if (!(file->balance(account.id(), startingDate)).isZero() && account.deepCurrencyPrice(startingDate) == MyMoneyMoney::ONE) { MyMoneyTransactionFilter filter; //get the transactions for the time before the report filter.setDateFilter(QDate(), startingDate); filter.addAccount(account.id()); filter.setReportAllSplits(true); QList startTransactions = file->transactionList(filter); if (startTransactions.size() > 0) { //get the last transaction MyMoneyTransaction startTrans = startTransactions.back(); MyMoneySplit s = startTrans.splitByAccount(account.id()); //get the price from the split of that account price = s.price(); if (m_config.isConvertCurrency()) price = price * account.baseCurrencyPrice(startingDate); } } if (m_config.isConvertCurrency()) { price = account.deepCurrencyPrice(startingDate) * account.baseCurrencyPrice(startingDate); } else { price = account.deepCurrencyPrice(startingDate); } MyMoneyMoney startingBal = file->balance(account.id(), startingDate) * price; //convert to lowest fraction startingBal = startingBal.convert(fraction); //calculate ending balance if (m_config.isConvertCurrency()) { price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate); } else { price = account.deepCurrencyPrice(endingDate); } MyMoneyMoney endingBal = file->balance((account).id(), endingDate) * price; //convert to lowest fraction endingBal = endingBal.convert(fraction); //add start balance to calculate return on investment MyMoneyMoney returnInvestment = startingBal; MyMoneyMoney paidDividend; CashFlowList buys; CashFlowList sells; CashFlowList reinvestincome; CashFlowList cashincome; report.setReportAllSplits(false); report.setConsiderCategory(true); report.clearAccountFilter(); report.addAccount(account.id()); QList transactions = file->transactionList(report); QList::const_iterator it_transaction = transactions.constBegin(); while (it_transaction != transactions.constEnd()) { // s is the split for the stock account MyMoneySplit s = (*it_transaction).splitByAccount(account.id()); //get price for the day of the transaction if we have to calculate base currency //we are using the value of the split which is in deep currency if (m_config.isConvertCurrency()) { price = account.baseCurrencyPrice((*it_transaction).postDate()); //we only need base currency because the value is in deep currency } else { price = MyMoneyMoney::ONE; } MyMoneyMoney value = s.value() * price; const QString& action = s.action(); if (action == MyMoneySplit::ActionBuyShares) { if (s.value().isPositive()) { buys += CashFlowListItem((*it_transaction).postDate(), -value); } else { sells += CashFlowListItem((*it_transaction).postDate(), -value); } returnInvestment += value; //convert to lowest fraction returnInvestment = returnInvestment.convert(fraction); } else if (action == MyMoneySplit::ActionReinvestDividend) { reinvestincome += CashFlowListItem((*it_transaction).postDate(), value); } else if (action == MyMoneySplit::ActionDividend || action == MyMoneySplit::ActionYield) { // find the split with the category, which has the actual amount of the dividend QList splits = (*it_transaction).splits(); QList::const_iterator it_split = splits.constBegin(); while (it_split != splits.constEnd()) { ReportAccount acc = (*it_split).accountId(); if (acc.isIncomeExpense()) { cashincome += CashFlowListItem((*it_transaction).postDate(), -(*it_split).value() * price); paidDividend += ((-(*it_split).value()) * price).convert(fraction); } ++it_split; } } else if (action == MyMoneySplit::ActionAddShares) { // Add shares is not a buy operation, do nothing } else { //if the split does not match any action above, add it as buy or sell depending on sign //if value is zero, get the price for that date if (s.value().isZero()) { if (m_config.isConvertCurrency()) { price = account.deepCurrencyPrice((*it_transaction).postDate()) * account.baseCurrencyPrice((*it_transaction).postDate()); } else { price = account.deepCurrencyPrice((*it_transaction).postDate()); } value = s.shares() * price; if (s.shares().isPositive()) { buys += CashFlowListItem((*it_transaction).postDate(), -value); } else { sells += CashFlowListItem((*it_transaction).postDate(), -value); } returnInvestment += value; } else { value = s.value() * price; if (s.value().isPositive()) { buys += CashFlowListItem((*it_transaction).postDate(), -value); } else { sells += CashFlowListItem((*it_transaction).postDate(), -value); } returnInvestment += value; } } ++it_transaction; } // Note that reinvested dividends are not included , because these do not // represent a cash flow event. CashFlowList all; all += buys; all += sells; all += cashincome; all += CashFlowListItem(startingDate, -startingBal); all += CashFlowListItem(endingDate, endingBal); //check if no activity on that term if (!returnInvestment.isZero() && !endingBal.isZero()) { returnInvestment = ((endingBal + paidDividend) - returnInvestment) / returnInvestment; returnInvestment = returnInvestment.convert(10000); } else { returnInvestment = MyMoneyMoney(); } try { double irr = all.IRR(); #ifdef Q_CC_MSVC MyMoneyMoney annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000); #else MyMoneyMoney annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000); #endif result["return"] = annualReturn.toString(); result["returninvestment"] = returnInvestment.toString(); } catch (QString e) { kDebug(2) << e; } result["buys"] = (-(buys.total())).toString(); result["sells"] = (-(sells.total())).toString(); result["cashincome"] = (cashincome.total()).toString(); result["reinvestincome"] = (reinvestincome.total()).toString(); result["startingbal"] = (startingBal).toString(); result["endingbal"] = (endingBal).toString(); } void QueryTable::constructAccountTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { ReportAccount account = *it_account; //get fraction for account int fraction = account.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction(); // Note, "Investment" accounts are never included in account rows because // they don't contain anything by themselves. In reports, they are only // useful as a "topaccount" aggregator of stock accounts if (account.isAssetLiability() && m_config.includes(account) && account.accountType() != MyMoneyAccount::Investment) { TableRow qaccountrow; // help for sort and render functions qaccountrow["rank"] = '0'; // // Handle currency conversion // MyMoneyMoney displayprice(1, 1); if (m_config.isConvertCurrency()) { // display currency is base currency, so set the price if (account.isForeignCurrency()) displayprice = account.baseCurrencyPrice(m_config.toDate()).reduce(); } else { // display currency is the account's deep currency. display this fact in the report qaccountrow["currency"] = account.currency().id(); } qaccountrow["account"] = account.name(); qaccountrow["accountid"] = account.id(); qaccountrow["topaccount"] = account.topParentName(); MyMoneyMoney shares = file->balance(account.id(), m_config.toDate()); qaccountrow["shares"] = shares.toString(); MyMoneyMoney netprice = account.deepCurrencyPrice(m_config.toDate()).reduce() * displayprice; qaccountrow["price"] = (netprice.reduce()).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); qaccountrow["value"] = (netprice.reduce() * shares.reduce()).convert(fraction).toString(); QString iid = (*it_account).institutionId(); // If an account does not have an institution, get it from the top-parent. if (iid.isEmpty() && ! account.isTopLevel()) { ReportAccount topaccount = account.topParent(); iid = topaccount.institutionId(); } if (iid.isEmpty()) qaccountrow["institution"] = i18nc("No institution", "None"); else qaccountrow["institution"] = file->institution(iid).name(); qaccountrow["type"] = KMyMoneyUtils::accountTypeToString((*it_account).accountType()); // TODO: Only do this if the report we're making really needs performance. Otherwise // it's an expensive calculation done for no reason if (account.isInvest()) { constructPerformanceRow(account, qaccountrow); } else qaccountrow["equitytype"].clear(); // don't add the account if it is closed. In fact, the business logic // should prevent that an account can be closed with a balance not equal // to zero, but we never know. if (!(shares.isZero() && account.isClosed())) m_rows += qaccountrow; } ++it_account; } } void QueryTable::constructSplitsTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); MyMoneyReport report(m_config); report.setReportAllSplits(false); report.setConsiderCategory(true); // support for opening and closing balances QMap accts; //get all transactions for this report QList transactions = file->transactionList(report); for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) { TableRow qA, qS; QDate pd; qA["id"] = qS["id"] = (* it_transaction).id(); qA["entrydate"] = qS["entrydate"] = (* it_transaction).entryDate().toString(Qt::ISODate); qA["postdate"] = qS["postdate"] = (* it_transaction).postDate().toString(Qt::ISODate); qA["commodity"] = qS["commodity"] = (* it_transaction).commodity(); pd = (* it_transaction).postDate(); qA["month"] = qS["month"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); qA["week"] = qS["week"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); qA["currency"] = qS["currency"] = ""; if ((* it_transaction).commodity() != file->baseCurrency().id()) { if (!report.isConvertCurrency()) { qA["currency"] = qS["currency"] = (*it_transaction).commodity(); } } // to handle splits, we decide on which account to base the split // (a reference point or point of view so to speak). here we take the // first account that is a stock account or loan account (or the first account // that is not an income or expense account if there is no stock or loan account) // to be the account (qA) that will have the sub-item "split" entries. we add // one transaction entry (qS) for each subsequent entry in the split. const QList& splits = (*it_transaction).splits(); QList::const_iterator myBegin, it_split; //S_end = splits.end(); for (it_split = splits.begin(), myBegin = splits.end(); it_split != splits.end(); ++it_split) { ReportAccount splitAcc = (* it_split).accountId(); // always put split with a "stock" account if it exists if (splitAcc.isInvest()) break; // prefer to put splits with a "loan" account if it exists if (splitAcc.isLoan()) myBegin = it_split; if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) { myBegin = it_split; } } // select our "reference" split if (it_split == splits.end()) { it_split = myBegin; } else { myBegin = it_split; } // if the split is still unknown, use the first one. I have seen this // happen with a transaction that has only a single split referencing an income or expense // account and has an amount and value of 0. Such a transaction will fall through // the above logic and leave 'it_split' pointing to splits.end() which causes the remainder // of this to end in an infinite loop. if (it_split == splits.end()) { it_split = splits.begin(); } // for "loan" reports, the loan transaction gets special treatment. // the splits of a loan transaction are placed on one line in the // reference (loan) account (qA). however, we process the matching // split entries (qS) normally. bool loan_special_case = false; - if (m_config.queryColumns() & MyMoneyReport::eQCloan) { + if (m_config.queryColumns() & MyMoneyReport::QueryColumns::Loan) { ReportAccount splitAcc = (*it_split).accountId(); loan_special_case = splitAcc.isLoan(); } // There is a slight chance that at this point myBegin is still pointing to splits.end() if the // transaction only has income and expense splits (which should not happen). In that case, point // it to the first split if (myBegin == splits.end()) { myBegin = splits.begin(); } //the account of the beginning splits ReportAccount myBeginAcc = (*myBegin).accountId(); bool include_me = true; QString a_fullname = ""; QString a_memo = ""; int pass = 1; do { MyMoneyMoney xr; ReportAccount splitAcc = (* it_split).accountId(); //get fraction for account int fraction = splitAcc.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = splitAcc.institutionId(); QString payee = (*it_split).payeeId(); const QList tagIdList = (*it_split).tagIdList(); if (m_config.isConvertCurrency()) { xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate()) * splitAcc.baseCurrencyPrice((*it_transaction).postDate())).reduce(); } else { xr = splitAcc.deepCurrencyPrice((*it_transaction).postDate()).reduce(); } // reverse the sign of incomes and expenses to keep consistency in the way it is displayed in other reports if (splitAcc.isIncomeExpense()) { xr = -xr; } if (splitAcc.isInvest()) { // use the institution of the parent for stock accounts institution = splitAcc.parent().institutionId(); MyMoneyMoney shares = (*it_split).shares(); qA["action"] = (*it_split).action(); qA["shares"] = shares.isZero() ? "" : (*it_split).shares().toString(); qA["price"] = shares.isZero() ? "" : xr.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative()) qA["action"] = "Sell"; qA["investaccount"] = splitAcc.parent().name(); } include_me = m_config.includes(splitAcc); a_fullname = splitAcc.fullName(); a_memo = (*it_split).memo(); qA["price"] = xr.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); qA["account"] = splitAcc.name(); qA["accountid"] = splitAcc.id(); qA["topaccount"] = splitAcc.topParentName(); qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); //FIXME-ALEX Is this useless? Isn't constructSplitsTable called only for cashflow type report? QString delimiter = ""; for (int i = 0; i < tagIdList.size(); i++) { qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified(); delimiter = ','; } qA["payee"] = payee.isEmpty() ? i18n("[Empty Payee]") : file->payee(payee).name().simplified(); qA["reconciledate"] = (*it_split).reconcileDate().toString(Qt::ISODate); qA["reconcileflag"] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); qA["number"] = (*it_split).number(); qA["memo"] = a_memo; qS["reconciledate"] = qA["reconciledate"]; qS["reconcileflag"] = qA["reconcileflag"]; qS["number"] = qA["number"]; qS["topcategory"] = splitAcc.topParentName(); // only include the configured accounts if (include_me) { // add the "summarized" split transaction // this is the sub-total of the split detail // convert to lowest fraction qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); qA["rank"] = '0'; //fill in account information if (! splitAcc.isIncomeExpense() && it_split != myBegin) { qA["account"] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", myBeginAcc.fullName()) : i18n("Transfer from %1", myBeginAcc.fullName()); } else if (it_split == myBegin) { //handle the main split if ((splits.count() > 2)) { //if it is the main split and has multiple splits, note that qA["account"] = i18n("[Split Transaction]"); } else { //fill the account name of the second split QList::const_iterator tempSplit = splits.begin(); //there are supposed to be only 2 splits if we ever get here if (tempSplit == myBegin && splits.count() > 1) ++tempSplit; //show the name of the category, or "transfer to/from" if it as an account ReportAccount tempSplitAcc = (*tempSplit).accountId(); if (! tempSplitAcc.isIncomeExpense()) { qA["account"] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", tempSplitAcc.fullName()) : i18n("Transfer from %1", tempSplitAcc.fullName()); } else { qA["account"] = tempSplitAcc.fullName(); } } } else { //in any other case, fill in the account name of the main split qA["account"] = myBeginAcc.fullName(); } //category data is always the one of the split qA ["category"] = splitAcc.fullName(); qA ["topcategory"] = splitAcc.topParentName(); qA ["categorytype"] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup()); m_rows += qA; // track accts that will need opening and closing balances accts.insert(splitAcc.id(), splitAcc); } ++it_split; // look for wrap-around if (it_split == splits.end()) it_split = splits.begin(); //check if there have been more passes than there are splits //this is to prevent infinite loops in cases of data inconsistency -- asoliverez ++pass; if (pass > splits.count()) break; } while (it_split != myBegin); if (loan_special_case) { m_rows += qA; } } // now run through our accts list and add opening and closing balances switch (m_config.rowType()) { case MyMoneyReport::eAccount: case MyMoneyReport::eTopAccount: break; // case MyMoneyReport::eCategory: // case MyMoneyReport::eTopCategory: // case MyMoneyReport::ePayee: // case MyMoneyReport::eMonth: // case MyMoneyReport::eWeek: default: return; } QDate startDate, endDate; report.validDateRange(startDate, endDate); QString strStartDate = startDate.toString(Qt::ISODate); QString strEndDate = endDate.toString(Qt::ISODate); startDate = startDate.addDays(-1); QMap::const_iterator it_account, accts_end; for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) { TableRow qA; ReportAccount account = (* it_account); //get fraction for account int fraction = account.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = account.institutionId(); // use the institution of the parent for stock accounts if (account.isInvest()) institution = account.parent().institutionId(); MyMoneyMoney startBalance, endBalance, startPrice, endPrice; MyMoneyMoney startShares, endShares; //get price and convert currency if necessary if (m_config.isConvertCurrency()) { startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce(); endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce(); } else { startPrice = account.deepCurrencyPrice(startDate).reduce(); endPrice = account.deepCurrencyPrice(endDate).reduce(); } startShares = file->balance(account.id(), startDate); endShares = file->balance(account.id(), endDate); //get starting and ending balances startBalance = startShares * startPrice; endBalance = endShares * endPrice; //starting balance // don't show currency if we're converting or if it's not foreign qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id(); qA["accountid"] = account.id(); qA["account"] = account.name(); qA["topaccount"] = account.topParentName(); qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qA["rank"] = "-2"; qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); if (account.isInvest()) { qA["shares"] = startShares.toString(); } qA["postdate"] = strStartDate; qA["balance"] = startBalance.convert(fraction).toString(); qA["value"].clear(); qA["id"] = 'A'; m_rows += qA; //ending balance qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); if (account.isInvest()) { qA["shares"] = endShares.toString(); } qA["postdate"] = strEndDate; qA["balance"] = endBalance.toString(); qA["id"] = 'Z'; m_rows += qA; } } } diff --git a/kmymoney/reports/querytabletest.cpp b/kmymoney/reports/querytabletest.cpp index 390330d31..5a25bbef5 100644 --- a/kmymoney/reports/querytabletest.cpp +++ b/kmymoney/reports/querytabletest.cpp @@ -1,790 +1,790 @@ /*************************************************************************** querytabletest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * 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 "querytabletest.h" #include #include #include #include #include #include #include #include #include #include #include #include "reportstestcommon.h" #include "querytable.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneystoragedump.h" #include "mymoneyreport.h" #include "mymoneystatement.h" #include "mymoneystoragexml.h" using namespace reports; using namespace test; QTEST_KDEMAIN_CORE_WITH_COMPONENTNAME(QueryTableTest, "kmymoney") void QueryTableTest::init() { storage = new MyMoneySeqAccessMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 100, 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), MyMoneyAccount::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount(QString("Credit Card"), MyMoneyAccount::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount(QString("Solo"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount(QString("Parent"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount(QString("Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount(QString("Foreign"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acTax = makeAccount(QString("Tax"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2005, 1, 11), acExpense, "", true); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void QueryTableTest::cleanup() { file->detachStorage(storage); delete storage; } void QueryTableTest::testQueryBasics() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eCategory); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account; + filter.setQueryColumns(static_cast(cols)); // filter.setName("Transactions by Category"); XMLandback(filter); QueryTable qtbl_1(filter); writeTabletoHTML(qtbl_1, "Transactions by Category.html"); QList rows = qtbl_1.rows(); QVERIFY(rows.count() == 12); QVERIFY(rows[0]["categorytype"] == "Expense"); QVERIFY(rows[0]["category"] == "Parent"); QVERIFY(rows[0]["postdate"] == "2004-02-01"); QVERIFY(rows[11]["categorytype"] == "Expense"); QVERIFY(rows[11]["category"] == "Solo"); QVERIFY(rows[11]["postdate"] == "2005-01-01"); QString html = qtbl_1.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Parent") == -(moParent1 + moParent2) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Parent: Child") == -(moChild) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Solo") == -(moSolo) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Expense") == -(moParent1 + moParent2 + moSolo + moChild) * 3); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eTopCategory); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account; + filter.setQueryColumns(static_cast(cols)); // filter.setName("Transactions by Top Category"); XMLandback(filter); QueryTable qtbl_2(filter); writeTabletoHTML(qtbl_2, "Transactions by Top Category.html"); rows = qtbl_2.rows(); QVERIFY(rows.count() == 12); QVERIFY(rows[0]["categorytype"] == "Expense"); QVERIFY(rows[0]["topcategory"] == "Parent"); QVERIFY(rows[0]["postdate"] == "2004-02-01"); QVERIFY(rows[8]["categorytype"] == "Expense"); QVERIFY(rows[8]["topcategory"] == "Parent"); QVERIFY(rows[8]["postdate"] == "2005-09-01"); QVERIFY(rows[11]["categorytype"] == "Expense"); QVERIFY(rows[11]["topcategory"] == "Solo"); QVERIFY(rows[11]["postdate"] == "2005-01-01"); html = qtbl_2.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Parent") == -(moParent1 + moParent2 + moChild) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Solo") == -(moSolo) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Expense") == -(moParent1 + moParent2 + moSolo + moChild) * 3); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category; + filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account.html"); rows = qtbl_3.rows(); #if 1 QVERIFY(rows.count() == 16); QVERIFY(rows[1]["account"] == "Checking Account"); QVERIFY(rows[1]["category"] == "Solo"); QVERIFY(rows[1]["postdate"] == "2004-01-01"); QVERIFY(rows[14]["account"] == "Credit Card"); QVERIFY(rows[14]["category"] == "Parent"); QVERIFY(rows[14]["postdate"] == "2005-09-01"); #else QVERIFY(rows.count() == 12); QVERIFY(rows[0]["account"] == "Checking Account"); QVERIFY(rows[0]["category"] == "Solo"); QVERIFY(rows[0]["postdate"] == "2004-01-01"); QVERIFY(rows[11]["account"] == "Credit Card"); QVERIFY(rows[11]["category"] == "Parent"); QVERIFY(rows[11]["postdate"] == "2005-09-01"); #endif html = qtbl_3.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance for checking account", "Total") + " Checking Account") == -(moSolo) * 3 + moCheckingOpen); QVERIFY(searchHTML(html, i18nc("Total balance for credit card", "Total") + " Credit Card") == -(moParent1 + moParent2 + moChild) * 3 + moCreditOpen); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::ePayee); filter.setName("Transactions by Payee"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCmemo | MyMoneyReport::eQCcategory; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Memo | MyMoneyReport::QueryColumns::Category; + filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_4(filter); writeTabletoHTML(qtbl_4, "Transactions by Payee.html"); rows = qtbl_4.rows(); QVERIFY(rows.count() == 12); QVERIFY(rows[0]["payee"] == "Test Payee"); QVERIFY(rows[0]["category"] == "Solo"); QVERIFY(rows[0]["postdate"] == "2004-01-01"); QVERIFY(rows[8]["payee"] == "Test Payee"); QVERIFY(rows[8]["category"] == "Parent: Child"); QVERIFY(rows[8]["postdate"] == "2004-11-07"); QVERIFY(rows[11]["payee"] == "Test Payee"); QVERIFY(rows[11]["category"] == "Parent"); QVERIFY(rows[11]["postdate"] == "2005-09-01"); html = qtbl_4.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Test Payee") == -(moParent1 + moParent2 + moSolo + moChild) * 3); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eMonth); filter.setName("Transactions by Month"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category; + filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_5(filter); writeTabletoHTML(qtbl_5, "Transactions by Month.html"); rows = qtbl_5.rows(); QVERIFY(rows.count() == 12); QVERIFY(rows[0]["payee"] == "Test Payee"); QVERIFY(rows[0]["category"] == "Solo"); QVERIFY(rows[0]["postdate"] == "2004-01-01"); QVERIFY(rows[8]["payee"] == "Test Payee"); QVERIFY(rows[8]["category"] == "Parent: Child"); QVERIFY(rows[8]["postdate"] == "2004-11-07"); QVERIFY(rows[11]["payee"] == "Test Payee"); QVERIFY(rows[11]["category"] == "Parent"); QVERIFY(rows[11]["postdate"] == "2005-09-01"); html = qtbl_5.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Month of 2004-01-01") == -moSolo); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Month of 2004-11-01") == -(moChild) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Month of 2004-05-01") == -moParent1 + moCheckingOpen); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eWeek); filter.setName("Transactions by Week"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category; + filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_6(filter); writeTabletoHTML(qtbl_6, "Transactions by Week.html"); rows = qtbl_6.rows(); QVERIFY(rows.count() == 12); QVERIFY(rows[0]["payee"] == "Test Payee"); QVERIFY(rows[0]["category"] == "Solo"); QVERIFY(rows[0]["postdate"] == "2004-01-01"); QVERIFY(rows[11]["payee"] == "Test Payee"); QVERIFY(rows[11]["category"] == "Parent"); QVERIFY(rows[11]["postdate"] == "2005-09-01"); html = qtbl_6.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Week of 2003-12-29") == -moSolo); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Week of 2004-11-01") == -(moChild) * 3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " Week of 2005-08-29") == -moParent2); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } // Test querytable::TableRow::operator> and operator== QueryTable::TableRow low; low["first"] = 'A'; low["second"] = 'B'; low["third"] = 'C'; QueryTable::TableRow high; high["first"] = 'A'; high["second"] = 'C'; high["third"] = 'B'; QueryTable::TableRow::setSortCriteria("first,second,third"); QVERIFY(low < high); QVERIFY(low <= high); QVERIFY(high > low); QVERIFY(high <= high); QVERIFY(high == high); } void QueryTableTest::testCashFlowAnalysis() { // // Test IRR calculations // CashFlowList list; list += CashFlowListItem(QDate(2004, 5, 3), MyMoneyMoney(1000.0)); list += CashFlowListItem(QDate(2004, 5, 20), MyMoneyMoney(59.0)); list += CashFlowListItem(QDate(2004, 6, 3), MyMoneyMoney(14.0)); list += CashFlowListItem(QDate(2004, 6, 24), MyMoneyMoney(92.0)); list += CashFlowListItem(QDate(2004, 7, 6), MyMoneyMoney(63.0)); list += CashFlowListItem(QDate(2004, 7, 25), MyMoneyMoney(15.0)); list += CashFlowListItem(QDate(2004, 8, 5), MyMoneyMoney(92.0)); list += CashFlowListItem(QDate(2004, 9, 2), MyMoneyMoney(18.0)); list += CashFlowListItem(QDate(2004, 9, 21), MyMoneyMoney(5.0)); list += CashFlowListItem(QDate(2004, 10, 16), MyMoneyMoney(-2037.0)); MyMoneyMoney IRR(list.IRR(), 1000); QVERIFY(IRR == MyMoneyMoney(1676, 1000)); list.pop_back(); list += CashFlowListItem(QDate(2004, 10, 16), MyMoneyMoney(-1358.0)); IRR = MyMoneyMoney(list.IRR(), 1000); QVERIFY(IRR.isZero()); } void QueryTableTest::testAccountQuery() { try { QString htmlcontext = QString("\n\n%1\n\n"); // // No transactions, opening balances only // MyMoneyReport filter; filter.setRowType(MyMoneyReport::eInstitution); filter.setName("Accounts by Institution (No transactions)"); XMLandback(filter); QueryTable qtbl_1(filter); writeTabletoHTML(qtbl_1, "Accounts by Institution (No transactions).html"); QList rows = qtbl_1.rows(); QVERIFY(rows.count() == 2); QVERIFY(rows[0]["account"] == "Checking Account"); QVERIFY(MyMoneyMoney(rows[0]["value"]) == moCheckingOpen); QVERIFY(rows[0]["equitytype"].isEmpty()); QVERIFY(rows[1]["account"] == "Credit Card"); QVERIFY(MyMoneyMoney(rows[1]["value"]) == moCreditOpen); QVERIFY(rows[1]["equitytype"].isEmpty()); QString html = qtbl_1.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + " None") == moCheckingOpen + moCreditOpen); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == moCheckingOpen + moCreditOpen); // // Adding in transactions // TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); filter.setRowType(MyMoneyReport::eInstitution); filter.setName("Accounts by Institution (With Transactions)"); XMLandback(filter); QueryTable qtbl_2(filter); rows = qtbl_2.rows(); QVERIFY(rows.count() == 2); QVERIFY(rows[0]["account"] == "Checking Account"); QVERIFY(MyMoneyMoney(rows[0]["value"]) == (moCheckingOpen - moSolo*3)); QVERIFY(rows[1]["account"] == "Credit Card"); QVERIFY(MyMoneyMoney(rows[1]["value"]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); html = qtbl_2.renderBody(); QVERIFY(searchHTML(html, i18n("Grand Total")) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); // // Account TYPES // filter.setRowType(MyMoneyReport::eAccountType); filter.setName("Accounts by Type"); XMLandback(filter); QueryTable qtbl_3(filter); rows = qtbl_3.rows(); QVERIFY(rows.count() == 2); QVERIFY(rows[0]["account"] == "Checking Account"); QVERIFY(MyMoneyMoney(rows[0]["value"]) == (moCheckingOpen - moSolo*3)); QVERIFY(rows[1]["account"] == "Credit Card"); QVERIFY(MyMoneyMoney(rows[1]["value"]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); html = qtbl_3.renderBody(); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + ' ' + i18n("Checking")) == moCheckingOpen - moSolo*3); QVERIFY(searchHTML(html, i18nc("Total balance", "Total") + ' ' + i18n("Credit Card")) == moCreditOpen - (moParent1 + moParent2 + moChild) * 3); QVERIFY(searchHTML(html, i18nc("Grand total balance", "Grand Total")) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testInvestment() { try { // Equities eqStock1 = makeEquity("Stock1", "STK1"); eqStock2 = makeEquity("Stock2", "STK2"); // Accounts acInvestment = makeAccount("Investment", MyMoneyAccount::Investment, moZero, QDate(2004, 1, 1), acAsset); acStock1 = makeAccount("Stock 1", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1); acStock2 = makeAccount("Stock 2", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2); acDividends = makeAccount("Dividends", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome); acInterest = makeAccount("Interest", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome); // Transactions // Date Action Shares Price Stock Asset Income InvTransactionHelper s1b1(QDate(2004, 2, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1b2(QDate(2004, 3, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString()); InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString()); InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends); InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends); InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends); InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(120.00), acStock1, acChecking, acDividends); InvTransactionHelper s1y1(QDate(2004, 9, 15), MyMoneySplit::ActionYield, MyMoneyMoney(10.00), MyMoneyMoney(110.00), acStock1, acChecking, acInterest); makeEquityPrice(eqStock1, QDate(2004, 10, 1), MyMoneyMoney(100.00)); // // Investment Transactions Report // MyMoneyReport invtran_r( MyMoneyReport::eTopAccount, - MyMoneyReport::eQCaction | MyMoneyReport::eQCshares | MyMoneyReport::eQCprice, + MyMoneyReport::QueryColumns::Action | MyMoneyReport::QueryColumns::Shares | MyMoneyReport::QueryColumns::Price, MyMoneyTransactionFilter::userDefined, MyMoneyReport::eDetailAll, i18n("Investment Transactions"), i18n("Test Report") ); invtran_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31)); invtran_r.setInvestmentsOnly(true); XMLandback(invtran_r); QueryTable invtran(invtran_r); #if 1 writeTabletoHTML(invtran, "investment_transactions_test.html"); QList rows = invtran.rows(); QVERIFY(rows.count() == 17); QVERIFY(MyMoneyMoney(rows[1]["value"]) == MyMoneyMoney(100000.00)); QVERIFY(MyMoneyMoney(rows[2]["value"]) == MyMoneyMoney(110000.00)); QVERIFY(MyMoneyMoney(rows[3]["value"]) == MyMoneyMoney(-24000.00)); QVERIFY(MyMoneyMoney(rows[4]["value"]) == MyMoneyMoney(-20000.00)); QVERIFY(MyMoneyMoney(rows[5]["value"]) == MyMoneyMoney(5000.00)); QVERIFY(MyMoneyMoney(rows[6]["value"]) == MyMoneyMoney(4000.00)); // need to fix these... fundamentally different from the original test //QVERIFY(MyMoneyMoney(invtran.m_rows[8]["value"])==MyMoneyMoney( -1000.00)); //QVERIFY(MyMoneyMoney(invtran.m_rows[11]["value"])==MyMoneyMoney( -1200.00)); //QVERIFY(MyMoneyMoney(invtran.m_rows[14]["value"])==MyMoneyMoney( -1100.00)); QVERIFY(MyMoneyMoney(rows[1]["price"]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[3]["price"]) == MyMoneyMoney(120.00)); QVERIFY(MyMoneyMoney(rows[5]["price"]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[7]["price"]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[10]["price"]) == MyMoneyMoney(120.00)); QVERIFY(MyMoneyMoney(rows[2]["shares"]) == MyMoneyMoney(1000.00)); QVERIFY(MyMoneyMoney(rows[4]["shares"]) == MyMoneyMoney(-200.00)); QVERIFY(MyMoneyMoney(rows[6]["shares"]) == MyMoneyMoney(50.00)); QVERIFY(MyMoneyMoney(rows[8]["shares"]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[11]["shares"]) == MyMoneyMoney(0.00)); QVERIFY(rows[1]["action"] == "Buy"); QVERIFY(rows[3]["action"] == "Sell"); QVERIFY(rows[5]["action"] == "Reinvest"); QVERIFY(rows[7]["action"] == "Dividend"); QVERIFY(rows[13]["action"] == "Yield"); #else QVERIFY(rows.count() == 9); QVERIFY(MyMoneyMoney(rows[0]["value"]) == MyMoneyMoney(100000.00)); QVERIFY(MyMoneyMoney(rows[1]["value"]) == MyMoneyMoney(110000.00)); QVERIFY(MyMoneyMoney(rows[2]["value"]) == MyMoneyMoney(-24000.00)); QVERIFY(MyMoneyMoney(rows[3]["value"]) == MyMoneyMoney(-20000.00)); QVERIFY(MyMoneyMoney(rows[4]["value"]) == MyMoneyMoney(5000.00)); QVERIFY(MyMoneyMoney(rows[5]["value"]) == MyMoneyMoney(4000.00)); QVERIFY(MyMoneyMoney(rows[6]["value"]) == MyMoneyMoney(-1000.00)); QVERIFY(MyMoneyMoney(rows[7]["value"]) == MyMoneyMoney(-1200.00)); QVERIFY(MyMoneyMoney(rows[8]["value"]) == MyMoneyMoney(-1100.00)); QVERIFY(MyMoneyMoney(rows[0]["price"]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[2]["price"]) == MyMoneyMoney(120.00)); QVERIFY(MyMoneyMoney(rows[4]["price"]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[6]["price"]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[8]["price"]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[1]["shares"]) == MyMoneyMoney(1000.00)); QVERIFY(MyMoneyMoney(rows[3]["shares"]) == MyMoneyMoney(-200.00)); QVERIFY(MyMoneyMoney(rows[5]["shares"]) == MyMoneyMoney(50.00)); QVERIFY(MyMoneyMoney(rows[7]["shares"]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[8]["shares"]) == MyMoneyMoney(0.00)); QVERIFY(rows[0]["action"] == "Buy"); QVERIFY(rows[2]["action"] == "Sell"); QVERIFY(rows[4]["action"] == "Reinvest"); QVERIFY(rows[6]["action"] == "Dividend"); QVERIFY(rows[8]["action"] == "Yield"); #endif QString html = invtran.renderBody(); #if 1 // i think this is the correct amount. different treatment of dividend and yield QVERIFY(searchHTML(html, i18n("Total Stock 1")) == MyMoneyMoney(175000.00)); QVERIFY(searchHTML(html, i18n("Grand Total")) == MyMoneyMoney(175000.00)); #else QVERIFY(searchHTML(html, i18n("Total Stock 1")) == MyMoneyMoney(171700.00)); QVERIFY(searchHTML(html, i18n("Grand Total")) == MyMoneyMoney(171700.00)); #endif // // Investment Performance Report // MyMoneyReport invhold_r( MyMoneyReport::eAccountByTopAccount, - MyMoneyReport::eQCperformance, + MyMoneyReport::QueryColumns::Performance, MyMoneyTransactionFilter::userDefined, MyMoneyReport::eDetailAll, i18n("Investment Performance by Account"), i18n("Test Report") ); invhold_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 10, 1)); invhold_r.setInvestmentsOnly(true); XMLandback(invhold_r); QueryTable invhold(invhold_r); writeTabletoHTML(invhold, "Investment Performance by Account.html"); rows = invhold.rows(); QVERIFY(rows.count() == 1); QVERIFY(MyMoneyMoney(rows[0]["return"]) == MyMoneyMoney("669/10000")); QVERIFY(MyMoneyMoney(rows[0]["buys"]) == MyMoneyMoney(210000.00)); QVERIFY(MyMoneyMoney(rows[0]["sells"]) == MyMoneyMoney(-44000.00)); QVERIFY(MyMoneyMoney(rows[0]["reinvestincome"]) == MyMoneyMoney(9000.00)); QVERIFY(MyMoneyMoney(rows[0]["cashincome"]) == MyMoneyMoney(3300.00)); QVERIFY(MyMoneyMoney(rows[0]["shares"]) == MyMoneyMoney(1700.00)); QVERIFY(MyMoneyMoney(rows[0]["price"]) == MyMoneyMoney(100.00)); html = invhold.renderBody(); QVERIFY(searchHTML(html, i18n("Grand Total")) == MyMoneyMoney(170000.00)); #if 0 // Dump file & reports QFile g("investmentkmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; IMyMoneyStorageFormat& interface = xml; interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); invtran.dump("invtran.html", "%1"); invhold.dump("invhold.html", "%1"); #endif } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } //this is to prevent me from making mistakes again when modifying balances - asoliverez //this case tests only the opening and ending balance of the accounts void QueryTableTest::testBalanceColumn() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; - filter.setQueryColumns(static_cast(cols)); // + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Balance; + filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account.html"); QString html = qtbl_3.renderBody(); QList rows = qtbl_3.rows(); QVERIFY(rows.count() == 16); //this is to make sure that the dates of closing and opening balances and the balance numbers are ok QString openingDate = KGlobal::locale()->formatDate(QDate(2004, 1, 1), KLocale::ShortDate); QString closingDate = KGlobal::locale()->formatDate(QDate(2005, 9, 1), KLocale::ShortDate); QVERIFY(html.indexOf(openingDate + "" + i18n("Opening Balance")) > 0); QVERIFY(html.indexOf(closingDate + "" + i18n("Closing Balance") + " -702.36") > 0); QVERIFY(html.indexOf(closingDate + "" + i18n("Closing Balance") + " -705.69") > 0); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testBalanceColumnWithMultipleCurrencies() { try { MyMoneyMoney moJpyOpening(0.0, 1); MyMoneyMoney moJpyPrice(0.010, 100); MyMoneyMoney moJpyPrice2(0.011, 100); MyMoneyMoney moJpyPrice3(0.024, 100); MyMoneyMoney moTransaction(100, 1); MyMoneyMoney moJpyTransaction(100, 1); QString acJpyChecking = makeAccount(QString("Japanese Checking"), MyMoneyAccount::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY"); makePrice("JPY", QDate(2004, 1, 1), MyMoneyMoney(moJpyPrice)); makePrice("JPY", QDate(2004, 5, 1), MyMoneyMoney(moJpyPrice2)); makePrice("JPY", QDate(2004, 6, 30), MyMoneyMoney(moJpyPrice3)); QDate openingDate(2004, 2, 20); QDate intermediateDate(2004, 5, 20); QDate closingDate(2004, 7, 20); TransactionHelper t1(openingDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t4(openingDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking); TransactionHelper t2(intermediateDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t5(intermediateDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking); TransactionHelper t3(closingDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t6(closingDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking); // test that an income/expense transaction that involves a currency exchange is properly reported TransactionHelper t7(intermediateDate, MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acSolo, "JPY"); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; - filter.setQueryColumns(static_cast(cols)); + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Balance; + filter.setQueryColumns(static_cast(cols)); // don't convert values to the default currency filter.setConvertCurrency(false); XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account (multiple currencies).html"); QString html = qtbl_3.renderBody(); QList rows = qtbl_3.rows(); QVERIFY(rows.count() == 19); //this is to make sure that the dates of closing and opening balances and the balance numbers are ok QString openingDateString = KGlobal::locale()->formatDate(openingDate, KLocale::ShortDate); QString intermediateDateString = KGlobal::locale()->formatDate(intermediateDate, KLocale::ShortDate); QString closingDateString = KGlobal::locale()->formatDate(closingDate, KLocale::ShortDate); // check the opening and closing balances QVERIFY(html.indexOf(openingDateString + "" + i18n("Opening Balance") + " 0.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " 304.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -300.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "JPY -400.00") > 0); // after a transfer of 100 JPY the balance should be 1.00 - price is 0.010 (precision of 2) QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer from Japanese Checking 1.00 1.00") > 0); // after a transfer of 100 the balance should be 101.00 QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer from Credit Card 100.00 101.00") > 0); // after a transfer of 100 JPY the balance should be 102.00 - price is 0.011 (precision of 2) QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer from Japanese Checking 1.00 102.00") > 0); // after a transfer of 100 the balance should be 202.00 QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer from Credit Card 100.00 202.00") > 0); // after a transfer of 100 JPY the balance should be 204.00 - price is 0.024 (precision of 2) QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer from Japanese Checking 2.00 204.00") > 0); // after a transfer of 100 the balance should be 304.00 QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer from Credit Card 100.00 304.00") > 0); // a 100.00 JPY withdrawal should be displayed as such even if the expense account uses another currency QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSoloJPY -100.00JPY -300.00") > 0); // now run the same report again but this time convert all values to the base currency and make sure the values are correct filter.setConvertCurrency(true); XMLandback(filter); QueryTable qtbl_4(filter); writeTabletoHTML(qtbl_4, "Transactions by Account (multiple currencies converted to base).html"); html = qtbl_4.renderBody(); rows = qtbl_4.rows(); QVERIFY(rows.count() == 19); // check the opening and closing balances QVERIFY(html.indexOf(openingDateString + "" + i18n("Opening Balance") + " 0.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " 304.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -300.00") > 0); // although the balance should be -5.00 it's -8.00 because the foreign currency balance is converted using the closing date price (0.024) QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -8.00") > 0); // a 100.00 JPY transfer should be displayed as -1.00 when converted to the base currency using the opening date price QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer to Checking Account -1.00 -1.00") > 0); // a 100.00 JPY transfer should be displayed as -1.00 when converted to the base currency using the intermediate date price QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer to Checking Account -1.00 -2.00") > 0); // a 100.00 JPY withdrawal should be displayed as -1.00 when converted to the base currency using the intermediate date price QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSolo -1.00 -3.00") > 0); // a 100.00 JPY transfer should be displayed as -2.00 when converted to the base currency using the closing date price (notice the balance is -5.00) QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer to Checking Account -2.00 -5.00") > 0); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testTaxReport() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acChecking, acTax); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eCategory); filter.setName("Tax Transactions"); - cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; - filter.setQueryColumns(static_cast(cols)); + cols = MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account; + filter.setQueryColumns(static_cast(cols)); filter.setTax(true); XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Tax Transactions.html"); QList rows = qtbl_3.rows(); QString html = qtbl_3.renderBody(); QVERIFY(rows.count() == 1); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } diff --git a/kmymoney/views/kreportsview.cpp b/kmymoney/views/kreportsview.cpp index a1e307ded..7f80e6eb6 100644 --- a/kmymoney/views/kreportsview.cpp +++ b/kmymoney/views/kreportsview.cpp @@ -1,1784 +1,1784 @@ /*************************************************************************** kreportsview.cpp - 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 ***************************************************************************/ /*************************************************************************** * * * 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 "kreportsview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include "querytable.h" #include "objectinfotable.h" #include "kreportconfigurationfilterdlg.h" using namespace reports; #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" /** * KReportsView::KReportTab Implementation */ KReportsView::KReportTab::KReportTab(KTabWidget* parent, const MyMoneyReport& report): QWidget(parent), m_part(new KHTMLPart(this)), m_chartView(new KReportChartView(this)), m_control(new kMyMoneyReportControl(this)), m_layout(new QVBoxLayout(this)), m_report(report), m_deleteMe(false), m_chartEnabled(false), m_showingChart(false), m_needReload(true), m_table(0) { m_layout->setSpacing(6); m_part->setFontScaleFactor(KMyMoneyGlobalSettings::fontSizePercentage()); //set button icons m_control->buttonChart->setIcon(KIcon("office-chart-line")); m_control->buttonClose->setIcon(KIcon("document-close")); m_control->buttonConfigure->setIcon(KIcon("configure")); m_control->buttonCopy->setIcon(KIcon("edit-copy")); m_control->buttonDelete->setIcon(KIcon("edit-delete")); m_control->buttonExport->setIcon(KIcon("document-export")); m_control->buttonNew->setIcon(KIcon("document-new")); m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_chartView->hide(); m_layout->addWidget(m_control); m_layout->addWidget(m_part->view()); m_layout->addWidget(m_chartView); parent->addTab(this, KIcon("application-vnd.oasis.opendocument.spreadsheet"), report.name()); parent->setTabEnabled(parent->indexOf(this), true); // get users character set encoding m_encoding = KGlobal::locale()->encoding(); } KReportsView::KReportTab::~KReportTab() { delete m_table; //This is to prevent a crash on exit with KDE 4.3.2 delete m_part; } void KReportsView::KReportTab::print() { if (m_part && m_part->view()) m_part->view()->print(); } void KReportsView::KReportTab::copyToClipboard() { QMimeData* pMimeData = new QMimeData(); pMimeData->setHtml(m_table->renderHTML(qobject_cast(this), m_encoding, m_report.name(), true)); QApplication::clipboard()->setMimeData(pMimeData); } void KReportsView::KReportTab::saveAs(const QString& filename, bool includeCSS) { QFile file(filename); if (file.open(QIODevice::WriteOnly)) { if (QFileInfo(filename).suffix().toLower() == "csv") { QTextStream(&file) << m_table->renderCSV(); } else { QString table = m_table->renderHTML(qobject_cast(this), m_encoding, m_report.name(), includeCSS); QTextStream stream(&file); stream << table; } file.close(); } } void KReportsView::KReportTab::loadTab() { m_needReload = true; if (isVisible()) { m_needReload = false; updateReport(); } } void KReportsView::KReportTab::showEvent(QShowEvent * event) { if (m_needReload) { m_needReload = false; updateReport(); } QWidget::showEvent(event); } void KReportsView::KReportTab::updateReport() { // 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() == MyMoneyReport::ePivotTable) { m_table = new PivotTable(m_report); m_chartEnabled = true; } else if (m_report.reportType() == MyMoneyReport::eQueryTable) { m_table = new QueryTable(m_report); m_chartEnabled = false; } else if (m_report.reportType() == MyMoneyReport::eInfoTable) { m_table = new ObjectInfoTable(m_report); m_chartEnabled = false; } m_part->begin(); m_part->write(m_table->renderHTML(qobject_cast(this), m_encoding, m_report.name())); m_part->end(); m_table->drawChart(*m_chartView); m_control->buttonChart->setEnabled(m_chartEnabled); if (m_report.isChartByDefault() && !m_showingChart) toggleChart(); } void KReportsView::KReportTab::toggleChart() { // for now it will just SHOW the chart. In the future it actually has to toggle it. if (m_showingChart) { m_part->view()->show(); m_chartView->hide(); m_control->buttonChart->setText(i18n("Chart")); m_control->buttonChart->setToolTip(i18n("Show the chart version of this report")); m_control->buttonChart->setIcon(KIcon("office-chart-line")); } else { m_part->view()->hide(); m_chartView->show(); m_control->buttonChart->setText(i18n("Report")); m_control->buttonChart->setToolTip(i18n("Show the report version of this chart")); m_control->buttonChart->setIcon(KIcon("view-financial-list")); } m_showingChart = ! m_showingChart; } /** * KReportsView Implementation */ class KReportsView::Private { public: Private() { fSavProps = new FileSaveProperties(); } ~Private() { delete fSavProps; } class FileSaveProperties { public: FileSaveProperties() { includeCssCheckBox = 0; cbIsChecked = true; filter = "*.csv"; filtCsv = "*.csv|" + i18nc("CSV (Filefilter)", "CSV files"); filtHtml = "*.html|" + i18nc("HTML (Filefilter)", "HTML files"); } /** * 'include css' checkbox for file save dialog. */ QCheckBox* includeCssCheckBox; /** * Persistent checked-state of includeCssCheckBox. */ bool cbIsChecked; /** * Current filtername of file save dialog * without description, e.g. @c '*.csv' */ QString filter; /** * CSV filtername and description. * * @see KFileDialog::KFileDialog */ QString filtCsv; /** * HTML filtername and description. * * @see KFileDialog::KFileDialog */ QString filtHtml; }; FileSaveProperties* fSavProps; }; KReportsView::KReportsView(QWidget *parent, const char *name) : KMyMoneyViewBase(parent, name, i18n("Reports")), d(new Private), m_needReload(false), m_reportListView(0) { // build reports toc setColumnsAlreadyAdjusted(false); m_reportTabWidget = new KTabWidget(this); 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, // so avoid any further action in case of doubleclick // (see slotItemDoubleClicked) m_tocTreeWidget->setExpandsOnDoubleClick(false); m_tocTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); m_tocTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection); m_listTabLayout->addWidget(m_tocTreeWidget); m_reportTabWidget->addTab(m_listTab, i18n("Reports")); connect(m_reportTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(slotClose(int))); connect(m_tocTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotItemDoubleClicked(QTreeWidgetItem*,int))); connect(m_tocTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotListContextMenu(QPoint))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); } KReportsView::~KReportsView() { delete d; } void KReportsView::showEvent(QShowEvent * event) { emit aboutToShow(); if (m_needReload) { loadView(); m_needReload = false; } KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (tab) emit reportSelected(tab->report()); else emit reportSelected(MyMoneyReport()); // don't forget base class implementation KMyMoneyViewBase::showEvent(event); } void KReportsView::slotLoadView() { m_needReload = true; if (isVisible()) { loadView(); m_needReload = false; } } void KReportsView::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++) { QTreeWidgetItem* 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()) { QString 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; QString 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; QString 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!! KReportTab* 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 KReportsView::slotOpenUrl(const KUrl &url, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&) { QString view = url.fileName(); QString command = url.queryItem("command"); QString id = url.queryItem("id"); QString tid = url.queryItem("tid"); if (view == VIEW_REPORTS) { if (command.isEmpty()) { // slotRefreshView(); } else if (command == "print") slotPrintView(); else if (command == "copy") slotCopyView(); else if (command == "save") slotSaveView(); else if (command == "configure") slotConfigure(); else if (command == "duplicate") slotDuplicate(); else if (command == "close") slotCloseCurrent(); else if (command == "delete") slotDelete(); else qWarning() << i18n("Unknown command '%1' in KReportsView::slotOpenUrl()", qPrintable(command)); } else if (view == VIEW_LEDGER) { emit ledgerSelected(id, tid); } else { qWarning() << i18n("Unknown view '%1' in KReportsView::slotOpenUrl()", qPrintable(view)); } } void KReportsView::slotPrintView() { KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (tab) tab->print(); } void KReportsView::slotCopyView() { KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (tab) tab->copyToClipboard(); } void KReportsView::slotSaveView() { KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (tab) { d->fSavProps->includeCssCheckBox = new QCheckBox(i18n("Include Stylesheet")); // restore checkbox checked state d->fSavProps->includeCssCheckBox->setChecked(d->fSavProps->cbIsChecked); QString filterList = d->fSavProps->filtCsv + '\n' + d->fSavProps->filtHtml; QPointer dlg = new KFileDialog(KUrl("kfiledialog:///kmymoney-export"), filterList, this, qobject_cast(d->fSavProps->includeCssCheckBox)); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotSaveFilterChanged(QString))); dlg->setOperationMode(KFileDialog::Saving); dlg->setCaption(i18n("Export as")); // set current file filter && enable or disable includeCssCheckBox KFileFilterCombo* combo = dlg->filterWidget(); if (d->fSavProps->filter == "*.csv") { combo->setCurrentFilter(d->fSavProps->filtCsv); d->fSavProps->includeCssCheckBox->setEnabled(false); } else { combo->setCurrentFilter(d->fSavProps->filtHtml); d->fSavProps->includeCssCheckBox->setEnabled(true); } if (dlg->exec() == QDialog::Accepted) { KUrl newURL = dlg->selectedUrl(); if (!newURL.isEmpty()) { QString newName = newURL.pathOrUrl(); if (newName.indexOf('.') == -1) newName.append(d->fSavProps->filter.remove('*')); // save checkbox checked state d->fSavProps->cbIsChecked = d->fSavProps->includeCssCheckBox->isChecked(); try { tab->saveAs(newName, d->fSavProps->includeCssCheckBox->isEnabled() && d->fSavProps->includeCssCheckBox->isChecked()); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Failed to save: %1", e.what())); } } } delete d->fSavProps->includeCssCheckBox; delete dlg; } } void KReportsView::slotSaveFilterChanged(const QString& filter) { d->fSavProps->filter = filter; if (filter == "*.html") { d->fSavProps->includeCssCheckBox->setEnabled(true); } else { d->fSavProps->includeCssCheckBox->setEnabled(false); } } void KReportsView::slotConfigure() { QString cm = "KReportsView::slotConfigure"; KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (!tab) { // nothing to do return; } MyMoneyReport report = tab->report(); if (report.comment() == i18n("Default Report") || report.comment() == i18n("Generated Report")) { report.setComment(i18n("Custom Report")); report.setName(i18n("%1 (Customized)", report.name())); } QPointer dlg = new KReportConfigurationFilterDlg(report); if (dlg->exec()) { MyMoneyReport newreport = dlg->getConfig(); // If this report has an ID, then MODIFY it, otherwise ADD it MyMoneyFileTransaction ft; try { if (! newreport.id().isEmpty()) { MyMoneyFile::instance()->modifyReport(newreport); ft.commit(); tab->modifyReport(newreport); m_reportTabWidget->setTabText(m_reportTabWidget->indexOf(tab), newreport.name()); m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(tab)) ; } else { MyMoneyFile::instance()->addReport(newreport); ft.commit(); QString reportGroupName = newreport.group(); // find report group TocItemGroup* tocItemGroup = m_allTocItemGroups[reportGroupName]; if (!tocItemGroup) { QString error = i18n("Could not find reportgroup \"%1\" for report \"%2\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", reportGroupName, newreport.name()); // write to messagehandler qWarning() << cm << error; // also inform user KMessageBox::error(m_reportTabWidget, error, i18n("Critical Error")); // cleanup delete dlg; return; } // do not add TocItemReport to TocItemGroup here, // this is done in loadView addReportTab(newreport); } } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Failed to configure report: %1", e.what())); } } delete dlg; } void KReportsView::slotDuplicate() { QString cm = "KReportsView::slotDuplicate"; KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (!tab) { // nothing to do return; } MyMoneyReport dupe = tab->report(); dupe.setName(i18n("Copy of %1", dupe.name())); if (dupe.comment() == i18n("Default Report")) dupe.setComment(i18n("Custom Report")); dupe.clearId(); QPointer dlg = new KReportConfigurationFilterDlg(dupe); if (dlg->exec()) { MyMoneyReport newReport = dlg->getConfig(); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addReport(newReport); ft.commit(); QString reportGroupName = newReport.group(); // find report group TocItemGroup* tocItemGroup = m_allTocItemGroups[reportGroupName]; if (!tocItemGroup) { QString error = i18n("Could not find reportgroup \"%1\" for report \"%2\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", reportGroupName, newReport.name()); // write to messagehandler qWarning() << cm << error; // also inform user KMessageBox::error(m_reportTabWidget, error, i18n("Critical Error")); // cleanup delete dlg; return; } // do not add TocItemReport to TocItemGroup here, // this is done in loadView addReportTab(newReport); } catch (const MyMoneyException &e) { QString error = i18n("Cannot add report, reason: \"%1\"", e.what()); // write to messagehandler qWarning() << cm << error; // also inform user KMessageBox::error(m_reportTabWidget, error, i18n("Critical Error")); } } delete dlg; } void KReportsView::slotDelete() { KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (!tab) { // nothing to do return; } MyMoneyReport report = tab->report(); if (! report.id().isEmpty()) { if (KMessageBox::Continue == deleteReportDialog(report.name())) { // close the tab and then remove the report so that it is not // generated again during the following loadView() call slotClose(m_reportTabWidget->currentIndex()); MyMoneyFileTransaction ft; MyMoneyFile::instance()->removeReport(report); ft.commit(); } } else { KMessageBox::information(this, QString("") + i18n("%1 is a default report, so it cannot be deleted.", report.name()) + QString(""), i18n("Delete Report?")); } } int KReportsView::deleteReportDialog(const QString &reportName) { return KMessageBox::warningContinueCancel(this, QString("") + i18n("Are you sure you want to delete report %1? There is no way to recover it.", reportName) + QString(""), i18n("Delete Report?")); } void KReportsView::slotOpenReport(const QString& id) { if (id.isEmpty()) { // nothing to do return; } KReportTab* page = 0; // Find the tab which contains the report int index = 1; while (index < m_reportTabWidget->count()) { KReportTab* current = dynamic_cast(m_reportTabWidget->widget(index)); if (current->report().id() == id) { page = current; break; } ++index; } // Show the tab, or create a new one, as needed if (page) m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(page)); else addReportTab(MyMoneyFile::instance()->report(id)); } void KReportsView::slotOpenReport(const MyMoneyReport& report) { kDebug(2) << Q_FUNC_INFO << " " << report.name(); KReportTab* page = 0; // Find the tab which contains the report indicated by this list item int index = 1; while (index < m_reportTabWidget->count()) { KReportTab* current = dynamic_cast(m_reportTabWidget->widget(index)); if (current->report().name() == report.name()) { page = current; break; } ++index; } // Show the tab, or create a new one, as needed if (page) m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(page)); else addReportTab(report); } void KReportsView::slotItemDoubleClicked(QTreeWidgetItem* item, int) { TocItem* tocItem = dynamic_cast(item); if (!tocItem->isReport()) { // toggle the expanded-state for reportgroup-items item->setExpanded(item->isExpanded() ? false : true); // nothing else to do for reportgroup-items return; } TocItemReport* reportTocItem = dynamic_cast(tocItem); MyMoneyReport& report = reportTocItem->getReport(); KReportTab* page = 0; // Find the tab which contains the report indicated by this list item int index = 1; while (index < m_reportTabWidget->count()) { KReportTab* current = dynamic_cast(m_reportTabWidget->widget(index)); // If this report has an ID, we'll use the ID to match if (! report.id().isEmpty()) { if (current->report().id() == report.id()) { page = current; break; } } // Otherwise, use the name to match. THIS ASSUMES that no 2 default reports // have the same name...but that would be pretty a boneheaded thing to do. else { if (current->report().name() == report.name()) { page = current; break; } } ++index; } // Show the tab, or create a new one, as needed if (page) m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(page)); else addReportTab(report); } void KReportsView::slotToggleChart() { KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget()); if (tab) tab->toggleChart(); } void KReportsView::slotCloseCurrent() { slotClose(m_reportTabWidget->currentIndex()); } void KReportsView::slotClose(int index) { KReportTab* tab = dynamic_cast(m_reportTabWidget->widget(index)); if (tab) { m_reportTabWidget->removeTab(index); tab->setReadyToDelete(true); } } void KReportsView::slotCloseAll() { KReportTab* tab = dynamic_cast(m_reportTabWidget->widget(1)); while (tab) { m_reportTabWidget->removeTab(m_reportTabWidget->indexOf(tab)); tab->setReadyToDelete(true); tab = dynamic_cast(m_reportTabWidget->widget(1)); } } void KReportsView::addReportTab(const MyMoneyReport& report) { KReportTab* tab = new KReportTab(m_reportTabWidget, report); connect(tab->control()->buttonChart, SIGNAL(clicked()), this, SLOT(slotToggleChart())); connect(tab->control()->buttonConfigure, SIGNAL(clicked()), this, SLOT(slotConfigure())); connect(tab->control()->buttonNew, SIGNAL(clicked()), this, SLOT(slotDuplicate())); connect(tab->control()->buttonCopy, SIGNAL(clicked()), this, SLOT(slotCopyView())); connect(tab->control()->buttonExport, SIGNAL(clicked()), this, SLOT(slotSaveView())); connect(tab->control()->buttonDelete, SIGNAL(clicked()), this, SLOT(slotDelete())); connect(tab->control()->buttonClose, SIGNAL(clicked()), this, SLOT(slotCloseCurrent())); connect(tab->browserExtenstion(), SIGNAL(openUrlRequest(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)), this, SLOT(slotOpenUrl(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments))); // if this is a default report, then you can't delete it! if (report.id().isEmpty()) tab->control()->buttonDelete->setEnabled(false); m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(tab)); } void KReportsView::slotListContextMenu(const QPoint & p) { QTreeWidgetItem *item = m_tocTreeWidget->itemAt(p); if (!item) { return; } TocItem* tocItem = dynamic_cast(item); if (!tocItem->isReport()) { // currently there is no context menu for reportgroup items return; } KMenu* contextmenu = new KMenu(this); contextmenu->addAction(i18nc("To open a new report", "&Open"), this, SLOT(slotOpenFromList())); contextmenu->addAction(i18nc("Configure a report", "&Configure"), this, SLOT(slotConfigureFromList())); contextmenu->addAction(i18n("&New report"), this, SLOT(slotNewFromList())); // Only add this option if it's a custom report. Default reports cannot be deleted TocItemReport* reportTocItem = dynamic_cast(tocItem); MyMoneyReport& report = reportTocItem->getReport(); if (! report.id().isEmpty()) { contextmenu->addAction(i18n("&Delete"), this, SLOT(slotDeleteFromList())); } contextmenu->popup(m_tocTreeWidget->mapToGlobal(p)); } void KReportsView::slotOpenFromList() { TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem()); if (tocItem) slotItemDoubleClicked(tocItem, 0); } void KReportsView::slotConfigureFromList() { TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem()); if (tocItem) { slotItemDoubleClicked(tocItem, 0); slotConfigure(); } } void KReportsView::slotNewFromList() { TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem()); if (tocItem) { slotItemDoubleClicked(tocItem, 0); slotDuplicate(); } } void KReportsView::slotDeleteFromList() { TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem()); if (tocItem) { TocItemReport* reportTocItem = dynamic_cast(tocItem); MyMoneyReport& report = reportTocItem->getReport(); // If this report does not have an ID, it's a default report and cannot be deleted if (! report.id().isEmpty() && KMessageBox::Continue == deleteReportDialog(report.name())) { // check if report's tab is open; start from 1 because 0 is toc tab for (int i = 1; i < m_reportTabWidget->count(); ++i) { KReportTab* tab = dynamic_cast(m_reportTabWidget->widget(i)); if (tab->report().id() == report.id()) { slotClose(i); // if open, close it, so no crash when switching to it break; } } MyMoneyFileTransaction ft; MyMoneyFile::instance()->removeReport(report); ft.commit(); } } } void KReportsView::defaultReports(QList& groups) { { ReportGroup list("Income and Expenses", i18n("Income and Expenses")); list.push_back(MyMoneyReport( MyMoneyReport::eExpenseIncome, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentMonth, MyMoneyReport::eDetailAll, i18n("Income and Expenses This Month"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eExpenseIncome, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Income and Expenses This Year"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eExpenseIncome, MyMoneyReport::Column::Years, MyMoneyTransactionFilter::allDates, MyMoneyReport::eDetailAll, i18n("Income and Expenses By Year"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eExpenseIncome, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last12Months, MyMoneyReport::eDetailTop, i18n("Income and Expenses Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartType(MyMoneyReport::eChartLine); list.back().setChartDataLabels(false); list.push_back(MyMoneyReport( MyMoneyReport::eExpenseIncome, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailGroup, i18n("Income and Expenses Pie Chart"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartType(MyMoneyReport::eChartPie); list.back().setShowingRowTotals(false); groups.push_back(list); } { ReportGroup list("Net Worth", i18n("Net Worth")); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailTop, i18n("Net Worth By Month"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::today, MyMoneyReport::eDetailTop, i18n("Net Worth Today"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Years, MyMoneyTransactionFilter::allDates, MyMoneyReport::eDetailTop, i18n("Net Worth By Year"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::next7Days, MyMoneyReport::eDetailTop, i18n("7-day Cash Flow Forecast"), i18n("Default Report") )); list.back().setIncludingSchedules(true); list.back().setColumnsAreDays(true); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last12Months, MyMoneyReport::eDetailTotal, i18n("Net Worth Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); list.push_back(MyMoneyReport( MyMoneyReport::eInstitution, - MyMoneyReport::eQCnone, + MyMoneyReport::QueryColumns::None, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailTop, i18n("Account Balances by Institution"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eAccountType, - MyMoneyReport::eQCnone, + MyMoneyReport::QueryColumns::None, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailTop, i18n("Account Balances by Type"), i18n("Default Report") )); groups.push_back(list); } { ReportGroup list("Transactions", i18n("Transactions")); list.push_back(MyMoneyReport( MyMoneyReport::eAccount, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag | MyMoneyReport::eQCbalance, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Tag | MyMoneyReport::QueryColumns::Balance, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Transactions by Account"), i18n("Default Report") )); //list.back().setConvertCurrency(false); list.push_back(MyMoneyReport( MyMoneyReport::eCategory, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount | MyMoneyReport::eQCtag, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account | MyMoneyReport::QueryColumns::Tag, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Transactions by Category"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::ePayee, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Tag, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Transactions by Payee"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eTag, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Category, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Transactions by Tag"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eMonth, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Tag, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Transactions by Month"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eWeek, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Tag, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Transactions by Week"), i18n("Default Report") )); list.push_back(MyMoneyReport( MyMoneyReport::eAccount, - MyMoneyReport::eQCloan, + MyMoneyReport::QueryColumns::Loan, MyMoneyTransactionFilter::allDates, MyMoneyReport::eDetailAll, i18n("Loan Transactions"), i18n("Default Report") )); list.back().setLoansOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eAccountReconcile, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Balance, MyMoneyTransactionFilter::last3Months, MyMoneyReport::eDetailAll, i18n("Transactions by Reconciliation Status"), i18n("Default Report") )); groups.push_back(list); } { ReportGroup list("CashFlow", i18n("Cash Flow")); list.push_back(MyMoneyReport( MyMoneyReport::eCashFlow, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Cash Flow Transactions This Month"), i18n("Default Report") )); groups.push_back(list); } { ReportGroup list("Investments", i18n("Investments")); list.push_back(MyMoneyReport( MyMoneyReport::eTopAccount, - MyMoneyReport::eQCaction | MyMoneyReport::eQCshares | MyMoneyReport::eQCprice, + MyMoneyReport::QueryColumns::Action | MyMoneyReport::QueryColumns::Shares | MyMoneyReport::QueryColumns::Price, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Investment Transactions"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eAccountByTopAccount, - MyMoneyReport::eQCshares | MyMoneyReport::eQCprice, + MyMoneyReport::QueryColumns::Shares | MyMoneyReport::QueryColumns::Price, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Investment Holdings by Account"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eEquityType, - MyMoneyReport::eQCshares | MyMoneyReport::eQCprice, + MyMoneyReport::QueryColumns::Shares | MyMoneyReport::QueryColumns::Price, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Investment Holdings by Type"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eAccountByTopAccount, - MyMoneyReport::eQCperformance, + MyMoneyReport::QueryColumns::Performance, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Investment Performance by Account"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eEquityType, - MyMoneyReport::eQCperformance, + MyMoneyReport::QueryColumns::Performance, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Investment Performance by Type"), i18n("Default Report") )); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::today, MyMoneyReport::eDetailAll, i18n("Investment Holdings Pie"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartPie); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last12Months, MyMoneyReport::eDetailAll, i18n("Investment Worth Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last12Months, MyMoneyReport::eDetailAll, i18n("Investment Price Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); 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.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last12Months, MyMoneyReport::eDetailAll, i18n("Investment Moving Average Price Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); 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.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last30Days, MyMoneyReport::eDetailAll, i18n("Investment Moving Average"), i18n("Default Report") )); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); list.back().setColumnsAreDays(true); list.back().setInvestmentsOnly(true); list.back().setIncludingBudgetActuals(false); list.back().setIncludingMovingAverage(true); list.back().setMovingAverageDays(10); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::last30Days, MyMoneyReport::eDetailAll, i18n("Investment Moving Average vs Actual"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); 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( MyMoneyReport::eCategory, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Tax Transactions by Category"), i18n("Default Report") )); list.back().setTax(true); list.push_back(MyMoneyReport( MyMoneyReport::ePayee, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory | MyMoneyReport::eQCaccount, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Account, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Tax Transactions by Payee"), i18n("Default Report") )); list.back().setTax(true); list.push_back(MyMoneyReport( MyMoneyReport::eCategory, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Account, MyMoneyTransactionFilter::lastFiscalYear, MyMoneyReport::eDetailAll, i18n("Tax Transactions by Category Last Fiscal Year"), i18n("Default Report") )); list.back().setTax(true); list.push_back(MyMoneyReport( MyMoneyReport::ePayee, - MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory | MyMoneyReport::eQCaccount, + MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Category | MyMoneyReport::QueryColumns::Account, MyMoneyTransactionFilter::lastFiscalYear, MyMoneyReport::eDetailAll, 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( MyMoneyReport::eBudgetActual, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("Budgeted vs. Actual This Year"), i18n("Default Report") )); list.back().setShowingRowTotals(true); list.back().setBudget("Any", true); list.push_back(MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::yearToMonth, MyMoneyReport::eDetailAll, 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(MyMoneyTransactionFilter::lastYear); } list.push_back(MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentMonth, MyMoneyReport::eDetailAll, i18n("Monthly Budgeted vs. Actual"), i18n("Default Report") )); list.back().setBudget("Any", true); list.push_back(MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentYear, MyMoneyReport::eDetailAll, i18n("Yearly Budgeted vs. Actual"), i18n("Default Report") )); list.back().setBudget("Any", true); list.back().setShowingRowTotals(true); list.push_back(MyMoneyReport( MyMoneyReport::eBudget, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentMonth, MyMoneyReport::eDetailAll, i18n("Monthly Budget"), i18n("Default Report") )); list.back().setBudget("Any", false); list.push_back(MyMoneyReport( MyMoneyReport::eBudget, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentYear, MyMoneyReport::eDetailAll, i18n("Yearly Budget"), i18n("Default Report") )); list.back().setBudget("Any", false); list.back().setShowingRowTotals(true); list.push_back(MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentYear, MyMoneyReport::eDetailGroup, i18n("Yearly Budgeted vs Actual Graph"), i18n("Default Report") )); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setBudget("Any", true); list.back().setChartType(MyMoneyReport::eChartLine); groups.push_back(list); } { ReportGroup list("Forecast", i18n("Forecast")); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::next12Months, MyMoneyReport::eDetailTop, i18n("Forecast By Month"), i18n("Default Report") )); list.back().setIncludingForecast(true); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::nextQuarter, MyMoneyReport::eDetailTop, i18n("Forecast Next Quarter"), i18n("Default Report") )); list.back().setColumnsAreDays(true); list.back().setIncludingForecast(true); list.push_back(MyMoneyReport( MyMoneyReport::eExpenseIncome, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentYear, MyMoneyReport::eDetailTop, i18n("Income and Expenses Forecast This Year"), i18n("Default Report") )); list.back().setIncludingForecast(true); list.push_back(MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::next3Months, MyMoneyReport::eDetailTotal, i18n("Net Worth Forecast Graph"), i18n("Default Report") )); list.back().setColumnsAreDays(true); list.back().setIncludingForecast(true); list.back().setChartByDefault(true); list.back().setChartGridLines(false); list.back().setChartType(MyMoneyReport::eChartLine); groups.push_back(list); } { ReportGroup list("Information", i18n("General Information")); list.push_back(MyMoneyReport( MyMoneyReport::eSchedule, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::next12Months, MyMoneyReport::eDetailAll, i18n("Schedule Information"), i18n("Default Report") )); list.back().setDetailLevel(MyMoneyReport::eDetailAll); list.push_back(MyMoneyReport( MyMoneyReport::eSchedule, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::next12Months, MyMoneyReport::eDetailAll, i18n("Schedule Summary Information"), i18n("Default Report") )); list.back().setDetailLevel(MyMoneyReport::eDetailTop); list.push_back(MyMoneyReport( MyMoneyReport::eAccountInfo, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::today, MyMoneyReport::eDetailAll, i18n("Account Information"), i18n("Default Report") )); list.back().setConvertCurrency(false); list.push_back(MyMoneyReport( MyMoneyReport::eAccountLoanInfo, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::today, MyMoneyReport::eDetailAll, i18n("Loan Information"), i18n("Default Report") )); list.back().setConvertCurrency(false); groups.push_back(list); } } bool KReportsView::columnsAlreadyAdjusted() { return m_columnsAlreadyAdjusted; } void KReportsView::setColumnsAlreadyAdjusted(bool adjusted) { m_columnsAlreadyAdjusted = adjusted; } void KReportsView::restoreTocExpandState(QMap& expandStates) { for (int 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); } } } } // Make sure, that these definitions are only used within this file // this does not seem to be necessary, but when building RPMs the // build option 'final' is used and all CPP files are concatenated. // So it could well be, that in another CPP file these definitions // are also used. #undef VIEW_LEDGER #undef VIEW_SCHEDULE #undef VIEW_WELCOME #undef VIEW_HOME #undef VIEW_REPORTS