diff --git a/kmymoney/converter/CMakeLists.txt b/kmymoney/converter/CMakeLists.txt --- a/kmymoney/converter/CMakeLists.txt +++ b/kmymoney/converter/CMakeLists.txt @@ -16,7 +16,7 @@ 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 KF5::I18n Qt5::Widgets Qt5::Gui Qt5::Sql Qt5::Xml Alkimia::alkimia KF5::KIOWidgets) +target_link_libraries(converter PUBLIC KF5::Service KF5::XmlGui KF5::Completion KF5::TextWidgets KF5::WidgetsAddons KF5::ConfigCore KF5::I18n Qt5::Widgets Qt5::Gui Qt5::Sql Qt5::Xml Alkimia::alkimia KF5::KIOWidgets kmm_csvimport_static) # we rely on some dialogs to be generated add_dependencies(converter dialogs) diff --git a/kmymoney/converter/webpricequote.h b/kmymoney/converter/webpricequote.h --- a/kmymoney/converter/webpricequote.h +++ b/kmymoney/converter/webpricequote.h @@ -35,6 +35,9 @@ // Project Headers #include "mymoneymoney.h" +#include "mymoneystatement.h" +#include "csvimport/csvimporter.h" + class KJob; /** Helper class to attend the process which is running the script, in the case @@ -104,15 +107,16 @@ struct WebPriceQuoteSource { WebPriceQuoteSource() : m_skipStripping(false) {} explicit WebPriceQuoteSource(const QString& name); - WebPriceQuoteSource(const QString& name, const QString& url, const QString& sym, const QString& price, const QString& date, const QString& dateformat, bool skipStripping = false); + WebPriceQuoteSource(const QString& name, const QString& url, const QString& csvUrl, const QString& sym, 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_sym; QString m_price; QString m_date; @@ -137,6 +141,7 @@ FinanceQuote } quoteSystemE; + void setDate(const QDate& _from, const QDate& _to); /** * This launches a web-based quote update for the given @p _symbol. * When the quote is received back from the web source, it will be @@ -161,21 +166,26 @@ * @return QStringList of quote source names */ static const QStringList quoteSources(const _quoteSystemE _system = Native); + static const QMap defaultCSVQuoteSources(); 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 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& _symbol, const QString& _id, const QString& _source = QString()); bool launchNative(const QString& _symbol, const QString& _id, const QString& _source = QString()); bool launchFinanceQuote(const QString& _symbol, const QString& _id, const QString& _source = QString()); void enter_loop(); diff --git a/kmymoney/converter/webpricequote.cpp b/kmymoney/converter/webpricequote.cpp --- a/kmymoney/converter/webpricequote.cpp +++ b/kmymoney/converter/webpricequote.cpp @@ -4,6 +4,8 @@ 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 ***************************************************************************/ /*************************************************************************** @@ -51,6 +53,7 @@ #include "mymoneyexception.h" #include "mymoneyqifprofile.h" +#include "mymoneyfile.h" Q_DECLARE_LOGGING_CATEGORY(WEBPRICEQUOTE) Q_LOGGING_CATEGORY(WEBPRICEQUOTE, "kmymoney_webpricequote") @@ -67,8 +70,11 @@ QString m_symbol; QString m_id; QDate m_date; + QDate m_fromDate; + QDate m_toDate; double m_price; WebPriceQuoteSource m_source; + PricesProfile m_CSVSource; }; WebPriceQuote::WebPriceQuote(QObject* _parent): @@ -88,12 +94,140 @@ delete d; } +void WebPriceQuote::setDate(const QDate& _from, const QDate& _to) +{ + d->m_fromDate = _from; + d->m_toDate = _to; +} + bool WebPriceQuote::launch(const QString& _symbol, const QString& _id, const QString& _sourcename) { if (_sourcename.contains("Finance::Quote")) return (launchFinanceQuote(_symbol, _id, _sourcename)); - else + else if ((!d->m_fromDate.isValid() || !d->m_toDate.isValid()) || + (d->m_fromDate == d->m_toDate && d->m_toDate == QDate::currentDate())) return (launchNative(_symbol, _id, _sourcename)); + else + return launchCSV(_symbol, _id, _sourcename); +} + +bool WebPriceQuote::launchCSV(const QString& _symbol, const QString& _id, const QString& _sourcename) +{ + d->m_symbol = _symbol; + d->m_id = _id; + +// 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; + if (sourcename == QLatin1String("Yahoo Currency")) + sourcename = QLatin1String("Stooq Currency"); + + if (quoteSources().contains(sourcename)) + d->m_source = WebPriceQuoteSource(sourcename); + else + emit error(i18n("Source %1 does not exist.", sourcename)); + + 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_id, d->m_symbol); + return false; + } + + bool isCurrency = false; + if (urlStr.contains(QLatin1String("%2"))) { + d->m_CSVSource.m_profileType = ProfileCurrencyPrices; + isCurrency = true; + } else + d->m_CSVSource.m_profileType = ProfileStockPrices; + + d->m_CSVSource.m_profileName = sourcename; + if (!d->m_CSVSource.readSettings(CSVImporter::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_id, d->m_symbol); + 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 + QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); + // if we've truly found 2 symbols delimited this way... + if (splitrx.indexIn(d->m_symbol) != -1) { + url = QUrl(urlStr.arg(splitrx.cap(1), splitrx.cap(2))); + d->m_CSVSource.m_currencySymbol = splitrx.cap(2); + d->m_CSVSource.m_securitySymbol = splitrx.cap(1); + } else { + qCDebug(WEBPRICEQUOTE) << "WebPriceQuote::launch() did not find 2 symbols"; + emit error(i18n("Cannot find from and to currency.")); + emit failed(d->m_id, d->m_symbol); + return false; + } + + } else { + // a regular one-symbol quote + url = QUrl(urlStr.arg(d->m_symbol)); + d->m_CSVSource.m_securityName = MyMoneyFile::instance()->security(d->m_id).name(); + d->m_CSVSource.m_securitySymbol = d->m_symbol; + } + + if (url.isLocalFile()) { + emit error(i18n("Local quote sources aren't supported.")); + emit failed(d->m_id, d->m_symbol); + 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& _symbol, const QString& _id, const QString& _sourcename) @@ -171,6 +305,27 @@ 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(); @@ -208,7 +363,7 @@ d->m_symbol = _symbol; d->m_id = _id; QString FQSource = _sourcename.section(' ', 1); - d->m_source = WebPriceQuoteSource(_sourcename, m_financeQuoteScriptPath, + d->m_source = WebPriceQuoteSource(_sourcename, m_financeQuoteScriptPath, m_financeQuoteScriptPath, "\"([^,\"]*)\",.*", // symbol regexp "[^,]*,[^,]*,\"([^\"]*)\"", // price regexp "[^,]*,([^,]*),.*", // date regexp @@ -235,6 +390,29 @@ return result; } +void WebPriceQuote::slotParseCSVQuote(const QString& filename) +{ + bool isOK = true; + if (filename.isEmpty()) + isOK = false; + else { + MyMoneyStatement st; + CSVImporter* csvImporter = new CSVImporter; + st = csvImporter->unattendedPricesImport(filename, &d->m_CSVSource); + if (!st.m_listPrices.isEmpty()) + emit csvquote(d->m_id, d->m_symbol, st); + else + isOK = false; + delete csvImporter; + QFile::remove(filename); + } + + if (!isOK) { + emit error(i18n("Unable to update price for %1", d->m_symbol)); + emit failed(d->m_id, d->m_symbol); + } +} + void WebPriceQuote::slotParseQuote(const QString& _quotedata) { QString quotedata = _quotedata; @@ -323,110 +501,142 @@ } } +const QMap WebPriceQuote::defaultCSVQuoteSources() +{ + QMap result; + + result[QLatin1String("Stooq")] = PricesProfile(QLatin1String("Stooq"), + 106, 1, 0, 0, 1, 0, 0, + QMap{{ColumnDate, 0}, {ColumnPrice, 4}}, + 2, ProfileStockPrices); + + result[QLatin1String("Stooq Currency")] = PricesProfile(QLatin1String("Stooq Currency"), + 106, 1, 0, 0, 1, 0, 0, + QMap{{ColumnDate, 0}, {ColumnPrice, 4}}, + 2, ProfileCurrencyPrices); + + result[QLatin1String("Yahoo")] = PricesProfile(QLatin1String("Yahoo"), + 106, 1, 0, 0, 0, 0, 0, + QMap{{ColumnDate, 0}, {ColumnPrice, 4}}, + 2, ProfileStockPrices); + return result; +} + const QMap WebPriceQuote::defaultQuoteSources() { QMap result; result["Yahoo"] = WebPriceQuoteSource("Yahoo", "http://finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d1", + "http://ichart.finance.yahoo.com/table.csv?s=%1&a=%m&b=%d&c=%y&d=%m&e=%d&f=%y&g=d&ignore=.csv", "\"([^,\"]*)\",.*", // symbolregexp "[^,]*,([^,]*),.*", // priceregexp "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp "%m %d %y" // dateformat ); result["Yahoo Currency"] = WebPriceQuoteSource("Yahoo Currency", - "http://finance.yahoo.com/d/quotes.csv?s=%1%2=X&f=sl1d1", - "\"([^,\"]*)\",.*", // symbolregexp - "[^,]*,([^,]*),.*", // priceregexp - "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp - "%m %d %y" // dateformat - ); + "http://finance.yahoo.com/d/quotes.csv?s=%1%2=X&f=sl1d1", + "", + "\"([^,\"]*)\",.*", // symbolregexp + "[^,]*,([^,]*),.*", // priceregexp + "[^,]*,[^,]*,\"([^\"]*)\"", // dateregexp + "%m %d %y" // dateformat + ); // 2009-08-20 Yahoo UK has no quotes and has comma separators // sl1d1 format for Yahoo UK doesn't seem to give a date ever // sl1d3 gives US locale time (9:99pm) and date (mm/dd/yyyy) result["Yahoo UK"] = WebPriceQuoteSource("Yahoo UK", - "http://uk.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3", - "^([^,]*),.*", // symbolregexp - "^[^,]*,([^,]*),.*", // priceregexp - "^[^,]*,[^,]*, [^ ]* (../../....).*", // dateregexp - "%m/%d/%y" // dateformat - ); + "http://uk.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3", + "", + "^([^,]*),.*", // symbolregexp + "^[^,]*,([^,]*),.*", // priceregexp + "^[^,]*,[^,]*, [^ ]* (../../....).*", // dateregexp + "%m/%d/%y" // dateformat + ); // sl1d1 format for Yahoo France doesn't seem to give a date ever // sl1d3 gives us time (99h99) and date result["Yahoo France"] = WebPriceQuoteSource("Yahoo France", - "http://fr.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3", - "([^;]*).*", // symbolregexp - "[^;]*.([^;]*),*", // priceregexp - "[^;]*.[^;]*...h...([^;]*)", // dateregexp - "%d/%m/%y" // dateformat - ); + "http://fr.finance.yahoo.com/d/quotes.csv?s=%1&f=sl1d3", + "", + "([^;]*).*", // symbolregexp + "[^;]*.([^;]*),*", // priceregexp + "[^;]*.[^;]*...h...([^;]*)", // dateregexp + "%d/%m/%y" // dateformat + ); result["Globe & Mail"] = WebPriceQuoteSource("Globe & Mail", - "http://globefunddb.theglobeandmail.com/gishome/plsql/gis.price_history?pi_fund_id=%1", - QString(), // symbolregexp - "Reinvestment Price \\w+ \\d+, \\d+ (\\d+\\.\\d+)", // priceregexp - "Reinvestment Price (\\w+ \\d+, \\d+)", // dateregexp - "%m %d %y" // dateformat - ); + "http://globefunddb.theglobeandmail.com/gishome/plsql/gis.price_history?pi_fund_id=%1", + "", + QString(), // symbolregexp + "Reinvestment Price \\w+ \\d+, \\d+ (\\d+\\.\\d+)", // priceregexp + "Reinvestment Price (\\w+ \\d+, \\d+)", // dateregexp + "%m %d %y" // dateformat + ); result["MSN.CA"] = WebPriceQuoteSource("MSN.CA", "http://ca.moneycentral.msn.com/investor/quotes/quotes.asp?symbol=%1", + "", QString(), // symbolregexp "(\\d+\\.\\d+) [+-]\\d+.\\d+", // priceregexp "Time of last trade (\\d+/\\d+/\\d+)", //dateregexp "%d %m %y" // dateformat ); // Finanztreff (replaces VWD.DE) and boerseonline supplied by Micahel Zimmerman result["Finanztreff"] = WebPriceQuoteSource("Finanztreff", - "http://finanztreff.de/kurse_einzelkurs_detail.htn?u=100&i=%1", - QString(), // symbolregexp - "([0-9]+,\\d+).+Gattung:Fonds", // priceregexp - "\\).(\\d+\\D+\\d+\\D+\\d+)", // dateregexp (doesn't work; date in chart - "%d.%m.%y" // dateformat - ); + "http://finanztreff.de/kurse_einzelkurs_detail.htn?u=100&i=%1", + "", + QString(), // symbolregexp + "([0-9]+,\\d+).+Gattung:Fonds", // priceregexp + "\\).(\\d+\\D+\\d+\\D+\\d+)", // dateregexp (doesn't work; date in chart + "%d.%m.%y" // dateformat + ); result["boerseonline"] = WebPriceQuoteSource("boerseonline", - "http://www.boerse-online.de/tools/boerse/einzelkurs_kurse.htm?&s=%1", - QString(), // symbolregexp - "Akt\\. Kurs.([\\d\\.]+,\\d\\d)", // priceregexp - "Datum.(\\d+\\.\\d+\\.\\d+)", // dateregexp (doesn't work; date in chart - "%d.%m.%y" // dateformat - ); + "http://www.boerse-online.de/tools/boerse/einzelkurs_kurse.htm?&s=%1", + "", + QString(), // symbolregexp + "Akt\\. Kurs.([\\d\\.]+,\\d\\d)", // priceregexp + "Datum.(\\d+\\.\\d+\\.\\d+)", // dateregexp (doesn't work; date in chart + "%d.%m.%y" // dateformat + ); // 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+)", // symbolregexp - "Letzter Kurs: ([0-9.]+,\\d+)", // priceregexp - ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp - "%d %m %y" // dateformat - ); + "http://www.wallstreet-online.de/si/?k=%1&spid=ws", + "", + "Symbol:(\\w+)", // symbolregexp + "Letzter Kurs: ([0-9.]+,\\d+)", // priceregexp + ", (\\d+\\D+\\d+\\D+\\d+)", // 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 result["Financial Times UK Funds"] = WebPriceQuoteSource("Financial Times UK Funds", - "http://funds.ft.com/uk/Tearsheet/Summary?s=%1", - "data-display-symbol=\"(.*):", // symbol regexp - "class=\"text first\">([\\d,]*\\d+\\.\\d+)", // price regexp - "As of market close\\ (.*)\\.", // date regexp - "%m %d %y", // date format - true // skip HTML stripping - ); + "http://funds.ft.com/uk/Tearsheet/Summary?s=%1", + "", + "data-display-symbol=\"(.*):", // symbol regexp + "class=\"text first\">([\\d,]*\\d+\\.\\d+)", // price regexp + "As of market close\\ (.*)\\.", // date regexp + "%m %d %y", // date format + true // skip HTML stripping + ); // This quote source provided by Danny Scott result["Yahoo Canada"] = WebPriceQuoteSource("Yahoo Canada", - "http://ca.finance.yahoo.com/q?s=%1", - "%1", // symbol regexp - "Last Trade: (\\d+\\.\\d+)", // price regexp - "day, (.\\D+\\d+\\D+\\d+)", // date regexp - "%m %d %y" // date format - ); + "http://ca.finance.yahoo.com/q?s=%1", + "", + "%1", // symbol regexp + "Last Trade: (\\d+\\.\\d+)", // price regexp + "day, (.\\D+\\d+\\D+\\d+)", // date regexp + "%m %d %y" // date format + ); // (tf2k) The "mpid" is I think the market place id. In this case five // stands for Hamburg. @@ -436,12 +646,13 @@ // 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+)", // symbolregexp - "Fonds \\(EUR\\) ([0-9.]+,\\d+)", // priceregexp - ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp - "%d %m %y" // dateformat - ); + "http://fonds.wallstreet-online.de/si/?k=%1&spid=ws&mpid=5", + "", + "Symbol:(\\w+)", // symbolregexp + "Fonds \\(EUR\\) ([0-9.]+,\\d+)", // priceregexp + ", (\\d+\\D+\\d+\\D+\\d+)", // dateregexp + "%d %m %y" // dateformat + ); // The following price quote was contributed by // Piotr Adacha @@ -454,22 +665,33 @@ // Europe), thus, I think it could be helpful for Polish users of KMyMoney (and // I am one of them for almost a year). - result["Gielda Papierow Wartosciowych (GPW)"] = WebPriceQuoteSource("Gielda Papierow Wartosciowych (GPW)", - "http://stooq.com/q/?s=%1", - QString(), // symbol regexp - "Last.*(\\d+\\.\\d+).*Date", // price regexp - "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp - "%y %m %d" // date format - ); + 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(), // symbol regexp + "Last.*(\\d+\\.\\d+).*Date", // price regexp + "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp + "%y %m %d" // date format + ); + + 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(), // symbol regexp + "Last.*(\\d+\\.\\d+).*Date", // price regexp + "(\\d{4,4}-\\d{2,2}-\\d{2,2})", // date regexp + "%y %m %d" // date format + ); // The following price quote is for getting prices of different funds // at OMX Baltic market. result["OMX Baltic funds"] = WebPriceQuoteSource("OMX Baltic funds", - "http://www.baltic.omxgroup.com/market/?pg=nontradeddetails¤cy=0&instrument=%1", - QString(), // symbolregexp - "NAV (\\d+,\\d+)", // priceregexp - "Kpv (\\d+.\\d+.\\d+)", // dateregexp - "%d.%m.%y" // dateformat + "http://www.baltic.omxgroup.com/market/?pg=nontradeddetails¤cy=0&instrument=%1", + "", + QString(), // symbolregexp + "NAV (\\d+,\\d+)", // priceregexp + "Kpv (\\d+.\\d+.\\d+)", // dateregexp + "%d.%m.%y" // dateformat ); // The following price quote was contributed by @@ -497,12 +719,13 @@ // http://forum.kde.org/update-stock-and-currency-prices-t-32049.html result["Financial Express"] = WebPriceQuoteSource("Financial Express", - "https://webfund6.financialexpress.net/Clients/Barclays/search_factsheet_summary.aspx?code=%1", - "ISIN Code[^G]*(GB..........).*", // symbolregexp - "Current Market Information[^0-9]*([0-9,\\.]+).*", // priceregexp - "Price Date[^0-9]*(../../....).*", // dateregexp - "%d/%m/%y" // dateformat - ); + "https://webfund6.financialexpress.net/Clients/Barclays/search_factsheet_summary.aspx?code=%1", + "", + "ISIN Code[^G]*(GB..........).*", // symbolregexp + "Current Market Information[^0-9]*([0-9,\\.]+).*", // priceregexp + "Price Date[^0-9]*(../../....).*", // dateregexp + "%d/%m/%y" // dateformat + ); // I suggest to include www.cash.ch as one of the pre-populated online @@ -518,6 +741,7 @@ result["Cash CH"] = WebPriceQuoteSource("Cash CH", "http://www.cash.ch/boerse/fonds/fondsguide/kursinfo/fullquote/%1", + "", "", // symbolregexp "([1-9][0-9]*\\.[0-9][0-9])", // priceregexp "([0-3][0-9]\\.[0-1][0-9]\\.[1-2][0-9][0-9][0-9])", // dateregexp @@ -566,6 +790,7 @@ groups += "Old Source"; grp = kconfig->group(QString("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("SymbolRegex", symbolRegExp); grp.writeEntry("PriceRegex", priceRegExp); grp.writeEntry("DateRegex", dateRegExp); @@ -612,9 +837,10 @@ // Helper class to load/save an individual source // -WebPriceQuoteSource::WebPriceQuoteSource(const QString& name, const QString& url, const QString& sym, const QString& price, const QString& date, const QString& dateformat, bool skipStripping): +WebPriceQuoteSource::WebPriceQuoteSource(const QString& name, const QString& url, const QString &csvUrl, const QString& sym, const QString& price, const QString& date, const QString& dateformat, bool skipStripping): m_name(name), m_url(url), + m_csvUrl(csvUrl), m_sym(sym), m_price(price), m_date(date), @@ -632,14 +858,16 @@ 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); diff --git a/kmymoney/dialogs/CMakeLists.txt b/kmymoney/dialogs/CMakeLists.txt --- a/kmymoney/dialogs/CMakeLists.txt +++ b/kmymoney/dialogs/CMakeLists.txt @@ -20,6 +20,7 @@ keditscheduledlg.cpp kenterscheduledlg.cpp kequitypriceupdatedlg.cpp + kequitypriceupdateconfdlg.cpp kexportdlg.cpp kfindtransactiondlg.cpp kgeneratesqldlg.cpp @@ -63,7 +64,7 @@ kcurrencycalculatordecl.ui kcurrencyeditdlg.ui kavailablecurrencydlg.ui kcurrencyeditordlg.ui keditscheduledlgdecl.ui kenterscheduledlgdecl.ui - kequitypriceupdatedlgdecl.ui kexportdlgdecl.ui + kequitypriceupdatedlgdecl.ui kequitypriceupdateconfdlg.ui kexportdlgdecl.ui kfindtransactiondlgdecl.ui kgeneratesqldlgdecl.ui kgncimportoptionsdlgdecl.ui kgncpricesourcedlgdecl.ui kimportdlgdecl.ui kloadtemplatedlgdecl.ui kmymoneyfileinfodlgdecl.ui kmymoneypricedlgdecl.ui diff --git a/kmymoney/dialogs/kequitypriceupdateconfdlg.h b/kmymoney/dialogs/kequitypriceupdateconfdlg.h new file mode 100644 --- /dev/null +++ b/kmymoney/dialogs/kequitypriceupdateconfdlg.h @@ -0,0 +1,53 @@ +/******************************************************************************* +* kequitypriceupdateconfdlg.cpp +* ------------------ +* begin : Sun May 21 2017 +* copyright : (C) 2017 by Łukasz Wojnilowicz +* email : lukasz.wojnilowicz@gmail.com +********************************************************************************/ + +/******************************************************************************* +* * +* 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 KEQUITYPRICEUPDATECONFDLG_H +#define KEQUITYPRICEUPDATECONFDLG_H + +#include "ui_kequitypriceupdateconfdlg.h" + +enum updatingPricePolicyE : int {eUpdateAllPrices = 0, eUpdateMissingPrices, eUpdateDownloadedPrices, eUpdateSameSourcePrices, eAsk, eUpdatingPricePolicyEnd}; + +namespace Ui +{ +class EquityPriceUpdateConfDlg; +} + +class EquityPriceUpdateConfDlg : public QDialog +{ + Q_OBJECT + +public: + explicit EquityPriceUpdateConfDlg(const updatingPricePolicyE policy); + ~EquityPriceUpdateConfDlg(); + + Ui::EquityPriceUpdateConfDlg* ui; + + updatingPricePolicyE policy(); +private: + void updatingPricePolicyChanged(const updatingPricePolicyE policy, bool toggled); + + updatingPricePolicyE m_updatingPricePolicy; +private slots: + void updateAllToggled(bool toggled); + void updateMissingToggled(bool toggled); + void updateDownloadedToggled(bool toggled); + void updateSameSourceToggled(bool toggled); + void askToggled(bool toggled); +}; + +#endif // KEQUITYPRICEUPDATECONFDLG_H diff --git a/kmymoney/dialogs/kequitypriceupdateconfdlg.cpp b/kmymoney/dialogs/kequitypriceupdateconfdlg.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/dialogs/kequitypriceupdateconfdlg.cpp @@ -0,0 +1,126 @@ +/******************************************************************************* +* kequitypriceupdateconfdlg.cpp +* ------------------ +* begin : Sun May 21 2017 +* copyright : (C) 2017 by Łukasz Wojnilowicz +* email : lukasz.wojnilowicz@gmail.com +********************************************************************************/ + +/******************************************************************************* +* * +* 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 "kequitypriceupdateconfdlg.h" + +EquityPriceUpdateConfDlg::EquityPriceUpdateConfDlg(const updatingPricePolicyE policy) : ui(new Ui::EquityPriceUpdateConfDlg) +{ + ui->setupUi(this); + switch(policy) { + case eUpdateAllPrices: + ui->m_updateAll->setChecked(true); + break; + case eUpdateMissingPrices: + ui->m_updateMissing->setChecked(true); + break; + case eUpdateDownloadedPrices: + ui->m_updateDownloaded->setChecked(true); + break; + case eUpdateSameSourcePrices: + ui->m_updateSource->setChecked(true); + break; + case eAsk: + ui->m_ask->setChecked(true); + break; + default: + break; + } + + m_updatingPricePolicy = policy; + connect(ui->m_updateAll, &QAbstractButton::toggled, this, &EquityPriceUpdateConfDlg::updateAllToggled); + connect(ui->m_updateMissing, &QAbstractButton::toggled, this, &EquityPriceUpdateConfDlg::updateMissingToggled); + connect(ui->m_updateDownloaded, &QAbstractButton::toggled, this, &EquityPriceUpdateConfDlg::updateDownloadedToggled); + connect(ui->m_updateSource, &QAbstractButton::toggled, this, &EquityPriceUpdateConfDlg::updateSameSourceToggled); + connect(ui->m_ask, &QAbstractButton::toggled, this, &EquityPriceUpdateConfDlg::askToggled); +} + +EquityPriceUpdateConfDlg::~EquityPriceUpdateConfDlg() +{ + delete ui; +} + +void EquityPriceUpdateConfDlg::updateAllToggled(bool toggled) +{ + updatingPricePolicyChanged(eUpdateAllPrices, toggled); +} + +void EquityPriceUpdateConfDlg::updateMissingToggled(bool toggled) +{ + updatingPricePolicyChanged(eUpdateMissingPrices, toggled); +} + +void EquityPriceUpdateConfDlg::updateDownloadedToggled(bool toggled) +{ + updatingPricePolicyChanged(eUpdateDownloadedPrices, toggled); +} + +void EquityPriceUpdateConfDlg::updateSameSourceToggled(bool toggled) +{ + updatingPricePolicyChanged(eUpdateSameSourcePrices, toggled); +} + +void EquityPriceUpdateConfDlg::askToggled(bool toggled) +{ + updatingPricePolicyChanged(eAsk, toggled); +} + +updatingPricePolicyE EquityPriceUpdateConfDlg::policy() +{ + return m_updatingPricePolicy; +} + +void EquityPriceUpdateConfDlg::updatingPricePolicyChanged(const updatingPricePolicyE policy, bool toggled) +{ + if (!toggled) + return; + + switch(policy) { + case eUpdateAllPrices: + ui->m_updateMissing->setChecked(false); + ui->m_updateDownloaded->setChecked(false); + ui->m_updateSource->setChecked(false); + ui->m_ask->setChecked(false); + break; + case eUpdateMissingPrices: + ui->m_updateAll->setChecked(false); + ui->m_updateDownloaded->setChecked(false); + ui->m_updateSource->setChecked(false); + ui->m_ask->setChecked(false); + break; + case eUpdateDownloadedPrices: + ui->m_updateAll->setChecked(false); + ui->m_updateMissing->setChecked(false); + ui->m_updateSource->setChecked(false); + ui->m_ask->setChecked(false); + break; + case eUpdateSameSourcePrices: + ui->m_updateAll->setChecked(false); + ui->m_updateMissing->setChecked(false); + ui->m_updateDownloaded->setChecked(false); + ui->m_ask->setChecked(false); + break; + case eAsk: + ui->m_updateAll->setChecked(false); + ui->m_updateDownloaded->setChecked(false); + ui->m_updateSource->setChecked(false); + ui->m_updateMissing->setChecked(false); + break; + default: + break; + } + m_updatingPricePolicy = policy; +} diff --git a/kmymoney/dialogs/kequitypriceupdateconfdlg.ui b/kmymoney/dialogs/kequitypriceupdateconfdlg.ui new file mode 100644 --- /dev/null +++ b/kmymoney/dialogs/kequitypriceupdateconfdlg.ui @@ -0,0 +1,132 @@ + + + EquityPriceUpdateConfDlg + + + + 0 + 0 + 238 + 228 + + + + Updating price configuration + + + + + + Updating price policy + + + + + + Update all + + + + + + + Update &missing + + + + + + + Update downloaded + + + + + + + Update from the same source + + + + + + + Ask + + + + + + + + + + + 0 + 0 + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + true + + + + + + + + + + buttonBox + accepted() + EquityPriceUpdateConfDlg + accept() + + + 302 + 276 + + + 302 + 152 + + + + + buttonBox + rejected() + EquityPriceUpdateConfDlg + reject() + + + 302 + 276 + + + 302 + 152 + + + + + + + 2 + + + 2 + + + true + + + true + + + true + + + diff --git a/kmymoney/dialogs/kequitypriceupdatedlg.h b/kmymoney/dialogs/kequitypriceupdatedlg.h --- a/kmymoney/dialogs/kequitypriceupdatedlg.h +++ b/kmymoney/dialogs/kequitypriceupdatedlg.h @@ -39,6 +39,7 @@ #include "webpricequote.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" +#include "kequitypriceupdateconfdlg.h" #include "ui_kequitypriceupdatedlgdecl.h" /** @@ -62,12 +63,15 @@ MyMoneyPrice price(const QString& id) const; protected slots: + void slotConfigureClicked(); void slotUpdateSelectedClicked(); void slotUpdateAllClicked(); void slotUpdateSelection(); + void slotDateChanged(); void logStatusMessage(const QString&); void logErrorMessage(const QString&); + void slotReceivedCSVQuote(const QString& _id, const QString& _symbol, MyMoneyStatement& st); void slotReceivedQuote(const QString&, const QString&, const QDate&, const double&); void slotQuoteFailed(const QString& _id, const QString& _symbol); @@ -78,6 +82,7 @@ private: bool m_fUpdateAll; + updatingPricePolicyE m_updatingPricePolicy; WebPriceQuote m_webQuote; }; diff --git a/kmymoney/dialogs/kequitypriceupdatedlg.cpp b/kmymoney/dialogs/kequitypriceupdatedlg.cpp --- a/kmymoney/dialogs/kequitypriceupdatedlg.cpp +++ b/kmymoney/dialogs/kequitypriceupdatedlg.cpp @@ -10,6 +10,7 @@ Thomas Baumgart Kevin Tambascio Ace Jones + 2017 Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** @@ -49,6 +50,7 @@ // ---------------------------------------------------------------------------- // Project Includes +#include "kmymoney.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" @@ -135,6 +137,11 @@ connect(btnUpdateSelected, SIGNAL(clicked()), this, SLOT(slotUpdateSelectedClicked())); connect(btnUpdateAll, SIGNAL(clicked()), this, SLOT(slotUpdateAllClicked())); + connect(m_fromDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotDateChanged())); + connect(m_toDate, SIGNAL(dateChanged(QDate)), this, SLOT(slotDateChanged())); + + connect(&m_webQuote, &WebPriceQuote::csvquote, + this, &KEquityPriceUpdateDlg::slotReceivedCSVQuote); connect(&m_webQuote, SIGNAL(quote(QString,QString,QDate,double)), this, SLOT(slotReceivedQuote(QString,QString,QDate,double))); connect(&m_webQuote, SIGNAL(failed(QString,QString)), @@ -146,9 +153,7 @@ connect(lvEquityList, SIGNAL(itemSelectionChanged()), this, SLOT(slotUpdateSelection())); - // Not implemented yet. - btnConfigure->hide(); - //connect(btnConfigure, SIGNAL(clicked()), this, SLOT(slotConfigureClicked())); + connect(btnConfigure, &QAbstractButton::clicked, this, &KEquityPriceUpdateDlg::slotConfigureClicked); if (!securityId.isEmpty()) { btnUpdateSelected->hide(); @@ -168,11 +173,21 @@ 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", QString().setNum(eUpdateMissingPrices)).toInt(); + if (policyValue >= eUpdatingPricePolicyEnd || policyValue < eUpdateAllPrices) + m_updatingPricePolicy = eUpdateMissingPrices; + else + m_updatingPricePolicy = static_cast(policyValue); } KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg() { - + KSharedConfigPtr config = KSharedConfig::openConfig(); + KConfigGroup grp = config->group("Equity Price Update"); + grp.writeEntry("PriceUpdatingPolicy", static_cast(m_updatingPricePolicy)); + grp.sync(); } void KEquityPriceUpdateDlg::addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance) @@ -369,6 +384,14 @@ } } +void KEquityPriceUpdateDlg::slotConfigureClicked() +{ + EquityPriceUpdateConfDlg *dlg = new EquityPriceUpdateConfDlg(m_updatingPricePolicy); + if (dlg->exec() == QDialog::Accepted) + m_updatingPricePolicy = dlg->policy(); + delete dlg; +} + void KEquityPriceUpdateDlg::slotUpdateSelection() { // Only enable the update button if there is a selection @@ -389,11 +412,12 @@ item = lvEquityList->invisibleRootItem()->child(skipCnt); ++skipCnt; } - + m_webQuote.setDate(m_fromDate->date(), m_toDate->date()); if (item) { prgOnlineProgress->setMaximum(1 + lvEquityList->invisibleRootItem()->childCount()); prgOnlineProgress->setValue(skipCnt); m_webQuote.launch(item->text(SYMBOL_COL), item->text(ID_COL), item->text(SOURCE_COL)); + } else { logErrorMessage("No security selected."); @@ -417,6 +441,18 @@ } } +void KEquityPriceUpdateDlg::slotDateChanged() +{ + m_fromDate->blockSignals(true); + m_toDate->blockSignals(true); + if (m_toDate->date() > QDate::currentDate()) + m_toDate->setDate(QDate::currentDate()); + if (m_toDate->date() < m_fromDate->date()) + m_fromDate->setDate(m_toDate->date()); + m_fromDate->blockSignals(false); + m_toDate->blockSignals(false); +} + void KEquityPriceUpdateDlg::slotQuoteFailed(const QString& _id, const QString& _symbol) { QList foundItems = lvEquityList->findItems(_id, Qt::MatchExactly, ID_COL); @@ -478,6 +514,139 @@ } } +void KEquityPriceUpdateDlg::slotReceivedCSVQuote(const QString& _id, const QString& _symbol, MyMoneyStatement& st) +{ + QList foundItems = lvEquityList->findItems(_id, Qt::MatchExactly, ID_COL); + QTreeWidgetItem* item = 0; + + if (! foundItems.empty()) + item = foundItems.at(0); + + QTreeWidgetItem* next = 0; + + if (item) { + MyMoneyFile *file = MyMoneyFile::instance(); + MyMoneySecurity fromCurrency, toCurrency; + + if (!_id.contains(QLatin1Char(' '))) { + 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(_id) != -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 (m_updatingPricePolicy != eUpdateAllPrices) { + QStringList qSources = WebPriceQuote::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(m_updatingPricePolicy) { + case eUpdateMissingPrices: + it = st.m_listPrices.erase(it); + break; + case eUpdateDownloadedPrices: + if (!qSources.contains(storedPrice.source())) + it = st.m_listPrices.erase(it); + else + ++it; + break; + case eUpdateSameSourcePrices: + if (storedPrice.source().compare((*it).m_sourceName) != 0) + it = st.m_listPrices.erase(it); + else + ++it; + break; + case eAsk: + { + int 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 KStandardGuiItem::Yes: + ++it; + break; + case KStandardGuiItem::No: + it = st.m_listPrices.erase(it); + break; + default: + case KStandardGuiItem::Cancel: + finishUpdate(); + return; + break; + } + break; + } + default: + ++it; + break; + } + } + } + } + + if (!st.m_listPrices.isEmpty()) { + kmymoney->slotStatementImport(st, true); + + 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(); + + QDate 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)", _symbol, _id)); + // make sure to make OK button available + } + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + + prgOnlineProgress->setValue(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 = lvEquityList->invisibleRootItem()->child(lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); + if (!m_fUpdateAll) { + while (next && !next->isSelected()) { + prgOnlineProgress->setValue(prgOnlineProgress->value() + 1); + next = lvEquityList->invisibleRootItem()->child(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.", _symbol, _id)); + } + + if (next) { + m_webQuote.launch(next->text(SYMBOL_COL), next->text(ID_COL), next->text(SOURCE_COL)); + } else { + finishUpdate(); + } +} + void KEquityPriceUpdateDlg::slotReceivedQuote(const QString& _id, const QString& _symbol, const QDate& _date, const double& _price) { QList foundItems = lvEquityList->findItems(_id, Qt::MatchExactly, ID_COL); diff --git a/kmymoney/dialogs/kequitypriceupdatedlgdecl.ui b/kmymoney/dialogs/kequitypriceupdatedlgdecl.ui --- a/kmymoney/dialogs/kequitypriceupdatedlgdecl.ui +++ b/kmymoney/dialogs/kequitypriceupdatedlgdecl.ui @@ -14,7 +14,7 @@ Update Stock and Currency Prices - + @@ -52,6 +52,30 @@ + + + + + From + + + + + + + + + + To + + + + + + + + + @@ -128,6 +152,11 @@ QTextEdit
ktextedit.h
+ + kMyMoneyDateInput + QWidget +
kmymoneydateinput.h
+
diff --git a/kmymoney/dialogs/kmymoneypricedlg.cpp b/kmymoney/dialogs/kmymoneypricedlg.cpp --- a/kmymoney/dialogs/kmymoneypricedlg.cpp +++ b/kmymoney/dialogs/kmymoneypricedlg.cpp @@ -308,31 +308,10 @@ void KMyMoneyPriceDlg::slotOnlinePriceUpdate() { - QList itemList = m_priceList->selectedItems(); - QTreeWidgetItem* item; - if (itemList.count() > 0) { - item = itemList.at(0); - if (item) { - //check whether the price is a currency or not - MyMoneyPrice price = item->data(0, Qt::UserRole).value(); - MyMoneySecurity security = MyMoneyFile::instance()->security(price.from()); - - //if it is not a currency, send a null string, which will trigger the update for all prices - QString stringId; - if (security.isCurrency()) { - stringId = QString(item->text(KPriceTreeItem::ePriceCommodity) + ' ' + item->text(KPriceTreeItem::ePriceCurrency)).toUtf8(); - } - QPointer dlg = new KEquityPriceUpdateDlg(this, stringId); - if (dlg->exec() == Accepted) - dlg->storePrices(); - delete dlg; - } - } else { - QPointer dlg = new KEquityPriceUpdateDlg(this); - if (dlg->exec() == Accepted && dlg) - dlg->storePrices(); - delete dlg; - } + QPointer dlg = new KEquityPriceUpdateDlg(this); + if (dlg->exec() == Accepted && dlg) + dlg->storePrices(); + delete dlg; } void KMyMoneyPriceDlg::slotOpenContextMenu(const QPoint& p) diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.h b/kmymoney/dialogs/settings/ksettingsonlinequotes.h --- a/kmymoney/dialogs/settings/ksettingsonlinequotes.h +++ b/kmymoney/dialogs/settings/ksettingsonlinequotes.h @@ -53,6 +53,7 @@ void resetConfig(); protected slots: + void slotDumpCSVProfile(); void slotUpdateEntry(); void slotLoadWidgets(); void slotEntryChanged(); diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp --- a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp +++ b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp @@ -30,6 +30,7 @@ #include #include #include +#include // ---------------------------------------------------------------------------- // Project Includes @@ -65,14 +66,16 @@ i18n("Use this to create a new entry for online quotes")); KGuiItem::assign(m_newButton, newButtenItem); + connect(m_dumpCSVProfile, SIGNAL(clicked()), this, SLOT(slotDumpCSVProfile())); connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateEntry())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotNewEntry())); connect(m_quoteSourceList, SIGNAL(itemSelectionChanged()), this, SLOT(slotLoadWidgets())); connect(m_quoteSourceList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotEntryRenamed(QListWidgetItem*))); connect(m_quoteSourceList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*))); connect(m_editURL, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged())); + connect(m_editCSVURL, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged())); connect(m_editSymbol, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged())); connect(m_editDate, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged())); connect(m_editDateFormat, SIGNAL(textChanged(QString)), this, SLOT(slotEntryChanged())); @@ -136,27 +139,31 @@ QListWidgetItem* item = m_quoteSourceList->currentItem(); m_editURL->setEnabled(true); + m_editCSVURL->setEnabled(true); m_editSymbol->setEnabled(true); m_editPrice->setEnabled(true); m_editDate->setEnabled(true); m_editDateFormat->setEnabled(true); m_skipStripping->setEnabled(true); m_editURL->setText(QString()); + m_editCSVURL->setText(QString()); m_editSymbol->setText(QString()); m_editPrice->setText(QString()); m_editDate->setText(QString()); m_editDateFormat->setText(QString()); if (item) { m_currentItem = WebPriceQuoteSource(item->text()); m_editURL->setText(m_currentItem.m_url); + m_editCSVURL->setText(m_currentItem.m_csvUrl); m_editSymbol->setText(m_currentItem.m_sym); m_editPrice->setText(m_currentItem.m_price); m_editDate->setText(m_currentItem.m_date); m_editDateFormat->setText(m_currentItem.m_dateformat); m_skipStripping->setChecked(m_currentItem.m_skipStripping); } else { m_editURL->setEnabled(false); + m_editCSVURL->setEnabled(false); m_editSymbol->setEnabled(false); m_editPrice->setEnabled(false); m_editDate->setEnabled(false); @@ -171,6 +178,7 @@ void KSettingsOnlineQuotes::slotEntryChanged() { bool modified = m_editURL->text() != m_currentItem.m_url + || m_editCSVURL->text() != m_currentItem.m_csvUrl || m_editSymbol->text() != m_currentItem.m_sym || m_editDate->text() != m_currentItem.m_date || m_editDateFormat->text() != m_currentItem.m_dateformat @@ -180,9 +188,45 @@ m_updateButton->setEnabled(modified); } +void KSettingsOnlineQuotes::slotDumpCSVProfile() +{ + KSharedConfigPtr config = CSVImporter::configFile(); + PricesProfile profile; + profile.m_profileName = m_currentItem.m_name; + profile.m_profileType = ProfileStockPrices; + bool profileExists = false; + bool writeProfile = true; + + if (profile.readSettings(config)) + profileExists = true; + else { + profile.m_profileType = ProfileCurrencyPrices; + if (profile.readSettings(config)) + profileExists = true; + } + + if (profileExists) + writeProfile = (KMessageBox::questionYesNoCancel(this, + i18n("CSV profile %1 already exists.
" + "Do you want to overwrite it?", + m_currentItem.m_name), + i18n("CSV Profile Already Exists")) == KMessageBox::Yes ? true : false); + + if (writeProfile) { + QMap quoteSources = WebPriceQuote::defaultCSVQuoteSources(); + profile = quoteSources.value(m_currentItem.m_name); + if (profile.m_profileName.compare(m_currentItem.m_name, Qt::CaseInsensitive) == 0) { + profile.writeSettings(config); + CSVImporter::profilesAction(profile.type(), ProfilesAdd, profile.m_profileName, profile.m_profileName); + } + } + CSVImporter::profilesAction(profile.type(), ProfilesUpdateLastUsed, profile.m_profileName, profile.m_profileName); +} + void KSettingsOnlineQuotes::slotUpdateEntry() { m_currentItem.m_url = m_editURL->text(); + m_currentItem.m_csvUrl = m_editCSVURL->text(); m_currentItem.m_sym = m_editSymbol->text(); m_currentItem.m_date = m_editDate->text(); m_currentItem.m_dateformat = m_editDateFormat->text(); diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotesdecl.ui b/kmymoney/dialogs/settings/ksettingsonlinequotesdecl.ui --- a/kmymoney/dialogs/settings/ksettingsonlinequotesdecl.ui +++ b/kmymoney/dialogs/settings/ksettingsonlinequotesdecl.ui @@ -1,155 +1,126 @@ - - - - - KSettingsOnlineQuotesDecl - - - - 0 - 0 - 512 - 442 - - - - - 0 - 0 - - - - Online Quotes - - - - - - - 7 - 3 - 0 - 0 - - - - - Name - - - true - - - true - - - + + KSettingsOnlineQuotesDecl + + + + 0 + 0 + 512 + 554 + + + + + 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 + + + + + + Regular Expression to extract the date from the downloaded data + + - - - - Details - - - <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> - - - - - - Date - - - false - - - - - - - Symbol - - - false - - - - - - - Regular Expression to extract the symbol from the downloaded data - - - - - - - Regular Expression to extract the price from the downloaded data - - - - - - - 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. - - - - - - - Price - - - false - - - - - - - Regular Expression to extract the date from the downloaded data - - - - - - - Date Format - - - false - - - - - - - URL - - - false - - - - - - - Regular Expression to extract the date from the downloaded data - - - - - - - Skip HTML stripping - - - <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> + + + + Date + + + false + + + + + + + Price + + + false + + + + + + + CSV URL + + + + + + + Symbol + + + false + + + + + + + Regular Expression to extract the date from the downloaded data + + + + + + + Date Format + + + false + + + + + + + Regular Expression to extract the price from the downloaded data + + + + + + + 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. + + + + + + + <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: @@ -159,61 +130,96 @@ <li>duplicate whitespace</li> </ul> </p> - - - - - + + + Skip HTML stripping + + + + + + + Regular Expression to extract the symbol from the downloaded data + + + + + + + URL + + + false + + - - - - - - New - - - - - - - Delete - - - - - - - - 240 - 20 - - - - QSizePolicy::Expanding - - - Qt::Horizontal - - - - - - - Update - - - - + + + + + + + + + + + New + + + + + + + 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 + + + + + + + Update + + + - - - - - QListWidget - QListWidget -
klistwidget.h
-
-
+
+
+ + + + + KLineEdit + QLineEdit +
klineedit.h
+
+
+ +