diff --git a/kmymoney/converter/CMakeLists.txt b/kmymoney/converter/CMakeLists.txt index 7f072ba93..31c7365ca 100644 --- a/kmymoney/converter/CMakeLists.txt +++ b/kmymoney/converter/CMakeLists.txt @@ -1,42 +1,41 @@ if(BUILD_TESTING) add_subdirectory( tests ) endif() set (libconverter_a_SOURCES mymoneystatementreader.cpp mymoneytemplate.cpp - webpricequote.cpp transactionmatchfinder.cpp existingtransactionmatchfinder.cpp scheduledtransactionmatchfinder.cpp ../widgets/kmymoneymoneyvalidator.cpp ) add_library(converter STATIC ${libconverter_a_SOURCES}) # TODO: clean dependencies target_link_libraries(converter PUBLIC KF5::Service KF5::XmlGui KF5::Completion KF5::TextWidgets KF5::WidgetsAddons KF5::ConfigCore Alkimia::alkimia KF5::KIOWidgets kmm_csvimportercore PRIVATE KF5::I18n kmymoney_common newaccountwizard ) # we rely on some dialogs to be generated add_dependencies(converter dialogs) ########### install files ############### install(FILES mymoneytemplate.h DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) diff --git a/kmymoney/converter/tests/converter-test.cpp b/kmymoney/converter/tests/converter-test.cpp deleted file mode 100644 index fec3fa927..000000000 --- a/kmymoney/converter/tests/converter-test.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/*************************************************************************** - convertertest.cpp - ------------------- - copyright : (C) 2002 by Thomas Baumgart - email : ipwizard@users.sourceforge.net - Ace Jones - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "converter-test.h" - -#include -#include - -// uses helper functions from reports tests -#include "tests/testutilities.h" -using namespace test; - -#include "mymoneyinstitution.h" -#include "mymoneysecurity.h" -#include "mymoneyprice.h" -#include "mymoneyreport.h" -#include "mymoneypayee.h" -#include "mymoneystatement.h" -#include "mymoneyexception.h" -#include "storage/mymoneystoragedump.h" -#include "webpricequote.h" - -QTEST_GUILESS_MAIN(ConverterTest) - -using namespace convertertest; - -void ConverterTest::init() -{ - storage = new MyMoneyStorageMgr; - file = MyMoneyFile::instance(); - file->attachStorage(storage); - - MyMoneyFileTransaction ft; - - file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); - file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); - file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); - file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); - file->setBaseCurrency(file->currency("USD")); - - MyMoneyPayee payeeTest; - payeeTest.setName("Test Payee"); - file->addPayee(payeeTest); - MyMoneyPayee payeeTest2; - payeeTest2.setName("Thomas Baumgart"); - file->addPayee(payeeTest2); - - acAsset = (MyMoneyFile::instance()->asset().id()); - acLiability = (MyMoneyFile::instance()->liability().id()); - acExpense = (MyMoneyFile::instance()->expense().id()); - acIncome = (MyMoneyFile::instance()->income().id()); - acChecking = makeAccount("Checking Account", eMyMoney::Account::Type::Checkings, moConverterCheckingOpen, QDate(2004, 5, 15), acAsset); - acCredit = makeAccount("Credit Card", eMyMoney::Account::Type::CreditCard, moConverterCreditOpen, QDate(2004, 7, 15), acLiability); - acSolo = makeAccount("Solo", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); - acParent = makeAccount("Parent", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); - acChild = makeAccount("Child", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); - acForeign = makeAccount("Foreign", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); - - MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); - file->addInstitution(i); - inBank = i.id(); - ft.commit(); -} - -void ConverterTest::cleanup() -{ - file->detachStorage(storage); - delete storage; -} - -void ConverterTest::testWebQuotes_data() -{ - QTest::addColumn("symbol"); - QTest::addColumn("testname"); - QTest::addColumn("source"); - - QTest::newRow("Yahoo UK") << "VOD.L" << "test Yahoo UK" << "Yahoo UK"; - QTest::newRow("Yahoo Currency") << "EUR > USD" << "test Yahoo Currency" << "Yahoo Currency"; - QTest::newRow("Financial Express") << "0585239" << "test Financial Express" << "Financial Express"; - QTest::newRow("Yahoo France") << "EAD.PA" << "test Yahoo France" << "Yahoo France"; - QTest::newRow("Globe & Mail") << "50492" << "test Globe-Mail" << "Globe & Mail"; - QTest::newRow("MSN Canada") << "TDB647" << "test MSN.CA" << "MSN.CA"; - -// QTest::newRow("Finanztreff") << "BASF.SE" << "test Finanztreff" << "Finanztreff"; -// QTest::newRow("boerseonline") << "symbol" << "test boerseonline" << "boerseonline"; -// QTest::newRow("Wallstreet-Online.DE (Default)") << "symbol" << "test Wallstreet-Online.DE (Default)" << "Wallstreet-Online.DE (Default)"; -// QTest::newRow("Financial Times UK") << "DZGEAE" << "test Financial Times UK Funds" << "Financial Times UK Funds"); - - QTest::newRow("Yahoo Canada") << "UTS.TO" << "test Yahoo Canada" << "Yahoo Canada"; - -// QTest::newRow("Wallstreed-Online.DE (Hamburg)") << "TDB647" << "test Wallstreet-Online.DE (Hamburg)" << "Wallstreet-Online.DE (Hamburg)"; -// QTest::newRow("Gielda Papierow Wartosciowych (GPW)") << "TDB647" << "test Gielda Papierow Wartosciowych (GPW)" << "Gielda Papierow Wartosciowych (GPW)"; -// QTest::newRow("OMX Baltic") << "TDB647" << "test OMX Baltic funds" << "OMX Baltic funds"; - - QTest::newRow("Finance::Quote usa") << "DIS" << "test F::Q usa" << "Finance::Quote usa"; -//UNTESTED: Other F::Q sources, local files, user custom sources -} - -void ConverterTest::testWebQuotesDefault() -{ -#ifdef PERFORM_ONLINE_TESTS - try { - WebPriceQuote q; - QuoteReceiver qr(&q); - - q.launch("DIS", "test default"); -// qDebug() << "ConverterTest::testWebQuotes(): quote for " << q.m_symbol << " on " << qr.m_date.toString() << " is " << qr.m_price.toString() << " errors(" << qr.m_errors.count() << "): " << qr.m_errors.join(" /// "); - - // No errors allowed - QVERIFY(qr.m_errors.count() == 0); - - // Quote date should be within the last week, or something bad is going on. - QVERIFY(qr.m_date <= QDate::currentDate()); - QVERIFY(qr.m_date >= QDate::currentDate().addDays(-7)); - - // Quote value should at least be positive - QVERIFY(qr.m_price.isPositive()); - } catch (const MyMoneyException &e) { - QFAIL(e.what()); - } -#endif -} - -void ConverterTest::testWebQuotes() -{ -#ifdef PERFORM_ONLINE_TESTS - try { - WebPriceQuote q; - QuoteReceiver qr(&q); - - QFETCH(QString, symbol); - QFETCH(QString, testname); - QFETCH(QString, source); - - q.launch(symbol, testname, source); - QVERIFY(qr.m_errors.count() == 0); - QVERIFY(qr.m_date <= QDate::currentDate().addDays(1)); - QVERIFY(qr.m_date >= QDate::currentDate().addDays(-7)); - QVERIFY(qr.m_price.isPositive()); - - } catch (const MyMoneyException &e) { - QFAIL(e.what()); - } -#endif -} - -void ConverterTest::testDateFormat() -{ - try { - MyMoneyDateFormat format("%mm-%dd-%yyyy"); - - QVERIFY(format.convertString("1-5-2005") == QDate(2005, 1, 5)); - QVERIFY(format.convertString("jan-15-2005") == QDate(2005, 1, 15)); - QVERIFY(format.convertString("august-25-2005") == QDate(2005, 8, 25)); - - format = MyMoneyDateFormat("%mm/%dd/%yy"); - - QVERIFY(format.convertString("1/5/05") == QDate(2005, 1, 5)); - QVERIFY(format.convertString("jan/15/05") == QDate(2005, 1, 15)); - QVERIFY(format.convertString("august/25/05") == QDate(2005, 8, 25)); - - format = MyMoneyDateFormat("%d\\.%m\\.%yy"); - - QVERIFY(format.convertString("1.5.05") == QDate(2005, 5, 1)); - QVERIFY(format.convertString("15.jan.05") == QDate(2005, 1, 15)); - QVERIFY(format.convertString("25.august.05") == QDate(2005, 8, 25)); - - format = MyMoneyDateFormat("%yyyy\\\\%dddd\\\\%mmmmmmmmmmm"); - - QVERIFY(format.convertString("2005\\31\\12") == QDate(2005, 12, 31)); - QVERIFY(format.convertString("2005\\15\\jan") == QDate(2005, 1, 15)); - QVERIFY(format.convertString("2005\\25\\august") == QDate(2005, 8, 25)); - - format = MyMoneyDateFormat("%m %dd, %yyyy"); - - QVERIFY(format.convertString("jan 15, 2005") == QDate(2005, 1, 15)); - QVERIFY(format.convertString("august 25, 2005") == QDate(2005, 8, 25)); - QVERIFY(format.convertString("january 1st, 2005") == QDate(2005, 1, 1)); - - format = MyMoneyDateFormat("%m %d %y"); - - QVERIFY(format.convertString("12/31/50", false, 2000) == QDate(1950, 12, 31)); - QVERIFY(format.convertString("1/1/90", false, 2000) == QDate(1990, 1, 1)); - QVERIFY(format.convertString("december 31st, 5", false) == QDate(2005, 12, 31)); - } catch (const MyMoneyException &e) { - QFAIL(e.what()); - } -} diff --git a/kmymoney/converter/tests/converter-test.h b/kmymoney/converter/tests/converter-test.h deleted file mode 100644 index 14b39d841..000000000 --- a/kmymoney/converter/tests/converter-test.h +++ /dev/null @@ -1,43 +0,0 @@ -/*************************************************************************** - convertertest.h - ------------------- - copyright : (C) 2002 by Thomas Baumgart - email : ipwizard@users.sourceforge.net - Ace Jones - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef CONVERTERTEST_H -#define CONVERTERTEST_H - -#include - -#include "mymoneyfile.h" -#include "storage/mymoneystoragemgr.h" - -class ConverterTest : public QObject -{ - Q_OBJECT - -private: - MyMoneyStorageMgr* storage; - MyMoneyFile* file; - -private Q_SLOTS: - void init(); - void cleanup(); - void testWebQuotesDefault(); - void testWebQuotes_data(); - void testWebQuotes(); - void testDateFormat(); -}; - -#endif // CONVERTERTEST_H diff --git a/kmymoney/converter/webpricequote.cpp b/kmymoney/converter/webpricequote.cpp deleted file mode 100644 index 8d7cbe3b2..000000000 --- a/kmymoney/converter/webpricequote.cpp +++ /dev/null @@ -1,1254 +0,0 @@ -/*************************************************************************** - webpricequote.cpp - ------------------- - begin : Thu Dec 30 2004 - copyright : (C) 2004 by Ace Jones - email : Ace Jones - copyright : (C) 2017 by Łukasz Wojniłowicz - email : Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "webpricequote.h" - -// ---------------------------------------------------------------------------- -// QT Headers - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// ---------------------------------------------------------------------------- -// KDE Headers - -#include -#include -#include -#include -#include -#include -#include -#include - -// ---------------------------------------------------------------------------- -// Project Headers - -#include "mymoneyexception.h" -#include "mymoneyfile.h" -#include "mymoneysecurity.h" - -Q_DECLARE_LOGGING_CATEGORY(WEBPRICEQUOTE) -Q_LOGGING_CATEGORY(WEBPRICEQUOTE, "kmymoney_webpricequote") - -// define static members -QString WebPriceQuote::m_financeQuoteScriptPath; -QStringList WebPriceQuote::m_financeQuoteSources; - -class WebPriceQuote::Private -{ -public: - WebPriceQuoteProcess m_filter; - QString m_quoteData; - QString m_webID; - QString m_kmmID; - QDate m_date; - QDate m_fromDate; - QDate m_toDate; - double m_price; - WebPriceQuoteSource m_source; - PricesProfile m_CSVSource; -}; - -WebPriceQuote::WebPriceQuote(QObject* _parent): - QObject(_parent), - d(new Private) -{ - // only do this once (I know, it is not thread safe, but it should - // always yield the same result so we don't do any semaphore foo here) - if (m_financeQuoteScriptPath.isEmpty()) { - m_financeQuoteScriptPath = QStandardPaths::locate(QStandardPaths::DataLocation, QString("misc/financequote.pl")); - } - connect(&d->m_filter, SIGNAL(processExited(QString)), this, SLOT(slotParseQuote(QString))); -} - -WebPriceQuote::~WebPriceQuote() -{ - delete d; -} - -void WebPriceQuote::setDate(const QDate& _from, const QDate& _to) -{ - d->m_fromDate = _from; - d->m_toDate = _to; -} - -bool WebPriceQuote::launch(const QString& _webID, const QString& _kmmID, const QString& _sourcename) -{ - if (_sourcename.contains("Finance::Quote")) - return (launchFinanceQuote(_webID, _kmmID, _sourcename)); - else if ((!d->m_fromDate.isValid() || !d->m_toDate.isValid()) || - (d->m_fromDate == d->m_toDate && d->m_toDate == QDate::currentDate())) - return (launchNative(_webID, _kmmID, _sourcename)); - else - return launchCSV(_webID, _kmmID, _sourcename); -} - -bool WebPriceQuote::launchCSV(const QString& _webID, const QString& _kmmID, const QString& _sourcename) -{ - d->m_webID = _webID; - d->m_kmmID = _kmmID; - -// emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); - - // Get sources from the config file - QString sourcename = _sourcename; - if (sourcename.isEmpty()) - return false; - - // for historical exchange rates we switch to Stooq - if (sourcename == QLatin1String("KMyMoney Currency")) - sourcename = QLatin1String("Stooq Currency"); - - if (quoteSources().contains(sourcename)) - d->m_source = WebPriceQuoteSource(sourcename); - else { - emit error(i18n("Source %1 does not exist.", sourcename)); - emit failed(d->m_kmmID, d->m_webID); - return false; - } - - int monthOffset = 0; - if (sourcename.contains(QLatin1String("Yahoo"), Qt::CaseInsensitive)) - monthOffset = -1; - - QUrl url; - QString urlStr = d->m_source.m_csvUrl; - int i = urlStr.indexOf(QLatin1String("%y")); - if (i != -1) - urlStr.replace(i, 2, QString().setNum(d->m_fromDate.year())); - i = urlStr.indexOf(QLatin1String("%y")); - if (i != -1) - urlStr.replace(i, 2, QString().setNum(d->m_toDate.year())); - - i = urlStr.indexOf(QLatin1String("%m")); - if (i != -1) - urlStr.replace(i, 2, QString().setNum(d->m_fromDate.month() + monthOffset).rightJustified(2, QLatin1Char('0'))); - i = urlStr.indexOf(QLatin1String("%m")); - if (i != -1) - urlStr.replace(i, 2, QString().setNum(d->m_toDate.month() + monthOffset).rightJustified(2, QLatin1Char('0'))); - - i = urlStr.indexOf(QLatin1String("%d")); - if (i != -1) - urlStr.replace(i, 2, QString().setNum(d->m_fromDate.day()).rightJustified(2, QLatin1Char('0'))); - i = urlStr.indexOf(QLatin1String("%d")); - if (i != -1) - urlStr.replace(i, 2, QString().setNum(d->m_toDate.day()).rightJustified(2, QLatin1Char('0'))); - - if (urlStr.contains(QLatin1String("%y")) || urlStr.contains(QLatin1String("%m")) || urlStr.contains(QLatin1String("%d"))) { - emit error(i18n("Cannot resolve input date.")); - emit failed(d->m_kmmID, d->m_webID); - return false; - } - - bool isCurrency = false; - if (urlStr.contains(QLatin1String("%2"))) { - d->m_CSVSource.m_profileType = Profile::CurrencyPrices; - isCurrency = true; - } else - d->m_CSVSource.m_profileType = Profile::StockPrices; - - d->m_CSVSource.m_profileName = sourcename; - if (!d->m_CSVSource.readSettings(CSVImporterCore::configFile())) { - QMap result = defaultCSVQuoteSources(); - d->m_CSVSource = result.value(sourcename); - if (d->m_CSVSource.m_profileName.isEmpty()) { - emit error(i18n("CSV source %1 does not exist.", sourcename)); - emit failed(d->m_kmmID, d->m_webID); - return false; - } - } - - if (isCurrency) { - // this is a two-symbol quote. split the symbol into two. valid symbol - // characters are: 0-9, A-Z and the dot. anything else is a separator - QRegularExpression splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match; - // if we've truly found 2 symbols delimited this way... - if (d->m_webID.indexOf(splitrx, 0, &match) != -1) { - url = QUrl(urlStr.arg(match.captured(1), match.captured(2))); - d->m_CSVSource.m_currencySymbol = match.captured(2); - d->m_CSVSource.m_securitySymbol = match.captured(1); - } else { - qCDebug(WEBPRICEQUOTE) << "WebPriceQuote::launch() did not find 2 symbols"; - emit error(i18n("Cannot find from and to currency.")); - emit failed(d->m_kmmID, d->m_webID); - return false; - } - - } else { - // a regular one-symbol quote - url = QUrl(urlStr.arg(d->m_webID)); - d->m_CSVSource.m_securityName = MyMoneyFile::instance()->security(d->m_kmmID).name(); - d->m_CSVSource.m_securitySymbol = MyMoneyFile::instance()->security(d->m_kmmID).tradingSymbol(); - } - - if (url.isLocalFile()) { - emit error(i18n("Local quote sources aren't supported.")); - emit failed(d->m_kmmID, d->m_webID); - return false; - } else { - //silent download - emit status(i18n("Fetching URL %1...", url.toDisplayString())); - QString tmpFile; - { - QTemporaryFile tmpFileFile; - tmpFileFile.setAutoRemove(false); - if (tmpFileFile.open()) - qDebug() << "created tmpfile"; - - tmpFile = tmpFileFile.fileName(); - } - QFile::remove(tmpFile); - const QUrl dest = QUrl::fromLocalFile(tmpFile); - KIO::Scheduler::checkSlaveOnHold(true); - KIO::Job *job = KIO::file_copy(url, dest, -1, KIO::HideProgressInfo); - connect(job, SIGNAL(result(KJob*)), - this, SLOT(downloadCSV(KJob*))); - } - return true; -} - -bool WebPriceQuote::launchNative(const QString& _webID, const QString& _kmmID, const QString& _sourcename) -{ - d->m_webID = _webID; - d->m_kmmID = _kmmID; - - if (_webID == i18n("[No identifier]")) { - emit error(i18n("%1 skipped because it doesn't have identification number.", _kmmID)); - emit failed(d->m_kmmID, d->m_webID); - return false; - } -// emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); - - // Get sources from the config file - QString sourcename = _sourcename; - if (sourcename.isEmpty()) - sourcename = "Yahoo"; - - if (quoteSources().contains(sourcename)) - d->m_source = WebPriceQuoteSource(sourcename); - else { - emit error(i18n("Source %1 does not exist.", sourcename)); - emit failed(d->m_kmmID, d->m_webID); - return false; - } - - QUrl url; - - // if the source has room for TWO symbols.. - if (d->m_source.m_url.contains("%2")) { - // this is a two-symbol quote. split the symbol into two. valid symbol - // characters are: 0-9, A-Z and the dot. anything else is a separator - QRegularExpression splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match; - // if we've truly found 2 symbols delimited this way... - if (d->m_webID.indexOf(splitrx, 0, &match) != -1) { - url = QUrl(d->m_source.m_url.arg(match.captured(1), match.captured(2))); - } else { - qCDebug(WEBPRICEQUOTE) << "WebPriceQuote::launch() did not find 2 symbols"; - } - } else { - // a regular one-symbol quote - url = QUrl(d->m_source.m_url.arg(d->m_webID)); - } - - if (url.isLocalFile()) { - emit status(i18nc("The process x is executing", "Executing %1...", url.toLocalFile())); - - QString program; - QStringList arguments = url.toLocalFile().split(' ', QString::SkipEmptyParts); - if (!arguments.isEmpty()) { - program = arguments.first(); - arguments.removeFirst(); - } - d->m_filter.setWebID(d->m_webID); - - d->m_filter.setProcessChannelMode(QProcess::MergedChannels); - d->m_filter.start(program, arguments); - - if (!d->m_filter.waitForStarted()) { - emit error(i18n("Unable to launch: %1", url.toLocalFile())); - slotParseQuote(QString()); - } - } else { - //silent download - emit status(i18n("Fetching URL %1...", url.toDisplayString())); - QString tmpFile; - { - QTemporaryFile tmpFileFile; - tmpFileFile.setAutoRemove(false); - if (tmpFileFile.open()) - qDebug() << "created tmpfile"; - - tmpFile = tmpFileFile.fileName(); - } - QFile::remove(tmpFile); - const QUrl dest = QUrl::fromLocalFile(tmpFile); - KIO::Scheduler::checkSlaveOnHold(true); - KIO::Job *job = KIO::file_copy(url, dest, -1, KIO::HideProgressInfo); - connect(job, SIGNAL(result(KJob*)), - this, SLOT(downloadResult(KJob*))); - } - return true; -} - -void WebPriceQuote::downloadCSV(KJob* job) -{ - QString tmpFile = dynamic_cast(job)->destUrl().toLocalFile(); - QUrl url = dynamic_cast(job)->srcUrl(); - if (!job->error()) - { - qDebug() << "Downloaded" << tmpFile << "from" << url; - QFile f(tmpFile); - if (f.open(QIODevice::ReadOnly)) { - f.close(); - slotParseCSVQuote(tmpFile); - } else { - emit error(i18n("Failed to open downloaded file")); - slotParseCSVQuote(QString()); - } - } else { - emit error(job->errorString()); - slotParseCSVQuote(QString()); - } -} - -void WebPriceQuote::downloadResult(KJob* job) -{ - QString tmpFile = dynamic_cast(job)->destUrl().toLocalFile(); - QUrl url = dynamic_cast(job)->srcUrl(); - if (!job->error()) - { - qDebug() << "Downloaded" << tmpFile << "from" << url; - QFile f(tmpFile); - if (f.open(QIODevice::ReadOnly)) { - // Find out the page encoding and convert it to unicode - QByteArray page = f.readAll(); - KEncodingProber prober(KEncodingProber::Universal); - prober.feed(page); - QTextCodec* codec = QTextCodec::codecForName(prober.encoding()); - if (!codec) - codec = QTextCodec::codecForLocale(); - QString quote = codec->toUnicode(page); - f.close(); - slotParseQuote(quote); - } else { - emit error(i18n("Failed to open downloaded file")); - slotParseQuote(QString()); - } - QFile::remove(tmpFile); - } else { - emit error(job->errorString()); - slotParseQuote(QString()); - } -} - -bool WebPriceQuote::launchFinanceQuote(const QString& _webID, const QString& _kmmID, - const QString& _sourcename) -{ - bool result = true; - d->m_webID = _webID; - d->m_kmmID = _kmmID; - QString FQSource = _sourcename.section(' ', 1); - d->m_source = WebPriceQuoteSource(_sourcename, m_financeQuoteScriptPath, m_financeQuoteScriptPath, - "\"([^,\"]*)\",.*", // webIDRegExp - WebPriceQuoteSource::identifyBy::Symbol, - "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp - "[^,]*,([^,]*),.*", // date regexp - "%y-%m-%d"); // date format - - //emit status(QString("(Debug) symbol=%1 id=%2...").arg(_symbol,_id)); - - QStringList arguments; - arguments << m_financeQuoteScriptPath << FQSource << KShell::quoteArg(_webID); - d->m_filter.setWebID(d->m_webID); - emit status(i18nc("Executing 'script' 'online source' 'investment symbol' ", "Executing %1 %2 %3...", m_financeQuoteScriptPath, FQSource, _webID)); - - d->m_filter.setProcessChannelMode(QProcess::MergedChannels); - d->m_filter.start(QLatin1Literal("perl"), arguments); - - // This seems to work best if we just block until done. - if (d->m_filter.waitForFinished()) { - result = true; - } else { - emit error(i18n("Unable to launch: %1", m_financeQuoteScriptPath)); - slotParseQuote(QString()); - } - - return result; -} - -void WebPriceQuote::slotParseCSVQuote(const QString& filename) -{ - bool isOK = true; - if (filename.isEmpty()) - isOK = false; - else { - MyMoneyStatement st; - CSVImporterCore* csvImporter = new CSVImporterCore; - st = csvImporter->unattendedImport(filename, &d->m_CSVSource); - if (!st.m_listPrices.isEmpty()) - emit csvquote(d->m_kmmID, d->m_webID, st); - else - isOK = false; - delete csvImporter; - QFile::remove(filename); - } - - if (!isOK) { - emit error(i18n("Unable to update price for %1", d->m_webID)); - emit failed(d->m_kmmID, d->m_webID); - } -} - -void WebPriceQuote::slotParseQuote(const QString& _quotedata) -{ - QString quotedata = _quotedata; - d->m_quoteData = quotedata; - - qCDebug(WEBPRICEQUOTE) << "quotedata" << _quotedata; - - if (! quotedata.isEmpty()) { - if (!d->m_source.m_skipStripping) { - // First, remove extraneous non-data elements - - // HTML tags - quotedata.remove(QRegularExpression("<[^>]*>")); - - // &...;'s - quotedata.replace(QRegularExpression("&\\w+;"), QLatin1String(" ")); - - // Extra white space - quotedata = quotedata.simplified(); - qCDebug(WEBPRICEQUOTE) << "stripped text" << quotedata; - } - - QRegularExpression webIDRegExp(d->m_source.m_webID); - QRegularExpression dateRegExp(d->m_source.m_date); - QRegularExpression priceRegExp(d->m_source.m_price); - QRegularExpressionMatch match; - - if (quotedata.indexOf(webIDRegExp, 0, &match) > -1) { - qCDebug(WEBPRICEQUOTE) << "Identifier" << match.captured(1); - emit status(i18n("Identifier found: '%1'", match.captured(1))); - } - - bool gotprice = false; - bool gotdate = false; - - if (quotedata.indexOf(priceRegExp, 0, &match) > -1) { - gotprice = true; - QString pricestr = match.captured(1); - - // Deal with exponential prices - // we extract the exponent and add it again before we convert to a double - QRegularExpression expRegExp("[eE][+-]?\\D+"); - int pos; - QString exponent; - if ((pos = pricestr.indexOf(expRegExp, 0, &match)) > -1) { - exponent = pricestr.mid(pos); - pricestr = pricestr.left(pos); - } - - // Deal with european quotes that come back as X.XXX,XX or XX,XXX - // - // We will make the assumption that ALL prices have a decimal separator. - // So "1,000" always means 1.0, not 1000.0. - // - // Remove all non-digits from the price string except the last one, and - // set the last one to a period. - - pos = pricestr.lastIndexOf(QRegularExpression("\\D")); - if (pos > 0) { - pricestr[pos] = QLatin1Char('.'); - pos = pricestr.lastIndexOf(QRegularExpression("\\D"), pos - 1); - } - while (pos > 0) { - pricestr.remove(pos, 1); - pos = pricestr.lastIndexOf(QRegularExpression("\\D"), pos); - } - pricestr.append(exponent); - - d->m_price = pricestr.toDouble(); - qCDebug(WEBPRICEQUOTE) << "Price" << pricestr; - emit status(i18n("Price found: '%1' (%2)", pricestr, d->m_price)); - } - - if (quotedata.indexOf(dateRegExp, 0, &match) > -1) { - QString datestr = match.captured(1); - - MyMoneyDateFormat dateparse(d->m_source.m_dateformat); - try { - d->m_date = dateparse.convertString(datestr, false /*strict*/); - gotdate = true; - qCDebug(WEBPRICEQUOTE) << "Date" << datestr; - emit status(i18n("Date found: '%1'", d->m_date.toString()));; - } catch (const MyMoneyException &) { - // emit error(i18n("Unable to parse date %1 using format %2: %3").arg(datestr,dateparse.format(),e.what())); - d->m_date = QDate::currentDate(); - gotdate = true; - } - } - - if (gotprice && gotdate) { - emit quote(d->m_kmmID, d->m_webID, d->m_date, d->m_price); - } else { - emit error(i18n("Unable to update price for %1 (no price or no date)", d->m_webID)); - emit failed(d->m_kmmID, d->m_webID); - } - } else { - emit error(i18n("Unable to update price for %1 (empty quote data)", d->m_webID)); - emit failed(d->m_kmmID, d->m_webID); - } -} - -const QMap WebPriceQuote::defaultCSVQuoteSources() -{ - QMap result; - - // tip: possible delimiter indexes are in csvenums.h - - result[QLatin1String("Stooq")] = PricesProfile(QLatin1String("Stooq"), - 106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Semicolon, - TextDelimiter::DoubleQuote, DecimalSymbol::Dot, - QMap{{Column::Date, 0}, {Column::Price, 4}}, - 2, Profile::StockPrices); - - result[QLatin1String("Stooq Currency")] = PricesProfile(QLatin1String("Stooq Currency"), - 106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Semicolon, - TextDelimiter::DoubleQuote, DecimalSymbol::Dot, - QMap{{Column::Date, 0}, {Column::Price, 4}}, - 2, Profile::CurrencyPrices); - - result[QLatin1String("Yahoo")] = PricesProfile(QLatin1String("Yahoo"), - 106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Comma, - TextDelimiter::DoubleQuote, DecimalSymbol::Dot, - QMap{{Column::Date, 0}, {Column::Price, 4}}, - 2, Profile::StockPrices); - - result[QLatin1String("Nasdaq Baltic - Shares")] = PricesProfile(QLatin1String("Nasdaq Baltic - Shares"), - 106, 1, 0, DateFormat::DayMonthYear, FieldDelimiter::Tab, - TextDelimiter::DoubleQuote, DecimalSymbol::Dot, - QMap{{Column::Date, 0}, {Column::Price, 5}}, - 2, Profile::StockPrices); - - result[QLatin1String("Nasdaq Baltic - Funds")] = PricesProfile(QLatin1String("Nasdaq Baltic - Funds"), - 106, 1, 0, DateFormat::DayMonthYear, FieldDelimiter::Tab, - TextDelimiter::DoubleQuote, DecimalSymbol::Dot, - QMap{{Column::Date, 0}, {Column::Price, 5}}, - 2, Profile::StockPrices); - return result; -} - -const QMap WebPriceQuote::defaultQuoteSources() -{ - QMap result; - - // Use fx-rate.net as the standard currency exchange rate source until - // we have the capability to use more than one source. Use a neutral - // name for the source. - result["KMyMoney Currency"] = WebPriceQuoteSource("KMyMoney Currency", - "https://fx-rate.net/%1/%2", - QString(), - "https://fx-rate.net/([^/]+/[^/]+)", - WebPriceQuoteSource::identifyBy::Symbol, - "1[ a-zA-Z]+=\\s*([,\\d+\\.]+)", - "updated\\s\\d+:\\d+:\\d+\\(\\w+\\)\\s+(\\d{1,2}/\\d{2}/\\d{4})", - "%d/%m/%y", - true // skip HTML stripping - ); - - // Update on 2017-06 by Łukasz Wojniłowicz - result["Globe & Mail"] = WebPriceQuoteSource("Globe & Mail", - "http://globefunddb.theglobeandmail.com/gishome/plsql/gis.price_history?pi_fund_id=%1", - QString(), - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::IdentificationNumber, - "Fund Price:\\D+(\\d+\\.\\d+)", // priceregexp - "Fund Price:.+as at (\\w+ \\d+, \\d+)\\)", // dateregexp - "%m %d %y" // dateformat - ); - - // Update on 2017-06 by Łukasz Wojniłowicz - result["MSN"] = WebPriceQuoteSource("MSN", - "http://www.msn.com/en-us/money/stockdetails/%1", - QString(), - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::Symbol, - "(\\d+\\.\\d+) [+-]\\d+.\\d+", // priceregexp - "(\\d+/\\d+/\\d+)", //dateregexp - "%y %m %d" // dateformat - ); - - // Finanztreff (replaces VWD.DE) supplied by Michael Zimmerman - result["Finanztreff"] = WebPriceQuoteSource("Finanztreff", - "http://finanztreff.de/kurse_einzelkurs_detail.htn?u=100&i=%1", - "", - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::IdentificationNumber, - "([0-9]+,\\d+).+Gattung:Fonds", // priceregexp - "\\).(\\d+\\D+\\d+\\D+\\d+)", // dateregexp (doesn't work; date in chart - "%d.%m.%y" // dateformat - ); - - // First revision by Michael Zimmerman - // Update on 2017-06 by Łukasz Wojniłowicz - result["boerseonlineaktien"] = WebPriceQuoteSource("Börse Online - Aktien", - "http://www.boerse-online.de/aktie/%1-Aktie", - QString(), - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::Name, - "Aktienkurs\\D+(\\d+,\\d{2})", // priceregexp - "Datum (\\d{2}\\.\\d{2}\\.\\d{4})", // dateregexp - "%d.%m.%y" // dateformat - ); - - // This quote source provided by e-mail and should replace the previous one: - // From: David Houlden - // To: kmymoney@kde.org - // Date: Sat, 6 Apr 2013 13:22:45 +0100 - // Updated on 2017-06 by Łukasz Wojniłowicz - result["Financial Times - UK Funds"] = WebPriceQuoteSource("Financial Times", - "http://funds.ft.com/uk/Tearsheet/Summary?s=%1", - QString(), - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::IdentificationNumber, - "Price\\D+([\\d,]*\\d+\\.\\d+)", // price regexp - "Data delayed at least 15 minutes, as of\\ (.*)\\.", // date regexp - "%m %d %y", // date format - true // skip HTML stripping - ); - - // The following two price sources were contributed by - // Marc Zahnlecker - - result["Wallstreet-Online.DE (Default)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Default)", - "http://www.wallstreet-online.de/si/?k=%1&spid=ws", - "", - "Symbol:(\\w+)", // webIDRegExp - WebPriceQuoteSource::identifyBy::Symbol, - "Letzter Kurs: ([0-9.]+,\\d+)", // priceregexp - ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp - "%d %m %y" // dateformat - ); - - // (tf2k) The "mpid" is I think the market place id. In this case five - // stands for Hamburg. - // - // Here the id for several market places: 2 Frankfurt, 3 Berlin, 4 - // Düsseldorf, 5 Hamburg, 6 München/Munich, 7 Hannover, 9 Stuttgart, 10 - // Xetra, 32 NASDAQ, 36 NYSE - - result["Wallstreet-Online.DE (Hamburg)"] = WebPriceQuoteSource("Wallstreet-Online.DE (Hamburg)", - "http://fonds.wallstreet-online.de/si/?k=%1&spid=ws&mpid=5", - "", - "Symbol:(\\w+)", // webIDRegExp - WebPriceQuoteSource::identifyBy::Symbol, - "Fonds \\(EUR\\) ([0-9.]+,\\d+)", // priceregexp - ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp - "%d %m %y" // dateformat - ); - // First revision on 2017-06 by Łukasz Wojniłowicz - result["Puls Biznesu"] = WebPriceQuoteSource("Puls Biznesu", - "http://notowania.pb.pl/instrument/%1", - QString(), - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::IdentificationNumber, - "(\\d+,\\d{2})\\D+\\d+,\\d{2}%", // price regexp - "(\\d{4}-\\d{2}-\\d{2}) \\d{2}:\\d{2}:\\d{2}", // date regexp - "%y %m %d" // date format - ); - - // The following price quote was contributed by - // Piotr Adacha - - // I would like to post new Online Query Settings for KMyMoney. This set is - // suitable to query stooq.com service, providing quotes for stocks, futures, - // mutual funds and other financial instruments from Polish Gielda Papierow - // Wartosciowych (GPW). Unfortunately, none of well-known international - // services provide quotes for this market (biggest one in central and eastern - // Europe), thus, I think it could be helpful for Polish users of KMyMoney (and - // I am one of them for almost a year). - - // Update on 2017-06 by Łukasz Wojniłowicz - result["Stooq"] = WebPriceQuoteSource("Stooq", - "http://stooq.com/q/?s=%1", - "http://stooq.pl/q/d/l/?s=%1&d1=%y%m%d&d2=%y%m%d&i=d&c=1", - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::Symbol, - "Last(\\d+\\.\\d+).*Date", // price regexp - "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp - "%y %m %d" // date format - ); - - // First revision on 2017-06 by Łukasz Wojniłowicz - result[QLatin1String("Stooq Currency")] = WebPriceQuoteSource("Stooq Currency", - "http://stooq.com/q/?s=%1%2", - "http://stooq.pl/q/d/l/?s=%1%2&d1=%y%m%d&d2=%y%m%d&i=d&c=1", - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::Symbol, - "Last.*(\\d+\\.\\d+).*Date", // price regexp - "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp - "%y %m %d" // date format - ); - - // First revision on 2017-06 by Łukasz Wojniłowicz - result["Nasdaq Baltic - Shares"] = WebPriceQuoteSource("Nasdaq Baltic - Shares", - "http://www.nasdaqbaltic.com/market/?pg=details&instrument=%1&lang=en", - "http://www.nasdaqbaltic.com/market/?instrument=%1&pg=details&tab=historical&lang=en&date=&start=%d.%m.%y&end=%d.%m.%y&pg=details&pg2=equity&downloadcsv=1&csv_style=english", - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::IdentificationNumber, - "lastPrice\\D+(\\d+,\\d+)", // priceregexp - "as of: (\\d{2}.\\d{2}.\\d{4})", // dateregexp - "%d.%m.%y", // dateformat - true - ); - - // First revision on 2017-06 by Łukasz Wojniłowicz - result["Nasdaq Baltic - Funds"] = WebPriceQuoteSource("Nasdaq Baltic - Funds", - "http://www.nasdaqbaltic.com/market/?pg=details&instrument=%1&lang=en", - "http://www.nasdaqbaltic.com/market/?instrument=%1&pg=details&tab=historical&lang=en&date=&start=%d.%m.%y&end=%d.%m.%y&pg=details&pg2=equity&downloadcsv=1&csv_style=english", - QString(), // webIDRegExp - WebPriceQuoteSource::identifyBy::IdentificationNumber, - "marketShareDetailTable(.+\\n){21}\\D+(\\d+)", // priceregexp - "as of: (\\d{2}.\\d{2}.\\d{4})", // dateregexp - "%d.%m.%y", // dateformat - true - ); - return result; -} - -const QStringList WebPriceQuote::quoteSources(const _quoteSystemE _system) -{ - if (_system == Native) - return (quoteSourcesNative()); - else - return (quoteSourcesFinanceQuote()); -} - -const QStringList WebPriceQuote::quoteSourcesNative() -{ - KSharedConfigPtr kconfig = KSharedConfig::openConfig(); - QStringList groups = kconfig->groupList(); - - QStringList::Iterator it; - QRegularExpression onlineQuoteSource(QString("^Online-Quote-Source-(.*)$")); - QRegularExpressionMatch match; - - // get rid of all 'non online quote source' entries - for (it = groups.begin(); it != groups.end(); it = groups.erase(it)) { - if ((*it).indexOf(onlineQuoteSource, 0, &match) >= 0) { - // Insert the name part - it = groups.insert(it, match.captured(1)); - ++it; - } - } - - // if the user has the OLD quote source defined, now is the - // time to remove that entry and convert it to the new system. - if (! groups.count() && kconfig->hasGroup("Online Quotes Options")) { - KConfigGroup grp = kconfig->group("Online Quotes Options"); - QString url(grp.readEntry("URL", "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1")); - QString webIDRegExp(grp.readEntry("SymbolRegex", "\"([^,\"]*)\",.*")); - QString priceRegExp(grp.readEntry("PriceRegex", "[^,]*,([^,]*),.*")); - QString dateRegExp(grp.readEntry("DateRegex", "[^,]*,[^,]*,\"([^\"]*)\"")); - kconfig->deleteGroup("Online Quotes Options"); - - groups += "Old Source"; - grp = kconfig->group(QString(QLatin1String("Online-Quote-Source-%1")).arg("Old Source")); - grp.writeEntry("URL", url); - grp.writeEntry("CSVURL", "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1"); - grp.writeEntry("IDRegex", webIDRegExp); - grp.writeEntry("PriceRegex", priceRegExp); - grp.writeEntry("DateRegex", dateRegExp); - grp.writeEntry("DateFormatRegex", "%m %d %y"); - grp.sync(); - } - - // if the user has OLD quote source based only on symbols (and not ISIN) - // now is the time to convert it to the new system. - foreach (const auto group, groups) { - KConfigGroup grp = kconfig->group(QString(QLatin1String("Online-Quote-Source-%1")).arg(group)); - if (grp.hasKey("SymbolRegex")) { - grp.writeEntry("IDRegex", grp.readEntry("SymbolRegex")); - grp.deleteEntry("SymbolRegex"); - } else - break; - } - - // Set up each of the default sources. These are done piecemeal so that - // when we add a new source, it's automatically picked up. And any changes - // are also picked up. - QMap defaults = defaultQuoteSources(); - QMap::const_iterator it_source = defaults.constBegin(); - while (it_source != defaults.constEnd()) { - if (! groups.contains((*it_source).m_name)) { - groups += (*it_source).m_name; - (*it_source).write(); - kconfig->sync(); - } - ++it_source; - } - - return groups; -} - -const QStringList WebPriceQuote::quoteSourcesFinanceQuote() -{ - if (m_financeQuoteSources.empty()) { // run the process one time only - // since this is a static function it can be called without constructing an object - // so we need to make sure that m_financeQuoteScriptPath is properly initialized - if (m_financeQuoteScriptPath.isEmpty()) { - m_financeQuoteScriptPath = QStandardPaths::locate(QStandardPaths::DataLocation, QString("misc/financequote.pl")); - } - FinanceQuoteProcess getList; - getList.launch(m_financeQuoteScriptPath); - while (!getList.isFinished()) { - QCoreApplication::processEvents(); - } - m_financeQuoteSources = getList.getSourceList(); - } - return (m_financeQuoteSources); -} - -// -// Helper class to load/save an individual source -// - -WebPriceQuoteSource::WebPriceQuoteSource(const QString& name, const QString& url, const QString &csvUrl, const QString& id, const identifyBy idBy, const QString& price, const QString& date, const QString& dateformat, bool skipStripping): - m_name(name), - m_url(url), - m_csvUrl(csvUrl), - m_webID(id), - m_webIDBy(idBy), - m_price(price), - m_date(date), - m_dateformat(dateformat), - m_skipStripping(skipStripping) -{ } - -WebPriceQuoteSource::WebPriceQuoteSource(const QString& name) -{ - m_name = name; - KSharedConfigPtr kconfig = KSharedConfig::openConfig(); - KConfigGroup grp = kconfig->group(QString("Online-Quote-Source-%1").arg(m_name)); - m_webID = grp.readEntry("IDRegex"); - m_webIDBy = static_cast(grp.readEntry("IDBy", "0").toInt()); - m_date = grp.readEntry("DateRegex"); - m_dateformat = grp.readEntry("DateFormatRegex", "%m %d %y"); - m_price = grp.readEntry("PriceRegex"); - m_url = grp.readEntry("URL"); - m_csvUrl = grp.readEntry("CSVURL"); - m_skipStripping = grp.readEntry("SkipStripping", false); -} - -void WebPriceQuoteSource::write() const -{ - KSharedConfigPtr kconfig = KSharedConfig::openConfig(); - KConfigGroup grp = kconfig->group(QString("Online-Quote-Source-%1").arg(m_name)); - grp.writeEntry("URL", m_url); - grp.writeEntry("CSVURL", m_csvUrl); - grp.writeEntry("PriceRegex", m_price); - grp.writeEntry("DateRegex", m_date); - grp.writeEntry("DateFormatRegex", m_dateformat); - grp.writeEntry("IDRegex", m_webID); - grp.writeEntry("IDBy", static_cast(m_webIDBy)); - if (m_skipStripping) - grp.writeEntry("SkipStripping", m_skipStripping); - else - grp.deleteEntry("SkipStripping"); - kconfig->sync(); -} - -void WebPriceQuoteSource::rename(const QString& name) -{ - remove(); - m_name = name; - write(); -} - -void WebPriceQuoteSource::remove() const -{ - KSharedConfigPtr kconfig = KSharedConfig::openConfig(); - kconfig->deleteGroup(QString("Online-Quote-Source-%1").arg(m_name)); - kconfig->sync(); -} - -// -// Helper class to babysit the KProcess used for running the local script in that case -// - -WebPriceQuoteProcess::WebPriceQuoteProcess() -{ - connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedDataFromFilter())); - connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus))); -} - -void WebPriceQuoteProcess::slotReceivedDataFromFilter() -{ -// qDebug() << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << QString(data); - m_string += QString(readAllStandardOutput()); -} - -void WebPriceQuoteProcess::slotProcessExited(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) -{ -// qDebug() << "WebPriceQuoteProcess::slotProcessExited()"; - emit processExited(m_string); - m_string.truncate(0); -} - -// -// Helper class to babysit the KProcess used for running the Finance Quote sources script -// - -FinanceQuoteProcess::FinanceQuoteProcess() -{ - m_isDone = false; - m_string = ""; - m_fqNames["aex"] = "AEX"; - m_fqNames["aex_futures"] = "AEX Futures"; - m_fqNames["aex_options"] = "AEX Options"; - m_fqNames["amfiindia"] = "AMFI India"; - m_fqNames["asegr"] = "ASE"; - m_fqNames["asia"] = "Asia (Yahoo, ...)"; - m_fqNames["asx"] = "ASX"; - m_fqNames["australia"] = "Australia (ASX, Yahoo, ...)"; - m_fqNames["bmonesbittburns"] = "BMO NesbittBurns"; - m_fqNames["brasil"] = "Brasil (Yahoo, ...)"; - m_fqNames["canada"] = "Canada (Yahoo, ...)"; - m_fqNames["canadamutual"] = "Canada Mutual (Fund Library, ...)"; - m_fqNames["deka"] = "Deka Investments"; - m_fqNames["dutch"] = "Dutch (AEX, ...)"; - m_fqNames["dwsfunds"] = "DWS"; - m_fqNames["europe"] = "Europe (Yahoo, ...)"; - m_fqNames["fidelity"] = "Fidelity (Fidelity, ...)"; - m_fqNames["fidelity_direct"] = "Fidelity Direct"; - m_fqNames["financecanada"] = "Finance Canada"; - m_fqNames["ftportfolios"] = "First Trust (First Trust, ...)"; - m_fqNames["ftportfolios_direct"] = "First Trust Portfolios"; - m_fqNames["fundlibrary"] = "Fund Library"; - m_fqNames["greece"] = "Greece (ASE, ...)"; - m_fqNames["indiamutual"] = "India Mutual (AMFI, ...)"; - m_fqNames["maninv"] = "Man Investments"; - m_fqNames["fool"] = "Motley Fool"; - m_fqNames["nasdaq"] = "Nasdaq (Yahoo, ...)"; - m_fqNames["nz"] = "New Zealand (Yahoo, ...)"; - m_fqNames["nyse"] = "NYSE (Yahoo, ...)"; - m_fqNames["nzx"] = "NZX"; - m_fqNames["platinum"] = "Platinum Asset Management"; - m_fqNames["seb_funds"] = "SEB"; - m_fqNames["sharenet"] = "Sharenet"; - m_fqNames["za"] = "South Africa (Sharenet, ...)"; - m_fqNames["troweprice_direct"] = "T. Rowe Price"; - m_fqNames["troweprice"] = "T. Rowe Price"; - m_fqNames["tdefunds"] = "TD Efunds"; - m_fqNames["tdwaterhouse"] = "TD Waterhouse Canada"; - m_fqNames["tiaacref"] = "TIAA-CREF"; - m_fqNames["trustnet"] = "Trustnet"; - m_fqNames["uk_unit_trusts"] = "U.K. Unit Trusts"; - m_fqNames["unionfunds"] = "Union Investments"; - m_fqNames["tsp"] = "US Govt. Thrift Savings Plan"; - m_fqNames["usfedbonds"] = "US Treasury Bonds"; - m_fqNames["usa"] = "USA (Yahoo, Fool ...)"; - m_fqNames["vanguard"] = "Vanguard"; - m_fqNames["vwd"] = "VWD"; - m_fqNames["yahoo"] = "Yahoo"; - m_fqNames["yahoo_asia"] = "Yahoo Asia"; - m_fqNames["yahoo_australia"] = "Yahoo Australia"; - m_fqNames["yahoo_brasil"] = "Yahoo Brasil"; - m_fqNames["yahoo_europe"] = "Yahoo Europe"; - m_fqNames["yahoo_nz"] = "Yahoo New Zealand"; - m_fqNames["zifunds"] = "Zuerich Investments"; - connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedDataFromFilter())); - connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited())); - connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(slotProcessExited())); -} - -void FinanceQuoteProcess::slotReceivedDataFromFilter() -{ - QByteArray data(readAllStandardOutput()); - -// qDebug() << "WebPriceQuoteProcess::slotReceivedDataFromFilter(): " << QString(data); - m_string += QString(data); -} - -void FinanceQuoteProcess::slotProcessExited() -{ -// qDebug() << "WebPriceQuoteProcess::slotProcessExited()"; - m_isDone = true; -} - -void FinanceQuoteProcess::launch(const QString& scriptPath) -{ - QStringList arguments; - arguments << scriptPath << QLatin1Literal("-l"); - setProcessChannelMode(QProcess::ForwardedOutputChannel); - start(QLatin1Literal("perl"), arguments); - if (! waitForStarted()) qWarning("Unable to start FQ script"); - return; -} - -const QStringList FinanceQuoteProcess::getSourceList() const -{ - QStringList raw = m_string.split(0x0A, QString::SkipEmptyParts); - QStringList sources; - QStringList::iterator it; - for (it = raw.begin(); it != raw.end(); ++it) { - if (m_fqNames[*it].isEmpty()) sources.append(*it); - else sources.append(m_fqNames[*it]); - } - sources.sort(); - return (sources); -} - -const QString FinanceQuoteProcess::crypticName(const QString& niceName) const -{ - QString ret(niceName); - fqNameMap::const_iterator it; - for (it = m_fqNames.begin(); it != m_fqNames.end(); ++it) { - if (niceName == it.value()) { - ret = it.key(); - break; - } - } - return (ret); -} - -const QString FinanceQuoteProcess::niceName(const QString& crypticName) const -{ - QString ret(m_fqNames[crypticName]); - if (ret.isEmpty()) ret = crypticName; - return (ret); -} -// -// Universal date converter -// - -// In 'strict' mode, this is designed to be compatible with the QIF profile date -// converter. However, that converter deals with the concept of an apostrophe -// format in a way I don't understand. So for the moment, they are 99% -// compatible, waiting on that issue. (acejones) - -const QDate MyMoneyDateFormat::convertString(const QString& _in, bool _strict, unsigned _centurymidpoint) const -{ - // - // Break date format string into component parts - // - - QRegularExpression formatrex("%([mdy]+)(\\W+)%([mdy]+)(\\W+)%([mdy]+)", QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match; - if (m_format.indexOf(formatrex, 0, &match) == -1) { - throw MYMONEYEXCEPTION_CSTRING("Invalid format string"); - } - - QStringList formatParts; - formatParts += match.captured(1); - formatParts += match.captured(3); - formatParts += match.captured(5); - - QStringList formatDelimiters; - formatDelimiters += match.captured(2); - formatDelimiters += match.captured(4); - match = QRegularExpressionMatch(); - - // - // Break input string up into component parts, - // using the delimiters found in the format string - // - - QRegularExpression inputrex; - inputrex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - - // strict mode means we must enforce the delimiters as specified in the - // format. non-strict allows any delimiters - if (_strict) - inputrex.setPattern(QString("(\\w+)%1(\\w+)%2(\\w+)").arg(formatDelimiters[0], formatDelimiters[1])); - else - inputrex.setPattern("(\\w+)\\W+(\\w+)\\W+(\\w+)"); - - if (_in.indexOf(inputrex, 0, &match) == -1) { - throw MYMONEYEXCEPTION_CSTRING("Invalid input string"); - } - - QStringList scannedParts; - scannedParts += match.captured(1).toLower(); - scannedParts += match.captured(2).toLower(); - scannedParts += match.captured(3).toLower(); - match = QRegularExpressionMatch(); - - // - // Convert the scanned parts into actual date components - // - unsigned day = 0, month = 0, year = 0; - bool ok; - QRegularExpression digitrex("(\\d+)"); - QStringList::const_iterator it_scanned = scannedParts.constBegin(); - QStringList::const_iterator it_format = formatParts.constBegin(); - while (it_scanned != scannedParts.constEnd()) { - // decide upon the first character of the part - switch ((*it_format).at(0).cell()) { - case 'd': - // remove any extraneous non-digits (e.g. read "3rd" as 3) - ok = false; - if ((*it_scanned).indexOf(digitrex, 0, &match) != -1) - day = match.captured(1).toUInt(&ok); - if (!ok || day > 31) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid day entry: %1").arg(*it_scanned)); - break; - case 'm': - month = (*it_scanned).toUInt(&ok); - if (!ok) { - month = 0; - // maybe it's a textual date - unsigned i = 1; - // search the name in the current selected locale - QLocale locale; - while (i <= 12) { - if (locale.standaloneMonthName(i).toLower() == *it_scanned - || locale.standaloneMonthName(i, QLocale::ShortFormat).toLower() == *it_scanned) { - month = i; - break; - } - ++i; - } - // in case we did not find the month in the current locale, - // we look for it in the C locale - if(month == 0) { - QLocale localeC(QLocale::C); - if( !(locale == localeC)) { - i = 1; - while (i <= 12) { - if (localeC.standaloneMonthName(i).toLower() == *it_scanned - || localeC.standaloneMonthName(i, QLocale::ShortFormat).toLower() == *it_scanned) { - month = i; - break; - } - ++i; - } - } - } - } - - if (month < 1 || month > 12) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid month entry: %1").arg(*it_scanned)); - - break; - case 'y': - if (_strict && (*it_scanned).length() != (*it_format).length()) - throw MYMONEYEXCEPTION(QString::fromLatin1("Length of year (%1) does not match expected length (%2).") - .arg(*it_scanned, *it_format)); - - year = (*it_scanned).toUInt(&ok); - - if (!ok) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid year entry: %1").arg(*it_scanned)); - - // - // 2-digit year case - // - // this algorithm will pick a year within +/- 50 years of the - // centurymidpoint parameter. i.e. if the midpoint is 2000, - // then 0-49 will become 2000-2049, and 50-99 will become 1950-1999 - if (year < 100) { - unsigned centuryend = _centurymidpoint + 50; - unsigned centurybegin = _centurymidpoint - 50; - - if (year < centuryend % 100) - year += 100; - year += centurybegin - centurybegin % 100; - } - - if (year < 1900) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid year (%1)").arg(year)); - - break; - default: - throw MYMONEYEXCEPTION_CSTRING("Invalid format character"); - } - - ++it_scanned; - ++it_format; - } - QDate result(year, month, day); - if (! result.isValid()) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid date (yr%1 mo%2 dy%3)").arg(year).arg(month).arg(day)); - - return result; -} - -// -// Unit test helpers -// - -convertertest::QuoteReceiver::QuoteReceiver(WebPriceQuote* q, QObject* parent) : - QObject(parent) -{ - connect(q, SIGNAL(quote(QString,QString,QDate,double)), - this, SLOT(slotGetQuote(QString,QString,QDate,double))); - connect(q, SIGNAL(status(QString)), - this, SLOT(slotStatus(QString))); - connect(q, SIGNAL(error(QString)), - this, SLOT(slotError(QString))); -} - -convertertest::QuoteReceiver::~QuoteReceiver() -{ -} - -void convertertest::QuoteReceiver::slotGetQuote(const QString&, const QString&, const QDate& d, const double& m) -{ -// qDebug() << "test::QuoteReceiver::slotGetQuote( , " << d << " , " << m.toString() << " )"; - - m_price = MyMoneyMoney(m); - m_date = d; -} - -void convertertest::QuoteReceiver::slotStatus(const QString& msg) -{ -// qDebug() << "test::QuoteReceiver::slotStatus( " << msg << " )"; - - m_statuses += msg; -} - -void convertertest::QuoteReceiver::slotError(const QString& msg) -{ -// qDebug() << "test::QuoteReceiver::slotError( " << msg << " )"; - - m_errors += msg; -} - -// leave this moc until we will have resolved our dependency issues -// now 'converter' depends on 'kmymoney' a pointer to the application -// defined in main.cpp, which makes this static library unusable without -// the --as-needed linker flag; -// otherwise the 'moc' methods of this object will be linked into the automoc -// object file which contains methods from all the other objects from this -// library, thus even if the --as-needed option is used all objects will be -// pulled in while linking 'convertertest' which only needs the WebPriceQuote -// object - spent a whole day investigating this -#include "moc_webpricequote.cpp" diff --git a/kmymoney/converter/webpricequote.h b/kmymoney/converter/webpricequote.h deleted file mode 100644 index 7d75a7f23..000000000 --- a/kmymoney/converter/webpricequote.h +++ /dev/null @@ -1,246 +0,0 @@ -/*************************************************************************** - webpricequote.h - ------------------- - begin : Thu Dec 30 2004 - copyright : (C) 2004 by Ace Jones - email : Ace Jones - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef WEBPRICEQUOTE_H -#define WEBPRICEQUOTE_H - -// ---------------------------------------------------------------------------- -// QT Headers - -#include -#include - -// ---------------------------------------------------------------------------- -// KDE Headers - -// ---------------------------------------------------------------------------- -// Project Headers - -#include "csv/import/core/csvimportercore.h" - -class KJob; -class QDate; -class QTextCodec; -/** -Helper class to attend the process which is running the script, in the case -of a local script being used to fetch the quote. - -@author Thomas Baumgart & Ace Jones -*/ -class WebPriceQuoteProcess: public QProcess -{ - Q_OBJECT -public: - WebPriceQuoteProcess(); - inline void setWebID(const QString& _webID) { - m_webID = _webID; m_string.truncate(0); - } - -public Q_SLOTS: - void slotReceivedDataFromFilter(); - void slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus); - -Q_SIGNALS: - void processExited(const QString&); - -private: - QString m_webID; - QString m_string; -}; - -/** -Helper class to run the Finance::Quote process. This is used only for the purpose of obtaining -a list of valid sources. The actual price quotes are obtained thru WebPriceQuoteProcess. -The class also contains functions to convert between the rather cryptic source names used -by the Finance::Quote package, and more user-friendly names. - -@author Thomas Baumgart & Ace Jones , Tony B - */ -class FinanceQuoteProcess: public QProcess -{ - Q_OBJECT -public: - FinanceQuoteProcess(); - void launch(const QString& scriptPath); - bool isFinished() const { - return(m_isDone); - }; - const QStringList getSourceList() const; - const QString crypticName(const QString& niceName) const; - const QString niceName(const QString& crypticName) const; - -public Q_SLOTS: - void slotReceivedDataFromFilter(); - void slotProcessExited(); - -private: - bool m_isDone; - QString m_string; - typedef QMap fqNameMap; - fqNameMap m_fqNames; -}; - -/** - * @author Thomas Baumgart & Ace Jones - * - * This is a helper class to store information about an online source - * for stock prices or currency exchange rates. - */ -struct WebPriceQuoteSource { - enum identifyBy {Symbol, IdentificationNumber, Name}; - WebPriceQuoteSource() : m_webIDBy(Symbol), m_skipStripping(false) {} - explicit WebPriceQuoteSource(const QString& name); - WebPriceQuoteSource(const QString& name, const QString& url, const QString& csvUrl, const QString& id, const identifyBy idBy, const QString& price, const QString& date, const QString& dateformat, bool skipStripping = false); - ~WebPriceQuoteSource() {} - - void write() const; - void rename(const QString& name); - void remove() const; - - QString m_name; - QString m_url; - QString m_csvUrl; - QString m_webID; - identifyBy m_webIDBy; - QString m_price; - QString m_date; - QString m_dateformat; - bool m_skipStripping; -}; - -/** -Retrieves a price quote from a web-based quote source - -@author Ace Jones -*/ -class WebPriceQuote: public QObject -{ - Q_OBJECT -public: - explicit WebPriceQuote(QObject* = 0); - ~WebPriceQuote(); - - typedef enum _quoteSystemE { - Native = 0, - FinanceQuote - } quoteSystemE; - - void setDate(const QDate& _from, const QDate& _to); - /** - * This launches a web-based quote update for the given @p _webID. - * When the quote is received back from the web source, it will be - * emitted on the 'quote' signal. - * - * @param _webID the identification of the stock to fetch a price for - * @param _kmmID an arbitrary identifier, which will be emitted in the quote - * signal when a price is sent back. - * @param _source the source of the quote (must be a valid value returned - * by quoteSources(). Send QString() to use the default - * source. - * @return bool Whether the quote fetch process was launched successfully - */ - - bool launch(const QString& _webID, const QString& _kmmID, const QString& _source = QString()); - - /** - * This returns a list of the names of the quote sources - * currently defined. - * - * @param _system whether to return Native or Finance::Quote source list - * @return QStringList of quote source names - */ - static const QStringList quoteSources(const _quoteSystemE _system = Native); - static const QMap defaultCSVQuoteSources(); - -Q_SIGNALS: - void csvquote(const QString&, const QString&, MyMoneyStatement&); - void quote(const QString&, const QString&, const QDate&, const double&); - void failed(const QString&, const QString&); - void status(const QString&); - void error(const QString&); - -protected Q_SLOTS: - void slotParseCSVQuote(const QString& filename); - void slotParseQuote(const QString&); - void downloadCSV(KJob* job); - void downloadResult(KJob* job); - -protected: - static const QMap defaultQuoteSources(); - -private: - bool launchCSV(const QString& _webID, const QString& _kmmID, const QString& _source = QString()); - bool launchNative(const QString& _webID, const QString& _kmmID, const QString& _source = QString()); - bool launchFinanceQuote(const QString& _webID, const QString& _kmmID, const QString& _source = QString()); - void enter_loop(); - - static const QStringList quoteSourcesNative(); - static const QStringList quoteSourcesFinanceQuote(); - -private: - /// \internal d-pointer class. - class Private; - /// \internal d-pointer instance. - Private* const d; - - static QString m_financeQuoteScriptPath; - static QStringList m_financeQuoteSources; - -}; - -class MyMoneyDateFormat -{ -public: - explicit MyMoneyDateFormat(const QString& _format): m_format(_format) {} - const QString convertDate(const QDate& _in) const; - const QDate convertString(const QString& _in, bool _strict = true, unsigned _centurymidpoint = QDate::currentDate().year()) const; - const QString& format() const { - return m_format; - } -private: - QString m_format; -}; - -namespace convertertest -{ - -/** -Simple class to handle signals/slots for unit tests - -@author Ace Jones -*/ -class QuoteReceiver : public QObject -{ - Q_OBJECT -public: - explicit QuoteReceiver(WebPriceQuote* q, QObject *parent = 0); - ~QuoteReceiver(); -public Q_SLOTS: - void slotGetQuote(const QString&, const QString&, const QDate&, const double&); - void slotStatus(const QString&); - void slotError(const QString&); -public: - QStringList m_statuses; - QStringList m_errors; - MyMoneyMoney m_price; - QDate m_date; -}; - -} // end namespace convertertest - - -#endif // WEBPRICEQUOTE_H diff --git a/kmymoney/dialogs/kequitypriceupdatedlg.cpp b/kmymoney/dialogs/kequitypriceupdatedlg.cpp index a8fad14c0..a75382179 100644 --- a/kmymoney/dialogs/kequitypriceupdatedlg.cpp +++ b/kmymoney/dialogs/kequitypriceupdatedlg.cpp @@ -1,811 +1,817 @@ /* * Copyright 2004-2017 Thomas Baumgart * Copyright 2004 Kevin Tambascio * Copyright 2004-2006 Ace Jones * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kequitypriceupdatedlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include +#include +#include +#include + // ---------------------------------------------------------------------------- // Project Includes #include "ui_kequitypriceupdatedlg.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" +#include "mymoneymoney.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" -#include "webpricequote.h" +#include "mymoneystatement.h" #include "kequitypriceupdateconfdlg.h" #include "kmymoneyutils.h" #include "mymoneyexception.h" #include "dialogenums.h" #define WEBID_COL 0 #define NAME_COL 1 #define PRICE_COL 2 #define DATE_COL 3 #define KMMID_COL 4 #define SOURCE_COL 5 class KEquityPriceUpdateDlgPrivate { Q_DISABLE_COPY(KEquityPriceUpdateDlgPrivate) Q_DECLARE_PUBLIC(KEquityPriceUpdateDlg) public: explicit KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg *qq) : q_ptr(qq), ui(new Ui::KEquityPriceUpdateDlg), m_fUpdateAll(false), m_updatingPricePolicy(eDialogs::UpdatePrice::All) { } ~KEquityPriceUpdateDlgPrivate() { delete ui; } void init(const QString& securityId) { Q_Q(KEquityPriceUpdateDlg); ui->setupUi(q); m_fUpdateAll = false; QStringList headerList; headerList << i18n("ID") << i18nc("Equity name", "Name") << i18n("Price") << i18n("Date"); ui->lvEquityList->header()->setSortIndicator(0, Qt::AscendingOrder); ui->lvEquityList->setColumnWidth(NAME_COL, 125); // This is a "get it up and running" hack. Will replace this in the future. headerList << i18nc("Internal identifier", "Internal ID") << i18nc("Online quote source", "Source"); ui->lvEquityList->setColumnWidth(KMMID_COL, 0); ui->lvEquityList->setHeaderLabels(headerList); ui->lvEquityList->setSelectionMode(QAbstractItemView::MultiSelection); ui->lvEquityList->setAllColumnsShowFocus(true); ui->btnUpdateAll->setEnabled(false); auto file = MyMoneyFile::instance(); // // Add each price pair that we know about // // send in securityId == "XXX YYY" to get a single-shot update for XXX to YYY. - // for consistency reasons, this accepts the same delimiters as WebPriceQuote::launch() + // for consistency reasons, this accepts the same delimiters as AlkOnlineQuote::launch() QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); MyMoneySecurityPair currencyIds; if (splitrx.indexIn(securityId) != -1) { currencyIds = MyMoneySecurityPair(splitrx.cap(1), splitrx.cap(2)); } MyMoneyPriceList prices = file->priceList(); for (MyMoneyPriceList::ConstIterator it_price = prices.constBegin(); it_price != prices.constEnd(); ++it_price) { const MyMoneySecurityPair& pair = it_price.key(); if (file->security(pair.first).isCurrency() && (securityId.isEmpty() || (pair == currencyIds))) { const MyMoneyPriceEntries& entries = (*it_price); if (entries.count() > 0 && entries.begin().key() <= QDate::currentDate()) { addPricePair(pair, false); ui->btnUpdateAll->setEnabled(true); } } } // // Add each investment // QList securities = file->securityList(); for (QList::const_iterator it = securities.constBegin(); it != securities.constEnd(); ++it) { if (!(*it).isCurrency() && (securityId.isEmpty() || ((*it).id() == securityId)) && !(*it).value("kmm-online-source").isEmpty() ) { addInvestment(*it); ui->btnUpdateAll->setEnabled(true); } } // if list is empty and a price pair has been requested, add it if (ui->lvEquityList->invisibleRootItem()->childCount() == 0 && !securityId.isEmpty()) { addPricePair(currencyIds, true); } q->connect(ui->btnUpdateSelected, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateSelectedClicked); q->connect(ui->btnUpdateAll, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateAllClicked); q->connect(ui->m_fromDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged); q->connect(ui->m_toDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged); - q->connect(&m_webQuote, &WebPriceQuote::csvquote, - q, &KEquityPriceUpdateDlg::slotReceivedCSVQuote); - q->connect(&m_webQuote, &WebPriceQuote::quote, +// q->connect(&m_webQuote, &AlkOnlineQuote::csvquote, +// q, &KEquityPriceUpdateDlg::slotReceivedCSVQuote); + q->connect(&m_webQuote, &AlkOnlineQuote::quote, q, &KEquityPriceUpdateDlg::slotReceivedQuote); - q->connect(&m_webQuote, &WebPriceQuote::failed, + q->connect(&m_webQuote, &AlkOnlineQuote::failed, q, &KEquityPriceUpdateDlg::slotQuoteFailed); - q->connect(&m_webQuote, &WebPriceQuote::status, + q->connect(&m_webQuote, &AlkOnlineQuote::status, q, &KEquityPriceUpdateDlg::logStatusMessage); - q->connect(&m_webQuote, &WebPriceQuote::error, + q->connect(&m_webQuote, &AlkOnlineQuote::error, q, &KEquityPriceUpdateDlg::logErrorMessage); q->connect(ui->lvEquityList, &QTreeWidget::itemSelectionChanged, q, &KEquityPriceUpdateDlg::slotUpdateSelection); q->connect(ui->btnConfigure, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotConfigureClicked); if (!securityId.isEmpty()) { ui->btnUpdateSelected->hide(); ui->btnUpdateAll->hide(); // delete layout1; QTimer::singleShot(100, q, SLOT(slotUpdateAllClicked())); } // Hide OK button until we have received the first update ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); if (ui->lvEquityList->invisibleRootItem()->childCount() == 0) { ui->btnUpdateAll->setEnabled(false); } q->slotUpdateSelection(); // previous versions of this dialog allowed to store a "Don't ask again" switch. // Since we don't support it anymore, we just get rid of it KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Notification Messages"); grp.deleteEntry("KEquityPriceUpdateDlg::slotQuoteFailed::Price Update Failed"); grp.sync(); grp = config->group("Equity Price Update"); int policyValue = grp.readEntry("PriceUpdatingPolicy", (int)eDialogs::UpdatePrice::Missing); if (policyValue > (int)eDialogs::UpdatePrice::Ask || policyValue < (int)eDialogs::UpdatePrice::All) m_updatingPricePolicy = eDialogs::UpdatePrice::Missing; else m_updatingPricePolicy = static_cast(policyValue); } void addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance) { auto file = MyMoneyFile::instance(); const auto symbol = QString::fromLatin1("%1 > %2").arg(pair.first, pair.second); const auto id = QString::fromLatin1("%1 %2").arg(pair.first, pair.second); // Check that the pair does not already exist if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) { const MyMoneyPrice &pr = file->price(pair.first, pair.second); if (pr.source() != QLatin1String("KMyMoney")) { bool keep = true; if ((pair.first == file->baseCurrency().id()) || (pair.second == file->baseCurrency().id())) { const QString& foreignCurrency = file->foreignCurrency(pair.first, pair.second); // check that the foreign currency is still in use QList::const_iterator it_a; QList list; file->accountList(list); for (it_a = list.constBegin(); !dontCheckExistance && it_a != list.constEnd(); ++it_a) { // if it's an account denominated in the foreign currency // keep it if (((*it_a).currencyId() == foreignCurrency) && !(*it_a).isClosed()) break; // if it's an investment traded in the foreign currency // keep it if ((*it_a).isInvest() && !(*it_a).isClosed()) { MyMoneySecurity sec = file->security((*it_a).currencyId()); if (sec.tradingCurrency() == foreignCurrency) break; } } // if it is in use, it_a is not equal to list.end() if (it_a == list.constEnd() && !dontCheckExistance) keep = false; } if (keep) { auto item = new QTreeWidgetItem(); item->setText(WEBID_COL, symbol); item->setText(NAME_COL, i18n("%1 units in %2", pair.first, pair.second)); if (pr.isValid()) { MyMoneySecurity fromCurrency = file->currency(pair.second); MyMoneySecurity toCurrency = file->currency(pair.first); item->setText(PRICE_COL, pr.rate(pair.second).formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision())); item->setText(DATE_COL, pr.date().toString(Qt::ISODate)); } item->setText(KMMID_COL, id); item->setText(SOURCE_COL, "KMyMoney Currency"); // This string value should not be localized ui->lvEquityList->invisibleRootItem()->addChild(item); } } } } void addInvestment(const MyMoneySecurity& inv) { const auto id = inv.id(); // Check that the pair does not already exist if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) { auto file = MyMoneyFile::instance(); // check that the security is still in use QList::const_iterator it_a; QList list; file->accountList(list); for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { if ((*it_a).isInvest() && ((*it_a).currencyId() == inv.id()) && !(*it_a).isClosed()) break; } // if it is in use, it_a is not equal to list.end() if (it_a != list.constEnd()) { QString webID; - WebPriceQuoteSource onlineSource(inv.value("kmm-online-source")); - if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::IdentificationNumber) - webID = inv.value("kmm-security-id"); // insert ISIN number... - else if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::Name) - webID = inv.name(); // ...or name... - else + AlkOnlineQuoteSource onlineSource(inv.value("kmm-online-source"), AlkOnlineQuotesProfileManager::instance().profiles().first()); +// if (onlineSource.m_webIDBy == AlkOnlineQuoteSource::identifyBy::IdentificationNumber) +// webID = inv.value("kmm-security-id"); // insert ISIN number... +// else if (onlineSource.m_webIDBy == AlkOnlineQuoteSource::identifyBy::Name) +// webID = inv.name(); // ...or name... +// else webID = inv.tradingSymbol(); // ...or symbol QTreeWidgetItem* item = new QTreeWidgetItem(); item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText)); if (webID.isEmpty()) { webID = i18n("[No identifier]"); item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText)); } item->setText(WEBID_COL, webID); item->setText(NAME_COL, inv.name()); MyMoneySecurity currency = file->currency(inv.tradingCurrency()); const MyMoneyPrice &pr = file->price(id.toUtf8(), inv.tradingCurrency()); if (pr.isValid()) { item->setText(PRICE_COL, pr.rate(currency.id()).formatMoney(currency.tradingSymbol(), inv.pricePrecision())); item->setText(DATE_COL, pr.date().toString(Qt::ISODate)); } item->setText(KMMID_COL, id); if (inv.value("kmm-online-quote-system") == "Finance::Quote") item->setText(SOURCE_COL, QString("Finance::Quote %1").arg(inv.value("kmm-online-source"))); else item->setText(SOURCE_COL, inv.value("kmm-online-source")); ui->lvEquityList->invisibleRootItem()->addChild(item); // If this investment is denominated in a foreign currency, ensure that // the appropriate price pair is also on the list if (currency.id() != file->baseCurrency().id()) { addPricePair(MyMoneySecurityPair(currency.id(), file->baseCurrency().id()), false); } } } } KEquityPriceUpdateDlg *q_ptr; Ui::KEquityPriceUpdateDlg *ui; bool m_fUpdateAll; eDialogs::UpdatePrice m_updatingPricePolicy; - WebPriceQuote m_webQuote; + AlkOnlineQuote m_webQuote; }; KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(QWidget *parent, const QString& securityId) : QDialog(parent), d_ptr(new KEquityPriceUpdateDlgPrivate(this)) { Q_D(KEquityPriceUpdateDlg); d->init(securityId); } KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg() { Q_D(KEquityPriceUpdateDlg); auto config = KSharedConfig::openConfig(); auto grp = config->group("Equity Price Update"); grp.writeEntry("PriceUpdatingPolicy", static_cast(d->m_updatingPricePolicy)); grp.sync(); delete d; } void KEquityPriceUpdateDlg::logErrorMessage(const QString& message) { logStatusMessage(QString("") + message + QString("")); } void KEquityPriceUpdateDlg::logStatusMessage(const QString& message) { Q_D(KEquityPriceUpdateDlg); d->ui->lbStatus->append(message); } MyMoneyPrice KEquityPriceUpdateDlg::price(const QString& id) const { Q_D(const KEquityPriceUpdateDlg); MyMoneyPrice price; QTreeWidgetItem* item = nullptr; QList foundItems = d->ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL); if (! foundItems.empty()) item = foundItems.at(0); if (item) { MyMoneyMoney rate(item->text(PRICE_COL)); if (!rate.isZero()) { QString kmm_id = item->text(KMMID_COL).toUtf8(); // if the ID has a space, then this is TWO ID's, so it's a currency quote if (kmm_id.contains(" ")) { QStringList ids = kmm_id.split(' ', QString::SkipEmptyParts); QString fromid = ids[0].toUtf8(); QString toid = ids[1].toUtf8(); price = MyMoneyPrice(fromid, toid, QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)); } else // otherwise, it's a security quote { MyMoneySecurity security = MyMoneyFile::instance()->security(kmm_id); price = MyMoneyPrice(kmm_id, security.tradingCurrency(), QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)); } } } return price; } void KEquityPriceUpdateDlg::storePrices() { Q_D(KEquityPriceUpdateDlg); // update the new prices into the equities auto file = MyMoneyFile::instance(); QString name; MyMoneyFileTransaction ft; try { for (auto i = 0; i < d->ui->lvEquityList->invisibleRootItem()->childCount(); ++i) { QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(i); // turn on signals before we modify the last entry in the list file->blockSignals(i < d->ui->lvEquityList->invisibleRootItem()->childCount() - 1); MyMoneyMoney rate(item->text(PRICE_COL)); if (!rate.isZero()) { QString id = item->text(KMMID_COL); QString fromid; QString toid; // if the ID has a space, then this is TWO ID's, so it's a currency quote if (id.contains(QLatin1Char(' '))) { QStringList ids = id.split(QLatin1Char(' '), QString::SkipEmptyParts); fromid = ids.at(0); toid = ids.at(1); name = QString::fromLatin1("%1 --> %2").arg(fromid, toid); } else { // otherwise, it's a security quote MyMoneySecurity security = file->security(id); name = security.name(); fromid = id; toid = security.tradingCurrency(); } // TODO (Ace) Better handling of the case where there is already a price // for this date. Currently, it just overrides the old value. Really it // should check to see if the price is the same and prompt the user. file->addPrice(MyMoneyPrice(fromid, toid, QDate::fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL))); } } ft.commit(); } catch (const MyMoneyException &) { qDebug("Unable to add price information for %s", qPrintable(name)); } } void KEquityPriceUpdateDlg::slotConfigureClicked() { Q_D(KEquityPriceUpdateDlg); QPointer dlg = new EquityPriceUpdateConfDlg(d->m_updatingPricePolicy); if (dlg->exec() == QDialog::Accepted) d->m_updatingPricePolicy = dlg->policy(); delete dlg; } void KEquityPriceUpdateDlg::slotUpdateSelection() { Q_D(KEquityPriceUpdateDlg); // Only enable the update button if there is a selection d->ui->btnUpdateSelected->setEnabled(false); if (! d->ui->lvEquityList->selectedItems().empty()) d->ui->btnUpdateSelected->setEnabled(true); } void KEquityPriceUpdateDlg::slotUpdateSelectedClicked() { Q_D(KEquityPriceUpdateDlg); // disable sorting while the update is running to maintain the current order of items on which // the update process depends and which could be changed with sorting enabled due to the updated values d->ui->lvEquityList->setSortingEnabled(false); auto item = d->ui->lvEquityList->invisibleRootItem()->child(0); auto skipCnt = 1; while (item && !item->isSelected()) { item = d->ui->lvEquityList->invisibleRootItem()->child(skipCnt); ++skipCnt; } - d->m_webQuote.setDate(d->ui->m_fromDate->date(), d->ui->m_toDate->date()); + // FIXME : Why do we need to set the date before, when launch determines the date + //d->m_webQuote.setDate(d->ui->m_fromDate->date(), d->ui->m_toDate->date()); if (item) { d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount()); d->ui->prgOnlineProgress->setValue(skipCnt); d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL)); } else { logErrorMessage("No security selected."); } } void KEquityPriceUpdateDlg::slotUpdateAllClicked() { Q_D(KEquityPriceUpdateDlg); // disable sorting while the update is running to maintain the current order of items on which // the update process depends and which could be changed with sorting enabled due to the updated values d->ui->lvEquityList->setSortingEnabled(false); QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(0); if (item) { d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount()); d->ui->prgOnlineProgress->setValue(1); d->m_fUpdateAll = true; d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL)); } else { logErrorMessage("Security list is empty."); } } void KEquityPriceUpdateDlg::slotDateChanged() { Q_D(KEquityPriceUpdateDlg); d->ui->m_fromDate->blockSignals(true); d->ui->m_toDate->blockSignals(true); if (d->ui->m_toDate->date() > QDate::currentDate()) d->ui->m_toDate->setDate(QDate::currentDate()); if (d->ui->m_fromDate->date() > d->ui->m_toDate->date()) d->ui->m_fromDate->setDate(d->ui->m_toDate->date()); d->ui->m_fromDate->blockSignals(false); d->ui->m_toDate->blockSignals(false); } void KEquityPriceUpdateDlg::slotQuoteFailed(const QString& _kmmID, const QString& _webID) { Q_D(KEquityPriceUpdateDlg); auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL); QTreeWidgetItem* item = nullptr; if (! foundItems.empty()) item = foundItems.at(0); // Give the user some options int result; if (_kmmID.contains(" ")) { if (item) result = KMessageBox::warningContinueCancel(this, i18n("Failed to retrieve an exchange rate for %1 from %2. It will be skipped this time.", _webID, item->text(SOURCE_COL)), i18n("Price Update Failed")); else return; } else if (!item) { return; } else { result = KMessageBox::questionYesNoCancel(this, QString::fromLatin1("%1").arg(i18n("Failed to retrieve a quote for %1 from %2. Press No to remove the online price source from this security permanently, Yes to continue updating this security during future price updates or Cancel to stop the current update operation.", _webID, item->text(SOURCE_COL))), i18n("Price Update Failed"), KStandardGuiItem::yes(), KStandardGuiItem::no()); } if (result == KMessageBox::No) { // Disable price updates for this security MyMoneyFileTransaction ft; try { // Get this security (by ID) MyMoneySecurity security = MyMoneyFile::instance()->security(_kmmID.toUtf8()); // Set the quote source to blank security.setValue("kmm-online-source", QString()); security.setValue("kmm-online-quote-system", QString()); // Re-commit the security MyMoneyFile::instance()->modifySecurity(security); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Cannot update security %1: %2", _webID, QString::fromLatin1(e.what())) + QString(""), i18n("Price Update Failed")); } } // As long as the user doesn't want to cancel, move on! if (result != KMessageBox::Cancel) { QTreeWidgetItem* next = nullptr; d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); item->setSelected(false); // launch the NEXT one ... in case of m_fUpdateAll == false, we // need to parse the list to find the next selected one next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); if (!d->m_fUpdateAll) { while (next && !next->isSelected()) { d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1); } } if (next) { d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL)); } else { finishUpdate(); } } else { finishUpdate(); } } void KEquityPriceUpdateDlg::slotReceivedCSVQuote(const QString& _kmmID, const QString& _webID, MyMoneyStatement& st) { Q_D(KEquityPriceUpdateDlg); auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL); QTreeWidgetItem* item = nullptr; if (! foundItems.empty()) item = foundItems.at(0); QTreeWidgetItem* next = nullptr; if (item) { auto file = MyMoneyFile::instance(); MyMoneySecurity fromCurrency, toCurrency; if (!_kmmID.contains(QLatin1Char(' '))) { try { toCurrency = MyMoneyFile::instance()->security(_kmmID); fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } else { QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); if (splitrx.indexIn(_kmmID) != -1) { try { fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8()); toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } } if (d->m_updatingPricePolicy != eDialogs::UpdatePrice::All) { - QStringList qSources = WebPriceQuote::quoteSources(); + QStringList qSources = AlkOnlineQuotesProfileManager::instance().profiles().first()->quoteSources(); for (auto it = st.m_listPrices.begin(); it != st.m_listPrices.end();) { MyMoneyPrice storedPrice = file->price(toCurrency.id(), fromCurrency.id(), (*it).m_date, true); bool priceValid = storedPrice.isValid(); if (!priceValid) ++it; else { switch(d->m_updatingPricePolicy) { case eDialogs::UpdatePrice::Missing: it = st.m_listPrices.erase(it); break; case eDialogs::UpdatePrice::Downloaded: if (!qSources.contains(storedPrice.source())) it = st.m_listPrices.erase(it); else ++it; break; case eDialogs::UpdatePrice::SameSource: if (storedPrice.source().compare((*it).m_sourceName) != 0) it = st.m_listPrices.erase(it); else ++it; break; case eDialogs::UpdatePrice::Ask: { auto result = KMessageBox::questionYesNoCancel(this, i18n("For %1 on %2 price %3 already exists.
" "Do you want to replace it with %4?", storedPrice.from(), storedPrice.date().toString(Qt::ISODate), QString().setNum(storedPrice.rate(storedPrice.to()).toDouble(), 'g', 10), QString().setNum((*it).m_amount.toDouble(), 'g', 10)), i18n("Price Already Exists")); switch(result) { case KMessageBox::ButtonCode::Yes: ++it; break; case KMessageBox::ButtonCode::No: it = st.m_listPrices.erase(it); break; default: case KMessageBox::ButtonCode::Cancel: finishUpdate(); return; break; } break; } default: ++it; break; } } } } if (!st.m_listPrices.isEmpty()) { MyMoneyFileTransaction ft; KMyMoneyUtils::processPriceList(st); ft.commit(); // latest price could be in the last or in the first row MyMoneyStatement::Price priceClass; if (st.m_listPrices.first().m_date > st.m_listPrices.last().m_date) priceClass = st.m_listPrices.first(); else priceClass = st.m_listPrices.last(); // update latest price in dialog if applicable auto latestDate = QDate::fromString(item->text(DATE_COL),Qt::ISODate); if (latestDate <= priceClass.m_date && priceClass.m_amount.isPositive()) { item->setText(PRICE_COL, priceClass.m_amount.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision())); item->setText(DATE_COL, priceClass.m_date.toString(Qt::ISODate)); item->setText(SOURCE_COL, priceClass.m_sourceName); } logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID)); // make sure to make OK button available } d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); item->setSelected(false); // launch the NEXT one ... in case of m_fUpdateAll == false, we // need to parse the list to find the next selected one next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); if (!d->m_fUpdateAll) { while (next && !next->isSelected()) { d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1); } } } else { logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID)); } if (next) { d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL)); } else { finishUpdate(); } } void KEquityPriceUpdateDlg::slotReceivedQuote(const QString& _kmmID, const QString& _webID, const QDate& _date, const double& _price) { Q_D(KEquityPriceUpdateDlg); auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL); QTreeWidgetItem* item = nullptr; if (! foundItems.empty()) item = foundItems.at(0); QTreeWidgetItem* next = 0; if (item) { if (_price > 0.0f && _date.isValid()) { QDate date = _date; if (date > QDate::currentDate()) date = QDate::currentDate(); MyMoneyMoney price = MyMoneyMoney::ONE; QString id = _kmmID.toUtf8(); MyMoneySecurity fromCurrency, toCurrency; if (_kmmID.contains(" ") == 0) { MyMoneySecurity security = MyMoneyFile::instance()->security(id); QString factor = security.value("kmm-online-factor"); if (!factor.isEmpty()) { price = price * MyMoneyMoney(factor); } try { toCurrency = MyMoneyFile::instance()->security(id); fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } else { QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); if (splitrx.indexIn(_kmmID) != -1) { try { fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8()); toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } } price *= MyMoneyMoney(_price, MyMoneyMoney::precToDenom(toCurrency.pricePrecision())); item->setText(PRICE_COL, price.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision())); item->setText(DATE_COL, date.toString(Qt::ISODate)); logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID)); // make sure to make OK button available d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } else { logErrorMessage(i18n("Received an invalid price for %1, unable to update.", _webID)); } d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); item->setSelected(false); // launch the NEXT one ... in case of m_fUpdateAll == false, we // need to parse the list to find the next selected one next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); if (!d->m_fUpdateAll) { while (next && !next->isSelected()) { d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1); } } } else { logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID)); } if (next) { d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL)); } else { finishUpdate(); } } void KEquityPriceUpdateDlg::finishUpdate() { Q_D(KEquityPriceUpdateDlg); // we've run past the end, reset to the default value. d->m_fUpdateAll = false; // force progress bar to show 100% d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->maximum()); // re-enable the sorting that was disabled during the update process d->ui->lvEquityList->setSortingEnabled(true); } // Make sure, that these definitions are only used within this file // this does not seem to be necessary, but when building RPMs the // build option 'final' is used and all CPP files are concatenated. // So it could well be, that in another CPP file these definitions // are also used. #undef WEBID_COL #undef NAME_COL #undef PRICE_COL #undef DATE_COL #undef KMMID_COL #undef SOURCE_COL diff --git a/kmymoney/dialogs/settings/CMakeLists.txt b/kmymoney/dialogs/settings/CMakeLists.txt index 69039e3bf..8e247092d 100644 --- a/kmymoney/dialogs/settings/CMakeLists.txt +++ b/kmymoney/dialogs/settings/CMakeLists.txt @@ -1,47 +1,45 @@ set (libsettings_a_SOURCES ksettingscolors.cpp ksettingsfonts.cpp ksettingsicons.cpp ksettingsgeneral.cpp ksettingshome.cpp - ksettingsonlinequotes.cpp ksettingsregister.cpp ksettingsschedules.cpp ksettingskmymoney.cpp ksettingsplugins.cpp ) set (libsettings_a_UI ksettingscolors.ui ksettingsfonts.ui ksettingsicons.ui ksettingsgeneral.ui ksettingshome.ui - ksettingsonlinequotes.ui ksettingsregister.ui ksettingsschedules.ui ) ki18n_wrap_ui(libsettings_a_SOURCES ${libsettings_a_UI} ) add_library(settings STATIC ${libsettings_a_SOURCES}) # TODO: cleanup dependencies target_link_libraries(settings PUBLIC KF5::Completion KF5::KIOWidgets KF5::TextWidgets KF5::I18n KF5::IconThemes KF5::Completion KF5::KCMUtils KF5::ItemViews Alkimia::alkimia ) if (ENABLE_HOLIDAYS) target_link_libraries(settings PUBLIC KF5::Holidays) endif() add_dependencies(settings widgets kmm_settings) diff --git a/kmymoney/dialogs/settings/ksettingskmymoney.cpp b/kmymoney/dialogs/settings/ksettingskmymoney.cpp index 01ae79836..a074ab977 100644 --- a/kmymoney/dialogs/settings/ksettingskmymoney.cpp +++ b/kmymoney/dialogs/settings/ksettingskmymoney.cpp @@ -1,82 +1,83 @@ /* * Copyright 2014-2016 Christian Dávid * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ksettingskmymoney.h" #include #include +#include + #include "ksettingsgeneral.h" #include "ksettingsregister.h" #include "ksettingscolors.h" #include "ksettingsfonts.h" #include "ksettingsicons.h" #include "ksettingsschedules.h" -#include "ksettingsonlinequotes.h" #include "ksettingshome.h" #include "ksettingsplugins.h" #include "icons.h" using namespace Icons; KSettingsKMyMoney::KSettingsKMyMoney(QWidget *parent, const QString &name, KCoreConfigSkeleton *config) : KConfigDialog(parent, name, config) { // create the pages ... const auto generalPage = new KSettingsGeneral(); const auto registerPage = new KSettingsRegister(); const auto homePage = new KSettingsHome(); const auto schedulesPage = new KSettingsSchedules(); const auto colorsPage = new KSettingsColors(); const auto fontsPage = new KSettingsFonts(); const auto iconsPage = new KSettingsIcons(); - const auto onlineQuotesPage = new KSettingsOnlineQuotes(); + const auto onlineQuotesPage = new AlkOnlineQuotesWidget; const auto pluginsPage = new KSettingsPlugins(); addPage(generalPage, i18nc("General settings", "General"), Icons::get(Icon::SystemRun).name()); addPage(homePage, i18n("Home"), Icons::get(Icon::ViewHome).name()); addPage(registerPage, i18nc("Ledger view settings", "Ledger"), Icons::get(Icon::ViewFinancialList).name()); addPage(schedulesPage, QString(i18n("Scheduled\ntransactions")).replace(QLatin1Char('\n'), QString::fromUtf8("\xe2\x80\xa8")), Icons::get(Icon::ViewSchedules).name()); addPage(onlineQuotesPage, i18n("Online Quotes"), Icons::get(Icon::PreferencesNetwork).name()); addPage(colorsPage, i18n("Colors"), Icons::get(Icon::PreferencesColor).name()); addPage(fontsPage, i18n("Fonts"), Icons::get(Icon::PreferencesFont).name()); addPage(iconsPage, i18n("Icons"), Icons::get(Icon::PreferencesIcon).name()); addPage(pluginsPage, i18n("Plugins"), Icons::get(Icon::NetworkDisconect).name(), QString(), false); setHelp("details.settings", "kmymoney"); connect(this, &KConfigDialog::rejected, schedulesPage, &KSettingsSchedules::slotResetRegion); connect(this, &KConfigDialog::rejected, iconsPage, &KSettingsIcons::slotResetTheme); connect(this, &KConfigDialog::settingsChanged, generalPage, &KSettingsGeneral::slotUpdateEquitiesVisibility); auto defaultButton = button(QDialogButtonBox::RestoreDefaults); auto applyButton = button(QDialogButtonBox::Apply); connect(this, &KConfigDialog::accepted, pluginsPage, &KSettingsPlugins::slotSavePluginConfiguration); connect(applyButton, &QPushButton::clicked, pluginsPage, &KSettingsPlugins::slotSavePluginConfiguration); connect(defaultButton, &QPushButton::clicked, pluginsPage, &KSettingsPlugins::slotResetToDefaults); connect(pluginsPage, &KSettingsPlugins::changed, this, &KSettingsKMyMoney::slotPluginsChanged); connect(pluginsPage, &KSettingsPlugins::settingsChanged, this, &KConfigDialog::settingsChanged); } void KSettingsKMyMoney::slotPluginsChanged(bool changed) { auto applyButton = button(QDialogButtonBox::Apply); applyButton->setEnabled(changed); } diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp deleted file mode 100644 index 040db4aba..000000000 --- a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2005-2010 Thomas Baumgart - * Copyright 2017-2018 Łukasz Wojniłowicz - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ksettingsonlinequotes.h" - -// ---------------------------------------------------------------------------- -// QT Includes - -#include -#include - -// ---------------------------------------------------------------------------- -// KDE Includes - -#include -#include -#include - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "ui_ksettingsonlinequotes.h" - -#include "kmymoney/converter/webpricequote.h" -#include "mymoneyfile.h" -#include "mymoneysecurity.h" -#include "icons/icons.h" - -using namespace Icons; - -class KSettingsOnlineQuotesPrivate -{ - Q_DISABLE_COPY(KSettingsOnlineQuotesPrivate) - -public: - KSettingsOnlineQuotesPrivate() : - ui(new Ui::KSettingsOnlineQuotes), - m_quoteInEditing(false) - { - } - - ~KSettingsOnlineQuotesPrivate() - { - delete ui; - } - - Ui::KSettingsOnlineQuotes *ui; - QList m_resetList; - WebPriceQuoteSource m_currentItem; - bool m_quoteInEditing; -}; - -KSettingsOnlineQuotes::KSettingsOnlineQuotes(QWidget *parent) : - QWidget(parent), - d_ptr(new KSettingsOnlineQuotesPrivate) -{ - Q_D(KSettingsOnlineQuotes); - d->ui->setupUi(this); - QStringList groups = WebPriceQuote::quoteSources(); - - loadList(true /*updateResetList*/); - - d->ui->m_updateButton->setEnabled(false); - - d->ui->m_updateButton->setIcon(Icons::get(Icon::DialogOK)); - d->ui->m_deleteButton->setIcon(Icons::get(Icon::EditDelete)); - d->ui->m_newButton->setIcon(Icons::get(Icon::DocumentNew)); - - d->ui->m_editIdentifyBy->addItem(i18n("Symbol"), WebPriceQuoteSource::identifyBy::Symbol); - d->ui->m_editIdentifyBy->addItem(i18n("Identification number"), WebPriceQuoteSource::identifyBy::IdentificationNumber); - d->ui->m_editIdentifyBy->addItem(i18n("Name"), WebPriceQuoteSource::identifyBy::Name); - - connect(d->ui->m_dumpCSVProfile, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotDumpCSVProfile); - connect(d->ui->m_updateButton, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotUpdateEntry); - connect(d->ui->m_newButton, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotNewEntry); - connect(d->ui->m_deleteButton, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotDeleteEntry); - - connect(d->ui->m_quoteSourceList, &QListWidget::itemSelectionChanged, this, &KSettingsOnlineQuotes::slotLoadWidgets); - connect(d->ui->m_quoteSourceList, &QListWidget::itemChanged, this, &KSettingsOnlineQuotes::slotEntryRenamed); - connect(d->ui->m_quoteSourceList, &QListWidget::itemDoubleClicked, this, &KSettingsOnlineQuotes::slotStartRename); - - connect(d->ui->m_editURL, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_editCSVURL, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_editIdentifier, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_editIdentifyBy, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_editDate, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_editDateFormat, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_editPrice, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); - connect(d->ui->m_skipStripping, &QAbstractButton::toggled, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); -} - -KSettingsOnlineQuotes::~KSettingsOnlineQuotes() -{ - Q_D(KSettingsOnlineQuotes); - delete d; -} - -void KSettingsOnlineQuotes::loadList(const bool updateResetList) -{ - Q_D(KSettingsOnlineQuotes); - //disconnect the slot while items are being loaded and reconnect at the end - disconnect(d->ui->m_quoteSourceList, &QListWidget::itemChanged, this, &KSettingsOnlineQuotes::slotEntryRenamed); - d->m_quoteInEditing = false; - QStringList groups = WebPriceQuote::quoteSources(); - - if (updateResetList) - d->m_resetList.clear(); - d->ui->m_quoteSourceList->clear(); - QStringList::Iterator it; - for (it = groups.begin(); it != groups.end(); ++it) { - QListWidgetItem* item = new QListWidgetItem(*it); - item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); - d->ui->m_quoteSourceList->addItem(item); - if (updateResetList) - d->m_resetList += WebPriceQuoteSource(*it); - } - d->ui->m_quoteSourceList->sortItems(); - - QListWidgetItem* first = d->ui->m_quoteSourceList->item(0); - if (first) - d->ui->m_quoteSourceList->setCurrentItem(first); - slotLoadWidgets(); - - d->ui->m_newButton->setEnabled((d->ui->m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly)).count() == 0); - connect(d->ui->m_quoteSourceList, &QListWidget::itemChanged, this, &KSettingsOnlineQuotes::slotEntryRenamed); -} - -void KSettingsOnlineQuotes::resetConfig() -{ - Q_D(KSettingsOnlineQuotes); - QStringList::ConstIterator it; - QStringList groups = WebPriceQuote::quoteSources(); - - // delete all currently defined entries - for (it = groups.constBegin(); it != groups.constEnd(); ++it) { - WebPriceQuoteSource(*it).remove(); - } - - // and write back the one's from the reset list - QList::ConstIterator itr; - for (itr = d->m_resetList.constBegin(); itr != d->m_resetList.constEnd(); ++itr) { - (*itr).write(); - } - - loadList(); -} - -void KSettingsOnlineQuotes::slotLoadWidgets() -{ - Q_D(KSettingsOnlineQuotes); - d->m_quoteInEditing = false; - QListWidgetItem* item = d->ui->m_quoteSourceList->currentItem(); - - d->ui->m_editURL->setEnabled(true); - d->ui->m_editCSVURL->setEnabled(true); - d->ui->m_editIdentifier->setEnabled(true); - d->ui->m_editIdentifyBy->setEnabled(true); - d->ui->m_editPrice->setEnabled(true); - d->ui->m_editDate->setEnabled(true); - d->ui->m_editDateFormat->setEnabled(true); - d->ui->m_skipStripping->setEnabled(true); - d->ui->m_dumpCSVProfile->setEnabled(true); - d->ui->m_deleteButton->setEnabled(true); - d->ui->m_editURL->setText(QString()); - d->ui->m_editCSVURL->setText(QString()); - d->ui->m_editIdentifier->setText(QString()); - d->ui->m_editIdentifyBy->setCurrentIndex(WebPriceQuoteSource::identifyBy::Symbol); - d->ui->m_editPrice->setText(QString()); - d->ui->m_editDate->setText(QString()); - d->ui->m_editDateFormat->setText(QString()); - - if (item) { - d->m_currentItem = WebPriceQuoteSource(item->text()); - d->ui->m_editURL->setText(d->m_currentItem.m_url); - d->ui->m_editCSVURL->setText(d->m_currentItem.m_csvUrl); - d->ui->m_editIdentifier->setText(d->m_currentItem.m_webID); - d->ui->m_editIdentifyBy->setCurrentIndex(d->m_currentItem.m_webIDBy); - d->ui->m_editPrice->setText(d->m_currentItem.m_price); - d->ui->m_editDate->setText(d->m_currentItem.m_date); - d->ui->m_editDateFormat->setText(d->m_currentItem.m_dateformat); - d->ui->m_skipStripping->setChecked(d->m_currentItem.m_skipStripping); - } else { - d->ui->m_editURL->setEnabled(false); - d->ui->m_editCSVURL->setEnabled(false); - d->ui->m_editIdentifier->setEnabled(false); - d->ui->m_editIdentifyBy->setEnabled(false); - d->ui->m_editPrice->setEnabled(false); - d->ui->m_editDate->setEnabled(false); - d->ui->m_editDateFormat->setEnabled(false); - d->ui->m_skipStripping->setEnabled(false); - d->ui->m_dumpCSVProfile->setEnabled(false); - d->ui->m_deleteButton->setEnabled(false); - } - - d->ui->m_updateButton->setEnabled(false); - -} - -void KSettingsOnlineQuotes::slotEntryChanged() -{ - Q_D(KSettingsOnlineQuotes); - bool modified = d->ui->m_editURL->text() != d->m_currentItem.m_url - || d->ui->m_editCSVURL->text() != d->m_currentItem.m_csvUrl - || d->ui->m_editIdentifier->text() != d->m_currentItem.m_webID - || d->ui->m_editIdentifyBy->currentData().toInt() != static_cast(d->m_currentItem.m_webIDBy) - || d->ui->m_editDate->text() != d->m_currentItem.m_date - || d->ui->m_editDateFormat->text() != d->m_currentItem.m_dateformat - || d->ui->m_editPrice->text() != d->m_currentItem.m_price - || d->ui->m_skipStripping->isChecked() != d->m_currentItem.m_skipStripping; - - d->ui->m_updateButton->setEnabled(modified); -} - -void KSettingsOnlineQuotes::slotEntryChanged(int) -{ - slotEntryChanged(); -} - -void KSettingsOnlineQuotes::slotEntryChanged(const QString&) -{ - slotEntryChanged(); -} - -void KSettingsOnlineQuotes::slotEntryChanged(bool) -{ - slotEntryChanged(); -} - -void KSettingsOnlineQuotes::slotDumpCSVProfile() -{ - Q_D(KSettingsOnlineQuotes); - KSharedConfigPtr config = CSVImporterCore::configFile(); - PricesProfile profile; - profile.m_profileName = d->m_currentItem.m_name; - profile.m_profileType = Profile::StockPrices; - bool profileExists = false; - bool writeProfile = true; - - if (profile.readSettings(config)) - profileExists = true; - else { - profile.m_profileType = Profile::CurrencyPrices; - if (profile.readSettings(config)) - profileExists = true; - } - - if (profileExists) - writeProfile = (KMessageBox::questionYesNoCancel(this, - i18n("CSV profile %1 already exists.
" - "Do you want to overwrite it?", - d->m_currentItem.m_name), - i18n("CSV Profile Already Exists")) == KMessageBox::Yes ? true : false); - - if (writeProfile) { - QMap quoteSources = WebPriceQuote::defaultCSVQuoteSources(); - profile = quoteSources.value(d->m_currentItem.m_name); - if (profile.m_profileName.compare(d->m_currentItem.m_name, Qt::CaseInsensitive) == 0) { - profile.writeSettings(config); - CSVImporterCore::profilesAction(profile.type(), ProfileAction::Add, profile.m_profileName, profile.m_profileName); - } - } - CSVImporterCore::profilesAction(profile.type(), ProfileAction::UpdateLastUsed, profile.m_profileName, profile.m_profileName); -} - -void KSettingsOnlineQuotes::slotUpdateEntry() -{ - Q_D(KSettingsOnlineQuotes); - d->m_currentItem.m_url = d->ui->m_editURL->text(); - d->m_currentItem.m_csvUrl = d->ui->m_editCSVURL->text(); - d->m_currentItem.m_webID = d->ui->m_editIdentifier->text(); - d->m_currentItem.m_webIDBy = static_cast(d->ui->m_editIdentifyBy->currentData().toInt()); - d->m_currentItem.m_date = d->ui->m_editDate->text(); - d->m_currentItem.m_dateformat = d->ui->m_editDateFormat->text(); - d->m_currentItem.m_price = d->ui->m_editPrice->text(); - d->m_currentItem.m_skipStripping = d->ui->m_skipStripping->isChecked(); - d->m_currentItem.write(); - slotEntryChanged(); -} - -void KSettingsOnlineQuotes::slotNewEntry() -{ - Q_D(KSettingsOnlineQuotes); - WebPriceQuoteSource newSource(i18n("New Quote Source")); - newSource.write(); - loadList(); - QListWidgetItem* item = d->ui->m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly).at(0); - if (item) { - d->ui->m_quoteSourceList->setCurrentItem(item); - slotLoadWidgets(); - } -} - -void KSettingsOnlineQuotes::slotDeleteEntry() -{ - Q_D(KSettingsOnlineQuotes); - // first check if no security is using this online source - auto securities = MyMoneyFile::instance()->securityList(); - foreach(const auto security, securities) { - if (security.value(QStringLiteral("kmm-online-source")).compare(d->m_currentItem.m_name) == 0) { - if (KMessageBox::questionYesNo(this, - i18n("Security %1 uses this quote source.
" - "Do you really want to remove it?", security.name()), - i18n("Delete quote source")) == KMessageBox::Yes) - break; // webpricequote can handle missing online quotes, so proceed without any extra action - else - return; - } - } - - // remove online source from webpricequote... - d->m_currentItem.remove(); - - // ...and from setting's list - auto row = d->ui->m_quoteSourceList->currentRow(); - QListWidgetItem *item = d->ui->m_quoteSourceList->takeItem(row); - if (item) - delete item; - item = nullptr; - - int count = d->ui->m_quoteSourceList->count(); - if (row < count) // select next available entry... - item = d->ui->m_quoteSourceList->item(row); - else if (row >= count && count > 0) // ...or last entry if this was the last entry... - item = d->ui->m_quoteSourceList->item(count - 1); - - if (item) { - d->ui->m_quoteSourceList->setCurrentItem(item); - slotLoadWidgets(); - } -} - -void KSettingsOnlineQuotes::slotStartRename(QListWidgetItem* item) -{ - Q_D(KSettingsOnlineQuotes); - d->m_quoteInEditing = true; - d->ui->m_quoteSourceList->editItem(item); -} - -void KSettingsOnlineQuotes::slotEntryRenamed(QListWidgetItem* item) -{ - Q_D(KSettingsOnlineQuotes); - //if there is no current item selected, exit - if (d->m_quoteInEditing == false || !d->ui->m_quoteSourceList->currentItem() || item != d->ui->m_quoteSourceList->currentItem()) - return; - - d->m_quoteInEditing = false; - QString text = item->text(); - int nameCount = 0; - for (auto i = 0; i < d->ui->m_quoteSourceList->count(); ++i) { - if (d->ui->m_quoteSourceList->item(i)->text() == text) - ++nameCount; - } - - // Make sure we get a non-empty and unique name - if (text.length() > 0 && nameCount == 1) { - d->m_currentItem.rename(text); - } else { - item->setText(d->m_currentItem.m_name); - } - d->ui->m_quoteSourceList->sortItems(); - d->ui->m_newButton->setEnabled(d->ui->m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly).count() == 0); -} diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.h b/kmymoney/dialogs/settings/ksettingsonlinequotes.h deleted file mode 100644 index c6116de12..000000000 --- a/kmymoney/dialogs/settings/ksettingsonlinequotes.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2005-2010 Thomas Baumgart - * Copyright 2017-2018 Łukasz Wojniłowicz - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef KSETTINGSONLINEQUOTES_H -#define KSETTINGSONLINEQUOTES_H - -// ---------------------------------------------------------------------------- -// QT Includes - -#include - -// ---------------------------------------------------------------------------- -// KDE Includes - -// ---------------------------------------------------------------------------- -// Project Includes - -class QListWidgetItem; - -class KSettingsOnlineQuotesPrivate; -class KSettingsOnlineQuotes : public QWidget -{ - Q_OBJECT - Q_DISABLE_COPY(KSettingsOnlineQuotes) - -public: - explicit KSettingsOnlineQuotes(QWidget* parent = nullptr); - ~KSettingsOnlineQuotes(); - - void writeConfig() {} - void readConfig() {} - void resetConfig(); - -protected Q_SLOTS: - void slotDumpCSVProfile(); - void slotUpdateEntry(); - void slotLoadWidgets(); - void slotEntryChanged(); - void slotEntryChanged(int idx); - void slotEntryChanged(const QString& str); - void slotEntryChanged(bool b); - void slotNewEntry(); - void slotDeleteEntry(); - void slotEntryRenamed(QListWidgetItem* item); - void slotStartRename(QListWidgetItem* item); - -protected: - void loadList(const bool updateResetList = false); - -private: - KSettingsOnlineQuotesPrivate * const d_ptr; - Q_DECLARE_PRIVATE(KSettingsOnlineQuotes) -}; - -#endif diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.ui b/kmymoney/dialogs/settings/ksettingsonlinequotes.ui deleted file mode 100644 index ff0748056..000000000 --- a/kmymoney/dialogs/settings/ksettingsonlinequotes.ui +++ /dev/null @@ -1,253 +0,0 @@ - - - KSettingsOnlineQuotes - - - - 0 - 0 - 512 - 592 - - - - - 0 - 0 - - - - Online Quotes - - - - - - - 0 - 0 - - - - - - - - <i>Enter regular expressions which can be used to parse the data returned from the URL entered above. The symbol, price, and date must be found in the quote data to be usable. You may also try the KMyMoney user's mailinglist at <a href="mailto:kmymoney@kde.org">kmymoney@kde.org</a> to find what settings work for other users in your country.</i> - - - Details - - - - - - Date Format - - - false - - - - - - - Identifier - - - false - - - - - - - - - - Regular Expression to extract the identifier from the downloaded data - - - - - - - Price - - - false - - - - - - - Regular Expression to extract the date from the downloaded data - - - - - - - Date - - - false - - - - - - - URL - - - false - - - - - - - CSV URL - - - - - - - URL to be used to download the quote - - - Enter the URL from which stock quotes will be fetched. <b>%1</b> will be replaced with the symbol for the security being quoted. For currency conversions, <b>%2</b> will be replaced with the currency to be quoted and <b>%1</b> with the currency the quote is based on. - - - - - - - Regular Expression to extract the date from the downloaded data - - - - - - - - - - Regular Expression to extract the price from the downloaded data - - - - - - - <p>For easier processing of the data returned by the online source, KMyMoney usually strips unused parts before it is parsed with the regular expressions. If matching of the fields relies on those items, then use this option to turn stripping off.</p> - -<p>The following items are usually removed by stripping: - -<ul> -<li>HTML tags such as <b>&lt;tag&gt;</b></li> -<li>& encoded characters such as <b>&amp;nbsp;</b></li> -<li>duplicate whitespace</li> -</ul> -</p> - - - Skip HTML stripping - - - - - - - Identify by - - - - - - - - - - - - Create a new source entry for online quotes - - - Use this to create a new entry for online quotes - - - New - - - - - - - Delete the selected source entry - - - Use this to delete the selected online source entry - - - Delete - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 240 - 20 - - - - - - - - Dumps CSV profile used for importing downloaded prices, which can be customized by user in CSV Importer. - - - Dump CSV - - - - - - - Accepts the entered data and stores it - - - Use this to accept the modified data. - - - Update - - - - - - - - - - - KLineEdit - QLineEdit -
klineedit.h
-
-
- - -
diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 9f93a57da..3b53eb4cc 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,3732 +1,3738 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart (C) 2017, 2018 by Łukasz Wojniłowicz ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoney.h" // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_HOLIDAYS #include #include #endif #ifdef ENABLE_ACTIVITIES #include #endif +#include +#include + // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneysettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/knewbankdlg.h" #include "dialogs/ksaveasquestion.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/ktemplateexportdlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/kmymoneyaccountselector.h" #include "widgets/kmymoneypayeecombo.h" #include "widgets/amountedit.h" #include "widgets/kmymoneyedit.h" #include "widgets/kmymoneymvccombo.h" #include "views/kmymoneyview.h" #include "models/models.h" #include "models/accountsmodel.h" #include "models/equitiesmodel.h" #include "models/securitiesmodel.h" #ifdef ENABLE_UNFINISHEDFEATURES #include "models/ledgermodel.h" #endif #include "mymoney/mymoneyobject.h" #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyinstitution.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneyaccountloan.h" #include "mymoney/mymoneysecurity.h" #include "mymoney/mymoneypayee.h" #include "mymoney/mymoneyprice.h" #include "mymoney/mymoneytag.h" #include "mymoney/mymoneybudget.h" #include "mymoney/mymoneyreport.h" #include "mymoney/mymoneysplit.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/mymoneytransactionfilter.h" #include "mymoneyexception.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmappinterface.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "kmymoneyplugin.h" #include "tasks/credittransfer.h" #include "icons/icons.h" #include "misc/webconnect.h" #include "storage/mymoneystoragemgr.h" #include "imymoneystorageformat.h" #include "transactioneditor.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" #include "storageenums.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "viewenums.h" #include "menuenums.h" #include "kmymoneyenums.h" #include "misc/platformtools.h" #ifdef ENABLE_SQLCIPHER #include "sqlcipher/sqlite3.h" #endif #ifdef KMM_DEBUG #include "mymoney/storage/mymoneystoragedump.h" #include "mymoneytracer.h" #endif using namespace Icons; using namespace eMenu; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: explicit Private(KMyMoneyApp *app) : q(app), m_backupState(backupStateE::BACKUP_IDLE), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(nullptr), m_startDialog(false), m_progressBar(nullptr), m_statusLabel(nullptr), m_autoSaveEnabled(true), m_autoSaveTimer(nullptr), m_progressTimer(nullptr), m_autoSavePeriod(0), m_inAutoSaving(false), m_recentFiles(nullptr), #ifdef ENABLE_HOLIDAYS m_holidayRegion(nullptr), #endif #ifdef ENABLE_ACTIVITIES m_activityResourceInstance(nullptr), #endif m_applicationIsReady(true), m_webConnect(new WebConnect(app)) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); struct storageInfo { eKMyMoney::StorageType type {eKMyMoney::StorageType::None}; bool isOpened {false}; QUrl url; }; storageInfo m_storageInfo; /** * The public interface. */ KMyMoneyApp * const q; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; KRecentFilesAction* m_recentFiles; #ifdef ENABLE_HOLIDAYS // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif #ifdef ENABLE_ACTIVITIES KActivities::ResourceInstance * m_activityResourceInstance; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; WebConnect* m_webConnect; // methods void consistencyCheck(bool alwaysDisplayResults); static void setThemedCSS(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const { auto file = MyMoneyFile::instance(); if (_acc.name() != name) { MyMoneyAccount acc(_acc); acc.setName(name); file->modifyAccount(acc); } } /** * This method updates names of currencies from file to localized names */ void updateCurrencyNames() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QList storedCurrencies = MyMoneyFile::instance()->currencyList(); QList availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); QStringList currencyIDs; foreach (auto currency, availableCurrencies) currencyIDs.append(currency.id()); try { foreach (auto currency, storedCurrencies) { int i = currencyIDs.indexOf(currency.id()); if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { currency.setName(availableCurrencies.at(i).name()); file->modifyCurrency(currency); } } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s updating currency names", e.what()); } } void updateAccountNames() { // make sure we setup the name of the base accounts in translated form try { MyMoneyFileTransaction ft; const auto file = MyMoneyFile::instance(); checkAccountName(file->asset(), i18n("Asset")); checkAccountName(file->liability(), i18n("Liability")); checkAccountName(file->income(), i18n("Income")); checkAccountName(file->expense(), i18n("Expense")); checkAccountName(file->equity(), i18n("Equity")); ft.commit(); } catch (const MyMoneyException &) { } } void ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } bool applyFileFixes() { const auto blocked = MyMoneyFile::instance()->blockSignals(true); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("General Options"); // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true if (grp.readEntry("SkipFix", false) != true) { MyMoneyFileTransaction ft; try { // Check if we have to modify the file before we allow to work with it auto s = MyMoneyFile::instance()->storage(); while (s->fileFixVersion() < s->currentFixVersion()) { qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); switch (s->fileFixVersion()) { case 0: fixFile_0(); s->setFileFixVersion(1); break; case 1: fixFile_1(); s->setFileFixVersion(2); break; case 2: fixFile_2(); s->setFileFixVersion(3); break; case 3: fixFile_3(); s->setFileFixVersion(4); break; // add new levels above. Don't forget to increase currentFixVersion() for all // the storage backends this fix applies to default: throw MYMONEYEXCEPTION(QString::fromLatin1("Unknown fix level in input file")); } } ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } } else { qDebug("Skipping automatic transaction fix!"); } MyMoneyFile::instance()->blockSignals(blocked); return true; } void connectStorageToModels() { const auto file = MyMoneyFile::instance(); const auto accountsModel = Models::instance()->accountsModel(); q->connect(file, &MyMoneyFile::objectAdded, accountsModel, &AccountsModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, accountsModel, &AccountsModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, accountsModel, &AccountsModel::slotObjectRemoved); q->connect(file, &MyMoneyFile::balanceChanged, accountsModel, &AccountsModel::slotBalanceOrValueChanged); q->connect(file, &MyMoneyFile::valueChanged, accountsModel, &AccountsModel::slotBalanceOrValueChanged); const auto institutionsModel = Models::instance()->institutionsModel(); q->connect(file, &MyMoneyFile::objectAdded, institutionsModel, &InstitutionsModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, institutionsModel, &InstitutionsModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, institutionsModel, &InstitutionsModel::slotObjectRemoved); q->connect(file, &MyMoneyFile::balanceChanged, institutionsModel, &AccountsModel::slotBalanceOrValueChanged); q->connect(file, &MyMoneyFile::valueChanged, institutionsModel, &AccountsModel::slotBalanceOrValueChanged); const auto equitiesModel = Models::instance()->equitiesModel(); q->connect(file, &MyMoneyFile::objectAdded, equitiesModel, &EquitiesModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, equitiesModel, &EquitiesModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, equitiesModel, &EquitiesModel::slotObjectRemoved); q->connect(file, &MyMoneyFile::balanceChanged, equitiesModel, &EquitiesModel::slotBalanceOrValueChanged); q->connect(file, &MyMoneyFile::valueChanged, equitiesModel, &EquitiesModel::slotBalanceOrValueChanged); const auto securitiesModel = Models::instance()->securitiesModel(); q->connect(file, &MyMoneyFile::objectAdded, securitiesModel, &SecuritiesModel::slotObjectAdded); q->connect(file, &MyMoneyFile::objectModified, securitiesModel, &SecuritiesModel::slotObjectModified); q->connect(file, &MyMoneyFile::objectRemoved, securitiesModel, &SecuritiesModel::slotObjectRemoved); #ifdef ENABLE_UNFINISHEDFEATURES const auto ledgerModel = Models::instance()->ledgerModel(); q->connect(file, &MyMoneyFile::objectAdded, ledgerModel, &LedgerModel::slotAddTransaction); q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifyTransaction); q->connect(file, &MyMoneyFile::objectRemoved, ledgerModel, &LedgerModel::slotRemoveTransaction); q->connect(file, &MyMoneyFile::objectAdded, ledgerModel, &LedgerModel::slotAddSchedule); q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifySchedule); q->connect(file, &MyMoneyFile::objectRemoved, ledgerModel, &LedgerModel::slotRemoveSchedule); #endif } void disconnectStorageFromModels() { const auto file = MyMoneyFile::instance(); q->disconnect(file, nullptr, Models::instance()->accountsModel(), nullptr); q->disconnect(file, nullptr, Models::instance()->institutionsModel(), nullptr); q->disconnect(file, nullptr, Models::instance()->equitiesModel(), nullptr); q->disconnect(file, nullptr, Models::instance()->securitiesModel(), nullptr); #ifdef ENABLE_UNFINISHEDFEATURES q->disconnect(file, nullptr, Models::instance()->ledgerModel(), nullptr); #endif } bool askAboutSaving() { const auto isFileNotSaved = q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->isEnabled(); const auto isNewFileNotSaved = m_storageInfo.isOpened && m_storageInfo.url.isEmpty(); auto fileNeedsToBeSaved = false; if (isFileNotSaved && KMyMoneySettings::autoSaveOnClose()) { fileNeedsToBeSaved = true; } else if (isFileNotSaved || isNewFileNotSaved) { switch (KMessageBox::warningYesNoCancel(q, i18n("The file has been changed, save it?"))) { case KMessageBox::ButtonCode::Yes: fileNeedsToBeSaved = true; break; case KMessageBox::ButtonCode::No: fileNeedsToBeSaved = false; break; case KMessageBox::ButtonCode::Cancel: default: return false; break; } } if (fileNeedsToBeSaved) { if (isFileNotSaved) return q->slotFileSave(); else if (isNewFileNotSaved) return q->slotFileSaveAs(); } return true; } /** * This method attaches an empty storage object to the MyMoneyFile * object. It calls removeStorage() to remove a possibly attached * storage object. */ void newStorage() { removeStorage(); auto file = MyMoneyFile::instance(); file->attachStorage(new MyMoneyStorageMgr); } /** * This method removes an attached storage from the MyMoneyFile * object. */ void removeStorage() { auto file = MyMoneyFile::instance(); auto p = file->storage(); if (p) { file->detachStorage(p); delete p; } } /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency() { auto file = MyMoneyFile::instance(); // check if we have a base currency. If not, we need to select one QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", e.what()); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(q); // connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); dlg->exec(); delete dlg; } try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", e.what()); } if (!baseId.isEmpty()) { // check that all accounts have a currency QList list; file->accountList(list); QList::Iterator it; // don't forget those standard accounts list << file->asset(); list << file->liability(); list << file->income(); list << file->expense(); list << file->equity(); for (it = list.begin(); it != list.end(); ++it) { QString cid; try { if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); } catch (const MyMoneyException &e) { qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what(); } if (cid.isEmpty()) { (*it).setCurrencyId(baseId); MyMoneyFileTransaction ft; try { file->modifyAccount(*it); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), e.what()); } } } } } /** * Call this to see if the MyMoneyFile contains any unsaved data. * * @retval true if any data has been modified but not saved * @retval false otherwise */ bool dirty() { if (!m_storageInfo.isOpened) return false; return MyMoneyFile::instance()->dirty(); } /* DO NOT ADD code to this function or any of it's called ones. Instead, create a new function, fixFile_n, and modify the initializeStorage() logic above to call it */ void fixFile_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void fixFile_2() { auto file = MyMoneyFile::instance(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); // scan the transactions and modify transactions with two splits // which reference an account and a category to have the memo text // of the account. auto count = 0; foreach (const auto transaction, transactionList) { if (transaction.splitCount() == 2) { QString accountId; QString categoryId; QString accountMemo; QString categoryMemo; foreach (const auto split, transaction.splits()) { auto acc = file->account(split.accountId()); if (acc.isIncomeExpense()) { categoryId = split.id(); categoryMemo = split.memo(); } else { accountId = split.id(); accountMemo = split.memo(); } } if (!accountId.isEmpty() && !categoryId.isEmpty() && accountMemo != categoryMemo) { MyMoneyTransaction t(transaction); MyMoneySplit s(t.splitById(categoryId)); s.setMemo(accountMemo); t.modifySplit(s); file->modifyTransaction(t); ++count; } } } qDebug("%d transactions fixed in fixFile_2", count); } void fixFile_1() { // we need to fix reports. If the account filter list contains // investment accounts, we need to add the stock accounts to the list // as well if we don't have the expert mode enabled if (!KMyMoneySettings::expertMode()) { try { QList reports = MyMoneyFile::instance()->reportList(); QList::iterator it_r; for (it_r = reports.begin(); it_r != reports.end(); ++it_r) { QStringList list; (*it_r).accounts(list); QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == eMyMoney::Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } if (!missing.isEmpty()) { (*it_r).addAccount(missing); MyMoneyFile::instance()->modifyReport(*it_r); } } } catch (const MyMoneyException &) { } } } #if 0 if (!m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; m_accountsView->selectedItems(list); // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected if (!KMyMoneySettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.begin(); it_a != list.end(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } list += missing; } m_filter.addAccount(list); } #endif void fixFile_0() { /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the * file, it is really a warning. So I'm going to print a debug warning, and * then go track them down when I see them to figure out how they got saved * out needing fixing anyway. */ auto file = MyMoneyFile::instance(); QList accountList; file->accountList(accountList); QList::Iterator it_a; QList scheduleList = file->scheduleList(); QList::Iterator it_s; MyMoneyAccount equity = file->equity(); MyMoneyAccount asset = file->asset(); bool equityListEmpty = equity.accountList().count() == 0; for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { if ((*it_a).accountType() == eMyMoney::Account::Type::Loan || (*it_a).accountType() == eMyMoney::Account::Type::AssetLoan) { fixLoanAccount_0(*it_a); } // until early before 0.8 release, the equity account was not saved to // the file. If we have an equity account with no sub-accounts but // find and equity account that has equity() as it's parent, we reparent // this account. Need to move it to asset() first, because otherwise // MyMoneyFile::reparent would act as NOP. if (equityListEmpty && (*it_a).accountType() == eMyMoney::Account::Type::Equity) { if ((*it_a).parentAccountId() == equity.id()) { auto acc = *it_a; // tricky, force parent account to be empty so that we really // can re-parent it acc.setParentAccountId(QString()); file->reparentAccount(acc, equity); qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); } } } for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { fixSchedule_0(*it_s); } fixTransactions_0(); } void fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; try { bool updated = false; // Check if the splits contain valid data and set it to // be valid. for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) { // the first split is always the account on which this transaction operates // and if the transaction commodity is not set, we take this if (it_s == splitList.constBegin() && t.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; try { auto acc = MyMoneyFile::instance()->account((*it_s).accountId()); t.setCommodity(acc.currencyId()); updated = true; } catch (const MyMoneyException &) { } } // make sure the account exists. If not, remove the split try { MyMoneyFile::instance()->account((*it_s).accountId()); } catch (const MyMoneyException &) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; t.removeSplit(*it_s); updated = true; } if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; MyMoneySplit split = *it_s; split.setReconcileDate(QDate()); split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); t.modifySplit(split); updated = true; } // the schedule logic used to operate only on the value field. // This is now obsolete. if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) { MyMoneySplit split = *it_s; split.setShares(split.value()); t.modifySplit(split); updated = true; } } // If there have been changes, update the schedule and // the engine data. if (updated) { sched.setTransaction(t); MyMoneyFile::instance()->modifySchedule(sched); } } catch (const MyMoneyException &e) { qWarning("Unable to update broken schedule: %s", e.what()); } } void fixLoanAccount_0(MyMoneyAccount acc) { if (acc.value("final-payment").isEmpty() || acc.value("term").isEmpty() || acc.value("periodic-payment").isEmpty() || acc.value("loan-amount").isEmpty() || acc.value("interest-calculation").isEmpty() || acc.value("schedule").isEmpty() || acc.value("fixed-interest").isEmpty()) { KMessageBox::information(q, i18n("

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

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

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

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

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

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

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

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

