diff --git a/kmymoney/dialogs/kenterscheduledlg.cpp b/kmymoney/dialogs/kenterscheduledlg.cpp index 61bb597b3..06ad59677 100644 --- a/kmymoney/dialogs/kenterscheduledlg.cpp +++ b/kmymoney/dialogs/kenterscheduledlg.cpp @@ -1,352 +1,349 @@ /*************************************************************************** kenterscheduledlg.cpp ------------------- begin : Sat Apr 7 2007 copyright : (C) 2007 by Thomas Baumgart email : 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 "kenterscheduledlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kcurrencycalculator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kmymoney.h" class KEnterScheduleDlg::Private { public: Private() : m_item(0), m_showWarningOnce(true) {} ~Private() {} MyMoneySchedule m_schedule; KMyMoneyRegister::Transaction* m_item; QWidgetList m_tabOrderWidgets; bool m_showWarningOnce; KMyMoneyUtils::EnterScheduleResultCodeE m_extendedReturnCode; }; KEnterScheduleDlg::KEnterScheduleDlg(QWidget *parent, const MyMoneySchedule& schedule) : KEnterScheduleDlgDecl(parent), d(new Private) { d->m_schedule = schedule; d->m_extendedReturnCode = KMyMoneyUtils::Enter; buttonOk->setIcon(KIcon(KMyMoneyGlobalSettings::enterScheduleIcon())); buttonSkip->setIcon(KIcon("media-seek-forward")); buttonCancel->setGuiItem(KStandardGuiItem::cancel()); buttonHelp->setGuiItem(KStandardGuiItem::help()); buttonIgnore->setHidden(true); buttonSkip->setHidden(true); // make sure, we have a tabbar with the form KMyMoneyTransactionForm::TabBar* tabbar = m_form->tabBar(m_form->parentWidget()); // we never need to see the register m_register->hide(); // ... setup the form ... m_form->setupForm(d->m_schedule.account()); // ... and the register ... m_register->clear(); // ... now add the transaction to register and form ... MyMoneyTransaction t = transaction(); d->m_item = KMyMoneyRegister::Register::transactionFactory(m_register, t, d->m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : d->m_schedule.transaction().splits().front(), 0); m_register->selectItem(d->m_item); // show the account row d->m_item->setShowRowInForm(0, true); m_form->slotSetTransaction(d->m_item); // no need to see the tabbar tabbar->hide(); // setup name and type m_scheduleName->setText(d->m_schedule.name()); m_type->setText(KMyMoneyUtils::scheduleTypeToString(d->m_schedule.type())); connect(buttonHelp, SIGNAL(clicked()), this, SLOT(slotShowHelp())); connect(buttonIgnore, SIGNAL(clicked()), this, SLOT(slotIgnore())); connect(buttonSkip, SIGNAL(clicked()), this, SLOT(slotSkip())); } KEnterScheduleDlg::~KEnterScheduleDlg() { delete d; } KMyMoneyUtils::EnterScheduleResultCodeE KEnterScheduleDlg::resultCode() const { if (result() == QDialog::Accepted) return d->m_extendedReturnCode; return KMyMoneyUtils::Cancel; } void KEnterScheduleDlg::showExtendedKeys(bool visible) { buttonIgnore->setVisible(visible); buttonSkip->setVisible(visible); } void KEnterScheduleDlg::slotIgnore() { d->m_extendedReturnCode = KMyMoneyUtils::Ignore; accept(); } void KEnterScheduleDlg::slotSkip() { d->m_extendedReturnCode = KMyMoneyUtils::Skip; accept(); } MyMoneyTransaction KEnterScheduleDlg::transaction() { MyMoneyTransaction t = d->m_schedule.transaction(); try { if (d->m_schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) { KMyMoneyUtils::calculateAutoLoan(d->m_schedule, t, QMap()); } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Unable to load schedule details"), e.what()); } t.clearId(); t.setEntryDate(QDate()); return t; } QDate KEnterScheduleDlg::date(const QDate& _date) const { QDate date(_date); return d->m_schedule.adjustedDate(date, d->m_schedule.weekendOption()); } void KEnterScheduleDlg::resizeEvent(QResizeEvent* ev) { m_register->resize(KMyMoneyRegister::DetailColumn); m_form->resize(KMyMoneyTransactionForm::ValueColumn1); KEnterScheduleDlgDecl::resizeEvent(ev); } void KEnterScheduleDlg::slotSetupSize() { resize(width(), minimumSizeHint().height()); } int KEnterScheduleDlg::exec() { if (d->m_showWarningOnce) { d->m_showWarningOnce = false; KMessageBox::information(this, QString("") + i18n("

Please check that all the details in the following dialog are correct and press OK.

Editable data can be changed and can either be applied to just this occurrence or for all subsequent occurrences for this schedule. (You will be asked what you intend after pressing OK in the following dialog)

") + QString("
"), i18n("Enter scheduled transaction"), "EnterScheduleDlgInfo"); } // force the initial height to be as small as possible QTimer::singleShot(0, this, SLOT(slotSetupSize())); return KEnterScheduleDlgDecl::exec(); } TransactionEditor* KEnterScheduleDlg::startEdit() { KMyMoneyRegister::SelectedTransactions list(m_register); TransactionEditor* editor = d->m_item->createEditor(m_form, list, QDate()); editor->m_scheduleInfo = d->m_schedule.name(); editor->m_paymentMethod = d->m_schedule.paymentType(); // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor) { if (!editor->fixTransactionCommodity(d->m_schedule.account())) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { connect(editor, SIGNAL(transactionDataSufficient(bool)), buttonOk, SLOT(setEnabled(bool))); connect(editor, SIGNAL(escapePressed()), buttonCancel, SLOT(animateClick())); connect(editor, SIGNAL(returnPressed()), buttonOk, SLOT(animateClick())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets())); // connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions))); connect(editor, SIGNAL(createPayee(QString,QString&)), kmymoney, SLOT(slotPayeeNew(QString,QString&))); connect(editor, SIGNAL(createTag(QString,QString&)), kmymoney, SLOT(slotTagNew(QString,QString&))); connect(editor, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); connect(editor, SIGNAL(createSecurity(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotInvestmentNew(MyMoneyAccount&,MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets())); // create the widgets, place them in the parent and load them with data // setup tab order d->m_tabOrderWidgets.clear(); KMyMoneyRegister::Action action = KMyMoneyRegister::ActionWithdrawal; switch (d->m_schedule.type()) { case MyMoneySchedule::TYPE_TRANSFER: action = KMyMoneyRegister::ActionTransfer; break; case MyMoneySchedule::TYPE_DEPOSIT: action = KMyMoneyRegister::ActionDeposit; break; case MyMoneySchedule::TYPE_LOANPAYMENT: switch (d->m_schedule.paymentType()) { case MyMoneySchedule::STYPE_DIRECTDEPOSIT: case MyMoneySchedule::STYPE_MANUALDEPOSIT: action = KMyMoneyRegister::ActionDeposit; break; default: break; } break; default: break; } editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action); MyMoneyTransaction t = d->m_schedule.transaction(); QString num = t.splits().first().number(); QWidget* w = editor->haveWidget("number"); if (d->m_schedule.paymentType() == MyMoneySchedule::STYPE_WRITECHEQUE) { MyMoneyFile* file = MyMoneyFile::instance(); if (file->checkNoUsed(d->m_schedule.account().id(), num)) { // increment and try again num = KMyMoneyUtils::getAdjacentNumber(num); } - num = KMyMoneyUtils::nextCheckNumber(d->m_schedule.account()); - KMyMoneyUtils::updateLastNumberUsed(d->m_schedule.account(), num); - d->m_schedule.account().setValue("lastNumberUsed", num); + num = KMyMoneyUtils::nextCheckNumber(d->m_schedule.account().value("lastNumberUsed")); if (w) { dynamic_cast(w)->loadText(num); } } else { - // if it's not a check, then we need to clear - // a possibly assigned check number + num = d->m_schedule.account().value("lastNumberUsed"); if (w) - dynamic_cast(w)->loadText(QString()); + dynamic_cast(w)->loadText(num); } Q_ASSERT(!d->m_tabOrderWidgets.isEmpty()); // editor->setup() leaves the tabbar as the last widget in the stack, but we // need it as first here. So we move it around. w = editor->haveWidget("tabbar"); if (w) { int idx = d->m_tabOrderWidgets.indexOf(w); if (idx != -1) { d->m_tabOrderWidgets.removeAt(idx); d->m_tabOrderWidgets.push_front(w); } } // don't forget our three buttons d->m_tabOrderWidgets.append(buttonOk); d->m_tabOrderWidgets.append(buttonCancel); d->m_tabOrderWidgets.append(buttonHelp); for (int i = 0; i < d->m_tabOrderWidgets.size(); ++i) { QWidget* w = d->m_tabOrderWidgets.at(i); if (w) { w->installEventFilter(this); w->installEventFilter(editor); } } // Check if the editor has some preference on where to set the focus // If not, set the focus to the first widget in the tab order QWidget* focusWidget = editor->firstWidget(); if (!focusWidget) focusWidget = d->m_tabOrderWidgets.first(); focusWidget->setFocus(); // Make sure, we use the adjusted date kMyMoneyDateInput* dateEdit = dynamic_cast(editor->haveWidget("postdate")); if (dateEdit) { dateEdit->setDate(d->m_schedule.adjustedNextDueDate()); } } return editor; } bool KEnterScheduleDlg::focusNextPrevChild(bool next) { bool rc = false; QWidget *w = 0; w = qApp->focusWidget(); int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", w->className()); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", w->className()); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' as focus", w->className()); w->setFocus(); rc = true; } } return rc; } void KEnterScheduleDlg::slotShowHelp() { KToolInvocation::invokeHelp("details.schedules.entering"); } diff --git a/kmymoney/dialogs/transactioneditor.cpp b/kmymoney/dialogs/transactioneditor.cpp index abcfef9c5..260311014 100644 --- a/kmymoney/dialogs/transactioneditor.cpp +++ b/kmymoney/dialogs/transactioneditor.cpp @@ -1,2326 +1,2331 @@ /*************************************************************************** transactioneditor.cpp ---------- begin : Wed Jun 07 2006 copyright : (C) 2006 by Thomas Baumgart email : 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 "transactioneditor.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "kmymoneymvccombo.h" #include #include #include #include #include #include #include #include #include #include "ksplittransactiondlg.h" #include "kcurrencycalculator.h" #include "kselecttransactionsdlg.h" using namespace KMyMoneyRegister; using namespace KMyMoneyTransactionForm; TransactionEditor::TransactionEditor() : m_paymentMethod(MyMoneySchedule::STYPE_ANY), m_regForm(0), m_item(0), m_initialAction(ActionNone), m_openEditSplits(false), m_memoChanged(false) { } TransactionEditor::TransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) : m_paymentMethod(MyMoneySchedule::STYPE_ANY), m_transactions(list), m_regForm(regForm), m_item(item), m_transaction(item->transaction()), m_split(item->split()), m_lastPostDate(lastPostDate), m_initialAction(ActionNone), m_openEditSplits(false), m_memoChanged(false) { m_item->startEditMode(); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateAccount())); } TransactionEditor::~TransactionEditor() { // Make sure the widgets do not send out signals to the editor anymore // After all, the editor is about to die //disconnect first tagCombo: KTagContainer *w = dynamic_cast(haveWidget("tag")); if (w && w->tagCombo()) { w->tagCombo()->disconnect(this); } QMap::iterator it_w; for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) { (*it_w)->disconnect(this); } m_regForm->removeEditWidgets(m_editWidgets); m_item->leaveEditMode(); emit finishEdit(m_transactions); } void TransactionEditor::slotUpdateAccount(const QString& id) { m_account = MyMoneyFile::instance()->account(id); setupPrecision(); } void TransactionEditor::slotUpdateAccount() { // reload m_account as it might have been changed m_account = MyMoneyFile::instance()->account(m_account.id()); setupPrecision(); } void TransactionEditor::setupPrecision() { const int prec = (m_account.id().isEmpty()) ? 2 : MyMoneyMoney::denomToPrec(m_account.fraction()); QStringList widgets = QString("amount,deposit,payment").split(','); QStringList::const_iterator it_w; for (it_w = widgets.constBegin(); it_w != widgets.constEnd(); ++it_w) { QWidget * w; if ((w = haveWidget(*it_w)) != 0) { dynamic_cast(w)->setPrecision(prec); } } } void TransactionEditor::setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account, KMyMoneyRegister::Action action) { m_account = account; m_initialAction = action; createEditWidgets(); m_regForm->arrangeEditWidgets(m_editWidgets, m_item); m_regForm->tabOrder(tabOrderWidgets, m_item); QWidget* w = haveWidget("tabbar"); if (w) { tabOrderWidgets.append(w); TabBar* tabbar = dynamic_cast(w); if ((tabbar) && (action == KMyMoneyRegister::ActionNone)) { action = static_cast(tabbar->currentIndex()); } } loadEditWidgets(action); // remove all unused widgets and don't forget to remove them // from the tab order list as well m_editWidgets.removeOrphans(); QWidgetList::iterator it_w; const QWidgetList editWidgets(m_editWidgets.values()); for (it_w = tabOrderWidgets.begin(); it_w != tabOrderWidgets.end();) { if (editWidgets.contains(*it_w)) { ++it_w; } else { // before we remove the widget, we make sure it's not a part of a known one. // these could be a direct child in case of KMyMoneyDateInput and KMyMoneyEdit // where we store the pointer to the surrounding frame in editWidgets // or the parent is called "KMyMoneyCategoryFrame" if (*it_w) { if (editWidgets.contains((*it_w)->parentWidget()) || ((*it_w)->parentWidget() && (*it_w)->parentWidget()->objectName() == QLatin1String("KMyMoneyCategoryFrame"))) { ++it_w; } else { // qDebug("Remove '%s' from taborder", qPrintable((*it_w)->objectName())); it_w = tabOrderWidgets.erase(it_w); } } else { it_w = tabOrderWidgets.erase(it_w); } } } clearFinalWidgets(); setupFinalWidgets(); slotUpdateButtonState(); } void TransactionEditor::clearFinalWidgets() { m_finalEditWidgets.clear(); } void TransactionEditor::addFinalWidget(const QWidget* w) { if (w) { m_finalEditWidgets << w; } } void TransactionEditor::slotReloadEditWidgets() { } bool TransactionEditor::eventFilter(QObject* o, QEvent* e) { bool rc = false; if (o == haveWidget("number")) { if (e->type() == QEvent::MouseButtonDblClick) { emit assignNumber(); rc = true; } } // if the object is a widget, the event is a key press event and // the object is one of our edit widgets, then .... if (o->isWidgetType() && (e->type() == QEvent::KeyPress) && m_editWidgets.values().contains(dynamic_cast(o))) { QKeyEvent* k = dynamic_cast(e); if ((k->modifiers() & Qt::KeyboardModifierMask) == 0 || (k->modifiers() & Qt::KeypadModifier) != 0) { bool isFinal = false; QList::const_iterator it_w; switch (k->key()) { case Qt::Key_Return: case Qt::Key_Enter: // we check, if the object is one of the m_finalEditWidgets and if it's // a kMyMoneyEdit object that the value is not 0. If any of that is the // case, it's the final object. In other cases, we convert the enter // key into a TAB key to move between the fields. Of course, we only need // to do this as long as the appropriate option is set. In all other cases, // we treat the return/enter key as such. if (KMyMoneyGlobalSettings::enterMovesBetweenFields()) { for (it_w = m_finalEditWidgets.constBegin(); !isFinal && it_w != m_finalEditWidgets.constEnd(); ++it_w) { if (*it_w == o) { if (dynamic_cast(*it_w)) { isFinal = !(dynamic_cast(*it_w)->value().isZero()); } else isFinal = true; } } } else isFinal = true; // for the non-final objects, we treat the return key as a TAB if (!isFinal) { QKeyEvent evt(e->type(), Qt::Key_Tab, k->modifiers(), QString(), k->isAutoRepeat(), k->count()); QApplication::sendEvent(o, &evt); // in case of a category item and the split button is visible // send a second event so that we get passed the button. if (dynamic_cast(o) && dynamic_cast(o)->splitButton()) QApplication::sendEvent(o, &evt); } else { QTimer::singleShot(0, this, SIGNAL(returnPressed())); } // don't process any further rc = true; break; case Qt::Key_Escape: QTimer::singleShot(0, this, SIGNAL(escapePressed())); break; } } } return rc; } +QString TransactionEditor::lastNumberUsed() const +{ + kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); + return number && !number->text().isEmpty() ? number->text() : m_account.value("lastNumberUsed"); +} + void TransactionEditor::slotNumberChanged(const QString& txt) { QString next = txt; kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); QString schedInfo; if (!m_scheduleInfo.isEmpty()) { schedInfo = i18n("
Processing schedule for %1.
", m_scheduleInfo); } while (MyMoneyFile::instance()->checkNoUsed(m_account.id(), next)) { QString dontShowAgain = QLatin1String("DuplicatedNumber"); KMessageBox::ButtonCode result; if ((!KMessageBox::shouldBeShownYesNo(dontShowAgain, result) && result == KMessageBox::Yes) || KMessageBox::questionYesNo(m_regForm, QLatin1String("") + schedInfo + i18n("
Check number %1 has already been used in account %2.
" "
Do you want to replace it with the next available number?
", next, m_account.name()) + QLatin1String("
"), i18n("Duplicate number"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontShowAgain) == KMessageBox::Yes) { assignNextCheckNumber(); - next = KMyMoneyUtils::nextCheckNumber(m_account); + next = KMyMoneyUtils::nextCheckNumber(lastNumberUsed()); } else { number->loadText(txt); break; } } } void TransactionEditor::slotUpdateMemoState() { KTextEdit* memo = dynamic_cast(m_editWidgets["memo"]); if (memo) { m_memoChanged = (memo->toPlainText() != m_memoText); } } void TransactionEditor::slotUpdateButtonState() { QString reason; emit transactionDataSufficient(isComplete(reason)); } QWidget* TransactionEditor::haveWidget(const QString& name) const { return m_editWidgets.haveWidget(name); } int TransactionEditor::slotEditSplits() { return KDialog::Rejected; } void TransactionEditor::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s) { m_transaction = t; m_split = s; loadEditWidgets(); } bool TransactionEditor::fixTransactionCommodity(const MyMoneyAccount& account) { bool rc = true; bool firstTimeMultiCurrency = true; m_account = account; MyMoneyFile* file = MyMoneyFile::instance(); // determine the max fraction for this account MyMoneySecurity sec = file->security(m_account.currencyId()); int fract = m_account.fraction(); // scan the list of selected transactions KMyMoneyRegister::SelectedTransactions::iterator it_t; for (it_t = m_transactions.begin(); (rc == true) && (it_t != m_transactions.end()); ++it_t) { // there was a time when the schedule editor did not setup the transaction commodity // let's give a helping hand here for those old schedules if ((*it_t).transaction().commodity().isEmpty()) (*it_t).transaction().setCommodity(m_account.currencyId()); // we need to check things only if a different commodity is used if (m_account.currencyId() != (*it_t).transaction().commodity()) { MyMoneySecurity osec = file->security((*it_t).transaction().commodity()); switch ((*it_t).transaction().splitCount()) { case 0: // new transaction, guess nothing's here yet ;) break; case 1: try { // make sure, that the value is equal to the shares, don't forget our own copy MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here if (m_split == splitB) m_split.setValue(splitB.shares()); splitB.setValue(splitB.shares()); (*it_t).transaction().modifySplit(splitB); } catch (const MyMoneyException &e) { qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), qPrintable(e.what())); } break; case 2: // If we deal with multiple currencies we make sure, that for // transactions with two splits, the transaction's commodity is the // currency of the currently selected account. This saves us from a // lot of grieve later on. We just have to switch the // transactions commodity. Let's assume the following scenario: // - transactions commodity is CA // - splitB and account's currencyId is CB // - splitA is of course in CA (otherwise we have a real problem) // - Value is V in both splits // - Shares in splitB is SB // - Shares in splitA is SA (and equal to V) // // We do the following: // - change transactions commodity to CB // - set V in both splits to SB // - modify the splits in the transaction try { // retrieve the splits MyMoneySplit& splitB = (*it_t).split(); // reference usage wanted here MyMoneySplit splitA = (*it_t).transaction().splitByAccount(m_account.id(), false); // - set V in both splits to SB. Don't forget our own copy if (m_split == splitB) { m_split.setValue(splitB.shares()); } splitB.setValue(splitB.shares()); splitA.setValue(-splitB.shares()); (*it_t).transaction().modifySplit(splitA); (*it_t).transaction().modifySplit(splitB); } catch (const MyMoneyException &e) { qDebug("Unable to update commodity to second splits currency in %s: '%s'", qPrintable((*it_t).transaction().id()), qPrintable(e.what())); } break; default: // TODO: use new logic by adjusting all splits by the price // extracted from the selected split. Inform the user that // this will happen and allow him to stop the processing (rc = false) try { QString msg; if (firstTimeMultiCurrency) { firstTimeMultiCurrency = false; if (!isMultiSelection()) { msg = i18n("This transaction has more than two splits and is originally based on a different currency (%1). Using this account to modify the transaction may result in rounding errors. Do you want to continue?", osec.name()); } else { msg = i18n("At least one of the selected transactions has more than two splits and is originally based on a different currency (%1). Using this account to modify the transactions may result in rounding errors. Do you want to continue?", osec.name()); } if (KMessageBox::warningContinueCancel(nullptr, QString("%1").arg(msg)) == KMessageBox::Cancel) { rc = false; } } if (rc == true) { MyMoneyMoney price; if (!(*it_t).split().shares().isZero() && !(*it_t).split().value().isZero()) price = (*it_t).split().shares() / (*it_t).split().value(); QList::iterator it_s; MyMoneySplit& mySplit = (*it_t).split(); for (it_s = (*it_t).transaction().splits().begin(); it_s != (*it_t).transaction().splits().end(); ++it_s) { MyMoneySplit s = (*it_s); if (s == mySplit) { s.setValue(s.shares()); if (mySplit == m_split) { m_split = s; } mySplit = s; } else { s.setValue((s.value() * price).convert(fract)); } (*it_t).transaction().modifySplit(s); } } } catch (const MyMoneyException &e) { qDebug("Unable to update commodity of split currency in %s: '%s'", qPrintable((*it_t).transaction().id()), qPrintable(e.what())); } break; } // set the transaction's ommodity to this account's currency (*it_t).transaction().setCommodity(m_account.currencyId()); // update our copy of the transaction that has the focus if ((*it_t).transaction().id() == m_transaction.id()) { m_transaction = (*it_t).transaction(); } } } return rc; } void TransactionEditor::assignNextCheckNumber() { if (canAssignNumber()) { kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); - QString num = KMyMoneyUtils::nextCheckNumber(m_account); + QString n = lastNumberUsed(); + QString num = KMyMoneyUtils::nextCheckNumber(n); bool showMessage = true; int rc = KMessageBox::No; QString schedInfo; if (!m_scheduleInfo.isEmpty()) { schedInfo = i18n("
Processing schedule for %1.
", m_scheduleInfo); } while (MyMoneyFile::instance()->checkNoUsed(m_account.id(), num)) { if (showMessage) { rc = KMessageBox::questionYesNo(m_regForm, QString("") + schedInfo + i18n("Check number %1 has already been used in account %2." "
Do you want to replace it with the next available number?
", num, m_account.name()) + QString("
"), i18n("Duplicate number")); showMessage = false; } if (rc == KMessageBox::Yes) { - num = KMyMoneyUtils::nextCheckNumber(m_account); - KMyMoneyUtils::updateLastNumberUsed(m_account, num); - m_account.setValue("lastNumberUsed", num); + num = KMyMoneyUtils::nextCheckNumber(num); number->loadText(num); } else { number->loadText(num); break; } } number->setText(num); } } void TransactionEditor::assignStatementNumber() { if (canAssignNumber()) { kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); QString num = m_account.value("lastNumberUsed"); number->loadText(num); } } void TransactionEditor::assignNextStatementNumber() { if (canAssignNumber()) { kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); - QString num = KMyMoneyUtils::nextStatementNumber(m_account); - KMyMoneyUtils::updateLastNumberUsed(m_account, num); - m_account.setValue("lastNumberUsed", num); + QString num = KMyMoneyUtils::nextStatementNumber(lastNumberUsed()); number->loadText(num); } } void TransactionEditor::assignNextStatementPageNumber() { if (canAssignNumber()) { kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); - QString num = KMyMoneyUtils::nextStatementPageNumber(m_account); - KMyMoneyUtils::updateLastNumberUsed(m_account, num); - m_account.setValue("lastNumberUsed", num); + QString num = KMyMoneyUtils::nextStatementPageNumber(lastNumberUsed()); number->loadText(num); } } bool TransactionEditor::canAssignNumber() const { kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); return (number != 0); } void TransactionEditor::setupCategoryWidget(KMyMoneyCategory* category, const QList& splits, QString& categoryId, const char* splitEditSlot, bool /* allowObjectCreation */) { disconnect(category, SIGNAL(focusIn()), this, splitEditSlot); #if 0 // FIXME must deal with the logic that suppressObjectCreation is // automatically turned off when the createItem() signal is connected if (allowObjectCreation) category->setSuppressObjectCreation(false); #endif switch (splits.count()) { case 0: categoryId.clear(); if (!category->currentText().isEmpty()) { // category->clearEditText(); // don't clear as could be from another widget - Bug 322768 // make sure, we don't see the selector category->completion()->hide(); } category->completion()->setSelected(QString()); break; case 1: categoryId = splits[0].accountId(); category->completion()->setSelected(categoryId); category->slotItemSelected(categoryId); break; default: categoryId.clear(); category->setSplitTransaction(); connect(category, SIGNAL(focusIn()), this, splitEditSlot); #if 0 // FIXME must deal with the logic that suppressObjectCreation is // automatically turned off when the createItem() signal is connected if (allowObjectCreation) category->setSuppressObjectCreation(true); #endif break; } } bool TransactionEditor::enterTransactions(QString& newId, bool askForSchedule, bool suppressBalanceWarnings) { newId.clear(); MyMoneyFile* file = MyMoneyFile::instance(); + kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); + if (number && !number->text().isEmpty()) + m_account.setValue("lastNumberUsed", number->text()); + // make sure to run through all stuff that is tied to 'focusout events'. m_regForm->parentWidget()->setFocus(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10); // we don't need to update our widgets anymore, so we just disconnect the signal disconnect(file, SIGNAL(dataChanged()), this, SLOT(slotReloadEditWidgets())); KMyMoneyRegister::SelectedTransactions::iterator it_t; MyMoneyTransaction t; bool newTransactionCreated = false; // make sure, that only a single new transaction can be created. // we need to update m_transactions to contain the new transaction // which is then stored in the variable t when we leave the loop. // m_transactions will be sent out in finishEdit() and forces // the new transaction to be selected in the ledger view // collect the transactions to be stored in the engine in a local // list first, so that the user has a chance to interrupt the storage // process QList list; bool storeTransactions = true; // collect transactions for (it_t = m_transactions.begin(); storeTransactions && !newTransactionCreated && it_t != m_transactions.end(); ++it_t) { storeTransactions = createTransaction(t, (*it_t).transaction(), (*it_t).split()); // if the transaction was created successfully, append it to the list if (storeTransactions) list.append(t); // if we created a new transaction keep that in mind if (t.id().isEmpty()) newTransactionCreated = true; } // if not interrupted by user, continue to store them in the engine if (storeTransactions) { int i = 0; emit statusMsg(i18n("Storing transactions")); emit statusProgress(0, list.count()); MyMoneyFileTransaction ft; try { QList::iterator it_ts; QMap minBalanceEarly; QMap minBalanceAbsolute; QMap maxCreditEarly; QMap maxCreditAbsolute; QMap accountIds; for (it_ts = list.begin(); it_ts != list.end(); ++it_ts) { // if we have a categorization, make sure we remove // the 'imported' flag automagically if ((*it_ts).splitCount() > 1) (*it_ts).setImported(false); // create information about min and max balances QList::const_iterator it_s; for (it_s = (*it_ts).splits().constBegin(); it_s != (*it_ts).splits().constEnd(); ++it_s) { MyMoneyAccount acc = file->account((*it_s).accountId()); accountIds[acc.id()] = true; MyMoneyMoney balance = file->balance(acc.id()); if (!acc.value("minBalanceEarly").isEmpty()) { minBalanceEarly[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceEarly")); } if (!acc.value("minBalanceAbsolute").isEmpty()) { minBalanceAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("minBalanceAbsolute")); minBalanceEarly[acc.id()] = false; } if (!acc.value("maxCreditEarly").isEmpty()) { maxCreditEarly[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditEarly")); } if (!acc.value("maxCreditAbsolute").isEmpty()) { maxCreditAbsolute[acc.id()] = balance < MyMoneyMoney(acc.value("maxCreditAbsolute")); maxCreditEarly[acc.id()] = false; } } if ((*it_ts).id().isEmpty()) { bool enter = true; if (askForSchedule && (*it_ts).postDate() > QDate::currentDate()) { KGuiItem enterItem; KGuiItem enterButton(i18n("&Enter"), KIcon("dialog-ok"), i18n("Accepts the entered data and stores it"), i18n("Use this to enter the transaction into the ledger.")); KGuiItem scheduleButton(i18n("&Schedule"), KIcon("appointment-new"), i18n("Accepts the entered data and stores it as schedule"), i18n("Use this to schedule the transaction for later entry into the ledger.")); enter = KMessageBox::questionYesNo(m_regForm, QString("%1").arg(i18n("The transaction you are about to enter has a post date in the future.

Do you want to enter it in the ledger or add it to the schedules?")), i18nc("Dialog caption for 'Enter or schedule' dialog", "Enter or schedule?"), enterButton, scheduleButton, "EnterOrScheduleTransactionInFuture") == KMessageBox::Yes; } if (enter) { // add new transaction file->addTransaction(*it_ts); // pass the newly assigned id on to the caller newId = (*it_ts).id(); // refresh account object for transactional changes // refresh account and transaction object because they might have changed m_account = file->account(m_account.id()); t = (*it_ts); // if a new transaction has a valid number, keep it with the account keepNewNumber((*it_ts)); } else { // turn object creation on, so that moving the focus does // not screw up the dialog that might be popping up emit objectCreation(true); emit scheduleTransaction(*it_ts, MyMoneySchedule::OCCUR_ONCE); emit objectCreation(false); newTransactionCreated = false; } // send out the post date of this transaction emit lastPostDateUsed((*it_ts).postDate()); } else { // modify existing transaction // its number might have been edited // bearing in mind it could contain alpha characters keepNewNumber((*it_ts)); file->modifyTransaction(*it_ts); } } emit statusProgress(i++, 0); // update m_transactions to contain the newly created transaction so that // it is selected as the current one // we need to do that before we commit the transaction to the engine // as we need it during the update of the views that is caused by committing already. if (newTransactionCreated) { m_transactions.clear(); MyMoneySplit s; // a transaction w/o a single split should not exist and adding it // should throw an exception in MyMoneyFile::addTransaction, but we // remain on the save side of things to check for it if (t.splitCount() > 0) s = t.splits().front(); KMyMoneyRegister::SelectedTransaction st(t, s); m_transactions.append(st); } // Save pricing information QList::const_iterator it_t; for (it_t = t.splits().constBegin(); it_t != t.splits().constEnd(); ++it_t) { if (((*it_t).action() != MyMoneySplit::ActionBuyShares) && ((*it_t).action() != MyMoneySplit::ActionReinvestDividend)) { continue; } QString id = (*it_t).accountId(); MyMoneyAccount acc = file->account(id); MyMoneySecurity sec = file->security(acc.currencyId()); MyMoneyPrice price(acc.currencyId(), sec.tradingCurrency(), t.postDate(), (*it_t).price(), "Transaction"); file->addPrice(price); break; } ft.commit(); // now analyze the balances and spit out warnings to the user QMap::const_iterator it_a; if (!suppressBalanceWarnings) { for (it_a = accountIds.constBegin(); it_a != accountIds.constEnd(); ++it_a) { QString msg; MyMoneyAccount acc = file->account(it_a.key()); MyMoneyMoney balance = file->balance(acc.id()); const MyMoneySecurity& sec = file->security(acc.currencyId()); QString key; key = "minBalanceEarly"; if (!acc.value(key).isEmpty()) { if (minBalanceEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { msg = QString("%1").arg(i18n("The balance of account %1 dropped below the warning balance of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); } } key = "minBalanceAbsolute"; if (!acc.value(key).isEmpty()) { if (minBalanceAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { msg = QString("%1").arg(i18n("The balance of account %1 dropped below the minimum balance of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); } } key = "maxCreditEarly"; if (!acc.value(key).isEmpty()) { if (maxCreditEarly[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { msg = QString("%1").arg(i18n("The balance of account %1 dropped below the maximum credit warning limit of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); } } key = "maxCreditAbsolute"; if (!acc.value(key).isEmpty()) { if (maxCreditAbsolute[acc.id()] == false && balance < MyMoneyMoney(acc.value(key))) { msg = QString("%1").arg(i18n("The balance of account %1 dropped below the maximum credit limit of %2.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(acc.value(key)), acc, sec))); } } if (!msg.isEmpty()) { emit balanceWarning(m_regForm, acc, msg); } } } } catch (const MyMoneyException &e) { qDebug("Unable to store transaction within engine: %s", qPrintable(e.what())); newTransactionCreated = false; } emit statusProgress(-1, -1); emit statusMsg(QString()); } return storeTransactions; } void TransactionEditor::keepNewNumber(const MyMoneyTransaction& tr) { // verify that new number, possibly containing alpha, is valid MyMoneyTransaction txn = tr; MyMoneyFile* file = MyMoneyFile::instance(); if (!txn.splits().isEmpty()) { QString number = txn.splits().first().number(); if (KMyMoneyUtils::numericPart(number) > 0) { // numeric is valid kMyMoneyLineEdit* numberEdit = dynamic_cast(haveWidget("number")); if (numberEdit) { numberEdit->loadText(number); MyMoneySplit split = txn.splits().first(); split.setNumber(number); txn.modifySplit(split); m_account.setValue("lastNumberUsed", number); file->modifyAccount(m_account); } } } } void TransactionEditor::resizeForm() { // force resizeing of the columns in the form KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast(m_regForm); if (form) { QMetaObject::invokeMethod(form, "resize", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(int, ValueColumn1)); } } StdTransactionEditor::StdTransactionEditor() : m_inUpdateVat(false) { } StdTransactionEditor::StdTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) : TransactionEditor(regForm, item, list, lastPostDate), m_inUpdateVat(false) { } StdTransactionEditor::~StdTransactionEditor() { } void StdTransactionEditor::createEditWidgets() { // we only create the account widget in case it is needed // to avoid confusion in the tab order later on. if (m_item->showRowInForm(0)) { KMyMoneyCategory* account = new KMyMoneyCategory; account->setClickMessage(i18n("Account")); account->setObjectName(QLatin1String("Account")); m_editWidgets["account"] = account; connect(account, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState())); connect(account, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateAccount(QString))); } KMyMoneyPayeeCombo* payee = new KMyMoneyPayeeCombo; payee->setClickMessage(i18n("Payer/Receiver")); payee->setObjectName(QLatin1String("Payee")); m_editWidgets["payee"] = payee; connect(payee, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createPayee(QString,QString&))); connect(payee, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); connect(payee, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdatePayee(QString))); connect(payee, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState())); KMyMoneyCategory* category = new KMyMoneyCategory(0, true); category->setClickMessage(i18n("Category/Account")); category->setObjectName(QLatin1String("Category/Account")); m_editWidgets["category"] = category; connect(category, SIGNAL(itemSelected(QString)), this, SLOT(slotUpdateCategory(QString))); connect(category, SIGNAL(editTextChanged(QString)), this, SLOT(slotUpdateButtonState())); connect(category, SIGNAL(createItem(QString,QString&)), this, SLOT(slotCreateCategory(QString,QString&))); connect(category, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); connect(category->splitButton(), SIGNAL(clicked()), this, SLOT(slotEditSplits())); // initially disable the split button since we don't have an account set if (category->splitButton()) category->splitButton()->setDisabled(m_account.id().isEmpty()); KTagContainer* tag = new KTagContainer; tag->tagCombo()->setClickMessage(i18n("Tag")); tag->tagCombo()->setObjectName(QLatin1String("Tag")); m_editWidgets["tag"] = tag; connect(tag->tagCombo(), SIGNAL(createItem(QString,QString&)), this, SIGNAL(createTag(QString,QString&))); connect(tag->tagCombo(), SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); KTextEdit* memo = new KTextEdit; memo->setObjectName(QLatin1String("Memo")); memo->setTabChangesFocus(true); connect(memo, SIGNAL(textChanged()), this, SLOT(slotUpdateMemoState())); connect(memo, SIGNAL(textChanged()), this, SLOT(slotUpdateButtonState())); m_editWidgets["memo"] = memo; m_memoText.clear(); m_memoChanged = false; bool showNumberField = true; switch (m_account.accountType()) { case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::Loan: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Equity: showNumberField = KMyMoneyGlobalSettings::alwaysShowNrField(); break; case MyMoneyAccount::Income: case MyMoneyAccount::Expense: showNumberField = false; break; default: break; } if (showNumberField) { kMyMoneyLineEdit* number = new kMyMoneyLineEdit; number->setClickMessage(i18n("Number")); number->setObjectName(QLatin1String("Number")); m_editWidgets["number"] = number; connect(number, SIGNAL(lineChanged(QString)), this, SLOT(slotNumberChanged(QString))); // number->installEventFilter(this); } kMyMoneyDateInput* postDate = new kMyMoneyDateInput; m_editWidgets["postdate"] = postDate; postDate->setObjectName(QLatin1String("PostDate")); connect(postDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotUpdateButtonState())); postDate->setDate(QDate()); kMyMoneyEdit* value = new kMyMoneyEdit; m_editWidgets["amount"] = value; value->setObjectName(QLatin1String("Amount")); value->setResetButtonVisible(false); connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateAmount(QString))); connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState())); value = new kMyMoneyEdit; m_editWidgets["payment"] = value; value->setObjectName(QLatin1String("Payment")); value->setResetButtonVisible(false); connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdatePayment(QString))); connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState())); value = new kMyMoneyEdit; m_editWidgets["deposit"] = value; value->setObjectName(QLatin1String("Deposit")); value->setResetButtonVisible(false); connect(value, SIGNAL(valueChanged(QString)), this, SLOT(slotUpdateDeposit(QString))); connect(value, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtonState())); KMyMoneyCashFlowCombo* cashflow = new KMyMoneyCashFlowCombo(0, m_account.accountGroup()); m_editWidgets["cashflow"] = cashflow; cashflow->setObjectName(QLatin1String("Cashflow")); connect(cashflow, SIGNAL(directionSelected(KMyMoneyRegister::CashFlowDirection)), this, SLOT(slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection))); KMyMoneyReconcileCombo* reconcile = new KMyMoneyReconcileCombo; m_editWidgets["status"] = reconcile; reconcile->setObjectName(QLatin1String("Reconcile")); KMyMoneyRegister::QWidgetContainer::iterator it_w; for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) { (*it_w)->installEventFilter(this); } // if we don't have more than 1 selected transaction, we don't need // the "don't change" item in some of the combo widgets if (!isMultiSelection()) { reconcile->removeDontCare(); cashflow->removeDontCare(); } QLabel* label; m_editWidgets["account-label"] = label = new QLabel(i18n("Account")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["category-label"] = label = new QLabel(i18n("Category")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["tag-label"] = label = new QLabel(i18n("Tags")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["number-label"] = label = new QLabel(i18n("Number")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["date-label"] = label = new QLabel(i18n("Date")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["amount-label"] = label = new QLabel(i18n("Amount")); label->setAlignment(Qt::AlignVCenter); m_editWidgets["status-label"] = label = new QLabel(i18n("Status")); label->setAlignment(Qt::AlignVCenter); // create a copy of tabbar above the form (if we are created for a form) KMyMoneyTransactionForm::TransactionForm* form = dynamic_cast(m_regForm); if (form) { KMyMoneyTransactionForm::TabBar* tabbar = new KMyMoneyTransactionForm::TabBar; m_editWidgets["tabbar"] = tabbar; tabbar->setObjectName(QLatin1String("TabBar")); tabbar->copyTabs(form->tabBar()); connect(tabbar, SIGNAL(tabCurrentChanged(int)), this, SLOT(slotUpdateAction(int))); connect(tabbar, SIGNAL(tabCurrentChanged(int)), this, SIGNAL(operationTypeChanged(int))); } setupPrecision(); } void StdTransactionEditor::setupCategoryWidget(QString& categoryId) { TransactionEditor::setupCategoryWidget(dynamic_cast(m_editWidgets["category"]), m_splits, categoryId, SLOT(slotEditSplits())); if (m_splits.count() == 1) m_shares = m_splits[0].shares(); } bool StdTransactionEditor::isTransfer(const QString& accId1, const QString& accId2) const { if (accId1.isEmpty() || accId2.isEmpty()) return false; return MyMoneyFile::instance()->account(accId1).isIncomeExpense() == MyMoneyFile::instance()->account(accId2).isIncomeExpense(); } void StdTransactionEditor::loadEditWidgets(KMyMoneyRegister::Action action) { // don't kick off VAT processing from here m_inUpdateVat = true; QMap::const_iterator it_w; QWidget* w; AccountSet aSet; // load the account widget KMyMoneyCategory* account = dynamic_cast(haveWidget("account")); if (account) { aSet.addAccountGroup(MyMoneyAccount::Asset); aSet.addAccountGroup(MyMoneyAccount::Liability); aSet.removeAccountType(MyMoneyAccount::AssetLoan); aSet.removeAccountType(MyMoneyAccount::CertificateDep); aSet.removeAccountType(MyMoneyAccount::Investment); aSet.removeAccountType(MyMoneyAccount::Stock); aSet.removeAccountType(MyMoneyAccount::MoneyMarket); aSet.removeAccountType(MyMoneyAccount::Loan); aSet.load(account->selector()); account->completion()->setSelected(m_account.id()); account->slotItemSelected(m_account.id()); } // load the payee widget KMyMoneyPayeeCombo* payee = dynamic_cast(m_editWidgets["payee"]); payee->loadPayees(MyMoneyFile::instance()->payeeList()); // load the category widget KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); disconnect(category, SIGNAL(focusIn()), this, SLOT(slotEditSplits())); // load the tag widget //KMyMoneyTagCombo* tag = dynamic_cast(m_editWidgets["tag"]); KTagContainer* tag = dynamic_cast(m_editWidgets["tag"]); tag->loadTags(MyMoneyFile::instance()->tagList()); // check if the current transaction has a reference to an equity account bool haveEquityAccount = false; QList::const_iterator it_s; for (it_s = m_transaction.splits().constBegin(); !haveEquityAccount && it_s != m_transaction.splits().constEnd(); ++it_s) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (acc.accountType() == MyMoneyAccount::Equity) haveEquityAccount = true; } aSet.clear(); aSet.addAccountGroup(MyMoneyAccount::Asset); aSet.addAccountGroup(MyMoneyAccount::Liability); aSet.addAccountGroup(MyMoneyAccount::Income); aSet.addAccountGroup(MyMoneyAccount::Expense); if (KMyMoneyGlobalSettings::expertMode() || haveEquityAccount) aSet.addAccountGroup(MyMoneyAccount::Equity); aSet.removeAccountType(MyMoneyAccount::CertificateDep); aSet.removeAccountType(MyMoneyAccount::Investment); aSet.removeAccountType(MyMoneyAccount::Stock); aSet.removeAccountType(MyMoneyAccount::MoneyMarket); aSet.load(category->selector()); // if an account is specified then remove it from the widget so that the user // cannot create a transfer with from and to account being the same account if (!m_account.id().isEmpty()) category->selector()->removeItem(m_account.id()); // also show memo text if isMultiSelection() dynamic_cast(m_editWidgets["memo"])->setText(m_split.memo()); // need to know if it changed m_memoText = m_split.memo(); m_memoChanged = false; if (!isMultiSelection()) { if (m_transaction.postDate().isValid()) dynamic_cast(m_editWidgets["postdate"])->setDate(m_transaction.postDate()); else if (m_lastPostDate.isValid()) dynamic_cast(m_editWidgets["postdate"])->setDate(m_lastPostDate); else dynamic_cast(m_editWidgets["postdate"])->setDate(QDate::currentDate()); if ((w = haveWidget("number")) != 0) { dynamic_cast(w)->loadText(m_split.number()); if (m_transaction.id().isEmpty() // new transaction && dynamic_cast(w)->text().isEmpty() // no number filled in && m_account.accountType() == MyMoneyAccount::Checkings // checkings account && KMyMoneyGlobalSettings::autoIncCheckNumber() // and auto inc number turned on? && action != KMyMoneyRegister::ActionDeposit // only transfers or withdrawals && (m_scheduleInfo.isEmpty() // normal transactions || m_paymentMethod == MyMoneySchedule::STYPE_WRITECHEQUE)) {// only for STYPE_WRITECHEQUE schedule transaction assignNextCheckNumber(); } } dynamic_cast(m_editWidgets["status"])->setState(m_split.reconcileFlag()); QString payeeId = m_split.payeeId(); if (!payeeId.isEmpty()) { payee->setSelectedItem(payeeId); } QList t = m_split.tagIdList(); if (!t.isEmpty()) { for (int i = 0; i < t.size(); i++) tag->addTagWidget(t[i]); } m_splits.clear(); if (m_transaction.splitCount() < 2) { category->completion()->setSelected(QString()); } else { QList::const_iterator it_s; for (it_s = m_transaction.splits().constBegin(); it_s != m_transaction.splits().constEnd(); ++it_s) { if ((*it_s) == m_split) continue; m_splits.append(*it_s); } } QString categoryId; setupCategoryWidget(categoryId); if ((w = haveWidget("cashflow")) != 0) { KMyMoneyCashFlowCombo* cashflow = dynamic_cast(w); cashflow->setDirection(!m_split.value().isPositive() ? KMyMoneyRegister::Payment : KMyMoneyRegister::Deposit); // include isZero case } if ((w = haveWidget("category-label")) != 0) { QLabel *categoryLabel = dynamic_cast(w); if (isTransfer(m_split.accountId(), categoryId)) { if (m_split.value().isPositive()) categoryLabel->setText(i18n("Transfer from")); else categoryLabel->setText(i18n("Transfer to")); } } MyMoneyMoney value = m_split.shares(); if (haveWidget("deposit")) { if (m_split.shares().isNegative()) { dynamic_cast(m_editWidgets["deposit"])->loadText(""); dynamic_cast(m_editWidgets["payment"])->setValue(value.abs()); } else { dynamic_cast(m_editWidgets["deposit"])->setValue(value.abs()); dynamic_cast(m_editWidgets["payment"])->loadText(""); } } if ((w = haveWidget("amount")) != 0) { dynamic_cast(w)->setValue(value.abs()); } slotUpdateCategory(categoryId); // try to preset for specific action if a new transaction is being started if (m_transaction.id().isEmpty()) { if ((w = haveWidget("category-label")) != 0) { TabBar* tabbar = dynamic_cast(haveWidget("tabbar")); if (action == KMyMoneyRegister::ActionNone) { if (tabbar) { action = static_cast(tabbar->currentIndex()); } } if (action != KMyMoneyRegister::ActionNone) { QLabel *categoryLabel = dynamic_cast(w); if (action == KMyMoneyRegister::ActionTransfer) { if (m_split.value().isPositive()) categoryLabel->setText(i18n("Transfer from")); else categoryLabel->setText(i18n("Transfer to")); } if ((w = haveWidget("cashflow")) != 0) { KMyMoneyCashFlowCombo* cashflow = dynamic_cast(w); if (action == KMyMoneyRegister::ActionDeposit || (action == KMyMoneyRegister::ActionTransfer && m_split.value().isPositive())) cashflow->setDirection(KMyMoneyRegister::Deposit); else cashflow->setDirection(KMyMoneyRegister::Payment); } if (tabbar) { tabbar->setCurrentIndex(action); } } } } else { TabBar* tabbar = dynamic_cast(haveWidget("tabbar")); if (tabbar) { if (!isTransfer(m_split.accountId(), categoryId)) { tabbar->setCurrentIndex(m_split.value().isNegative() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit); } else { tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer); } } } } else { // isMultiSelection() dynamic_cast(m_editWidgets["postdate"])->loadDate(QDate()); dynamic_cast(m_editWidgets["status"])->setState(MyMoneySplit::Unknown); if (haveWidget("deposit")) { dynamic_cast(m_editWidgets["deposit"])->loadText(""); dynamic_cast(m_editWidgets["deposit"])->setAllowEmpty(); dynamic_cast(m_editWidgets["payment"])->loadText(""); dynamic_cast(m_editWidgets["payment"])->setAllowEmpty(); } if ((w = haveWidget("amount")) != 0) { dynamic_cast(w)->loadText(""); dynamic_cast(w)->setAllowEmpty(); } slotUpdateAction(action); if ((w = haveWidget("tabbar")) != 0) { w->setEnabled(false); } category->completion()->setSelected(QString()); } // allow kick off VAT processing again m_inUpdateVat = false; } QWidget* StdTransactionEditor::firstWidget() const { QWidget* w = 0; if (m_initialAction != KMyMoneyRegister::ActionNone) { w = haveWidget("payee"); } return w; } void StdTransactionEditor::slotReloadEditWidgets() { // reload category widget KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); QString categoryId = category->selectedItem(); AccountSet aSet; aSet.addAccountGroup(MyMoneyAccount::Asset); aSet.addAccountGroup(MyMoneyAccount::Liability); aSet.addAccountGroup(MyMoneyAccount::Income); aSet.addAccountGroup(MyMoneyAccount::Expense); if (KMyMoneyGlobalSettings::expertMode()) aSet.addAccountGroup(MyMoneyAccount::Equity); aSet.load(category->selector()); // if an account is specified then remove it from the widget so that the user // cannot create a transfer with from and to account being the same account if (!m_account.id().isEmpty()) category->selector()->removeItem(m_account.id()); if (!categoryId.isEmpty()) category->setSelectedItem(categoryId); // reload payee widget KMyMoneyPayeeCombo* payee = dynamic_cast(m_editWidgets["payee"]); QString payeeId = payee->selectedItem(); payee->loadPayees(MyMoneyFile::instance()->payeeList()); if (!payeeId.isEmpty()) { payee->setSelectedItem(payeeId); } // reload tag widget KTagContainer* tag = dynamic_cast(m_editWidgets["tag"]); QString tagId = tag->tagCombo()->selectedItem(); tag->loadTags(MyMoneyFile::instance()->tagList()); if (!tagId.isEmpty()) { tag->RemoveAllTagWidgets(); tag->addTagWidget(tagId); } } void StdTransactionEditor::slotUpdatePayee(const QString& payeeId) { // we have a new payee assigned to this transaction. // in case there is no category assigned, no value entered and no // memo available, we search for the last transaction of this payee // in the account. if (m_transaction.id().isEmpty() && m_splits.count() == 0 && KMyMoneyGlobalSettings::autoFillTransaction() != 0) { // check if category is empty KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); QStringList list; category->selectedItems(list); if (!list.isEmpty()) return; // check if memo is empty KTextEdit* memo = dynamic_cast(m_editWidgets["memo"]); if (memo && !memo->toPlainText().isEmpty()) return; // check if all value fields are empty kMyMoneyEdit* amount; QStringList fields; fields << "amount" << "payment" << "deposit"; QStringList::const_iterator it_f; for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) { amount = dynamic_cast(haveWidget(*it_f)); if (amount && !amount->value().isZero()) return; } #if 0 // Tony mentioned, that autofill does not work when he changed the date. Well, // that certainly makes sense when you enter transactions in register mode as // opposed to form mode, because the date field is located prior to the date // field in the tab order of the widgets and the user might have already // changed it. // // So I commented out the code that checks the date but left it in for reference. // (ipwizard, 2008-04-07) // check if date has been altered by user kMyMoneyDateInput* postDate = dynamic_cast(m_editWidgets["postdate"]); if ((m_lastPostDate.isValid() && (postDate->date() != m_lastPostDate)) || (!m_lastPostDate.isValid() && (postDate->date() != QDate::currentDate()))) return; #endif // if we got here, we have to autofill autoFill(payeeId); } // If payee has associated default account (category), set that now. const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeId); if (payeeObj.defaultAccountEnabled()) { KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); category->slotItemSelected(payeeObj.defaultAccountId()); } } MyMoneyMoney StdTransactionEditor::shares(const MyMoneyTransaction& t) const { MyMoneyMoney result; QList::const_iterator it_s; for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if ((*it_s).accountId() == m_account.id()) { result += (*it_s).shares(); } } return result; } struct uniqTransaction { const MyMoneyTransaction* t; int cnt; }; void StdTransactionEditor::autoFill(const QString& payeeId) { QList > list; MyMoneyTransactionFilter filter(m_account.id()); filter.addPayee(payeeId); MyMoneyFile::instance()->transactionList(list, filter); if (!list.empty()) { // ok, we found at least one previous transaction. now we clear out // what we have collected so far and add those splits from // the previous transaction. QList >::const_iterator it_t; QMap uniqList; // collect the transactions and see if we have any duplicates for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { QString key = (*it_t).first.accountSignature(); int cnt = 0; QMap::iterator it_u; do { QString ukey = QString("%1-%2").arg(key).arg(cnt); it_u = uniqList.find(ukey); if (it_u == uniqList.end()) { uniqList[ukey].t = &((*it_t).first); uniqList[ukey].cnt = 1; } else if (KMyMoneyGlobalSettings::autoFillTransaction() == 1) { // we already have a transaction with this signature. we must // now check, if we should really treat it as a duplicate according // to the value comparison delta. MyMoneyMoney s1 = shares(*((*it_u).t)); MyMoneyMoney s2 = shares((*it_t).first); if (s2.abs() > s1.abs()) { MyMoneyMoney t(s1); s1 = s2; s2 = t; } MyMoneyMoney diff; if (s2.isZero()) diff = s1.abs(); else diff = ((s1 - s2) / s2).convert(10000); if (diff.isPositive() && diff <= MyMoneyMoney(KMyMoneyGlobalSettings::autoFillDifference(), 100)) { uniqList[ukey].t = &((*it_t).first); break; // end while loop } } else if (KMyMoneyGlobalSettings::autoFillTransaction() == 2) { (*it_u).cnt++; break; // end while loop } ++cnt; } while (it_u != uniqList.end()); } MyMoneyTransaction t; if (KMyMoneyGlobalSettings::autoFillTransaction() != 2) { #if 0 // I removed this code to allow cancellation of an autofill if // it does not match even if there is only a single matching // transaction for the payee in question. In case, we want to revert // to the old behavior, don't forget to uncomment the closing // brace further down in the code as well. (ipwizard 2009-01-16) if (uniqList.count() == 1) { t = list.last().first; } else { #endif QPointer dlg = new KSelectTransactionsDlg(m_account, m_regForm); dlg->setWindowTitle(i18n("Select autofill transaction")); QMap::const_iterator it_u; for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) { dlg->addTransaction(*(*it_u).t); } // setup sort order dlg->m_register->setSortOrder("1,-9,-4"); // sort the transactions according to the sort setting dlg->m_register->sortItems(); // and select the last item if (dlg->m_register->lastItem()) dlg->m_register->selectItem(dlg->m_register->lastItem()); if (dlg->exec() == KDialog::Accepted) { t = dlg->transaction(); } #if 0 } #endif } else { int maxCnt = 0; QMap::const_iterator it_u; for (it_u = uniqList.constBegin(); it_u != uniqList.constEnd(); ++it_u) { if ((*it_u).cnt > maxCnt) { t = *(*it_u).t; maxCnt = (*it_u).cnt; } } } if (t != MyMoneyTransaction()) { m_transaction.removeSplits(); m_split = MyMoneySplit(); MyMoneySplit otherSplit; QList::ConstIterator it; for (it = t.splits().constBegin(); it != t.splits().constEnd(); ++it) { MyMoneySplit s(*it); s.setReconcileFlag(MyMoneySplit::NotReconciled); s.setReconcileDate(QDate()); s.clearId(); s.setBankID(QString()); // older versions of KMyMoney used to set the action // we don't need this anymore if (s.action() != MyMoneySplit::ActionAmortization && s.action() != MyMoneySplit::ActionInterest) { s.setAction(QString()); } // FIXME update check number. The old comment contained // // // If a check number is already specified by the user it is // used. If the input field is empty and the previous transaction // contains a checknumber, the next usable check number will be assigned // to the transaction. // kMyMoneyLineEdit* editNr = dynamic_cast(haveWidget("number")); if (editNr && !editNr->text().isEmpty()) { s.setNumber(editNr->text()); } else if (!s.number().isEmpty()) { - s.setNumber(KMyMoneyUtils::nextCheckNumber(m_account)); + s.setNumber(KMyMoneyUtils::nextCheckNumber(m_account.value("lastNumberUsed"))); } // if the memos should not be used with autofill or // if the transaction has exactly two splits, remove // the memo text of the split that does not reference // the current account. This allows the user to change // the autofilled memo text which will then also be used // in this split. See createTransaction() for this logic. if ((s.accountId() != m_account.id() && t.splitCount() == 2) || !KMyMoneyGlobalSettings::autoFillUseMemos()) s.setMemo(QString()); m_transaction.addSplit(s); if (s.accountId() == m_account.id() && m_split == MyMoneySplit()) { m_split = s; } else { otherSplit = s; } } // make sure to extract the right action KMyMoneyRegister::Action action; action = m_split.shares().isNegative() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit; if (m_transaction.splitCount() == 2) { MyMoneyAccount acc = MyMoneyFile::instance()->account(otherSplit.accountId()); if (acc.isAssetLiability()) action = KMyMoneyRegister::ActionTransfer; } // now setup the widgets with the new data but keep the date QDate date = dynamic_cast(m_editWidgets["postdate"])->date(); loadEditWidgets(action); dynamic_cast(m_editWidgets["postdate"])->setDate(date); } } // focus jumps into the category field QWidget* w; if ((w = haveWidget("payee")) != 0) { w->setFocus(); } } void StdTransactionEditor::slotUpdateAction(int action) { TabBar* tabbar = dynamic_cast(haveWidget("tabbar")); if (tabbar) { QLabel* categoryLabel = dynamic_cast(haveWidget("category-label")); KMyMoneyCashFlowCombo* cashflow = dynamic_cast(m_editWidgets["cashflow"]); switch (action) { case KMyMoneyRegister::ActionDeposit: categoryLabel->setText(i18n("Category")); cashflow->setDirection(KMyMoneyRegister::Deposit); break; case KMyMoneyRegister::ActionTransfer: if (m_split.shares().isNegative()) { cashflow->setDirection(KMyMoneyRegister::Payment); categoryLabel->setText(i18n("Transfer to")); } else { cashflow->setDirection(KMyMoneyRegister::Deposit); categoryLabel->setText(i18n("Transfer from")); } tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer); slotUpdateCashFlow(cashflow->direction()); break; case KMyMoneyRegister::ActionWithdrawal: categoryLabel->setText(i18n("Category")); cashflow->setDirection(KMyMoneyRegister::Payment); break; } resizeForm(); } } void StdTransactionEditor::slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection dir) { QLabel* categoryLabel = dynamic_cast(haveWidget("category-label")); KMyMoneyCashFlowCombo* cashflow = dynamic_cast(haveWidget("cashflow")); cashflow->setDirection(dir); // qDebug("Update cashflow to %d", dir); if (categoryLabel) { TabBar* tabbar = dynamic_cast(haveWidget("tabbar")); if (!tabbar) return; // no transaction form if (categoryLabel->text() != i18n("Category")) { tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer); if (dir == KMyMoneyRegister::Deposit) { categoryLabel->setText(i18n("Transfer from")); } else { categoryLabel->setText(i18n("Transfer to")); } resizeForm(); } else { if (dir == KMyMoneyRegister::Deposit) tabbar->setCurrentIndex(KMyMoneyRegister::ActionDeposit); else tabbar->setCurrentIndex(KMyMoneyRegister::ActionWithdrawal); } } } void StdTransactionEditor::slotUpdateCategory(const QString& id) { QLabel *categoryLabel = dynamic_cast(haveWidget("category-label")); // qDebug("Update category to %s", qPrintable(id)); if (categoryLabel) { TabBar* tabbar = dynamic_cast(haveWidget("tabbar")); kMyMoneyEdit* amount = dynamic_cast(m_editWidgets["amount"]); MyMoneyMoney val = amount->value(); if (categoryLabel->text() == i18n("Transfer from")) { val = -val; } else { val = val.abs(); } if (tabbar) { tabbar->setTabEnabled(KMyMoneyRegister::ActionTransfer, true); tabbar->setTabEnabled(KMyMoneyRegister::ActionDeposit, true); tabbar->setTabEnabled(KMyMoneyRegister::ActionWithdrawal, true); } bool disableTransferTab = false; if (!id.isEmpty()) { MyMoneyAccount acc = MyMoneyFile::instance()->account(id); if (acc.isAssetLiability() || acc.accountGroup() == MyMoneyAccount::Equity) { if (tabbar) { tabbar->setCurrentIndex(KMyMoneyRegister::ActionTransfer); tabbar->setTabEnabled(KMyMoneyRegister::ActionDeposit, false); tabbar->setTabEnabled(KMyMoneyRegister::ActionWithdrawal, false); } KMyMoneyCashFlowCombo* cashflow = dynamic_cast(m_editWidgets["cashflow"]); if (val.isZero()) { if (cashflow && (cashflow->direction() == KMyMoneyRegister::Deposit)) { categoryLabel->setText(i18n("Transfer from")); } else { categoryLabel->setText(i18n("Transfer to")); } } else if (val.isNegative()) { categoryLabel->setText(i18n("Transfer from")); cashflow->setDirection(KMyMoneyRegister::Deposit); } else categoryLabel->setText(i18n("Transfer to")); } else { disableTransferTab = true; categoryLabel->setText(i18n("Category")); } updateAmount(val); } else { //id.isEmpty() KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); disableTransferTab = !category->currentText().isEmpty(); categoryLabel->setText(i18n("Category")); } if (tabbar) { if (disableTransferTab) { // set the proper tab before disabling the currently active tab if (tabbar->currentIndex() == KMyMoneyRegister::ActionTransfer) { tabbar->setCurrentIndex(val.isPositive() ? KMyMoneyRegister::ActionWithdrawal : KMyMoneyRegister::ActionDeposit); } tabbar->setTabEnabled(KMyMoneyRegister::ActionTransfer, false); } tabbar->update(); } resizeForm(); } updateVAT(false); } void StdTransactionEditor::slotUpdatePayment(const QString& txt) { MyMoneyMoney val(txt); if (val.isNegative()) { dynamic_cast(m_editWidgets["deposit"])->setValue(val.abs()); dynamic_cast(m_editWidgets["payment"])->clearText(); } else { dynamic_cast(m_editWidgets["deposit"])->clearText(); } updateVAT(); } void StdTransactionEditor::slotUpdateDeposit(const QString& txt) { MyMoneyMoney val(txt); if (val.isNegative()) { dynamic_cast(m_editWidgets["payment"])->setValue(val.abs()); dynamic_cast(m_editWidgets["deposit"])->clearText(); } else { dynamic_cast(m_editWidgets["payment"])->clearText(); } updateVAT(); } void StdTransactionEditor::slotUpdateAmount(const QString& txt) { // qDebug("Update amount to %s", qPrintable(txt)); MyMoneyMoney val(txt); updateAmount(val); updateVAT(true); } void StdTransactionEditor::updateAmount(const MyMoneyMoney& val) { // we don't do anything if we have multiple transactions selected if (isMultiSelection()) return; QLabel *categoryLabel = dynamic_cast(haveWidget("category-label")); if (categoryLabel) { KMyMoneyCashFlowCombo* cashflow = dynamic_cast(m_editWidgets["cashflow"]); if (!val.isPositive()) { // fixes BUG321317 if (categoryLabel->text() != i18n("Category")) { if (cashflow->direction() == KMyMoneyRegister::Payment) { categoryLabel->setText(i18n("Transfer to")); } } else { slotUpdateCashFlow(cashflow->direction()); } dynamic_cast(m_editWidgets["amount"])->setValue(val.abs()); } else { if (categoryLabel->text() != i18n("Category")) { if (cashflow->direction() == KMyMoneyRegister::Payment) { categoryLabel->setText(i18n("Transfer to")); } else { categoryLabel->setText(i18n("Transfer from")); cashflow->setDirection(KMyMoneyRegister::Deposit); // editing with +ve shows 'from' not 'pay to' } } dynamic_cast(m_editWidgets["amount"])->setValue(val.abs()); } } } void StdTransactionEditor::updateVAT(bool amountChanged) { // make sure that we don't do this recursively if (m_inUpdateVat) return; // we don't do anything if we have multiple transactions selected if (isMultiSelection()) return; // if auto vat assignment for this account is turned off // we don't care about taxes if (m_account.value("NoVat") == "Yes") return; // more splits than category and tax are not supported if (m_splits.count() > 2) return; // in order to do anything, we need an amount MyMoneyMoney amount, newAmount; bool amountOk; amount = amountFromWidget(&amountOk); if (!amountOk) return; // If the transaction has a tax and a category split, remove the tax split if (m_splits.count() == 2) { newAmount = removeVatSplit(); if (m_splits.count() == 2) // not removed? return; } else { // otherwise, we need a category KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); if (category->selectedItem().isEmpty()) return; // if no VAT account is associated with this category/account, then we bail out MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem()); if (cat.value("VatAccount").isEmpty()) return; newAmount = amount; } // seems we have everything we need if (amountChanged) newAmount = amount; MyMoneyTransaction transaction; if (createTransaction(transaction, m_transaction, m_split)) { if (addVatSplit(transaction, newAmount)) { m_transaction = transaction; if (!m_transaction.splits().isEmpty()) m_split = m_transaction.splits().front(); loadEditWidgets(); // if we made this a split transaction, then move the // focus to the memo field if (qApp->focusWidget() == haveWidget("category")) { QWidget* w = haveWidget("memo"); if (w) w->setFocus(); } } } } bool StdTransactionEditor::addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount) { if (tr.splitCount() != 2) return false; MyMoneyFile* file = MyMoneyFile::instance(); // extract the category split from the transaction MyMoneyAccount category = file->account(tr.splitByAccount(m_account.id(), false).accountId()); return file->addVATSplit(tr, m_account, category, amount); } MyMoneyMoney StdTransactionEditor::removeVatSplit() { // we only deal with splits that have three splits if (m_splits.count() != 2) return amountFromWidget(); MyMoneySplit c; // category split MyMoneySplit t; // tax split bool netValue = false; QList::const_iterator it_s; for (it_s = m_splits.constBegin(); it_s != m_splits.constEnd(); ++it_s) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (!acc.value("VatAccount").isEmpty()) { netValue = (acc.value("VatAmount").toLower() == "net"); c = (*it_s); } else if (!acc.value("VatRate").isEmpty()) { t = (*it_s); } } // bail out if not all splits are setup if (c.id().isEmpty() || t.id().isEmpty()) return amountFromWidget(); MyMoneyMoney amount; // reduce the splits if (netValue) { amount = -c.shares(); } else { amount = -(c.shares() + t.shares()); } // remove tax split from the list, ... m_splits.clear(); m_splits.append(c); // ... make sure that the widget is updated ... // block the signals to avoid popping up the split editor dialog // for nothing m_editWidgets["category"]->blockSignals(true); QString id; setupCategoryWidget(id); m_editWidgets["category"]->blockSignals(false); // ... and return the updated amount return amount; } bool StdTransactionEditor::isComplete(QString& reason) const { reason.clear(); QMap::const_iterator it_w; kMyMoneyDateInput* postDate = dynamic_cast(m_editWidgets["postdate"]); if (postDate) { QDate accountOpeningDate = m_account.openingDate(); for (QList::const_iterator it_s = m_splits.constBegin(); it_s != m_splits.constEnd(); ++it_s) { const MyMoneyAccount& acc = MyMoneyFile::instance()->account((*it_s).accountId()); // compute the newest opening date of all accounts involved in the transaction if (acc.openingDate() > accountOpeningDate) accountOpeningDate = acc.openingDate(); } // check the selected category in case m_splits hasn't been updated yet KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); if (category && !category->selectedItem().isEmpty()) { MyMoneyAccount cat = MyMoneyFile::instance()->account(category->selectedItem()); if (cat.openingDate() > accountOpeningDate) accountOpeningDate = cat.openingDate(); } if (postDate->date().isValid() && (postDate->date() < accountOpeningDate)) { postDate->markAsBadDate(true, KMyMoneyGlobalSettings::listNegativeValueColor()); reason = i18n("Cannot enter transaction with postdate prior to account's opening date."); postDate->setToolTip(reason); return false; } postDate->markAsBadDate(); postDate->setToolTip(""); } for (it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) { KMyMoneyPayeeCombo* payee = dynamic_cast(*it_w); KTagContainer* tagContainer = dynamic_cast(*it_w); KMyMoneyCategory* category = dynamic_cast(*it_w); kMyMoneyEdit* amount = dynamic_cast(*it_w); KMyMoneyReconcileCombo* reconcile = dynamic_cast(*it_w); KMyMoneyCashFlowCombo* cashflow = dynamic_cast(*it_w); KTextEdit* memo = dynamic_cast(*it_w); if (payee && !(payee->currentText().isEmpty())) break; if (category && !category->lineEdit()->text().isEmpty()) break; if (amount && !(amount->value().isZero())) break; // the following widgets are only checked if we are editing multiple transactions if (isMultiSelection()) { TabBar* tabbar = dynamic_cast(haveWidget("tabbar")); if (tabbar) { tabbar->setEnabled(true); } if (reconcile && reconcile->state() != MyMoneySplit::Unknown) break; if (cashflow && cashflow->direction() != KMyMoneyRegister::Unknown) break; if (postDate->date().isValid() && (postDate->date() >= m_account.openingDate())) break; if (memo && m_memoChanged) break; if (tagContainer && !(tagContainer->selectedTags().isEmpty())) // Tag is optional field break; } } return it_w != m_editWidgets.end(); } void StdTransactionEditor::slotCreateCategory(const QString& name, QString& id) { MyMoneyAccount acc, parent; acc.setName(name); KMyMoneyCashFlowCombo* cashflow = dynamic_cast(haveWidget("cashflow")); if (cashflow) { // form based input if (cashflow->direction() == KMyMoneyRegister::Deposit) parent = MyMoneyFile::instance()->income(); else parent = MyMoneyFile::instance()->expense(); } else if (haveWidget("deposit")) { // register based input kMyMoneyEdit* deposit = dynamic_cast(m_editWidgets["deposit"]); if (deposit->value().isPositive()) parent = MyMoneyFile::instance()->income(); else parent = MyMoneyFile::instance()->expense(); } else parent = MyMoneyFile::instance()->expense(); // TODO extract possible first part of a hierarchy and check if it is one // of our top categories. If so, remove it and select the parent // according to this information. emit createCategory(acc, parent); // return id id = acc.id(); } int StdTransactionEditor::slotEditSplits() { int rc = KDialog::Rejected; if (!m_openEditSplits) { // only get in here in a single instance m_openEditSplits = true; // force focus change to update all data QWidget* w = dynamic_cast(m_editWidgets["category"])->splitButton(); if (w) w->setFocus(); kMyMoneyEdit* amount = dynamic_cast(haveWidget("amount")); kMyMoneyEdit* deposit = dynamic_cast(haveWidget("deposit")); kMyMoneyEdit* payment = dynamic_cast(haveWidget("payment")); KMyMoneyCashFlowCombo* cashflow = 0; KMyMoneyRegister::CashFlowDirection dir = KMyMoneyRegister::Unknown; bool isValidAmount = false; if (amount) { isValidAmount = amount->lineedit()->text().length() != 0; cashflow = dynamic_cast(haveWidget("cashflow")); if (cashflow) dir = cashflow->direction(); } else { if (deposit) { if (deposit->lineedit()->text().length() != 0) { isValidAmount = true; dir = KMyMoneyRegister::Deposit; } } if (payment) { if (payment->lineedit()->text().length() != 0) { isValidAmount = true; dir = KMyMoneyRegister::Payment; } } if (!deposit || !payment) { qDebug("Internal error: deposit(%p) & payment(%p) widgets not found but required", deposit, payment); return rc; } } if (dir == KMyMoneyRegister::Unknown) dir = KMyMoneyRegister::Payment; MyMoneyTransaction transaction; if (createTransaction(transaction, m_transaction, m_split)) { MyMoneyMoney value; QPointer dlg = new KSplitTransactionDlg(transaction, transaction.splits().isEmpty() ? MyMoneySplit() : transaction.splits().front(), m_account, isValidAmount, dir == KMyMoneyRegister::Deposit, MyMoneyMoney(), m_priceInfo, m_regForm); connect(dlg, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); connect(dlg, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount))); connect(dlg, SIGNAL(createTag(QString, QString&)), this, SIGNAL(createTag(QString, QString&))); if ((rc = dlg->exec()) == KDialog::Accepted) { m_transaction = dlg->transaction(); if (!m_transaction.splits().isEmpty()) m_split = m_transaction.splits().front(); loadEditWidgets(); } delete dlg; } // focus jumps into the tag field if ((w = haveWidget("tag")) != 0) { w->setFocus(); } m_openEditSplits = false; } return rc; } void StdTransactionEditor::checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId) { if (s.accountId().isEmpty()) return; MyMoneyAccount acc = MyMoneyFile::instance()->account(s.accountId()); if (acc.isIncomeExpense()) { s.setPayeeId(payeeId); } else { if (s.payeeId().isEmpty()) s.setPayeeId(payeeId); } } MyMoneyMoney StdTransactionEditor::amountFromWidget(bool* update) const { bool updateValue = false; MyMoneyMoney value; KMyMoneyCashFlowCombo* cashflow = dynamic_cast(haveWidget("cashflow")); if (cashflow) { // form based input kMyMoneyEdit* amount = dynamic_cast(m_editWidgets["amount"]); // if both fields do not contain changes -> no need to update if (cashflow->direction() != KMyMoneyRegister::Unknown && !amount->lineedit()->text().isEmpty()) updateValue = true; value = amount->value(); if (cashflow->direction() == KMyMoneyRegister::Payment) value = -value; } else if (haveWidget("deposit")) { // register based input kMyMoneyEdit* deposit = dynamic_cast(m_editWidgets["deposit"]); kMyMoneyEdit* payment = dynamic_cast(m_editWidgets["payment"]); // if both fields do not contain text -> no need to update if (!(deposit->lineedit()->text().isEmpty() && payment->lineedit()->text().isEmpty())) updateValue = true; if (deposit->value().isPositive()) value = deposit->value(); else value = -(payment->value()); } if (update) *update = updateValue; // determine the max fraction for this account and // adjust the value accordingly return value.convert(m_account.fraction()); } bool StdTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog) { // extract price info from original transaction m_priceInfo.clear(); QList::const_iterator it_s; if (!torig.id().isEmpty()) { for (it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) { if ((*it_s).id() != sorig.id()) { MyMoneyAccount cat = MyMoneyFile::instance()->account((*it_s).accountId()); if (cat.currencyId() != m_account.currencyId()) { if (!(*it_s).shares().isZero() && !(*it_s).value().isZero()) { m_priceInfo[cat.currencyId()] = ((*it_s).shares() / (*it_s).value()).reduce(); } } } } } t = torig; t.removeSplits(); t.setCommodity(m_account.currencyId()); kMyMoneyDateInput* postDate = dynamic_cast(m_editWidgets["postdate"]); if (postDate->date().isValid()) { t.setPostDate(postDate->date()); } // we start with the previous values, make sure we can add them later on MyMoneySplit s0 = sorig; s0.clearId(); // make sure we reference this account here s0.setAccountId(m_account.id()); // memo and number field are special: if we have multiple transactions selected // and the edit field is empty, we treat it as "not modified". // FIXME a better approach would be to have a 'dirty' flag with the widgets // which identifies if the originally loaded value has been modified // by the user KTextEdit* memo = dynamic_cast(m_editWidgets["memo"]); if (memo) { if (!isMultiSelection() || (isMultiSelection() && m_memoChanged)) s0.setMemo(memo->toPlainText()); } kMyMoneyLineEdit* number = dynamic_cast(haveWidget("number")); if (number) { if (!isMultiSelection() || (isMultiSelection() && !number->text().isEmpty())) s0.setNumber(number->text()); } KMyMoneyPayeeCombo* payee = dynamic_cast(m_editWidgets["payee"]); QString payeeId; if (!isMultiSelection() || (isMultiSelection() && !payee->currentText().isEmpty())) { payeeId = payee->selectedItem(); s0.setPayeeId(payeeId); } //KMyMoneyTagCombo* tag = dynamic_cast(m_editWidgets["tag"]); KTagContainer* tag = dynamic_cast(m_editWidgets["tag"]); if (!isMultiSelection() || (isMultiSelection() && !tag->selectedTags().isEmpty())) { s0.setTagIdList(tag->selectedTags()); } bool updateValue; MyMoneyMoney value = amountFromWidget(&updateValue); if (updateValue) { // for this account, the shares and value is the same s0.setValue(value); s0.setShares(value); } else { value = s0.value(); } // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given KMyMoneyReconcileCombo* status = dynamic_cast(m_editWidgets["status"]); if (status->state() != MyMoneySplit::Unknown) s0.setReconcileFlag(status->state()); if (s0.reconcileFlag() == MyMoneySplit::Reconciled && !s0.reconcileDate().isValid()) s0.setReconcileDate(QDate::currentDate()); checkPayeeInSplit(s0, payeeId); // add the split to the transaction t.addSplit(s0); // if we have no other split we create it // if we have none or only one other split, we reconstruct it here // if we have more than one other split, we take them as they are // make sure to perform all those changes on a local copy QList splits = m_splits; MyMoneySplit s1; if (splits.isEmpty()) { s1.setMemo(s0.memo()); splits.append(s1); // make sure we will fill the value and share fields later on updateValue = true; } // FIXME in multiSelection we currently only support transactions with one // or two splits. So we check the original transaction and extract the other // split or create it if (isMultiSelection()) { if (torig.splitCount() == 2) { QList::const_iterator it_s; for (it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) { if ((*it_s).id() == sorig.id()) continue; s1 = *it_s; s1.clearId(); break; } } } else { if (splits.count() == 1) { s1 = splits[0]; s1.clearId(); } } if (isMultiSelection() || splits.count() == 1) { KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); if (!isMultiSelection() || (isMultiSelection() && !category->currentText().isEmpty())) { s1.setAccountId(category->selectedItem()); } // if the first split has a memo but the second split is empty, // we just copy the memo text over if (memo) { if (!isMultiSelection() || (isMultiSelection() && !memo->toPlainText().isEmpty())) { // if the memo is filled, we check if the // account referenced by s1 is a regular account or a category. // in case of a regular account, we just leave the memo as is // in case of a category we simply copy the new value over the old. // in case we don't even have an account id, we just skip because // the split will be removed later on anyway. if (!s1.memo().isEmpty() && s1.memo() != s0.memo()) { if (!s1.accountId().isEmpty()) { MyMoneyAccount acc = MyMoneyFile::instance()->account(s1.accountId()); if (acc.isIncomeExpense()) s1.setMemo(s0.memo()); else if (KMessageBox::questionYesNo(m_regForm, i18n("Do you want to replace memo

%1

with memo

%2

in the other split?", s1.memo(), s0.memo()), i18n("Copy memo"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String("CopyMemoOver")) == KMessageBox::Yes) s1.setMemo(s0.memo()); } } else { s1.setMemo(s0.memo()); } } } if (updateValue && !s1.accountId().isEmpty()) { s1.setValue(-value); MyMoneyMoney shares; if (!skipPriceDialog) { if (!KCurrencyCalculator::setupSplitPrice(shares, t, s1, m_priceInfo, m_regForm)) return false; } else { MyMoneyAccount cat = MyMoneyFile::instance()->account(s1.accountId()); if (m_priceInfo.find(cat.currencyId()) != m_priceInfo.end()) { shares = (s1.value() * m_priceInfo[cat.currencyId()]).reduce().convert(cat.fraction()); } else shares = s1.value(); } s1.setShares(shares); } checkPayeeInSplit(s1, payeeId); if (!s1.accountId().isEmpty()) t.addSplit(s1); } else { QList::iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { s1 = *it_s; s1.clearId(); checkPayeeInSplit(s1, payeeId); t.addSplit(s1); } } return true; } void StdTransactionEditor::setupFinalWidgets() { addFinalWidget(haveWidget("deposit")); addFinalWidget(haveWidget("payment")); addFinalWidget(haveWidget("amount")); addFinalWidget(haveWidget("status")); } void StdTransactionEditor::slotUpdateAccount(const QString& id) { TransactionEditor::slotUpdateAccount(id); KMyMoneyCategory* category = dynamic_cast(m_editWidgets["category"]); if (category && category->splitButton()) { category->splitButton()->setDisabled(id.isEmpty()); } } diff --git a/kmymoney/dialogs/transactioneditor.h b/kmymoney/dialogs/transactioneditor.h index aa8d18ecb..d26317084 100644 --- a/kmymoney/dialogs/transactioneditor.h +++ b/kmymoney/dialogs/transactioneditor.h @@ -1,446 +1,447 @@ /*************************************************************************** transactioneditor.h ---------- begin : Wed Jun 07 2006 copyright : (C) 2006 by Thomas Baumgart email : 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. * * * ***************************************************************************/ #ifndef TRANSACTIONEDITOR_H #define TRANSACTIONEDITOR_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyschedule.h" #include #include class KMyMoneyCategory; class TransactionEditor : public QObject { Q_OBJECT public: TransactionEditor(); TransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate); virtual ~TransactionEditor(); /** * This method is used as a helper because virtual methods cannot be * called within a constructor. Thus setup() should be called immediately * after a TransactionEditor() object or one of its derivatives is * constructed. The parameter @a account identifies the account that * is currently opened in the calling ledger view. * * This account will not be included in category sets. The default is * no account so all will be shown. I have no idea anymore, what I * tried to say with the first sentence above. :( Maybe this is crap. * * @param tabOrderWidgets QWidgetList which will be filled with the pointers * to the editWidgets in their tab order * @param account account that is currently shown in the calling ledger view * @param action default action (defaults to ActionNone). */ void setup(QWidgetList& tabOrderWidgets, const MyMoneyAccount& account = MyMoneyAccount(), KMyMoneyRegister::Action action = KMyMoneyRegister::ActionNone); /** * Enter the transactions into the ledger. In case of a newly created * transaction @a newId contains the assigned id. In case @a askForSchedule * is true (the default), the user will be asked if he wants to enter new * transactions with a post date in the future into the ledger or rather * create a schedule for them. In case @a suppressBalanceWarnings is @p false * (the default) a warning will be displayed when the balance crosses the minimum * or maximum balance settings for the account. */ virtual bool enterTransactions(QString& newId, bool askForSchedule = true, bool suppressBalanceWarnings = false); /** * This method creates a transaction based on the contents of the current widgets, * the splits in m_split in single selection mode or an existing transaction/split * and the contents of the widgets in multi selection mode. * * The split referencing the current account is returned as the first split in the * transaction's split list. * * @param t reference to created transaction * @param torig the original transaction * @param sorig the original split * @param skipPriceDialog if @p true the user will not be requested for price information * (defaults to @p false) * * @return @p false if aborted by user, @p true otherwise * * @note Usually not used directly. If unsure, use enterTransactions() instead. */ virtual bool createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog = false) = 0; /** * This method returns information about the completeness of the data * entered. This can be used to control the availability of the * 'Enter transaction' action. * * @retval true if entering the transaction into the engine * @retval false if not enough information is present to enter the * transaction into the engine * * @param reason will be filled with a string about the reason why the * completeness is not reached. Empty if the return value * is @c true. * * @sa transactionDataSufficient() */ virtual bool isComplete(QString& reason) const = 0; /** * This method returns information if the editor is started with multiple transactions * being selected or not. * * @retval false only a single transaction was selected when the editor was started * @retval true multiple transactions were selected when the editor was started */ virtual bool isMultiSelection() const { return m_transactions.count() > 1; } virtual bool fixTransactionCommodity(const MyMoneyAccount& account); + QString lastNumberUsed() const; virtual bool canAssignNumber() const; virtual void assignNextCheckNumber(); virtual void assignStatementNumber(); virtual void assignNextStatementNumber(); virtual void assignNextStatementPageNumber(); /** * Returns a pointer to the widget that should receive * the focus after the editor has been started. */ virtual QWidget* firstWidget() const = 0; /** * Returns a pointer to a widget by name */ QWidget* haveWidget(const QString& name) const; void setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s); bool eventFilter(QObject* o, QEvent* e); const MyMoneyAccount& account() const { return m_account; } void clearFinalWidgets(); void addFinalWidget(const QWidget*); QString m_memoText; QString m_scheduleInfo; MyMoneySchedule::paymentTypeE m_paymentMethod; public slots: void slotReloadEditWidgets(); /** * The default implementation returns QDialog::Rejected */ virtual int slotEditSplits(); /** * Modify the account which the transaction should be based on. The * initial value for the account is passed during setup(). * * @param id of the account to be used */ virtual void slotUpdateAccount(const QString& id); protected: virtual void createEditWidgets() = 0; virtual void setupFinalWidgets() = 0; virtual void loadEditWidgets(KMyMoneyRegister::Action action = KMyMoneyRegister::ActionNone) = 0; void setupCategoryWidget(KMyMoneyCategory* category, const QList& splits, QString& categoryId, const char* splitEditSlot, bool allowObjectCreation = true); void resizeForm(); /** * This method sets the precision of the value widgets to reflect * the account in m_account. If m_account has no id, the precision * defaults to 2. */ void setupPrecision(); protected slots: void slotUpdateButtonState(); void slotUpdateMemoState(); void slotUpdateAccount(); void slotNumberChanged(const QString&); signals: /** * This signal is sent out by the destructor to inform other entities * that editing has been finished. The parameter @a t contains the list * of transactions that were processed. */ void finishEdit(const KMyMoneyRegister::SelectedTransactions& t); /** * This signal is sent out whenever enough data is present to enter the * transaction into the ledger. This signal can be used to control the * KAction which implements entering the transaction. * * @sa isComplete() * * @param state @a true if enough data is present, @a false otherwise. */ void transactionDataSufficient(bool state); /** * This signal is sent out, when a new payee needs to be created * @sa KMyMoneyCombo::createItem() * * @param txt The name of the payee to be created * @param id A connected slot should store the id of the created object in this variable */ void createPayee(const QString& txt, QString& id); /** * This signal is sent out, when a new category needs to be created * Depending on the setting of either a payment or deposit, the parent * account will be preset to Expense or Income. * * @param account reference to account info. Will be filled by called slot * @param parent reference to parent account */ void createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This signal is sent out, when a new tag needs to be created * @param txt The name of the tag to be created * @param id A connected slot should store the id of the created object in this variable */ void createTag(const QString& txt, QString& id); /** * This signal is sent out, when a new security (e.g. stock )needs to be created * @a Parent should be the investment account under which the security account * will be created. * * @param account reference to account info. Will be filled by called slot * @param parent reference to parent account */ void createSecurity(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * Signal is emitted, if any of the widgets enters (@a state equals @a true) * or leaves (@a state equals @a false) object creation mode. * * @param state Enter (@a true) or leave (@a false) object creation */ void objectCreation(bool state); void statusMsg(const QString& txt); void statusProgress(int cnt, int base); /** * This signal is sent out for each newly added transaction * * @param date the post date of the newly created transaction */ void lastPostDateUsed(const QDate& date); /** * This signal is sent out, if the user decides to schedule the transaction @a t * rather then adding it to the ledger right away. */ void scheduleTransaction(const MyMoneyTransaction& t, MyMoneySchedule::occurrenceE occurrence); /** * This signal is sent out, if the user double clicks the number field */ void assignNumber(); /** * This signal is sent out, if the user has pressed the ESC key. */ void escapePressed(); /** * This signal is sent out, if the user has pressed the Return or Enter * key and asks to end editing the transaction */ void returnPressed(); /** * This signal is sent out, if any of the balance warning levels * for @p account has been reached. @p msg contains the message text. * @p parent points to the parent widget to be used for the warning message box. */ void balanceWarning(QWidget* parent, const MyMoneyAccount& account, const QString& msg); void operationTypeChanged(int index); protected: QList m_splits; KMyMoneyRegister::SelectedTransactions m_transactions; QList m_finalEditWidgets; TransactionEditorContainer* m_regForm; KMyMoneyRegister::Transaction* m_item; KMyMoneyRegister::QWidgetContainer m_editWidgets; MyMoneyAccount m_account; MyMoneyTransaction m_transaction; MyMoneySplit m_split; QDate m_lastPostDate; QMap m_priceInfo; KMyMoneyRegister::Action m_initialAction; bool m_openEditSplits; bool m_memoChanged; private: /** * If a new or an edited transaction has a valid number, keep it with the account */ void keepNewNumber(const MyMoneyTransaction& tr); }; class StdTransactionEditor : public TransactionEditor { Q_OBJECT public: StdTransactionEditor(); StdTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::Transaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate); ~StdTransactionEditor(); bool isComplete(QString& reason) const; QWidget* firstWidget() const; /** * This method creates a transaction based on the contents of the current widgets, * the splits in m_split in single selection mode or an existing transaction/split * and the contents of the widgets in multi selection mode. * * The split referencing the current account is returned as the first split in the * transaction's split list. * * @param t reference to created transaction * @param torig the original transaction * @param sorig the original split * @param skipPriceDialog if @p true the user will not be requested for price information * (defaults to @p false) * * @return @p false if aborted by user, @p true otherwise * * @note Usually not used directly. If unsure, use enterTransactions() instead. */ bool createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool skipPriceDialog = false); public slots: int slotEditSplits(); void slotUpdateAmount(const QString&); protected slots: void slotReloadEditWidgets(); void slotUpdatePayment(const QString&); void slotUpdateDeposit(const QString&); void slotUpdateCategory(const QString&); void slotUpdatePayee(const QString&); //void slotUpdateTag(const QString&); void slotUpdateCashFlow(KMyMoneyRegister::CashFlowDirection); void slotCreateCategory(const QString&, QString&); void slotUpdateAction(int action); void slotUpdateAccount(const QString& id); protected: /** * This method creates all necessary widgets for this transaction editor. * All signals will be connected to the relevant slots. */ void createEditWidgets(); /** * This method (re-)loads the widgets with the transaction information * contained in @a m_transaction and @a m_split. * * @param action preset the edit wigdets for @a action if no transaction * is present */ void loadEditWidgets(KMyMoneyRegister::Action action = KMyMoneyRegister::ActionNone); void setupCategoryWidget(QString&); void updateAmount(const MyMoneyMoney& value); bool isTransfer(const QString& accId1, const QString& accId2) const; void checkPayeeInSplit(MyMoneySplit& s, const QString& payeeId); /** * This method fills the editor widgets with the last transaction * that can be found for payee @a payeeId in the account @a m_account. */ void autoFill(const QString& payeeId); /** * Extracts the amount of the transaction from the widgets depending * if form or register based input method is used. * Returns if an amount has been found in @a update. * * @param update pointer to update information flag * @return amount of transaction (deposit positive, payment negative) */ MyMoneyMoney amountFromWidget(bool* update = 0) const; /** * Create or update a VAT split */ void updateVAT(bool amountChanged = true); MyMoneyMoney removeVatSplit(); /** * This method adds a VAT split to transaction @a tr if necessary. * * @param tr transaction that the split should be added to * @param amount Amount to be used for the calculation. Depending upon the * setting of the resp. category, this value is treated as * either gross or net value. * @retval false VAT split has not been added * @retval true VAT split has been added */ bool addVatSplit(MyMoneyTransaction& tr, const MyMoneyMoney& amount); void setupFinalWidgets(); /** * This method returns the sum of all splits of transaction @a t that * reference account m_account. */ MyMoneyMoney shares(const MyMoneyTransaction& t) const; private: MyMoneyMoney m_shares; bool m_inUpdateVat; }; #endif diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 46115c4d7..b5a96866e 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7950 +1,7968 @@ /*************************************************************************** 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), m_printer(0) { // 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); } ~Private() { delete m_printer; } 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; QPrinter* m_printer; // methods void consistencyCheck(bool alwaysDisplayResults); void setCustomColors(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); QPrinter* printer(); }; 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())); actionCollection()->addAction(KStandardAction::PrintPreview, this, SLOT(slotPrintPreviewView())); 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_check_number = actionCollection()->addAction("transaction_assign_check_number"); - transaction_assign_check_number->setText(i18n("Assign next check number")); - transaction_assign_check_number->setShortcut(KShortcut("Ctrl+Shift+N")); - connect(transaction_assign_check_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNextCheckNumber())); + KAction *transaction_assign_next_check_number = actionCollection()->addAction("transaction_assign_next_check_number"); + transaction_assign_next_check_number->setText(i18n("Assign next check number")); + transaction_assign_next_check_number->setShortcut(KShortcut("Ctrl+Shift+N")); + connect(transaction_assign_next_check_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNextCheckNumber())); KAction *transaction_assign_statement_number = actionCollection()->addAction("transaction_assign_statement_number"); transaction_assign_statement_number->setText(i18n("Assign last used statement number")); connect(transaction_assign_statement_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignStatementNumber())); KAction *transaction_assign_next_statement_number = actionCollection()->addAction("transaction_assign_next_statement_number"); transaction_assign_next_statement_number->setText(i18n("Assign next statement number")); connect(transaction_assign_next_statement_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNextStatementNumber())); - KAction *transaction_assign_statement_page_number = actionCollection()->addAction("transaction_assign_statement_page_number"); - transaction_assign_statement_page_number->setText(i18n("Assign next statement page number")); - connect(transaction_assign_statement_page_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNextStatementPageNumber())); + KAction *transaction_assign_next_statement_page_number = actionCollection()->addAction("transaction_assign_next_statement_page_number"); + transaction_assign_next_statement_page_number->setText(i18n("Assign next statement page number")); + connect(transaction_assign_next_statement_page_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNextStatementPageNumber())); 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; } /** * return printer instance * @return printer instance */ QPrinter *KMyMoneyApp::printer() { return d->printer(); } 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(nullptr, 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(nullptr, 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(nullptr, 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::Row::Account, MyMoneyReport::QueryColumns::Number | MyMoneyReport::QueryColumns::Payee | MyMoneyReport::QueryColumns::Category, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::DetailLevel::All, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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::slotTransactionAssignCheckNumber() +{ + if (d->m_transactionEditor->account().accountType() == MyMoneyAccount::Checkings // checkings account + && KMyMoneyGlobalSettings::autoIncCheckNumber() // and auto inc number turned on? + && d->m_transactionEditor->m_paymentMethod == MyMoneySchedule::STYPE_WRITECHEQUE) {// only for STYPE_WRITECHEQUE schedule transaction + slotTransactionAssignNextCheckNumber(); + } else { + slotTransactionAssignStatementNumber(); + } +} + void KMyMoneyApp::slotTransactionAssignNextCheckNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextCheckNumber(); } void KMyMoneyApp::slotTransactionAssignStatementNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignStatementNumber(); } void KMyMoneyApp::slotTransactionAssignNextStatementNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextStatementNumber(); } void KMyMoneyApp::slotTransactionAssignNextStatementPageNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextStatementPageNumber(); } 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(nullptr, 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(nullptr, 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::slotPrintPreviewView() { d->m_myMoneyView->slotPrintPreviewView(); } 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_check_number")->setEnabled(false); + action("transaction_assign_next_check_number")->setEnabled(false); + action("transaction_assign_statement_number")->setEnabled(false); + action("transaction_assign_next_statement_number")->setEnabled(false); + action("transaction_assign_next_statement_page_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_check_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); + action("transaction_assign_next_check_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); + action("transaction_assign_statement_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); + action("transaction_assign_next_statement_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); + action("transaction_assign_next_statement_page_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); } bool isSchedule = !d->m_selectedSchedule.id().isEmpty(); action("transaction_edit")->setVisible(!isSchedule); action("transaction_editsplits")->setVisible(!isSchedule); action("transaction_enter")->setVisible(!isSchedule); action("transaction_cancel")->setVisible(!isSchedule); action("transaction_delete")->setVisible(!isSchedule); action("transaction_match")->setVisible(!isSchedule); action("transaction_accept")->setVisible(!isSchedule); action("schedule_new")->setVisible(isSchedule); action("schedule_edit")->setVisible(isSchedule); action("schedule_delete")->setVisible(isSchedule); action("schedule_duplicate")->setVisible(isSchedule); action("schedule_enter")->setVisible(isSchedule); action("schedule_skip")->setVisible(isSchedule); if (isSchedule) { 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(nullptr, 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;}"); } } QPrinter *KMyMoneyApp::Private::printer() { if (!m_printer) m_printer = new QPrinter; return m_printer; } 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(nullptr, 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/kmymoney.h b/kmymoney/kmymoney.h index e3b7388f5..acad5b91b 100644 --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -1,1407 +1,1411 @@ /*************************************************************************** kmymoney.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes ***************************************************************************/ /*************************************************************************** * * * 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 KMYMONEY_H #define KMYMONEY_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include "mymoneyschedule.h" #include #include #include #include #include #include #include #include #include "mymoney/onlinejob.h" #include "mymoney/onlinejobtyped.h" #include "onlinetasks/interfaces/tasks/credittransfer.h" class QResizeEvent; class QPrinter; class QTreeWidgetItem; class KPluginInfo; /*! \mainpage KMyMoney Main Page for API documentation. * * \section intro Introduction * * This is the API documentation for KMyMoney. It should be used as a reference * for KMyMoney developers and users who wish to see how KMyMoney works. This * documentation will be kept up-to-date as development progresses and should be * read for new features that have been developed in KMyMoney. */ /** * The base class for KMyMoney application windows. It sets up the main * window and reads the config file as well as providing a menubar, toolbar * and statusbar. * * @see KMyMoneyView * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2006-2008 * * @short Main application class. */ class KMyMoneyApp : public KXmlGuiWindow, public IMyMoneyProcessingCalendar { Q_OBJECT private slots: /** * Keep track of objects that are destroyed by external events */ void slotObjectDestroyed(QObject* o); /** * Add a context menu to the list used by KMessageBox::informationList to display the consistency check results. */ void slotInstallConsistencyCheckContextMenu(); /** * Handle the context menu of the list used by KMessageBox::informationList to display the consistency check results. */ void slotShowContextMenuForConsistencyCheck(const QPoint &); protected slots: void slotFileSaveAsFilterChanged(const QString& filter); /** * This slot is intended to be used as part of auto saving. This is used when the * QTimer emits the timeout signal and simply checks that the file is dirty (has * received modifications to its contents), and call the appropriate method to * save the file. Furthermore, re-starts the timer (possibly not needed). * @author mvillarino 2005 * @see KMyMoneyApp::slotDataChanged() */ void slotAutoSave(); /** * This slot re-enables all message for which the "Don't show again" * option had been selected. */ void slotEnableMessages(); /** * Called when the user asks for file information. */ void slotFileFileInfo(); /** * Called to run performance test. */ void slotPerformanceTest(); /** * Called to generate the sql to create kmymoney database tables etc. */ void slotGenerateSql(); /** * Debugging only: turn on/off traces */ void slotToggleTraces(); /** * Debugging only: turn on/off timers */ void slotToggleTimers(); /** * Called when the user asks for the personal information. */ void slotFileViewPersonal(); /** * Called when the user wishes to import tab delimeted transactions * into the current account. An account must be open for this to * work. Calls KMyMoneyView::slotAccountImportAscii. * * @see MyMoneyAccount */ void slotQifImport(); /** * Called when a QIF import is finished. */ void slotQifImportFinished(); /** * Opens a file selector dialog for the user to choose an existing OFX * file from the file system to be imported. This slot is expected to * be called from the UI. */ void slotGncImport(); /** * Open a dialog with a chart of the balance for the currently selected * account (m_selectedAccount). Return once the dialog is closed. Don't do * anything if no account is selected or charts are not available. */ void slotAccountChart(); /** * Opens a file selector dialog for the user to choose an existing KMM * statement file from the file system to be imported. This is for testing * only. KMM statement files are not designed to be exposed to the user. */ void slotStatementImport(); void slotLoadAccountTemplates(); void slotSaveAccountTemplates(); /** * Called when the user wishes to export some transaction to a * QIF formatted file. An account must be open for this to work. * Uses MyMoneyQifWriter() for the actual output. */ void slotQifExport(); /** * Open up the application wide settings dialog. * * @see KSettingsDlg */ void slotSettings(); /** * Called when the user wishes to backup the current file */ void slotBackupFile(); /** * Perform mount operation before making a backup of the current file */ void slotBackupMount(); /** * Perform the backup write operation */ bool slotBackupWriteFile(); /** * Perform unmount operation after making a backup of the current file */ void slotBackupUnmount(); /** * Finish backup of the current file */ void slotBackupFinish(); /** * Handle events on making a backup of the current file */ void slotBackupHandleEvents(); void slotShowTipOfTheDay(); void slotQifProfileEditor(); void slotShowPreviousView(); void slotShowNextView(); /** * Brings up a dialog to let the user search for specific transaction(s). It then * opens a results window to display those transactions. */ void slotFindTransaction(); /** * Destroys a possibly open the search dialog */ void slotCloseSearchDialog(); /** * Preloads the input dialog with the data of the current * selected institution and brings up the input dialog * and saves the information entered. */ void slotInstitutionEdit(const MyMoneyObject& obj = MyMoneyInstitution()); /** * Deletes the current selected institution. */ void slotInstitutionDelete(); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name. The parent defaults to * MyMoneyFile::expense() * * @param name Name of the account to be created. Could include a full hierarchy * @param id reference to storage which will receive the id after successful creation * * @note Typically, this slot can be connected to the * StdTransactionEditor::createCategory(const QString&, QString&) or * KMyMoneyCombo::createItem(const QString&, QString&) signal. */ void slotCategoryNew(const QString& name, QString& id); /** * Calls the print logic for the current view */ void slotPrintView(); /** * Calls the print preview logic for the current view */ void slotPrintPreviewView(); /** * Create a new investment */ void slotInvestmentNew(); /** * Create a new investment in a given @p parent investment account */ void slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This slot opens the investment editor to edit the currently * selected investment if possible */ void slotInvestmentEdit(); /** * Deletes the current selected investment. */ void slotInvestmentDelete(); /** * Performs online update for currently selected investment */ void slotOnlinePriceUpdate(); /** * Performs manual update for currently selected investment */ void slotManualPriceUpdate(); /** * Call this slot, if any configuration parameter has changed */ void slotUpdateConfiguration(); /** */ bool slotPayeeNew(const QString& newnameBase, QString& id); void slotPayeeNew(); /** */ void slotPayeeDelete(); /** * Slot that merges two or more selected payess into a new payee */ void slotPayeeMerge(); /** */ void slotTagNew(const QString& newnameBase, QString& id); void slotTagNew(); /** */ void slotTagDelete(); /** */ void slotBudgetNew(); /** */ void slotBudgetDelete(); /** */ void slotBudgetCopy(); /** */ void slotBudgetChangeYear(); /** */ void slotBudgetForecast(); /** */ void slotCurrencyNew(); /** */ void slotCurrencyUpdate(const QString ¤cyId, const QString& currencyName, const QString& currencyTradingSymbol); /** */ void slotCurrencyDelete(); /** */ void slotCurrencySetBase(); /** * This slot is used to start new features during the development cycle */ void slotNewFeature(); /** */ void slotTransactionsNew(); /** */ void slotTransactionsEdit(); /** */ void slotTransactionsEditSplits(); /** */ void slotTransactionsDelete(); /** */ void slotTransactionsEnter(); /** */ void slotTransactionsCancel(); /** */ void slotTransactionsCancelOrEnter(bool& okToSelect); /** */ void slotTransactionDuplicate(); /** */ void slotToggleReconciliationFlag(); /** */ void slotMarkTransactionCleared(); /** */ void slotMarkTransactionReconciled(); /** */ void slotMarkTransactionNotReconciled(); /** */ void slotTransactionGotoAccount(); /** */ void slotTransactionGotoPayee(); /** */ void slotTransactionCreateSchedule(); + /** + */ + void slotTransactionAssignCheckNumber(); + /** */ void slotTransactionAssignNextCheckNumber(); /** */ void slotTransactionAssignStatementNumber(); /** */ void slotTransactionAssignNextStatementNumber(); /** */ void slotTransactionAssignNextStatementPageNumber(); /** */ void slotTransactionCombine(); /** * This method takes the selected splits and checks that only one transaction (src) * has more than one split and all others have only a single one. It then copies the * splits of the @b src transaction to all others. */ void slotTransactionCopySplits(); /** * Accept the selected transactions that are marked as 'imported' and remove the flag */ void slotTransactionsAccept(); /** * This slot triggers an update of all views and restarts * a single shot timer to call itself again at beginning of * the next day. */ void slotDateChanged(); /** * This slot will be called when the engine data changed * and the application object needs to update its state. */ void slotDataChanged(); void slotMoveToAccount(const QString& id); void slotUpdateMoveToAccountMenu(); /** * This slot collects information for a new scheduled transaction * based on transaction @a t and @a occurrence and saves it in the engine. */ void slotScheduleNew(const MyMoneyTransaction& t, MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_MONTHLY); /** */ void slotScheduleDuplicate(); void slotKDELanguageSettings(); void slotAccountMapOnline(); void slotAccountUnmapOnline(); void slotAccountUpdateOnline(); void slotAccountUpdateOnlineAll(); /** * @brief Start dialog for an online banking transfer */ void slotNewOnlineTransfer(); /** * @brief Start dialog to edit onlineJob if possible * @param onlineJob id to edit */ void slotEditOnlineJob(const QString); /** * @brief Start dialog to edit onlineJob if possible */ void slotEditOnlineJob(const onlineJob); /** * @brief Start dialog to edit onlineJob if possible */ void slotEditOnlineJob(const onlineJobTyped); /** * @brief Saves an online banking job */ void slotOnlineJobSave(onlineJob job); /** * @brief Queue an online banking job */ void slotOnlineJobSend(onlineJob job); /** * @brief Send a list of onlineJobs */ void slotOnlineJobSend(QList jobs); /** * @brief Show the log currently selected online job */ void slotOnlineJobLog(); void slotOnlineJobLog(const QStringList& onlineJobIds); void slotManageGpgKeys(); void slotKeySelected(int idx); void slotStatusProgressDone(); public: /** * This method checks if there is at least one asset or liability account * in the current storage object. If not, it starts the new account wizard. */ void createInitialAccount(); /** * This method returns the last URL used or an empty URL * depending on the option setting if the last file should * be opened during startup or the open file dialog should * be displayed. * * @return URL of last opened file or empty if the program * should start with the open file dialog */ const KUrl lastOpenedURL(); /** * construtor of KMyMoneyApp, calls all init functions to create the application. */ explicit KMyMoneyApp(QWidget* parent = 0); /** * Destructor */ ~KMyMoneyApp(); static void progressCallback(int current, int total, const QString&); void writeLastUsedDir(const QString& directory); QString readLastUsedDir() const; void writeLastUsedFile(const QString& fileName); QString readLastUsedFile() const; /** * Returns whether there is an importer available that can handle this file */ bool isImportableFile(const KUrl& url); /** * This method is used to update the caption of the application window. * It sets the caption to "filename [modified] - KMyMoney". * * @param skipActions if true, the actions will not be updated. This * is usually onyl required by some early calls when * these widgets are not yet created (the default is false). */ void updateCaption(bool skipActions = false); /** * This method returns a list of all 'other' dcop registered kmymoney processes. * It's a subset of the return of DCOPclient()->registeredApplications(). * * @retval QStringList of process ids */ const QList instanceList() const; /** * Dump a list of the names of all defined KActions to stdout. */ void dumpActions() const; /** * Popup the context menu with the respective @p containerName. * Valid container names are defined in kmymoneyui.rc */ void showContextMenu(const QString& containerName); /** * This method opens the category editor with the data found in @a account. The * parent account is preset to @a parent but can be modified. If the user * acknowledges, the category is created. */ void createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This method returns the account for a given @a key - @a value pair. * If the account is not found in the list of accounts, MyMoneyAccount() * is returned. The @a key - @a value pair can be in the account's kvp * container or the account's online settings kvp container. */ const MyMoneyAccount& account(const QString& key, const QString& value) const; /** * This method set the online parameters stored in @a kvps with the * account referenced by @a acc. */ void setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps); KUrl selectFile(const QString& title, const QString& path, const QString& mask, KFile::Mode mode, QWidget *widget); /** * This methods returns the account from the list of accounts identified by * an account id or account name including an account hierachy. * * The parent account specifies from which account the search should be started. * In case the parent account does not have an id, the method scans all top-level accounts. * * If the account is not found in the list of accounts, MyMoneyAccount() is returned. * * @param acc account to find * @param parent parent account to search from * @retval found MyMoneyAccount account instance * @retval MyMoneyAccount() if not found */ const MyMoneyAccount& findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const; void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); const QString filename() const; void webConnect(const QString& sourceUrl, const QByteArray& asn_id); /** * Checks if the file with the @a url already exists. If so, * the user is asked if he/she wants to override the file. * If the user's answer is negative, @p false will be returned. * @p true will be returned in all other cases. */ bool okToWriteFile(const KUrl& url); protected: /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file */ void saveOptions(); /** * Creates the interfaces necessary for the plugins to work. Therefore, * this method must be called prior to loadPlugins(). */ void createInterfaces(); /** * load all available plugins. Make sure you have called createInterfaces() * before you call this one. */ void loadPlugins(); /** read general Options again and initialize all variables like the recent file list */ void readOptions(); /** initializes the KActions of the application */ void initActions(); /** initializes the dynamic menus (account selectors) */ void initDynamicMenus(); /** sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** * @brief Establish connections between actions and views * * Must be called after creation of actions and views. */ void connectActionsAndViews(); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calls saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * The settings are saved using saveOptions() if we are about to close. * @see KMainWindow#queryClose * @see QWidget#closeEvent */ virtual bool queryClose(); void slotCheckSchedules(); virtual void resizeEvent(QResizeEvent*); void createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount); /** * This method checks, if an account can be closed or not. An account * can be closed if: * * - the balance is zero and * - all children are already closed and * - there is no unfinished schedule referencing the account * * @param acc reference to MyMoneyAccount object in question * @retval true account can be closed * @retval false account cannot be closed */ KMyMoneyUtils::CanCloseAccountCodeE canCloseAccount(const MyMoneyAccount& acc) const; /** * This method checks if an account can be closed and enables/disables * the close account action * If disabled, it sets a tooltip explaning why it cannot be closed * @brief enableCloseAccountAction * @param acc reference to MyMoneyAccount object in question */ void enableCloseAccountAction(const MyMoneyAccount& acc); /** * Check if a list contains a payee with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool payeeInList(const QList& list, const QString& id) const; /** * Check if a list contains a tag with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool tagInList(const QList& list, const QString& id) const; /** * Mark the selected transactions as provided by @a flag. If * flag is @a MyMoneySplit::Unknown, the future state depends * on the current stat of the split's flag accoring to the * following table: * * - NotReconciled --> Cleared * - Cleared --> Reconciled * - Reconciled --> NotReconciled */ void markTransaction(MyMoneySplit::reconcileFlagE flag); /** * This method allows to skip the next scheduled transaction of * the given schedule @a s. * */ void skipSchedule(MyMoneySchedule& s); /** * This method allows to enter the next scheduled transaction of * the given schedule @a s. In case @a extendedKeys is @a true, * the given schedule can also be skipped or ignored. * If @a autoEnter is @a true and the schedule does not contain * an estimated value, the schedule is entered as is without further * interaction with the user. In all other cases, the user will * be presented a dialog and allowed to adjust the values for this * instance of the schedule. * * The transaction will be created and entered into the ledger * and the schedule updated. */ KMyMoneyUtils::EnterScheduleResultCodeE enterSchedule(MyMoneySchedule& s, bool autoEnter = false, bool extendedKeys = false); /** * Creates a new institution entry in the MyMoneyFile engine * * @param institution MyMoneyInstitution object containing the data of * the institution to be created. */ void createInstitution(MyMoneyInstitution& institution); /** * This method unmatches the currently selected transactions */ void transactionUnmatch(); /** * This method matches the currently selected transactions */ void transactionMatch(); /** * This method preloads the holidays for the duration of the default forecast period */ void preloadHolidays(); public slots: void slotFileInfoDialog(); /** */ void slotFileNew(); /** open a file and load it into the document*/ void slotFileOpen(); /** opens a file from the recent files menu */ void slotFileOpenRecent(const KUrl& url); /** open a SQL database */ void slotOpenDatabase(); /** * saves the current document. If it has no name yet, the user * will be queried for it. * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSave(); /** * ask the user for the filename and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSaveAs(); /** * ask the user to select a database and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool slotSaveAsDatabase(); /** asks for saving if the file is modified, then closes the actual file and window */ void slotFileCloseWindow(); /** asks for saving if the file is modified, then closes the actual file */ void slotFileClose(); /** * closes all open windows by calling close() on each memberList item * until the list is empty, then quits the application. * If queryClose() returns false because the user canceled the * saveModified() dialog, the closing breaks. */ void slotFileQuit(); void slotFileConsistencyCheck(); /** * fires up the price table editor */ void slotPriceDialog(); /** * fires up the currency table editor */ void slotCurrencyDialog(); /** * Toggles the hide reconciled transactions setting */ void slotHideReconciledTransactions(); /** * Toggles the hide unused categories setting */ void slotHideUnusedCategories(); /** * Toggles the show all accounts setting */ void slotShowAllAccounts(); /** * changes the statusbar contents for the standard label permanently, * used to indicate current actions. Returns the previous value for * 'stacked' usage. * * @param text the text that is displayed in the statusbar */ const QString slotStatusMsg(const QString &text); /** * This method changes the progress bar in the status line according * to the parameters @p current and @p total. The following special * cases exist: * * - current = -1 and total = -1 will reset the progress bar * - current = ?? and total != 0 will setup the 100% mark to @p total * - current = xx and total == 0 will set the percentage * * @param current the current value with respect to the initialised * 100% mark * @param total the total value (100%) */ void slotStatusProgressBar(int current, int total = 0); /** * Called to update stock and currency prices from the user menu */ void slotEquityPriceUpdate(); /** * Imports a KMM statement into the engine, triggering the appropriate * UI to handle account matching, payee creation, and someday * payee and transaction matching. */ bool slotStatementImport(const MyMoneyStatement& s); /** * Essentially similar to the above slot, except this will load the file * from disk first, given the URL. */ bool slotStatementImport(const QString& url); /** * This slot starts the reconciliation of the currently selected account */ void slotAccountReconcileStart(); /** * This slot finishes a previously started reconciliation */ void slotAccountReconcileFinish(); /** * This slot postpones a previously started reconciliations */ void slotAccountReconcilePostpone(); /** * This slot deletes the currently selected account if possible */ void slotAccountDelete(); /** * This slot opens the account editor to edit the currently * selected account if possible */ void slotAccountEdit(); /** * This slot opens the selected account in the ledger view */ void slotAccountOpen(const MyMoneyObject& = MyMoneyAccount()); /** * This slot closes the currently selected account if possible */ void slotAccountClose(); /** * This slot re-openes the currently selected account if possible */ void slotAccountReopen(); /** * This slot reparents account @p src to be a child of account @p dest * * @param src account to be reparented * @param dest new parent */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyAccount& dest); /** * This slot reparents account @p src to be a held at institution @p dest * * @param src account to be reparented * @param dest new parent institution */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyInstitution& dest); /** * This slot creates a transaction report for the selected account * and opens it in the reports view. */ void slotAccountTransactionReport(); /** * This slot opens the account options menu at the current cursor * position. */ void slotShowAccountContextMenu(const MyMoneyObject&); /** * This slot opens the schedule options menu at the current cursor * position. */ void slotShowScheduleContextMenu(); /** * This slot opens the institution options menu at the current cursor * position. */ void slotShowInstitutionContextMenu(const MyMoneyObject&); /** * This slot opens the investment options menu at the current cursor * position. */ void slotShowInvestmentContextMenu(); /** * This slot opens the payee options menu at the current cursor * position. */ void slotShowPayeeContextMenu(); /** * This slot opens the tag options menu at the current cursor * position. */ void slotShowTagContextMenu(); /** * This slot opens the budget options menu at the current cursor * position. */ void slotShowBudgetContextMenu(); /** * This slot opens the transaction options menu at the current cursor * position. */ void slotShowTransactionContextMenu(); /** * This slot opens the currency options menu at the current cursor * position. */ void slotShowCurrencyContextMenu(); /** * This slot opens the price options menu at the current cursor * position. */ void slotShowPriceContextMenu(); /** * Open onlineJob options menu at current cursor position. */ void slotShowOnlineJobContextMenu(); /** * This slot collects information for a new scheduled transaction * and saves it in the engine. @sa slotScheduleNew(const MyMoneyTransaction&) */ void slotScheduleNew(); /** * This slot allows to edit information the currently selected schedule */ void slotScheduleEdit(); /** * This slot allows to delete the currently selected schedule */ void slotScheduleDelete(); /** * This slot allows to enter the next scheduled transaction of * the currently selected schedule */ void slotScheduleEnter(); /** * This slot allows to skip the next scheduled transaction of * the currently selected schedule */ void slotScheduleSkip(); /** * This slot fires up the KCalc application */ void slotToolsStartKCalc(); void slotResetSelections(); void slotSelectAccount(const MyMoneyObject& account = MyMoneyAccount()); void slotSelectInstitution(const MyMoneyObject& institution = MyMoneyInstitution()); void slotSelectInvestment(const MyMoneyObject& account = MyMoneyAccount()); void slotSelectSchedule(const MyMoneySchedule& schedule = MyMoneySchedule()); void slotSelectPayees(const QList& list); void slotSelectTags(const QList& list); void slotSelectBudget(const QList& list); void slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list); void slotSelectCurrency(const MyMoneySecurity& currency = MyMoneySecurity()); void slotSelectPrice(const MyMoneyPrice& price = MyMoneyPrice()); void slotTransactionMatch(); /** * Brings up the new account wizard and saves the information. */ void slotAccountNew(); void slotAccountNew(MyMoneyAccount&); /** * Brings up the new category editor and saves the information. */ void slotCategoryNew(); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name and parent account. * * @param account reference of category to be created. The @p name member * should be filled by the caller. The object will be filled * with additional information during the creation process * esp. the @p id member. * @param parent reference to parent account (defaults to none) */ void slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent = MyMoneyAccount()); /** * This method updates all KAction items to the current state. */ void slotUpdateActions(); /** * Brings up the input dialog and saves the information. */ void slotInstitutionNew(); /** * Brings up the input dialog and saves the information. If * the institution has been created, the @a id member is filled, * otherwise it is empty. * * @param institution reference to data to be used to create the * institution. id member will be updated. */ void slotInstitutionNew(MyMoneyInstitution& institution); /** * Called whenever a plugin is plugged in */ void slotPluginPlug(KPluginInfo*); /** * Called whenever a plugin is unplugged */ void slotPluginUnplug(KPluginInfo*); private: /** * Create the transaction move menu and setup necessary connections. */ void createTransactionMoveMenu(); /** * This method sets the holidayRegion for use by the processing calendar. */ void setHolidayRegion(const QString& holidayRegion); /** * Load the status bar with the 'ready' message. This is hold in a single * place, so that is consistent with isReady(). */ void ready(); /** * Check if the status bar contains the 'ready' message. The return * value is used e.g. to detect if a quit operation is allowed or not. * * @retval true application is idle * @retval false application is active working on a longer operation */ bool isReady(); /** * Delete a possibly existing transaction editor but make sure to remove * any reference to it so that we avoid using a half-dead object */ void deleteTransactionEditor(); /** * delete all selected transactions w/o further questions */ void doDeleteTransactions(); /** * Re-implemented from IMyMoneyProcessingCalendar */ bool isProcessingDate(const QDate& date) const; /** * Depending on the setting of AutoSaveOnQuit, this method * asks the user to save the file or not. * * @returns see return values of KMessageBox::warningYesNoCancel() */ int askSaveOnClose(); /** * Implement common task when deleting or merging payees */ bool payeeReassign(int type); signals: /** * This signal is emitted when a new file is loaded. In the case file * is closed, this signal is also emitted with an empty url. */ void fileLoaded(const KUrl& url); /** * This signal is emitted when a payee/list of payees has been selected by * the GUI. If no payee is selected or the selection is removed, * @p payees is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void payeesSelected(const QList& payees); /** * This signal is emitted when a tag/list of tags has been selected by * the GUI. If no tag is selected or the selection is removed, * @p tags is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void tagsSelected(const QList& tags); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is sent out, when the user presses Ctrl+A or activates * the Select all transactions action. */ void selectAllTransactions(); /** * This signal is emitted when a list of budgets has been selected by * the GUI. If no budget is selected or the selection is removed, * @a budget is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void budgetSelected(const QList& budget); void budgetRename(); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); void investmentSelected(const MyMoneyAccount& account); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when a new schedule has been selected by * the GUI. If no schedule is selected or the selection is removed, * @a schedule is identical to MyMoneySchedule(). This signal is used * by plugins to get information about changes. */ void scheduleSelected(const MyMoneySchedule& schedule); /** * This signal is emitted when a new currency has been selected by * the GUI. If no currency is selected or the selection is removed, * @a currency is identical to MyMoneySecurity(). This signal is used * by plugins to get information about changes. */ void currencySelected(const MyMoneySecurity& currency); /** * This signal is emitted when a new price has been selected by * the GUI. If no price is selected or the selection is removed, * @a price is identical to MyMoneyPrice(). */ void priceSelected(const MyMoneyPrice& price); void payeeRename(); void payeeCreated(const QString& id); void tagRename(); void tagCreated(const QString& id); void currencyRename(); void currencyCreated(const QString& id); void priceEdit(); void priceNew(); void priceDelete(); void priceOnlineUpdate(); void startMatchTransaction(const MyMoneyTransaction& t); void cancelMatchTransaction(); /** * This signal is emitted when an account has been successfully reconciled * and all transactions are updated in the engine. It can be used by plugins * to create reconciliation reports. * * @param account the account data * @param date the reconciliation date as provided through the dialog * @param startingBalance the starting balance as provided through the dialog * @param endingBalance the ending balance as provided through the dialog * @param transactionList reference to QList of QPair containing all * transaction/split pairs processed by the reconciliation. */ void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); public: /** * This method is implemented for convenience. It returns a dynamic_cast-ed * pointer to an action found in actionCollection(). * If the action with the name @p actionName is not found or the object * is not of type KToggleAction, a pointer to a static non-configured * KToggleAction object is returned and a warning is printed to stderr. */ KToggleAction* toggleAction(const QString& actionName) const; QPrinter *printer(); private: /// \internal d-pointer class. class Private; /* * Actually, one should write "Private * const d" but that confuses the KIDL * compiler in this context. It complains about the const keyword. So we leave * it out here */ /// \internal d-pointer instance. Private* d; }; extern KMyMoneyApp *kmymoney; class KMStatus { public: KMStatus(const QString &text); ~KMStatus(); private: QString m_prevText; }; #define KMSTATUS(msg) KMStatus _thisStatus(msg) #endif // KMYMONEY_H diff --git a/kmymoney/kmymoneyutils.cpp b/kmymoney/kmymoneyutils.cpp index f8acc3d1f..ab5cbbc97 100644 --- a/kmymoney/kmymoneyutils.cpp +++ b/kmymoney/kmymoneyutils.cpp @@ -1,636 +1,591 @@ /*************************************************************************** kmymoneyutils.cpp - description ------------------- begin : Wed Feb 5 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmymoneyutils.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include KMyMoneyUtils::KMyMoneyUtils() { } KMyMoneyUtils::~KMyMoneyUtils() { } const QString KMyMoneyUtils::accountTypeToString(const MyMoneyAccount::accountTypeE accountType) { return MyMoneyAccount::accountTypeToString(accountType); } MyMoneyAccount::accountTypeE KMyMoneyUtils::stringToAccountType(const QString& type) { MyMoneyAccount::accountTypeE rc = MyMoneyAccount::UnknownAccountType; QString tmp = type.toLower(); if (tmp == i18n("Checking").toLower()) rc = MyMoneyAccount::Checkings; else if (tmp == i18n("Savings").toLower()) rc = MyMoneyAccount::Savings; else if (tmp == i18n("Credit Card").toLower()) rc = MyMoneyAccount::CreditCard; else if (tmp == i18n("Cash").toLower()) rc = MyMoneyAccount::Cash; else if (tmp == i18n("Loan").toLower()) rc = MyMoneyAccount::Loan; else if (tmp == i18n("Certificate of Deposit").toLower()) rc = MyMoneyAccount::CertificateDep; else if (tmp == i18n("Investment").toLower()) rc = MyMoneyAccount::Investment; else if (tmp == i18n("Money Market").toLower()) rc = MyMoneyAccount::MoneyMarket; else if (tmp == i18n("Asset").toLower()) rc = MyMoneyAccount::Asset; else if (tmp == i18n("Liability").toLower()) rc = MyMoneyAccount::Liability; else if (tmp == i18n("Currency").toLower()) rc = MyMoneyAccount::Currency; else if (tmp == i18n("Income").toLower()) rc = MyMoneyAccount::Income; else if (tmp == i18n("Expense").toLower()) rc = MyMoneyAccount::Expense; else if (tmp == i18n("Investment Loan").toLower()) rc = MyMoneyAccount::AssetLoan; else if (tmp == i18n("Stock").toLower()) rc = MyMoneyAccount::Stock; else if (tmp == i18n("Equity").toLower()) rc = MyMoneyAccount::Equity; return rc; } MyMoneySecurity::eSECURITYTYPE KMyMoneyUtils::stringToSecurity(const QString& txt) { MyMoneySecurity::eSECURITYTYPE rc = MyMoneySecurity::SECURITY_NONE; QString tmp = txt.toLower(); if (tmp == i18n("Stock").toLower()) rc = MyMoneySecurity::SECURITY_STOCK; else if (tmp == i18n("Mutual Fund").toLower()) rc = MyMoneySecurity::SECURITY_MUTUALFUND; else if (tmp == i18n("Bond").toLower()) rc = MyMoneySecurity::SECURITY_BOND; else if (tmp == i18n("Currency").toLower()) rc = MyMoneySecurity::SECURITY_CURRENCY; return rc; } const QString KMyMoneyUtils::securityTypeToString(const MyMoneySecurity::eSECURITYTYPE securityType) { return i18n(MyMoneySecurity::securityTypeToString(securityType).toLatin1()); } const QString KMyMoneyUtils::occurrenceToString(const MyMoneySchedule::occurrenceE occurrence) { return i18nc("Frequency of schedule", MyMoneySchedule::occurrenceToString(occurrence).toLatin1()); } const QString KMyMoneyUtils::paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType) { return i18nc("Scheduled Transaction payment type", MyMoneySchedule::paymentMethodToString(paymentType).toLatin1()); } const QString KMyMoneyUtils::weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption) { return i18n(MyMoneySchedule::weekendOptionToString(weekendOption).toLatin1()); } const QString KMyMoneyUtils::scheduleTypeToString(MyMoneySchedule::typeE type) { return i18nc("Scheduled transaction type", MyMoneySchedule::scheduleTypeToString(type).toLatin1()); } KGuiItem KMyMoneyUtils::scheduleNewGuiItem() { KGuiItem splitGuiItem(i18n("&New Schedule..."), KIcon("document-new"), i18n("Create a new schedule."), i18n("Use this to create a new schedule.")); return splitGuiItem; } KGuiItem KMyMoneyUtils::accountsFilterGuiItem() { KGuiItem splitGuiItem(i18n("&Filter"), KIcon("view-filter"), i18n("Filter out accounts"), i18n("Use this to filter out accounts")); return splitGuiItem; } const char* homePageItems[] = { I18N_NOOP("Payments"), I18N_NOOP("Preferred accounts"), I18N_NOOP("Payment accounts"), I18N_NOOP("Favorite reports"), I18N_NOOP("Forecast (schedule)"), I18N_NOOP("Net worth forecast"), I18N_NOOP("Forecast (history)"), I18N_NOOP("Assets and Liabilities"), I18N_NOOP("Budget"), I18N_NOOP("CashFlow"), // insert new items above this comment 0 }; const QString KMyMoneyUtils::homePageItemToString(const int idx) { QString rc; if (abs(idx) > 0 && abs(idx) < static_cast(sizeof(homePageItems) / sizeof(homePageItems[0]))) { rc = i18n(homePageItems[abs(idx-1)]); } return rc; } int KMyMoneyUtils::stringToHomePageItem(const QString& txt) { int idx = 0; for (idx = 0; homePageItems[idx] != 0; ++idx) { if (txt == i18n(homePageItems[idx])) return idx + 1; } return 0; } bool KMyMoneyUtils::appendCorrectFileExt(QString& str, const QString& strExtToUse) { bool rc = false; if (!str.isEmpty()) { //find last . delminator int nLoc = str.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = str.left(nLoc + 1); strExt = str.right(str.length() - (nLoc + 1)); if (strExt.indexOf(strExtToUse, 0, Qt::CaseInsensitive) == -1) { // if the extension given contains a period, we remove our's if (strExtToUse.indexOf('.') != -1) strTemp = strTemp.left(strTemp.length() - 1); //append extension to make complete file name strTemp.append(strExtToUse); str = strTemp; rc = true; } } else { str.append("."); str.append(strExtToUse); rc = true; } } return rc; } void KMyMoneyUtils::checkConstants() { Q_ASSERT(static_cast(KLocale::ParensAround) == static_cast(MyMoneyMoney::ParensAround)); Q_ASSERT(static_cast(KLocale::BeforeQuantityMoney) == static_cast(MyMoneyMoney::BeforeQuantityMoney)); Q_ASSERT(static_cast(KLocale::AfterQuantityMoney) == static_cast(MyMoneyMoney::AfterQuantityMoney)); Q_ASSERT(static_cast(KLocale::BeforeMoney) == static_cast(MyMoneyMoney::BeforeMoney)); Q_ASSERT(static_cast(KLocale::AfterMoney) == static_cast(MyMoneyMoney::AfterMoney)); } QString KMyMoneyUtils::variableCSS() { QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color(); QColor link = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color(); QString css; css += "\n"; return css; } QString KMyMoneyUtils::findResource(const char* type, const QString& filename) { QString language = KGlobal::locale()->language(); QString country = KGlobal::locale()->country(); QString rc, mask; // check that the placeholder is present if (!filename.indexOf("%1")) { qWarning("%%1 not found in '%s'", qPrintable(filename)); return filename; } // search the given resource mask = filename.arg("_%1.%2"); rc = KGlobal::dirs()->findResource(type, mask.arg(country).arg(language)); if (rc.isEmpty()) { mask = filename.arg("_%1"); rc = KGlobal::dirs()->findResource(type, mask.arg(language)); } if (rc.isEmpty()) { // qDebug(QString("html/home_%1.html not found").arg(country).toLatin1()); rc = KGlobal::dirs()->findResource(type, mask.arg(country)); } if (rc.isEmpty()) { rc = KGlobal::dirs()->findResource(type, filename.arg("")); } if (rc.isEmpty()) { qWarning("No resource found for (%s,%s)", type, qPrintable(filename)); } return rc; } const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t) { QList::ConstIterator it_s; MyMoneySplit investmentAccountSplit; for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if (!(*it_s).accountId().isEmpty()) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (acc.isInvest()) { return *it_s; } // if we have a reference to an investment account, we remember it here if (acc.accountType() == MyMoneyAccount::Investment) investmentAccountSplit = *it_s; } } // if we haven't found a stock split, we see if we've seen // an investment account on the way. If so, we return it. if (!investmentAccountSplit.id().isEmpty()) return investmentAccountSplit; // if none was found, we return an empty split. return MyMoneySplit(); } KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t) { if (!stockSplit(t).id().isEmpty()) return InvestmentTransaction; if (t.splitCount() < 2) { return Unknown; } else if (t.splitCount() > 2) { // FIXME check for loan transaction here return SplitTransaction; } QString ida, idb; if (t.splits().size() > 0) ida = t.splits()[0].accountId(); if (t.splits().size() > 1) idb = t.splits()[1].accountId(); if (ida.isEmpty() || idb.isEmpty()) return Unknown; MyMoneyAccount a, b; a = MyMoneyFile::instance()->account(ida); b = MyMoneyFile::instance()->account(idb); if ((a.accountGroup() == MyMoneyAccount::Asset || a.accountGroup() == MyMoneyAccount::Liability) && (b.accountGroup() == MyMoneyAccount::Asset || b.accountGroup() == MyMoneyAccount::Liability)) return Transfer; return Normal; } void KMyMoneyUtils::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap& balances) { try { MyMoneyForecast::calculateAutoLoan(schedule, transaction, balances); } catch (const MyMoneyException &e) { KMessageBox::detailedError(nullptr, i18n("Unable to load schedule details"), e.what()); } } -QString KMyMoneyUtils::nextCheckNumber(const MyMoneyAccount& acc) +QString KMyMoneyUtils::nextCheckNumber(const QString &num) { QString number; // +-#1--+ +#2++-#3-++-#4--+ QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?")); - if (exp.indexIn(acc.value("lastNumberUsed")) != -1) { - setLastNumberUsed(acc.value("lastNumberUsed")); + if (exp.indexIn(num) != -1) { QString arg1 = exp.cap(1); QString arg2 = exp.cap(2); QString arg3 = QString::number(exp.cap(3).toULong() + 1); QString arg4 = exp.cap(4); number = QString("%1%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4); // if new number is longer than previous one and we identified // preceding 0s, then remove one of the preceding zeros - if (arg2.length() > 0 && (number.length() != acc.value("lastNumberUsed").length())) { + if (arg2.length() > 0 && (number.length() != num.length())) { arg2 = arg2.mid(1); number = QString("%1%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4); } } else { number = '1'; } return number; } -QString KMyMoneyUtils::nextStatementNumber(const MyMoneyAccount& acc) +QString KMyMoneyUtils::nextStatementNumber(const QString &num) { QString number; // 1.1 // 4.5 // 4/2018.1 // 1,1 // 4,5 // 4/2018,1 QRegExp exp(QString("(\\d+)/(\\d+)([.,])(\\d+)")); QRegExp exp2(QString("(\\d+)([.,])(\\d+)")); - if (exp.indexIn(acc.value("lastNumberUsed")) != -1) { - setLastNumberUsed(acc.value("lastNumberUsed")); + if (exp.indexIn(num) != -1) { QString arg1 = QString::number(exp.cap(1).toULong() + 1); QString arg2 = exp.cap(2); QString arg3 = exp.cap(3); QString arg4 = exp.cap(4); number = QString("%1/%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4); - } else if (exp2.indexIn(acc.value("lastNumberUsed")) != -1) { - setLastNumberUsed(acc.value("lastNumberUsed")); + } else if (exp2.indexIn(num) != -1) { QString arg1 = QString::number(exp2.cap(1).toULong() + 1); QString arg2 = exp2.cap(2); QString arg3 = exp2.cap(3); number = QString("%1%2%3").arg(arg1).arg(arg2).arg(arg3); } else { number = "1.1"; } return number; } -QString KMyMoneyUtils::nextStatementPageNumber(const MyMoneyAccount &acc) +QString KMyMoneyUtils::nextStatementPageNumber(const QString &num) { QString number; // 1.1 // 4.5 // 4/2018.1 // 1,1 // 4,5 // 4/2018,1 QRegExp exp(QString("(\\d+)/(\\d+)([.,])(\\d+)")); QRegExp exp2(QString("^(\\d+)([.,])(\\d+)$")); - if (exp.indexIn(acc.value("lastNumberUsed")) != -1) { - setLastNumberUsed(acc.value("lastNumberUsed")); + if (exp.indexIn(num) != -1) { QString arg1 = exp.cap(1); QString arg2 = exp.cap(2); QString arg3 = exp.cap(3); QString arg4 = QString::number(exp.cap(4).toULong() + 1); number = QString("%1/%2%3%4").arg(arg1).arg(arg2).arg(arg4); - } else if (exp2.indexIn(acc.value("lastNumberUsed")) != -1) { - setLastNumberUsed(acc.value("lastNumberUsed")); + } else if (exp2.indexIn(num) != -1) { QString arg1 = exp2.cap(1); QString arg2 = exp2.cap(2); QString arg3 = QString::number(exp2.cap(3).toULong() + 1); number = QString("%1%2%3").arg(arg1).arg(arg2).arg(arg3); } else { number = "1.1"; } return number; } -void KMyMoneyUtils::updateLastNumberUsed(const MyMoneyAccount& acc, const QString& number) -{ - MyMoneyAccount accnt = acc; - QString num = number; - // now check if this number has been used already - MyMoneyFile* file = MyMoneyFile::instance(); - if (file->checkNoUsed(accnt.id(), num)) { - // if a number has been entered which is immediately prior to - // an existing number, the next new number produced would clash - // so need to look ahead for free next number - bool free = false; - for (int i = 0; i < 10; i++) { - // find next unused number - 10 tries (arbitrary) - if (file->checkNoUsed(accnt.id(), num)) { - // increment and try again - num = getAdjacentNumber(num); - } else { - // found a free number - free = true; - break; - } - } - if (!free) { - qDebug() << "No free number found - set to '1'"; - num = '1'; - } - setLastNumberUsed(getAdjacentNumber(num, - 1)); - } -} - -void KMyMoneyUtils::setLastNumberUsed(const QString& num) -{ - m_lastNumberUsed = num; -} - -QString KMyMoneyUtils::lastNumberUsed() -{ - return m_lastNumberUsed; -} - QString KMyMoneyUtils::getAdjacentNumber(const QString& number, int offset) { QString num = number; // +-#1--+ +#2++-#3-++-#4--+ QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?")); if (exp.indexIn(num) != -1) { QString arg1 = exp.cap(1); QString arg2 = exp.cap(2); QString arg3 = QString::number(exp.cap(3).toULong() + offset); QString arg4 = exp.cap(4); num = QString("%1%2%3%4").arg(arg1).arg(arg2).arg(arg3).arg(arg4); } else { num = '1'; } // next free number return num; } quint64 KMyMoneyUtils::numericPart(const QString & num) { quint64 num64 = 0; QRegExp exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?")); if (exp.indexIn(num) != -1) { QString arg1 = exp.cap(1); QString arg2 = exp.cap(2); QString arg3 = QString::number(exp.cap(3).toULongLong()); QString arg4 = exp.cap(4); num64 = QString("%2%3").arg(arg2).arg(arg3).toULongLong(); } return num64; } QString KMyMoneyUtils::reconcileStateToString(MyMoneySplit::reconcileFlagE flag, bool text) { QString txt; if (text) { switch (flag) { case MyMoneySplit::NotReconciled: txt = i18nc("Reconciliation state 'Not reconciled'", "Not reconciled"); break; case MyMoneySplit::Cleared: txt = i18nc("Reconciliation state 'Cleared'", "Cleared"); break; case MyMoneySplit::Reconciled: txt = i18nc("Reconciliation state 'Reconciled'", "Reconciled"); break; case MyMoneySplit::Frozen: txt = i18nc("Reconciliation state 'Frozen'", "Frozen"); break; default: txt = i18nc("Unknown reconciliation state", "Unknown"); break; } } else { switch (flag) { case MyMoneySplit::NotReconciled: break; case MyMoneySplit::Cleared: txt = i18nc("Reconciliation flag C", "C"); break; case MyMoneySplit::Reconciled: txt = i18nc("Reconciliation flag R", "R"); break; case MyMoneySplit::Frozen: txt = i18nc("Reconciliation flag F", "F"); break; default: txt = i18nc("Flag for unknown reconciliation state", "?"); break; } } return txt; } MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule) { MyMoneyTransaction t = schedule.transaction(); try { if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) { calculateAutoLoan(schedule, t, QMap()); } } catch (const MyMoneyException &e) { qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), qPrintable(e.what())); } t.clearId(); t.setEntryDate(QDate()); return t; } KXmlGuiWindow* KMyMoneyUtils::mainWindow() { foreach (QWidget *widget, QApplication::topLevelWidgets()) { KXmlGuiWindow* result = dynamic_cast(widget); if (result) return result; } return 0; } void KMyMoneyUtils::updateWizardButtons(QWizard* wizard) { // setup text on buttons wizard->setButtonText(QWizard::NextButton, i18nc("Go to next page of the wizard", "&Next")); wizard->setButtonText(QWizard::BackButton, KStandardGuiItem::back().text()); // setup icons wizard->button(QWizard::FinishButton)->setIcon(KStandardGuiItem::ok().icon()); wizard->button(QWizard::CancelButton)->setIcon(KStandardGuiItem::cancel().icon()); wizard->button(QWizard::NextButton)->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon()); wizard->button(QWizard::BackButton)->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon()); } QPixmap KMyMoneyUtils::overlayIcon(const QString icon, const QString overlay, const Qt::Corner corner, int size) { int x, y; QPixmap result; QString overlaidIcon = icon + '-' + overlay; // If found in the cache, return quickly if (QPixmapCache::find(overlaidIcon, result)) { return result; } // try to retrieve the main icon from cache if (!QPixmapCache::find(icon, result)) { result = DesktopIcon(icon, size); QPixmapCache::insert(icon, result); } QPainter pixmapPainter(&result); QPixmap ovly = DesktopIcon(overlay, size); switch (corner) { case Qt::TopLeftCorner: x = 0; y = 0; break; case Qt::TopRightCorner: x = ovly.width() / 2; y = 0; break; case Qt::BottomLeftCorner: x = 0; y = ovly.height() / 2; break; case Qt::BottomRightCorner: default: x = ovly.width() / 2; y = ovly.height() / 2; break; } pixmapPainter.drawPixmap(x, y, ovly.width() / 2, ovly.height() / 2, ovly); //save for later use QPixmapCache::insert(overlaidIcon, result); return result; } diff --git a/kmymoney/kmymoneyutils.h b/kmymoney/kmymoneyutils.h index 59ccea35e..859cd439a 100644 --- a/kmymoney/kmymoneyutils.h +++ b/kmymoney/kmymoneyutils.h @@ -1,436 +1,429 @@ /*************************************************************************** kmymoneyutils.h - description ------------------- begin : Wed Feb 5 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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 KMYMONEYUTILS_H #define KMYMONEYUTILS_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include "mymoneyschedule.h" #include /** * @author Thomas Baumgart */ -static QString m_lastNumberUsed; class QWizard; class KMyMoneyUtils { public: /** * This enum is used to describe the bits of an account type filter mask. * Each bit is used to define a specific account class. Multiple classes * can be specified by OR'ing multiple entries. The special entry @p last * marks the left most bit in the mask and is used by scanners of this * bitmask to determine the end of processing. */ enum categoryTypeE { none = 0x000, ///< no account class selected liability = 0x001, ///< liability accounts selected asset = 0x002, ///< asset accounts selected expense = 0x004, ///< expense accounts selected income = 0x008, ///< income accounts selected equity = 0x010, ///< equity accounts selected checking = 0x020, ///< checking accounts selected savings = 0x040, ///< savings accounts selected investment = 0x080, ///< investment accounts selected creditCard = 0x100, ///< credit card accounts selected last = 0x200 ///< the leftmost bit in the mask }; enum transactionTypeE { /** * Unknown transaction type (e.g. used for a transaction with only * a single split) */ Unknown, /** * A 'normal' transaction is one that consists out two splits: one * referencing an income/expense account, the other referencing * an asset/liability account. */ Normal, /** * A transfer denotes a transaction consisting of two splits. * Both of the splits reference an asset/liability * account. */ Transfer, /** * Whenever a transaction consists of more than 2 splits, * it is treated as 'split transaction'. */ SplitTransaction, /** * This transaction denotes a specific transaction where * a loan account is involved. Usually, a special dialog * is used to modify this transaction. */ LoanPayment, /** * This transaction denotes a specific transaction where * an investment is involved. Usually, a special dialog * is used to modify this transaction. */ InvestmentTransaction }; enum EnterScheduleResultCodeE { Cancel = 0, // cancel the operation Enter, // enter the schedule Skip, // skip the schedule Ignore // ignore the schedule }; enum CanCloseAccountCodeE { AccountCanClose = 0, // can close the account AccountBalanceNonZero, // balance is non zero AccountChildrenOpen, // account has open children account AccountScheduleReference // account is referenced in a schedule }; static const int maxHomePageItems = 5; KMyMoneyUtils(); ~KMyMoneyUtils(); /** * This method is used to convert the internal representation of * an account type into a human readable format * * @param accountType numerical representation of the account type. * For possible values, see MyMoneyAccount::accountTypeE * @return QString representing the human readable form translated according to the language cataglogue * * @sa MyMoneyAccount::accountTypeToString() */ static const QString accountTypeToString(const MyMoneyAccount::accountTypeE accountType); /** * This method is used to convert an account type from its * string form to the internal used numeric value. * * @param type reference to a QString containing the string to convert * @return accountTypeE containing the internal used numeric value. For possible * values see MyMoneyAccount::accountTypeE */ static MyMoneyAccount::accountTypeE stringToAccountType(const QString& type); /** * This method is used to convert a security type from its * string form to the internal used numeric value. * * @param txt reference to a QString containing the string to convert * @return eSECURITYTYPE containing the internal used numeric value. For possible * values see MyMoneySecurity::eSECURITYTYPE */ static MyMoneySecurity::eSECURITYTYPE stringToSecurity(const QString& txt); /** * This method is used to convert the internal representation of * an security type into a human readable format * * @param securityType enumerated representation of the security type. * For possible values, see MyMoneySecurity::eSECURITYTYPE * @return QString representing the human readable form translated according to the language cataglogue * * @sa MyMoneySecurity::securityTypeToString() */ static const QString securityTypeToString(const MyMoneySecurity::eSECURITYTYPE securityType); /** * This method is used to convert the occurrence type from its * internal representation into a human readable format. * * @param occurrence numerical representation of the MyMoneySchedule * occurrence type * * @return QString representing the human readable format translated according to the language cataglogue * * @sa MyMoneySchedule::occurrenceToString() * * @deprecated Use i18n(MyMoneySchedule::occurrenceToString(occurrence)) instead */ static const QString occurrenceToString(const MyMoneySchedule::occurrenceE occurrence); /** * This method is used to convert the payment type from its * internal representation into a human readable format. * * @param paymentType numerical representation of the MyMoneySchedule * payment type * * @return QString representing the human readable format translated according to the language cataglogue * * @sa MyMoneySchedule::paymentMethodToString() */ static const QString paymentMethodToString(MyMoneySchedule::paymentTypeE paymentType); /** * This method is used to convert the schedule weekend option from its * internal representation into a human readable format. * * @param weekendOption numerical representation of the MyMoneySchedule * weekend option * * @return QString representing the human readable format translated according to the language cataglogue * * @sa MyMoneySchedule::weekendOptionToString() */ static const QString weekendOptionToString(MyMoneySchedule::weekendOptionE weekendOption); /** * This method is used to convert the schedule type from its * internal representation into a human readable format. * * @param type numerical representation of the MyMoneySchedule * schedule type * * @return QString representing the human readable format translated according to the language cataglogue * * @sa MyMoneySchedule::scheduleTypeToString() */ static const QString scheduleTypeToString(MyMoneySchedule::typeE type); /** * This method is used to convert a numeric index of an item * represented on the home page into its string form. * * @param idx numeric index of item * * @return QString with text of this item */ static const QString homePageItemToString(const int idx); /** * This method is used to convert the name of a home page item * to its internal numerical representation * * @param txt QString reference of the items name * * @retval 0 @p txt is unknown * @retval >0 numeric value for @p txt */ static int stringToHomePageItem(const QString& txt); /** * Retrieve a KDE KGuiItem for the new schedule button. * * @return The KGuiItem that can be used to display the icon and text */ static KGuiItem scheduleNewGuiItem(); /** * Retrieve a KDE KGuiItem for the account filter button * * @return The KGuiItem that can be used to display the icon and text */ static KGuiItem accountsFilterGuiItem(); /** * This method adds the file extension passed as argument @p extension * to the end of the file name passed as argument @p name if it is not present. * If @p name contains an extension it will be removed. * * @param name filename to be checked * @param extension extension to be added (w/o the dot) * * @retval true if @p name was changed * @retval false if @p name remained unchanged */ static bool appendCorrectFileExt(QString& name, const QString& extension); /** * Check that internal MyMoney engine constants use the same * values as the KDE constants. */ static void checkConstants(); static QString variableCSS(); /** * This method searches a KDE specific resource and applies country and * language settings during the search. Therefore, the parameter @p filename must contain * the characters '%1' which gets replaced with the language/country values. * * The search is performed in the following order (stopped immediately if a file was found): * - @c \%1 is replaced with _\.\ * - @c \%1 is replaced with _\ * - @c \%1 is replaced with _\ * - @c \%1 is replaced with the empty string * * @c \ and @c \ denote the respective KDE settings. * * Example: The KDE settings for country is Spain (es) and language is set * to Galician (gl). The code for looking up a file looks like this: * * @code * * : * QString fname = KMyMoneyUtils::findResource("appdata", "html/home%1.html") * : * * @endcode * * The method calls KStandardDirs::findResource() with the following values for the * parameter @p filename: * * - html/home_es.gl.html * - html/home_gl.html * - html/home_es.html * - html/home.html * * @note See KStandardDirs::findResource() for details on the parameters */ static QString findResource(const char* type, const QString& filename); /** * This method returns the split referencing a stock account if * one exists in the transaction passed as @p t. If none is present * in @p t, an empty MyMoneySplit() object will be returned. * * @param t transaction to be checked for a stock account * @return MyMoneySplit object referencing a stock account or an * empty MyMoneySplit object. */ static const MyMoneySplit stockSplit(const MyMoneyTransaction& t); /** * This method analyses the splits of a transaction and returns * the type of transaction. Possible values are defined by the * KMyMoneyUtils::transactionTypeE enum. * * @param t const reference to the transaction * * @return KMyMoneyUtils::transactionTypeE value of the action */ static transactionTypeE transactionType(const MyMoneyTransaction& t); /** * This method modifies a scheduled loan transaction such that all * references to automatic calculated values are resolved to actual values. * * @param schedule const reference to the schedule the transaction is based on * @param transaction reference to the transaction to be checked and modified * @param balances QMap of (account-id,balance) pairs to be used as current balance * for the calculation of interest. If map is empty, the engine * will be interrogated for current balances. */ static void calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap& balances); /** * Return next check number for account @a acc. */ - static QString nextCheckNumber(const MyMoneyAccount& acc); + static QString nextCheckNumber(const QString &num); /** * Return next bank statement number for account @a acc. */ - static QString nextStatementNumber(const MyMoneyAccount &acc); + static QString nextStatementNumber(const QString &num); /** * Return next bank statement page number for account @a acc. */ - static QString nextStatementPageNumber(const MyMoneyAccount &acc); - - static void updateLastNumberUsed(const MyMoneyAccount& acc, const QString& number); - - static void setLastNumberUsed(const QString& num); - - static QString lastNumberUsed(); + static QString nextStatementPageNumber(const QString &num); /** * Returns previous number if offset is -1 or * the following number if offset is 1. */ static QString getAdjacentNumber(const QString& number, int offset = 1); /** * remove any non-numeric characters from check number * to allow validity check */ static quint64 numericPart(const QString & num); /** * Returns the text representing the reconcile flag. If @a text is @p true * then the full text will be returned otherwise a short form (usually one character). */ static QString reconcileStateToString(MyMoneySplit::reconcileFlagE flag, bool text = false); /** * Returns the transaction for @a schedule. In case of a loan payment the * transaction will be modified by calculateAutoLoan(). * The ID of the transaction as well as the entryDate will be reset. * * @returns adjusted transaction */ static MyMoneyTransaction scheduledTransaction(const MyMoneySchedule& schedule); /** * This method replaces the deprecated QApplication::mainWidget() from Qt 3.x. * It assumes that there is only one KXmlGuiWindow in the application, and * returns it. * * @return the first KXmlGuiWindow found in QApplication::topLevelWidgets() */ static KXmlGuiWindow* mainWindow(); /** * This method sets the button text and icons to the KDE standard ones * for the QWizard passed as argument. */ static void updateWizardButtons(QWizard *); /** * This method overlays an icon over another one, to get a composite one * eg. an icon to add accounts */ static QPixmap overlayIcon(const QString source, const QString overlay, const Qt::Corner corner = Qt::BottomRightCorner, int size = 0); }; #endif diff --git a/kmymoney/views/kgloballedgerview.cpp b/kmymoney/views/kgloballedgerview.cpp index 386551781..5aa4832df 100644 --- a/kmymoney/views/kgloballedgerview.cpp +++ b/kmymoney/views/kgloballedgerview.cpp @@ -1,1609 +1,1609 @@ /*************************************************************************** kgloballedgerview.cpp - description ------------------- begin : Wed Jul 26 2006 copyright : (C) 2006 by Thomas Baumgart email : 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 "kgloballedgerview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "kmymoneyaccountcombo.h" #include "kmymoneytitlelabel.h" #include "register.h" #include "transactioneditor.h" #include "selectedtransaction.h" #include "kmymoneyglobalsettings.h" #include "registersearchline.h" #include "kfindtransactiondlg.h" #include "kmymoney.h" #include "scheduledtransaction.h" #include "models.h" class KGlobalLedgerView::Private { public: Private(); // used to store the id of an item and the id of an immeadiate unselected sibling void storeId(KMyMoneyRegister::RegisterItem *item, QString &id, QString &backupId) { if (item) { // the id of the item id = item->id(); // the id of the item's previous/next unselected item for (KMyMoneyRegister::RegisterItem *it = item->prevItem(); it != 0 && backupId.isEmpty(); it = it->prevItem()) { if (!it->isSelected()) { backupId = it->id(); } } // if we didn't found previous unselected items search trough the next items for (KMyMoneyRegister::RegisterItem *it = item->nextItem(); it != 0 && backupId.isEmpty(); it = it->nextItem()) { if (!it->isSelected()) { backupId = it->id(); } } } } // use to match an item by it's id or a backup id which has a lower precedence void matchItemById(KMyMoneyRegister::RegisterItem **item, KMyMoneyRegister::Transaction* t, QString &id, QString &backupId) { if (!backupId.isEmpty() && t->id() == backupId) *item = t; if (!id.isEmpty() && t->id() == id) { // we found the real thing there's no need for the backup anymore backupId.clear(); *item = t; } } MousePressFilter* m_mousePressFilter; KMyMoneyRegister::RegisterSearchLineWidget* m_registerSearchLine; QString m_reconciliationAccount; QDate m_reconciliationDate; MyMoneyMoney m_endingBalance; int m_precision; bool m_recursion; bool m_showDetails; KMyMoneyRegister::Action m_action; // models AccountNamesFilterProxyModel *m_filterProxyModel; // widgets KMyMoneyAccountCombo* m_accountComboBox; MyMoneyMoney m_totalBalance; bool m_balanceIsApproximated; }; MousePressFilter::MousePressFilter(QWidget* parent) : QObject(parent), m_lastMousePressEvent(0), m_filterActive(true) { } void MousePressFilter::addWidget(QWidget* w) { m_parents.append(w); } void MousePressFilter::setFilterActive(bool state) { m_filterActive = state; } bool MousePressFilter::isChildOf(QWidget* child, QWidget *parent) { while (child) { if (child == parent) return true; // If one of the ancestors is a KPassivePopup or a KDialog or a popup widget then // it's as if it is a child of our own because these widgets could // appear during transaction entry (message boxes, completer widgets) if (dynamic_cast(child) || dynamic_cast(child) || ((child->windowFlags() & Qt::Popup) && child != kmymoney)) return true; child = child->parentWidget(); } return false; } bool MousePressFilter::eventFilter(QObject* o, QEvent* e) { if (m_filterActive) { if (e->type() == QEvent::MouseButtonPress && !m_lastMousePressEvent) { QList::const_iterator it_w; for (it_w = m_parents.constBegin(); it_w != m_parents.constEnd(); ++it_w) { if (isChildOf((QWidget*)o, (*it_w))) { m_lastMousePressEvent = e; break; } } if (it_w == m_parents.constEnd()) { m_lastMousePressEvent = e; bool rc = true; emit mousePressedOnExternalWidget(rc); qDebug() << rc; return !rc; } } if (e->type() != QEvent::MouseButtonPress) { m_lastMousePressEvent = 0; } } return false; } KGlobalLedgerView::Private::Private() : m_mousePressFilter(0), m_registerSearchLine(0), m_recursion(false), m_showDetails(false), m_filterProxyModel(0), m_accountComboBox(0), m_balanceIsApproximated(false) { } QDate KGlobalLedgerView::m_lastPostDate; KGlobalLedgerView::KGlobalLedgerView(QWidget *parent, const char *name) : KMyMoneyViewBase(parent, name, i18n("Ledgers")), d(new Private), m_needReload(false), m_newAccountLoaded(true), m_inEditMode(false) { d->m_mousePressFilter = new MousePressFilter((QWidget*)this); d->m_action = KMyMoneyRegister::ActionNone; // the proxy filter model d->m_filterProxyModel = new AccountNamesFilterProxyModel(this); d->m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); d->m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); d->m_filterProxyModel->addAccountGroup(MyMoneyAccount::Equity); d->m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); d->m_filterProxyModel->sort(0); // create the toolbar frame at the top of the view m_toolbarFrame = new QFrame(this); QHBoxLayout* toolbarLayout = new QHBoxLayout(m_toolbarFrame); toolbarLayout->setContentsMargins(0, 0, 0, 0); toolbarLayout->setSpacing(6); // the account selector widget d->m_accountComboBox = new KMyMoneyAccountCombo(d->m_filterProxyModel, m_toolbarFrame); toolbarLayout->addWidget(d->m_accountComboBox); layout()->addWidget(m_toolbarFrame); toolbarLayout->setStretchFactor(d->m_accountComboBox, 60); // create the register frame m_registerFrame = new QFrame(this); QVBoxLayout* registerFrameLayout = new QVBoxLayout(m_registerFrame); registerFrameLayout->setContentsMargins(0, 0, 0, 0); registerFrameLayout->setSpacing(0); layout()->addWidget(m_registerFrame); layout()->setStretchFactor(m_registerFrame, 2); m_register = new KMyMoneyRegister::Register(m_registerFrame); m_register->setUsedWithEditor(true); registerFrameLayout->addWidget(m_register); m_register->installEventFilter(this); connect(m_register, SIGNAL(openContextMenu()), this, SIGNAL(openContextMenu())); connect(m_register, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateSummaryLine(KMyMoneyRegister::SelectedTransactions))); connect(m_register->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotSortOptions())); connect(m_register, SIGNAL(reconcileStateColumnClicked(KMyMoneyRegister::Transaction*)), this, SLOT(slotToggleTransactionMark(KMyMoneyRegister::Transaction*))); // insert search line widget d->m_registerSearchLine = new KMyMoneyRegister::RegisterSearchLineWidget(m_register, m_toolbarFrame); toolbarLayout->addWidget(d->m_registerSearchLine); toolbarLayout->setStretchFactor(d->m_registerSearchLine, 100); // create the summary frame m_summaryFrame = new QFrame(this); QHBoxLayout* summaryFrameLayout = new QHBoxLayout(m_summaryFrame); summaryFrameLayout->setContentsMargins(0, 0, 0, 0); summaryFrameLayout->setSpacing(0); m_leftSummaryLabel = new QLabel(m_summaryFrame); m_centerSummaryLabel = new QLabel(m_summaryFrame); m_rightSummaryLabel = new QLabel(m_summaryFrame); summaryFrameLayout->addWidget(m_leftSummaryLabel); QSpacerItem* spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); summaryFrameLayout->addItem(spacer); summaryFrameLayout->addWidget(m_centerSummaryLabel); spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); summaryFrameLayout->addItem(spacer); summaryFrameLayout->addWidget(m_rightSummaryLabel); layout()->addWidget(m_summaryFrame); // create the button frame m_buttonFrame = new QFrame(this); QVBoxLayout* buttonLayout = new QVBoxLayout(m_buttonFrame); buttonLayout->setContentsMargins(0, 0, 0, 0); buttonLayout->setSpacing(0); layout()->addWidget(m_buttonFrame); m_buttonbar = new KToolBar(m_buttonFrame, 0, true); m_buttonbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); buttonLayout->addWidget(m_buttonbar); m_buttonbar->addAction(kmymoney->action("transaction_new")); m_buttonbar->addAction(kmymoney->action("transaction_edit")); m_buttonbar->addAction(kmymoney->action("transaction_delete")); m_buttonbar->addAction(kmymoney->action("transaction_enter")); m_buttonbar->addAction(kmymoney->action("transaction_cancel")); m_buttonbar->addAction(kmymoney->action("transaction_accept")); m_buttonbar->addAction(kmymoney->action("transaction_match")); m_buttonbar->addAction(kmymoney->action("schedule_new")); m_buttonbar->addAction(kmymoney->action("schedule_edit")); m_buttonbar->addAction(kmymoney->action("schedule_delete")); m_buttonbar->addAction(kmymoney->action("schedule_duplicate")); m_buttonbar->addAction(kmymoney->action("schedule_enter")); m_buttonbar->addAction(kmymoney->action("schedule_skip")); // create the transaction form frame m_formFrame = new QFrame(this); QVBoxLayout* frameLayout = new QVBoxLayout(m_formFrame); frameLayout->setContentsMargins(5, 5, 5, 5); frameLayout->setSpacing(0); m_form = new KMyMoneyTransactionForm::TransactionForm(m_formFrame); frameLayout->addWidget(m_form->tabBar(m_formFrame)); frameLayout->addWidget(m_form); m_formFrame->setFrameShape(QFrame::Panel); m_formFrame->setFrameShadow(QFrame::Raised); layout()->addWidget(m_formFrame); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); connect(m_register, SIGNAL(focusChanged(KMyMoneyRegister::Transaction*)), m_form, SLOT(slotSetTransaction(KMyMoneyRegister::Transaction*))); connect(m_register, SIGNAL(focusChanged()), kmymoney, SLOT(slotUpdateActions())); connect(d->m_accountComboBox, SIGNAL(accountSelected(QString)), this, SLOT(slotSelectAccount(QString))); connect(m_register, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions))); connect(m_register, SIGNAL(editTransaction()), this, SIGNAL(startEdit())); connect(m_register, SIGNAL(emptyItemSelected()), this, SLOT(slotNewTransaction())); connect(m_register, SIGNAL(aboutToSelectItem(KMyMoneyRegister::RegisterItem*,bool&)), this, SLOT(slotAboutToSelectItem(KMyMoneyRegister::RegisterItem*,bool&))); connect(d->m_mousePressFilter, SIGNAL(mousePressedOnExternalWidget(bool&)), this, SIGNAL(cancelOrEndEdit(bool&))); connect(m_form, SIGNAL(newTransaction(KMyMoneyRegister::Action)), this, SLOT(slotNewTransaction(KMyMoneyRegister::Action))); // setup mouse press filter d->m_mousePressFilter->addWidget(m_formFrame); d->m_mousePressFilter->addWidget(m_buttonFrame); d->m_mousePressFilter->addWidget(m_summaryFrame); d->m_mousePressFilter->addWidget(m_registerFrame); m_tooltipPosn = QPoint(); } KGlobalLedgerView::~KGlobalLedgerView() { delete d; } void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect) { Q_UNUSED(item); emit cancelOrEndEdit(okToSelect); } void KGlobalLedgerView::slotLoadView() { m_needReload = true; if (isVisible()) { if (!m_inEditMode) { setUpdatesEnabled(false); loadView(); setUpdatesEnabled(true); m_needReload = false; // force a new account if the current one is empty m_newAccountLoaded = m_account.id().isEmpty(); } } } void KGlobalLedgerView::clear() { // clear current register contents m_register->clear(); // setup header font QFont font = KMyMoneyGlobalSettings::listHeaderFont(); QFontMetrics fm(font); m_register->horizontalHeader()->setFont(font); // setup cell font font = KMyMoneyGlobalSettings::listCellFont(); m_register->setFont(font); // clear the form m_form->clear(); // the selected transactions list m_transactionList.clear(); // and the selected account in the combo box d->m_accountComboBox->setSelected(QString()); // fraction defaults to two digits d->m_precision = 2; } void KGlobalLedgerView::loadView() { MYMONEYTRACER(tracer); // setup form visibility m_formFrame->setVisible(KMyMoneyGlobalSettings::transactionForm()); // no account selected emit accountSelected(MyMoneyAccount()); // no transaction selected KMyMoneyRegister::SelectedTransactions list; emit transactionsSelected(list); QMap isSelected; QString focusItemId; QString backUpFocusItemId; // in case the focus item is removed QString anchorItemId; QString backUpAnchorItemId; // in case the anchor item is removed if (!m_newAccountLoaded) { // remember the current selected transactions KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); for (; item; item = item->nextItem()) { if (item->isSelected()) { isSelected[item->id()] = true; } } // remember the item that has the focus d->storeId(m_register->focusItem(), focusItemId, backUpFocusItemId); // and the one that has the selection anchor d->storeId(m_register->anchorItem(), anchorItemId, backUpAnchorItemId); } else { d->m_registerSearchLine->searchLine()->reset(); } // clear the current contents ... clear(); // ... load the combobox widget and select current account ... loadAccounts(); // ... setup the register columns ... m_register->setupRegister(m_account); // ... setup the form ... m_form->setupForm(m_account); if (m_account.id().isEmpty()) { // if we don't have an account we bail out setEnabled(false); return; } setEnabled(true); m_register->setUpdatesEnabled(false); // ... and recreate it KMyMoneyRegister::RegisterItem* focusItem = 0; KMyMoneyRegister::RegisterItem* anchorItem = 0; QMap actBalance, clearedBalance, futureBalance; QMap::iterator it_b; try { // setup the filter to select the transactions we want to display // and update the sort order QString sortOrder; QString key; QDate reconciliationDate = d->m_reconciliationDate; MyMoneyTransactionFilter filter(m_account.id()); // if it's an investment account, we also take care of // the sub-accounts (stock accounts) if (m_account.accountType() == MyMoneyAccount::Investment) filter.addAccount(m_account.accountList()); if (isReconciliationAccount()) { key = "kmm-sort-reconcile"; sortOrder = KMyMoneyGlobalSettings::sortReconcileView(); filter.addState(MyMoneyTransactionFilter::notReconciled); filter.addState(MyMoneyTransactionFilter::cleared); } else { filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); key = "kmm-sort-std"; sortOrder = KMyMoneyGlobalSettings::sortNormalView(); if (KMyMoneyGlobalSettings::hideReconciledTransactions() && !m_account.isIncomeExpense()) { filter.addState(MyMoneyTransactionFilter::notReconciled); filter.addState(MyMoneyTransactionFilter::cleared); } } filter.setReportAllSplits(true); // check if we have an account override of the sort order if (!m_account.value(key).isEmpty()) sortOrder = m_account.value(key); // setup sort order m_register->setSortOrder(sortOrder); // retrieve the list from the engine MyMoneyFile::instance()->transactionList(m_transactionList, filter); kmymoney->slotStatusProgressBar(0, m_transactionList.count()); // create the elements for the register QList >::const_iterator it; QMapuniqueMap; int i = 0; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Transaction* t = KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); actBalance[t->split().accountId()] = MyMoneyMoney(); kmymoney->slotStatusProgressBar(++i, 0); // if we're in reconciliation and the state is cleared, we // force the item to show in dimmed intensity to get a visual focus // on those items, that we need to work on if (isReconciliationAccount() && (*it).second.reconcileFlag() == MyMoneySplit::Cleared) { t->setReducedIntensity(true); } } // create dummy entries for the scheduled transactions if sorted by postdate int period = KMyMoneyGlobalSettings::schedulePreview(); if (m_register->primarySortKey() == KMyMoneyRegister::PostDateSort) { // show scheduled transactions which have a scheduled postdate // within the next 'period' days. In reconciliation mode, the // period starts on the statement date. QDate endDate = QDate::currentDate().addDays(period); if (isReconciliationAccount()) endDate = reconciliationDate.addDays(period); QList scheduleList = MyMoneyFile::instance()->scheduleList(m_account.id()); while (scheduleList.count() > 0) { MyMoneySchedule& s = scheduleList.first(); for (;;) { if (s.isFinished() || s.adjustedNextDueDate() > endDate) { break; } MyMoneyTransaction t(s.id(), KMyMoneyUtils::scheduledTransaction(s)); // if the transaction is scheduled and overdue, it can't // certainly be posted in the past. So we take today's date // as the alternative if (s.isOverdue() && !KMyMoneyGlobalSettings::showPlannedScheduleDates()) { t.setPostDate(s.adjustedDate(QDate::currentDate(), s.weekendOption())); } else { t.setPostDate(s.adjustedNextDueDate()); } const QList& splits = t.splits(); QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).accountId() == m_account.id()) { new KMyMoneyRegister::StdTransactionScheduled(m_register, t, *it_s, uniqueMap[t.id()]); } } // keep track of this payment locally (not in the engine) if (s.isOverdue() && !KMyMoneyGlobalSettings::showPlannedScheduleDates()) { s.setLastPayment(QDate::currentDate()); } else { s.setLastPayment(s.nextDueDate()); } // if this is a one time schedule, we can bail out here as we're done if (s.occurrence() == MyMoneySchedule::OCCUR_ONCE) break; // for all others, we check if the next payment date is still 'in range' QDate nextDueDate = s.nextPayment(s.nextDueDate()); if (nextDueDate.isValid()) { s.setNextDueDate(nextDueDate); } else { break; } } scheduleList.pop_front(); } } // add the group markers m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_register->sortItems(); // remove trailing and adjacent markers m_register->removeUnwantedGroupMarkers(); // add special markers for reconciliation now so that they do not get // removed by m_register->removeUnwantedGroupMarkers(). Needs resorting // of items but that's ok. KMyMoneyRegister::StatementGroupMarker* statement = 0; KMyMoneyRegister::StatementGroupMarker* dStatement = 0; KMyMoneyRegister::StatementGroupMarker* pStatement = 0; if (isReconciliationAccount()) { switch (m_register->primarySortKey()) { case KMyMoneyRegister::PostDateSort: statement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Details")); m_register->sortItems(); break; case KMyMoneyRegister::TypeSort: dStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Deposit Details")); pStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Payment, reconciliationDate, i18n("Statement Payment Details")); m_register->sortItems(); break; default: break; } } // we need at least the balance for the account we currently show actBalance[m_account.id()] = MyMoneyMoney(); if (m_account.accountType() == MyMoneyAccount::Investment) { QList::const_iterator it_a; for (it_a = m_account.accountList().begin(); it_a != m_account.accountList().end(); ++it_a) { actBalance[*it_a] = MyMoneyMoney(); } } // determine balances (actual, cleared). We do this by getting the actual // balance of all entered transactions from the engine and walk the list // of transactions backward. Also re-select a transaction if it was // selected before and setup the focus item. MyMoneyMoney factor(1, 1); if (m_account.accountGroup() == MyMoneyAccount::Liability || m_account.accountGroup() == MyMoneyAccount::Equity) factor = -factor; QMap deposits; QMap payments; QMap depositAmount; QMap paymentAmount; for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { MyMoneyMoney balance = MyMoneyFile::instance()->balance(it_b.key()); balance = balance * factor; clearedBalance[it_b.key()] = futureBalance[it_b.key()] = (*it_b) = balance; deposits[it_b.key()] = payments[it_b.key()] = 0; depositAmount[it_b.key()] = MyMoneyMoney(); paymentAmount[it_b.key()] = MyMoneyMoney(); } tracer.printf("total balance of %s = %s", qPrintable(m_account.name()), qPrintable(actBalance[m_account.id()].formatMoney("", 2))); tracer.printf("future balance of %s = %s", qPrintable(m_account.name()), qPrintable(futureBalance[m_account.id()].formatMoney("", 2))); tracer.printf("cleared balance of %s = %s", qPrintable(m_account.name()), qPrintable(clearedBalance[m_account.id()].formatMoney("", 2))); KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); focusItem = 0; // take care of possibly trailing scheduled transactions (bump up the future balance) while (p) { if (p->isSelectable()) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t && t->isScheduled()) { MyMoneyMoney balance = futureBalance[t->split().accountId()]; const MyMoneySplit& split = t->split(); // if this split is a stock split, we can't just add the amount of shares if (t->transaction().isStockSplit()) { balance = balance * split.shares(); } else { balance += split.shares() * factor; } futureBalance[split.accountId()] = balance; } else if (t && !focusItem) focusItem = p; } p = p->prevItem(); } p = m_register->lastItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (isSelected.contains(t->id())) t->setSelected(true); d->matchItemById(&focusItem, t, focusItemId, backUpFocusItemId); d->matchItemById(&anchorItem, t, anchorItemId, backUpAnchorItemId); const MyMoneySplit& split = t->split(); MyMoneyMoney balance = futureBalance[split.accountId()]; t->setBalance(balance); // if this split is a stock split, we can't just add the amount of shares if (t->transaction().isStockSplit()) { balance /= split.shares(); } else { balance -= split.shares() * factor; } if (!t->isScheduled()) { if (isReconciliationAccount() && t->transaction().postDate() <= reconciliationDate && split.reconcileFlag() == MyMoneySplit::Cleared) { if (split.shares().isNegative()) { payments[split.accountId()]++; paymentAmount[split.accountId()] += split.shares(); } else { deposits[split.accountId()]++; depositAmount[split.accountId()] += split.shares(); } } if (t->transaction().postDate() > QDate::currentDate()) { tracer.printf("Reducing actual balance by %s because %s/%s(%s) is in the future", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable(t->transaction().id()), qPrintable(split.id()), qPrintable(t->transaction().postDate().toString(Qt::ISODate))); actBalance[split.accountId()] -= split.shares() * factor; } } futureBalance[split.accountId()] = balance; } p = p->prevItem(); } clearedBalance[m_account.id()] = MyMoneyFile::instance()->clearedBalance(m_account.id(), reconciliationDate); tracer.printf("total balance of %s = %s", qPrintable(m_account.name()), qPrintable(actBalance[m_account.id()].formatMoney("", 2))); tracer.printf("future balance of %s = %s", qPrintable(m_account.name()), qPrintable(futureBalance[m_account.id()].formatMoney("", 2))); tracer.printf("cleared balance of %s = %s", qPrintable(m_account.name()), qPrintable(clearedBalance[m_account.id()].formatMoney("", 2))); // update statement information if (statement) { const QString aboutDeposits = i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_account.id()], depositAmount[m_account.id()].abs().formatMoney(m_account.fraction())); const QString aboutPayments = i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_account.id()], paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction())); statement->setText(i18nc("%1 is a string, e.g. 7 deposits; %2 is a string, e.g. 4 payments", "%1, %2", aboutDeposits, aboutPayments)); } if (pStatement) { pStatement->setText(i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_account.id()] , paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction()))); } if (dStatement) { dStatement->setText(i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_account.id()] , depositAmount[m_account.id()].abs().formatMoney(m_account.fraction()))); } // add a last empty entry for new transactions // leave some information about the current account MyMoneySplit split; split.setReconcileFlag(MyMoneySplit::NotReconciled); // make sure to use the value specified in the option during reconciliation if (isReconciliationAccount()) split.setReconcileFlag(static_cast(KMyMoneyGlobalSettings::defaultReconciliationState())); KMyMoneyRegister::Register::transactionFactory(m_register, MyMoneyTransaction(), split, 0); m_register->updateRegister(true); if (focusItem) { // in case we have some selected items we just set the focus item // in other cases, we make the focusitem also the selected item if (anchorItem && (anchorItem != focusItem)) { m_register->setFocusItem(focusItem); m_register->setAnchorItem(anchorItem); } else m_register->selectItem(focusItem, true); } else { // just use the empty line at the end if nothing else exists in the ledger p = m_register->lastItem(); m_register->setFocusItem(p); m_register->selectItem(p); focusItem = p; } updateSummaryLine(actBalance, clearedBalance); kmymoney->slotStatusProgressBar(-1, -1); } catch (const MyMoneyException &) { m_account = MyMoneyAccount(); clear(); } d->m_showDetails = KMyMoneyGlobalSettings::showRegisterDetailed(); // and tell everyone what's selected emit accountSelected(m_account); KMyMoneyRegister::SelectedTransactions actualSelection(m_register); emit transactionsSelected(actualSelection); } void KGlobalLedgerView::updateSummaryLine(const QMap& actBalance, const QMap& clearedBalance) { MyMoneyFile* file = MyMoneyFile::instance(); m_leftSummaryLabel->show(); m_centerSummaryLabel->show(); m_rightSummaryLabel->show(); if (isReconciliationAccount()) { if (m_account.accountType() != MyMoneyAccount::Investment) { m_leftSummaryLabel->setText(i18n("Statement: %1", d->m_endingBalance.formatMoney("", d->m_precision))); m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_account.id()].formatMoney("", d->m_precision))); d->m_totalBalance = clearedBalance[m_account.id()] - d->m_endingBalance; } } else { // update summary line in normal mode QDate reconcileDate = m_account.lastReconciliationDate(); if (reconcileDate.isValid()) { m_leftSummaryLabel->setText(i18n("Last reconciled: %1", KGlobal::locale()->formatDate(reconcileDate, KLocale::ShortDate))); } else { m_leftSummaryLabel->setText(i18n("Never reconciled")); } QPalette palette = m_rightSummaryLabel->palette(); palette.setColor(m_rightSummaryLabel->foregroundRole(), m_leftSummaryLabel->palette().color(foregroundRole())); if (m_account.accountType() != MyMoneyAccount::Investment) { m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_account.id()].formatMoney("", d->m_precision))); d->m_totalBalance = actBalance[m_account.id()]; } else { m_centerSummaryLabel->hide(); MyMoneyMoney balance; MyMoneySecurity base = file->baseCurrency(); QMap::const_iterator it_b; // reset the approximated flag d->m_balanceIsApproximated = false; for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { MyMoneyAccount stock = file->account(it_b.key()); QString currencyId = stock.currencyId(); MyMoneySecurity sec = file->security(currencyId); MyMoneyMoney rate(1, 1); if (stock.isInvest()) { currencyId = sec.tradingCurrency(); const MyMoneyPrice &priceInfo = file->price(sec.id(), currencyId); d->m_balanceIsApproximated |= !priceInfo.isValid(); rate = priceInfo.rate(sec.tradingCurrency()); } if (currencyId != base.id()) { const MyMoneyPrice &priceInfo = file->price(sec.tradingCurrency(), base.id()); d->m_balanceIsApproximated |= !priceInfo.isValid(); rate = (rate * priceInfo.rate(base.id())).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())); } balance += ((*it_b) * rate).convert(base.smallestAccountFraction()); } d->m_totalBalance = balance; } m_rightSummaryLabel->setPalette(palette); } // determine the number of selected transactions KMyMoneyRegister::SelectedTransactions selection; m_register->selectedTransactions(selection); slotUpdateSummaryLine(selection); } void KGlobalLedgerView::slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions& selection) { if (selection.count() > 1) { MyMoneyMoney balance; foreach (const KMyMoneyRegister::SelectedTransaction& t, selection) { if (!t.isScheduled()) { balance += t.split().shares(); } } m_rightSummaryLabel->setText(QString("%1: %2").arg(QChar(0x2211), balance.formatMoney("", d->m_precision))); } else { if (isReconciliationAccount()) { m_rightSummaryLabel->setText(i18n("Difference: %1", d->m_totalBalance.formatMoney("", d->m_precision))); } else { if (m_account.accountType() != MyMoneyAccount::Investment) { m_rightSummaryLabel->setText(i18n("Balance: %1", d->m_totalBalance.formatMoney("", d->m_precision))); bool showNegative = d->m_totalBalance.isNegative(); if (m_account.accountGroup() == MyMoneyAccount::Liability && !d->m_totalBalance.isZero()) showNegative = !showNegative; if (showNegative) { QPalette palette = m_rightSummaryLabel->palette(); palette.setColor(m_rightSummaryLabel->foregroundRole(), KMyMoneyGlobalSettings::listNegativeValueColor()); m_rightSummaryLabel->setPalette(palette); } } else { m_rightSummaryLabel->setText(i18n("Investment value: %1%2", d->m_balanceIsApproximated ? "~" : "", d->m_totalBalance.formatMoney(MyMoneyFile::instance()->baseCurrency().tradingSymbol(), d->m_precision))); } } } } void KGlobalLedgerView::resizeEvent(QResizeEvent* ev) { m_register->resize(KMyMoneyRegister::DetailColumn); m_form->resize(KMyMoneyTransactionForm::ValueColumn1); KMyMoneyViewBase::resizeEvent(ev); } void KGlobalLedgerView::loadAccounts() { MyMoneyFile* file = MyMoneyFile::instance(); // check if the current account still exists and make it the // current account if (!m_account.id().isEmpty()) { try { m_account = file->account(m_account.id()); } catch (const MyMoneyException &) { m_account = MyMoneyAccount(); return; } } // TODO: check why the invalidate is needed here d->m_filterProxyModel->invalidate(); d->m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->toggleAction("view_show_all_accounts")->isChecked()); d->m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); d->m_accountComboBox->expandAll(); if (m_account.id().isEmpty()) { // find the first favorite account QModelIndexList list = Models::instance()->accountsModel()->match(Models::instance()->accountsModel()->index(0, 0), AccountsModel::AccountFavoriteRole, QVariant(true), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); if (list.count() > 0) { QVariant accountId = list.front().data(AccountsModel::AccountIdRole); if (accountId.isValid()) { m_account = file->account(accountId.toString()); } } if (m_account.id().isEmpty()) { // there are no favorite accounts find any account QModelIndexList list = Models::instance()->accountsModel()->match(Models::instance()->accountsModel()->index(0, 0), Qt::DisplayRole, QVariant(QString("*")), -1, Qt::MatchFlags(Qt::MatchWildcard | Qt::MatchRecursive)); for (QModelIndexList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { if (!it->parent().isValid()) continue; // skip the top level accounts QVariant accountId = (*it).data(AccountsModel::AccountIdRole); if (accountId.isValid()) { MyMoneyAccount a = file->account(accountId.toString()); if (!a.isInvest()) { m_account = a; break; } } } } } if (!m_account.id().isEmpty()) { d->m_accountComboBox->setSelected(m_account.id()); try { d->m_precision = MyMoneyMoney::denomToPrec(m_account.fraction()); } catch (const MyMoneyException &) { qDebug("Security %s for account %s not found", qPrintable(m_account.currencyId()), qPrintable(m_account.name())); d->m_precision = 2; } } } void KGlobalLedgerView::selectTransaction(const QString& id) { if (!id.isEmpty()) { KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (t->transaction().id() == id) { m_register->selectItem(t); m_register->ensureItemVisible(t); break; } } p = p->prevItem(); } } } void KGlobalLedgerView::slotSelectAllTransactions() { m_register->clearSelection(); KMyMoneyRegister::RegisterItem* p = m_register->firstItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) { t->setSelected(true); } } p = p->nextItem(); } // this is here only to re-paint the items without selecting anything because the data (including the selection) is not really held in the model right now m_register->selectAll(); // inform everyone else about the selected items KMyMoneyRegister::SelectedTransactions list(m_register); emit transactionsSelected(list); } void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) { if (d->m_reconciliationAccount != acc.id()) { // make sure the account is selected if (!acc.id().isEmpty()) slotSelectAccount(acc.id()); d->m_reconciliationAccount = acc.id(); d->m_reconciliationDate = reconciliationDate; d->m_endingBalance = endingBalance; if (acc.accountGroup() == MyMoneyAccount::Liability) d->m_endingBalance = -endingBalance; m_newAccountLoaded = true; if (acc.id().isEmpty()) { m_buttonbar->removeAction(kmymoney->action("account_reconcile_postpone")); m_buttonbar->removeAction(kmymoney->action("account_reconcile_finish")); } else { m_buttonbar->addAction(kmymoney->action("account_reconcile_postpone")); m_buttonbar->addAction(kmymoney->action("account_reconcile_finish")); // when we start reconciliation, we need to reload the view // because no data has been changed. When postponing or finishing // reconciliation, the data change in the engine takes care of updateing // the view. slotLoadView(); } } } bool KGlobalLedgerView::isReconciliationAccount() const { return m_account.id() == d->m_reconciliationAccount; } bool KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return false; if (d->m_recursion) return false; d->m_recursion = true; const MyMoneyAccount& acc = dynamic_cast(obj); bool rc = slotSelectAccount(acc.id()); d->m_recursion = false; return rc; } bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId) { bool rc = true; if (!id.isEmpty()) { if (m_account.id() != id) { try { m_account = MyMoneyFile::instance()->account(id); // if a stock account is selected, we show the // the corresponding parent (investment) account if (m_account.isInvest()) { m_account = MyMoneyFile::instance()->account(m_account.parentAccountId()); } m_newAccountLoaded = true; slotLoadView(); } catch (const MyMoneyException &) { qDebug("Unable to retrieve account %s", qPrintable(id)); rc = false; } } else { // we need to refresh m_account.m_accountList, a child could have been deleted m_account = MyMoneyFile::instance()->account(id); emit accountSelected(m_account); } selectTransaction(transactionId); } return rc; } void KGlobalLedgerView::slotNewTransaction(KMyMoneyRegister::Action id) { if (!m_inEditMode) { d->m_action = id; emit newTransaction(); } } void KGlobalLedgerView::slotNewTransaction() { slotNewTransaction(KMyMoneyRegister::ActionNone); } void KGlobalLedgerView::setupDefaultAction() { switch (m_account.accountType()) { case MyMoneyAccount::Asset: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Savings: d->m_action = KMyMoneyRegister::ActionDeposit; default: d->m_action = KMyMoneyRegister::ActionWithdrawal; break; } } bool KGlobalLedgerView::selectEmptyTransaction() { bool rc = false; if (!m_inEditMode) { // in case we don't know the type of transaction to be created, // have at least one selected transaction and the id of // this transaction is not empty, we take it as template for the // transaction to be created KMyMoneyRegister::SelectedTransactions list(m_register); if ((d->m_action == KMyMoneyRegister::ActionNone) && (!list.isEmpty()) && (!list[0].transaction().id().isEmpty())) { // the new transaction to be created will have the same type // as the one that currently has the focus KMyMoneyRegister::Transaction* t = dynamic_cast(m_register->focusItem()); if (t) d->m_action = t->actionType(); m_register->clearSelection(); } // if we still don't have an idea which type of transaction // to create, we use the default. if (d->m_action == KMyMoneyRegister::ActionNone) { setupDefaultAction(); } m_register->selectItem(m_register->lastItem()); m_register->updateRegister(); rc = true; } return rc; } TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) { // we use the warnlevel to keep track, if we have to warn the // user that some or all splits have been reconciled or if the // user cannot modify the transaction if at least one split // has the status frozen. The following value are used: // // 0 - no sweat, user can modify // 1 - user should be warned that at least one split has been reconciled // already // 2 - user will be informed, that this transaction cannot be changed anymore int warnLevel = list.warnLevel(); Q_ASSERT(warnLevel < 2); // otherwise the edit action should not be enabled switch (warnLevel) { case 0: break; case 1: if (KMessageBox::warningContinueCancel(nullptr, i18n( "At least one split of the selected transactions has been reconciled. " "Do you wish to continue to edit the transactions anyway?" ), i18n("Transaction already reconciled"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "EditReconciledTransaction") == KMessageBox::Cancel) { warnLevel = 2; } break; case 2: KMessageBox::sorry(nullptr, i18n("At least one split of the selected transactions has been frozen. " "Editing the transactions is therefore prohibited."), i18n("Transaction already frozen")); break; case 3: KMessageBox::sorry(nullptr, i18n("At least one split of the selected transaction references an account that has been closed. " "Editing the transactions is therefore prohibited."), i18n("Account closed")); break; } if (warnLevel > 1) return 0; TransactionEditor* editor = 0; KMyMoneyRegister::Transaction* item = dynamic_cast(m_register->focusItem()); if (item) { // in case the current focus item is not selected, we move the focus to the first selected transaction if (!item->isSelected()) { KMyMoneyRegister::RegisterItem* p; for (p = m_register->firstItem(); p; p = p->nextItem()) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t && t->isSelected()) { m_register->setFocusItem(t); item = t; break; } } } // decide, if we edit in the register or in the form TransactionEditorContainer* parent; if (m_formFrame->isVisible()) parent = m_form; else { parent = m_register; } editor = item->createEditor(parent, list, m_lastPostDate); // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor) { if (!editor->fixTransactionCommodity(m_account)) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { if (parent == m_register) { // make sure, the height of the table is correct m_register->updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm()); } m_inEditMode = true; connect(editor, SIGNAL(transactionDataSufficient(bool)), kmymoney->action("transaction_enter"), SLOT(setEnabled(bool))); connect(editor, SIGNAL(returnPressed()), kmymoney->action("transaction_enter"), SLOT(trigger())); connect(editor, SIGNAL(escapePressed()), kmymoney->action("transaction_cancel"), SLOT(trigger())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets())); connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions))); connect(editor, SIGNAL(objectCreation(bool)), d->m_mousePressFilter, SLOT(setFilterDeactive(bool))); connect(editor, SIGNAL(createPayee(QString,QString&)), kmymoney, SLOT(slotPayeeNew(QString,QString&))); connect(editor, SIGNAL(createTag(QString,QString&)), kmymoney, SLOT(slotTagNew(QString,QString&))); connect(editor, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); connect(editor, SIGNAL(createSecurity(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotInvestmentNew(MyMoneyAccount&,MyMoneyAccount))); - connect(editor, SIGNAL(assignNumber()), kmymoney, SLOT(slotTransactionAssignNumber())); + connect(editor, SIGNAL(assignNumber()), kmymoney, SLOT(slotTransactionAssignCheckNumber())); connect(editor, SIGNAL(lastPostDateUsed(QDate)), this, SLOT(slotKeepPostDate(QDate))); // create the widgets, place them in the parent and load them with data // setup tab order m_tabOrderWidgets.clear(); editor->setup(m_tabOrderWidgets, m_account, d->m_action); Q_ASSERT(!m_tabOrderWidgets.isEmpty()); // install event filter in all taborder widgets QWidgetList::const_iterator it_w = m_tabOrderWidgets.constBegin(); for (; it_w != m_tabOrderWidgets.constEnd(); ++it_w) { (*it_w)->installEventFilter(this); } // Install a filter that checks if a mouse press happened outside // of one of our own widgets. qApp->installEventFilter(d->m_mousePressFilter); // Check if the editor has some preference on where to set the focus // If not, set the focus to the first widget in the tab order QWidget* focusWidget = editor->firstWidget(); if (!focusWidget) focusWidget = m_tabOrderWidgets.first(); // for some reason, this only works reliably if delayed a bit QTimer::singleShot(10, focusWidget, SLOT(setFocus())); // preset to 'I have no idea which type to create' for the next round. d->m_action = KMyMoneyRegister::ActionNone; } } return editor; } void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list) { m_inEditMode = false; qApp->removeEventFilter(d->m_mousePressFilter); // a possible focusOut event may have removed the focus, so we // install it back again. m_register->focusItem()->setFocus(true); // if we come back from editing a new item, we make sure that // we always select the very last known transaction entry no // matter if the transaction has been created or not. if (list.count() && list[0].transaction().id().isEmpty()) { // block signals to prevent some infinite loops that might occur here. m_register->blockSignals(true); m_register->clearSelection(); KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); if (p && p->prevItem()) p = p->prevItem(); m_register->selectItem(p); m_register->updateRegister(true); m_register->blockSignals(false); // we need to update the form manually as sending signals was blocked KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) m_form->slotSetTransaction(t); } else { if (!KMyMoneySettings::transactionForm()) { // update the row height of the transactions because it might differ between viewing/editing mode when not using the transaction form m_register->blockSignals(true); m_register->updateRegister(true); m_register->blockSignals(false); } } if (m_needReload) slotLoadView(); m_register->setFocus(); } bool KGlobalLedgerView::focusNextPrevChild(bool next) { bool rc = false; // qDebug("KGlobalLedgerView::focusNextPrevChild(editmode=%s)", m_inEditMode ? "true" : "false"); if (m_inEditMode) { QWidget *w = 0; w = qApp->focusWidget(); // qDebug("w = %p", w); int currentWidgetIndex = m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", qPrintable(w->objectName())); w = w->parentWidget(); currentWidgetIndex = m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", qPrintable(w->objectName())); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' (%p) as focus", qPrintable(w->objectName()), w); w->setFocus(); rc = true; } } } else rc = KMyMoneyViewBase::focusNextPrevChild(next); return rc; } void KGlobalLedgerView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { if (!m_inEditMode) { setUpdatesEnabled(false); loadView(); setUpdatesEnabled(true); m_needReload = false; m_newAccountLoaded = false; } } else { emit accountSelected(m_account); KMyMoneyRegister::SelectedTransactions list(m_register); emit transactionsSelected(list); } // don't forget base class implementation KMyMoneyViewBase::showEvent(event); } bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e) { bool rc = false; // Need to capture mouse position here as QEvent::ToolTip is too slow m_tooltipPosn = QCursor::pos(); if (e->type() == QEvent::KeyPress) { if (m_inEditMode) { // qDebug("object = %s, key = %d", o->className(), k->key()); if (o == m_register) { // we hide all key press events from the register // while editing a transaction rc = true; } } } if (!rc) rc = KMyMoneyViewBase::eventFilter(o, e); return rc; } void KGlobalLedgerView::showTooltip(const QString msg) const { QToolTip::showText(m_tooltipPosn, msg); } void KGlobalLedgerView::slotSortOptions() { QPointer dlg = new KSortOptionDlg(this); QString key; QString sortOrder, def; if (isReconciliationAccount()) { key = "kmm-sort-reconcile"; def = KMyMoneyGlobalSettings::sortReconcileView(); } else { key = "kmm-sort-std"; def = KMyMoneyGlobalSettings::sortNormalView(); } // check if we have an account override of the sort order if (!m_account.value(key).isEmpty()) sortOrder = m_account.value(key); QString oldOrder = sortOrder; dlg->setSortOption(sortOrder, def); if (dlg->exec() == QDialog::Accepted) { if (dlg != 0) { sortOrder = dlg->sortOption(); if (sortOrder != oldOrder) { if (sortOrder.isEmpty()) { m_account.deletePair(key); } else { m_account.setValue(key, sortOrder); } MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(m_account); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to update sort order for account '%s': %s", qPrintable(m_account.name()), qPrintable(e.what())); } } } } delete dlg; } void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */) { if (!m_inEditMode) { emit toggleReconciliationFlag(); } } void KGlobalLedgerView::slotKeepPostDate(const QDate& date) { m_lastPostDate = date; } bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const { bool rc = true; if (m_account.id().isEmpty()) { tooltip = i18n("Cannot create transactions when no account is selected."); rc = false; } if (m_account.accountGroup() == MyMoneyAccount::Income || m_account.accountGroup() == MyMoneyAccount::Expense) { tooltip = i18n("Cannot create transactions in the context of a category."); showTooltip(tooltip); rc = false; } if (m_account.isClosed()) { tooltip = i18n("Cannot create transactions in a closed account."); showTooltip(tooltip); rc = false; } return rc; } bool KGlobalLedgerView::canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { MyMoneyAccount acc; QString closedAccount; if (m_register->focusItem() == 0) return false; bool rc = true; if (list.warnLevel() == 3) { //Closed account somewhere KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) { QList splitList = (*it_t).transaction().splits(); QString id = splitList.first().accountId(); acc = MyMoneyFile::instance()->account(id); if (!acc.isClosed()) { //wrong split, try other id = splitList.last().accountId(); acc = MyMoneyFile::instance()->account(id); } closedAccount = acc.name(); break; } tooltip = i18n("Cannot process transactions in account %1, which is closed.", closedAccount); showTooltip(tooltip); return false; } if (!m_register->focusItem()->isSelected()) { tooltip = i18n("Cannot process transaction with focus if it is not selected."); showTooltip(tooltip); return false; } tooltip.clear(); return !list.isEmpty(); } bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { return canProcessTransactions(list, tooltip) && list.canModify(); } bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { return canProcessTransactions(list, tooltip) && list.canDuplicate(); } bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { // check if we can edit the list of transactions. We can edit, if // // a) no mix of standard and investment transactions exist // b) if a split transaction is selected, this is the only selection // c) none of the splits is frozen // d) the transaction having the current focus is selected // check for d) if (!canProcessTransactions(list, tooltip)) return false; // check for c) if (list.warnLevel() == 2) { tooltip = i18n("Cannot edit transactions with frozen splits."); showTooltip(tooltip); return false; } bool rc = true; int investmentTransactions = 0; int normalTransactions = 0; if (m_account.accountGroup() == MyMoneyAccount::Income || m_account.accountGroup() == MyMoneyAccount::Expense) { tooltip = i18n("Cannot edit transactions in the context of a category."); showTooltip(tooltip); rc = false; } if (m_account.isClosed()) { tooltip = i18n("Cannot create or edit any transactions in Account %1 as it is closed", m_account.name()); showTooltip(tooltip); rc = false; } KMyMoneyRegister::SelectedTransactions::const_iterator it_t; QString action; for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) { if ((*it_t).transaction().id().isEmpty()) { tooltip.clear(); rc = false; continue; } if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) { if (action.isEmpty()) { action = (*it_t).split().action(); continue; } if (action == (*it_t).split().action()) { continue; } else { tooltip = (i18n("Cannot edit mixed investment action/type transactions together.")); showTooltip(tooltip); rc = false; break; } } if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) ++investmentTransactions; else ++normalTransactions; // check for a) if (investmentTransactions != 0 && normalTransactions != 0) { tooltip = i18n("Cannot edit investment transactions and non-investment transactions together."); showTooltip(tooltip); rc = false; break; } // check for b) but only for normalTransactions if ((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) { if (list.count() > 1) { tooltip = i18n("Cannot edit multiple split transactions at once."); showTooltip(tooltip); rc = false; break; } } } // check for multiple transactions being selected in an investment account // we do not allow editing in this case: https://bugs.kde.org/show_bug.cgi?id=240816 // later on, we might allow to edit investment transactions of the same type /// Can now disable the following check. /* if (rc == true && investmentTransactions > 1) { tooltip = i18n("Cannot edit multiple investment transactions at once"); rc = false; }*/ // now check that we have the correct account type for investment transactions if (rc == true && investmentTransactions != 0) { if (m_account.accountType() != MyMoneyAccount::Investment) { tooltip = i18n("Cannot edit investment transactions in the context of this account."); rc = false; } } return rc; }