File %1 is already opened in another instance of KMyMoney

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

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

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); return false; } if (d->m_storageInfo.isOpened) if (!slotFileClose()) return false; // open the database d->m_storageInfo.type = eKMyMoney::StorageType::None; for (auto &plugin : pPlugins.storage) { try { if (auto pStorage = plugin->open(url)) { MyMoneyFile::instance()->attachStorage(pStorage); d->m_storageInfo.type = plugin->storageType(); if (plugin->storageType() != eKMyMoney::StorageType::GNC) { d->m_storageInfo.url = url; writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); /* Don't use url variable after KRecentFilesAction::addUrl * as it might delete it. * More in API reference to this method */ d->m_recentFiles->addUrl(url); } d->m_storageInfo.isOpened = true; break; } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Cannot open file as requested."), QString::fromLatin1(e.what())); return false; } } if(d->m_storageInfo.type == eKMyMoney::StorageType::None) { KMessageBox::error(this, i18n("Could not read your data source. Please check the KMyMoney settings that the necessary plugin is enabled.")); return false; } d->fileAction(eKMyMoney::FileAction::Opened); return true; } bool KMyMoneyApp::slotFileSave() { KMSTATUS(i18n("Saving file...")); for (const auto& plugin : pPlugins.storage) { if (plugin->storageType() == d->m_storageInfo.type) { d->consistencyCheck(false); try { if (plugin->save(d->m_storageInfo.url)) { d->fileAction(eKMyMoney::FileAction::Saved); return true; } return false; } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what()); return false; } } } KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); return false; } bool KMyMoneyApp::slotFileSaveAs() { KMSTATUS(i18n("Saving file as....")); QVector availableFileTypes; for (const auto& plugin : pPlugins.storage) { switch (plugin->storageType()) { case eKMyMoney::StorageType::GNC: break; default: availableFileTypes.append(plugin->storageType()); break; } } auto chosenFileType = eKMyMoney::StorageType::None; switch (availableFileTypes.count()) { case 0: KMessageBox::error(this, i18n("Couldn't find any plugin for saving storage.")); return false; case 1: chosenFileType = availableFileTypes.first(); break; default: { QPointer dlg = new KSaveAsQuestion(availableFileTypes, this); auto rc = dlg->exec(); if (dlg) { auto fileType = dlg->fileType(); delete dlg; if (rc != QDialog::Accepted) return false; chosenFileType = fileType; } } } for (const auto &plugin : pPlugins.storage) { if (chosenFileType == plugin->storageType()) { try { d->consistencyCheck(false); if (plugin->saveAs()) { d->fileAction(eKMyMoney::FileAction::Saved); d->m_storageInfo.type = plugin->storageType(); return true; } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what()); } } } return false; } bool KMyMoneyApp::slotFileClose() { if (!d->m_storageInfo.isOpened) return true; if (!d->askAboutSaving()) return false; d->fileAction(eKMyMoney::FileAction::Closing); d->removeStorage(); d->m_storageInfo = KMyMoneyApp::Private::storageInfo(); d->fileAction(eKMyMoney::FileAction::Closed); return true; } void KMyMoneyApp::slotFileQuit() { // don't modify the status message here as this will prevent quit from working!! // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 bool quitApplication = true; QList memberList = KMainWindow::memberList(); if (!memberList.isEmpty()) { QList::const_iterator w_it = memberList.constBegin(); for (; w_it != memberList.constEnd(); ++w_it) { // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, // the window and the application stay open. if (!(*w_it)->close()) { quitApplication = false; break; } } } // We will only quit if all windows were processed and not cancelled if (quitApplication) { QCoreApplication::quit(); } } void KMyMoneyApp::Private::fileAction(eKMyMoney::FileAction action) { switch(action) { case eKMyMoney::FileAction::Opened: q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); updateAccountNames(); updateCurrencyNames(); selectBaseCurrency(); // setup the standard precision AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); applyFileFixes(); Models::instance()->fileOpened(); connectStorageToModels(); // inform everyone about new data MyMoneyFile::instance()->forceDataChanged(); updateActions(); m_myMoneyView->slotFileOpened(); onlineJobAdministration::instance()->updateActions(); m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened); m_myMoneyView->slotRefreshViews(); onlineJobAdministration::instance()->updateOnlineTaskProperties(); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); #ifdef ENABLE_ACTIVITIES m_activityResourceInstance->setUri(m_storageInfo.url); #endif // start the check for scheduled transactions that need to be // entered as soon as the event loop becomes active. QMetaObject::invokeMethod(q, "slotCheckSchedules", Qt::QueuedConnection); // make sure to catch view activations connect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected); break; case eKMyMoney::FileAction::Saved: q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); m_autoSaveTimer->stop(); break; case eKMyMoney::FileAction::Closing: disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); // make sure to not catch view activations anymore disconnect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected); m_myMoneyView->slotFileClosed(); // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) Models::instance()->fileClosed(); break; case eKMyMoney::FileAction::Closed: q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); disconnectStorageFromModels(); q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened); updateActions(); break; case eKMyMoney::FileAction::Changed: q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(true && !m_storageInfo.url.isEmpty()); // As this method is called every time the MyMoneyFile instance // notifies a modification, it's the perfect place to start the timer if needed if (m_autoSaveEnabled && !m_autoSaveTimer->isActive()) { m_autoSaveTimer->setSingleShot(true); m_autoSaveTimer->start(m_autoSavePeriod * 60 * 1000); //miliseconds } pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts()); break; default: break; } updateCaption(); } KMStatus::KMStatus(const QString &text) : m_prevText(kmymoney->slotStatusMsg(text)) { } KMStatus::~KMStatus() { kmymoney->slotStatusMsg(m_prevText); } void KMyMoneyApp::Private::unlinkStatementXML() { QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); } } diff --git a/kmymoney/plugins/gnc/import/kgncpricesourcedlg.cpp b/kmymoney/plugins/gnc/import/kgncpricesourcedlg.cpp index 561b51903..66d1ffd4d 100644 --- a/kmymoney/plugins/gnc/import/kgncpricesourcedlg.cpp +++ b/kmymoney/plugins/gnc/import/kgncpricesourcedlg.cpp @@ -1,144 +1,144 @@ /*************************************************************************** kgncpricesourcedlg.cpp ------------------- copyright : (C) 2005 by Tony Bloomfield (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgncpricesourcedlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes -#include "webpricequote.h" +#include #include "ui_kgncpricesourcedlg.h" class KGncPriceSourceDlgPrivate { Q_DISABLE_COPY(KGncPriceSourceDlgPrivate) public: KGncPriceSourceDlgPrivate() : ui(new Ui::KGncPriceSourceDlg), currentButton(0) { } ~KGncPriceSourceDlgPrivate() { delete ui; } Ui::KGncPriceSourceDlg *ui; int currentButton; }; KGncPriceSourceDlg::KGncPriceSourceDlg(const QString &stockName, const QString& gncSource, QWidget * parent) : QDialog(parent), d_ptr(new KGncPriceSourceDlgPrivate) { Q_D(KGncPriceSourceDlg); d->ui->setupUi(this); // signals and slots connections connect(d->ui->buttonsSource, static_cast(&QButtonGroup::buttonClicked), this, &KGncPriceSourceDlg::buttonPressed); connect(d->ui->buttonBox, &QDialogButtonBox::helpRequested, this, &KGncPriceSourceDlg::slotHelp); // initialize data fields d->ui->textStockName->setText(i18n("Investment: %1", stockName)); d->ui->textGncSource->setText(i18n("Quote source: %1", gncSource)); d->ui->listKnownSource->clear(); // TODO: return this feature // d->ui->listKnownSource->insertItems(0, WebPriceQuote::quoteSources()); d->ui->lineUserSource->setText(gncSource); d->ui->checkAlwaysUse->setChecked(true); d->ui->buttonsSource->setId(d->ui->buttonNoSource, 0); d->ui->buttonsSource->setId(d->ui->buttonSelectSource, 1); d->ui->buttonsSource->setId(d->ui->buttonUserSource, 2); d->ui->buttonsSource->button(0)->setChecked(true); buttonPressed(0); } KGncPriceSourceDlg::~KGncPriceSourceDlg() { Q_D(KGncPriceSourceDlg); delete d; } enum ButtonIds {NOSOURCE = 0, KMMSOURCE, USERSOURCE}; void KGncPriceSourceDlg::buttonPressed(int buttonId) { Q_D(KGncPriceSourceDlg); d->currentButton = buttonId; switch (d->currentButton) { case NOSOURCE: d->ui->listKnownSource->clearSelection(); d->ui->listKnownSource->setEnabled(false); d->ui->lineUserSource->deselect(); d->ui->lineUserSource->setEnabled(false); break; case KMMSOURCE: d->ui->lineUserSource->deselect(); d->ui->lineUserSource->setEnabled(false); d->ui->listKnownSource->setEnabled(true); d->ui->listKnownSource->setFocus(); d->ui->listKnownSource->setCurrentRow(0); break; case USERSOURCE: d->ui->listKnownSource->clearSelection(); d->ui->listKnownSource->setEnabled(false); d->ui->lineUserSource->setEnabled(true); d->ui->lineUserSource->selectAll(); d->ui->lineUserSource->setFocus(); break; } } QString KGncPriceSourceDlg::selectedSource() const { Q_D(const KGncPriceSourceDlg); switch (d->currentButton) { case KMMSOURCE: return d->ui->listKnownSource->currentItem()->text(); case USERSOURCE: return d->ui->lineUserSource->text(); case NOSOURCE: default: return QString(); } } bool KGncPriceSourceDlg::alwaysUse() const { Q_D(const KGncPriceSourceDlg); return d->ui->checkAlwaysUse->isChecked(); } void KGncPriceSourceDlg::slotHelp() { KHelpClient::invokeHelp("details.impexp.gncquotes"); } diff --git a/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt b/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt index 7d658b4e4..ec70a2c41 100644 --- a/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt +++ b/kmymoney/wizards/newinvestmentwizard/CMakeLists.txt @@ -1,24 +1,24 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) set (libnewinvestmentwizard_a_SOURCES knewinvestmentwizard.cpp konlineupdatewizardpage.cpp kinvestmentdetailswizardpage.cpp kinvestmenttypewizardpage.cpp ) set (libnewinvestmentwizard_a_UI knewinvestmentwizard.ui kinvestmentdetailswizardpage.ui kinvestmenttypewizardpage.ui konlineupdatewizardpage.ui ) ki18n_wrap_ui(libnewinvestmentwizard_a_SOURCES ${libnewinvestmentwizard_a_UI} ) add_library(newinvestmentwizard STATIC ${libnewinvestmentwizard_a_SOURCES}) # TODO: cleanup dependencies -target_link_libraries(newinvestmentwizard KF5::I18n KF5::ConfigWidgets KF5::Completion KF5::KIOWidgets KF5::WidgetsAddons Alkimia::alkimia Qt5::Widgets Qt5::Xml) +target_link_libraries(newinvestmentwizard KF5::I18n KF5::ConfigWidgets KF5::Completion KF5::KIOWidgets KF5::WidgetsAddons Alkimia::alkimia Qt5::Widgets Qt5::Xml kmm_mymoney) # we rely on some widgets to be generated add_dependencies(newinvestmentwizard widgets) diff --git a/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp b/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp index f4b21370d..01eca743f 100644 --- a/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp +++ b/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp @@ -1,325 +1,329 @@ /*************************************************************************** knewinvestmentwizard - description ------------------- begin : Sat Dec 4 2004 copyright : (C) 2004 by Thomas Baumgart email : kmymoney-devel@kde.org (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "knewinvestmentwizard.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include +#include +#include + // ---------------------------------------------------------------------------- // Project Includes #include "ui_knewinvestmentwizard.h" #include "mymoneyaccount.h" +#include "mymoneyenums.h" #include "mymoneysecurity.h" #include "mymoneyfile.h" -#include "webpricequote.h" +#include "mymoneymoney.h" #include "kmymoneyutils.h" #include "mymoneyexception.h" class KNewInvestmentWizardPrivate { Q_DISABLE_COPY(KNewInvestmentWizardPrivate) Q_DECLARE_PUBLIC(KNewInvestmentWizard) public: explicit KNewInvestmentWizardPrivate(KNewInvestmentWizard *qq) : q_ptr(qq), ui(new Ui::KNewInvestmentWizard), m_createAccount(false) { } ~KNewInvestmentWizardPrivate() { delete ui; } void init1() { Q_Q(KNewInvestmentWizard); ui->m_onlineUpdatePage->slotSourceChanged(false); // make sure, the back button does not clear fields q->setOption(QWizard::IndependentPages, true); // enable the help button q->setOption(q->HaveHelpButton, true); q->connect(q, &KNewInvestmentWizard::helpRequested, q, &KNewInvestmentWizard::slotHelp); m_createAccount = true; // Update label in case of edit if (!m_account.id().isEmpty()) { ui->m_investmentTypePage->setIntroLabelText(i18n("This wizard allows you to modify the selected investment.")); } if (!m_security.id().isEmpty()) { ui->m_investmentTypePage->setIntroLabelText(i18n("This wizard allows you to modify the selected security.")); } KMyMoneyUtils::updateWizardButtons(q); } void init2() { ui->m_investmentTypePage->init2(m_security); ui->m_investmentDetailsPage->init2(m_security); ui->m_onlineUpdatePage->init2(m_security); ui->m_onlineUpdatePage->slotCheckPage(m_security.value("kmm-online-source")); } KNewInvestmentWizard *q_ptr; Ui::KNewInvestmentWizard *ui; MyMoneyAccount m_account; MyMoneySecurity m_security; bool m_createAccount; }; KNewInvestmentWizard::KNewInvestmentWizard(QWidget *parent) : QWizard(parent), d_ptr(new KNewInvestmentWizardPrivate(this)) { Q_D(KNewInvestmentWizard); d->ui->setupUi(this); d->init1(); d->ui->m_onlineUpdatePage->slotCheckPage(QString()); d->ui->m_investmentDetailsPage->setupInvestmentSymbol(); connect(d->ui->m_investmentDetailsPage, &KInvestmentDetailsWizardPage::checkForExistingSymbol, this, &KNewInvestmentWizard::slotCheckForExistingSymbol); } KNewInvestmentWizard::KNewInvestmentWizard(const MyMoneyAccount& acc, QWidget *parent) : QWizard(parent), d_ptr(new KNewInvestmentWizardPrivate(this)) { Q_D(KNewInvestmentWizard); d->ui->setupUi(this); d->m_account = acc; setWindowTitle(i18n("Investment detail wizard")); d->init1(); // load the widgets with the data setName(d->m_account.name()); d->m_security = MyMoneyFile::instance()->security(d->m_account.currencyId()); d->init2(); int priceMode = 0; if (!d->m_account.value("priceMode").isEmpty()) priceMode = d->m_account.value("priceMode").toInt(); d->ui->m_investmentDetailsPage->setCurrentPriceMode(priceMode); } KNewInvestmentWizard::KNewInvestmentWizard(const MyMoneySecurity& security, QWidget *parent) : QWizard(parent), d_ptr(new KNewInvestmentWizardPrivate(this)) { Q_D(KNewInvestmentWizard); d->ui->setupUi(this); d->m_security = security; setWindowTitle(i18n("Security detail wizard")); d->init1(); d->m_createAccount = false; // load the widgets with the data setName(security.name()); d->init2(); // no chance to change the price mode here d->ui->m_investmentDetailsPage->setCurrentPriceMode(0); d->ui->m_investmentDetailsPage->setPriceModeEnabled(false); } KNewInvestmentWizard::~KNewInvestmentWizard() { } void KNewInvestmentWizard::setName(const QString& name) { Q_D(KNewInvestmentWizard); d->ui->m_investmentDetailsPage->setName(name); } void KNewInvestmentWizard::slotCheckForExistingSymbol(const QString& symbol) { Q_D(KNewInvestmentWizard); Q_UNUSED(symbol); if (field("investmentName").toString().isEmpty()) { QList list = MyMoneyFile::instance()->securityList(); auto type = static_cast(field("securityType").toInt()); foreach (const MyMoneySecurity& it_s, list) { if (it_s.securityType() == type && it_s.tradingSymbol() == field("investmentSymbol").toString()) { d->m_security = MyMoneySecurity(); if (KMessageBox::questionYesNo(this, i18n("The selected symbol is already on file. Do you want to reuse the existing security?"), i18n("Security found")) == KMessageBox::Yes) { d->m_security = it_s; d->init2(); d->ui->m_investmentDetailsPage->loadName(d->m_security.name()); } break; } } } } void KNewInvestmentWizard::slotHelp() { KHelpClient::invokeHelp("details.investments.newinvestmentwizard"); } void KNewInvestmentWizard::createObjects(const QString& parentId) { Q_D(KNewInvestmentWizard); auto file = MyMoneyFile::instance(); auto type = static_cast(field("securityType").toInt()); auto roundingMethod = static_cast(field("roundingMethod").toInt()); MyMoneyFileTransaction ft; try { // update all relevant attributes only, if we create a stock // account and the security is unknown or we modifiy the security MyMoneySecurity newSecurity(d->m_security); newSecurity.setName(field("investmentName").toString()); newSecurity.setTradingSymbol(field("investmentSymbol").toString()); newSecurity.setTradingMarket(field("tradingMarket").toString()); newSecurity.setSmallestAccountFraction(field("fraction").value().formatMoney("", 0, false).toUInt()); newSecurity.setPricePrecision(MyMoneyMoney(field("pricePrecision").toUInt()).formatMoney("", 0, false).toUInt()); newSecurity.setTradingCurrency(field("tradingCurrencyEdit").value().id()); newSecurity.setSecurityType(type); newSecurity.setRoundingMethod(roundingMethod); newSecurity.deletePair("kmm-online-source"); newSecurity.deletePair("kmm-online-quote-system"); newSecurity.deletePair("kmm-online-factor"); newSecurity.deletePair("kmm-security-id"); if (!field("onlineSourceCombo").toString().isEmpty()) { if (field("useFinanceQuote").toBool()) { - FinanceQuoteProcess p; + AlkFinanceQuoteProcess p; newSecurity.setValue("kmm-online-quote-system", "Finance::Quote"); newSecurity.setValue("kmm-online-source", p.crypticName(field("onlineSourceCombo").toString())); } else { newSecurity.setValue("kmm-online-source", field("onlineSourceCombo").toString()); } } if (d->ui->m_onlineUpdatePage->isOnlineFactorEnabled() && (field("onlineFactor").value() != MyMoneyMoney::ONE)) newSecurity.setValue("kmm-online-factor", field("onlineFactor").value().toString()); if (!field("investmentIdentification").toString().isEmpty()) newSecurity.setValue("kmm-security-id", field("investmentIdentification").toString()); if (d->m_security.id().isEmpty() || newSecurity != d->m_security) { d->m_security = newSecurity; // add or update it if (d->m_security.id().isEmpty()) { file->addSecurity(d->m_security); } else { file->modifySecurity(d->m_security); } } if (d->m_createAccount) { // now that the security exists, we can add the account to store it d->m_account.setName(field("investmentName").toString()); if (d->m_account.accountType() == eMyMoney::Account::Type::Unknown) d->m_account.setAccountType(eMyMoney::Account::Type::Stock); d->m_account.setCurrencyId(d->m_security.id()); switch (d->ui->m_investmentDetailsPage->priceMode()) { case 0: d->m_account.deletePair("priceMode"); break; case 1: case 2: d->m_account.setValue("priceMode", QString("%1").arg(d->ui->m_investmentDetailsPage->priceMode())); break; } // update account's fraction in case its security fraction has changed // otherwise KMM restart is required because this won't happen automatically d->m_account.fraction(d->m_security); if (d->m_account.id().isEmpty()) { MyMoneyAccount parent = file->account(parentId); file->addAccount(d->m_account, parent); } else file->modifyAccount(d->m_account); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to create all objects for the investment"), QString::fromLatin1(e.what())); } } void KNewInvestmentWizard::newInvestment(const MyMoneyAccount& parent) { QPointer dlg = new KNewInvestmentWizard; if (dlg->exec() == QDialog::Accepted) dlg->createObjects(parent.id()); delete dlg; } void KNewInvestmentWizard::newInvestment(MyMoneyAccount& account, const MyMoneyAccount& parent) { QString dontShowAgain = "CreateNewInvestments"; if (KMessageBox::questionYesNo(nullptr, i18n("The security %1 currently does not exist as sub-account of %2. " "Do you want to create it?", account.name(), parent.name()), i18n("Create security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontShowAgain) == KMessageBox::Yes) { QPointer dlg = new KNewInvestmentWizard; dlg->setName(account.name()); if (dlg->exec() == QDialog::Accepted) { dlg->createObjects(parent.id()); account = dlg->account(); } delete dlg; } 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 KNewInvestmentWizard::editInvestment(const MyMoneyAccount& parent) { QPointer dlg = new KNewInvestmentWizard(parent); if (dlg->exec() == QDialog::Accepted) dlg->createObjects(parent.id()); delete dlg; } MyMoneyAccount KNewInvestmentWizard::account() const { Q_D(const KNewInvestmentWizard); return d->m_account; } diff --git a/kmymoney/wizards/newinvestmentwizard/konlineupdatewizardpage.cpp b/kmymoney/wizards/newinvestmentwizard/konlineupdatewizardpage.cpp index e32f89404..e38083157 100644 --- a/kmymoney/wizards/newinvestmentwizard/konlineupdatewizardpage.cpp +++ b/kmymoney/wizards/newinvestmentwizard/konlineupdatewizardpage.cpp @@ -1,129 +1,132 @@ /*************************************************************************** konlineupdatewizardpage - description ------------------- begin : Sun Jun 27 2010 copyright : (C) 2010 by Fernando Vilas email : kmymoney-devel@kde.org (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "konlineupdatewizardpage.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes +#include .h> +#include +#include +#include // ---------------------------------------------------------------------------- // Project Includes #include "ui_konlineupdatewizardpage.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" -#include "webpricequote.h" KOnlineUpdateWizardPage::KOnlineUpdateWizardPage(QWidget *parent) : QWizardPage(parent), ui(new Ui::KOnlineUpdateWizardPage) { ui->setupUi(this); ui->m_onlineFactor->setValue(MyMoneyMoney::ONE); ui->m_onlineFactor->setPrecision(4); // make ui->m_onlineSourceCombo sortable QSortFilterProxyModel* proxy = new QSortFilterProxyModel(ui->m_onlineSourceCombo); proxy->setSourceModel(ui->m_onlineSourceCombo->model()); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); ui->m_onlineSourceCombo->model()->setParent(proxy); ui->m_onlineSourceCombo->setModel(proxy); // Connect signals-slots connect(ui->m_useFinanceQuote, &QAbstractButton::toggled, this, &KOnlineUpdateWizardPage::slotSourceChanged); // Register the fields with the QWizard and connect the // appropriate signals to update the "Next" button correctly registerField("onlineFactor", ui->m_onlineFactor, "value"); registerField("onlineSourceCombo", ui->m_onlineSourceCombo, "currentText", SIGNAL(currentIndexChanged(QString))); registerField("useFinanceQuote", ui->m_useFinanceQuote); connect(ui->m_onlineSourceCombo, static_cast(&QComboBox::currentIndexChanged), this, &KOnlineUpdateWizardPage::slotCheckPage); connect(ui->m_onlineFactor, &KMyMoneyEdit::textChanged, this, &QWizardPage::completeChanged); connect(ui->m_onlineSourceCombo, static_cast(&QComboBox::activated), this, &QWizardPage::completeChanged); connect(ui->m_useFinanceQuote, &QAbstractButton::toggled, this, &QWizardPage::completeChanged); } KOnlineUpdateWizardPage::~KOnlineUpdateWizardPage() { delete ui; } /** * Set the values based on the @param security */ void KOnlineUpdateWizardPage::init2(const MyMoneySecurity& security) { int idx; if (security.value("kmm-online-quote-system") == "Finance::Quote") { - FinanceQuoteProcess p; + AlkFinanceQuoteProcess p; ui->m_useFinanceQuote->setChecked(true); idx = ui->m_onlineSourceCombo->findText(p.niceName(security.value("kmm-online-source"))); } else { idx = ui->m_onlineSourceCombo->findText(security.value("kmm-online-source")); } // in case we did not find the entry, we use the empty one if (idx == -1) idx = ui->m_onlineSourceCombo->findText(QString()); ui->m_onlineSourceCombo->setCurrentIndex(idx); if (!security.value("kmm-online-factor").isEmpty()) ui->m_onlineFactor->setValue(MyMoneyMoney(security.value("kmm-online-factor"))); } /** * Update the "Next" button */ bool KOnlineUpdateWizardPage::isComplete() const { return !(ui->m_onlineFactor->isEnabled() && ui->m_onlineFactor->value().isZero()); } bool KOnlineUpdateWizardPage::isOnlineFactorEnabled() const { return ui->m_onlineFactor->isEnabled(); } void KOnlineUpdateWizardPage::slotCheckPage(const QString& txt) { ui->m_onlineFactor->setEnabled(!txt.isEmpty()); } void KOnlineUpdateWizardPage::slotSourceChanged(bool useFQ) { ui->m_onlineSourceCombo->clear(); ui->m_onlineSourceCombo->insertItem(0, QString()); - if (useFQ) { - ui->m_onlineSourceCombo->addItems(WebPriceQuote::quoteSources(WebPriceQuote::FinanceQuote)); - } else { - ui->m_onlineSourceCombo->addItems(WebPriceQuote::quoteSources()); - } +// if (useFQ) { +// ui->m_onlineSourceCombo->addItems(AlkOnlineQuoteSource::quoteSources(AlkOnlineQuoteSource::FinanceQuote)); +// } else { + ui->m_onlineSourceCombo->addItems(AlkOnlineQuotesProfileManager::instance().profiles().first()->quoteSources()); +// } ui->m_onlineSourceCombo->model()->sort(0); }