diff --git a/CMakeLists.txt b/CMakeLists.txt index ce51c778b..a6bfdd290 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,326 +1,329 @@ # The CMake version we require cmake_minimum_required(VERSION 3.1) # Setting the name of the main project project(KMyMoney VERSION "4.100.0") # Determine the GIT reference (if we're based on GIT) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") execute_process(COMMAND git rev-parse --short HEAD WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE VERSION_SUFFIX OUTPUT_STRIP_TRAILING_WHITESPACE) set(VERSION_SUFFIX "-${VERSION_SUFFIX}") # Add variables which are similar to the build in names of cmake set(PROJECT_VERSION_SUFFIX "${VERSION_SUFFIX}") set(${PROJECT_NAME}_VERSION_SUFFIX "${VERSION_SUFFIX}") endif() # Automoc all sources set(CMAKE_AUTOMOC TRUE) if (POLICY CMP0063) cmake_policy(SET CMP0063 NEW) # Policy introduced in CMake version 3.3 endif() ######################### General Requirements ########################## find_package(ECM 0.0.11 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(KDEInstallDirs) include(KDECMakeSettings) include(FeatureSummary) include(GenerateExportHeader) include(KMyMoneyMacros) set(GPG_ENCRYPTION "no") set (OPT_KF5_COMPONENTS DocTools Holidays Contacts Akonadi IdentityManagement Activities Kross) find_package(Gpgmepp) if (Gpgmepp_FOUND) set(GPG_ENCRYPTION "yes") else() set(OPT_KF5_COMPONENTS ${OPT_KF5_COMPONENTS} Gpgmepp) endif() find_package(Qt5 5.6 REQUIRED COMPONENTS Core DBus Widgets Svg Sql Xml Test PrintSupport) find_package(KF5 5.2 REQUIRED COMPONENTS Archive CoreAddons Config ConfigWidgets I18n Completion KCMUtils ItemModels ItemViews Service Wallet IconThemes XmlGui TextWidgets Notifications KIO OPTIONAL_COMPONENTS ${OPT_KF5_COMPONENTS} ) find_package(LibAlkimia 6.0.0 REQUIRED) find_package(KChart 2.6.0 REQUIRED) if(KF5Gpgmepp_FOUND) set(GPG_ENCRYPTION "yes") add_definitions(-DGpgmepp_FOUND) endif() add_definitions(-DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING) if(NOT MSVC) # MSVC has no regular standards version switches set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) endif() # use DBus only on Linux if(CMAKE_SYSTEM_NAME MATCHES "Linux") set(KMM_DBUS 1) endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) # check for Doxygen find_package(Doxygen) if(DOXYGEN_FOUND) set(APIDOC_DIR ${CMAKE_CURRENT_BINARY_DIR}/apidocs) make_directory("${APIDOC_DIR}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kmymoney.doxygen.in ${CMAKE_CURRENT_BINARY_DIR}/kmymoney.doxygen IMMEDIATE) add_custom_target(apidoc "${DOXYGEN}" "${CMAKE_CURRENT_BINARY_DIR}/kmymoney.doxygen") endif(DOXYGEN_FOUND) # check some include files exists set(CMAKE_REQUIRED_DEFINITIONS -D_XOPEN_SOURCE=500 -D_BSD_SOURCE) include (CheckIncludeFile) check_include_file("sys/types.h" HAVE_SYS_TYPES_H) check_include_file("unistd.h" HAVE_UNISTD_H) # include check for members in structs include (CheckStructHasMember) ######################### Special Requirements ########################## # This is needed for QtSqlite and QtDesigner # (they'll install files to ${QT_INSTALL_DIR}/plugins/) get_filename_component(QT_BIN_DIR "${QT_MOC_EXECUTABLE}" PATH) get_filename_component(QT_DIR ${QT_BIN_DIR} PATH) set(QT_INSTALL_DIR ${QT_DIR} CACHE PATH "Qt install prefix defaults to the Qt prefix: ${QT_DIR}") if(KF5IdentityManagement_FOUND AND KF5Akonadi_FOUND AND KF5Contacts_FOUND) set(KMM_ADDRESSBOOK_FOUND true) endif() # check for optional QWebEngine option(ENABLE_WEBENGINE "Enable QWebEngine" OFF) if(ENABLE_WEBENGINE) find_package(Qt5WebEngineWidgets 5.8 REQUIRED) if(Qt5WebEngineWidgets_VERSION VERSION_GREATER 5.8.99 AND Qt5WebEngineWidgets_VERSION VERSION_LESS 5.9.2) message(WARNING "QWebEngine version ${Qt5WebEngineWidgets_VERSION} is known to be unstable with KMyMoney") endif() else(ENABLE_WEBENGINE) find_package(KF5WebKit REQUIRED) endif(ENABLE_WEBENGINE) # check for optional OFX support set(LIBOFX_DEFAULT "AUTO") if(DEFINED ENABLE_LIBOFX) set(LIBOFX_DEFAULT ${ENABLE_LIBOFX}) endif(DEFINED ENABLE_LIBOFX) option(ENABLE_LIBOFX "Enable OFX plugin" ON) if(ENABLE_LIBOFX) find_package(LibOfx) if(NOT LIBOFX_FOUND) if(NOT LIBOFX_DEFAULT STREQUAL "AUTO") message(FATAL_ERROR "LibOFX not found") endif(NOT LIBOFX_DEFAULT STREQUAL "AUTO") set(ENABLE_LIBOFX OFF CACHE BOOL "Enable OFX plugin" FORCE) else(NOT LIBOFX_FOUND) check_struct_has_member("struct OfxFiLogin" "clientuid" "libofx/libofx.h" LIBOFX_HAVE_CLIENTUID) endif(NOT LIBOFX_FOUND) endif(ENABLE_LIBOFX) # check for optional KBanking support set(KBANKING_FOUND "AUTO") mark_as_advanced(KBANKING_FOUND) if(DEFINED ENABLE_KBANKING) set(KBANKING_FOUND OFF) endif(DEFINED ENABLE_KBANKING) option(ENABLE_KBANKING "Enable KBanking plugin" ON) if(ENABLE_KBANKING) find_package(Qt5QuickWidgets) # Includes Qt5Qml find_package(AQBANKING 5.6.5) find_package(gwenhywfar 4.15.3) find_package(gwengui-cpp) find_package(gwengui-qt5) if (AQBANKING_FOUND AND gwengui-cpp_FOUND AND gwengui-qt5_FOUND AND Qt5QuickWidgets_FOUND) set(KBANKING_FOUND ON) else() if(NOT KBANKING_FOUND STREQUAL "AUTO") message(FATAL_ERROR "KBanking requirements not met") endif(NOT KBANKING_FOUND STREQUAL "AUTO") set(KBANKING_FOUND OFF) set(ENABLE_KBANKING OFF CACHE BOOL "Enable KBanking plugin" FORCE) endif () endif(ENABLE_KBANKING) # check for optional Weboob support set(WEBOOB_FOUND "AUTO") mark_as_advanced(WEBOOB_FOUND) if(DEFINED ENABLE_WEBOOB) set(WEBOOB_FOUND OFF) endif(DEFINED ENABLE_WEBOOB) option(ENABLE_WEBOOB "Enable weboob plugin" ON) if(ENABLE_WEBOOB) if(KF5Kross_FOUND) set(WEBOOB_FOUND ON) else(KF5Kross_FOUND) if(NOT WEBOOB_FOUND STREQUAL "AUTO") message(FATAL_ERROR "Weboob requirements not met") endif(NOT WEBOOB_FOUND STREQUAL "AUTO") set(WEBOOB_FOUND OFF) set(ENABLE_WEBOOB OFF CACHE BOOL "Enable weboob plugin" FORCE) endif(KF5Kross_FOUND) endif(ENABLE_WEBOOB) # check for optional ical support set(LIBICAL_DEFAULT "AUTO") if(DEFINED ENABLE_LIBICAL) set(LIBICAL_DEFAULT ${ENABLE_LIBICAL}) endif(DEFINED ENABLE_LIBICAL) option(ENABLE_LIBICAL "Enable Calendar plugin" ON) if(ENABLE_LIBICAL) find_package(Libical) if(NOT LIBICAL_FOUND) if(NOT LIBICAL_DEFAULT STREQUAL "AUTO") message(FATAL_ERROR "LIBICAL not found") endif(NOT LIBICAL_DEFAULT STREQUAL "AUTO") set(ENABLE_LIBICAL OFF CACHE BOOL "Enable Calendar plugin" FORCE) endif(NOT LIBICAL_FOUND) endif(ENABLE_LIBICAL) +option(ENABLE_QIFIMPORTER "Enable QIF Importer" ON) +option(ENABLE_QIFEXPORTER "Enable QIF Exporter" ON) + # TODO: this should be removed enable_testing() ######################### Settings ########################## # If no build type is set, use "Release with Debug Info" if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build. Possible values are: 'Release' 'RelWithDebInfo' 'Debug' 'Debugfull' 'Profile' The default value is: 'RelWithDebInfo'" FORCE) # tells gcc to enable exception handling include(KDECompilerSettings) kde_enable_exceptions() if(CMAKE_SYSTEM_NAME MATCHES "Linux") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") endif() # IDEA: Set on a per target base set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(CMAKE_COMPILER_IS_GNUCXX) # be more pedantic about common symbols set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Wlogical-op") # Debug, Debugfull, Profile set(CMAKE_CXX_FLAGS_DEBUG "-g -O2 -fno-reorder-blocks -fno-schedule-insns -fno-inline") set(CMAKE_CXX_FLAGS_DEBUGFULL "-g3 -fno-inline") set(CMAKE_CXX_FLAGS_PROFILE "-g3 -fno-inline -ftest-coverage -fprofile-arcs") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op -Wextra") # be pedantic about undefined symbols when linking shared libraries if(CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "kFreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "GNU") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--as-needed") endif() endif() # preprocessor definitions in case this is a debug build set(CMAKE_CXX_FLAGS_DEBUGFULL "${CMAKE_CXX_FLAGS_DEBUGFULL} -DQT_STRICT_ITERATORS -DKMM_DEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUGFULL} -DKMM_DEBUG") option(USE_MODELTEST "Compile with ModelTest code (default=OFF)" OFF) option(USE_QT_DESIGNER "Install KMyMoney specific widget library for Qt-Designer (default=OFF)" OFF) ######################### The Actual Targets ########################## add_subdirectory( libkgpgfile ) add_subdirectory( tools ) add_subdirectory( kmymoney ) if(KF5DocTools_FOUND) add_subdirectory( doc ) endif() ######################### Output Results ############################# # create the config.h file out of the config.h.cmake configure_file("config-kmymoney.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config-kmymoney.h") configure_file("config-kmymoney-version.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config-kmymoney-version.h") # this macro maps the boolean variable ${_varname} to "yes"/"no" # and writes the output to the variable nice_${_varname} macro(nice_yesno _varname) if(${_varname}) set("nice_${_varname}" "yes") else(${_varname}) set("nice_${_varname}" "no") endif(${_varname}) endmacro() nice_yesno("KF5Holidays_FOUND") nice_yesno("Gpgmepp_FOUND") nice_yesno("KMM_ADDRESSBOOK_FOUND") nice_yesno("LIBOFX_FOUND") nice_yesno("LIBOFX_HAVE_CLIENTUID") nice_yesno("KBANKING_FOUND") nice_yesno("WEBOOB_FOUND") nice_yesno("LIBICAL_FOUND") nice_yesno("ENABLE_SQLCIPHER") nice_yesno("USE_QT_DESIGNER") nice_yesno("USE_MODELTEST") nice_yesno("DOXYGEN_FOUND") nice_yesno("ENABLE_WEBENGINE") message(" -------- KMyMoney ${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX} -------- Configure results (user options): -------------------------------------------- GpgME Encryption: ${GPG_ENCRYPTION} KDE PIM holidays: ${nice_KF5Holidays_FOUND} KDE PIM addressbook: ${nice_KMM_ADDRESSBOOK_FOUND} OFX plugin: ${nice_LIBOFX_FOUND} OFX supports CLIENTUID: ${nice_LIBOFX_HAVE_CLIENTUID} KBanking plugin: ${nice_KBANKING_FOUND} weboob plugin: ${nice_WEBOOB_FOUND} iCalendar export plugin: ${nice_LIBICAL_FOUND} SQLCipher plugin: ${nice_ENABLE_SQLCIPHER} QWebEngine: ${nice_ENABLE_WEBENGINE} -------------------------------------------- Configure results (developer options): -------------------------------------------- Qt-Designer library support: ${nice_USE_QT_DESIGNER} Generate modeltest code: ${nice_USE_MODELTEST} Generate API documentation with Doxygen: ${nice_DOXYGEN_FOUND}") message(" Build type: ${CMAKE_BUILD_TYPE}") diff --git a/kmymoney/converter/CMakeLists.txt b/kmymoney/converter/CMakeLists.txt index c0da320b4..cc4203b75 100644 --- a/kmymoney/converter/CMakeLists.txt +++ b/kmymoney/converter/CMakeLists.txt @@ -1,28 +1,25 @@ add_subdirectory( tests ) set (libconverter_a_SOURCES mymoneygncreader.cpp - mymoneyqifprofile.cpp - mymoneyqifreader.cpp - mymoneyqifwriter.cpp mymoneystatementreader.cpp mymoneytemplate.cpp webpricequote.cpp transactionmatchfinder.cpp existingtransactionmatchfinder.cpp scheduledtransactionmatchfinder.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 KF5::I18n Qt5::Widgets Qt5::Gui Qt5::Sql Qt5::Xml Alkimia::alkimia KF5::KIOWidgets kmm_csvimport_core) # 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/mymoneystatementreader.h b/kmymoney/converter/mymoneystatementreader.h index 6c1c6a997..5f563ad7c 100644 --- a/kmymoney/converter/mymoneystatementreader.h +++ b/kmymoney/converter/mymoneystatementreader.h @@ -1,165 +1,164 @@ /*************************************************************************** mymoneystatementreader ------------------- begin : Mon Aug 30 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTATEMENTREADER_H #define MYMONEYSTATEMENTREADER_H #include #include #include -#include "mymoneyqifprofile.h" #include "mymoneyaccount.h" #include "mymoneystatement.h" #include "transactionmatcher.h" #include "transactionmatchfinder.h" class MyMoneyFileTransaction; class QStringList; /** * This is a pared-down version of a MyMoneyQifReader object * * @author Ace Jones */ class MyMoneyStatementReader : public QObject { Q_OBJECT public: MyMoneyStatementReader(); ~MyMoneyStatementReader(); /** * This method imports data from the MyMoneyStatement object @a s * into the MyMoney engine. It leaves some statistical information * in the @a messages string list * * @retval true the import was processed successfully * @retval false the import resulted in a failure. */ bool import(const MyMoneyStatement& s, QStringList& messages); /** * This method is used to modify the auto payee creation flag. * If this flag is set, records for payees that are not currently * found in the engine will be automatically created with no * further user interaction required. If this flag is no set, * the user will be asked if the payee should be created or not. * If the MyMoneyQifReader object is created auto payee creation * is turned off. * * @param create flag if this feature should be turned on (@p true) * or turned off (@p false) */ void setAutoCreatePayee(bool create); void setAskPayeeCategory(bool ask); const MyMoneyAccount& account() const { return m_account; }; void setProgressCallback(void(*callback)(int, int, const QString&)); /** * Returns true in case any transaction has been added to the engine * during the import of the statement. Only returns useful result * after import() has been called. */ bool anyTransactionAdded() const; private: /** * This method is used to update the progress information. It * checks if an appropriate function is known and calls it. * * For a parameter description see KMyMoneyView::progressCallback(). */ void signalProgress(int current, int total, const QString& = ""); void processTransactionEntry(const MyMoneyStatement::Transaction& t_in); void processSecurityEntry(const MyMoneyStatement::Security& s_in); void processPriceEntry(const MyMoneyStatement::Price& p_in); enum SelectCreateMode { Create = 0, Select }; /** * This method is used to select or create brokerage account if * it isn't specified in statement transaction. */ QString SelectBrokerageAccount(); /** * This method is used to find an account using the account's name * stored in @p account in the current MyMoneyFile object. If it does not * exist, the user has the chance to create it or to skip processing * of this account. * * Please see the documentation for this function in MyMoneyQifReader * * @param mode Is either Create or Select depending on the above table * @param account Reference to MyMoneyAccount object */ bool selectOrCreateAccount(const SelectCreateMode mode, MyMoneyAccount& account); private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; MyMoneyAccount m_account; MyMoneyAccount m_brokerageAccount; QStringList m_dontAskAgain; bool m_userAbort; bool m_autoCreatePayee; bool m_askPayeeCategory; MyMoneyFileTransaction* m_ft; void (*m_progressCallback)(int, int, const QString&); void handleMatchingOfExistingTransaction(TransactionMatcher & matcher, MyMoneyTransaction matchedTransaction, MyMoneySplit matchedSplit, MyMoneyTransaction & importedTransaction, const MyMoneySplit & importedSplit, const TransactionMatchFinder::MatchResult & matchResult); void handleMatchingOfScheduledTransaction(TransactionMatcher & matcher, MyMoneySchedule matchedSchedule, MyMoneySplit matchedSplit, const MyMoneyTransaction & importedTransaction, const MyMoneySplit & importedSplit); void addTransaction(MyMoneyTransaction & transaction); /** Asks the user whether to enter a schedule transaction to match it with imported one * @param matchedSchedule the schedule which matches the imported transaction * @param importedSplit the split of the imported transaction which matches the split of the schedule * @return true, if user confirmed to enter the schedule to match it with imported transaction; false otherwise */ bool askUserToEnterScheduleForMatching(const MyMoneySchedule& matchedSchedule, const MyMoneySplit& importedSplit, const MyMoneyTransaction & importedTransaction) const; }; #endif diff --git a/kmymoney/converter/webpricequote.cpp b/kmymoney/converter/webpricequote.cpp index c2f3bf2f6..cfba74174 100644 --- a/kmymoney/converter/webpricequote.cpp +++ b/kmymoney/converter/webpricequote.cpp @@ -1,1285 +1,1284 @@ /*************************************************************************** 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 #include #include #include // ---------------------------------------------------------------------------- // Project Headers #include "mymoneyexception.h" -#include "mymoneyqifprofile.h" #include "mymoneyfile.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; 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)); 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(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_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; CSVImporter* csvImporter = new CSVImporter; st = csvImporter->unattendedPricesImport(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; bool gotprice = false; bool gotdate = false; qCDebug(WEBPRICEQUOTE) << "quotedata" << _quotedata; if (! quotedata.isEmpty()) { if (!d->m_source.m_skipStripping) { // First, remove extranous 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))); } if (quotedata.indexOf(priceRegExp, 0, &match) > -1) { gotprice = true; // 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. QString pricestr = match.captured(1); int 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); } 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; 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", "\"([^,\"]*)\",.*", // webIDRegExp WebPriceQuoteSource::identifyBy::Symbol, "[^,]*,([^,]*),.*", // 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", "", "\"([^,\"]*)\",.*", // webIDRegExp WebPriceQuoteSource::identifyBy::Symbol, "[^,]*,([^,]*),.*", // 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", "", "^([^,]*),.*", // webIDRegExp WebPriceQuoteSource::identifyBy::Symbol, "^[^,]*,([^,]*),.*", // 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", "", "([^;]*).*", // webIDRegExp WebPriceQuoteSource::identifyBy::Symbol, "[^;]*.([^;]*),*", // priceregexp "[^;]*.[^;]*...h...([^;]*)", // dateregexp "%d/%m/%y" // dateformat ); // This quote source provided by Danny Scott result["Yahoo Canada"] = WebPriceQuoteSource("Yahoo Canada", "http://ca.finance.yahoo.com/q?s=%1", "", "%1", // webIDRegExp WebPriceQuoteSource::identifyBy::Symbol, "Last Trade: (\\d+\\.\\d+)", // price regexp "day, (.\\D+\\d+\\D+\\d+)", // date regexp "%m %d %y" // date format ); // 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 compatable 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% // compatable, 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("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("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("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("Invalid month entry: %1").arg(*it_scanned)); break; case 'y': if (_strict && (*it_scanned).length() != (*it_format).length()) throw MYMONEYEXCEPTION(QString("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("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("Invalid year (%1)").arg(year)); break; default: throw MYMONEYEXCEPTION("Invalid format character"); } ++it_scanned; ++it_format; } QDate result(year, month, day); if (! result.isValid()) throw MYMONEYEXCEPTION(QString("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/dialogs/CMakeLists.txt b/kmymoney/dialogs/CMakeLists.txt index 7a2f18208..ddeb7d0fc 100644 --- a/kmymoney/dialogs/CMakeLists.txt +++ b/kmymoney/dialogs/CMakeLists.txt @@ -1,112 +1,109 @@ add_subdirectory( settings ) ########### next target ############### set(libdialogs_a_SOURCES splitadjustdialog.cpp investactivities.cpp investtransactioneditor.cpp kaccountselectdlg.cpp kbackupdlg.cpp kbalancechartdlg.cpp kbalancewarning.cpp kcategoryreassigndlg.cpp kchooseimportexportdlg.cpp kconfirmmanualenterdlg.cpp kcurrencycalculator.cpp kcurrencyeditdlg.cpp kavailablecurrencydlg.cpp kcurrencyeditordlg.cpp keditscheduledlg.cpp kenterscheduledlg.cpp kequitypriceupdatedlg.cpp kequitypriceupdateconfdlg.cpp - kexportdlg.cpp kfindtransactiondlg.cpp kgeneratesqldlg.cpp kgncimportoptionsdlg.cpp kgncpricesourcedlg.cpp kgpgkeyselectiondlg.cpp - kimportdlg.cpp kloadtemplatedlg.cpp kmergetransactionsdlg.cpp kmymoneyfileinfodlg.cpp kmymoneypricedlg.cpp kmymoneysplittable.cpp knewaccountdlg.cpp knewbankdlg.cpp knewbudgetdlg.cpp knewequityentrydlg.cpp editpersonaldatadlg.cpp kpayeereassigndlg.cpp ktagreassigndlg.cpp kreportconfigurationfilterdlg.cpp kselectdatabasedlg.cpp kselecttransactionsdlg.cpp ksplittransactiondlg.cpp ktemplateexportdlg.cpp kupdatestockpricedlg.cpp - mymoneyqifprofileeditor.cpp transactioneditor.cpp transactionmatcher.cpp konlinetransferform.cpp ) set(dialogs_HEADERS splitadjustdialog.h investtransactioneditor.h kcurrencycalculator.h transactioneditor.h ) set(dialogs_UI splitadjustdialog.ui kaccountselectdlgdecl.ui kbackupdlgdecl.ui kcategoryreassigndlgdecl.ui kchooseimportexportdlgdecl.ui kconfirmmanualenterdlgdecl.ui kcurrencycalculatordecl.ui kcurrencyeditdlg.ui kavailablecurrencydlg.ui kcurrencyeditordlg.ui keditscheduledlgdecl.ui kenterscheduledlgdecl.ui - kequitypriceupdatedlgdecl.ui kequitypriceupdateconfdlg.ui kexportdlgdecl.ui + kequitypriceupdatedlgdecl.ui kequitypriceupdateconfdlg.ui kfindtransactiondlgdecl.ui kgeneratesqldlgdecl.ui kgncimportoptionsdlgdecl.ui - kgncpricesourcedlgdecl.ui kimportdlgdecl.ui kloadtemplatedlgdecl.ui + kgncpricesourcedlgdecl.ui kloadtemplatedlgdecl.ui kmymoneyfileinfodlgdecl.ui kmymoneypricedlgdecl.ui knewaccountdlgdecl.ui knewbankdlgdecl.ui knewbudgetdlgdecl.ui knewequityentrydecl.ui editpersonaldatadlgdecl.ui kpayeereassigndlgdecl.ui ktagreassigndlgdecl.ui kselectdatabasedlgdecl.ui kselecttransactionsdlgdecl.ui ksortoptiondlg.ui ksplitcorrectiondlg.ui ksplittransactiondlgdecl.ui ktemplateexportdlg.ui - kupdatestockpricedlgdecl.ui mymoneyqifprofileeditordecl.ui + kupdatestockpricedlgdecl.ui ../widgets/kaccounttemplateselectordecl.ui ../widgets/transactionsortoptiondecl.ui konlinetransferformdecl.ui ) ki18n_wrap_ui(libdialogs_a_SOURCES ${dialogs_UI} ) add_library(dialogs STATIC ${libdialogs_a_SOURCES}) target_link_libraries(dialogs PUBLIC KChart KF5::ItemViews KF5::I18n KF5::TextWidgets KF5::Completion Qt5::Widgets Qt5::Sql Alkimia::alkimia kmm_mymoney onlinetask_interfaces kmm_widgets ) target_link_libraries(dialogs LINK_PUBLIC kmm_widgets kmm_mymoney onlinetask_interfaces ) ########### install files ############### install(FILES ${dialogs_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) diff --git a/kmymoney/dialogs/kexportdlgdecl.ui b/kmymoney/dialogs/kexportdlgdecl.ui deleted file mode 100644 index 902bf8030..000000000 --- a/kmymoney/dialogs/kexportdlgdecl.ui +++ /dev/null @@ -1,424 +0,0 @@ - - - - - - KExportDlgDecl - - - QIF Export - - - true - - - - 11 - - - 6 - - - - - 0 - - - 6 - - - - - - 5 - 7 - 0 - 0 - - - - From this dialog you are able to export transactions to a quicken compatible file, (known as a QIF file, because of the extension). Please enter the path to the QIF file or select it by clicking on the Browse button. - -You can choose the file's path, the account and the format of the QIF file (profile). Choose Account to export all the transactions between the specified dates or just categories. You can also limit the transactions that are exported by start and ending date. Once you have pressed the Export button a message box will appear when the export has completed detailing how many transactions, categories and payees were exported. - - - Qt::AlignTop|Qt::AlignLeft - - - true - - - - - - - - - QFrame::HLine - - - QFrame::Raised - - - - - - - 0 - - - 6 - - - - - - 0 - 1 - 0 - 0 - - - - File to export to: - - - false - - - - - - - - - - &Browse... - - - - - - - - - - - - - Account to export - - - false - - - - - - - - - - - - 0 - - - 6 - - - - - QIF Profile - - - false - - - - - - - 0 - - - 6 - - - - - - 3 - 0 - 0 - 0 - - - - - 200 - 0 - - - - false - - - - - - - New... - - - - - - - - - - - - - - - Contents to Export - - - - 11 - - - 6 - - - - - Account - - - - - - - Categories - - - - - - - - - - Date Range - - - - 11 - - - 6 - - - - - - - - - - - 0 - 1 - 0 - 0 - - - - - 50 - 0 - - - - End on: - - - false - - - - - - - - 0 - 1 - 0 - 0 - - - - - 50 - 0 - - - - Start on: - - - false - - - - - - - - 3 - 1 - 0 - 0 - - - - - - - - - - - - - - 230 - 16 - - - - QSizePolicy::Expanding - - - Qt::Horizontal - - - - - - - - - QFrame::HLine - - - QFrame::Sunken - - - - - - - 0 - - - 6 - - - - - - 451 - 0 - - - - QSizePolicy::Expanding - - - Qt::Horizontal - - - - - - - &Export - - - true - - - true - - - - - - - Cancel - - - true - - - - - - - - - - - kMyMoneyDateInput - QWidget -
../widgets/kmymoneydateinput.h
- - 90 - 25 - - 0 - - 1 - 1 - - image0 -
- - KMyMoneyAccountCombo - QWidget -
../widgets/kmymoneyaccountcombo.h
- - -1 - 25 - - 0 - - 3 - 5 - - image1 -
- - KComboBox - QComboBox -
kcombobox.h
-
- - KLineEdit - QLineEdit -
klineedit.h
-
-
- - - 789c6dd2c10ac2300c00d07bbf2234b7229d1be245fc04c5a3201e4615f430059d0711ff5ddb2e6bb236ec90eed134cb5a19d8ef36602af5ecdbfeeac05dda0798d3abebde87e3faa374d3807fa0d633a52d38d8de6f679fe33fc776e196f53cd010188256a3600a292882096246517815ca99884606e18044a3a40d91824820924265a7923a2e8bcd05f33db1173e002913175f2a6be6d3294871a2d95fa00e8a94ee017b69d339d90df1e77c57ea072ede6758 - - - 789c9595c94eeb581086f73c4544ed50ab6eecd889ad562f1802843961a6d58b632764228c616cf5bb77f9d45fe75e11165c4c703e55d55fc319f8b152bb38daafadfc587a9abbf9b8ac9523f7585be93fcf66ef7ffff3d7bf4bcb715c93df2891d7f21f4bcb5cd4cadac1ddedc003fd02ddf9af16274075ffe3b91738f33c01276aa722b0b753ec390b7c0cce107f092e238d7fa838aa578fe713b0c5bf1aabdddd8033e49f1923be052e11df31867dc77366fe1c81cdbeab1c5b3d4d30f2d351c571dd988fc196ffda731e43cf811b98a79b06d6fa5e8c11ff1458e775054ec0dd8a1b91f9731d9ca9dd0d9513d35ff79c9b9dc7c68ddcdbf7c125ecefca16cf7e9e4994987dcfb34b73d86363e41f559cc6691ff93f8c119f2a3723b5bb8931faffc91abf691cabfd0e9c42ffd21876bf9ea90bf946c6a9f37ca66cf16e0c463fb46a8cf95e0456bb3346be3ad8fa3d34463fcf153763b33b678cf853e516e6415bc688bf31567fde04a7e8cffcd13ffbf56f4afea6ef971265f157bdd218f9d7c0e8878fc0a88ffdfe6f355a51abe1b9ad9c59ff0781fd7e62afdf2a5a79abf0f63765f377857123f2f67bb0e5f3e7256bb4f24cf3ed83fbcad4530efe99e722373e5476981faf05d679ad1aa3fe0b63ad8737c0982fed19c33ea8384ff2284f3c1f283b9be746609dc79bb19e37877837403d3bca458cf3531a23de9fdfbc147fed673bb0d6f7688cfc5960cdd737c6bc87caa2affe8dc09aefc338d17e3be026f291b1fad318ecb0bf38b0f677628cf5f1f7ab4b4c8f7363d4731b58eb2763d43f02231f9f0656bd8631f44cdfe6d535c67afaf9b8d245aef43c05a7e04439c44f8d511f2b9736cf79609d0ff44b9bd719d8ea7b0763fdd89f8742ea6beabfed73e53206df05d6fbecd618e7f311dc84ff0bd82993df7f453fd81f02abdecc58f737a56087fbf3d518f14f81f5bed17afb7dac07b58dd19fbfcfcab418383d3f73e550df36d8fa7f0eacfa57ca03ccbb3b67928ffbc6435eaf10ef92fb3ce06b1e7ef18c78cc138d80bee329dff08c6ff98eeff9811ff989e7fcecdf2ffcca6ffcfed39f0bf93610ef0f5ee5355ee70d6ef3266fc9df6deec8b3c3bbbcf7497f5fb40ff8503c8eb8cb3d6fedf1b1d0099f4accd927fd219f7bef0bf1eaf1255f719d23ff3de606279c7ed26f4addeba25629b738e39c98881c891695d45fd4a7016fd075e54d431ad1980f684253bae11ecde896ee16f41fe95e6c0f9c55def4484f92ab4573afd1a6e785fa5f65264ebc72effd22b1aff446eff421d60b5a5dd09fd39af8af134b254fbe8f8836a84d958aa3ad85fab765de3dea48979b555db4237ebbb447fbf23ea0c3c5fa799b643ea2d595ba7ba2bd2b9e0f744c27dca0d32feaef5029fe056f499767744e173413cb842ee98aea5fe877a4d7a85a577aa148a6b94fb1ecb3091d5183924ffa234a659f9c4ae61b8e6cffd2896837a945d9c2fa8e650fcadea2bc72e3b69399b842a2afe41eebbb81bbfea43f7143d983a91b714a776e4cab74e89f3a6db9899bba9b4ffadf7c82fe6ff857e7f7f7f425e25b39baf3e5fffe5cfa1fdc4beba0 - - -
diff --git a/kmymoney/dialogs/mymoneyqifprofileeditordecl.ui b/kmymoney/dialogs/mymoneyqifprofileeditordecl.ui deleted file mode 100644 index fb762418d..000000000 --- a/kmymoney/dialogs/mymoneyqifprofileeditordecl.ui +++ /dev/null @@ -1,820 +0,0 @@ - - - - - - MyMoneyQifProfileEditorDecl - - - - 0 - 0 - 736 - 300 - - - - QIF Profile Editor - - - true - - - - 11 - - - 6 - - - - - - - - 0 - 7 - 0 - 0 - - - - - - - - - - - - - - General - - - - - - - - - 140 - 0 - - - - Void mark - - - false - - - - - - - - 140 - 0 - - - - Opening Balance text - - - false - - - - - - - - - - - 140 - 0 - - - - Type field text - - - false - - - - - - - - - - - - - - 140 - 0 - - - - Account delimiter - - - false - - - - - - - - - - - - - - 140 - 0 - - - - Description - - - false - - - - - - - - - Attempt to match similar transactions - - - - - - - - 20 - 20 - - - - QSizePolicy::Expanding - - - Qt::Vertical - - - - - - - - Filter - - - - - - - - - 140 - 0 - - - - Output filter location - - - false - - - - - - - - - - false - - - - - - - - 140 - 0 - - - - Input filter file type - - - false - - - - - - - *.qif - - - - - - - - 140 - 0 - - - - Input filter location - - - false - - - - - - - - - - 20 - 90 - - - - QSizePolicy::Expanding - - - Qt::Vertical - - - - - - - - Date - - - - - - - - - - The format of the dates in the QIF file. - - - - - - - - 140 - 0 - - - - Date Format - - - The format of the dates in the QIF file. - - - false - - - - - - - If a QIF file contains date entries with years represented with two digits then either an apostrophe or a slash may be used to delimit the year in the dates for certain centuries. This enables 1905 to be distinguished from 2005. Specify here which range of years will have year delimited by an apostrophe (eg. for Quicken this is usually 1900-1949). - - - - - - - - 140 - 0 - - - - Apostrophe Handling - - - If a QIF file contains date entries with years represented with two digits then either an apostrophe or a slash may be used to delimit the year in the dates for certain centuries. This enables 1905 to be distinguished from 2005. Specify here which range of years will have year delimited by an apostrophe (eg. for Quicken this is usually 1900-1949). - - - false - - - - - - - - - - 90 - 20 - - - - QSizePolicy::Expanding - - - Qt::Horizontal - - - - - - - - - - 20 - 80 - - - - QSizePolicy::Expanding - - - Qt::Vertical - - - - - - - - Amounts - - - - - - Qt::ScrollBarAlwaysOff - - - true - - - QAbstractItemView::SingleSelection - - - - Field - - - false - - - false - - - - - QIF-Record - - - false - - - false - - - - - Decimal Symbol - - - false - - - false - - - - - Thousand Delimiter - - - false - - - false - - - - - SortColumnInvisible - - - true - - - true - - - - - Value - - - T - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - Split-Value - - - $ - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - Commission - - - O - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - Price - - - I - - - - - - - - - 3 - - - - - - - - - - - - - - - - - - - - Quantity - - - Q - - - - - - - - - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 150 - 0 - - - - Decimal Symbol - - - false - - - - - - - - 150 - 0 - - - - Thousands Separator - - - false - - - - - - - - - - - - - 100 - 20 - - - - QSizePolicy::Expanding - - - Qt::Horizontal - - - - - - - - - - 16 - 16 - - - - QSizePolicy::Expanding - - - Qt::Vertical - - - - - - - - - - - - - 0 - - - 6 - - - - - Help - - - - - - - New - - - - - - - Delete - - - - - - - Rename - - - - - - - - 104 - 0 - - - - QSizePolicy::Expanding - - - Qt::Horizontal - - - - - - - Reset - - - - - - - OK - - - - - - true - - - true - - - - - - - Cancel - - - - - - true - - - - - - - - - - - KComboBox - QComboBox -
kcombobox.h
-
- - KLineEdit - QLineEdit -
klineedit.h
-
- - QTabWidget - QTabWidget -
ktabwidget.h
-
-
- - m_profileListBox - profileTabs - m_editDescription - m_editType - m_editOpeningBalance - m_editAccountDelimiter - m_editVoidMark - m_helpButton - m_newButton - m_deleteButton - m_renameButton - m_resetButton - m_okButton - m_cancelButton - m_editDateFormat - m_editApostrophe - m_decimalBox - m_thousandsBox - m_editAmounts - m_editInputFilterLocation - m_editOutputFilterLocation - -
diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 8e4c6bf7a..30b4ae716 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7753 +1,7559 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config-kmymoney.h" #include "kmymoney.h" // for _getpid #ifdef Q_OS_WIN32 //krazy:exclude=cpp #include #else #if HAVE_SYS_TYPES_H #include #endif #if HAVE_UNISTD_H #include #endif #endif // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" -#include "dialogs/kexportdlg.h" -#include "dialogs/kimportdlg.h" -#include "dialogs/mymoneyqifprofileeditor.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/kfindtransactiondlg.h" #include "dialogs/knewbankdlg.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kselectdatabasedlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/knewloanwizard.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/ktagreassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "dialogs/kmergetransactionsdlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kgeneratesqldlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/kgpgkeyselectiondlg.h" #include "dialogs/ktemplateexportdlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/onlinejobmessagesview.h" #include "widgets/kmymoneymvccombo.h" #include "widgets/kmymoneycompletion.h" #include "views/kmymoneyview.h" #include "views/konlinejoboutbox.h" #include "models/onlinejobmessagesmodel.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/storage/mymoneystoragedump.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/onlinejob.h" #include "mymoney/onlinetransfer.h" #include "mymoney/onlinejobadministration.h" -#include "converter/mymoneyqifwriter.h" -#include "converter/mymoneyqifreader.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "icons/icons.h" #include #include #include "konlinetransferform.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" using namespace Icons; static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; // define the default period to warn about an expiring recoverkey to 30 days // but allows to override this setting during build time #ifndef RECOVER_KEY_EXPIRATION_WARNING #define RECOVER_KEY_EXPIRATION_WARNING 30 #endif const QHash KMyMoneyApp::s_Actions { {Action::FileOpenDatabase, QStringLiteral("open_database")}, {Action::FileSaveAsDatabase, QStringLiteral("saveas_database")}, {Action::FileBackup, QStringLiteral("file_backup")}, {Action::FileImportGNC, QStringLiteral("file_import_gnc")}, - {Action::FileImportQIF, QStringLiteral("file_import_qif")}, - {Action::FileExportQIF, QStringLiteral("file_export_qif")}, {Action::FileImportStatement, QStringLiteral("file_import_statement")}, {Action::FileImportTemplate, QStringLiteral("file_import_template")}, {Action::FileExportTemplate, QStringLiteral("file_export_template")}, {Action::FilePersonalData, QStringLiteral("view_personal_data")}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump")}, #endif {Action::FileInformation, QStringLiteral("view_file_info")}, {Action::EditFindTransaction, QStringLiteral("edit_find_transaction")}, {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail")}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions")}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories")}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts")}, {Action::InstitutionNew, QStringLiteral("institution_new")}, {Action::InstitutionEdit, QStringLiteral("institution_edit")}, {Action::InstitutionDelete, QStringLiteral("institution_delete")}, {Action::AccountNew, QStringLiteral("account_new")}, {Action::AccountOpen, QStringLiteral("account_open")}, {Action::AccountStartReconciliation, QStringLiteral("account_reconcile")}, {Action::AccountFinishReconciliation, QStringLiteral("account_reconcile_finish")}, {Action::AccountPostponeReconciliation, QStringLiteral("account_reconcile_postpone")}, {Action::AccountEdit, QStringLiteral("account_edit")}, {Action::AccountDelete, QStringLiteral("account_delete")}, {Action::AccountClose, QStringLiteral("account_close")}, {Action::AccountReopen, QStringLiteral("account_reopen")}, {Action::AccountTransactionReport, QStringLiteral("account_transaction_report")}, {Action::AccountBalanceChart, QStringLiteral("account_chart")}, {Action::AccountOnlineMap, QStringLiteral("account_online_map")}, {Action::AccountOnlineUnmap, QStringLiteral("account_online_unmap")}, {Action::AccountUpdateMenu, QStringLiteral("account_online_update_menu")}, {Action::AccountUpdate, QStringLiteral("account_online_update")}, {Action::AccountUpdateAll, QStringLiteral("account_online_update_all")}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer")}, {Action::CategoryNew, QStringLiteral("category_new")}, {Action::CategoryEdit, QStringLiteral("category_edit")}, {Action::CategoryDelete, QStringLiteral("category_delete")}, - {Action::ToolQIF, QStringLiteral("tools_qif_editor")}, {Action::ToolCurrencies, QStringLiteral("tools_currency_editor")}, {Action::ToolPrices, QStringLiteral("tools_price_editor")}, {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices")}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check")}, {Action::ToolPerformance, QStringLiteral("tools_performancetest")}, {Action::ToolSQL, QStringLiteral("tools_generate_sql")}, {Action::ToolCalculator, QStringLiteral("tools_kcalc")}, {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages")}, {Action::SettingsLanguage, QStringLiteral("settings_language")}, {Action::HelpShow, QStringLiteral("help_show_tip")}, {Action::TransactionNew, QStringLiteral("transaction_new")}, {Action::TransactionEdit, QStringLiteral("transaction_edit")}, {Action::TransactionEnter, QStringLiteral("transaction_enter")}, {Action::TransactionEditSplits, QStringLiteral("transaction_editsplits")}, {Action::TransactionCancel, QStringLiteral("transaction_cancel")}, {Action::TransactionDelete, QStringLiteral("transaction_delete")}, {Action::TransactionDuplicate, QStringLiteral("transaction_duplicate")}, {Action::TransactionMatch, QStringLiteral("transaction_match")}, {Action::TransactionAccept, QStringLiteral("transaction_accept")}, {Action::TransactionToggleReconciled, QStringLiteral("transaction_mark_toggle")}, {Action::TransactionToggleCleared, QStringLiteral("transaction_mark_cleared")}, {Action::TransactionReconciled, QStringLiteral("transaction_mark_reconciled")}, {Action::TransactionNotReconciled, QStringLiteral("transaction_mark_notreconciled")}, {Action::TransactionSelectAll, QStringLiteral("transaction_select_all")}, {Action::TransactionGoToAccount, QStringLiteral("transaction_goto_account")}, {Action::TransactionGoToPayee, QStringLiteral("transaction_goto_payee")}, {Action::TransactionCreateSchedule, QStringLiteral("transaction_create_schedule")}, {Action::TransactionAssignNumber, QStringLiteral("transaction_assign_number")}, {Action::TransactionCombine, QStringLiteral("transaction_combine")}, {Action::TransactionCopySplits, QStringLiteral("transaction_copy_splits")}, {Action::TransactionMoveMenu, QStringLiteral("transaction_move_menu")}, {Action::TransactionMarkMenu, QStringLiteral("transaction_mark_menu")}, {Action::TransactionContextMarkMenu, QStringLiteral("transaction_context_mark_menu")}, {Action::InvestmentNew, QStringLiteral("investment_new")}, {Action::InvestmentEdit, QStringLiteral("investment_edit")}, {Action::InvestmentDelete, QStringLiteral("investment_delete")}, {Action::InvestmentOnlinePrice, QStringLiteral("investment_online_price_update")}, {Action::InvestmentManualPrice, QStringLiteral("investment_manual_price_update")}, {Action::ScheduleNew, QStringLiteral("schedule_new")}, {Action::ScheduleEdit, QStringLiteral("schedule_edit")}, {Action::ScheduleDelete, QStringLiteral("schedule_delete")}, {Action::ScheduleDuplicate, QStringLiteral("schedule_duplicate")}, {Action::ScheduleEnter, QStringLiteral("schedule_enter")}, {Action::ScheduleSkip, QStringLiteral("schedule_skip")}, {Action::PayeeNew, QStringLiteral("payee_new")}, {Action::PayeeRename, QStringLiteral("payee_rename")}, {Action::PayeeDelete, QStringLiteral("payee_delete")}, {Action::PayeeMerge, QStringLiteral("payee_merge")}, {Action::TagNew, QStringLiteral("tag_new")}, {Action::TagRename, QStringLiteral("tag_rename")}, {Action::TagDelete, QStringLiteral("tag_delete")}, {Action::BudgetNew, QStringLiteral("budget_new")}, {Action::BudgetRename, QStringLiteral("budget_rename")}, {Action::BudgetDelete, QStringLiteral("budget_delete")}, {Action::BudgetCopy, QStringLiteral("budget_copy")}, {Action::BudgetChangeYear, QStringLiteral("budget_change_year")}, {Action::BudgetForecast, QStringLiteral("budget_forecast")}, {Action::CurrencyNew, QStringLiteral("currency_new")}, {Action::CurrencyRename, QStringLiteral("currency_rename")}, {Action::CurrencyDelete, QStringLiteral("currency_delete")}, {Action::CurrencySetBase, QStringLiteral("currency_setbase")}, {Action::PriceNew, QStringLiteral("price_new")}, {Action::PriceEdit, QStringLiteral("price_edit")}, {Action::PriceUpdate, QStringLiteral("price_update")}, {Action::PriceDelete, QStringLiteral("price_delete")}, #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard")}, {Action::DebugTraces, QStringLiteral("debug_traces")}, #endif {Action::DebugTimers, QStringLiteral("debug_timers")}, {Action::OnlineJobDelete, QStringLiteral("onlinejob_delete")}, {Action::OnlineJobEdit, QStringLiteral("onlinejob_edit")}, {Action::OnlineJobLog, QStringLiteral("onlinejob_log")}, }; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; typedef void(KMyMoneyApp::*KMyMoneyAppFunc)(); class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), m_ft(0), m_moveToAccountSelector(0), m_statementXMLindex(0), m_balanceWarning(0), m_collectingStatements(false), m_pluginLoader(0), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(0), m_progressBar(0), - m_qifReader(0), m_smtReader(0), m_searchDlg(0), m_autoSaveTimer(0), m_progressTimer(0), m_inAutoSaving(false), m_transactionEditor(0), m_endingBalanceDlg(0), m_saveEncrypted(0), m_additionalKeyLabel(0), m_additionalKeyButton(0), m_recentFiles(0), #ifdef KF5Holidays_FOUND m_holidayRegion(0), #endif m_applicationIsReady(true) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); /** * The public interface. */ KMyMoneyApp * const q; MyMoneyFileTransaction* m_ft; kMyMoneyAccountSelector* m_moveToAccountSelector; int m_statementXMLindex; KBalanceWarning* m_balanceWarning; bool m_collectingStatements; QStringList m_statementResults; KMyMoneyPlugin::PluginLoader* m_pluginLoader; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * @brief List of all plugged plugins * * The key is the file name of the plugin. */ QMap m_plugins; /** * @brief List of plugged importer plugins * * The key is the objectName of the plugin. */ QMap m_importerPlugins; /** * @brief List of plugged online plugins * * The key is the objectName of the plugin. */ QMap m_onlinePlugins; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; /// The URL of the file currently being edited when open. QUrl m_fileName; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; - MyMoneyQifReader* m_qifReader; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; QObject* m_pluginInterface; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneyAccount m_selectedInvestment; MyMoneyInstitution m_selectedInstitution; MyMoneySchedule m_selectedSchedule; MyMoneySecurity m_selectedCurrency; MyMoneyPrice m_selectedPrice; QList m_selectedPayees; QList m_selectedTags; QList m_selectedBudgets; KMyMoneyRegister::SelectedTransactions m_selectedTransactions; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // pointer to the current transaction editor TransactionEditor* m_transactionEditor; // Reconciliation dialog KEndingBalanceDlg* m_endingBalanceDlg; // Pointer to the combo box used for key selection during // File/Save as KComboBox* m_saveEncrypted; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; QStringList m_additionalGpgKeys; QLabel* m_additionalKeyLabel; QPushButton* m_additionalKeyButton; KRecentFilesAction* m_recentFiles; #ifdef KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; // methods void consistencyCheck(bool alwaysDisplayResults); void setCustomColors(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney", QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setCustomColors(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); updateCaption(true); 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); { QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants 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(); initActions(); initDynamicMenus(); d->m_myMoneyView = new KMyMoneyView(this/*the global variable kmymoney is not yet assigned. So we pass it here*/); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, SIGNAL(aboutToChangeView()), this, SLOT(slotResetSelections())); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connectActionsAndViews(); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); loadPlugins(); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(actionCollection()->action(s_Actions[Action::ViewTransactionDetail]), &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); d->m_backupState = BACKUP_IDLE; // TODO: port kf5 int weekStart = 1;//KLocale::global()->workingWeekStartDay(); int weekEnd = 7;//KLocale::global()->workingWeekEndDay(); bool startFirst = (weekStart < weekEnd); for (int i = 0; i < 8; i++) { if (startFirst) d->m_processingDays.setBit(i, (i >= weekStart && i <= weekEnd)); else d->m_processingDays.setBit(i, (i >= weekStart || i <= weekEnd)); } d->m_autoSaveTimer = new QTimer(this); d->m_progressTimer = new QTimer(this); connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone())); // make sure, we get a note when the engine changes state connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotDataChanged())); // make sure we have a balance warning object d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(); // kickstart date change timer slotDateChanged(); connect(this, SIGNAL(fileLoaded(QUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties())); } KMyMoneyApp::~KMyMoneyApp() { // delete cached objects since the are in the way // when unloading the plugins onlineJobAdministration::instance()->clearCaches(); // we need to unload all plugins before we destroy anything else unloadPlugins(); delete d->m_searchDlg; - delete d->m_qifReader; delete d->m_transactionEditor; delete d->m_endingBalanceDlg; delete d->m_moveToAccountSelector; #ifdef KF5Holidays_FOUND delete d->m_holidayRegion; #endif delete d; } QUrl KMyMoneyApp::lastOpenedURL() { QUrl url = d->m_startDialog ? QUrl() : d->m_fileName; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotObjectDestroyed(QObject* o) { if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = 0; } } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } void KMyMoneyApp::createTransactionMoveMenu() { if (!d->m_moveToAccountSelector) { QWidget* w = factory()->container("transaction_move_menu", this); QMenu *menu = dynamic_cast(w); if (menu) { QWidgetAction *accountSelectorAction = new QWidgetAction(menu); d->m_moveToAccountSelector = new kMyMoneyAccountSelector(menu, 0, false); d->m_moveToAccountSelector->setObjectName("transaction_move_menu_selector"); accountSelectorAction->setDefaultWidget(d->m_moveToAccountSelector); menu->addAction(accountSelectorAction); connect(d->m_moveToAccountSelector, SIGNAL(destroyed(QObject*)), this, SLOT(slotObjectDestroyed(QObject*))); connect(d->m_moveToAccountSelector, SIGNAL(itemSelected(QString)), this, SLOT(slotMoveToAccount(QString))); } } } void KMyMoneyApp::initDynamicMenus() { connect(this, SIGNAL(accountSelected(MyMoneyAccount)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateMoveToAccountMenu())); } void KMyMoneyApp::initActions() { KActionCollection *aC = actionCollection(); // ************* // 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); KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC); KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC); /* Look-up table for all custom actions. It's required for: 1) building QList with QActions to be added to ActionCollection 2) adding custom features to QActions like e.g. overlaid icon or keyboard shortcut */ QHash lutActions; // ************* // Adding all actions // ************* { // struct for QAction's vital (except icon) informations struct actionInfo { Action action; KMyMoneyAppFunc callback; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase, i18n("Open database..."), Icon::SVNUpdate}, {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase, i18n("Save as database..."), Icon::FileArchiver}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile, i18n("Backup..."), Icon::Empty}, {Action::FileImportGNC, &KMyMoneyApp::slotGncImport, i18n("GnuCash..."), Icon::Empty}, - {Action::FileImportQIF, &KMyMoneyApp::slotQifImport, i18n("QIF..."), Icon::Empty}, - {Action::FileExportQIF, &KMyMoneyApp::slotQifExport, i18n("QIF..."), Icon::Empty}, {Action::FileImportStatement, &KMyMoneyApp::slotStatementImport, i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates, i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates, i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal, i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo, i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog, i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, &KMyMoneyApp::slotFindTransaction, i18n("Find transaction..."), Icon::Empty}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail, i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions, i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories, i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts, i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::InstitutionNew, &KMyMoneyApp::slotInstitutionNew, i18n("New institution..."), Icon::Empty}, {Action::InstitutionEdit, &KMyMoneyApp::slotInstitutionEdit, i18n("Edit institution..."), Icon::Empty}, {Action::InstitutionDelete, &KMyMoneyApp::slotInstitutionDelete, i18n("Delete institution..."), Icon::Empty}, // ***************** // The accounts menu // ***************** {Action::AccountNew, &KMyMoneyApp::slotAccountNew, i18n("New account..."), Icon::Empty}, {Action::AccountOpen, &KMyMoneyApp::slotAccountOpen, i18n("Open ledger"), Icon::ViewFinancialList}, {Action::AccountStartReconciliation, &KMyMoneyApp::slotAccountReconcileStart, i18n("Reconcile..."), Icon::Reconcile}, {Action::AccountFinishReconciliation, &KMyMoneyApp::slotAccountReconcileFinish, i18nc("Finish reconciliation", "Finish"), Icon::Empty}, {Action::AccountPostponeReconciliation, &KMyMoneyApp::slotAccountReconcilePostpone, i18n("Postpone reconciliation"), Icon::MediaPlaybackPause}, {Action::AccountEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit account..."), Icon::Empty}, {Action::AccountDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete account..."), Icon::Empty}, {Action::AccountClose, &KMyMoneyApp::slotAccountClose, i18n("Close account"), Icon::Empty}, {Action::AccountReopen, &KMyMoneyApp::slotAccountReopen, i18n("Reopen account"), Icon::Empty}, {Action::AccountTransactionReport, &KMyMoneyApp::slotAccountTransactionReport, i18n("Transaction report"), Icon::ViewFinancialList}, {Action::AccountBalanceChart, &KMyMoneyApp::slotAccountChart, i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::AccountOnlineMap, &KMyMoneyApp::slotAccountMapOnline, i18n("Map account..."), Icon::NewsSubscribe}, {Action::AccountOnlineUnmap, &KMyMoneyApp::slotAccountUnmapOnline, i18n("Unmap account..."), Icon::NewsUnsubscribe}, {Action::AccountUpdateMenu, &KMyMoneyApp::slotAccountUpdateOnline, i18nc("Update online accounts menu", "Update"), Icon::Empty}, {Action::AccountUpdate, &KMyMoneyApp::slotAccountUpdateOnline, i18n("Update account..."), Icon::Empty}, {Action::AccountUpdateAll, &KMyMoneyApp::slotAccountUpdateOnlineAll, i18n("Update all accounts..."), Icon::Empty}, {Action::AccountCreditTransfer, &KMyMoneyApp::slotNewOnlineTransfer, i18n("New credit transfer"), Icon::Empty}, // ******************* // The categories menu // ******************* {Action::CategoryNew, &KMyMoneyApp::slotCategoryNew, i18n("New category..."), Icon::Empty}, {Action::CategoryEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit category..."), Icon::Empty}, {Action::CategoryDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete category..."), Icon::Empty}, // ************** // The tools menu // ************** - {Action::ToolQIF, &KMyMoneyApp::slotQifProfileEditor, i18n("QIF Profile Editor..."), Icon::DocumentProperties}, {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog, i18n("Currencies..."), Icon::ViewCurrencyList}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog, i18n("Prices..."), Icon::Empty}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate, i18n("Update Stock and Currency Prices..."), Icon::Empty}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck, i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest, i18n("Performance-Test"), Icon::Fork}, {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql, i18n("Generate Database SQL"), Icon::Empty}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc, i18n("Calculator..."), Icon::AccessoriesCalculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages, i18n("Enable all messages"), Icon::Empty}, {Action::SettingsLanguage, &KMyMoneyApp::slotKDELanguageSettings, i18n("KDE language settings..."), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay, i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::TransactionNew, &KMyMoneyApp::slotTransactionsNew, i18nc("New transaction button", "New"), Icon::Empty}, {Action::TransactionEdit, &KMyMoneyApp::slotTransactionsEdit, i18nc("Edit transaction button", "Edit"), Icon::Empty}, {Action::TransactionEnter, &KMyMoneyApp::slotTransactionsEnter, i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::TransactionEditSplits, &KMyMoneyApp::slotTransactionsEditSplits, i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::TransactionCancel, &KMyMoneyApp::slotTransactionsCancel, i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::TransactionDelete, &KMyMoneyApp::slotTransactionsDelete, i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::TransactionDuplicate, &KMyMoneyApp::slotTransactionDuplicate, i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::TransactionMatch, &KMyMoneyApp::slotTransactionMatch, i18nc("Button text for match transaction", "Match"),Icon::Empty}, {Action::TransactionAccept, &KMyMoneyApp::slotTransactionsAccept, i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::Empty}, {Action::TransactionToggleReconciled, &KMyMoneyApp::slotToggleReconciliationFlag, i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::TransactionToggleCleared, &KMyMoneyApp::slotMarkTransactionCleared, i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::TransactionReconciled, &KMyMoneyApp::slotMarkTransactionReconciled, i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::TransactionNotReconciled, &KMyMoneyApp::slotMarkTransactionNotReconciled, i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, {Action::TransactionSelectAll, &KMyMoneyApp::selectAllTransactions, i18nc("Select all transactions", "Select all"), Icon::Empty}, {Action::TransactionGoToAccount, &KMyMoneyApp::slotTransactionGotoAccount, i18n("Go to account"), Icon::GoJump}, {Action::TransactionGoToPayee, &KMyMoneyApp::slotTransactionGotoPayee, i18n("Go to payee"), Icon::GoJump}, {Action::TransactionCreateSchedule, &KMyMoneyApp::slotTransactionCreateSchedule, i18n("Create scheduled transaction..."), Icon::AppointmentNew}, {Action::TransactionAssignNumber, &KMyMoneyApp::slotTransactionAssignNumber, i18n("Assign next number"), Icon::Empty}, {Action::TransactionCombine, &KMyMoneyApp::slotTransactionCombine, i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::TransactionCopySplits, &KMyMoneyApp::slotTransactionCopySplits, i18n("Copy splits"), Icon::Empty}, //Investment {Action::InvestmentNew, &KMyMoneyApp::slotInvestmentNew, i18n("New investment..."), Icon::Empty}, {Action::InvestmentEdit, &KMyMoneyApp::slotInvestmentEdit, i18n("Edit investment..."), Icon::Empty}, {Action::InvestmentDelete, &KMyMoneyApp::slotInvestmentDelete, i18n("Delete investment..."), Icon::Empty}, {Action::InvestmentOnlinePrice, &KMyMoneyApp::slotOnlinePriceUpdate, i18n("Online price update..."), Icon::Empty}, {Action::InvestmentManualPrice, &KMyMoneyApp::slotManualPriceUpdate, i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::ScheduleNew, &KMyMoneyApp::slotScheduleNew, i18n("New scheduled transaction"), Icon::AppointmentNew}, {Action::ScheduleEdit, &KMyMoneyApp::slotScheduleEdit, i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::ScheduleDelete, &KMyMoneyApp::slotScheduleDelete, i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::ScheduleDuplicate, &KMyMoneyApp::slotScheduleDuplicate, i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::ScheduleEnter, &KMyMoneyApp::slotScheduleEnter, i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::ScheduleSkip, &KMyMoneyApp::slotScheduleSkip, i18n("Skip next transaction..."), Icon::MediaSeekForward}, //Payees {Action::PayeeNew, &KMyMoneyApp::slotPayeeNew, i18n("New payee"), Icon::ListAddUser}, {Action::PayeeRename, &KMyMoneyApp::payeeRename, i18n("Rename payee"), Icon::PayeeRename}, {Action::PayeeDelete, &KMyMoneyApp::slotPayeeDelete, i18n("Delete payee"), Icon::ListRemoveUser}, {Action::PayeeMerge, &KMyMoneyApp::slotPayeeMerge, i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::TagNew, &KMyMoneyApp::slotTagNew, i18n("New tag"), Icon::ListAddTag}, {Action::TagRename, &KMyMoneyApp::tagRename, i18n("Rename tag"), Icon::TagRename}, {Action::TagDelete, &KMyMoneyApp::slotTagDelete, i18n("Delete tag"), Icon::ListRemoveTag}, //Budget {Action::BudgetNew, &KMyMoneyApp::slotBudgetNew, i18n("New budget"), Icon::Empty}, {Action::BudgetRename, &KMyMoneyApp::budgetRename, i18n("Rename budget"), Icon::Empty}, {Action::BudgetDelete, &KMyMoneyApp::slotBudgetDelete, i18n("Delete budget"), Icon::Empty}, {Action::BudgetCopy, &KMyMoneyApp::slotBudgetCopy, i18n("Copy budget"), Icon::Empty}, {Action::BudgetChangeYear, &KMyMoneyApp::slotBudgetChangeYear, i18n("Change budget year"), Icon::ViewCalendar}, {Action::BudgetForecast, &KMyMoneyApp::slotBudgetForecast, i18n("Budget based on forecast"), Icon::ViewForecast}, //Currency actions {Action::CurrencyNew, &KMyMoneyApp::slotCurrencyNew, i18n("New currency"), Icon::Empty}, {Action::CurrencyRename, &KMyMoneyApp::currencyRename, i18n("Rename currency"), Icon::EditRename}, {Action::CurrencyDelete, &KMyMoneyApp::slotCurrencyDelete, i18n("Delete currency"), Icon::EditDelete}, {Action::CurrencySetBase, &KMyMoneyApp::slotCurrencySetBase, i18n("Select as base currency"), Icon::KMyMoney}, //Price actions {Action::PriceNew, &KMyMoneyApp::priceNew, i18n("New price..."), Icon::DocumentNew}, {Action::PriceEdit, &KMyMoneyApp::priceEdit, i18n("Edit price..."), Icon::DocumentEdit}, {Action::PriceUpdate, &KMyMoneyApp::priceOnlineUpdate, i18n("Online Price Update..."), Icon::Empty}, {Action::PriceDelete, &KMyMoneyApp::priceDelete, i18n("Delete price..."), Icon::EditDelete}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature, i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces, i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers, i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::OnlineJobDelete, &KMyMoneyApp::slotRemoveJob, i18n("Remove credit transfer"), Icon::EditDelete}, {Action::OnlineJobEdit, &KMyMoneyApp::slotEditJob, i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::OnlineJobLog, &KMyMoneyApp::slotOnlineJobLog, i18n("Show log"), Icon::Empty}, }; foreach (const auto info, actionInfos) { QAction *a = new QAction(0); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(s_Actions.value(info.action)); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(QIcon::fromTheme(g_Icons.value(info.icon))); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } // ************* // Setting some of added actions checkable // ************* { // Some of acitions schould be checkable, // so set them here const QVector checkableActions { Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories, #ifdef KMM_DEBUG Action::DebugTraces, #endif Action::ViewShowAll }; foreach (const auto it, checkableActions) lutActions[it]->setCheckable(true); } // ************* // Setting custom icons for some of added actions // ************* { // struct for QAction's icon overlays struct iconOverlayInfo { Action action; Icon icon; Icon overlay; Qt::Corner corner; }; const QVector actionOverlaidIcons { {Action::EditFindTransaction, Icon::ViewFinancialTransfer, Icon::EditFind, Qt::BottomRightCorner}, {Action::InstitutionNew, Icon::ViewBank, Icon::ListAdd, Qt::BottomRightCorner}, {Action::InstitutionEdit, Icon::ViewBank, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::InstitutionDelete, Icon::ViewBank, Icon::EditDelete, Qt::BottomRightCorner}, {Action::AccountNew, Icon::ViewBankAccount, Icon::ListAdd, Qt::TopRightCorner}, {Action::AccountFinishReconciliation, Icon::Merge, Icon::DialogOK, Qt::BottomRightCorner}, {Action::AccountEdit, Icon::ViewBankAccount, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::AccountDelete, Icon::ViewBankAccount, Icon::EditDelete, Qt::BottomRightCorner}, {Action::AccountClose, Icon::ViewBankAccount, Icon::DialogClose, Qt::BottomRightCorner}, {Action::AccountReopen, Icon::ViewBankAccount, Icon::DialogOK, Qt::BottomRightCorner}, {Action::AccountUpdateMenu, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountUpdate, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountUpdateAll, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountCreditTransfer, Icon::ViewBankAccount, Icon::MailMessageNew, Qt::BottomRightCorner}, {Action::CategoryNew, Icon::ViewFinancialCategories, Icon::ListAdd, Qt::TopRightCorner}, {Action::CategoryEdit, Icon::ViewFinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::CategoryDelete, Icon::ViewFinancialCategories, Icon::EditDelete, Qt::BottomRightCorner}, {Action::ToolUpdatePrices, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}, {Action::TransactionNew, Icon::ViewFinancialTransfer, Icon::ListAdd, Qt::TopRightCorner}, {Action::TransactionEdit, Icon::ViewFinancialTransfer, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::TransactionMatch, Icon::ViewFinancialTransfer, Icon::DocumentImport, Qt::BottomRightCorner}, {Action::TransactionAccept, Icon::ViewFinancialTransfer, Icon::DialogOKApply, Qt::BottomRightCorner}, {Action::InvestmentNew, Icon::ViewInvestment, Icon::ListAdd, Qt::TopRightCorner}, {Action::InvestmentEdit, Icon::ViewInvestment, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::InvestmentDelete, Icon::ViewInvestment, Icon::EditDelete, Qt::BottomRightCorner}, {Action::InvestmentOnlinePrice, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}, {Action::BudgetNew, Icon::ViewTimeScheduleCalculus, Icon::ListAdd, Qt::TopRightCorner}, {Action::BudgetRename, Icon::ViewTimeScheduleCalculus, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::BudgetDelete, Icon::ViewTimeScheduleCalculus, Icon::EditDelete, Qt::BottomRightCorner}, {Action::BudgetCopy, Icon::ViewTimeScheduleCalculus, Icon::EditCopy, Qt::BottomRightCorner}, {Action::PriceUpdate, Icon::ViewCurrencyList, Icon::Download, Qt::BottomRightCorner} }; for(const auto& it : actionOverlaidIcons) lutActions[it.action]->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[it.icon], g_Icons[it.overlay], it.corner)); } // ************* // 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::AccountStartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::TransactionNew, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::TransactionToggleReconciled, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::TransactionToggleCleared, Qt::CTRL + Qt::Key_Alt + Qt::Key_Space)}, {qMakePair(Action::TransactionReconciled, Qt::CTRL + Qt::Key_Shift + Qt::Key_Space)}, {qMakePair(Action::TransactionSelectAll, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::TransactionAssignNumber, Qt::CTRL + Qt::Key_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(KMyMoneyGlobalSettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneyGlobalSettings::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 QAction *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(QIcon::fromTheme(g_Icons[Icon::DocumentImport])); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentExport])); } void KMyMoneyApp::connectActionsAndViews() { KOnlineJobOutbox *const outbox = d->m_myMoneyView->getOnlineJobOutbox(); Q_CHECK_PTR(outbox); QAction *const onlineJob_delete = actionCollection()->action(s_Actions[Action::OnlineJobDelete]); Q_CHECK_PTR(onlineJob_delete); connect(onlineJob_delete, SIGNAL(triggered()), outbox, SLOT(slotRemoveJob())); QAction *const onlineJob_edit = actionCollection()->action(s_Actions[Action::OnlineJobEdit]); Q_CHECK_PTR(onlineJob_edit); connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob())); } #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 actionCollection()->action(s_Actions[_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"); actionCollection()->action(s_Actions[Action::ViewHideReconciled])->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); actionCollection()->action(s_Actions[Action::ViewHideCategories])->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); updateCaption(true); } int KMyMoneyApp::askSaveOnClose() { int ans; if (KMyMoneyGlobalSettings::autoSaveOnClose()) { ans = KMessageBox::Yes; } else { ans = KMessageBox::warningYesNoCancel(this, i18n("The file has been changed, save it?")); } return ans; } bool KMyMoneyApp::queryClose() { if (!isReady()) return false; if (d->m_myMoneyView->dirty()) { int ans = askSaveOnClose(); if (ans == KMessageBox::Cancel) return false; else if (ans == KMessageBox::Yes) { bool saved = slotFileSave(); saveOptions(); return saved; } } if (d->m_myMoneyView->isDatabase()) slotFileClose(); // close off the database saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KMyMoneyApp::slotFileInfoDialog() { QPointer dlg = new KMyMoneyFileInfoDlg(0); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPerformanceTest() { // dump performance report to stderr int measurement[2]; QTime timer; MyMoneyAccount acc; qDebug("--- Starting performance tests ---"); // AccountList MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; timer.start(); for (int i = 0; i < 1000; ++i) { QList list; MyMoneyFile::instance()->accountList(list); measurement[i != 0] = timer.elapsed(); } std::cerr << "accountList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of asset account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of asset account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "totalBalance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of expense account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of expense account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); timer.start(); for (int i = 0; i < 1000; ++i) { MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] = timer.elapsed(); } std::cerr << "totalBalance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { list = MyMoneyFile::instance()->transactionList(filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { MyMoneyFile::instance()->transactionList(list, filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList(list)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } MyMoneyFile::instance()->preloadCache(); } void KMyMoneyApp::slotFileNew() { KMSTATUS(i18n("Creating new document...")); slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->newFile(); d->m_fileName = QUrl(); updateCaption(); NewUserWizard::Wizard *wizard = new NewUserWizard::Wizard(); if (wizard->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // 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 MyMoneyAccount acc = wizard->account(); if (acc.name().length()) { acc.setInstitutionId(inst.id()); MyMoneyAccount asset = file->asset(); file->addAccount(acc, asset); // create possible opening balance transaction if (!wizard->openingBalance().isZero()) { file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); } } // import the account templates QList templates = wizard->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } d->m_fileName = wizard->url(); ft.commit(); KMyMoneyGlobalSettings::setFirstTimeRun(false); // FIXME This is a bit clumsy. We re-read the freshly // created file to be able to run through all the // fixup logic and then save it to keep the modified // flag off. slotFileSave(); d->m_myMoneyView->readFile(d->m_fileName); slotFileSave(); // now keep the filename in the recent files used list //KRecentFilesAction *p = dynamic_cast(action(KStandardAction::name(KStandardAction::OpenRecent))); //if(p) d->m_recentFiles->addUrl(d->m_fileName); writeLastUsedFile(d->m_fileName.url()); } catch (const MyMoneyException &) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } if (wizard->startSettingsAfterFinished()) slotSettings(); } else { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } delete wizard; updateCaption(); emit fileLoaded(d->m_fileName); } } QUrl KMyMoneyApp::selectFile(const QString& /*title*/, const QString& _path, const QString& mask, QFileDialog::FileMode mode, QWidget* widget) { Q_UNUSED(mask) Q_UNUSED(mode) QString path(_path); // if the path is not specified open the file dialog in the last used directory // 'kmymoney' is the keyword that identifies the last used directory in KFileDialog if (path.isEmpty()) path = "kfiledialog:///kmymoney-import"; // TODO: port KF5 QPointer dialog = new QFileDialog(this, QString(), path); //dialog->setMode(mode); QUrl url; if (dialog->exec() == QDialog::Accepted && dialog != 0) { // TODO: port KF5 //url = dialog->selectedUrl(); } // in case we have an additional widget, we remove it from the // dialog, so that the caller can still access it. Therefore, it is // the callers responsibility to delete the object if (widget) widget->setParent(0); delete dialog; return url; } // General open void KMyMoneyApp::slotFileOpen() { KMSTATUS(i18n("Open a file.")); QString prevDir = readLastUsedDir(); QPointer dialog = new QFileDialog(this, QString(), prevDir, i18n("KMyMoney files (*.kmy *.xml);;All files")); dialog->setFileMode(QFileDialog::ExistingFile); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { slotFileOpenRecent(dialog->selectedUrls().first()); } delete dialog; } void KMyMoneyApp::slotOpenDatabase() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite); if (!dialog->checkDrivers()) { delete dialog; return; } if (dialog->exec() == QDialog::Accepted && dialog != 0) { slotFileOpenRecent(dialog->selectedURL()); } delete dialog; } bool KMyMoneyApp::isImportableFile(const 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 = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url.path())) { result = true; break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } void KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { KMSTATUS(i18n("Loading file...")); QUrl lastFile = d->m_fileName; // check if there are other instances which might have this file open QList list = instanceList(); QList::ConstIterator it; bool duplicate = false; #ifdef KMM_DBUS for (it = list.constBegin(); duplicate == false && it != list.constEnd(); ++it) { QDBusInterface remoteApp(*it, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) { qDebug("D-Bus error while calling app->filename()"); } else { if (reply.value() == url.url()) { duplicate = true; } } } #endif if (!duplicate) { QUrl newurl = url; if ((newurl.scheme() == "sql")) { if (QUrlQuery(newurl).queryItemValue("driver") == "QMYSQL3") { // fix any old urls // TODO: port KF5 //newurl.removeQueryItem("driver"); //newurl.addQueryItem("driver", "QMYSQL"); } if (QUrlQuery(newurl).queryItemValue("driver") == "QSQLITE3") { // TODO: port KF5 //newurl.removeQueryItem("driver"); //newurl.addQueryItem("driver", "QSQLITE"); } // check if a password is needed. it may be if the URL came from the last/recent file list QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl); if (!dialog->checkDrivers()) { delete dialog; return; } // if we need to supply a password, then show the dialog // otherwise it isn't needed if ((QUrlQuery(newurl).queryItemValue("secure").toLower() == "yes") && newurl.password().isEmpty()) { if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { newurl = dialog->selectedURL(); } else { delete dialog; return; } } delete dialog; } // TODO: port KF5 if ((newurl.scheme() == "sql") || (newurl.isValid() /*&& KIO::NetAccess::exists(newurl, KIO::NetAccess::SourceSide, this)*/)) { slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { try { if (d->m_myMoneyView->readFile(newurl)) { if ((d->m_myMoneyView->isNativeFile())) { d->m_fileName = newurl; updateCaption(); d->m_recentFiles->addUrl(newurl); writeLastUsedFile(newurl.toDisplayString(QUrl::PreferLocalFile)); } else { d->m_fileName = QUrl(); // imported files have no filename } // Check the schedules slotCheckSchedules(); } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", e.what())); } updateCaption(); emit fileLoaded(d->m_fileName); } else { /*fileOpen failed - should we do something or maybe fileOpen puts out the message... - it does for database*/ } } else { // newurl invalid slotFileClose(); KMessageBox::sorry(this, i18n("

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

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); } } else { // isDuplicate KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); } } bool KMyMoneyApp::slotFileSave() { // if there's nothing changed, there's no need to save anything if (!d->m_myMoneyView->dirty()) return true; bool rc = false; KMSTATUS(i18n("Saving file...")); if (d->m_fileName.isEmpty()) return slotFileSaveAs(); d->consistencyCheck(false); /*if (myMoneyView->isDatabase()) { rc = myMoneyView->saveDatabase(m_fileName); // the 'save' function is no longer relevant for a database*/ setEnabled(false); rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); setEnabled(true); d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileSaveAsFilterChanged(const QString& filter) { if (!d->m_saveEncrypted) return; if (filter.compare(QLatin1String("*.kmy"), Qt::CaseInsensitive) != 0) { d->m_saveEncrypted->setCurrentItem(0); d->m_saveEncrypted->setEnabled(false); } else { d->m_saveEncrypted->setEnabled(true); } } void KMyMoneyApp::slotManageGpgKeys() { QPointer dlg = new KGpgKeySelectionDlg(this); dlg->setKeys(d->m_additionalGpgKeys); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->m_additionalGpgKeys = dlg->keys(); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count())); } delete dlg; } void KMyMoneyApp::slotKeySelected(int idx) { int cnt = 0; if (idx != 0) { cnt = d->m_additionalGpgKeys.count(); } d->m_additionalKeyLabel->setEnabled(idx != 0); d->m_additionalKeyButton->setEnabled(idx != 0); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", cnt)); } bool KMyMoneyApp::slotFileSaveAs() { bool rc = false; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); KMSTATUS(i18n("Saving file with a new filename...")); // fill the additional key list with the default d->m_additionalGpgKeys = KMyMoneyGlobalSettings::gpgRecipientList(); QWidget* vbox = new QWidget(); QVBoxLayout *vboxVBoxLayout = new QVBoxLayout(vbox); vboxVBoxLayout->setMargin(0); if (KGPGFile::GPGAvailable()) { QWidget* keyBox = new QWidget(vbox); QHBoxLayout *keyBoxHBoxLayout = new QHBoxLayout(keyBox); keyBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(keyBox); QLabel *keyLabel = new QLabel(i18n("Encryption key to be used"), keyBox); keyBoxHBoxLayout->addWidget(keyLabel); d->m_saveEncrypted = new KComboBox(keyBox); keyBoxHBoxLayout->addWidget(d->m_saveEncrypted); QWidget* labelBox = new QWidget(vbox); QHBoxLayout *labelBoxHBoxLayout = new QHBoxLayout(labelBox); labelBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(labelBox); d->m_additionalKeyLabel = new QLabel(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count()), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyLabel); d->m_additionalKeyButton = new QPushButton(i18n("Manage additional keys"), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyButton); connect(d->m_additionalKeyButton, SIGNAL(clicked()), this, SLOT(slotManageGpgKeys())); connect(d->m_saveEncrypted, SIGNAL(activated(int)), this, SLOT(slotKeySelected(int))); // fill the secret key list and combo box QStringList keyList; KGPGFile::secretKeyList(keyList); d->m_saveEncrypted->addItem(i18n("No encryption")); for (QStringList::iterator it = keyList.begin(); it != keyList.end(); ++it) { QStringList fields = (*it).split(':', QString::SkipEmptyParts); if (fields[0] != recoveryKeyId) { // replace parenthesis in name field with brackets QString name = fields[1]; name.replace('(', "["); name.replace(')', "]"); name = QString("%1 (0x%2)").arg(name).arg(fields[0]); d->m_saveEncrypted->addItem(name); if (name.contains(KMyMoneyGlobalSettings::gpgRecipient())) { d->m_saveEncrypted->setCurrentItem(name); } } } } QString prevDir; // don't prompt file name if not a native file if (d->m_myMoneyView->isNativeFile()) prevDir = readLastUsedDir(); QPointer dlg = new QFileDialog(this, i18n("Save As"), prevDir, QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); dlg->setAcceptMode(QFileDialog::AcceptSave); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotFileSaveAsFilterChanged(QString))); if (dlg->exec() == QDialog::Accepted && dlg != 0) { QUrl newURL = dlg->selectedUrls().first(); if (!newURL.fileName().isEmpty()) { d->consistencyCheck(false); // deleting the dialog will delete the combobox pointed to by d->m_saveEncrypted so get the key name here QString selectedKeyName; if (d->m_saveEncrypted && d->m_saveEncrypted->currentIndex() != 0) selectedKeyName = d->m_saveEncrypted->currentText(); d->m_saveEncrypted = 0; QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); // append extension if not present if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) newName.append(QLatin1String(".kmy")); newURL = QUrl::fromUserInput(newName); d->m_recentFiles->addUrl(newURL); setEnabled(false); // If this is the anonymous file export, just save it, don't actually take the // name, or remember it! Don't even try to encrypt it if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) rc = d->m_myMoneyView->saveFile(newURL); else { d->m_fileName = newURL; QString encryptionKeys; QRegExp keyExp(".* \\((.*)\\)"); if (keyExp.indexIn(selectedKeyName) != -1) { encryptionKeys = keyExp.cap(1); } if (!d->m_additionalGpgKeys.isEmpty()) { if (!encryptionKeys.isEmpty()) encryptionKeys.append(QLatin1Char(',')); encryptionKeys.append(d->m_additionalGpgKeys.join(QLatin1Char(','))); } rc = d->m_myMoneyView->saveFile(d->m_fileName, encryptionKeys); //write the directory used for this file as the default one for next time. writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); writeLastUsedFile(newName); } d->m_autoSaveTimer->stop(); setEnabled(true); } } delete dlg; updateCaption(); return rc; } void KMyMoneyApp::slotSaveAsDatabase() { saveAsDatabase(); } bool KMyMoneyApp::saveAsDatabase() { bool rc = false; QUrl oldUrl; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) { dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); oldUrl = d->m_fileName.isEmpty() ? lastOpenedURL() : d->m_fileName; } KMSTATUS(i18n("Saving file to database...")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); QUrl url = oldUrl; if (!dialog->checkDrivers()) { delete dialog; return (false); } while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { url = dialog->selectedURL(); // If the protocol is SQL for the old and new, and the hostname and database names match // Let the user know that the current database cannot be saved on top of itself. if (url.scheme() == "sql" && oldUrl.scheme() == "sql" && oldUrl.host() == url.host() && QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver") && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { KMessageBox::sorry(this, i18n("Cannot save to current database.")); } else { try { rc = d->m_myMoneyView->saveAsDatabase(url); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot save to current database: %1", e.what())); } } } delete dialog; if (rc) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(url); writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); } d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileCloseWindow() { KMSTATUS(i18n("Closing window...")); if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } close(); } void KMyMoneyApp::slotFileClose() { bool okToSelect = true; // check if transaction editor is open and ask user what he wants to do slotTransactionsCancelOrEnter(okToSelect); if (!okToSelect) return; // no update status here, as we might delete the status too early. if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } d->closeFile(); } void KMyMoneyApp::slotFileQuit() { // don't modify the status message here as this will prevent quit from working!! // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 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::slotShowTransactionDetail() { } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneyGlobalSettings::setHideReconciledTransactions(actionCollection()->action(s_Actions[Action::ViewHideReconciled])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneyGlobalSettings::setHideUnusedCategory(actionCollection()->action(s_Actions[Action::ViewHideCategories])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(actionCollection()->action(s_Actions[Action::DebugTraces])->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = actionCollection()->action(s_Actions[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_myMoneyView->fileOpen()) { 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->userNameText); user.setAddress(editPersonalDataDlg->userStreetText); user.setCity(editPersonalDataDlg->userTownText); user.setState(editPersonalDataDlg->userCountyText); user.setPostcode(editPersonalDataDlg->userPostcodeText); user.setTelephone(editPersonalDataDlg->userTelephoneText); user.setEmail(editPersonalDataDlg->userEmailText); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", e.what())); } } delete editPersonalDataDlg; } void KMyMoneyApp::slotFileFileInfo() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } QFile g("kmymoney.dump"); g.open(QIODevice::WriteOnly); QDataStream st(&g); MyMoneyStorageDump dumper; dumper.writeStream(st, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); } void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); int rc; QPointer dlg = new KLoadTemplateDlg(); if ((rc = dlg->exec()) == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { // import the account templates QList templates = dlg->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to import template(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name(); QDir d(savePath); if (!d.exists()) d.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; } } } -void KMyMoneyApp::slotQifImport() -{ - if (d->m_qifReader == 0) { - // FIXME: the menu entry for qif import should be disabled here - - QPointer dlg = new KImportDlg(this); - - if (dlg->exec() == QDialog::Accepted && dlg != 0) { - KMSTATUS(i18n("Importing file...")); - d->m_qifReader = new MyMoneyQifReader; - - // remove all kmm-statement-#.txt files - d->unlinkStatementXML(); - - connect(d->m_qifReader, SIGNAL(importFinished()), this, SLOT(slotQifImportFinished())); - - d->m_qifReader->setURL(dlg->file()); - - d->m_qifReader->setProfile(dlg->profile()); - d->m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); - d->m_qifReader->setProgressCallback(&progressCallback); - - // disable all standard widgets during the import - setEnabled(false); - - d->m_ft = new MyMoneyFileTransaction(); - d->m_collectingStatements = true; - d->m_statementResults.clear(); - if (!d->m_qifReader->startImport()) { - // if the import failed to start make sure that slotQifImportFinished is called otherwise the application will be left disabled - QTimer::singleShot(0, this, SLOT(slotQifImportFinished())); - } - } - delete dlg; - - slotUpdateActions(); - } -} - -void KMyMoneyApp::slotQifImportFinished() -{ - if (d->m_qifReader != 0) { - d->m_qifReader->finishImport(); - d->m_ft->commit(); - d->m_collectingStatements = false; - - KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); - -#if 0 - // fixme: re-enable the QIF import menu options - if (d->m_qifReader->finishImport()) { - if (verifyImportedData(d->m_qifReader->account())) { - // keep the new data set, destroy the backup copy - delete d->m_engineBackup; - d->m_engineBackup = 0; - } - } - - if (d->m_engineBackup != 0) { - // user cancelled, destroy the updated set and keep the backup copy - IMyMoneyStorage* data = file->storage(); - - - if (data != 0) { - file->detachStorage(data); - delete data; - } - file->attachStorage(d->m_engineBackup); - d->m_engineBackup = 0; - - } -#endif - - // update the views as they might still contain invalid data - // from the import session. The same applies for the window caption - d->m_myMoneyView->slotRefreshViews(); - updateCaption(); - - delete d->m_qifReader; - d->m_qifReader = 0; - } - delete d->m_ft; - d->m_ft = 0; - - slotStatusProgressBar(-1, -1); - ready(); - - // re-enable all standard widgets - setEnabled(true); - slotUpdateActions(); -} - void KMyMoneyApp::slotGncImport() { if (d->m_myMoneyView->fileOpen()) { switch (KMessageBox::questionYesNoCancel(0, i18n("You cannot import GnuCash data into an existing file. Do you wish to save this file?"), PACKAGE)) { case KMessageBox::Yes: slotFileSave(); break; case KMessageBox::No: d->closeFile(); break; default: return; } } KMSTATUS(i18n("Importing a GnuCash file.")); QUrl fileToRead = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("GnuCash files (*.gnucash *.xac *.gnc);;All files (*)")); if (!fileToRead.isEmpty()) { // call the importer if (d->m_myMoneyView->readFile(fileToRead)) { // imported files don't have a name d->m_fileName = QUrl(); updateCaption(); emit fileLoaded(d->m_fileName); } } } void KMyMoneyApp::slotAccountChart() { if (!d->m_selectedAccount.id().isEmpty()) { QPointer dlg = new KBalanceChartDlg(d->m_selectedAccount, this); dlg->exec(); delete dlg; } } // // KMyMoneyApp::slotStatementImport() is for testing only. The MyMoneyStatement // is not intended to be exposed to users in XML form. // void KMyMoneyApp::slotStatementImport() { bool result = false; KMSTATUS(i18n("Importing an XML Statement.")); QList files{QFileDialog::getOpenFileUrls(this, QString(), QUrl(), i18n("XML files (*.xml);;All files (*)"))}; if (!files.isEmpty()) { d->m_collectingStatements = (files.count() > 1); foreach (const QUrl &url, files) { qDebug("Processing '%s'", qPrintable(url.path())); result |= slotStatementImport(url.path()); } /* QFile f( dialog->selectedURL().path() ); f.open( QIODevice::ReadOnly ); QString error = "Unable to parse file"; QDomDocument* doc = new QDomDocument; if(doc->setContent(&f, FALSE)) { if ( doc->doctype().name() == "KMYMONEY-STATEMENT" ) { QDomElement rootElement = doc->documentElement(); if(!rootElement.isNull()) { QDomNode child = rootElement.firstChild(); if(!child.isNull() && child.isElement()) { MyMoneyStatement s; if ( s.read(child.toElement()) ) result = slotStatementImport(s); else error = "File does not contain any statements"; } } } else error = "File is not a KMyMoney Statement"; } delete doc; if ( !result ) { QMessageBox::critical( this, i18n("Critical Error"), i18n("Unable to read file %1: %2").arg( dialog->selectedURL().path()).arg(error), QMessageBox::Ok, 0 ); }*/ } if (!result) { // re-enable all standard widgets setEnabled(true); } } bool KMyMoneyApp::slotStatementImport(const QString& url) { bool result = false; MyMoneyStatement s; if (MyMoneyStatement::readXMLFile(s, url)) result = slotStatementImport(s); else KMessageBox::error(this, i18n("Error importing %1: This file is not a valid KMM statement file.", url), i18n("Invalid Statement")); return result; } bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s, bool silent) { bool result = false; // keep a copy of the statement if (KMyMoneySettings::logImportedStatements()) { QString logFile = QString("%1/kmm-statement-%2.txt").arg(KMyMoneySettings::logPath()).arg(d->m_statementXMLindex++); MyMoneyStatement::writeXMLFile(s, logFile); } // we use an object on the heap here, so that we can check the presence // of it during slotUpdateActions() by looking at the pointer. d->m_smtReader = new MyMoneyStatementReader; d->m_smtReader->setAutoCreatePayee(true); d->m_smtReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); QStringList messages; result = d->m_smtReader->import(s, messages); bool transactionAdded = d->m_smtReader->anyTransactionAdded(); // get rid of the statement reader and tell everyone else // about the destruction by setting the pointer to zero delete d->m_smtReader; d->m_smtReader = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); if (!d->m_collectingStatements && !silent) KMessageBox::informationList(this, i18n("The statement has been processed with the following results:"), messages, i18n("Statement stats")); else if (transactionAdded) d->m_statementResults += messages; slotUpdateActions();// Re-enable menu items after import via plugin. return result; } -void KMyMoneyApp::slotQifExport() -{ - KMSTATUS(i18n("Exporting file...")); - - QPointer dlg = new KExportDlg(this); - - if (dlg->exec() == QDialog::Accepted && dlg != 0) { - if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) { - MyMoneyQifWriter writer; - connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); - - writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), - dlg->accountSelected(), dlg->categorySelected(), - dlg->startDate(), dlg->endDate()); - } - } - delete dlg; -} - bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; // TODO: port KF5 //if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, this)) { // 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 KConfigDialog* dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self()); connect(dlg, &KConfigDialog::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration() { MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); LedgerSeperator::setFirstFiscalDate(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); d->m_myMoneyView->updateViewType(); // update the sql storage module settings MyMoneyStorageSql::setStartDate(KMyMoneyGlobalSettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneyGlobalSettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneyGlobalSettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneyGlobalSettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneyGlobalSettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setCustomColors(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } void KMyMoneyApp::slotBackupFile() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->m_myMoneyView->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } if (d->m_fileName.isEmpty()) return; if (!d->m_fileName.isLocalFile()) { KMessageBox::sorry(this, i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_fileName.url()), i18n("Local files only")); return; } // TODO: port KF5 #if 0 QPointer backupDlg = new KBackupDlg(this); #ifdef Q_OS_WIN backupDlg->mountCheckBox->setEnabled(false); #endif int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBox->isChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->txtMountPoint->text(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); #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; #endif } 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() { QString today; today.sprintf("-%04d-%02d-%02d.kmy", QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().day()); QString backupfile = d->m_mountpoint + '/' + d->m_fileName.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_fileName.toLocalFile()) + "+ nul") << QDir::toNativeSeparators(backupfile); #else d->m_proc << "cp" << "-f"; d->m_proc << d->m_fileName.toLocalFile() << backupfile; #endif d->m_backupState = BACKUP_COPYING; 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() { // TODO: port KF5 #if 0 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; } #endif } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(this); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } -void KMyMoneyApp::slotQifProfileEditor() -{ - QPointer editor = new MyMoneyQifProfileEditor(true, this); - editor->setObjectName("QIF Profile Editor"); - editor->exec(); - delete editor; - -} - void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneyGlobalSettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::slotFindTransaction() { if (d->m_searchDlg == 0) { d->m_searchDlg = new KFindTransactionDlg(this); connect(d->m_searchDlg, SIGNAL(destroyed()), this, SLOT(slotCloseSearchDialog())); connect(d->m_searchDlg, SIGNAL(transactionSelected(QString,QString)), d->m_myMoneyView, SLOT(slotLedgerSelected(QString,QString))); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KMyMoneyApp::slotCloseSearchDialog() { if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = 0; } void KMyMoneyApp::createInstitution(MyMoneyInstitution& institution) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Cannot add institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionNew() { MyMoneyInstitution institution; slotInstitutionNew(institution); } void KMyMoneyApp::slotInstitutionNew(MyMoneyInstitution& institution) { institution.clearId(); QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { institution = dlg->institution(); createInstitution(institution); } delete dlg; } void KMyMoneyApp::slotInstitutionEdit() { slotInstitutionEdit(MyMoneyInstitution()); } void KMyMoneyApp::slotInstitutionEdit(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; // make sure the selected object has an id if (d->m_selectedInstitution.id().isEmpty()) return; try { MyMoneyFile* file = MyMoneyFile::instance(); //grab a pointer to the view, regardless of it being a account or institution view. MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); // bankSuccess is not checked anymore because d->m_file->institution will throw anyway QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { file->modifyInstitution(dlg->institution()); ft.commit(); slotSelectInstitution(file->institution(dlg->institution().id())); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store institution: %1", e.what())); } } delete dlg; } catch (const MyMoneyException &e) { if (!obj.id().isEmpty()) KMessageBox::information(this, i18n("Unable to edit institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionDelete() { MyMoneyFile *file = MyMoneyFile::instance(); try { MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); if ((KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the institution %1?

", institution.name()))) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { file->removeInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } -const MyMoneyAccount& KMyMoneyApp::findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const -{ - static MyMoneyAccount nullAccount; - - MyMoneyFile* file = MyMoneyFile::instance(); - QList parents; - try { - // search by id - if (!acc.id().isEmpty()) { - return file->account(acc.id()); - } - // collect the parents. in case parent does not have an id, we scan the all top-level accounts - if (parent.id().isEmpty()) { - parents << file->asset(); - parents << file->liability(); - parents << file->income(); - parents << file->expense(); - parents << file->equity(); - } else { - parents << parent; - } - QList::const_iterator it_p; - for (it_p = parents.constBegin(); it_p != parents.constEnd(); ++it_p) { - MyMoneyAccount parentAccount = *it_p; - // search by name (allow hierarchy) - int pos; - // check for ':' in the name and use it as separator for a hierarchy - QString name = acc.name(); - bool notFound = false; - while ((pos = name.indexOf(MyMoneyFile::AccountSeperator)) != -1) { - QString part = name.left(pos); - QString remainder = name.mid(pos + 1); - const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); - // if account has not been found, continue with next top level parent - if (existingAccount.id().isEmpty()) { - notFound = true; - break; - } - parentAccount = existingAccount; - name = remainder; - } - if (notFound) - continue; - const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, name); - if (!existingAccount.id().isEmpty()) { - if (acc.accountType() != MyMoneyAccount::UnknownAccountType) { - if (acc.accountType() != existingAccount.accountType()) - continue; - } - return existingAccount; - } - } - } catch (const MyMoneyException &e) { - KMessageBox::error(0, i18n("Unable to find account: %1", e.what())); - } - return nullAccount; -} - void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == MyMoneyAccount::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", e.what())); } } void KMyMoneyApp::slotCategoryNew(const QString& name, QString& id) { MyMoneyAccount account; account.setName(name); slotCategoryNew(account, MyMoneyFile::instance()->expense()); id = account.id(); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (KMessageBox::questionYesNo(this, QString("%1").arg(i18n("

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

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

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

Do you really want to delete the investment %1?

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

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

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

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

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

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

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

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

", src.name(), dst.name(), e.what())); } } void KMyMoneyApp::slotAccountTransactionReport() { // Generate a transaction report that contains transactions for only the // currently selected account. if (!d->m_selectedAccount.id().isEmpty()) { MyMoneyReport report( MyMoneyReport::eAccount, MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("%1 YTD Account Transactions", d->m_selectedAccount.name()), i18n("Generated Report") ); report.setGroup(i18n("Transactions")); report.addAccount(d->m_selectedAccount.id()); d->m_myMoneyView->slotShowReport(report); } } void KMyMoneyApp::slotScheduleNew() { slotScheduleNew(MyMoneyTransaction()); } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, MyMoneySchedule::occurrenceE occurrence) { MyMoneySchedule schedule; schedule.setOccurrence(occurrence); // if the schedule is based on an existing transaction, // we take the post date and project it to the next // schedule in a month. if (_t != MyMoneyTransaction()) { MyMoneyTransaction t(_t); schedule.setTransaction(t); if (occurrence != MyMoneySchedule::OCCUR_ONCE) schedule.setNextDueDate(schedule.nextPayment(t.postDate())); } QPointer dlg = new KEditScheduleDlg(schedule, this); TransactionEditor* transactionEditor = dlg->startEdit(); if (transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { schedule = dlg->schedule(); MyMoneyFile::instance()->addSchedule(schedule); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to add scheduled transaction: %1", e.what()), i18n("Add scheduled transaction")); } } } delete transactionEditor; delete dlg; } void KMyMoneyApp::slotScheduleEdit() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); KEditScheduleDlg* sched_dlg = 0; KEditLoanWizard* loan_wiz = 0; switch (schedule.type()) { case MyMoneySchedule::TYPE_BILL: case MyMoneySchedule::TYPE_DEPOSIT: case MyMoneySchedule::TYPE_TRANSFER: sched_dlg = new KEditScheduleDlg(schedule, this); d->m_transactionEditor = sched_dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart()); if (sched_dlg->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = sched_dlg->schedule(); // Check whether the new Schedule Date // is at or before the lastPaymentDate // If it is, ask the user whether to clear the // lastPaymentDate const QDate& next = sched.nextDueDate(); const QDate& last = sched.lastPayment(); if (next.isValid() && last.isValid() && next <= last) { // Entered a date effectively no later // than previous payment. Date would be // updated automatically so we probably // want to clear it. Let's ask the user. if (KMessageBox::questionYesNo(this, QString("") + i18n("You have entered a scheduled transaction date of %1. Because the scheduled transaction was last paid on %2, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset. Do you want to reset the last payment date?", QLocale().toString(next, QLocale::ShortFormat), QLocale().toString(last, QLocale::ShortFormat)) + QString(""), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) { sched.setLastPayment(QDate()); } } MyMoneyFile::instance()->modifySchedule(sched); // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } deleteTransactionEditor(); } delete sched_dlg; break; case MyMoneySchedule::TYPE_LOANPAYMENT: loan_wiz = new KEditLoanWizard(schedule.account(2)); connect(loan_wiz, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(loan_wiz, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (loan_wiz->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule()); MyMoneyFile::instance()->modifyAccount(loan_wiz->account()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } delete loan_wiz; break; case MyMoneySchedule::TYPE_ANY: break; } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDelete() { if (!d->m_selectedSchedule.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); QString msg = i18n("

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

", d->m_selectedSchedule.name()); if (sched.type() == MyMoneySchedule::TYPE_LOANPAYMENT) { msg += QString(" "); msg += i18n("In case of loan payments it is currently not possible to recreate the scheduled transaction."); } if (KMessageBox::questionYesNo(this, msg) == KMessageBox::No) return; MyMoneyFile::instance()->removeSchedule(sched); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to remove scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->actionCollection()->action(s_Actions[Action::ScheduleDuplicate])->isEnabled()) { MyMoneySchedule sch = d->m_selectedSchedule; sch.clearId(); sch.setLastPayment(QDate()); sch.setName(i18nc("Copy of scheduled transaction name", "Copy of %1", sch.name())); // make sure that we set a valid next due date if the original next due date is invalid if (!sch.nextDueDate().isValid()) sch.setNextDueDate(QDate::currentDate()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addSchedule(sch); ft.commit(); // select the new schedule in the view if (!d->m_selectedSchedule.id().isEmpty()) d->m_myMoneyView->slotScheduleSelected(sch.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to duplicate scheduled transaction: '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleSkip() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); skipSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::skipSchedule(MyMoneySchedule& schedule) { if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); if (!schedule.isFinished()) { if (schedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { QDate next = schedule.nextDueDate(); if (!schedule.isFinished() && (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to skip the %1 transaction scheduled for %2?", schedule.name(), QLocale().toString(next, QLocale::ShortFormat)) + QString(""))) == KMessageBox::Yes) { MyMoneyFileTransaction ft; schedule.setLastPayment(next); schedule.setNextDueDate(schedule.nextPayment(next)); MyMoneyFile::instance()->modifySchedule(schedule); ft.commit(); } } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, QString("") + i18n("Unable to skip scheduled transaction %1.", schedule.name()) + QString(""), e.what()); } } } void KMyMoneyApp::slotScheduleEnter() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); enterSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } KMyMoneyUtils::EnterScheduleResultCodeE KMyMoneyApp::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Cancel; if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); return rc; } QPointer dlg = new KEnterScheduleDlg(this, schedule); try { QDate origDueDate = schedule.nextDueDate(); dlg->showExtendedKeys(extendedKeys); d->m_transactionEditor = dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); MyMoneyTransaction torig, taccepted; d->m_transactionEditor->createTransaction(torig, dlg->transaction(), schedule.transaction().splits().isEmpty() ? MyMoneySplit() : schedule.transaction().splits().front(), true); // force actions to be available no matter what (will be updated according to the state during // slotTransactionsEnter or slotTransactionsCancel) kmymoney->actionCollection()->action(s_Actions[Action::TransactionCancel])->setEnabled(true); kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->setEnabled(true); KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce; if (!autoEnter || !schedule.isFixed()) { for (; dlg != 0;) { rc = KMyMoneyUtils::Cancel; if (dlg->exec() == QDialog::Accepted && dlg != 0) { rc = dlg->resultCode(); if (rc == KMyMoneyUtils::Enter) { d->m_transactionEditor->createTransaction(taccepted, torig, torig.splits().isEmpty() ? MyMoneySplit() : torig.splits().front(), true); // make sure to suppress comparison of some data: postDate torig.setPostDate(taccepted.postDate()); if (torig != taccepted) { QPointer cdlg = new KConfirmManualEnterDlg(schedule, this); cdlg->loadTransactions(torig, taccepted); if (cdlg->exec() == QDialog::Accepted) { action = cdlg->action(); delete cdlg; break; } delete cdlg; // the user has chosen 'cancel' during confirmation, // we go back to the editor continue; } } else if (rc == KMyMoneyUtils::Skip) { slotTransactionsCancel(); skipSchedule(schedule); } else { slotTransactionsCancel(); } } else { if (autoEnter) { if (KMessageBox::warningYesNo(this, i18n("Are you sure you wish to stop this scheduled transaction from being entered into the register?\n\nKMyMoney will prompt you again next time it starts unless you manually enter it later.")) == KMessageBox::No) { // the user has chosen 'No' for the above question, // we go back to the editor continue; } } slotTransactionsCancel(); } break; } } // if we still have the editor around here, the user did not cancel if ((d->m_transactionEditor != 0) && (dlg != 0)) { MyMoneyFileTransaction ft; try { MyMoneyTransaction t; // add the new transaction switch (action) { case KConfirmManualEnterDlg::UseOriginal: // setup widgets with original transaction data d->m_transactionEditor->setTransaction(dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front()); // and create a transaction based on that data taccepted = MyMoneyTransaction(); d->m_transactionEditor->createTransaction(taccepted, dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front(), true); break; case KConfirmManualEnterDlg::ModifyAlways: torig = taccepted; torig.setPostDate(origDueDate); schedule.setTransaction(torig); break; case KConfirmManualEnterDlg::ModifyOnce: break; } QString newId; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId, false)) { if (!newId.isEmpty()) { MyMoneyTransaction t = MyMoneyFile::instance()->transaction(newId); schedule.setLastPayment(t.postDate()); } // in case the next due date is invalid, the schedule is finished // we mark it as such by setting the next due date to one day past the end QDate nextDueDate = schedule.nextPayment(origDueDate); if (!nextDueDate.isValid()) { schedule.setNextDueDate(schedule.endDate().addDays(1)); } else { schedule.setNextDueDate(nextDueDate); } MyMoneyFile::instance()->modifySchedule(schedule); rc = KMyMoneyUtils::Enter; // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } deleteTransactionEditor(); } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } delete dlg; } return rc; } bool KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Payee")) { // Ask the user if that is what he intended to do? QString msg = QLatin1String("") + i18n("Do you want to add %1 as payer/receiver?", newnameBase) + QLatin1String(""); if (KMessageBox::questionYesNo(this, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->payeeByName(newname); newname = QString("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyPayee p; p.setName(newname); MyMoneyFile::instance()->addPayee(p); id = p.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); doit = false; } } return doit; } void KMyMoneyApp::slotPayeeNew() { QString id; slotPayeeNew(i18n("New Payee"), id); // the callbacks should have made sure, that the payees view has been // updated already. So we search for the id in the list of items // and select it. emit payeeCreated(id); } bool KMyMoneyApp::payeeInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotPayeeDelete() { if (d->m_selectedPayees.isEmpty()) return; // shouldn't happen // get confirmation from user QString prompt; if (d->m_selectedPayees.size() == 1) prompt = i18n("

Do you really want to remove the payee %1?

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

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

Do you really want to remove the tag %1?

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

Do you really want to remove the budget %1?

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

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

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

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

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

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

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain); } switch (rc) { case KMessageBox::Yes: slotTransactionsCancel(); break; case KMessageBox::No: slotTransactionsEnter(); // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } break; case KMessageBox::Cancel: // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); okToSelect = false; break; } } } oneTime = false; } } void KMyMoneyApp::slotToggleReconciliationFlag() { markTransaction(MyMoneySplit::Unknown); } void KMyMoneyApp::slotMarkTransactionCleared() { markTransaction(MyMoneySplit::Cleared); } void KMyMoneyApp::slotMarkTransactionReconciled() { markTransaction(MyMoneySplit::Reconciled); } void KMyMoneyApp::slotMarkTransactionNotReconciled() { markTransaction(MyMoneySplit::NotReconciled); } void KMyMoneyApp::markTransaction(MyMoneySplit::reconcileFlagE flag) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // turn on signals before we modify the last entry in the list cnt--; MyMoneyFile::instance()->blockSignals(cnt != 0); // get a fresh copy MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); MyMoneySplit sp = t.splitById((*it_t).split().id()); if (sp.reconcileFlag() != flag) { if (flag == MyMoneySplit::Unknown) { if (d->m_reconciliationAccount.id().isEmpty()) { // in normal mode we cycle through all states switch (sp.reconcileFlag()) { case MyMoneySplit::NotReconciled: sp.setReconcileFlag(MyMoneySplit::Cleared); break; case MyMoneySplit::Cleared: sp.setReconcileFlag(MyMoneySplit::Reconciled); break; case MyMoneySplit::Reconciled: sp.setReconcileFlag(MyMoneySplit::NotReconciled); break; default: break; } } else { // in reconciliation mode we skip the reconciled state switch (sp.reconcileFlag()) { case MyMoneySplit::NotReconciled: sp.setReconcileFlag(MyMoneySplit::Cleared); break; case MyMoneySplit::Cleared: sp.setReconcileFlag(MyMoneySplit::NotReconciled); break; default: break; } } } else { sp.setReconcileFlag(flag); } t.modifySplit(sp); MyMoneyFile::instance()->modifyTransaction(t); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionsAccept() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // reload transaction in case it got changed during the course of this loop MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); if (t.isImported()) { t.setImported(false); if (!d->m_selectedAccount.id().isEmpty()) { QList list = t.splits(); QList::const_iterator it_s; for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { if ((*it_s).reconcileFlag() == MyMoneySplit::NotReconciled) { MyMoneySplit s = (*it_s); s.setReconcileFlag(MyMoneySplit::Cleared); t.modifySplit(s); } } } } MyMoneyFile::instance()->modifyTransaction(t); } if ((*it_t).split().isMatched()) { // reload split in case it got changed during the course of this loop MyMoneySplit s = t.splitById((*it_t).split().id()); TransactionMatcher matcher(d->m_selectedAccount); matcher.accept(t, s); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to accept transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionGotoAccount() { if (!d->m_accountGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides // d->m_accountGoto while calling slotUpdateActions() QString accountId = d->m_accountGoto; d->m_myMoneyView->slotLedgerSelected(accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionGotoPayee() { if (!d->m_payeeGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides // d->m_payeeGoto and d->m_selectedAccount while calling slotUpdateActions() QString payeeId = d->m_payeeGoto; QString accountId = d->m_selectedAccount.id(); d->m_myMoneyView->slotPayeeSelected(payeeId, accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionCreateSchedule() { if (d->m_selectedTransactions.count() == 1) { // make sure to have the current selected split as first split in the schedule MyMoneyTransaction t = d->m_selectedTransactions[0].transaction(); MyMoneySplit s = d->m_selectedTransactions[0].split(); QString splitId = s.id(); s.clearId(); s.setReconcileFlag(MyMoneySplit::NotReconciled); s.setReconcileDate(QDate()); t.removeSplits(); t.addSplit(s); const QList& splits = d->m_selectedTransactions[0].transaction().splits(); QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).id() != splitId) { MyMoneySplit s0 = (*it_s); s0.clearId(); s0.setReconcileFlag(MyMoneySplit::NotReconciled); s0.setReconcileDate(QDate()); t.addSplit(s0); } } slotScheduleNew(t); } } void KMyMoneyApp::slotTransactionAssignNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextNumber(); } void KMyMoneyApp::slotTransactionCombine() { qDebug("slotTransactionCombine() not implemented yet"); } void KMyMoneyApp::slotTransactionCopySplits() { MyMoneyFile* file = MyMoneyFile::instance(); if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; KMyMoneyRegister::SelectedTransaction selectedSourceTransaction; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: selectedSourceTransaction = st; multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { MyMoneyFileTransaction ft; try { const MyMoneyTransaction& sourceTransaction = selectedSourceTransaction.transaction(); const MyMoneySplit& sourceSplit = selectedSourceTransaction.split(); foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { MyMoneyTransaction t = st.transaction(); // don't process the source transaction if (sourceTransaction.id() == t.id()) { continue; } const MyMoneySplit& baseSplit = st.split(); if (t.splitCount() == 1) { foreach (const MyMoneySplit& split, sourceTransaction.splits()) { // Don't copy the source split, as we already have that // as part of the destination transaction if (split.id() == sourceSplit.id()) { continue; } MyMoneySplit sp(split); // clear the ID and reconciliation state sp.clearId(); sp.setReconcileFlag(MyMoneySplit::NotReconciled); sp.setReconcileDate(QDate()); // in case it is a simple transaction consisting of two splits, // we can adjust the share and value part of the second split we // just created. We need to keep a possible price in mind in case // of different currencies if (sourceTransaction.splitCount() == 2) { sp.setValue(-baseSplit.value()); sp.setShares(-(baseSplit.shares() * baseSplit.price())); } t.addSplit(sp); } file->modifyTransaction(t); } } ft.commit(); } catch (const MyMoneyException &) { qDebug() << "transactionCopySplits() failed"; } } } } void KMyMoneyApp::slotMoveToAccount(const QString& id) { // close the menu, if it is still open QWidget* w = factory()->container("transaction_context_menu", this); if (w && w->isVisible()) { w->close(); } if (!d->m_selectedTransactions.isEmpty()) { MyMoneyFileTransaction ft; try { KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { d->moveInvestmentTransaction(d->m_selectedAccount.id(), id, (*it_t).transaction()); } else { QList::const_iterator it_s; bool changed = false; MyMoneyTransaction t = (*it_t).transaction(); for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { MyMoneySplit s = (*it_s); s.setAccountId(id); t.modifySplit(s); changed = true; } } if (changed) { MyMoneyFile::instance()->modifyTransaction(t); } } } ft.commit(); } catch (const MyMoneyException &) { } } } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; for (QList::const_iterator it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { stockAccountId = (*it_s).accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = *it_s; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; QStringList accountList = toInvAcc.accountList(); for (QStringList::const_iterator it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) { if (MyMoneyFile::instance()->account((*it_a)).currencyId() == stockSecurityId) { newStockAccountId = (*it_a); break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::slotUpdateMoveToAccountMenu() { createTransactionMoveMenu(); // in case we were not able to create the selector, we // better get out of here. Anything else would cause // a crash later on (accountSet.load) if (!d->m_moveToAccountSelector) return; if (!d->m_selectedAccount.id().isEmpty()) { AccountSet accountSet; if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { accountSet.addAccountType(MyMoneyAccount::Investment); } else if (d->m_selectedAccount.isAssetLiability()) { accountSet.addAccountType(MyMoneyAccount::Checkings); accountSet.addAccountType(MyMoneyAccount::Savings); accountSet.addAccountType(MyMoneyAccount::Cash); accountSet.addAccountType(MyMoneyAccount::AssetLoan); accountSet.addAccountType(MyMoneyAccount::CertificateDep); accountSet.addAccountType(MyMoneyAccount::MoneyMarket); accountSet.addAccountType(MyMoneyAccount::Asset); accountSet.addAccountType(MyMoneyAccount::Currency); accountSet.addAccountType(MyMoneyAccount::CreditCard); accountSet.addAccountType(MyMoneyAccount::Loan); accountSet.addAccountType(MyMoneyAccount::Liability); } else if (d->m_selectedAccount.isIncomeExpense()) { accountSet.addAccountType(MyMoneyAccount::Income); accountSet.addAccountType(MyMoneyAccount::Expense); } accountSet.load(d->m_moveToAccountSelector); // remove those accounts that we currently reference KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { QList::const_iterator it_s; for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { d->m_moveToAccountSelector->removeItem((*it_s).accountId()); } } // remove those accounts from the list that are denominated // in a different currency QStringList list = d->m_moveToAccountSelector->accountList(); QList::const_iterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.currencyId() != d->m_selectedAccount.currencyId()) d->m_moveToAccountSelector->removeItem((*it_a)); } } } void KMyMoneyApp::slotTransactionMatch() { // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed QString transactionActionText = actionCollection()->action(s_Actions[Action::TransactionMatch])->text(); transactionActionText.remove('&'); if (transactionActionText == i18nc("Button text for match transaction", "Match")) transactionMatch(); else transactionUnmatch(); } void KMyMoneyApp::transactionUnmatch() { KMyMoneyRegister::SelectedTransactions::const_iterator it; MyMoneyFileTransaction ft; try { for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).split().isMatched()) { TransactionMatcher matcher(d->m_selectedAccount); matcher.unmatch((*it).transaction(), (*it).split()); } } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to unmatch the selected transactions"), e.what()); } } void KMyMoneyApp::transactionMatch() { if (d->m_selectedTransactions.count() != 2) return; MyMoneyTransaction startMatchTransaction; MyMoneyTransaction endMatchTransaction; MyMoneySplit startSplit; MyMoneySplit endSplit; KMyMoneyRegister::SelectedTransactions::const_iterator it; KMyMoneyRegister::SelectedTransactions toBeDeleted; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) { if (endMatchTransaction.id().isEmpty()) { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } else { //This is a second imported transaction, we still want to merge startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } } else if (!(*it).split().isMatched()) { if (startMatchTransaction.id().isEmpty()) { startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } else { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } } } #if 0 KMergeTransactionsDlg dlg(d->m_selectedAccount); dlg.addTransaction(startMatchTransaction); dlg.addTransaction(endMatchTransaction); if (dlg.exec() == QDialog::Accepted) #endif { MyMoneyFileTransaction ft; try { if (startMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No manually entered transaction selected for matching")); if (endMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No imported transaction selected for matching")); TransactionMatcher matcher(d->m_selectedAccount); matcher.match(startMatchTransaction, startSplit, endMatchTransaction, endSplit, true); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to match the selected transactions"), e.what()); } } } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); QMenu *menu = dynamic_cast(w); if (menu) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotShowTransactionContextMenu() { if (d->m_selectedTransactions.isEmpty() && d->m_selectedSchedule != MyMoneySchedule()) { showContextMenu("schedule_context_menu"); } else { showContextMenu("transaction_context_menu"); } } void KMyMoneyApp::slotShowInvestmentContextMenu() { showContextMenu("investment_context_menu"); } void KMyMoneyApp::slotShowScheduleContextMenu() { showContextMenu("schedule_context_menu"); } void KMyMoneyApp::slotShowAccountContextMenu(const MyMoneyObject& obj) { // qDebug("KMyMoneyApp::slotShowAccountContextMenu"); if (typeid(obj) != typeid(MyMoneyAccount)) return; const MyMoneyAccount& acc = dynamic_cast(obj); // if the selected account is actually a stock account, we // call the right slot instead if (acc.isInvest()) { showContextMenu("investment_context_menu"); } else if (acc.isIncomeExpense()) { showContextMenu("category_context_menu"); } else { showContextMenu("account_context_menu"); } } void KMyMoneyApp::slotShowInstitutionContextMenu(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; showContextMenu("institution_context_menu"); } void KMyMoneyApp::slotShowPayeeContextMenu() { showContextMenu("payee_context_menu"); } void KMyMoneyApp::slotShowTagContextMenu() { showContextMenu("tag_context_menu"); } void KMyMoneyApp::slotShowBudgetContextMenu() { showContextMenu("budget_context_menu"); } void KMyMoneyApp::slotShowCurrencyContextMenu() { showContextMenu("currency_context_menu"); } void KMyMoneyApp::slotShowPriceContextMenu() { showContextMenu("price_context_menu"); } void KMyMoneyApp::slotShowOnlineJobContextMenu() { showContextMenu("onlinejob_context_menu"); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } void KMyMoneyApp::updateCaption(bool skipActions) { QString caption; caption = d->m_fileName.fileName(); if (caption.isEmpty() && d->m_myMoneyView && d->m_myMoneyView->fileOpen()) caption = i18n("Untitled"); // MyMoneyFile::instance()->dirty() throws an exception, if // there's no storage object available. In this case, we // assume that the storage object is not changed. Actually, // this can only happen if we are newly created early on. bool modified; try { modified = MyMoneyFile::instance()->dirty(); } catch (const MyMoneyException &) { modified = false; skipActions = true; } #ifdef KMM_DEBUG caption += QString(" (%1 x %2)").arg(width()).arg(height()); #endif setCaption(caption, modified); if (!skipActions) { d->m_myMoneyView->enableViewsIfFileOpen(); slotUpdateActions(); } } void KMyMoneyApp::slotUpdateActions() { MyMoneyFile* file = MyMoneyFile::instance(); const bool fileOpen = d->m_myMoneyView->fileOpen(); const bool modified = file->dirty(); - const bool importRunning = (d->m_qifReader != 0) || (d->m_smtReader != 0); + const bool importRunning = (d->m_smtReader != 0); QWidget* w; KActionCollection *aC = actionCollection(); // ************* // Disabling actions to be disabled at this point // ************* { static const QVector disabledActions { Action::AccountStartReconciliation, Action::AccountFinishReconciliation, Action::AccountPostponeReconciliation, Action::AccountEdit, Action::AccountDelete, Action::AccountOpen, Action::AccountClose, Action::AccountReopen, Action::AccountTransactionReport, Action::AccountOnlineMap, Action::AccountOnlineUnmap, Action::AccountUpdate, Action::AccountUpdateAll, Action::AccountBalanceChart, Action::CategoryEdit, Action::CategoryDelete, Action::InstitutionEdit, Action::InstitutionDelete, Action::InvestmentEdit, Action::InvestmentNew, Action::InvestmentDelete, Action::InvestmentOnlinePrice, Action::InvestmentManualPrice, Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleEnter, Action::ScheduleSkip, Action::PayeeDelete, Action::PayeeRename, Action::PayeeMerge, Action::TagDelete, Action::TagRename, Action::BudgetDelete, Action::BudgetRename, Action::BudgetChangeYear, Action::BudgetNew, Action::BudgetCopy, Action::BudgetForecast, Action::TransactionEdit, Action::TransactionEditSplits, Action::TransactionEnter, Action::TransactionCancel, Action::TransactionDelete, Action::TransactionMatch, Action::TransactionAccept, Action::TransactionDuplicate, Action::TransactionToggleReconciled, Action::TransactionToggleCleared, Action::TransactionGoToAccount, Action::TransactionGoToPayee, Action::TransactionAssignNumber, Action::TransactionCreateSchedule, Action::TransactionCombine, Action::TransactionSelectAll, Action::TransactionCopySplits, Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleDuplicate, Action::ScheduleEnter, Action::ScheduleSkip, Action::CurrencyRename, Action::CurrencyDelete, Action::CurrencySetBase, Action::PriceEdit, Action::PriceDelete, Action::PriceUpdate }; foreach (const auto a, disabledActions) aC->action(s_Actions.value(a))->setEnabled(false); } // ************* // Disabling actions based on conditions // ************* { QString tooltip = i18n("Create a new transaction"); const QVector> actionStates { {qMakePair(Action::FileOpenDatabase, true)}, {qMakePair(Action::FileSaveAsDatabase, fileOpen)}, {qMakePair(Action::FilePersonalData, fileOpen)}, {qMakePair(Action::FileBackup, (fileOpen && !d->m_myMoneyView->isDatabase()))}, {qMakePair(Action::FileInformation, fileOpen)}, - {qMakePair(Action::FileExportQIF, fileOpen && !importRunning)}, - {qMakePair(Action::FileImportQIF, fileOpen && !importRunning)}, {qMakePair(Action::FileImportGNC, !importRunning)}, {qMakePair(Action::FileImportTemplate, fileOpen && !importRunning)}, {qMakePair(Action::FileExportTemplate, fileOpen && !importRunning)}, #ifdef KMM_DEBUG {qMakePair(Action::FileDump, fileOpen)}, #endif {qMakePair(Action::EditFindTransaction, fileOpen)}, {qMakePair(Action::ToolCurrencies, fileOpen)}, {qMakePair(Action::ToolPrices, fileOpen)}, {qMakePair(Action::ToolUpdatePrices, fileOpen)}, {qMakePair(Action::ToolConsistency, fileOpen)}, {qMakePair(Action::AccountNew, fileOpen)}, {qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())}, {qMakePair(Action::CategoryDelete, fileOpen)}, {qMakePair(Action::InstitutionNew, fileOpen)}, {qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))}, {qMakePair(Action::ScheduleNew, fileOpen)}, {qMakePair(Action::CurrencyNew, fileOpen)}, {qMakePair(Action::PriceNew, fileOpen)}, }; foreach (const auto a, actionStates) aC->action(s_Actions.value(a.first))->setEnabled(a.second); } // ************* // Disabling standard actions based on conditions // ************* aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified && !d->m_myMoneyView->isDatabase()); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print)))->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for match transaction", "Match")); aC->action(s_Actions[Action::TransactionNew])->setToolTip(i18n("Create a new transaction")); w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(false); w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(false); w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(false); // ************* // Enabling actions based on conditions // ************* // FIXME for now it's always on, but we should only allow it, if we // can select at least a single transaction aC->action(s_Actions[Action::TransactionSelectAll])->setEnabled(true); if (!d->m_selectedTransactions.isEmpty()) { // enable 'delete transaction' only if at least one of the // selected transactions does not reference a closed account bool enable = false; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) { enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction()); } aC->action(s_Actions[Action::TransactionDelete])->setEnabled(enable); if (!d->m_transactionEditor) { QString tooltip = i18n("Duplicate the current selected transactions"); aC->action(s_Actions[Action::TransactionDuplicate])->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); aC->action(s_Actions[Action::TransactionDuplicate])->setToolTip(tooltip); if (d->m_myMoneyView->canEditTransactions(d->m_selectedTransactions, tooltip)) { aC->action(s_Actions[Action::TransactionEdit])->setEnabled(true); // editing splits is allowed only if we have one transaction selected if (d->m_selectedTransactions.count() == 1) { aC->action(s_Actions[Action::TransactionEditSplits])->setEnabled(true); } if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != MyMoneyAccount::Investment) { aC->action(s_Actions[Action::TransactionCreateSchedule])->setEnabled(d->m_selectedTransactions.count() == 1); } } aC->action(s_Actions[Action::TransactionEdit])->setToolTip(tooltip); if (!d->m_selectedAccount.isClosed()) { w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(true); } w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(true); w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(true); // Allow marking the transaction if at least one is selected aC->action(s_Actions[Action::TransactionToggleCleared])->setEnabled(true); aC->action(s_Actions[Action::TransactionReconciled])->setEnabled(true); aC->action(s_Actions[Action::TransactionNotReconciled])->setEnabled(true); aC->action(s_Actions[Action::TransactionToggleReconciled])->setEnabled(true); if (!d->m_accountGoto.isEmpty()) aC->action(s_Actions[Action::TransactionGoToAccount])->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) aC->action(s_Actions[Action::TransactionGoToPayee])->setEnabled(true); // Matching is enabled as soon as one regular and one imported transaction is selected int matchedCount = 0; int importedCount = 0; KMyMoneyRegister::SelectedTransactions::const_iterator it; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) ++importedCount; if ((*it).split().isMatched()) ++matchedCount; } if (d->m_selectedTransactions.count() == 2 /* && aC->action(s_Actions[Action::TransactionEdit])->isEnabled() */) { aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true); } if (importedCount != 0 || matchedCount != 0) aC->action(s_Actions[Action::TransactionAccept])->setEnabled(true); if (matchedCount != 0) { aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true); aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for unmatch transaction", "Unmatch")); aC->action(s_Actions[Action::TransactionMatch])->setIcon(QIcon("process-stop")); } if (d->m_selectedTransactions.count() > 1) { aC->action(s_Actions[Action::TransactionCombine])->setEnabled(true); } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true); } } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch(st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) { aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true); } } } else { aC->action(s_Actions[Action::TransactionAssignNumber])->setEnabled(d->m_transactionEditor->canAssignNumber()); aC->action(s_Actions[Action::TransactionNew])->setEnabled(false); aC->action(s_Actions[Action::TransactionDelete])->setEnabled(false); QString reason; aC->action(s_Actions[Action::TransactionEnter])->setEnabled(d->m_transactionEditor->isComplete(reason)); //FIXME: Port to KDE4 // the next line somehow worked in KDE3 but does not have // any influence under KDE4 /// Works for me when 'reason' is set. Allan aC->action(s_Actions[Action::TransactionEnter])->setToolTip(reason); aC->action(s_Actions[Action::TransactionCancel])->setEnabled(true); } } QList accList; file->accountList(accList); QList::const_iterator it_a; QMap::const_iterator it_p = d->m_onlinePlugins.constEnd(); for (it_a = accList.constBegin(); (it_p == d->m_onlinePlugins.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if (!(*it_a).onlineBankingSettings().value("provider").isEmpty()) { // check if provider is available it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { aC->action(s_Actions[Action::AccountUpdateAll])->setEnabled(true); aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true); } } } } MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { switch (d->m_selectedAccount.accountGroup()) { case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Equity: aC->action(s_Actions[Action::AccountTransactionReport])->setEnabled(true); aC->action(s_Actions[Action::AccountEdit])->setEnabled(true); aC->action(s_Actions[Action::AccountDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount)); aC->action(s_Actions[Action::AccountOpen])->setEnabled(true); if (d->m_selectedAccount.accountGroup() != MyMoneyAccount::Equity) { if (d->m_reconciliationAccount.id().isEmpty()) { aC->action(s_Actions[Action::AccountStartReconciliation])->setEnabled(true); aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(i18n("Reconcile")); } else { QString tip; tip = i18n("Reconcile - disabled because you are currently reconciling %1", d->m_reconciliationAccount.name()); aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(tip); if (!d->m_transactionEditor) { aC->action(s_Actions[Action::AccountFinishReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); aC->action(s_Actions[Action::AccountPostponeReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); } } } if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) aC->action(s_Actions[Action::InvestmentNew])->setEnabled(true); if (d->m_selectedAccount.isClosed()) aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedAccount); if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) { aC->action(s_Actions[Action::AccountOnlineUnmap])->setEnabled(true); // check if provider is available QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { aC->action(s_Actions[Action::AccountUpdate])->setEnabled(true); aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true); } } } else { aC->action(s_Actions[Action::AccountOnlineMap])->setEnabled(d->m_onlinePlugins.count() > 0); } aC->action(s_Actions[Action::AccountBalanceChart])->setEnabled(true); break; case MyMoneyAccount::Income : case MyMoneyAccount::Expense : aC->action(s_Actions[Action::CategoryEdit])->setEnabled(true); // enable delete action, if category/account itself is not referenced // by any object except accounts, because we want to allow // deleting of sub-categories. Also, we allow transactions, schedules and budgets // to be present because we can re-assign them during the delete process skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); skip.setBit(IMyMoneyStorage::RefCheckAccount); skip.setBit(IMyMoneyStorage::RefCheckSchedule); skip.setBit(IMyMoneyStorage::RefCheckBudget); aC->action(s_Actions[Action::CategoryDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount, skip)); aC->action(s_Actions[Action::AccountOpen])->setEnabled(true); break; default: break; } } } if (!d->m_selectedInstitution.id().isEmpty()) { aC->action(s_Actions[Action::InstitutionEdit])->setEnabled(true); aC->action(s_Actions[Action::InstitutionDelete])->setEnabled(!file->isReferenced(d->m_selectedInstitution)); } if (!d->m_selectedInvestment.id().isEmpty()) { aC->action(s_Actions[Action::InvestmentEdit])->setEnabled(true); aC->action(s_Actions[Action::InvestmentDelete])->setEnabled(!file->isReferenced(d->m_selectedInvestment)); aC->action(s_Actions[Action::InvestmentManualPrice])->setEnabled(true); try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); if (!security.value("kmm-online-source").isEmpty()) aC->action(s_Actions[Action::InvestmentOnlinePrice])->setEnabled(true); } catch (const MyMoneyException &e) { qDebug("Error retrieving security for investment %s: %s", qPrintable(d->m_selectedInvestment.name()), qPrintable(e.what())); } if (d->m_selectedInvestment.isClosed()) aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedInvestment); } if (!d->m_selectedSchedule.id().isEmpty()) { aC->action(s_Actions[Action::ScheduleEdit])->setEnabled(true); aC->action(s_Actions[Action::ScheduleDuplicate])->setEnabled(true); aC->action(s_Actions[Action::ScheduleDelete])->setEnabled(!file->isReferenced(d->m_selectedSchedule)); if (!d->m_selectedSchedule.isFinished()) { aC->action(s_Actions[Action::ScheduleEnter])->setEnabled(true); // a schedule with a single occurrence cannot be skipped if (d->m_selectedSchedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { aC->action(s_Actions[Action::ScheduleSkip])->setEnabled(true); } } } if (d->m_selectedPayees.count() >= 1) { aC->action(s_Actions[Action::PayeeRename])->setEnabled(d->m_selectedPayees.count() == 1); aC->action(s_Actions[Action::PayeeMerge])->setEnabled(d->m_selectedPayees.count() > 1); aC->action(s_Actions[Action::PayeeDelete])->setEnabled(true); } if (d->m_selectedTags.count() >= 1) { aC->action(s_Actions[Action::TagRename])->setEnabled(d->m_selectedTags.count() == 1); aC->action(s_Actions[Action::TagDelete])->setEnabled(true); } if (d->m_selectedBudgets.count() >= 1) { aC->action(s_Actions[Action::BudgetDelete])->setEnabled(true); if (d->m_selectedBudgets.count() == 1) { aC->action(s_Actions[Action::BudgetChangeYear])->setEnabled(true); aC->action(s_Actions[Action::BudgetCopy])->setEnabled(true); aC->action(s_Actions[Action::BudgetRename])->setEnabled(true); aC->action(s_Actions[Action::BudgetForecast])->setEnabled(true); } } if (!d->m_selectedCurrency.id().isEmpty()) { aC->action(s_Actions[Action::CurrencyRename])->setEnabled(true); // no need to check each transaction. accounts are enough in this case skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); aC->action(s_Actions[Action::CurrencyDelete])->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); if (d->m_selectedCurrency.id() != file->baseCurrency().id()) aC->action(s_Actions[Action::CurrencySetBase])->setEnabled(true); } if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != QLatin1String("KMyMoney")) { aC->action(s_Actions[Action::PriceEdit])->setEnabled(true); aC->action(s_Actions[Action::PriceDelete])->setEnabled(true); //enable online update if it is a currency MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedPrice.from()); aC->action(s_Actions[Action::PriceUpdate])->setEnabled(security.isCurrency()); } } void KMyMoneyApp::slotResetSelections() { slotSelectAccount(); slotSelectInstitution(); slotSelectInvestment(); slotSelectSchedule(); slotSelectCurrency(); slotSelectPrice(); slotSelectPayees(QList()); slotSelectTags(QList()); slotSelectBudget(QList()); slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); slotUpdateActions(); } void KMyMoneyApp::slotSelectCurrency() { slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotSelectCurrency(const MyMoneySecurity& currency) { d->m_selectedCurrency = currency; slotUpdateActions(); emit currencySelected(d->m_selectedCurrency); } void KMyMoneyApp::slotSelectPrice() { slotSelectPrice(MyMoneyPrice()); } void KMyMoneyApp::slotSelectPrice(const MyMoneyPrice& price) { d->m_selectedPrice = price; slotUpdateActions(); emit priceSelected(d->m_selectedPrice); } void KMyMoneyApp::slotSelectBudget(const QList& list) { d->m_selectedBudgets = list; slotUpdateActions(); emit budgetSelected(d->m_selectedBudgets); } void KMyMoneyApp::slotSelectPayees(const QList& list) { d->m_selectedPayees = list; slotUpdateActions(); emit payeesSelected(d->m_selectedPayees); } void KMyMoneyApp::slotSelectTags(const QList& list) { d->m_selectedTags = list; slotUpdateActions(); emit tagsSelected(d->m_selectedTags); } void KMyMoneyApp::slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list) { // list can either contain a list of transactions or a single selected scheduled transaction // in the latter case, the transaction id is actually the one of the schedule. In order // to differentiate between the two, we just ask for the schedule. If we don't find one - because // we passed the id of a real transaction - then we know that fact. We use the schedule here, // because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips // to the backend which we would have to do if we check for the transaction. d->m_selectedTransactions.clear(); d->m_selectedSchedule = MyMoneySchedule(); d->m_accountGoto.clear(); d->m_payeeGoto.clear(); if (!list.isEmpty() && !list.first().isScheduled()) { d->m_selectedTransactions = list; if (list.count() == 1) { const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); if (!sp.payeeId().isEmpty()) { try { MyMoneyPayee payee = MyMoneyFile::instance()->payee(sp.payeeId()); if (!payee.name().isEmpty()) { d->m_payeeGoto = payee.id(); QString name = payee.name(); name.replace(QRegExp("&(?!&)"), "&&"); actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to '%1'", name)); } } catch (const MyMoneyException &) { } } try { QList::const_iterator it_s; const MyMoneyTransaction& t = d->m_selectedTransactions[0].transaction(); // search the first non-income/non-expense accunt and use it for the 'goto account' const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if ((*it_s).id() != sp.id()) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (!acc.isIncomeExpense()) { // for stock accounts we show the portfolio account if (acc.isInvest()) { acc = MyMoneyFile::instance()->account(acc.parentAccountId()); } d->m_accountGoto = acc.id(); QString name = acc.name(); name.replace(QRegExp("&(?!&)"), "&&"); actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to '%1'", name)); break; } } } } catch (const MyMoneyException &) { } } slotUpdateActions(); emit transactionsSelected(d->m_selectedTransactions); } else if (!list.isEmpty()) { slotSelectSchedule(MyMoneyFile::instance()->schedule(list.first().scheduleId())); } else { slotUpdateActions(); } // make sure, we show some neutral menu entry if we don't have an object if (d->m_payeeGoto.isEmpty()) actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to payee")); if (d->m_accountGoto.isEmpty()) actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to account")); } void KMyMoneyApp::slotSelectInstitution() { slotSelectInstitution(MyMoneyInstitution()); } void KMyMoneyApp::slotSelectInstitution(const MyMoneyObject& institution) { if (typeid(institution) != typeid(MyMoneyInstitution)) return; d->m_selectedInstitution = dynamic_cast(institution); // qDebug("slotSelectInstitution('%s')", d->m_selectedInstitution.name().data()); slotUpdateActions(); emit institutionSelected(d->m_selectedInstitution); } void KMyMoneyApp::slotSelectAccount() { slotSelectAccount(MyMoneyAccount()); } void KMyMoneyApp::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; d->m_selectedAccount = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (!acc.isInvest()) d->m_selectedAccount = acc; // qDebug("slotSelectAccount('%s')", d->m_selectedAccount.name().data()); slotUpdateActions(); emit accountSelected(d->m_selectedAccount); } void KMyMoneyApp::slotSelectInvestment() { slotSelectInvestment(MyMoneyAccount()); } void KMyMoneyApp::slotSelectInvestment(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; // qDebug("slotSelectInvestment('%s')", account.name().data()); d->m_selectedInvestment = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (acc.isInvest()) d->m_selectedInvestment = acc; slotUpdateActions(); emit investmentSelected(d->m_selectedInvestment); } void KMyMoneyApp::slotSelectSchedule() { slotSelectSchedule(MyMoneySchedule()); } void KMyMoneyApp::slotSelectSchedule(const MyMoneySchedule& schedule) { // qDebug("slotSelectSchedule('%s')", schedule.name().data()); d->m_selectedSchedule = schedule; slotUpdateActions(); emit scheduleSelected(d->m_selectedSchedule); } void KMyMoneyApp::slotDataChanged() { // As this method is called every time the MyMoneyFile instance // notifies a modification, it's the perfect place to start the timer if needed if (d->m_autoSaveEnabled && !d->m_autoSaveTimer->isActive()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); //miliseconds } updateCaption(); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneySecurity)), this, SLOT(slotSelectCurrency(MyMoneySecurity))); connect(dlg, SIGNAL(openContextMenu(MyMoneySecurity)), this, SLOT(slotShowCurrencyContextMenu())); connect(this, SIGNAL(currencyRename()), dlg, SLOT(slotStartRename())); connect(dlg, SIGNAL(updateCurrency(QString,QString,QString)), this, SLOT(slotCurrencyUpdate(QString,QString,QString))); connect(this, SIGNAL(currencyCreated(QString)), dlg, SLOT(slotSelectCurrency(QString))); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotCurrencySetBase())); dlg->exec(); delete dlg; slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneyPrice)), this, SLOT(slotSelectPrice(MyMoneyPrice))); connect(dlg, SIGNAL(openContextMenu(MyMoneyPrice)), this, SLOT(slotShowPriceContextMenu())); connect(this, SIGNAL(priceNew()), dlg, SLOT(slotNewPrice())); connect(this, SIGNAL(priceEdit()), dlg, SLOT(slotEditPrice())); connect(this, SIGNAL(priceDelete()), dlg, SLOT(slotDeletePrice())); connect(this, SIGNAL(priceOnlineUpdate()), dlg, SLOT(slotOnlinePriceUpdate())); dlg->exec(); } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); updateCaption(); } void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult) { KMSTATUS(i18n("Running consistency check...")); MyMoneyFileTransaction ft; try { m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck(); ft.commit(); } catch (const MyMoneyException &e) { m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what())); // always display the result if the check failed alwaysDisplayResult = true; } // in case the consistency check was OK, we get a single line as result // in all errneous cases, we get more than one line and force the // display of them. if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) { QString msg = i18n("The consistency check has found no issues in your data. Details are presented below."); if (m_consistencyCheckResult.size() > 1) msg = i18n("The consistency check has found some issues in your data. Details are presented below. Those issues that could not be corrected automatically need to be solved by the user."); // install a context menu for the list after the dialog is displayed QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu())); KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result")); } // this data is no longer needed m_consistencyCheckResult.clear(); } void KMyMoneyApp::Private::copyConsistencyCheckResults() { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n"))); } void KMyMoneyApp::Private::saveConsistencyCheckResults() { 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::setCustomColors() { // setup the kmymoney custom colors if needed if (KMyMoneyGlobalSettings::useSystemColors()) { qApp->setStyleSheet(QString()); } else { qApp->setStyleSheet("QTreeView, QTableView#register, QTableView#m_register, QTableView#splittable, QListView { background-color: " + KMyMoneyGlobalSettings::listBGColor().name() + ';' + "alternate-background-color: " + KMyMoneyGlobalSettings::listColor().name() + ';' + "background-clip: content;}"); } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneyGlobalSettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneyGlobalSettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != KMyMoneyUtils::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != KMyMoneyUtils::Ignore && rc != KMyMoneyUtils::Cancel) { rc = enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == KMyMoneyUtils::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = KMyMoneyUtils::Enter; } } updateCaption(); } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = 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_fileName.url(); } 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 shange this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somwhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { #ifdef _MSC_VER uint thisProcPid = _getpid(); #else uint thisProcPid = getpid(); #endif if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id) { // // Web connect attempts to go through the known importers and see if the file // can be importing using that method. If so, it will import it using that // plugin // 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) { while (!d->m_importUrlsQueue.isEmpty()) { // get the value of the next item from the queue // but leave it on the queue for now QString url = d->m_importUrlsQueue.head(); // Bring this window to the forefront. This method was suggested by // Lubos Lunak of the KDE core development team. // TODO: port KF5 //KStartupInfo::setNewStartupId(this, asn_id); // Make sure we have an open file if (! d->m_myMoneyView->fileOpen() && KMessageBox::warningContinueCancel(kmymoney, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) kmymoney->slotFileOpen(); // only continue if the user really did open a file. if (d->m_myMoneyView->fileOpen()) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { QList statements; if (!(*it_plugin)->import(url)) { KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error")); } break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url)) slotStatementImport(url); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } } } void KMyMoneyApp::slotEnableMessages() { KMessageBox::enableAllMessages(); KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages")); } void KMyMoneyApp::createInterfaces() { // Sets up the plugin interface KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this, this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this, this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(this, d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::loadPlugins() { Q_ASSERT(!d->m_pluginLoader); d->m_pluginLoader = new KMyMoneyPlugin::PluginLoader(this); //! @todo Junior Job: Improve the config read system KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group{ config->group("Plugins") }; const auto plugins = KPluginLoader::findPlugins("kmymoney"); d->m_pluginLoader->addPluginInfo(plugins); for (const KPluginMetaData & pluginData : plugins) { // Only load plugins which are enabled and have the right serviceType. Other serviceTypes are loaded on demand. if (isPluginEnabled(pluginData, group)) slotPluginLoad(pluginData); } connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginEnabled, this, &KMyMoneyApp::slotPluginLoad); connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginDisabled, this, &KMyMoneyApp::slotPluginUnload); } void KMyMoneyApp::unloadPlugins() { Q_ASSERT(d->m_pluginLoader); delete d->m_pluginLoader; } inline bool KMyMoneyApp::isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup) { //! @fixme: there is a function in KMyMoneyPlugin::PluginLoader which has to have the same content if (metaData.serviceTypes().contains("KMyMoney/Plugin")) { const QString keyName{metaData.name() + "Enabled"}; if (configGroup.hasKey(keyName)) return configGroup.readEntry(keyName, true); return metaData.isEnabledByDefault(); } return false; } void KMyMoneyApp::slotPluginLoad(const KPluginMetaData& metaData) { std::unique_ptr loader{new QPluginLoader{metaData.fileName()}}; QObject* plugin = loader->instance(); if (!plugin) { qWarning("Could not load plugin '%s', error: %s", qPrintable(metaData.fileName()), qPrintable(loader->errorString())); return; } if ( d->m_plugins.contains(metaData.fileName()) ) { /** @fixme Handle a reload e.g. objectNames are equal but the object are different (plugin != d->m_plugins[plugin->objectName()]) * Also it could be usefull to drop the dependence on objectName() */ /* Note: there is nothing to delete here because if the plugin was loaded already, plugin points to the same object as the previously loaded one. */ return; } KMyMoneyPlugin::Plugin* kmmPlugin = qobject_cast(plugin); if (!kmmPlugin) { qWarning("Could not load plugin '%s'.", qPrintable(metaData.fileName())); return; } // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for extended online plugin KMyMoneyPlugin::OnlinePluginExtended* ope = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // Tell the plugin it is about to get plugged kmmPlugin->plug(); // plug the plugin guiFactory()->addClient(kmmPlugin); d->m_plugins[metaData.fileName()] = kmmPlugin; if (op) d->m_onlinePlugins[plugin->objectName()] = op; if (ope) onlineJobAdministration::instance()->addPlugin(plugin->objectName(), ope); if (ip) d->m_importerPlugins[plugin->objectName()] = ip; slotUpdateActions(); } void KMyMoneyApp::slotPluginUnload(const KPluginMetaData& metaData) { KMyMoneyPlugin::Plugin* plugin = d->m_plugins[metaData.fileName()]; // Remove and test if the plugin was actually loaded if (!d->m_plugins.remove(metaData.fileName()) || plugin == nullptr) return; // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // unplug the plugin guiFactory()->removeClient(plugin); if (op) d->m_onlinePlugins.remove(plugin->objectName()); if (ip) d->m_importerPlugins.remove(plugin->objectName()); plugin->unplug(); slotUpdateActions(); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->m_myMoneyView->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((dt.secsTo(nextDay) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } const MyMoneyAccount& KMyMoneyApp::account(const QString& key, const QString& value) const { QList list; QList::const_iterator it_a; MyMoneyFile::instance()->accountList(list); QString accId; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { // search in the account's kvp container const QString& accountKvpValue = (*it_a).value(key); // search in the account's online settings kvp container const QString& onlineSettingsKvpValue = (*it_a).onlineBankingSettings().value(key); if (accountKvpValue.contains(value) || onlineSettingsKvpValue.contains(value)) { if(accId.isEmpty()) { accId = (*it_a).id(); } } if (accountKvpValue == value || onlineSettingsKvpValue == value) { accId = (*it_a).id(); break; } } // return the account found or an empty element return MyMoneyFile::instance()->account(accId); } void KMyMoneyApp::setAccountOnlineParameters(const MyMoneyAccount& _acc, const MyMoneyKeyValueContainer& kvps) { MyMoneyFileTransaction ft; try { MyMoneyAccount acc = MyMoneyFile::instance()->account(_acc.id()); acc.setOnlineBankingSettings(kvps); MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to setup online parameters for account '%1'", _acc.name()), e.what()); } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_selectedAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_selectedAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_selectedAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_selectedAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", e.what())); } } } void KMyMoneyApp::slotAccountMapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // already an account mapped if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; // check if user tries to map a brokerageAccount if (d->m_selectedAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins.count()) { case 0: break; case 1: provider = d->m_onlinePlugins.begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins.constBegin(); it_p != d->m_onlinePlugins.constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins.constFind(provider); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider; MyMoneyAccount acc(d->m_selectedAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", e.what())); } } } } void KMyMoneyApp::slotAccountUpdateOnlineAll() { QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; d->m_statementResults.clear(); d->m_collectingStatements = true; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if ((*it_a).onlineBankingSettings().value("provider").isEmpty() || d->m_onlinePlugins.find((*it_a).onlineBankingSettings().value("provider")) == d->m_onlinePlugins.end()) { it_a = accList.erase(it_a); } else ++it_a; } QVector disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll}; foreach (const auto a, disabledActions) actionCollection()->action(s_Actions.value(a))->setEnabled(false); // now work on the remaining list of accounts int cnt = accList.count() - 1; for (it_a = accList.begin(); it_a != accList.end(); ++it_a) { it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); (*it_p)->updateAccount(*it_a, cnt != 0); --cnt; } d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotAccountUpdateOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // no online account mapped if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; QVector disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll}; foreach (const auto a, disabledActions) actionCollection()->action(s_Actions.value(a))->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it d->m_collectingStatements = true; d->m_statementResults.clear(); (*it_p)->updateAccount(d->m_selectedAccount); d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); } // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotNewOnlineTransfer() { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); if (!d->m_selectedAccount.id().isEmpty()) { transferForm->setCurrentAccount(d->m_selectedAccount.id()); } connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotEditOnlineJob(const QString jobId) { try { const onlineJob constJob = MyMoneyFile::instance()->getOnlineJob(jobId); slotEditOnlineJob(constJob); } catch (MyMoneyException&) { // Prevent a crash in very rare cases } } void KMyMoneyApp::slotEditOnlineJob(onlineJob job) { try { slotEditOnlineJob(onlineJobTyped(job)); } catch (MyMoneyException&) { } } void KMyMoneyApp::slotEditOnlineJob(const onlineJobTyped job) { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); transferForm->setOnlineJob(job); connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotOnlineJobSave(onlineJob job) { MyMoneyFileTransaction fileTransaction; if (job.id() == MyMoneyObject::emptyId()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); } /** @todo when onlineJob queue is used, continue here */ void KMyMoneyApp::slotOnlineJobSend(onlineJob job) { MyMoneyFileTransaction fileTransaction; if (job.id() == MyMoneyObject::emptyId()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); QList jobList; jobList.append(job); slotOnlineJobSend(jobList); } void KMyMoneyApp::slotOnlineJobSend(QList jobs) { MyMoneyFile *const kmmFile = MyMoneyFile::instance(); QMultiMap jobsByPlugin; // Sort jobs by online plugin & lock them foreach (onlineJob job, jobs) { Q_ASSERT(job.id() != MyMoneyObject::emptyId()); // find the provider const MyMoneyAccount originAcc = job.responsibleMyMoneyAccount(); job.setLock(); job.addJobMessage(onlineJobMessage(onlineJobMessage::debug, "KMyMoneyApp::slotOnlineJobSend", "Added to queue for plugin '" + originAcc.onlineBankingSettings().value("provider") + '\'')); MyMoneyFileTransaction fileTransaction; kmmFile->modifyOnlineJob(job); fileTransaction.commit(); jobsByPlugin.insert(originAcc.onlineBankingSettings().value("provider"), job); } // Send onlineJobs to plugins QList usedPlugins = jobsByPlugin.keys(); std::sort(usedPlugins.begin(), usedPlugins.end()); const QList::iterator newEnd = std::unique(usedPlugins.begin(), usedPlugins.end()); usedPlugins.erase(newEnd, usedPlugins.end()); foreach (const QString& pluginKey, usedPlugins) { QMap::const_iterator it_p = d->m_onlinePlugins.constFind(pluginKey); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it KMyMoneyPlugin::OnlinePluginExtended *pluginExt = dynamic_cast< KMyMoneyPlugin::OnlinePluginExtended* >(*it_p); if (pluginExt == 0) { qWarning("Job given for plugin which is not an extended plugin"); continue; } //! @fixme remove debug message qDebug() << "Sending " << jobsByPlugin.count(pluginKey) << " job(s) to online plugin " << pluginKey; QList jobsToExecute = jobsByPlugin.values(pluginKey); QList executedJobs = jobsToExecute; pluginExt->sendOnlineJob(executedJobs); // Save possible changes of the online job and remove lock MyMoneyFileTransaction fileTransaction; foreach (onlineJob job, executedJobs) { fileTransaction.restart(); job.setLock(false); kmmFile->modifyOnlineJob(job); fileTransaction.commit(); } if (Q_UNLIKELY(executedJobs.size() != jobsToExecute.size())) { // OnlinePlugin did not return all jobs qWarning() << "Error saving send online tasks. After restart you should see at minimum all successfully executed jobs marked send. Imperfect plugin: " << pluginExt->objectName(); } } else { qWarning() << "Error, got onlineJob for an account without online plugin."; /** @FIXME can this actually happen? */ } } } void KMyMoneyApp::slotRemoveJob() { } void KMyMoneyApp::slotEditJob() { } void KMyMoneyApp::slotOnlineJobLog() { QStringList jobIds = d->m_myMoneyView->getOnlineJobOutbox()->selectedOnlineJobs(); slotOnlineJobLog(jobIds); } void KMyMoneyApp::slotOnlineJobLog(const QStringList& onlineJobIds) { onlineJobMessagesView *const dialog = new onlineJobMessagesView(); onlineJobMessagesModel *const model = new onlineJobMessagesModel(dialog); model->setOnlineJob(MyMoneyFile::instance()->getOnlineJob(onlineJobIds.first())); dialog->setModel(model); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); // Note: Objects are not deleted here, Qt's parent-child system has to do that. } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef KF5Holidays_FOUND //since the cost of updating the cache is now not negligible //check whether the region has been modified if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) { // Delete the previous holidayRegion before creating a new one. delete d->m_holidayRegion; // Create a new holidayRegion. d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion); //clear and update the holiday cache preloadHolidays(); } #else Q_UNUSED(holidayRegion); #endif } bool KMyMoneyApp::isProcessingDate(const QDate& date) const { #ifdef KF5Holidays_FOUND if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; if (!d->m_holidayRegion || !d->m_holidayRegion->isValid()) return true; //check first whether it's already in cache if (d->m_holidayMap.contains(date)) { return d->m_holidayMap.value(date, true); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else Q_UNUSED(date); return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef KF5Holidays_FOUND //clear the cache before loading d->m_holidayMap.clear(); //only do this if it is a valid region if (d->m_holidayRegion && d->m_holidayRegion->isValid()) { //load holidays for the forecast days plus 1 cycle, to be on the safe side int forecastDays = KMyMoneyGlobalSettings::forecastDays() + KMyMoneyGlobalSettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); //look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { 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 } KMStatus::KMStatus(const QString &text) { m_prevText = kmymoney->slotStatusMsg(text); } KMStatus::~KMStatus() { kmymoney->slotStatusMsg(m_prevText); } void KMyMoneyApp::Private::unlinkStatementXML() { QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); } m_statementXMLindex = 0; } void KMyMoneyApp::Private::closeFile() { q->slotSelectAccount(); q->slotSelectInstitution(); q->slotSelectInvestment(); q->slotSelectSchedule(); q->slotSelectCurrency(); q->slotSelectBudget(QList()); q->slotSelectPayees(QList()); q->slotSelectTags(QList()); q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(m_reconciliationAccount); m_myMoneyView->closeFile(); m_fileName = QUrl(); q->updateCaption(); // just create a new balance warning object delete m_balanceWarning; m_balanceWarning = new KBalanceWarning(q); emit q->fileLoaded(m_fileName); } diff --git a/kmymoney/kmymoney.h b/kmymoney/kmymoney.h index 7dd766335..3f781d80a 100644 --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -1,1496 +1,1457 @@ /*************************************************************************** kmymoney.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEY_H #define KMYMONEY_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include "mymoneyschedule.h" #include #include #include #include #include #include #include #include #include "mymoney/onlinejob.h" #include "mymoney/onlinejobtyped.h" #include "onlinetasks/interfaces/tasks/credittransfer.h" class QResizeEvent; class QTreeWidgetItem; class KPluginInfo; enum class Action { FileOpenDatabase, FileSaveAsDatabase, FileBackup, - FileImportGNC, FileImportQIF, FileExportQIF, + FileImportGNC, FileImportStatement, FileImportTemplate, FileExportTemplate, #ifdef KMM_DEBUG FileDump, #endif FilePersonalData, FileInformation, EditFindTransaction, ViewTransactionDetail, ViewHideReconciled, ViewHideCategories, ViewShowAll, InstitutionNew, InstitutionEdit, InstitutionDelete, AccountNew, AccountOpen, AccountStartReconciliation, AccountFinishReconciliation, AccountPostponeReconciliation, AccountEdit, AccountDelete, AccountClose, AccountReopen, AccountTransactionReport, AccountBalanceChart, AccountUpdateMenu, AccountOnlineMap, AccountOnlineUnmap, AccountUpdate, AccountUpdateAll, AccountCreditTransfer, CategoryNew, CategoryEdit, CategoryDelete, - ToolQIF, ToolCurrencies, + ToolCurrencies, ToolPrices, ToolUpdatePrices, ToolConsistency, ToolPerformance, ToolSQL, ToolCalculator, SettingsAllMessages, SettingsLanguage, HelpShow, TransactionNew, TransactionEdit, TransactionEnter, TransactionEditSplits, TransactionCancel, TransactionDelete, TransactionDuplicate, TransactionMatch, TransactionAccept, TransactionToggleReconciled, TransactionToggleCleared, TransactionReconciled, TransactionNotReconciled, TransactionSelectAll, TransactionGoToAccount, TransactionGoToPayee, TransactionCreateSchedule, TransactionAssignNumber, TransactionCombine, TransactionCopySplits, TransactionMoveMenu, TransactionMarkMenu, TransactionContextMarkMenu, InvestmentNew, InvestmentEdit, InvestmentDelete, InvestmentOnlinePrice, InvestmentManualPrice, ScheduleNew, ScheduleEdit, ScheduleDelete, ScheduleDuplicate, ScheduleEnter, ScheduleSkip, PayeeNew, PayeeRename, PayeeDelete, PayeeMerge, TagNew, TagRename, TagDelete, BudgetNew, BudgetRename, BudgetDelete, BudgetCopy, BudgetChangeYear, BudgetForecast, CurrencyNew, CurrencyRename, CurrencyDelete, CurrencySetBase, PriceNew, PriceDelete, PriceUpdate, PriceEdit, #ifdef KMM_DEBUG WizardNewUser, DebugTraces, #endif DebugTimers, OnlineJobDelete, OnlineJobEdit, OnlineJobLog }; inline uint qHash(const Action key, uint seed) { return ::qHash(static_cast(key), seed); } /*! \mainpage KMyMoney Main Page for API documentation. * * \section intro Introduction * * This is the API documentation for KMyMoney. It should be used as a reference * for KMyMoney developers and users who wish to see how KMyMoney works. This * documentation will be kept up-to-date as development progresses and should be * read for new features that have been developed in KMyMoney. */ /** * The base class for KMyMoney application windows. It sets up the main * window and reads the config file as well as providing a menubar, toolbar * and statusbar. * * @see KMyMoneyView * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2006-2008 * * @short Main application class. */ class KMyMoneyApp : public KXmlGuiWindow, public IMyMoneyProcessingCalendar { Q_OBJECT private slots: /** * Keep track of objects that are destroyed by external events */ void slotObjectDestroyed(QObject* o); /** * Add a context menu to the list used by KMessageBox::informationList to display the consistency check results. */ void slotInstallConsistencyCheckContextMenu(); /** * Handle the context menu of the list used by KMessageBox::informationList to display the consistency check results. */ void slotShowContextMenuForConsistencyCheck(const QPoint &); protected slots: void slotFileSaveAsFilterChanged(const QString& filter); /** * This slot is intended to be used as part of auto saving. This is used when the * QTimer emits the timeout signal and simply checks that the file is dirty (has * received modifications to its contents), and call the appropriate method to * save the file. Furthermore, re-starts the timer (possibly not needed). * @author mvillarino 2005 * @see KMyMoneyApp::slotDataChanged() */ void slotAutoSave(); /** * This slot re-enables all message for which the "Don't show again" * option had been selected. */ void slotEnableMessages(); /** * Called when the user asks for file information. */ void slotFileFileInfo(); /** * Called to run performance test. */ void slotPerformanceTest(); /** * Called to generate the sql to create kmymoney database tables etc. */ void slotGenerateSql(); #ifdef KMM_DEBUG /** * Debugging only: turn on/off traces */ void slotToggleTraces(); #endif /** * Debugging only: turn on/off timers */ void slotToggleTimers(); /** * Called when the user asks for the personal information. */ void slotFileViewPersonal(); - /** - * Called when the user wishes to import tab delimeted transactions - * into the current account. An account must be open for this to - * work. Calls KMyMoneyView::slotAccountImportAscii. - * - * @see MyMoneyAccount - */ - void slotQifImport(); - - /** - * Called when a QIF import is finished. - */ - void slotQifImportFinished(); - /** * Opens a file selector dialog for the user to choose an existing OFX * file from the file system to be imported. This slot is expected to * be called from the UI. */ void slotGncImport(); /** * Open a dialog with a chart of the balance for the currently selected * account (m_selectedAccount). Return once the dialog is closed. Don't do * anything if no account is selected or charts are not available. */ void slotAccountChart(); /** * Opens a file selector dialog for the user to choose an existing KMM * statement file from the file system to be imported. This is for testing * only. KMM statement files are not designed to be exposed to the user. */ void slotStatementImport(); void slotLoadAccountTemplates(); void slotSaveAccountTemplates(); - /** - * Called when the user wishes to export some transaction to a - * QIF formatted file. An account must be open for this to work. - * Uses MyMoneyQifWriter() for the actual output. - */ - void slotQifExport(); - /** * Open up the application wide settings dialog. * * @see KSettingsDlg */ void slotSettings(); /** * Called to show credits window. */ void slotShowCredits(); /** * Called when the user wishes to backup the current file */ void slotBackupFile(); /** * Perform mount operation before making a backup of the current file */ void slotBackupMount(); /** * Perform the backup write operation */ bool slotBackupWriteFile(); /** * Perform unmount operation after making a backup of the current file */ void slotBackupUnmount(); /** * Finish backup of the current file */ void slotBackupFinish(); /** * Handle events on making a backup of the current file */ void slotBackupHandleEvents(); void slotShowTipOfTheDay(); - void slotQifProfileEditor(); - void slotShowPreviousView(); void slotShowNextView(); /** * Brings up a dialog to let the user search for specific transaction(s). It then * opens a results window to display those transactions. */ void slotFindTransaction(); /** * Destroys a possibly open the search dialog */ void slotCloseSearchDialog(); /** * Preloads the input dialog with the data of the current * selected institution and brings up the input dialog * and saves the information entered. */ void slotInstitutionEdit(); void slotInstitutionEdit(const MyMoneyObject &obj); /** * Deletes the current selected institution. */ void slotInstitutionDelete(); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name. The parent defaults to * MyMoneyFile::expense() * * @param name Name of the account to be created. Could include a full hierarchy * @param id reference to storage which will receive the id after successful creation * * @note Typically, this slot can be connected to the * StdTransactionEditor::createCategory(const QString&, QString&) or * KMyMoneyCombo::createItem(const QString&, QString&) signal. */ void slotCategoryNew(const QString& name, QString& id); /** * Calls the print logic for the current view */ void slotPrintView(); /** * Create a new investment */ void slotInvestmentNew(); /** * Create a new investment in a given @p parent investment account */ void slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This slot opens the investment editor to edit the currently * selected investment if possible */ void slotInvestmentEdit(); /** * Deletes the current selected investment. */ void slotInvestmentDelete(); /** * Performs online update for currently selected investment */ void slotOnlinePriceUpdate(); /** * Performs manual update for currently selected investment */ void slotManualPriceUpdate(); /** * Call this slot, if any configuration parameter has changed */ void slotUpdateConfiguration(); /** */ bool slotPayeeNew(const QString& newnameBase, QString& id); void slotPayeeNew(); /** */ void slotPayeeDelete(); /** * Slot that merges two or more selected payess into a new payee */ void slotPayeeMerge(); /** */ void slotTagNew(const QString& newnameBase, QString& id); void slotTagNew(); /** */ void slotTagDelete(); /** */ void slotBudgetNew(); /** */ void slotBudgetDelete(); /** */ void slotBudgetCopy(); /** */ void slotBudgetChangeYear(); /** */ void slotBudgetForecast(); /** */ void slotCurrencyNew(); /** */ void slotCurrencyUpdate(const QString ¤cyId, const QString& currencyName, const QString& currencyTradingSymbol); /** */ void slotCurrencyDelete(); /** */ void slotCurrencySetBase(); /** * This slot is used to start new features during the development cycle */ void slotNewFeature(); /** */ void slotTransactionsNew(); /** */ void slotTransactionsEdit(); /** */ void slotTransactionsEditSplits(); /** */ void slotTransactionsDelete(); /** */ void slotTransactionsEnter(); /** */ void slotTransactionsCancel(); /** */ void slotTransactionsCancelOrEnter(bool& okToSelect); /** */ void slotTransactionDuplicate(); /** */ void slotToggleReconciliationFlag(); /** */ void slotMarkTransactionCleared(); /** */ void slotMarkTransactionReconciled(); /** */ void slotMarkTransactionNotReconciled(); /** */ void slotTransactionGotoAccount(); /** */ void slotTransactionGotoPayee(); /** */ void slotTransactionCreateSchedule(); /** */ void slotTransactionAssignNumber(); /** */ void slotTransactionCombine(); /** * This method takes the selected splits and checks that only one transaction (src) * has more than one split and all others have only a single one. It then copies the * splits of the @b src transaction to all others. */ void slotTransactionCopySplits(); /** * Accept the selected transactions that are marked as 'imported' and remove the flag */ void slotTransactionsAccept(); /** * This slot triggers an update of all views and restarts * a single shot timer to call itself again at beginning of * the next day. */ void slotDateChanged(); /** * This slot will be called when the engine data changed * and the application object needs to update its state. */ void slotDataChanged(); void slotMoveToAccount(const QString& id); void slotUpdateMoveToAccountMenu(); /** * This slot collects information for a new scheduled transaction * based on transaction @a t and @a occurrence and saves it in the engine. */ void slotScheduleNew(const MyMoneyTransaction& t, MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_MONTHLY); /** */ void slotScheduleDuplicate(); void slotKDELanguageSettings(); void slotAccountMapOnline(); void slotAccountUnmapOnline(); void slotAccountUpdateOnline(); void slotAccountUpdateOnlineAll(); /** * @brief Start dialog for an online banking transfer */ void slotNewOnlineTransfer(); /** * @brief Start dialog to edit onlineJob if possible * @param onlineJob id to edit */ void slotEditOnlineJob(const QString); /** * @brief Start dialog to edit onlineJob if possible */ void slotEditOnlineJob(const onlineJob); /** * @brief Start dialog to edit onlineJob if possible */ void slotEditOnlineJob(const onlineJobTyped); /** * @brief Saves an online banking job */ void slotOnlineJobSave(onlineJob job); /** * @brief Queue an online banking job */ void slotOnlineJobSend(onlineJob job); /** * @brief Send a list of onlineJobs */ void slotOnlineJobSend(QList jobs); /** * dummy method needed just for initialization */ void slotRemoveJob(); void slotEditJob(); /** * @brief Show the log currently selected online job */ void slotOnlineJobLog(); void slotOnlineJobLog(const QStringList& onlineJobIds); void slotManageGpgKeys(); void slotKeySelected(int idx); void slotStatusProgressDone(); public: /** * This method checks if there is at least one asset or liability account * in the current storage object. If not, it starts the new account wizard. */ void createInitialAccount(); /** * This method returns the last URL used or an empty URL * depending on the option setting if the last file should * be opened during startup or the open file dialog should * be displayed. * * @return URL of last opened file or empty if the program * should start with the open file dialog */ QUrl lastOpenedURL(); /** * construtor of KMyMoneyApp, calls all init functions to create the application. */ explicit KMyMoneyApp(QWidget* parent = 0); /** * Destructor */ ~KMyMoneyApp(); static void progressCallback(int current, int total, const QString&); void writeLastUsedDir(const QString& directory); QString readLastUsedDir() const; void writeLastUsedFile(const QString& fileName); QString readLastUsedFile() const; /** * Returns whether there is an importer available that can handle this file */ bool isImportableFile(const QUrl &url); /** * This method is used to update the caption of the application window. * It sets the caption to "filename [modified] - KMyMoney". * * @param skipActions if true, the actions will not be updated. This * is usually onyl required by some early calls when * these widgets are not yet created (the default is false). */ void updateCaption(bool skipActions = false); /** * This method returns a list of all 'other' dcop registered kmymoney processes. * It's a subset of the return of DCOPclient()->registeredApplications(). * * @retval QStringList of process ids */ QList instanceList() const; #ifdef KMM_DEBUG /** * Dump a list of the names of all defined KActions to stdout. */ void dumpActions() const; #endif /** * Popup the context menu with the respective @p containerName. * Valid container names are defined in kmymoneyui.rc */ void showContextMenu(const QString& containerName); /** * This method opens the category editor with the data found in @a account. The * parent account is preset to @a parent but can be modified. If the user * acknowledges, the category is created. */ void createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This method returns the account for a given @a key - @a value pair. * If the account is not found in the list of accounts, MyMoneyAccount() * is returned. The @a key - @a value pair can be in the account's kvp * container or the account's online settings kvp container. */ const MyMoneyAccount& account(const QString& key, const QString& value) const; /** * This method set the online parameters stored in @a kvps with the * account referenced by @a acc. */ void setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps); QUrl selectFile(const QString& title, const QString& path, const QString& mask, QFileDialog::FileMode, QWidget *widget); - /** - * This methods returns the account from the list of accounts identified by - * an account id or account name including an account hierachy. - * - * The parent account specifies from which account the search should be started. - * In case the parent account does not have an id, the method scans all top-level accounts. - * - * If the account is not found in the list of accounts, MyMoneyAccount() is returned. - * - * @param acc account to find - * @param parent parent account to search from - * @retval found MyMoneyAccount account instance - * @retval MyMoneyAccount() if not found - */ - const MyMoneyAccount& findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const; - void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); QString filename() const; void webConnect(const QString& sourceUrl, const QByteArray& asn_id); /** * Checks if the file with the @a url already exists. If so, * the user is asked if he/she wants to override the file. * If the user's answer is negative, @p false will be returned. * @p true will be returned in all other cases. */ bool okToWriteFile(const QUrl &url); protected: /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file */ void saveOptions(); /** * Creates the interfaces necessary for the plugins to work. Therefore, * this method must be called prior to loadPlugins(). */ void createInterfaces(); /** * load all available plugins. Make sure you have called createInterfaces() * before you call this one. */ void loadPlugins(); /** * unload all available plugins. Make sure you have called loadPlugins() * before you call this one. */ void unloadPlugins(); /** * @brief Checks if the given plugin is loaded on start up * * This filters plugins which are loaded on demand only and deactivated plugins. * The configGroup must point to the correct group already. */ bool isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup); /** * read general options again and initialize all variables like the recent file list */ void readOptions(); /** initializes the KActions of the application */ void initActions(); /** initializes the dynamic menus (account selectors) */ void initDynamicMenus(); /** * sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** * @brief Establish connections between actions and views * * Must be called after creation of actions and views. */ void connectActionsAndViews(); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calls saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * The settings are saved using saveOptions() if we are about to close. * @see KMainWindow#queryClose * @see QWidget#closeEvent */ virtual bool queryClose(); void slotCheckSchedules(); virtual void resizeEvent(QResizeEvent*); void createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount); /** * This method checks, if an account can be closed or not. An account * can be closed if: * * - the balance is zero and * - all children are already closed and * - there is no unfinished schedule referencing the account * * @param acc reference to MyMoneyAccount object in question * @retval true account can be closed * @retval false account cannot be closed */ KMyMoneyUtils::CanCloseAccountCodeE canCloseAccount(const MyMoneyAccount& acc) const; /** * This method checks if an account can be closed and enables/disables * the close account action * If disabled, it sets a tooltip explaning why it cannot be closed * @brief enableCloseAccountAction * @param acc reference to MyMoneyAccount object in question */ void enableCloseAccountAction(const MyMoneyAccount& acc); /** * Check if a list contains a payee with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool payeeInList(const QList& list, const QString& id) const; /** * Check if a list contains a tag with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool tagInList(const QList& list, const QString& id) const; /** * Mark the selected transactions as provided by @a flag. If * flag is @a MyMoneySplit::Unknown, the future state depends * on the current stat of the split's flag accoring to the * following table: * * - NotReconciled --> Cleared * - Cleared --> Reconciled * - Reconciled --> NotReconciled */ void markTransaction(MyMoneySplit::reconcileFlagE flag); /** * This method allows to skip the next scheduled transaction of * the given schedule @a s. * */ void skipSchedule(MyMoneySchedule& s); /** * This method allows to enter the next scheduled transaction of * the given schedule @a s. In case @a extendedKeys is @a true, * the given schedule can also be skipped or ignored. * If @a autoEnter is @a true and the schedule does not contain * an estimated value, the schedule is entered as is without further * interaction with the user. In all other cases, the user will * be presented a dialog and allowed to adjust the values for this * instance of the schedule. * * The transaction will be created and entered into the ledger * and the schedule updated. */ KMyMoneyUtils::EnterScheduleResultCodeE enterSchedule(MyMoneySchedule& s, bool autoEnter = false, bool extendedKeys = false); /** * Creates a new institution entry in the MyMoneyFile engine * * @param institution MyMoneyInstitution object containing the data of * the institution to be created. */ void createInstitution(MyMoneyInstitution& institution); /** * This method unmatches the currently selected transactions */ void transactionUnmatch(); /** * This method matches the currently selected transactions */ void transactionMatch(); /** * This method preloads the holidays for the duration of the default forecast period */ void preloadHolidays(); public slots: void slotFileInfoDialog(); /** */ void slotFileNew(); /** open a file and load it into the document*/ void slotFileOpen(); /** opens a file from the recent files menu */ void slotFileOpenRecent(const QUrl &url); /** open a SQL database */ void slotOpenDatabase(); /** * saves the current document. If it has no name yet, the user * will be queried for it. * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSave(); /** * ask the user for the filename and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSaveAs(); /** * ask the user to select a database and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool saveAsDatabase(); void slotSaveAsDatabase(); /** asks for saving if the file is modified, then closes the actual file and window */ void slotFileCloseWindow(); /** asks for saving if the file is modified, then closes the actual file */ void slotFileClose(); /** * closes all open windows by calling close() on each memberList item * until the list is empty, then quits the application. * If queryClose() returns false because the user canceled the * saveModified() dialog, the closing breaks. */ void slotFileQuit(); void slotFileConsistencyCheck(); /** * fires up the price table editor */ void slotPriceDialog(); /** * fires up the currency table editor */ void slotCurrencyDialog(); /** * dummy method needed just for initialization */ void slotShowTransactionDetail(); /** * Toggles the hide reconciled transactions setting */ void slotHideReconciledTransactions(); /** * Toggles the hide unused categories setting */ void slotHideUnusedCategories(); /** * Toggles the show all accounts setting */ void slotShowAllAccounts(); /** * changes the statusbar contents for the standard label permanently, * used to indicate current actions. Returns the previous value for * 'stacked' usage. * * @param text the text that is displayed in the statusbar */ QString slotStatusMsg(const QString &text); /** * This method changes the progress bar in the status line according * to the parameters @p current and @p total. The following special * cases exist: * * - current = -1 and total = -1 will reset the progress bar * - current = ?? and total != 0 will setup the 100% mark to @p total * - current = xx and total == 0 will set the percentage * * @param current the current value with respect to the initialised * 100% mark * @param total the total value (100%) */ void slotStatusProgressBar(int current, int total = 0); /** * Called to update stock and currency prices from the user menu */ void slotEquityPriceUpdate(); /** * Imports a KMM statement into the engine, triggering the appropriate * UI to handle account matching, payee creation, and someday * payee and transaction matching. */ bool slotStatementImport(const MyMoneyStatement& s, bool silent = false); /** * Essentially similar to the above slot, except this will load the file * from disk first, given the URL. */ bool slotStatementImport(const QString& url); /** * This slot starts the reconciliation of the currently selected account */ void slotAccountReconcileStart(); /** * This slot finishes a previously started reconciliation */ void slotAccountReconcileFinish(); /** * This slot postpones a previously started reconciliations */ void slotAccountReconcilePostpone(); /** * This slot deletes the currently selected account if possible */ void slotAccountDelete(); /** * This slot opens the account editor to edit the currently * selected account if possible */ void slotAccountEdit(); /** * This slot opens the selected account in the ledger view */ void slotAccountOpen(); void slotAccountOpen(const MyMoneyObject&); /** * This slot closes the currently selected account if possible */ void slotAccountClose(); /** * This slot re-openes the currently selected account if possible */ void slotAccountReopen(); /** * This slot reparents account @p src to be a child of account @p dest * * @param src account to be reparented * @param dest new parent */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyAccount& dest); /** * This slot reparents account @p src to be a held at institution @p dest * * @param src account to be reparented * @param dest new parent institution */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyInstitution& dest); /** * This slot creates a transaction report for the selected account * and opens it in the reports view. */ void slotAccountTransactionReport(); /** * This slot opens the account options menu at the current cursor * position. */ void slotShowAccountContextMenu(const MyMoneyObject&); /** * This slot opens the schedule options menu at the current cursor * position. */ void slotShowScheduleContextMenu(); /** * This slot opens the institution options menu at the current cursor * position. */ void slotShowInstitutionContextMenu(const MyMoneyObject&); /** * This slot opens the investment options menu at the current cursor * position. */ void slotShowInvestmentContextMenu(); /** * This slot opens the payee options menu at the current cursor * position. */ void slotShowPayeeContextMenu(); /** * This slot opens the tag options menu at the current cursor * position. */ void slotShowTagContextMenu(); /** * This slot opens the budget options menu at the current cursor * position. */ void slotShowBudgetContextMenu(); /** * This slot opens the transaction options menu at the current cursor * position. */ void slotShowTransactionContextMenu(); /** * This slot opens the currency options menu at the current cursor * position. */ void slotShowCurrencyContextMenu(); /** * This slot opens the price options menu at the current cursor * position. */ void slotShowPriceContextMenu(); /** * Open onlineJob options menu at current cursor position. */ void slotShowOnlineJobContextMenu(); /** * This slot collects information for a new scheduled transaction * and saves it in the engine. @sa slotScheduleNew(const MyMoneyTransaction&) */ void slotScheduleNew(); /** * This slot allows to edit information the currently selected schedule */ void slotScheduleEdit(); /** * This slot allows to delete the currently selected schedule */ void slotScheduleDelete(); /** * This slot allows to enter the next scheduled transaction of * the currently selected schedule */ void slotScheduleEnter(); /** * This slot allows to skip the next scheduled transaction of * the currently selected schedule */ void slotScheduleSkip(); /** * This slot fires up the KCalc application */ void slotToolsStartKCalc(); void slotResetSelections(); void slotSelectAccount(); void slotSelectAccount(const MyMoneyObject& account); void slotSelectInstitution(); void slotSelectInstitution(const MyMoneyObject& institution); void slotSelectInvestment(); void slotSelectInvestment(const MyMoneyObject& account); void slotSelectSchedule(); void slotSelectSchedule(const MyMoneySchedule& schedule); void slotSelectPayees(const QList& list); void slotSelectTags(const QList& list); void slotSelectBudget(const QList& list); void slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list); void slotSelectCurrency(); void slotSelectCurrency(const MyMoneySecurity& currency); void slotSelectPrice(); void slotSelectPrice(const MyMoneyPrice& price); void slotTransactionMatch(); /** * Brings up the new account wizard and saves the information. */ void slotAccountNew(); void slotAccountNew(MyMoneyAccount&); /** * Brings up the new category editor and saves the information. */ void slotCategoryNew(); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name and parent account. * * @param account reference of category to be created. The @p name member * should be filled by the caller. The object will be filled * with additional information during the creation process * esp. the @p id member. * @param parent reference to parent account (defaults to none) */ void slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent = MyMoneyAccount()); /** * This method updates all KAction items to the current state. */ void slotUpdateActions(); /** * Brings up the input dialog and saves the information. */ void slotInstitutionNew(); /** * Brings up the input dialog and saves the information. If * the institution has been created, the @a id member is filled, * otherwise it is empty. * * @param institution reference to data to be used to create the * institution. id member will be updated. */ void slotInstitutionNew(MyMoneyInstitution& institution); /** * Loads a plugin */ void slotPluginLoad(const KPluginMetaData& metaData); /** * Unloads a plugin */ void slotPluginUnload(const KPluginMetaData& metaData); private: /** * Create the transaction move menu and setup necessary connections. */ void createTransactionMoveMenu(); /** * This method sets the holidayRegion for use by the processing calendar. */ void setHolidayRegion(const QString& holidayRegion); /** * Load the status bar with the 'ready' message. This is hold in a single * place, so that is consistent with isReady(). */ void ready(); /** * Check if the status bar contains the 'ready' message. The return * value is used e.g. to detect if a quit operation is allowed or not. * * @retval true application is idle * @retval false application is active working on a longer operation */ bool isReady(); /** * Delete a possibly existing transaction editor but make sure to remove * any reference to it so that we avoid using a half-dead object */ void deleteTransactionEditor(); /** * delete all selected transactions w/o further questions */ void doDeleteTransactions(); /** * Re-implemented from IMyMoneyProcessingCalendar */ bool isProcessingDate(const QDate& date) const; /** * Depending on the setting of AutoSaveOnQuit, this method * asks the user to save the file or not. * * @returns see return values of KMessageBox::warningYesNoCancel() */ int askSaveOnClose(); /** * Implement common task when deleting or merging payees */ bool payeeReassign(int type); signals: /** * This signal is emitted when a new file is loaded. In the case file * is closed, this signal is also emitted with an empty url. */ void fileLoaded(const QUrl &url); /** * This signal is emitted when a payee/list of payees has been selected by * the GUI. If no payee is selected or the selection is removed, * @p payees is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void payeesSelected(const QList& payees); /** * This signal is emitted when a tag/list of tags has been selected by * the GUI. If no tag is selected or the selection is removed, * @p tags is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void tagsSelected(const QList& tags); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is sent out, when the user presses Ctrl+A or activates * the Select all transactions action. */ void selectAllTransactions(); /** * This signal is emitted when a list of budgets has been selected by * the GUI. If no budget is selected or the selection is removed, * @a budget is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void budgetSelected(const QList& budget); void budgetRename(); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); void investmentSelected(const MyMoneyAccount& account); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when a new schedule has been selected by * the GUI. If no schedule is selected or the selection is removed, * @a schedule is identical to MyMoneySchedule(). This signal is used * by plugins to get information about changes. */ void scheduleSelected(const MyMoneySchedule& schedule); /** * This signal is emitted when a new currency has been selected by * the GUI. If no currency is selected or the selection is removed, * @a currency is identical to MyMoneySecurity(). This signal is used * by plugins to get information about changes. */ void currencySelected(const MyMoneySecurity& currency); /** * This signal is emitted when a new price has been selected by * the GUI. If no price is selected or the selection is removed, * @a price is identical to MyMoneyPrice(). */ void priceSelected(const MyMoneyPrice& price); void payeeRename(); void payeeCreated(const QString& id); void tagRename(); void tagCreated(const QString& id); void currencyRename(); void currencyCreated(const QString& id); void priceEdit(); void priceNew(); void priceDelete(); void priceOnlineUpdate(); void startMatchTransaction(const MyMoneyTransaction& t); void cancelMatchTransaction(); /** * This signal is emitted when an account has been successfully reconciled * and all transactions are updated in the engine. It can be used by plugins * to create reconciliation reports. * * @param account the account data * @param date the reconciliation date as provided through the dialog * @param startingBalance the starting balance as provided through the dialog * @param endingBalance the ending balance as provided through the dialog * @param transactionList reference to QList of QPair containing all * transaction/split pairs processed by the reconciliation. */ void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); public: bool isActionToggled(const Action _a); static const QHash s_Actions; private: /// \internal d-pointer class. class Private; /* * Actually, one should write "Private * const d" but that confuses the KIDL * compiler in this context. It complains about the const keyword. So we leave * it out here */ /// \internal d-pointer instance. Private* d; }; extern KMyMoneyApp *kmymoney; class KMStatus { public: KMStatus(const QString &text); ~KMStatus(); private: QString m_prevText; }; #define KMSTATUS(msg) KMStatus _thisStatus(msg) #endif // KMYMONEY_H diff --git a/kmymoney/kmymoneyui.rc b/kmymoney/kmymoneyui.rc index 027f889b8..0342fc9b6 100644 --- a/kmymoney/kmymoneyui.rc +++ b/kmymoney/kmymoneyui.rc @@ -1,287 +1,284 @@ &Import - &Export - &Institution &Account &Category &Transaction Mark transaction as... Mark transaction T&ools - Account options Category options Institution options Payee options Tag options Budget options Investment options Scheduled transactions options Transaction options Move transaction to... Select account Mark transaction as... Mark transaction Currency options Price options Credit transfer options Main Toolbar diff --git a/kmymoney/plugins/CMakeLists.txt b/kmymoney/plugins/CMakeLists.txt index b85f0a0b6..a75cc35b0 100644 --- a/kmymoney/plugins/CMakeLists.txt +++ b/kmymoney/plugins/CMakeLists.txt @@ -1,81 +1,82 @@ add_subdirectory( onlinetasks ) add_subdirectory( ibanbicdata ) add_subdirectory( interfaces ) add_subdirectory( csvimport ) add_subdirectory( csvexport ) +add_subdirectory( qif ) if(LIBOFX_FOUND) add_subdirectory( ofximport ) endif(LIBOFX_FOUND) if (LIBICAL_FOUND) add_subdirectory( icalendarexport ) endif (LIBICAL_FOUND) add_subdirectory( reconciliationreport ) add_subdirectory( printcheck ) if (KBANKING_FOUND) add_subdirectory( kbanking ) endif (KBANKING_FOUND) option(ENABLE_SQLCIPHER "Enable SQLCipher plugin" OFF) if (ENABLE_SQLCIPHER) add_subdirectory(sqlcipher) endif(ENABLE_SQLCIPHER) option(ENABLE_ONLINEJOBPLUGINMOCKUP "Enable onlineJob-plugin mockup (only for developers)" OFF) if (ENABLE_ONLINEJOBPLUGINMOCKUP) add_subdirectory(onlinejobpluginmockup) endif() if (WEBOOB_FOUND) add_subdirectory( weboob ) endif (WEBOOB_FOUND) ########### next target ############### set(kmm_plugin_LIB_SRCS importinterface.cpp kmymoneyplugin.cpp statementinterface.cpp viewinterface.cpp onlinepluginextended.cpp interfaceloader.cpp ) set(plugins_HEADERS importinterface.h kmymoneyplugin.h statementinterface.h viewinterface.h ${CMAKE_CURRENT_BINARY_DIR}/kmm_plugin_export.h onlinepluginextended.h ) add_library(kmm_plugin SHARED ${kmm_plugin_LIB_SRCS}) generate_export_header(kmm_plugin BASE_NAME kmm_plugin) target_link_libraries(kmm_plugin PUBLIC KF5::XmlGui KF5::KCMUtils KF5::KIOWidgets Qt5::Gui Qt5::Widgets kmm_mymoney ) set_target_properties(kmm_plugin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) ########### install files ############### install(TARGETS kmm_plugin ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${plugins_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) install(FILES kmymoney-importerplugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) diff --git a/kmymoney/plugins/qif/CMakeLists.txt b/kmymoney/plugins/qif/CMakeLists.txt new file mode 100644 index 000000000..a170d16b5 --- /dev/null +++ b/kmymoney/plugins/qif/CMakeLists.txt @@ -0,0 +1,11 @@ +if(ENABLE_QIFIMPORTER) + add_subdirectory( import ) +endif() + +if(ENABLE_QIFEXPORTER) + add_subdirectory( export ) +endif() + +if(ENABLE_QIFIMPORTER OR ENABLE_QIFEXPORTER) + add_subdirectory( config ) +endif() diff --git a/kmymoney/plugins/qif/config/CMakeLists.txt b/kmymoney/plugins/qif/config/CMakeLists.txt new file mode 100644 index 000000000..e14b26cfa --- /dev/null +++ b/kmymoney/plugins/qif/config/CMakeLists.txt @@ -0,0 +1,41 @@ +# the KCM module +set(kcm_kmm_qif_PART_SRCS + mymoneyqifprofileeditor.cpp + mymoneyqifprofile.cpp + kcm_qif.cpp + ) + +ki18n_wrap_ui(kcm_kmm_qif_PART_SRCS mymoneyqifprofileeditor.ui) + +add_library(kcm_kmm_qif MODULE ${kcm_kmm_qif_PART_SRCS}) + +if(ENABLE_QIFIMPORTER) + kcoreaddons_desktop_to_json(kcm_kmm_qif kcm_kmm_qifimport.desktop) +endif() + +if(ENABLE_QIFEXPORTER) + kcoreaddons_desktop_to_json(kcm_kmm_qif kcm_kmm_qifexport.desktop) +endif() + + +target_link_libraries(kcm_kmm_qif + kmm_mymoney + KF5::I18n + KF5::ConfigWidgets + KF5::Completion + KF5::KIOWidgets + ) + +install(TARGETS kcm_kmm_qif + DESTINATION ${KDE_INSTALL_PLUGINDIR}) + +if(ENABLE_QIFIMPORTER) + install(FILES kcm_kmm_qifimport.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) +endif() + +if(ENABLE_QIFEXPORTER) + install(FILES kcm_kmm_qifexport.desktop + DESTINATION ${SERVICES_INSTALL_DIR}) +endif() + diff --git a/kmymoney/plugins/qif/config/kcm_kmm_qifexport.desktop b/kmymoney/plugins/qif/config/kcm_kmm_qifexport.desktop new file mode 100644 index 000000000..c181cc3aa --- /dev/null +++ b/kmymoney/plugins/qif/config/kcm_kmm_qifexport.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Name=QIF Importer configuration +Name[x-test]=xxQIF Importer configurationxx +Icon=document-import +Type=Service +ServiceTypes=KCModule +Exec=kcmshell5 kmm_qifexport +Categories=Qt;KDE;kmymoney; +Keywords=qifexport;kmymoney; +Keywords[x-test]=xxqifimportxx;xxkmymoneyxx; +X-KDE-Library=kcm_kmm_qif +X-KDE-ParentApp=qifexport +X-KDE-ParentComponents=qifexport +Comment=Configuration for QIF Importer plugin +Comment[x-test]=xxConfiguration for QIF Importer pluginxx diff --git a/kmymoney/plugins/qif/config/kcm_kmm_qifimport.desktop b/kmymoney/plugins/qif/config/kcm_kmm_qifimport.desktop new file mode 100644 index 000000000..186662ac5 --- /dev/null +++ b/kmymoney/plugins/qif/config/kcm_kmm_qifimport.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Name=QIF Importer configuration +Name[x-test]=xxQIF Importer configurationxx +Icon=document-import +Type=Service +ServiceTypes=KCModule +Exec=kcmshell5 kmm_qifimport +Categories=Qt;KDE;kmymoney; +Keywords=qifimport;kmymoney; +Keywords[x-test]=xxqifimportxx;xxkmymoneyxx; +X-KDE-Library=kcm_kmm_qif +X-KDE-ParentApp=qifimport +X-KDE-ParentComponents=qifimport +Comment=Configuration for QIF Importer plugin +Comment[x-test]=xxConfiguration for QIF Importer pluginxx diff --git a/kmymoney/plugins/qif/config/kcm_qif.cpp b/kmymoney/plugins/qif/config/kcm_qif.cpp new file mode 100644 index 000000000..232468330 --- /dev/null +++ b/kmymoney/plugins/qif/config/kcm_qif.cpp @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version * + * accepted by the membership of KDE e.V. (or its successor approved * + * by the membership of KDE e.V.), which shall act as a proxy * + * defined in Section 14 of version 3 of the license. * + * * + * 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 "kcm_qif.h" + +// KDE includes +#include + +#include "mymoneyqifprofileeditor.h" + +K_PLUGIN_FACTORY(KCMqifFactory, registerPlugin();) + +KCMqif::KCMqif(QWidget *parent, const QVariantList& args) : KCModule(0/*KCMqifFactory::componentData()*/, parent, args) +{ + auto editor = new MyMoneyQifProfileEditor(true, this); + auto layout = new QVBoxLayout; + setLayout(layout); + layout->addWidget(editor); + setButtons(NoAdditionalButton); + load(); +} + +#include "kcm_qif.moc" diff --git a/kmymoney/plugins/qif/config/kcm_qif.h b/kmymoney/plugins/qif/config/kcm_qif.h new file mode 100644 index 000000000..7385a9dcf --- /dev/null +++ b/kmymoney/plugins/qif/config/kcm_qif.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright 2017 Łukasz Wojniłowicz 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) version 3 or any later version * + * accepted by the membership of KDE e.V. (or its successor approved * + * by the membership of KDE e.V.), which shall act as a proxy * + * defined in Section 14 of version 3 of the license. * + * * + * 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 KCM_QIF_H +#define KCM_QIF_H + +#include + +class KCMqif : public KCModule +{ +public: + KCMqif(QWidget* parent, const QVariantList& args); +}; + +#endif // KCM_QIF_H + diff --git a/kmymoney/converter/mymoneyqifprofile.cpp b/kmymoney/plugins/qif/config/mymoneyqifprofile.cpp similarity index 100% rename from kmymoney/converter/mymoneyqifprofile.cpp rename to kmymoney/plugins/qif/config/mymoneyqifprofile.cpp diff --git a/kmymoney/converter/mymoneyqifprofile.h b/kmymoney/plugins/qif/config/mymoneyqifprofile.h similarity index 100% rename from kmymoney/converter/mymoneyqifprofile.h rename to kmymoney/plugins/qif/config/mymoneyqifprofile.h diff --git a/kmymoney/dialogs/mymoneyqifprofileeditor.cpp b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.cpp similarity index 85% rename from kmymoney/dialogs/mymoneyqifprofileeditor.cpp rename to kmymoney/plugins/qif/config/mymoneyqifprofileeditor.cpp index 25700f2a6..8f3470d10 100644 --- a/kmymoney/dialogs/mymoneyqifprofileeditor.cpp +++ b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.cpp @@ -1,469 +1,452 @@ /*************************************************************************** kqifprofileeditor.cpp - description ------------------- begin : Tue Dec 24 2002 copyright : (C) 2002 by Thomas Baumgart email : thb@net-bembel.de ***************************************************************************/ /*************************************************************************** * * * 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 "mymoneyqifprofileeditor.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include using namespace Icons; MyMoneyQifProfileNameValidator::MyMoneyQifProfileNameValidator(QObject *o) : QValidator(o) { } MyMoneyQifProfileNameValidator::~MyMoneyQifProfileNameValidator() { } QValidator::State MyMoneyQifProfileNameValidator::validate(QString& name, int&) const { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Profiles"); QStringList list = grp.readEntry("profiles", QStringList()); // invalid character? if (name.contains(",") != 0) return QValidator::Invalid; // would not work in this form (empty or existing name) if (name.isEmpty() || list.contains(name)) return QValidator::Intermediate; // is OK return QValidator::Acceptable; } MyMoneyQifProfileEditor::MyMoneyQifProfileEditor(const bool edit, QWidget *parent) - : MyMoneyQifProfileEditorDecl(parent), + : QWidget(parent), m_inEdit(edit), m_isDirty(false), m_isAccepted(false), m_selectedAmountType(0) { + setupUi(this); loadWidgets(); loadProfileListFromConfig(); // load button icons KGuiItem::assign(m_resetButton, KStandardGuiItem::reset()); - KGuiItem::assign(m_cancelButton, KStandardGuiItem::cancel()); - KGuiItem::assign(m_okButton, KStandardGuiItem::ok()); KGuiItem::assign(m_deleteButton, KStandardGuiItem::del()); KGuiItem::assign(m_helpButton, KStandardGuiItem::help()); KGuiItem newButtenItem(i18nc("New profile", "&New"), QIcon::fromTheme(g_Icons[Icon::DocumentNew]), i18n("Create a new profile"), i18n("Use this to create a new QIF import/export profile")); KGuiItem::assign(m_newButton, newButtenItem); - connect(m_profileListBox, SIGNAL(currentTextChanged(QString)), this, SLOT(slotLoadProfileFromConfig(QString))); - connect(m_resetButton, SIGNAL(clicked()), this, SLOT(slotReset())); - connect(m_okButton, SIGNAL(clicked()), this, SLOT(slotOk())); - connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRename())); - connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDelete())); - connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotNew())); - connect(m_cancelButton, SIGNAL(clicked()), this, SLOT(reject())); - connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); - - connect(m_editDescription, SIGNAL(textChanged(QString)), &m_profile, SLOT(setProfileDescription(QString))); - connect(m_editType, SIGNAL(textChanged(QString)), &m_profile, SLOT(setProfileType(QString))); - connect(m_editOpeningBalance, SIGNAL(textChanged(QString)), &m_profile, SLOT(setOpeningBalanceText(QString))); - connect(m_editAccountDelimiter, SIGNAL(textChanged(QString)), &m_profile, SLOT(setAccountDelimiter(QString))); - connect(m_editVoidMark, SIGNAL(textChanged(QString)), &m_profile, SLOT(setVoidMark(QString))); + connect(m_profileListBox, &QListWidget::currentTextChanged, this, &MyMoneyQifProfileEditor::slotLoadProfileFromConfig); + connect(m_resetButton, &QAbstractButton::clicked, this, &MyMoneyQifProfileEditor::slotReset); + connect(m_renameButton, &QAbstractButton::clicked, this, &MyMoneyQifProfileEditor::slotRename); + connect(m_deleteButton, &QAbstractButton::clicked, this, &MyMoneyQifProfileEditor::slotDelete); + connect(m_newButton, &QAbstractButton::clicked, this, &MyMoneyQifProfileEditor::slotNew); + connect(m_helpButton, &QAbstractButton::clicked, this, &MyMoneyQifProfileEditor::slotHelp); + + connect(m_editDescription, &QLineEdit::textChanged, &m_profile, &MyMoneyQifProfile::setProfileDescription); + connect(m_editType, &QLineEdit::textChanged, &m_profile, &MyMoneyQifProfile::setProfileType); + connect(m_editOpeningBalance, &QLineEdit::textChanged, &m_profile, &MyMoneyQifProfile::setOpeningBalanceText); + connect(m_editAccountDelimiter, &QLineEdit::textChanged, &m_profile, &MyMoneyQifProfile::setAccountDelimiter); + connect(m_editVoidMark, &QLineEdit::textChanged, &m_profile, &MyMoneyQifProfile::setVoidMark); connect(m_editDateFormat, SIGNAL(highlighted(QString)), &m_profile, SLOT(setOutputDateFormat(QString))); connect(m_editApostrophe, SIGNAL(highlighted(QString)), &m_profile, SLOT(setApostropheFormat(QString))); - connect(m_editAmounts, SIGNAL(itemSelectionChanged()), this, SLOT(slotAmountTypeSelected())); + connect(m_editAmounts, &QTreeWidget::itemSelectionChanged, this, &MyMoneyQifProfileEditor::slotAmountTypeSelected); connect(m_decimalBox, SIGNAL(activated(QString)), this, SLOT(slotDecimalChanged(QString))); connect(m_thousandsBox, SIGNAL(activated(QString)), this, SLOT(slotThousandsChanged(QString))); - connect(m_editInputFilterLocation, SIGNAL(textChanged(QString)), &m_profile, SLOT(setFilterScriptImport(QString))); - connect(m_editInputFilterLocation, SIGNAL(urlSelected(QUrl)), m_editInputFilterLocation, SLOT(setUrl(QUrl))); + connect(m_editInputFilterLocation, &KUrlRequester::textChanged, &m_profile, &MyMoneyQifProfile::setFilterScriptImport); + connect(m_editInputFilterLocation, &KUrlRequester::urlSelected, m_editInputFilterLocation, &KUrlRequester::setUrl); - connect(m_editInputFilterFileType, SIGNAL(textChanged(QString)), &m_profile, SLOT(setFilterFileType(QString))); + connect(m_editInputFilterFileType, &QLineEdit::textChanged, &m_profile, &MyMoneyQifProfile::setFilterFileType); - connect(m_editOutputFilterLocation, SIGNAL(textChanged(QString)), &m_profile, SLOT(setFilterScriptExport(QString))); - connect(m_editOutputFilterLocation, SIGNAL(urlSelected(QUrl)), m_editOutputFilterLocation, SLOT(setUrl(QUrl))); + connect(m_editOutputFilterLocation, &KUrlRequester::textChanged, &m_profile, &MyMoneyQifProfile::setFilterScriptExport); + connect(m_editOutputFilterLocation, &KUrlRequester::urlSelected, m_editOutputFilterLocation, &KUrlRequester::setUrl); - connect(m_attemptMatch, SIGNAL(toggled(bool)), &m_profile, SLOT(setAttemptMatchDuplicates(bool))); + connect(m_attemptMatch, &QAbstractButton::toggled, &m_profile, &MyMoneyQifProfile::setAttemptMatchDuplicates); } MyMoneyQifProfileEditor::~MyMoneyQifProfileEditor() { if (m_inEdit && m_isDirty && m_isAccepted) { KSharedConfigPtr config = KSharedConfig::openConfig(); config->sync(); } else { slotReset(); } delete tabMoney; delete tabDate; } void MyMoneyQifProfileEditor::loadWidgets() { if (m_inEdit) setWindowTitle(i18n("QIF Profile Editor")); else setWindowTitle(i18n("QIF Profile Selector")); m_editDateFormat->clear(); m_editDateFormat->addItem("%d/%m/%yy"); m_editDateFormat->addItem("%d/%mmm/%yy"); m_editDateFormat->addItem("%d/%m/%yyyy"); m_editDateFormat->addItem("%d/%mmm/%yyyy"); m_editDateFormat->addItem("%d/%m%yy"); m_editDateFormat->addItem("%d/%mmm%yy"); m_editDateFormat->addItem("%d.%m.%yy"); m_editDateFormat->addItem("%d.%m.%yyyy"); m_editDateFormat->addItem("%m.%d.%yy"); m_editDateFormat->addItem("%m.%d.%yyyy"); m_editDateFormat->addItem("%m/%d/%yy"); m_editDateFormat->addItem("%mmm/%d/%yy"); m_editDateFormat->addItem("%m/%d/%yyyy"); m_editDateFormat->addItem("%m-%d-%yyyy"); m_editDateFormat->addItem("%mmm/%d/%yyyy"); m_editDateFormat->addItem("%m%d%yy"); m_editDateFormat->addItem("%mmm/%d%yy"); m_editDateFormat->addItem("%yyyy-%mm-%dd"); m_editDateFormat->addItem("%m/%d'%yyyy"); m_editApostrophe->clear(); m_editApostrophe->addItem("1900-1949"); m_editApostrophe->addItem("1900-1999"); m_editApostrophe->addItem("2000-2099"); m_editAmounts->setColumnHidden(4, true); m_editAmounts->sortItems(4, Qt::AscendingOrder); m_decimalBox->addItem(" "); m_decimalBox->addItem(","); m_decimalBox->addItem("."); m_thousandsBox->addItem(" "); m_thousandsBox->addItem(","); m_thousandsBox->addItem("."); m_editDescription->setEnabled(m_inEdit); m_editType->setEnabled(m_inEdit); m_editDateFormat->setEnabled(m_inEdit); m_editApostrophe->setEnabled(m_inEdit); m_editAmounts->setEnabled(m_inEdit); m_decimalBox->setEnabled(m_inEdit); m_thousandsBox->setEnabled(m_inEdit); m_editOpeningBalance->setEnabled(m_inEdit); m_editAccountDelimiter->setEnabled(m_inEdit); m_editVoidMark->setEnabled(m_inEdit); m_editInputFilterLocation->setEnabled(m_inEdit); m_editOutputFilterLocation->setEnabled(m_inEdit); m_editInputFilterFileType->setEnabled(m_inEdit); if (!m_inEdit) { m_renameButton->hide(); m_deleteButton->hide(); m_resetButton->hide(); m_newButton->hide(); } } void MyMoneyQifProfileEditor::loadProfileListFromConfig() { QFontMetrics fontMetrics(m_profileListBox->font()); int w = 100; // minimum is 100 pixels width for the list box if (m_profile.isDirty()) { m_profile.saveProfile(); m_isDirty = true; } m_profileListBox->clear(); QStringList list; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Profiles"); list = grp.readEntry("profiles", QStringList()); if (list.count() == 0) { m_profile.clear(); m_profile.setProfileDescription(i18n("The default QIF profile")); addProfile("Default"); grp = config->group("Profiles"); list = grp.readEntry("profiles", QStringList()); } list.sort(); m_profileListBox->addItems(list); if (!list.isEmpty()) { m_profileListBox->item(0)->setSelected(true); slotLoadProfileFromConfig(list[0]); } for (int i = 0; i < list.count(); ++i) { int nw = fontMetrics.width(list[i]) + 10; w = qMax(w, nw); } w = qMin(w, 200); m_profileListBox->setMinimumWidth(w); } void MyMoneyQifProfileEditor::slotLoadProfileFromConfig(const QString& profile) { QString profileName = profile; if (m_profile.isDirty()) { m_profile.saveProfile(); m_isDirty = true; } if (m_profileListBox->findItems(profileName, Qt::MatchExactly | Qt::MatchCaseSensitive).empty()) { profileName = m_profileListBox->item(0)->text(); } m_profile.loadProfile("Profile-" + profileName); QList lbi = m_profileListBox->findItems(profileName, Qt::MatchExactly | Qt::MatchCaseSensitive); if (!lbi.empty()) { m_profileListBox->setCurrentItem(lbi.at(0)); } showProfile(); } void MyMoneyQifProfileEditor::showProfile() { m_editDescription->setText(m_profile.profileDescription()); m_editType->setText(m_profile.profileType()); m_editOpeningBalance->setText(m_profile.openingBalanceText()); m_editAccountDelimiter->setText(m_profile.accountDelimiter()); m_editVoidMark->setText(m_profile.voidMark()); m_editInputFilterLocation->setUrl(QUrl::fromLocalFile(m_profile.filterScriptImport())); m_editOutputFilterLocation->setUrl(QUrl::fromLocalFile(m_profile.filterScriptExport())); m_editInputFilterFileType->setText(m_profile.filterFileType()); // load combo boxes int idx = m_editDateFormat->findText(m_profile.outputDateFormat()); if (idx == -1) idx = 0; m_editDateFormat->setCurrentIndex(idx); idx = m_editApostrophe->findText(m_profile.apostropheFormat()); if (idx == -1) idx = 0; m_editApostrophe->setCurrentIndex(idx); m_attemptMatch->setChecked(m_profile.attemptMatchDuplicates()); QTreeWidgetItemIterator it(m_editAmounts); while (*it) { QChar key = (*it)->text(1)[0]; (*it)->setText(2, m_profile.amountDecimal(key)); (*it)->setTextAlignment(2, Qt::AlignCenter); (*it)->setText(3, m_profile.amountThousands(key)); (*it)->setTextAlignment(3, Qt::AlignCenter); if (m_selectedAmountType == 0 && key == 'T' && m_inEdit) { (*it)->setSelected(true); slotAmountTypeSelected(); } else if ((*it) == m_selectedAmountType) { (*it)->setSelected(true); slotAmountTypeSelected(); } ++it; } } void MyMoneyQifProfileEditor::deleteProfile(const QString& name) { KSharedConfigPtr config = KSharedConfig::openConfig(); config->deleteGroup("Profile-" + name); KConfigGroup grp = config->group("Profiles"); QStringList list = grp.readEntry("profiles", QStringList()); list.removeAll(name); grp.writeEntry("profiles", list); m_isDirty = true; } void MyMoneyQifProfileEditor::addProfile(const QString& name) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Profiles"); QStringList list = grp.readEntry("profiles", QStringList()); list += name; list.sort(); grp.writeEntry("profiles", list); m_profile.setProfileName("Profile-" + name); m_profile.saveProfile(); m_isDirty = true; } -void MyMoneyQifProfileEditor::slotOk() -{ - if (m_profile.isDirty()) - m_isDirty = true; - - m_profile.saveProfile(); - - KSharedConfigPtr config = KSharedConfig::openConfig(); - config->sync(); - - m_isAccepted = true; - accept(); -} - void MyMoneyQifProfileEditor::slotReset() { // first flush any changes m_profile.saveProfile(); KSharedConfigPtr config = KSharedConfig::openConfig(); config->reparseConfiguration(); QString currentProfile = m_profile.profileName().mid(8); loadProfileListFromConfig(); slotLoadProfileFromConfig(currentProfile); m_isDirty = false; } void MyMoneyQifProfileEditor::slotRename() { bool ok; QString newName = enterName(ok); if (ok == true) { deleteProfile(m_profile.profileName().mid(8)); addProfile(newName); loadProfileListFromConfig(); slotLoadProfileFromConfig(newName); } } void MyMoneyQifProfileEditor::slotNew() { bool ok; QString newName = enterName(ok); if (ok == true) { m_profile.clear(); addProfile(newName); loadProfileListFromConfig(); slotLoadProfileFromConfig(newName); } } const QString MyMoneyQifProfileEditor::enterName(bool& ok) { // TODO: port KF5 use the validator //MyMoneyQifProfileNameValidator val(this); return QInputDialog::getText(this, i18n("QIF Profile Editor"), i18n("Enter new profile name"), QLineEdit::Normal, QString(), &ok); } void MyMoneyQifProfileEditor::slotDelete() { QString profile = m_profile.profileName().mid(8); if (KMessageBox::questionYesNo(this, i18n("Do you really want to delete profile '%1'?", profile)) == KMessageBox::Yes) { int idx = m_profileListBox->currentRow(); m_profile.saveProfile(); deleteProfile(profile); loadProfileListFromConfig(); if (idx >= m_profileListBox->count()) idx = m_profileListBox->count() - 1; m_profileListBox->setCurrentRow(idx); slotLoadProfileFromConfig(m_profileListBox->item(idx)->text()); } } void MyMoneyQifProfileEditor::slotHelp() { KHelpClient::invokeHelp("details.impexp.qifimp.profile"); } void MyMoneyQifProfileEditor::slotAmountTypeSelected() { QList selectedItems = m_editAmounts->selectedItems(); if (! selectedItems.empty()) { QTreeWidgetItem* item = selectedItems.at(0); m_decimalBox->setCurrentIndex(m_decimalBox->findText(item->text(2), Qt::MatchExactly)); m_thousandsBox->setCurrentIndex(m_thousandsBox->findText(item->text(3), Qt::MatchExactly)); m_selectedAmountType = item; } } void MyMoneyQifProfileEditor::slotDecimalChanged(const QString& val) { if (m_selectedAmountType != 0) { QChar key = m_selectedAmountType->text(1)[0]; m_profile.setAmountDecimal(key, val[0]); m_selectedAmountType->setText(2, val); } } void MyMoneyQifProfileEditor::slotThousandsChanged(const QString& val) { if (m_selectedAmountType != 0) { QChar key = m_selectedAmountType->text(1)[0]; m_profile.setAmountThousands(key, val[0]); m_selectedAmountType->setText(3, val); } } const QString MyMoneyQifProfileEditor::selectedProfile() const { return m_profileListBox->currentItem()->text(); } diff --git a/kmymoney/dialogs/mymoneyqifprofileeditor.h b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h similarity index 89% rename from kmymoney/dialogs/mymoneyqifprofileeditor.h rename to kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h index a8a3a3a4f..11e2a41e8 100644 --- a/kmymoney/dialogs/mymoneyqifprofileeditor.h +++ b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h @@ -1,104 +1,93 @@ /*************************************************************************** kqifprofileeditor.h - description ------------------- begin : Tue Dec 24 2002 copyright : (C) 2002 by Thomas Baumgart email : thb@net-bembel.de ***************************************************************************/ /*************************************************************************** * * * 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 MYMONEYQIFPROFILEEDITOR_H #define MYMONEYQIFPROFILEEDITOR_H // ---------------------------------------------------------------------------- // QT Includes #include #include class QTreeWidgetItem; // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes -#include "ui_mymoneyqifprofileeditordecl.h" +#include "ui_mymoneyqifprofileeditor.h" #include "mymoneyqifprofile.h" /** * @author Thomas Baumgart */ class MyMoneyQifProfileNameValidator : public QValidator { Q_OBJECT public: MyMoneyQifProfileNameValidator(QObject* o); virtual ~MyMoneyQifProfileNameValidator(); QValidator::State validate(QString&, int&) const; }; -class MyMoneyQifProfileEditorDecl : public QDialog, public Ui::MyMoneyQifProfileEditorDecl -{ -public: - MyMoneyQifProfileEditorDecl(QWidget *parent) : QDialog(parent) { - setupUi(this); - } -}; - -class MyMoneyQifProfileEditor : public MyMoneyQifProfileEditorDecl +class MyMoneyQifProfileEditor : public QWidget, public Ui::MyMoneyQifProfileEditor { Q_OBJECT public: explicit MyMoneyQifProfileEditor(const bool edit = false, QWidget *parent = 0); virtual ~MyMoneyQifProfileEditor(); /** * This method returns the currently selected profile in the list box. */ const QString selectedProfile() const; -public slots: - void slotOk(); - protected slots: void slotLoadProfileFromConfig(const QString& name); void slotReset(); void slotRename(); void slotDelete(); void slotNew(); void slotAmountTypeSelected(); void slotDecimalChanged(const QString& val); void slotThousandsChanged(const QString& val); void slotHelp(); private: void loadProfileListFromConfig(); void loadWidgets(); void showProfile(); void addProfile(const QString& name); void deleteProfile(const QString& name); const QString enterName(bool& ok); private: bool m_inEdit; MyMoneyQifProfile m_profile; bool m_isDirty; bool m_isAccepted; QTreeWidgetItem* m_selectedAmountType; }; #endif diff --git a/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.ui b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.ui new file mode 100644 index 000000000..658d7d294 --- /dev/null +++ b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.ui @@ -0,0 +1,696 @@ + + + MyMoneyQifProfileEditor + + + + 0 + 0 + 736 + 334 + + + + QIF Profile Editor + + + + 6 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + General + + + + + + + + + 140 + 0 + + + + Void mark + + + false + + + + + + + + 140 + 0 + + + + Opening Balance text + + + false + + + + + + + + + + + 140 + 0 + + + + Type field text + + + false + + + + + + + + + + + + + + 140 + 0 + + + + Account delimiter + + + false + + + + + + + + + + + + + + 140 + 0 + + + + Description + + + false + + + + + + + + + Attempt to match similar transactions + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + Filter + + + + + + + + + 140 + 0 + + + + Output filter location + + + false + + + + + + + + + + false + + + + + + + + 140 + 0 + + + + Input filter file type + + + false + + + + + + + *.qif + + + + + + + + 140 + 0 + + + + Input filter location + + + false + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 90 + + + + + + + + + Date + + + + + + + + + + The format of the dates in the QIF file. + + + + + + + + 140 + 0 + + + + The format of the dates in the QIF file. + + + Date Format + + + false + + + + + + + If a QIF file contains date entries with years represented with two digits then either an apostrophe or a slash may be used to delimit the year in the dates for certain centuries. This enables 1905 to be distinguished from 2005. Specify here which range of years will have year delimited by an apostrophe (eg. for Quicken this is usually 1900-1949). + + + + + + + + 140 + 0 + + + + If a QIF file contains date entries with years represented with two digits then either an apostrophe or a slash may be used to delimit the year in the dates for certain centuries. This enables 1905 to be distinguished from 2005. Specify here which range of years will have year delimited by an apostrophe (eg. for Quicken this is usually 1900-1949). + + + Apostrophe Handling + + + false + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 90 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 80 + + + + + + + + + Amounts + + + + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::SingleSelection + + + true + + + + Field + + + + + QIF-Record + + + + + Decimal Symbol + + + + + Thousand Delimiter + + + + + SortColumnInvisible + + + + + Value + + + T + + + + + + + + + 0 + + + + + Split-Value + + + $ + + + + + + + + + 1 + + + + + Commission + + + O + + + + + + + + + 2 + + + + + Price + + + I + + + + + + + + + 3 + + + + + Quantity + + + Q + + + + + + + + + 4 + + + + + + + + + + + + + + + + 150 + 0 + + + + Decimal Symbol + + + false + + + + + + + + 150 + 0 + + + + Thousands Separator + + + false + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 100 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 16 + 16 + + + + + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Help + + + + + + + New + + + + + + + Delete + + + + + + + Rename + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 104 + 0 + + + + + + + + Reset + + + + + + + + + + + KUrlRequester + QWidget +
kurlrequester.h
+
+ + KLineEdit + QLineEdit +
klineedit.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+
+ + m_profileListBox + profileTabs + m_editDescription + m_editType + m_editOpeningBalance + m_editAccountDelimiter + m_editVoidMark + m_helpButton + m_newButton + m_deleteButton + m_renameButton + m_resetButton + m_editDateFormat + m_editApostrophe + m_decimalBox + m_thousandsBox + m_editAmounts + m_editInputFilterLocation + m_editOutputFilterLocation + + + +
diff --git a/kmymoney/plugins/qif/export/CMakeLists.txt b/kmymoney/plugins/qif/export/CMakeLists.txt new file mode 100644 index 000000000..43bb900c3 --- /dev/null +++ b/kmymoney/plugins/qif/export/CMakeLists.txt @@ -0,0 +1,33 @@ +# patch the version with the version defined in the build system +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/qifexport.json.in ${CMAKE_CURRENT_BINARY_DIR}/qifexport.json @ONLY) + +########### next target ############### + +set(kmm_qifexport_PART_SRCS + qifexporterplugin.cpp + ../config/mymoneyqifprofile.cpp + mymoneyqifwriter.cpp + kexportdlg.cpp + ${CMAKE_SOURCE_DIR}/kmymoney/widgets/kmymoneyaccountcombo.cpp +) + +set(kmm_qifexport_PART_UI + kexportdlgdecl.ui +) + +ki18n_wrap_ui(kmm_qifexport_PART_SRCS ${kmm_qifexport_PART_UI}) + +add_library(kmm_qifexport MODULE ${kmm_qifexport_PART_SRCS}) + +target_link_libraries(kmm_qifexport + kmm_plugin + models #TODO: Get rid of this big dependency +) + +########### install files ############### + +install(FILES kmm_qifexport.rc + DESTINATION "${KXMLGUI_INSTALL_DIR}/kmm_qifexport") + +install(TARGETS kmm_qifexport + DESTINATION "${KDE_INSTALL_PLUGINDIR}/kmymoney/") diff --git a/kmymoney/plugins/qif/export/kcm_kmm_qifexport.desktop b/kmymoney/plugins/qif/export/kcm_kmm_qifexport.desktop new file mode 100644 index 000000000..afa204a47 --- /dev/null +++ b/kmymoney/plugins/qif/export/kcm_kmm_qifexport.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Name=QIF Importer configuration +Name[x-test]=xxQIF Importer configurationxx +Icon=document-import +Type=Service +ServiceTypes=KCModule +Exec=kcmshell5 kmm_qifimport +Categories=Qt;KDE;kmymoney; +Keywords=qifexport;kmymoney; +Keywords[x-test]=xxqifimportxx;xxkmymoneyxx; +X-KDE-Library=kcm_kmm_qifimport +X-KDE-ParentApp=qifexport +X-KDE-ParentComponents=qifexport +Comment=Configuration for QIF Importer plugin +Comment[x-test]=xxConfiguration for QIF Importer pluginxx diff --git a/kmymoney/dialogs/kexportdlg.cpp b/kmymoney/plugins/qif/export/kexportdlg.cpp similarity index 82% rename from kmymoney/dialogs/kexportdlg.cpp rename to kmymoney/plugins/qif/export/kexportdlg.cpp index bbc0e4d71..e974c7749 100644 --- a/kmymoney/dialogs/kexportdlg.cpp +++ b/kmymoney/plugins/qif/export/kexportdlg.cpp @@ -1,244 +1,222 @@ /*************************************************************************** kexportdlg.cpp - description ------------------- begin : Tue May 22 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez ***************************************************************************/ /*************************************************************************** * * * 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 "kexportdlg.h" // ---------------------------------------------------------------------------- // QT Headers #include #include #include #include #include #include +#include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Headers #include "mymoneycategory.h" -#include "mymoneyqifprofileeditor.h" #include "mymoneyfile.h" #include "kmymoneyaccountcombo.h" #include "kmymoneyutils.h" #include "models.h" #include using namespace Icons; KExportDlg::KExportDlg(QWidget *parent) : KExportDlgDecl(parent) { // Set (almost) all the last used options readConfig(); loadProfiles(true); loadAccounts(); // load button icons KGuiItem::assign(m_qbuttonCancel, KStandardGuiItem::cancel()); KGuiItem okButtenItem(i18n("&Export"), QIcon::fromTheme(g_Icons[Icon::DocumentExport]), i18n("Start operation"), i18n("Use this to start the export operation")); KGuiItem::assign(m_qbuttonOk, okButtenItem); KGuiItem browseButtenItem(i18n("&Browse..."), QIcon::fromTheme(g_Icons[Icon::DocumentOpen]), i18n("Select filename"), i18n("Use this to select a filename to export to")); KGuiItem::assign(m_qbuttonBrowse, browseButtenItem); - KGuiItem newButtenItem(i18nc("New profile", "&New..."), - QIcon::fromTheme(g_Icons[Icon::DocumentNew]), - i18n("Create a new profile"), - i18n("Use this to open the profile editor")); - KGuiItem::assign(m_profileEditorButton, newButtenItem); - - // connect the buttons to their functionality - connect(m_qbuttonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowse())); - connect(m_profileEditorButton, SIGNAL(clicked()), this, SLOT(slotNewProfile())); - connect(m_qbuttonOk, SIGNAL(clicked()), this, SLOT(slotOkClicked())); - connect(m_qbuttonCancel, SIGNAL(clicked()), this, SLOT(reject())); + connect(m_qbuttonBrowse, &QAbstractButton::clicked, this, &KExportDlg::slotBrowse); + connect(m_qbuttonOk, &QAbstractButton::clicked, this, &KExportDlg::slotOkClicked); + connect(m_qbuttonCancel, &QAbstractButton::clicked, this, &QDialog::reject); // connect the change signals to the check slot and perform initial check connect(m_qlineeditFile, SIGNAL(textChanged(QString)), this, SLOT(checkData())); connect(m_qcheckboxAccount, SIGNAL(toggled(bool)), this, SLOT(checkData())); connect(m_qcheckboxCategories, SIGNAL(toggled(bool)), this, SLOT(checkData())); connect(m_accountComboBox, SIGNAL(accountSelected(QString)), this, SLOT(checkData(QString))); connect(m_profileComboBox, SIGNAL(activated(int)), this, SLOT(checkData())); connect(m_kmymoneydateStart, SIGNAL(dateChanged(QDate)), this, SLOT(checkData())); connect(m_kmymoneydateEnd, SIGNAL(dateChanged(QDate)), this, SLOT(checkData())); checkData(QString()); } KExportDlg::~KExportDlg() { } void KExportDlg::slotBrowse() { - QString newName(QFileDialog::getSaveFileName(this, QString(), QString(), QLatin1String("*.QIF"))); - KMyMoneyUtils::appendCorrectFileExt(newName, QLatin1String("qif")); + auto newName(QFileDialog::getSaveFileName(this, QString(), QString(), QLatin1String("*.QIF"))); + if (!newName.endsWith(QLatin1String(".qif"), Qt::CaseInsensitive)) + newName.append(QLatin1String(".qif")); if (!newName.isEmpty()) m_qlineeditFile->setText(newName); } -void KExportDlg::slotNewProfile() -{ - QPointer editor = new MyMoneyQifProfileEditor(true, this); - editor->setObjectName("QIF Profile Editor"); - if (editor->exec()) { - m_profileComboBox->setCurrentIndex(m_profileComboBox->findText(editor->selectedProfile(), Qt::MatchExactly)); - loadProfiles(); - } - delete editor; -} - void KExportDlg::loadProfiles(const bool selectLast) { - // Creating an editor object here makes sure that - // we have at least the default profile available - MyMoneyQifProfileEditor* edit = new MyMoneyQifProfileEditor(true, 0); - edit->slotOk(); - delete edit; - QString current = m_profileComboBox->currentText(); m_profileComboBox->clear(); QStringList list; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Profiles"); list = grp.readEntry("profiles", QStringList()); list.sort(); m_profileComboBox->insertItems(0, list); if (selectLast == true) { grp = config->group("Last Use Settings"); current = grp.readEntry("KExportDlg_LastProfile"); } m_profileComboBox->setCurrentItem(0); if (list.contains(current) > 0) m_profileComboBox->setCurrentIndex(m_profileComboBox->findText(current, Qt::MatchExactly)); } void KExportDlg::slotOkClicked() { // Make sure we save the last used settings for use next time, writeConfig(); accept(); } void KExportDlg::readConfig() { KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup kgrp = kconfig->group("Last Use Settings"); m_qlineeditFile->setText(kgrp.readEntry("KExportDlg_LastFile")); m_qcheckboxAccount->setChecked(kgrp.readEntry("KExportDlg_AccountOpt", true)); m_qcheckboxCategories->setChecked(kgrp.readEntry("KExportDlg_CatOpt", true)); m_kmymoneydateStart->setDate(kgrp.readEntry("KExportDlg_StartDate", QDate())); m_kmymoneydateEnd->setDate(kgrp.readEntry("KExportDlg_EndDate", QDate())); // m_profileComboBox is loaded in loadProfiles(), so we don't worry here // m_accountComboBox is loaded in loadAccounts(), so we don't worry here } void KExportDlg::writeConfig() { KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("Last Use Settings"); grp.writeEntry("KExportDlg_LastFile", m_qlineeditFile->text()); grp.writeEntry("KExportDlg_AccountOpt", m_qcheckboxAccount->isChecked()); grp.writeEntry("KExportDlg_CatOpt", m_qcheckboxCategories->isChecked()); grp.writeEntry("KExportDlg_StartDate", QDateTime(m_kmymoneydateStart->date())); grp.writeEntry("KExportDlg_EndDate", QDateTime(m_kmymoneydateEnd->date())); grp.writeEntry("KExportDlg_LastProfile", m_profileComboBox->currentText()); kconfig->sync(); } void KExportDlg::checkData(const QString& accountId) { bool okEnabled = false; if (!m_qlineeditFile->text().isEmpty()) { - QString strFile(m_qlineeditFile->text()); - if (KMyMoneyUtils::appendCorrectFileExt(strFile, QString("qif"))) - m_qlineeditFile->setText(strFile); + auto strFile(m_qlineeditFile->text()); + if (!strFile.endsWith(QLatin1String(".qif"), Qt::CaseInsensitive)) + strFile.append(QLatin1String(".qif")); + m_qlineeditFile->setText(strFile); } MyMoneyAccount account; if (!accountId.isEmpty()) { MyMoneyFile* file = MyMoneyFile::instance(); account = file->account(accountId); if (m_lastAccount != accountId) { MyMoneyTransactionFilter filter(accountId); QList list = file->transactionList(filter); QList::Iterator it; if (!list.isEmpty()) { it = list.begin(); m_kmymoneydateStart->loadDate((*it).postDate()); it = list.end(); --it; m_kmymoneydateEnd->loadDate((*it).postDate()); } m_lastAccount = accountId; m_accountComboBox->setSelected(account.id()); } } if (!m_qlineeditFile->text().isEmpty() && !m_accountComboBox->getSelected().isEmpty() && !m_profileComboBox->currentText().isEmpty() && m_kmymoneydateStart->date() <= m_kmymoneydateEnd->date() && (m_qcheckboxAccount->isChecked() || m_qcheckboxCategories->isChecked())) okEnabled = true; m_qbuttonOk->setEnabled(okEnabled); } void KExportDlg::loadAccounts() { auto filterProxyModel = new AccountNamesFilterProxyModel(this); filterProxyModel->addAccountGroup(QVector {MyMoneyAccount::Asset, MyMoneyAccount::Liability}); auto const model = Models::instance()->accountsModel(); + model->load(); filterProxyModel->init(model, model->getColumns()); filterProxyModel->sort(AccountsModel::Account); m_accountComboBox->setModel(filterProxyModel); } QString KExportDlg::accountId() const { return m_lastAccount; } diff --git a/kmymoney/dialogs/kexportdlg.h b/kmymoney/plugins/qif/export/kexportdlg.h similarity index 98% rename from kmymoney/dialogs/kexportdlg.h rename to kmymoney/plugins/qif/export/kexportdlg.h index e23322902..38dc64a66 100644 --- a/kmymoney/dialogs/kexportdlg.h +++ b/kmymoney/plugins/qif/export/kexportdlg.h @@ -1,201 +1,196 @@ /*************************************************************************** kexportdlg.h - description ------------------- begin : Tue May 22 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KEXPORTDLG_H #define KEXPORTDLG_H // ---------------------------------------------------------------------------- // QT Headers #include #include // ---------------------------------------------------------------------------- // KDE Headers #include // ---------------------------------------------------------------------------- // Project Headers #include "kmymoneydateinput.h" #include "ui_kexportdlgdecl.h" /** * This class is used to select the required user input to export * a specified account to the popular QIF format. * It relies upon the QIF file handling routines in MyMoneyQifProfile and * MyMoneyQifWriter to do the actual writing of QIF files. * * It uses the global KConfig object to read and write the application * settings. * * @see MyMoneyAccount, MyMoneyQifProfile, MyMoneyQifProfileEditor * * @author Felix Rodriguez, Michael Edwardes, Thomas Baumgart 2000-2003 * * @short A class to select user data required to export a specified account to the popular QIF format. **/ class KExportDlgDecl : public QDialog, public Ui::KExportDlgDecl { public: KExportDlgDecl(QWidget *parent) : QDialog(parent) { setupUi(this); } }; class KExportDlg : public KExportDlgDecl { Q_OBJECT public: KExportDlg(QWidget *parent); ~KExportDlg(); /** * This method returns the filename entered into the edit field * * @return QString with filename */ const QString filename() const { return m_qlineeditFile->text(); }; /** * This method returns the account id that has been selected for export * * @return QString with account id */ QString accountId() const; /** * This method returns the name of the profile that has been selected * for the export operation * * @return QString with profile name */ const QString profile() const { return m_profileComboBox->currentText(); }; /** * This method returns the start date of the export dialog */ const QDate startDate() const { return m_kmymoneydateStart->date(); }; /** * This method returns the end date of the export dialog */ const QDate endDate() const { return m_kmymoneydateEnd->date(); }; /** * This method returns the state of the account checkbox */ bool accountSelected() const { return m_qcheckboxAccount->isChecked(); }; /** * This method returns the state of the account checkbox */ bool categorySelected() const { return m_qcheckboxCategories->isChecked(); }; protected slots: /** * Called when the user clicked on the OK button */ void slotOkClicked(); /** * Called when the user needs to browse the filesystem for a QIF file */ void slotBrowse(); - /** - * Called when the user needs a new profile - */ - void slotNewProfile(); - /** * This slot checks whether all data is correct to enable * the 'Export' button. The enable state of the 'Export' button * is updated appropriately. * * If the parameter @p account is not empty, then it is assumed * a new account is selected and the date fields will be loaded * with the date of the first and last transaction within this * account. * * @param account The id of the selected account. */ void checkData(const QString& account = QString()); private: void readConfig(); void writeConfig(); /** * This method loads the available profiles into * the combo box. The parameter @p selectLast controls if * the last profile used is preset or not. If preset is not * selected, the current selection remains. If the currently selected * text is not present in the list anymore, the first item will be * selected. * * @param selectLast If true, the last used profile is selected. The * default is false. */ void loadProfiles(const bool selectLast = false); /** * This method is used to load the available accounts into the * combo box for selection. */ void loadAccounts(); /** * This method is used to load an account hierarchy into a string list * * @param strList Reference to the string list to setup * @param id Account id to add * @param leadIn constant leadin to be added in front of the account name */ // void addCategories(QStringList& strList, const QString& id, const QString& leadIn) const; /** * This method is used to return the account id of a given account name * * @param account name of the account * @return the ID of the account will be returned. * See MyMoneyFile::nameToAccount() for details. */ // QString accountId(const QString& account) const; private: QString m_lastAccount; }; #endif diff --git a/kmymoney/plugins/qif/export/kexportdlgdecl.ui b/kmymoney/plugins/qif/export/kexportdlgdecl.ui new file mode 100644 index 000000000..a9af5182c --- /dev/null +++ b/kmymoney/plugins/qif/export/kexportdlgdecl.ui @@ -0,0 +1,459 @@ + + + KExportDlgDecl + + + + 0 + 0 + 655 + 468 + + + + QIF Export + + + true + + + true + + + + 6 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + From this dialog you are able to export transactions to a quicken compatible file, (known as a QIF file, because of the extension). Please enter the path to the QIF file or select it by clicking on the Browse button. + +You can choose the file's path, the account and the format of the QIF file (profile). Choose Account to export all the transactions between the specified dates or just categories. You can also limit the transactions that are exported by start and ending date. Once you have pressed the Export button a message box will appear when the export has completed detailing how many transactions, categories and payees were exported. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + QFrame::HLine + + + QFrame::Raised + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + File to export to: + + + false + + + + + + + + + + &Browse... + + + + + + + + + + + + + Account to export + + + false + + + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QIF Profile + + + false + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 200 + 0 + + + + false + + + + + + + + + + + + + + + Contents to Export + + + + 6 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + Account + + + + + + + Categories + + + + + + + + + + Date Range + + + + 6 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + End on: + + + false + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + Start on: + + + false + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 230 + 16 + + + + + + + + + + QFrame::HLine + + + QFrame::Sunken + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 451 + 0 + + + + + + + + &Export + + + true + + + true + + + + + + + Cancel + + + true + + + + + + + + + + + KLineEdit + QLineEdit +
klineedit.h
+
+ + KComboBox + QComboBox +
kcombobox.h
+
+ + kMyMoneyDateInput + QWidget +
../widgets/kmymoneydateinput.h
+
+ + KMyMoneyAccountCombo + QWidget +
../widgets/kmymoneyaccountcombo.h
+
+
+ + +
diff --git a/kmymoney/plugins/qif/export/kmm_qifexport.rc b/kmymoney/plugins/qif/export/kmm_qifexport.rc new file mode 100644 index 000000000..4bcee1cee --- /dev/null +++ b/kmymoney/plugins/qif/export/kmm_qifexport.rc @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/kmymoney/converter/mymoneyqifwriter.cpp b/kmymoney/plugins/qif/export/mymoneyqifwriter.cpp similarity index 100% rename from kmymoney/converter/mymoneyqifwriter.cpp rename to kmymoney/plugins/qif/export/mymoneyqifwriter.cpp diff --git a/kmymoney/converter/mymoneyqifwriter.h b/kmymoney/plugins/qif/export/mymoneyqifwriter.h similarity index 99% rename from kmymoney/converter/mymoneyqifwriter.h rename to kmymoney/plugins/qif/export/mymoneyqifwriter.h index 176b8c47a..e4aaa9de6 100644 --- a/kmymoney/converter/mymoneyqifwriter.h +++ b/kmymoney/plugins/qif/export/mymoneyqifwriter.h @@ -1,143 +1,144 @@ /*************************************************************************** mymoneyqifwriter.h - description ------------------- begin : Sun Jan 5 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYQIFWRITER_H #define MYMONEYQIFWRITER_H // ---------------------------------------------------------------------------- // QT Headers #include #include #include // ---------------------------------------------------------------------------- // KDE Headers // ---------------------------------------------------------------------------- // Project Headers +#include "../config/mymoneyqifprofile.h" + class MyMoneyTransaction; class MyMoneySplit; -#include "mymoneyqifprofile.h" /** * @author Thomas Baumgart */ /** * This class represents the QIF writer. All conversion between the * internal representation of accounts, transactions is handled in this * object. The conversion is controlled using a MyMoneyQifProfile to allow * the user to control the conversion. */ class MyMoneyQifWriter : public QObject { Q_OBJECT public: MyMoneyQifWriter(); ~MyMoneyQifWriter(); /** * This method is used to start the conversion. The parameters control * the destination of the data and the parts that will be exported. * Individual errors will be reported using message boxes. * * @param filename The name of the output file with full path information * @param profile The name of the profile to be used for conversion * @param accountId The id of the account that will be exported * @param accountData If true, the transactions will be exported * @param categoryData If true, the categories will be exported as well * @param startDate Transactions before this date will not be exported * @param endDate Transactions after this date will not be exported */ void write(const QString &filename, const QString &profile, const QString &accountId, const bool accountData, const bool categoryData, const QDate &startDate, const QDate &endDate); private: /** * This method writes the entries necessary for an account. First * the leadin, and then the transactions that are in the account * specified by @p accountId in the range from @p startDate to @p * endDate. * * @param s reference to textstream * @param accountId id of the account to be written * @param startDate date from which entries are written * @param endDate date until which entries are written */ void writeAccountEntry(QTextStream &s, const QString &accountId, const QDate &startDate, const QDate &endDate); /** * This method writes the category entries to the stream * @p s. It writes the leadin and uses writeCategoryEntries() * to write the entries and emits signalProgess() where needed. * * @param s reference to textstream */ void writeCategoryEntries(QTextStream &s); /** * This method writes the category entry for account with * the ID @p accountId to the stream @p s. All subaccounts * are processed as well. * * @param s reference to textstream * @param accountId id of the account to be written * @param leadIn constant text that will be prepended to the account's name */ void writeCategoryEntry(QTextStream &s, const QString &accountId, const QString &leadIn); void writeTransactionEntry(QTextStream &s, const MyMoneyTransaction &t, const QString &accountId); void writeSplitEntry(QTextStream &s, const MyMoneySplit &t); void extractInvestmentEntries(QTextStream &s, const QString &accountId, const QDate &startDate, const QDate &endDate); void writeInvestmentEntry(QTextStream &stream, const MyMoneyTransaction &t, const int count); signals: /** * This signal is emitted while the operation progresses. * When the operation starts, the signal is emitted with * @p current being 0 and @p max having the maximum value. * * During the operation, the signal is emitted with @p current * containing the current value on the way to the maximum value. * @p max will be 0 in this case. * * When the operation is finished, the signal is emitted with * @p current and @p max set to -1 to identify the end of the * operation. * * @param current see above * @param max see above */ void signalProgress(int current, int max); private: MyMoneyQifProfile m_qifProfile; }; #endif diff --git a/kmymoney/plugins/qif/export/qifexport.json.in b/kmymoney/plugins/qif/export/qifexport.json.in new file mode 100644 index 000000000..22f155a31 --- /dev/null +++ b/kmymoney/plugins/qif/export/qifexport.json.in @@ -0,0 +1,21 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "mte@users.sourceforge.net", + "Name": "Michael Edwardes" + } + ], + "Description": "Add QIF exporting to KMyMoney", + "EnabledByDefault": true, + "Icon": "document-export", + "Id": "qifexport", + "License": "GPL", + "Name": "QIF Exporter", + "ServiceTypes": [ + "KMyMoney/Plugin" + ], + "Version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@", + "Website": "https://kmymoney.org/plugins.html" + } +} diff --git a/kmymoney/plugins/qif/export/qifexporterplugin.cpp b/kmymoney/plugins/qif/export/qifexporterplugin.cpp new file mode 100644 index 000000000..0f6064447 --- /dev/null +++ b/kmymoney/plugins/qif/export/qifexporterplugin.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + qifimporterplugin.cpp + (based on qifimporterplugin.cpp) + ------------------- + + copyright : (C) 2017 by Łukasz Wojniłowicz + 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 "qifexporterplugin.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kexportdlg.h" + +#include "mymoneyqifwriter.h" + + +QIFExporterPlugin::QIFExporterPlugin() : + KMyMoneyPlugin::Plugin(nullptr, "qifexport"/*must be the same as X-KDE-PluginInfo-Name*/) +{ + setComponentName("kmm_qifexport", i18n("QIF exporter")); + setXMLFile("kmm_qifexport.rc"); + createActions(); + // For information, announce that we have been loaded. + qDebug("KMyMoney qifexport plugin loaded"); +} + +QIFExporterPlugin::~QIFExporterPlugin() +{ +} + +void QIFExporterPlugin::createActions() +{ + m_action = actionCollection()->addAction("file_export_qif"); + m_action->setText(i18n("QIF...")); + connect(m_action, &QAction::triggered, this, &QIFExporterPlugin::slotQifExport); +} + + +void QIFExporterPlugin::slotQifExport() +{ + m_action->setEnabled(false); + QPointer dlg = new KExportDlg(nullptr); + if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { +// if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) { + MyMoneyQifWriter writer; + connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); + + writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), + dlg->accountSelected(), dlg->categorySelected(), + dlg->startDate(), dlg->endDate()); +// } + } + delete dlg; + m_action->setEnabled(true); +} + +//#include "qifexporterplugin.moc" diff --git a/kmymoney/plugins/qif/export/qifexporterplugin.h b/kmymoney/plugins/qif/export/qifexporterplugin.h new file mode 100644 index 000000000..64d0b53af --- /dev/null +++ b/kmymoney/plugins/qif/export/qifexporterplugin.h @@ -0,0 +1,57 @@ +/*************************************************************************** + qifexporterplugin.h + ------------------- + copyright : (C) 2017 by Łukasz Wojniłowicz + 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 QIFEXPORTERPLUGIN_H +#define QIFEXPORTERPLUGIN_H + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// QT Includes + +// Project Includes + +#include "kmymoneyplugin.h" + +class MyMoneyQifReader; + +class QIFExporterPlugin : public KMyMoneyPlugin::Plugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kmymoney.plugins.qifexport" FILE "qifexport.json") + +public: + explicit QIFExporterPlugin(); + ~QIFExporterPlugin(); + + QAction *m_action; + + MyMoneyQifReader *m_qifReader; + +public slots: + /** + * Called when the user wishes to export some transaction to a + * QIF formatted file. An account must be open for this to work. + * Uses MyMoneyQifWriter() for the actual output. + */ + void slotQifExport(); + +protected: + void createActions(); +}; + +#endif diff --git a/kmymoney/plugins/qif/import/CMakeLists.txt b/kmymoney/plugins/qif/import/CMakeLists.txt new file mode 100644 index 000000000..f583c3fdd --- /dev/null +++ b/kmymoney/plugins/qif/import/CMakeLists.txt @@ -0,0 +1,32 @@ +# patch the version with the version defined in the build system +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/qifimport.json.in ${CMAKE_CURRENT_BINARY_DIR}/qifimport.json @ONLY) + +########### next target ############### + +set(kmm_qifimport_PART_SRCS + qifimporterplugin.cpp + ../config/mymoneyqifprofile.cpp + mymoneyqifreader.cpp + kimportdlg.cpp +) + +set(kmm_qifimport_PART_UI + kimportdlgdecl.ui +) + +ki18n_wrap_ui(kmm_qifimport_PART_SRCS ${kmm_qifimport_PART_UI}) + +add_library(kmm_qifimport MODULE ${kmm_qifimport_PART_SRCS}) + +target_link_libraries(kmm_qifimport + kmm_plugin + Alkimia::alkimia +) + +########### install files ############### + +install(FILES kmm_qifimport.rc + DESTINATION "${KXMLGUI_INSTALL_DIR}/kmm_qifimport") + +install(TARGETS kmm_qifimport + DESTINATION "${KDE_INSTALL_PLUGINDIR}/kmymoney/") diff --git a/kmymoney/dialogs/kimportdlg.cpp b/kmymoney/plugins/qif/import/kimportdlg.cpp similarity index 89% rename from kmymoney/dialogs/kimportdlg.cpp rename to kmymoney/plugins/qif/import/kimportdlg.cpp index 27efe727e..d3ced41c0 100644 --- a/kmymoney/dialogs/kimportdlg.cpp +++ b/kmymoney/plugins/qif/import/kimportdlg.cpp @@ -1,217 +1,196 @@ /*************************************************************************** kimportdlg.cpp - description ------------------- begin : Wed May 16 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kimportdlg.h" // ---------------------------------------------------------------------------- // QT Headers #include #include #include #include #include +#include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Headers #include "kmymoneyutils.h" #include #include -#include "mymoneyqifprofileeditor.h" -#include "mymoneyqifprofile.h" +#include "../config/mymoneyqifprofile.h" #include using namespace Icons; KImportDlg::KImportDlg(QWidget *parent) : KImportDlgDecl(parent) { // Set all the last used options readConfig(); loadProfiles(true); // load button icons KGuiItem okButtenItem(i18n("&Import"), QIcon::fromTheme(g_Icons[Icon::DocumentImport]), i18n("Start operation"), i18n("Use this to start the import operation")); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Ok), okButtenItem); KGuiItem browseButtenItem(i18n("&Browse..."), QIcon::fromTheme(g_Icons[Icon::DocumentOpen]), i18n("Select filename"), i18n("Use this to select a filename to export to")); KGuiItem::assign(m_qbuttonBrowse, browseButtenItem); KGuiItem newButtenItem(i18nc("New profile", "&New..."), QIcon::fromTheme(g_Icons[Icon::DocumentNew]), i18n("Create a new profile"), i18n("Use this to open the profile editor")); - KGuiItem::assign(m_profileEditorButton, newButtenItem); // connect the buttons to their functionality connect(m_qbuttonBrowse, SIGNAL(clicked()), this, SLOT(slotBrowse())); connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(slotOkClicked())); connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - connect(m_profileEditorButton, SIGNAL(clicked()), this, SLOT(slotNewProfile())); // connect the change signals to the check slot and perform initial check connect(m_qlineeditFile, SIGNAL(textChanged(QString)), this, SLOT(slotFileTextChanged(QString))); // setup button enable status slotFileTextChanged(m_qlineeditFile->text()); } KImportDlg::~KImportDlg() { } void KImportDlg::slotBrowse() { // determine what the browse prefix should be from the current profile MyMoneyQifProfile tmpprofile; tmpprofile.loadProfile("Profile-" + profile()); QUrl file = QFileDialog::getOpenFileUrl(this, i18n("Import File..."), QUrl("kfiledialog:///kmymoney-import"), i18n("Import files (%1);;All files (%2)", tmpprofile.filterFileType(), "*") ); if (!file.isEmpty()) { m_qlineeditFile->setText(file.toDisplayString(QUrl::PreferLocalFile)); } } void KImportDlg::slotOkClicked() { // Save the used options. writeConfig(); // leave dialog directly accept(); } void KImportDlg::readConfig() { KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup kgrp = kconfig->group("Last Use Settings"); m_qlineeditFile->setText(kgrp.readEntry("KImportDlg_LastFile")); } void KImportDlg::writeConfig() { KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("Last Use Settings"); grp.writeEntry("KImportDlg_LastFile", m_qlineeditFile->text()); grp.writeEntry("KImportDlg_LastProfile", m_profileComboBox->currentText()); kconfig->sync(); } /** Make sure the text input is ok */ void KImportDlg::slotFileTextChanged(const QString& text) { // TODO: port to kf5 Q_UNUSED(text) #if 0 if (!text.isEmpty() && KIO::NetAccess::exists(file(), KIO::NetAccess::SourceSide, KMyMoneyUtils::mainWindow())) { // m_qcomboboxDateFormat->setEnabled(true); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); m_qlineeditFile->setText(text); } else { // m_qcomboboxDateFormat->setEnabled(false); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } #endif } -void KImportDlg::slotNewProfile() -{ - QPointer editor = new MyMoneyQifProfileEditor(true, this); - editor->setObjectName("QIF Profile Editor"); - - if (editor->exec()) { - loadProfiles(); - m_profileComboBox->setCurrentIndex(m_profileComboBox->findText(editor->selectedProfile(), Qt::MatchExactly)); - } - - delete editor; -} - void KImportDlg::loadProfiles(const bool selectLast) { - // Creating an editor object here makes sure that - // we have at least the default profile available - MyMoneyQifProfileEditor* edit = new MyMoneyQifProfileEditor(true, 0); - edit->slotOk(); - delete edit; - QString current = m_profileComboBox->currentText(); m_profileComboBox->clear(); QStringList list; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Profiles"); list = grp.readEntry("profiles", QStringList()); list.sort(); m_profileComboBox->addItems(list); if (selectLast == true) { config->group("Last Use Settings"); current = grp.readEntry("KImportDlg_LastProfile"); } int index = m_profileComboBox->findText(current, Qt::MatchExactly); if (index > -1) { m_profileComboBox->setCurrentIndex(index); } else { m_profileComboBox->setCurrentIndex(0); } } void KImportDlg::addCategories(QStringList& strList, const QString& id, const QString& leadIn) const { MyMoneyFile *file = MyMoneyFile::instance(); QString name; MyMoneyAccount account = file->account(id); QStringList accList = account.accountList(); QStringList::ConstIterator it_a; for (it_a = accList.constBegin(); it_a != accList.constEnd(); ++it_a) { account = file->account(*it_a); strList << leadIn + account.name(); addCategories(strList, *it_a, leadIn + account.name() + MyMoneyFile::AccountSeperator); } } diff --git a/kmymoney/dialogs/kimportdlg.h b/kmymoney/plugins/qif/import/kimportdlg.h similarity index 97% rename from kmymoney/dialogs/kimportdlg.h rename to kmymoney/plugins/qif/import/kimportdlg.h index e46990b7e..c09e373e2 100644 --- a/kmymoney/dialogs/kimportdlg.h +++ b/kmymoney/plugins/qif/import/kimportdlg.h @@ -1,128 +1,123 @@ /*************************************************************************** kimportdlg.h - description ------------------- begin : Wed May 16 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez ***************************************************************************/ /*************************************************************************** * * * 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 KIMPORTDLG_H #define KIMPORTDLG_H // ---------------------------------------------------------------------------- // Qt Headers #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include // ---------------------------------------------------------------------------- // Project Headers #include "ui_kimportdlgdecl.h" /** * This class is used to import a qif file to an account. * It relies upon the QIF file handling routines in MyMoneyAccount to do * the actual writing of QIF files. * * It uses the global KConfig object to read and write the application * settings. * * @see MyMoneyAccount * * @author Felix Rodriguez, Michael Edwardes 2000-2001 * * @short A class to import a qif file to an account. **/ class KImportDlgDecl : public QDialog, public Ui::KImportDlgDecl { public: KImportDlgDecl(QWidget *parent) : QDialog(parent) { setupUi(this); } }; class KImportDlg : public KImportDlgDecl { Q_OBJECT public: /** * Standard constructor */ KImportDlg(QWidget *parent); /** Standard destructor */ ~KImportDlg(); /** */ const QUrl file() const { return QUrl::fromUserInput(m_qlineeditFile->text()); }; /** */ const QString profile() const { return m_profileComboBox->currentText(); }; protected slots: /** Called to let the user browse for a QIF file to import from. */ void slotBrowse(); /** Test whether to enable the buttons */ void slotFileTextChanged(const QString& text); - /** - * Called when the user needs a new profile - */ - void slotNewProfile(); - void slotOkClicked(); private: /** * This method loads the available profiles into * the combo box. The parameter @p selectLast controls if * the last profile used is preset or not. If preset is not * selected, the current selection remains. If the currently selected * text is not present in the list anymore, the first item will be * selected. * * @param selectLast If true, the last used profile is selected. The * default is false. */ void loadProfiles(const bool selectLast = false); /** * This method is used to load an account hierarchy into a string list * * @param strList Reference to the string list to setup * @param id Account id to add * @param leadIn constant leadin to be added in front of the account name */ void addCategories(QStringList& strList, const QString& id, const QString& leadIn) const; void readConfig(); void writeConfig(); }; #endif diff --git a/kmymoney/dialogs/kimportdlgdecl.ui b/kmymoney/plugins/qif/import/kimportdlgdecl.ui similarity index 94% rename from kmymoney/dialogs/kimportdlgdecl.ui rename to kmymoney/plugins/qif/import/kimportdlgdecl.ui index e61bf882c..e45922477 100644 --- a/kmymoney/dialogs/kimportdlgdecl.ui +++ b/kmymoney/plugins/qif/import/kimportdlgdecl.ui @@ -1,196 +1,189 @@ KImportDlgDecl - - Qt::WindowModal - 0 0 595 464 QIF Import true + + true + 0 0 Qt::NoFocus <p>From this dialog you are able to import transactions from a Quicken<b>&trade;</b> compatible file, (known as a QIF file, because of the extension).</p> <p>Please enter the path to the QIF file or select it by clicking on the Browse button. Once you have the file's path press the Import button and KMyMoney will import all the transactions, categories and payees it finds.</p> true 0 0 QIF File to Import: false &Browse... Import options - - + + - QIF Profile + Source of QIF false - - - - - 0 - 0 - - - - - 200 - 0 - - - - false - - - - - - - New... - - - - - + + - Source of QIF + QIF Profile false Select <b>Bank statement</b> turns on automatic category matching which is turned off in case of <b>Other application</b>. Use the latter if you import files from other Personal Finance Management software. Bank statement Other application + + + + + 0 + 0 + + + + + 200 + 0 + + + + false + + + Qt::Vertical 20 47 QFrame::HLine QFrame::Raised QDialogButtonBox::Cancel|QDialogButtonBox::Ok m_optionGroupBox qLabel1 Line1 m_buttonBox KLineEdit QLineEdit
klineedit.h
KComboBox QComboBox
kcombobox.h
diff --git a/kmymoney/plugins/qif/import/kmm_qifimport.rc b/kmymoney/plugins/qif/import/kmm_qifimport.rc new file mode 100644 index 000000000..b68b487d2 --- /dev/null +++ b/kmymoney/plugins/qif/import/kmm_qifimport.rc @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/kmymoney/converter/mymoneyqifreader.cpp b/kmymoney/plugins/qif/import/mymoneyqifreader.cpp similarity index 88% rename from kmymoney/converter/mymoneyqifreader.cpp rename to kmymoney/plugins/qif/import/mymoneyqifreader.cpp index d05fe65dc..117abe748 100644 --- a/kmymoney/converter/mymoneyqifreader.cpp +++ b/kmymoney/plugins/qif/import/mymoneyqifreader.cpp @@ -1,2237 +1,2070 @@ /*************************************************************************** mymoneyqifreader.cpp ------------------- begin : Mon Jan 27 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyqifreader.h" // ---------------------------------------------------------------------------- // QT Headers #include #include #include #include #include #include #include +#include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include #include "kjobwidgets.h" #include "kio/job.h" // ---------------------------------------------------------------------------- // Project Headers #include "mymoneyfile.h" -#include "kaccountselectdlg.h" -#include "kmymoney.h" #include "kmymoneyglobalsettings.h" -#include "mymoneystatementreader.h" #include // define this to debug the code. Using external filters // while debugging did not work too good for me, so I added // this code. // #define DEBUG_IMPORT #ifdef DEBUG_IMPORT #ifdef __GNUC__ #warning "DEBUG_IMPORT defined --> external filter not available!!!!!!!" #endif #endif class MyMoneyQifReader::Private { public: Private() : accountType(MyMoneyAccount::Checkings), mapCategories(true) {} const QString accountTypeToQif(MyMoneyAccount::accountTypeE type) const; /** * finalize the current statement and add it to the statement list */ void finishStatement(); bool isTransfer(QString& name, const QString& leftDelim, const QString& rightDelim); /** * Converts the QIF specific N-record of investment transactions into * a category name */ const QString typeToAccountName(const QString& type) const; /** * Converts the QIF reconcile state to the KMyMoney reconcile state */ MyMoneySplit::reconcileFlagE reconcileState(const QString& state) const; /** */ void fixMultiLineMemo(QString& memo) const; public: /** * the statement that is currently collected/processed */ MyMoneyStatement st; /** * the list of all statements to be sent to MyMoneyStatementReader */ QList statements; /** * a list of already used hashes in this file */ QMap m_hashMap; QString st_AccountName; QString st_AccountId; MyMoneyAccount::accountTypeE accountType; bool firstTransaction; bool mapCategories; MyMoneyQifReader::QifEntryTypeE transactionType; }; void MyMoneyQifReader::Private::fixMultiLineMemo(QString& memo) const { memo.replace("\\n", "\n"); } void MyMoneyQifReader::Private::finishStatement() { // in case we have collected any data in the statement, we keep it if ((st.m_listTransactions.count() + st.m_listPrices.count() + st.m_listSecurities.count()) > 0) { statements += st; qDebug("Statement with %d transactions, %d prices and %d securities added to the statement list", st.m_listTransactions.count(), st.m_listPrices.count(), st.m_listSecurities.count()); } MyMoneyStatement::EType type = st.m_eType; //stash type and... // start with a fresh statement st = MyMoneyStatement(); st.m_skipCategoryMatching = !mapCategories; st.m_eType = type; } const QString MyMoneyQifReader::Private::accountTypeToQif(MyMoneyAccount::accountTypeE type) const { QString rc = "Bank"; switch (type) { default: break; case MyMoneyAccount::Cash: rc = "Cash"; break; case MyMoneyAccount::CreditCard: rc = "CCard"; break; case MyMoneyAccount::Asset: rc = "Oth A"; break; case MyMoneyAccount::Liability: rc = "Oth L"; break; case MyMoneyAccount::Investment: rc = "Port"; break; } return rc; } const QString MyMoneyQifReader::Private::typeToAccountName(const QString& type) const { if (type == "reinvint") return i18nc("Category name", "Reinvested interest"); if (type == "reinvdiv") return i18nc("Category name", "Reinvested dividend"); if (type == "reinvlg") return i18nc("Category name", "Reinvested dividend (long term)"); if (type == "reinvsh") return i18nc("Category name", "Reinvested dividend (short term)"); if (type == "div") return i18nc("Category name", "Dividend"); if (type == "intinc") return i18nc("Category name", "Interest"); if (type == "cgshort") return i18nc("Category name", "Capital Gain (short term)"); if (type == "cgmid") return i18nc("Category name", "Capital Gain (mid term)"); if (type == "cglong") return i18nc("Category name", "Capital Gain (long term)"); if (type == "rtrncap") return i18nc("Category name", "Returned capital"); if (type == "miscinc") return i18nc("Category name", "Miscellaneous income"); if (type == "miscexp") return i18nc("Category name", "Miscellaneous expense"); if (type == "sell" || type == "buy") return i18nc("Category name", "Investment fees"); return i18n("Unknown QIF type %1", type); } bool MyMoneyQifReader::Private::isTransfer(QString& tmp, const QString& leftDelim, const QString& rightDelim) { // it's a transfer, extract the account name // I've seen entries like this // // S[Mehrwertsteuer]/_VATCode_N_I (The '/' is the Quicken class symbol) // // so extracting is a bit more complex and we use a regexp for it QRegExp exp(QString("\\%1(.*)\\%2(.*)").arg(leftDelim, rightDelim)); bool rc; if ((rc = (exp.indexIn(tmp) != -1)) == true) { tmp = exp.cap(1) + exp.cap(2); tmp = tmp.trimmed(); } return rc; } MyMoneySplit::reconcileFlagE MyMoneyQifReader::Private::reconcileState(const QString& state) const { if (state == "X" || state == "R") // Reconciled return MyMoneySplit::Reconciled; if (state == "*") // Cleared return MyMoneySplit::Cleared; return MyMoneySplit::NotReconciled; } MyMoneyQifReader::MyMoneyQifReader() : d(new Private) { m_skipAccount = false; m_transactionsProcessed = m_transactionsSkipped = 0; m_progressCallback = 0; m_file = 0; m_entryType = EntryUnknown; m_processingData = false; m_userAbort = false; m_warnedInvestment = false; m_warnedSecurity = false; m_warnedPrice = false; connect(&m_filter, SIGNAL(bytesWritten(qint64)), this, SLOT(slotSendDataToFilter())); connect(&m_filter, SIGNAL(readyReadStandardOutput()), this, SLOT(slotReceivedDataFromFilter())); connect(&m_filter, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotImportFinished())); connect(&m_filter, SIGNAL(readyReadStandardError()), this, SLOT(slotReceivedErrorFromFilter())); } MyMoneyQifReader::~MyMoneyQifReader() { delete m_file; delete d; } void MyMoneyQifReader::setCategoryMapping(bool map) { d->mapCategories = map; } void MyMoneyQifReader::setURL(const QUrl &url) { m_url = url; } void MyMoneyQifReader::setProfile(const QString& profile) { m_qifProfile.loadProfile("Profile-" + profile); } void MyMoneyQifReader::slotSendDataToFilter() { long len; if (m_file->atEnd()) { m_filter.closeWriteChannel(); } else { len = m_file->read(m_buffer, sizeof(m_buffer)); if (len == -1) { qWarning("Failed to read block from QIF import file"); m_filter.closeWriteChannel(); m_filter.kill(); } else { m_filter.write(m_buffer, len); } } } void MyMoneyQifReader::slotReceivedErrorFromFilter() { qWarning("%s", qPrintable(QString(m_filter.readAllStandardError()))); } void MyMoneyQifReader::slotReceivedDataFromFilter() { parseReceivedData(m_filter.readAllStandardOutput()); } void MyMoneyQifReader::parseReceivedData(const QByteArray& data) { const char* buff = data.data(); int len = data.length(); m_pos += len; // signalProgress(m_pos, 0); while (len) { // process char if (*buff == '\n' || *buff == '\r') { // found EOL if (!m_lineBuffer.isEmpty()) { m_qifLines << QString::fromUtf8(m_lineBuffer.trimmed()); } m_lineBuffer = QByteArray(); } else { // collect all others m_lineBuffer += (*buff); } ++buff; --len; } } void MyMoneyQifReader::slotImportFinished() { // check if the last EOL char was missing and add the trailing line if (!m_lineBuffer.isEmpty()) { m_qifLines << QString::fromUtf8(m_lineBuffer.trimmed()); } qDebug("Read %ld bytes", m_pos); QTimer::singleShot(0, this, SLOT(slotProcessData())); } void MyMoneyQifReader::slotProcessData() { signalProgress(-1, -1); // scan the file and try to determine numeric and date formats m_qifProfile.autoDetect(m_qifLines); // the detection is accurate for numeric values, but it could be // that the dates were too ambiguous so that we have to let the user // decide which one to pick. QStringList dateFormats; m_qifProfile.possibleDateFormats(dateFormats); QString format; if (dateFormats.count() > 1) { bool ok; format = QInputDialog::getItem(0, i18n("Date format selection"), i18n("Pick the date format that suits your input file"), dateFormats, 05, false, &ok); if (!ok) { m_userAbort = true; } } else format = dateFormats.first(); if (!format.isEmpty()) { m_qifProfile.setInputDateFormat(format); qDebug("Selected date format: '%s'", qPrintable(format)); } else { // cancel the process because there is probably nothing to work with m_userAbort = true; } signalProgress(0, m_qifLines.count(), i18n("Importing QIF...")); QStringList::iterator it; for (it = m_qifLines.begin(); m_userAbort == false && it != m_qifLines.end(); ++it) { ++m_linenumber; // qDebug("Proc: '%s'", (*it).data()); if ((*it).startsWith('!')) { processQifSpecial(*it); m_qifEntry.clear(); } else if (*it == "^") { if (m_qifEntry.count() > 0) { signalProgress(m_linenumber, 0); processQifEntry(); m_qifEntry.clear(); } } else { m_qifEntry += *it; } } d->finishStatement(); qDebug("%d lines processed", m_linenumber); signalProgress(-1, -1); - emit importFinished(); + emit statementsReady(d->statements); } bool MyMoneyQifReader::startImport() { bool rc = false; d->st = MyMoneyStatement(); d->st.m_skipCategoryMatching = !d->mapCategories; m_dontAskAgain.clear(); m_accountTranslation.clear(); m_userAbort = false; m_pos = 0; m_linenumber = 0; m_filename.clear(); m_data.clear(); if (m_url.isEmpty()) { return rc; } else if (m_url.isLocalFile()) { m_filename = m_url.toLocalFile(); } else { m_filename = QDir::tempPath(); if(!m_filename.endsWith(QDir::separator())) m_filename += QDir::separator(); m_filename += m_url.fileName(); qDebug() << "Source:" << m_url.toDisplayString() << "Destination:" << m_filename; KIO::FileCopyJob *job = KIO::file_copy(m_url, QUrl::fromUserInput(m_filename), -1, KIO::Overwrite); - KJobWidgets::setWindow(job, kmymoney); +// KJobWidgets::setWindow(job, kmymoney); job->exec(); if (job->error()) { KMessageBox::detailedError(0, i18n("Error while loading file '%1'.", m_url.toDisplayString()), job->errorString(), i18n("File access error")); return rc; } } m_file = new QFile(m_filename); if (m_file->open(QIODevice::ReadOnly)) { #ifdef DEBUG_IMPORT qint64 len; while (!m_file->atEnd()) { len = m_file->read(m_buffer, sizeof(m_buffer)); if (len == -1) { qWarning("Failed to read block from QIF import file"); } else { parseReceivedData(QByteArray(m_buffer, len)); } } QTimer::singleShot(0, this, SLOT(slotImportFinished())); rc = true; #else QString program; QStringList arguments; program.clear(); arguments.clear(); // start filter process, use 'cat -' as the default filter if (m_qifProfile.filterScriptImport().isEmpty()) { #ifdef Q_OS_WIN32 //krazy:exclude=cpp // this is the Windows equivalent of 'cat -' but since 'type' does not work with stdin // we pass the filename converted to native separators as a parameter program = "cmd.exe"; arguments << "/c"; arguments << "type"; arguments << QDir::toNativeSeparators(m_filename); #else program = "cat"; arguments << "-"; #endif } else { arguments << m_qifProfile.filterScriptImport().split(' ', QString::KeepEmptyParts); } m_entryType = EntryUnknown; m_filter.setProcessChannelMode(QProcess::MergedChannels); m_filter.start(program, arguments); if (m_filter.waitForStarted()) { signalProgress(0, m_file->size(), i18n("Reading QIF...")); slotSendDataToFilter(); rc = true; +// emit statementsReady(d->statements); } else { KMessageBox::detailedError(0, i18n("Error while running the filter '%1'.", m_filter.program()), m_filter.errorString(), i18n("Filter error")); } #endif } return rc; } -bool MyMoneyQifReader::finishImport() -{ - bool rc = false; - -#ifdef DEBUG_IMPORT - delete m_file; - m_file = 0; - - // remove the Don't ask again entries - KSharedConfigPtr config = KSharedConfig::openConfig(); - KConfigGroup grp = config->group(QString::fromLatin1("Notification Messages")); - QStringList::ConstIterator it; - - for (it = m_dontAskAgain.begin(); it != m_dontAskAgain.end(); ++it) { - grp.deleteEntry(*it); - } - grp.sync(); - m_dontAskAgain.clear(); - m_accountTranslation.clear(); - - signalProgress(-1, -1); - rc = !m_userAbort; - -#else - if (QProcess::Running != m_filter.state()) { - delete m_file; - m_file = 0; - - // remove the Don't ask again entries - KSharedConfigPtr config = KSharedConfig::openConfig(); - KConfigGroup grp = config->group(QString::fromLatin1("Notification Messages")); - QStringList::ConstIterator it; - - for (it = m_dontAskAgain.constBegin(); it != m_dontAskAgain.constEnd(); ++it) { - grp.deleteEntry(*it); - } - grp.sync(); - m_dontAskAgain.clear(); - m_accountTranslation.clear(); - - signalProgress(-1, -1); - rc = !m_userAbort && QProcess::NormalExit == m_filter.exitStatus(); - } else { - qWarning("MyMoneyQifReader::finishImport() must not be called while the filter\n\tprocess is still running."); - } -#endif - - // if a temporary file was constructed by NetAccess::download, - // then it will be removed with the next call. Otherwise, it - // stays untouched on the local filesystem - if(!m_url.isLocalFile()) - KIO::file_delete(QUrl::fromUserInput(m_filename)); - - // Now to import the statements - QList::const_iterator it_st; - for (it_st = d->statements.constBegin(); it_st != d->statements.constEnd(); ++it_st) - kmymoney->slotStatementImport(*it_st); - return rc; -} - void MyMoneyQifReader::processQifSpecial(const QString& _line) { QString line = _line.mid(1); // get rid of exclamation mark if (line.left(5).toLower() == QString("type:")) { line = line.mid(5); // exportable accounts if (line.toLower() == "ccard" || KMyMoneyGlobalSettings::qifCreditCard().toLower().contains(line.toLower())) { d->accountType = MyMoneyAccount::CreditCard; d->firstTransaction = true; d->transactionType = m_entryType = EntryTransaction; } else if (line.toLower() == "bank" || KMyMoneyGlobalSettings::qifBank().toLower().contains(line.toLower())) { d->accountType = MyMoneyAccount::Checkings; d->firstTransaction = true; d->transactionType = m_entryType = EntryTransaction; } else if (line.toLower() == "cash" || KMyMoneyGlobalSettings::qifCash().toLower().contains(line.toLower())) { d->accountType = MyMoneyAccount::Cash; d->firstTransaction = true; d->transactionType = m_entryType = EntryTransaction; } else if (line.toLower() == "oth a" || KMyMoneyGlobalSettings::qifAsset().toLower().contains(line.toLower())) { d->accountType = MyMoneyAccount::Asset; d->firstTransaction = true; d->transactionType = m_entryType = EntryTransaction; } else if (line.toLower() == "oth l" || line.toLower() == i18nc("QIF tag for liability account", "Oth L").toLower()) { d->accountType = MyMoneyAccount::Liability; d->firstTransaction = true; d->transactionType = m_entryType = EntryTransaction; } else if (line.toLower() == "invst" || line.toLower() == i18nc("QIF tag for investment account", "Invst").toLower()) { d->accountType = MyMoneyAccount::Investment; d->transactionType = m_entryType = EntryInvestmentTransaction; } else if (line.toLower() == "invoice" || KMyMoneyGlobalSettings::qifInvoice().toLower().contains(line.toLower())) { m_entryType = EntrySkip; } else if (line.toLower() == "tax") { m_entryType = EntrySkip; } else if (line.toLower() == "bill") { m_entryType = EntrySkip; // exportable lists } else if (line.toLower() == "cat" || line.toLower() == i18nc("QIF tag for category", "Cat").toLower()) { m_entryType = EntryCategory; } else if (line.toLower() == "security" || line.toLower() == i18nc("QIF tag for security", "Security").toLower()) { m_entryType = EntrySecurity; } else if (line.toLower() == "prices" || line.toLower() == i18nc("QIF tag for prices", "Prices").toLower()) { m_entryType = EntryPrice; } else if (line.toLower() == "payee") { m_entryType = EntryPayee; } else if (line.toLower() == "memorized") { m_entryType = EntryMemorizedTransaction; } else if (line.toLower() == "class" || line.toLower() == i18nc("QIF tag for a class", "Class").toLower()) { m_entryType = EntryClass; } else if (line.toLower() == "budget") { m_entryType = EntrySkip; } else if (line.toLower() == "invitem") { m_entryType = EntrySkip; } else if (line.toLower() == "template") { m_entryType = EntrySkip; } else { qWarning("Unknown type code '%s' in QIF file on line %d", qPrintable(line), m_linenumber); m_entryType = EntrySkip; } // option headers } else if (line.toLower() == "account") { m_entryType = EntryAccount; } else if (line.toLower() == "option:autoswitch") { m_entryType = EntryAccount; } else if (line.toLower() == "clear:autoswitch") { m_entryType = d->transactionType; } } void MyMoneyQifReader::processQifEntry() { // This method processes a 'QIF Entry' which is everything between two caret // signs // try { switch (m_entryType) { case EntryCategory: processCategoryEntry(); break; case EntryUnknown: qDebug() << "Line " << m_linenumber << ": Warning: Found an entry without a type being specified. Checking assumed."; processTransactionEntry(); break; case EntryTransaction: processTransactionEntry(); break; case EntryInvestmentTransaction: processInvestmentTransactionEntry(); break; case EntryAccount: processAccountEntry(); break; case EntrySecurity: processSecurityEntry(); break; case EntryPrice: processPriceEntry(); break; case EntryPayee: processPayeeEntry(); break; case EntryClass: qDebug() << "Line " << m_linenumber << ": Classes are not yet supported!"; break; case EntryMemorizedTransaction: qDebug() << "Line " << m_linenumber << ": Memorized transactions are not yet implemented!"; break; case EntrySkip: break; default: qDebug() << "Line " << m_linenumber << ": EntryType " << m_entryType << " not yet implemented!"; break; } } catch (const MyMoneyException &e) { if (e.what() != "USERABORT") { qDebug() << "Line " << m_linenumber << ": Unhandled error: " << e.what(); } else { m_userAbort = true; } } } const QString MyMoneyQifReader::extractLine(const QChar& id, int cnt) { QStringList::ConstIterator it; m_extractedLine = -1; for (it = m_qifEntry.constBegin(); it != m_qifEntry.constEnd(); ++it) { ++m_extractedLine; if ((*it)[0] == id) { if (cnt-- == 1) { return (*it).mid(1); } } } m_extractedLine = -1; return QString(); } bool MyMoneyQifReader::extractSplits(QList& listqSplits) const { // *** With apologies to QString MyMoneyQifReader::extractLine *** QStringList::ConstIterator it; bool ret = false; bool memoPresent = false; int neededCount = 0; qSplit q; for (it = m_qifEntry.constBegin(); it != m_qifEntry.constEnd(); ++it) { if (((*it)[0] == 'S') || ((*it)[0] == '$') || ((*it)[0] == 'E')) { memoPresent = false; // in case no memo line in this split if ((*it)[0] == 'E') { q.m_strMemo = (*it).mid(1); // 'E' = Memo d->fixMultiLineMemo(q.m_strMemo); memoPresent = true; // This transaction contains memo } else if ((*it)[0] == 'S') { q.m_strCategoryName = (*it).mid(1); // 'S' = CategoryName neededCount ++; } else if ((*it)[0] == '$') { q.m_amount = (*it).mid(1); // '$' = Amount neededCount ++; } if (neededCount > 1) { // CategoryName & Amount essential listqSplits += q; // Add valid split if (!memoPresent) { // If no memo, clear previous q.m_strMemo.clear(); } qSplit q; // Start new split neededCount = 0; ret = true; } } } return ret; } #if 0 void MyMoneyQifReader::processMSAccountEntry(const MyMoneyAccount::accountTypeE accountType) { if (extractLine('P').toLower() == m_qifProfile.openingBalanceText().toLower()) { m_account = MyMoneyAccount(); m_account.setAccountType(accountType); QString txt = extractLine('T'); MyMoneyMoney balance = m_qifProfile.value('T', txt); QDate date = m_qifProfile.date(extractLine('D')); m_account.setOpeningDate(date); QString name = extractLine('L'); if (name.left(1) == m_qifProfile.accountDelimiter().left(1)) { name = name.mid(1, name.length() - 2); } d->st_AccountName = name; m_account.setName(name); selectOrCreateAccount(Select, m_account, balance); d->st.m_accountId = m_account.id(); if (! balance.isZero()) { MyMoneyFile* file = MyMoneyFile::instance(); QString openingtxid = file->openingBalanceTransaction(m_account); MyMoneyFileTransaction ft; if (! openingtxid.isEmpty()) { MyMoneyTransaction openingtx = file->transaction(openingtxid); MyMoneySplit split = openingtx.splitByAccount(m_account.id()); if (split.shares() != balance) { const MyMoneySecurity& sec = file->security(m_account.currencyId()); if (KMessageBox::questionYesNo( KMyMoneyUtils::mainWindow(), i18n("The %1 account currently has an opening balance of %2. This QIF file reports an opening balance of %3. Would you like to overwrite the current balance with the one from the QIF file?", m_account.name(), split.shares().formatMoney(m_account, sec), balance.formatMoney(m_account, sec)), i18n("Overwrite opening balance"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "OverwriteOpeningBalance") == KMessageBox::Yes) { file->removeTransaction(openingtx); m_account.setOpeningDate(date); file->createOpeningBalanceTransaction(m_account, balance); } } } else { // Add an opening balance m_account.setOpeningDate(date); file->createOpeningBalanceTransaction(m_account, balance); } ft.commit(); } } else { // for some unknown reason, Quicken 2001 generates the following (somewhat // misleading) sequence of lines: // // 1: !Account // 2: NAT&T Universal // 3: DAT&T Univers(...xxxx) [CLOSED] // 4: TCCard // 5: ^ // 6: !Type:CCard // 7: !Account // 8: NCFCU Visa // 9: DRick's CFCU Visa card (...xxxx) // 10: TCCard // 11: ^ // 12: !Type:CCard // 13: D1/ 4' 1 // // Lines 1-5 are processed via processQifEntry() and processAccountEntry() // Then Quicken issues line 6 but since the account does not carry any // transaction does not write an end delimiter. Arrrgh! So we end up with // a QIF entry comprising of lines 6-11 and end up in this routine. Actually, // lines 7-11 are the leadin for the next account. So we check here if // the !Type:xxx record also contains an !Account line and process the // entry as required. // // (Ace) I think a better solution here is to handle exclamation point // lines separately from entries. In the above case: // Line 1 would set the mode to "account entries". // Lines 2-5 would be interpreted as an account entry. This would set m_account. // Line 6 would set the mode to "cc transaction entries". // Line 7 would immediately set the mode to "account entries" again // Lines 8-11 would be interpreted as an account entry. This would set m_account. // Line 12 would set the mode to "cc transaction entries" // Lines 13+ would be interpreted as cc transaction entries, and life is good int exclamationCnt = 1; QString category; do { category = extractLine('!', exclamationCnt++); } while (!category.isEmpty() && category != "Account"); // we have such a weird empty account if (category == "Account") { processAccountEntry(); } else { selectOrCreateAccount(Select, m_account); d->st_AccountName = m_account.name(); d->st.m_strAccountName = m_account.name(); d->st.m_accountId = m_account.id(); d->st.m_strAccountNumber = m_account.id(); m_account.setNumber(m_account.id()); if (m_entryType == EntryInvestmentTransaction) processInvestmentTransactionEntry(); else processTransactionEntry(); } } } #endif void MyMoneyQifReader::processPayeeEntry() { // TODO } void MyMoneyQifReader::processCategoryEntry() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount account = MyMoneyAccount(); account.setName(extractLine('N')); account.setDescription(extractLine('D')); MyMoneyAccount parentAccount; //The extractline routine will more than likely return 'empty', // so also have to test that either the 'I' or 'E' was detected //and set up accounts accordingly. if ((!extractLine('I').isEmpty()) || (m_extractedLine != -1)) { account.setAccountType(MyMoneyAccount::Income); parentAccount = file->income(); } else if ((!extractLine('E').isEmpty()) || (m_extractedLine != -1)) { account.setAccountType(MyMoneyAccount::Expense); parentAccount = file->expense(); } // check if we can find the account already in the file - MyMoneyAccount acc = kmymoney->findAccount(account, MyMoneyAccount()); + auto acc = findAccount(account, MyMoneyAccount()); // if not, we just create it if (acc.id().isEmpty()) { MyMoneyAccount brokerage; file->createAccount(account, parentAccount, brokerage, MyMoneyMoney()); } } +const MyMoneyAccount& MyMoneyQifReader::findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const +{ + static MyMoneyAccount nullAccount; + + MyMoneyFile* file = MyMoneyFile::instance(); + QList parents; + try { + // search by id + if (!acc.id().isEmpty()) { + return file->account(acc.id()); + } + // collect the parents. in case parent does not have an id, we scan the all top-level accounts + if (parent.id().isEmpty()) { + parents << file->asset(); + parents << file->liability(); + parents << file->income(); + parents << file->expense(); + parents << file->equity(); + } else { + parents << parent; + } + QList::const_iterator it_p; + for (it_p = parents.constBegin(); it_p != parents.constEnd(); ++it_p) { + MyMoneyAccount parentAccount = *it_p; + // search by name (allow hierarchy) + int pos; + // check for ':' in the name and use it as separator for a hierarchy + QString name = acc.name(); + bool notFound = false; + while ((pos = name.indexOf(MyMoneyFile::AccountSeperator)) != -1) { + QString part = name.left(pos); + QString remainder = name.mid(pos + 1); + const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); + // if account has not been found, continue with next top level parent + if (existingAccount.id().isEmpty()) { + notFound = true; + break; + } + parentAccount = existingAccount; + name = remainder; + } + if (notFound) + continue; + const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, name); + if (!existingAccount.id().isEmpty()) { + if (acc.accountType() != MyMoneyAccount::UnknownAccountType) { + if (acc.accountType() != existingAccount.accountType()) + continue; + } + return existingAccount; + } + } + } catch (const MyMoneyException &e) { + KMessageBox::error(0, i18n("Unable to find account: %1", e.what())); + } + return nullAccount; +} + const QString MyMoneyQifReader::transferAccount(const QString& name, bool useBrokerage) { QString accountId; QStringList tmpEntry = m_qifEntry; // keep temp copies MyMoneyAccount tmpAccount = m_account; m_qifEntry.clear(); // and construct a temp entry to create/search the account m_qifEntry << QString("N%1").arg(name); m_qifEntry << QString("Tunknown"); m_qifEntry << QString("D%1").arg(i18n("Autogenerated by QIF importer")); accountId = processAccountEntry(false); // in case we found a reference to an investment account, we need // to switch to the brokerage account instead. MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId); if (useBrokerage && (acc.accountType() == MyMoneyAccount::Investment)) { m_qifEntry.clear(); // and construct a temp entry to create/search the account m_qifEntry << QString("N%1").arg(acc.brokerageName()); m_qifEntry << QString("Tunknown"); m_qifEntry << QString("D%1").arg(i18n("Autogenerated by QIF importer")); accountId = processAccountEntry(false); } m_qifEntry = tmpEntry; // restore local copies m_account = tmpAccount; return accountId; } void MyMoneyQifReader::createOpeningBalance(MyMoneyAccount::_accountTypeE accType) { MyMoneyFile* file = MyMoneyFile::instance(); // if we don't have a name for the current account we need to extract the name from the L-record if (m_account.name().isEmpty()) { QString name = extractLine('L'); if (name.isEmpty()) { name = i18n("QIF imported, no account name supplied"); } d->isTransfer(name, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)); QStringList entry = m_qifEntry; // keep a temp copy m_qifEntry.clear(); // and construct a temp entry to create/search the account m_qifEntry << QString("N%1").arg(name); m_qifEntry << QString("T%1").arg(d->accountTypeToQif(accType)); m_qifEntry << QString("D%1").arg(i18n("Autogenerated by QIF importer")); processAccountEntry(); m_qifEntry = entry; // restore local copy } MyMoneyFileTransaction ft; try { bool needCreate = true; MyMoneyAccount acc = m_account; // in case we're dealing with an investment account, we better use // the accompanying brokerage account for the opening balance acc = file->accountByName(m_account.brokerageName()); // check if we already have an opening balance transaction QString tid = file->openingBalanceTransaction(acc); MyMoneyTransaction ot; if (!tid.isEmpty()) { ot = file->transaction(tid); MyMoneySplit s0 = ot.splitByAccount(acc.id()); // if the value is the same, we can silently skip this transaction if (s0.shares() == m_qifProfile.value('T', extractLine('T'))) { needCreate = false; } if (needCreate) { // in case we create it anyway, we issue a warning to the user to check it manually KMessageBox::sorry(0, QString("%1").arg(i18n("KMyMoney has imported a second opening balance transaction into account %1 which differs from the one found already on file. Please correct this manually once the import is done.", acc.name())), i18n("Opening balance problem")); } } if (needCreate) { acc.setOpeningDate(m_qifProfile.date(extractLine('D'))); file->modifyAccount(acc); MyMoneyTransaction t = file->createOpeningBalanceTransaction(acc, m_qifProfile.value('T', extractLine('T'))); if (!t.id().isEmpty()) { t.setImported(); file->modifyTransaction(t); } ft.commit(); } // make sure to use the updated version of the account if (m_account.id() == acc.id()) m_account = acc; // remember which account we created d->st.m_accountId = m_account.id(); } catch (const MyMoneyException &e) { KMessageBox::detailedError(0, i18n("Error while creating opening balance transaction"), QString("%1(%2):%3").arg(e.file()).arg(e.line()).arg(e.what()), i18n("File access error")); } } void MyMoneyQifReader::processTransactionEntry() { ++m_transactionsProcessed; // in case the user selected to skip the account or the account // was not found we skip this transaction /* if(m_account.id().isEmpty()) { m_transactionsSkipped++; return; } */ MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyStatement::Split s1; MyMoneyStatement::Transaction tr; QString tmp; QString accountId; int pos; QString payee = extractLine('P'); unsigned long h; h = MyMoneyTransaction::hash(m_qifEntry.join(";")); QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(m_qifProfile.date(extractLine('D')).toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); QMap::const_iterator it; it = d->m_hashMap.constFind(hash); if (it == d->m_hashMap.constEnd()) { d->m_hashMap[hash] = true; break; } ++idx; } tr.m_strBankID = hash; if (d->firstTransaction) { // check if this is an opening balance transaction and process it out of the statement if (!payee.isEmpty() && ((payee.toLower() == "opening balance") || KMyMoneyGlobalSettings::qifOpeningBalance().toLower().contains(payee.toLower()))) { createOpeningBalance(d->accountType); d->firstTransaction = false; return; } } // Process general transaction data if (d->st.m_accountId.isEmpty()) d->st.m_accountId = m_account.id(); s1.m_accountId = d->st.m_accountId; switch (d->accountType) { case MyMoneyAccount::Checkings: d->st.m_eType=MyMoneyStatement::etCheckings; break; case MyMoneyAccount::Savings: d->st.m_eType=MyMoneyStatement::etSavings; break; case MyMoneyAccount::Investment: d->st.m_eType=MyMoneyStatement::etInvestment; break; case MyMoneyAccount::CreditCard: d->st.m_eType=MyMoneyStatement::etCreditCard; break; default: d->st.m_eType=MyMoneyStatement::etNone; break; } tr.m_datePosted = (m_qifProfile.date(extractLine('D'))); if (!tr.m_datePosted.isValid()) { int rc = KMessageBox::warningContinueCancel(0, i18n("The date entry \"%1\" read from the file cannot be interpreted through the current " "date profile setting of \"%2\".\n\nPressing \"Continue\" will " "assign todays date to the transaction. Pressing \"Cancel\" will abort " "the import operation. You can then restart the import and select a different " "QIF profile or create a new one.", extractLine('D'), m_qifProfile.inputDateFormat()), i18n("Invalid date format")); switch (rc) { case KMessageBox::Continue: tr.m_datePosted = (QDate::currentDate()); break; case KMessageBox::Cancel: throw MYMONEYEXCEPTION("USERABORT"); break; } } tmp = extractLine('L'); pos = tmp.lastIndexOf("--"); if (tmp.left(1) == m_qifProfile.accountDelimiter().left(1)) { // it's a transfer, so we wipe the memo // tmp = ""; why?? // st.m_strAccountName = tmp; } else if (pos != -1) { // what's this? // t.setValue("Dialog", tmp.mid(pos+2)); tmp = tmp.left(pos); } // t.setMemo(tmp); // Assign the "#" field to the transaction's bank id // This is the custom KMM extension to QIF for a unique ID tmp = extractLine('#'); if (!tmp.isEmpty()) { tr.m_strBankID = QString("ID %1").arg(tmp); } #if 0 // Collect data for the account's split s1.m_accountId = m_account.id(); tmp = extractLine('S'); pos = tmp.findRev("--"); if (pos != -1) { tmp = tmp.left(pos); } if (tmp.left(1) == m_qifProfile.accountDelimiter().left(1)) // it's a transfer, extract the account name tmp = tmp.mid(1, tmp.length() - 2); s1.m_strCategoryName = tmp; #endif // TODO (Ace) Deal with currencies more gracefully. QIF cannot deal with multiple // currencies, so we should assume that transactions imported into a given // account are in THAT ACCOUNT's currency. If one of those involves a transfer // to an account with a different currency, value and shares should be // different. (Shares is in the target account's currency, value is in the // transaction's) s1.m_amount = m_qifProfile.value('T', extractLine('T')); tr.m_amount = m_qifProfile.value('T', extractLine('T')); tr.m_shares = m_qifProfile.value('T', extractLine('T')); tmp = extractLine('N'); if (!tmp.isEmpty()) tr.m_strNumber = tmp; if (!payee.isEmpty()) { tr.m_strPayee = payee; } tr.m_reconcile = d->reconcileState(extractLine('C')); tr.m_strMemo = extractLine('M'); d->fixMultiLineMemo(tr.m_strMemo); s1.m_strMemo = tr.m_strMemo; // tr.m_listSplits.append(s1); // split transaction // ****** ensure each field is ****** // * attached to correct split * QList listqSplits; if (! extractSplits(listqSplits)) { MyMoneyAccount account; // use the same values for the second split, but clear the ID and reverse the value MyMoneyStatement::Split s2 = s1; s2.m_reconcile = tr.m_reconcile; s2.m_amount = (-s1.m_amount); // s2.clearId(); // standard transaction tmp = extractLine('L'); if (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) { accountId = transferAccount(tmp, false); } else { /* pos = tmp.findRev("--"); if(pos != -1) { t.setValue("Dialog", tmp.mid(pos+2)); tmp = tmp.left(pos); }*/ // it's an expense / income tmp = tmp.trimmed(); accountId = file->checkCategory(tmp, s1.m_amount, s2.m_amount); } if (!accountId.isEmpty()) { try { MyMoneyAccount account = file->account(accountId); // FIXME: check that the type matches and ask if not if (account.accountType() == MyMoneyAccount::Investment) { qDebug() << "Line " << m_linenumber << ": Cannot transfer to/from an investment account. Transaction ignored."; return; } if (account.id() == m_account.id()) { qDebug() << "Line " << m_linenumber << ": Cannot transfer to the same account. Transfer ignored."; accountId.clear(); } } catch (const MyMoneyException &) { qDebug() << "Line " << m_linenumber << ": Account with id " << accountId.data() << " not found"; accountId.clear(); } } if (!accountId.isEmpty()) { s2.m_accountId = accountId; s2.m_strCategoryName = tmp; tr.m_listSplits.append(s2); } } else { int count; for (count = 1; count <= listqSplits.count(); ++count) { // Use true splits count MyMoneyStatement::Split s2 = s1; s2.m_amount = (-m_qifProfile.value('$', listqSplits[count-1].m_amount)); // Amount of split s2.m_strMemo = listqSplits[count-1].m_strMemo; // Memo in split tmp = listqSplits[count-1].m_strCategoryName; // Category in split if (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) { accountId = transferAccount(tmp, false); } else { pos = tmp.lastIndexOf("--"); if (pos != -1) { tmp = tmp.left(pos); } tmp = tmp.trimmed(); accountId = file->checkCategory(tmp, s1.m_amount, s2.m_amount); } if (!accountId.isEmpty()) { try { MyMoneyAccount account = file->account(accountId); // FIXME: check that the type matches and ask if not if (account.accountType() == MyMoneyAccount::Investment) { qDebug() << "Line " << m_linenumber << ": Cannot convert a split transfer to/from an investment account. Split removed. Total amount adjusted from " << tr.m_amount.formatMoney("", 2) << " to " << (tr.m_amount + s2.m_amount).formatMoney("", 2) << "\n"; tr.m_amount += s2.m_amount; continue; } if (account.id() == m_account.id()) { qDebug() << "Line " << m_linenumber << ": Cannot transfer to the same account. Transfer ignored."; accountId.clear(); } } catch (const MyMoneyException &) { qDebug() << "Line " << m_linenumber << ": Account with id " << accountId.data() << " not found"; accountId.clear(); } } if (!accountId.isEmpty()) { s2.m_accountId = accountId; s2.m_strCategoryName = tmp; tr.m_listSplits += s2; // in case the transaction does not have a memo and we // process the first split just copy the memo over if (tr.m_listSplits.count() == 1 && tr.m_strMemo.isEmpty()) tr.m_strMemo = s2.m_strMemo; } else { // TODO add an option to create a "Unassigned" category // for now, we just drop the split which will show up as unbalanced // transaction in the KMyMoney ledger view } } } // Add the transaction to the statement d->st.m_listTransactions += tr; } void MyMoneyQifReader::processInvestmentTransactionEntry() { // qDebug() << "Investment Transaction:" << m_qifEntry.count() << " lines"; /* Items for Investment Accounts Field Indicator Explanation D Date N Action Y Security (NAME, not symbol) I Price Q Quantity (number of shares or split ratio) T Transaction amount C Cleared status P Text in the first line for transfers and reminders (Payee) M Memo O Commission L Account for the transfer $ Amount transferred ^ End of the entry It will be presumed all transactions are to the associated cash account, if one exists, unless otherwise noted by the 'L' field. Expense/Income categories will be automatically generated, "_Dividend", "_InterestIncome", etc. */ MyMoneyStatement::Transaction tr; d->st.m_eType = MyMoneyStatement::etInvestment; // t.setCommodity(m_account.currencyId()); // 'D' field: Date QDate date = m_qifProfile.date(extractLine('D')); if (date.isValid()) tr.m_datePosted = date; else { int rc = KMessageBox::warningContinueCancel(0, i18n("The date entry \"%1\" read from the file cannot be interpreted through the current " "date profile setting of \"%2\".\n\nPressing \"Continue\" will " "assign todays date to the transaction. Pressing \"Cancel\" will abort " "the import operation. You can then restart the import and select a different " "QIF profile or create a new one.", extractLine('D'), m_qifProfile.inputDateFormat()), i18n("Invalid date format")); switch (rc) { case KMessageBox::Continue: tr.m_datePosted = QDate::currentDate(); break; case KMessageBox::Cancel: throw MYMONEYEXCEPTION("USERABORT"); break; } } // 'M' field: Memo QString memo = extractLine('M'); d->fixMultiLineMemo(memo); tr.m_strMemo = memo; unsigned long h; h = MyMoneyTransaction::hash(m_qifEntry.join(";")); QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(m_qifProfile.date(extractLine('D')).toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); QMap::const_iterator it; it = d->m_hashMap.constFind(hash); if (it == d->m_hashMap.constEnd()) { d->m_hashMap[hash] = true; break; } ++idx; } tr.m_strBankID = hash; // '#' field: BankID QString tmp = extractLine('#'); if (! tmp.isEmpty()) tr.m_strBankID = QString("ID %1").arg(tmp); // Reconciliation flag tr.m_reconcile = d->reconcileState(extractLine('C')); // 'O' field: Fees tr.m_fees = m_qifProfile.value('T', extractLine('O')); // 'T' field: Amount MyMoneyMoney amount = m_qifProfile.value('T', extractLine('T')); tr.m_amount = amount; MyMoneyStatement::Price price; price.m_date = date; price.m_strSecurity = extractLine('Y'); price.m_amount = m_qifProfile.value('T', extractLine('I')); #if 0 // we must check for that later, because certain activities don't need a security // 'Y' field: Security name QString securityname = extractLine('Y').toLower(); if (securityname.isEmpty()) { qDebug() << "Line " << m_linenumber << ": Investment transaction without a security is not supported."; return; } tr.m_strSecurity = securityname; #endif #if 0 // For now, we let the statement reader take care of that. // The big problem here is that the Y field is not the SYMBOL, it's the NAME. // The name is not very unique, because people could have used slightly different // abbreviations or ordered words differently, etc. // // If there is a perfect name match with a subordinate stock account, great. // More likely, we have to rely on the QIF file containing !Type:Security // records, which tell us the mapping from name to symbol. // // Therefore, generally it is not recommended to import a QIF file containing // investment transactions but NOT containing security records. QString securitysymbol = m_investmentMap[securityname]; // the correct account is the stock account which matches two criteria: // (1) it is a sub-account of the selected investment account, and either // (2a) the security name of the transaction matches the name of the security, OR // (2b) the security name of the transaction maps to a symbol which matches the symbol of the security // search through each subordinate account bool found = false; MyMoneyAccount thisaccount = m_account; QStringList accounts = thisaccount.accountList(); QStringList::const_iterator it_account = accounts.begin(); while (!found && it_account != accounts.end()) { QString currencyid = file->account(*it_account).currencyId(); MyMoneySecurity security = file->security(currencyid); QString symbol = security.tradingSymbol().toLower(); QString name = security.name().toLower(); if (securityname == name || securitysymbol == symbol) { d->st_AccountId = *it_account; s1.m_accountId = *it_account; thisaccount = file->account(*it_account); found = true; #if 0 // update the price, while we're here. in the future, this should be // an option QString basecurrencyid = file->baseCurrency().id(); MyMoneyPrice price = file->price(currencyid, basecurrencyid, t_in.m_datePosted, true); if (!price.isValid()) { MyMoneyPrice newprice(currencyid, basecurrencyid, t_in.m_datePosted, t_in.m_moneyAmount / t_in.m_dShares, i18n("Statement Importer")); file->addPrice(newprice); } #endif } ++it_account; } if (!found) { qDebug() << "Line " << m_linenumber << ": Security " << securityname << " not found in this account. Transaction ignored."; // If the security is not known, notify the user // TODO (Ace) A "SelectOrCreateAccount" interface for investments KMessageBox::information(0, i18n("This investment account does not contain the \"%1\" security. " "Transactions involving this security will be ignored.", securityname), i18n("Security not found"), QString("MissingSecurity%1").arg(securityname.trimmed())); return; } #endif // 'Y' field: Security tr.m_strSecurity = extractLine('Y'); // 'Q' field: Quantity MyMoneyMoney quantity = m_qifProfile.value('T', extractLine('Q')); // 'N' field: Action QString action = extractLine('N').toLower(); // remove trailing X, which seems to have no purpose (?!) bool xAction = false; if (action.endsWith('x')) { action = action.left(action.length() - 1); xAction = true; } tmp = extractLine('L'); // if the action ends in an X, the L-Record contains the asset account // to which the dividend should be transferred. In the other cases, it // may contain a category that identifies the income category for the // dividend payment if ((xAction == true) || (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1)) == true)) { tmp = tmp.remove(QRegExp("[\\[\\]]")); // xAction != true so ignore any'[ and ]' if (!tmp.isEmpty()) { // use 'L' record name tr.m_strBrokerageAccount = tmp; transferAccount(tmp); // make sure the account exists } else { tr.m_strBrokerageAccount = m_account.brokerageName();// use brokerage account transferAccount(m_account.brokerageName()); // make sure the account exists } } else { tmp = tmp.remove(QRegExp("[\\[\\]]")); // xAction != true so ignore any'[ and ]' tr.m_strInterestCategory = tmp; tr.m_strBrokerageAccount = m_account.brokerageName(); } // Whether to create a cash split for the other side of the value QString accountname; //= extractLine('L'); if (action == "reinvint" || action == "reinvdiv" || action == "reinvlg" || action == "reinvsh") { d->st.m_listPrices += price; tr.m_shares = quantity; tr.m_eAction = (MyMoneyStatement::Transaction::eaReinvestDividend); tr.m_price = m_qifProfile.value('I', extractLine('I')); tr.m_strInterestCategory = extractLine('L'); if (tr.m_strInterestCategory.isEmpty()) { tr.m_strInterestCategory = d->typeToAccountName(action); } } else if (action == "div" || action == "cgshort" || action == "cgmid" || action == "cglong" || action == "rtrncap") { tr.m_eAction = (MyMoneyStatement::Transaction::eaCashDividend); // make sure, we have valid category. Either taken from the L-Record above, // or derived from the action code if (tr.m_strInterestCategory.isEmpty()) { tr.m_strInterestCategory = d->typeToAccountName(action); } // For historic reasons (coming from the OFX importer) the statement // reader expects the dividend with a reverse sign. So we just do that. tr.m_amount -= tr.m_fees; // We need an extra split which will be the zero-amount investment split // that serves to mark this transaction as a cash dividend and note which // stock account it belongs to. MyMoneyStatement::Split s2; s2.m_amount = MyMoneyMoney(); s2.m_strCategoryName = extractLine('Y'); tr.m_listSplits.append(s2); } else if (action == "intinc" || action == "miscinc" || action == "miscexp") { tr.m_eAction = (MyMoneyStatement::Transaction::eaInterest); if (action == "miscexp") tr.m_eAction = (MyMoneyStatement::Transaction::eaFees); // make sure, we have a valid category. Either taken from the L-Record above, // or derived from the action code if (tr.m_strInterestCategory.isEmpty()) { tr.m_strInterestCategory = d->typeToAccountName(action); } if (action == "intinc") { MyMoneyMoney price = m_qifProfile.value('I', extractLine('I')); tr.m_amount -= tr.m_fees; if ((!quantity.isZero()) && (!price.isZero())) tr.m_amount = -(quantity * price); } else // For historic reasons (coming from the OFX importer) the statement // reader expects the dividend with a reverse sign. So we just do that. if (action != "miscexp") tr.m_amount = -(amount - tr.m_fees); if (tr.m_strMemo.isEmpty()) tr.m_strMemo = (QString("%1 %2").arg(extractLine('Y')).arg(d->typeToAccountName(action))).trimmed(); } else if (action == "xin" || action == "xout") { QString payee = extractLine('P'); if (!payee.isEmpty() && ((payee.toLower() == "opening balance") || KMyMoneyGlobalSettings::qifOpeningBalance().toLower().contains(payee.toLower()))) { createOpeningBalance(MyMoneyAccount::Investment); return; } tr.m_eAction = (MyMoneyStatement::Transaction::eaNone); MyMoneyStatement::Split s2; QString tmp = extractLine('L'); if (d->isTransfer(tmp, m_qifProfile.accountDelimiter().left(1), m_qifProfile.accountDelimiter().mid(1, 1))) { s2.m_accountId = transferAccount(tmp); s2.m_strCategoryName = tmp; } else { s2.m_strCategoryName = extractLine('L'); if (tr.m_strInterestCategory.isEmpty()) { s2.m_strCategoryName = d->typeToAccountName(action); } } if (action == "xout") tr.m_amount = -tr.m_amount; s2.m_amount = -tr.m_amount; tr.m_listSplits.append(s2); } else if (action == "buy") { d->st.m_listPrices += price; tr.m_price = m_qifProfile.value('I', extractLine('I')); tr.m_shares = quantity; tr.m_amount = -amount; tr.m_eAction = (MyMoneyStatement::Transaction::eaBuy); } else if (action == "sell") { d->st.m_listPrices += price; tr.m_price = m_qifProfile.value('I', extractLine('I')); tr.m_shares = -quantity; tr.m_amount = amount; tr.m_eAction = (MyMoneyStatement::Transaction::eaSell); } else if (action == "shrsin") { tr.m_shares = quantity; tr.m_eAction = (MyMoneyStatement::Transaction::eaShrsin); } else if (action == "shrsout") { tr.m_shares = -quantity; tr.m_eAction = (MyMoneyStatement::Transaction::eaShrsout); } else if (action == "stksplit") { MyMoneyMoney splitfactor = (quantity / MyMoneyMoney(10, 1)).reduce(); // Stock splits not supported // qDebug() << "Line " << m_linenumber << ": Stock split not supported (date=" << date << " security=" << securityname << " factor=" << splitfactor.toString() << ")"; // s1.setShares(splitfactor); // s1.setValue(0); // s1.setAction(MyMoneySplit::ActionSplitShares); // return; } else { // Unsupported action type qDebug() << "Line " << m_linenumber << ": Unsupported transaction action (" << action << ")"; return; } d->st.m_strAccountName = accountname; // accountname appears not to get set d->st.m_listTransactions += tr; /************************************************************************* * * These transactions are natively supported by KMyMoney * *************************************************************************/ /* D1/ 3' 5 NShrsIn YGENERAL MOTORS CORP 52BR1 I20 Q200 U4,000.00 T4,000.00 M200 shares added to account @ $20/share ^ */ /* ^ D1/14' 5 NShrsOut YTEMPLETON GROWTH 97GJ0 Q50 90 ^ */ /* D1/28' 5 NBuy YGENERAL MOTORS CORP 52BR1 I24.35 Q100 U2,435.00 T2,435.00 ^ */ /* D1/ 5' 5 NSell YUnited Vanguard I8.41 Q50 U420.50 T420.50 ^ */ /* D1/ 7' 5 NReinvDiv YFRANKLIN INCOME 97GM2 I38 Q1 U38.00 T38.00 ^ */ /************************************************************************* * * These transactions are all different kinds of income. (Anything that * follows the DNYUT pattern). They are all handled the same, the only * difference is which income account the income is placed into. By * default, it's placed into _xxx where xxx is the right side of the * N field. e.g. NDiv transaction goes into the _Div account * *************************************************************************/ /* D1/10' 5 NDiv YTEMPLETON GROWTH 97GJ0 U10.00 T10.00 ^ */ /* D1/10' 5 NIntInc YTEMPLETON GROWTH 97GJ0 U20.00 T20.00 ^ */ /* D1/10' 5 NCGShort YTEMPLETON GROWTH 97GJ0 U111.00 T111.00 ^ */ /* D1/10' 5 NCGLong YTEMPLETON GROWTH 97GJ0 U333.00 T333.00 ^ */ /* D1/10' 5 NCGMid YTEMPLETON GROWTH 97GJ0 U222.00 T222.00 ^ */ /* D2/ 2' 5 NRtrnCap YFRANKLIN INCOME 97GM2 U1,234.00 T1,234.00 ^ */ /************************************************************************* * * These transactions deal with miscellaneous activity that KMyMoney * does not support, but may support in the future. * *************************************************************************/ /* Note the Q field is the split ratio per 10 shares, so Q12.5 is a 12.5:10 split, otherwise known as 5:4. D1/14' 5 NStkSplit YIBM Q12.5 ^ */ /************************************************************************* * * These transactions deal with short positions and options, which are * not supported at all by KMyMoney. They will be ignored for now. * There may be a way to hack around this, by creating a new security * "IBM_Short". * *************************************************************************/ /* D1/21' 5 NShtSell YIBM I92.38 Q100 U9,238.00 T9,238.00 ^ */ /* D1/28' 5 NCvrShrt YIBM I92.89 Q100 U9,339.00 T9,339.00 O50.00 ^ */ /* D6/ 1' 5 NVest YIBM Option Q20 ^ */ /* D6/ 8' 5 NExercise YIBM Option I60.952381 Q20 MFrom IBM Option Grant 6/1/2004 ^ */ /* D6/ 1'14 NExpire YIBM Option Q5 ^ */ /************************************************************************* * * These transactions do not have an associated investment ("Y" field) * so presumably they are only valid for the cash account. Once I * understand how these are really implemented, they can probably be * handled without much trouble. * *************************************************************************/ /* D1/14' 5 NCash U-100.00 T-100.00 LBank Chrg ^ */ /* D1/15' 5 NXOut U500.00 T500.00 L[CU Savings] $500.00 ^ */ /* D1/28' 5 NXIn U1,000.00 T1,000.00 L[CU Checking] $1,000.00 ^ */ /* D1/25' 5 NMargInt U25.00 T25.00 ^ */ } const QString MyMoneyQifReader::findOrCreateIncomeAccount(const QString& searchname) { QString result; MyMoneyFile *file = MyMoneyFile::instance(); // First, try to find this account as an income account MyMoneyAccount acc = file->income(); QStringList list = acc.accountList(); QStringList::ConstIterator it_accid = list.constBegin(); while (it_accid != list.constEnd()) { acc = file->account(*it_accid); if (acc.name() == searchname) { result = *it_accid; break; } ++it_accid; } // If we did not find the account, now we must create one. if (result.isEmpty()) { MyMoneyAccount acc; acc.setName(searchname); acc.setAccountType(MyMoneyAccount::Income); MyMoneyAccount income = file->income(); MyMoneyFileTransaction ft; file->addAccount(acc, income); ft.commit(); result = acc.id(); } return result; } // TODO (Ace) Combine this and the previous function const QString MyMoneyQifReader::findOrCreateExpenseAccount(const QString& searchname) { QString result; MyMoneyFile *file = MyMoneyFile::instance(); // First, try to find this account as an income account MyMoneyAccount acc = file->expense(); QStringList list = acc.accountList(); QStringList::ConstIterator it_accid = list.constBegin(); while (it_accid != list.constEnd()) { acc = file->account(*it_accid); if (acc.name() == searchname) { result = *it_accid; break; } ++it_accid; } // If we did not find the account, now we must create one. if (result.isEmpty()) { MyMoneyAccount acc; acc.setName(searchname); acc.setAccountType(MyMoneyAccount::Expense); MyMoneyFileTransaction ft; MyMoneyAccount expense = file->expense(); file->addAccount(acc, expense); ft.commit(); result = acc.id(); } return result; } const QString MyMoneyQifReader::processAccountEntry(bool resetAccountId) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount account; QString tmp; account.setName(extractLine('N')); // qDebug("Process account '%s'", account.name().data()); account.setDescription(extractLine('D')); tmp = extractLine('$'); if (tmp.length() > 0) account.setValue("lastStatementBalance", tmp); tmp = extractLine('/'); if (tmp.length() > 0) account.setValue("lastStatementDate", m_qifProfile.date(tmp).toString("yyyy-MM-dd")); QifEntryTypeE transactionType = EntryTransaction; QString type = extractLine('T').toLower().remove(QRegExp("\\s+")); if (type == m_qifProfile.profileType().toLower().remove(QRegExp("\\s+"))) { account.setAccountType(MyMoneyAccount::Checkings); } else if (type == "ccard" || type == "creditcard") { account.setAccountType(MyMoneyAccount::CreditCard); } else if (type == "cash") { account.setAccountType(MyMoneyAccount::Cash); } else if (type == "otha") { account.setAccountType(MyMoneyAccount::Asset); } else if (type == "othl") { account.setAccountType(MyMoneyAccount::Liability); } else if (type == "invst" || type == "port") { account.setAccountType(MyMoneyAccount::Investment); transactionType = EntryInvestmentTransaction; } else if (type == "mutual") { // stock account w/o umbrella investment account account.setAccountType(MyMoneyAccount::Stock); transactionType = EntryInvestmentTransaction; } else if (type == "unknown") { // don't do anything with the type, leave it unknown } else { account.setAccountType(MyMoneyAccount::Checkings); qDebug() << "Line " << m_linenumber << ": Unknown account type '" << type << "', checkings assumed"; } // check if we can find the account already in the file - MyMoneyAccount acc = kmymoney->findAccount(account, MyMoneyAccount()); + auto acc = findAccount(account, MyMoneyAccount()); if (acc.id().isEmpty()) { // in case the account is not found by name and the type is // unknown, we have to assume something and create a checking account. // this might be wrong, but we have no choice at this point. if (account.accountType() == MyMoneyAccount::UnknownAccountType) account.setAccountType(MyMoneyAccount::Checkings); MyMoneyAccount parentAccount; MyMoneyAccount brokerage; // in case it's a stock account, we need to setup a fix investment account if (account.isInvest()) { acc.setName(i18n("%1 (Investment)", account.name())); // use the same name for the investment account acc.setDescription(i18n("Autogenerated by QIF importer from type Mutual account entry")); acc.setAccountType(MyMoneyAccount::Investment); parentAccount = file->asset(); file->createAccount(acc, parentAccount, brokerage, MyMoneyMoney()); parentAccount = acc; qDebug("We still need to create the stock account in MyMoneyQifReader::processAccountEntry()"); } else { // setup parent according the type of the account switch (account.accountGroup()) { case MyMoneyAccount::Asset: default: parentAccount = file->asset(); break; case MyMoneyAccount::Liability: parentAccount = file->liability(); break; case MyMoneyAccount::Equity: parentAccount = file->equity(); break; } } // investment accounts will receive a brokerage account, as KMyMoney // currently does not allow to store funds in the investment account directly // but only create it (not here, but later) if it is needed if (account.accountType() == MyMoneyAccount::Investment) { brokerage.setName(QString()); // brokerage name empty so account not created yet brokerage.setAccountType(MyMoneyAccount::Checkings); brokerage.setCurrencyId(MyMoneyFile::instance()->baseCurrency().id()); } file->createAccount(account, parentAccount, brokerage, MyMoneyMoney()); acc = account; // qDebug("Account created"); } else { // qDebug("Existing account found"); } if (resetAccountId) { // possibly start a new statement d->finishStatement(); m_account = acc; d->st.m_accountId = m_account.id(); // needed here for account selection d->transactionType = transactionType; } return acc.id(); } -void MyMoneyQifReader::selectOrCreateAccount(const SelectCreateMode mode, MyMoneyAccount& account, const MyMoneyMoney& balance) -{ - MyMoneyFile* file = MyMoneyFile::instance(); - - QString accountId; - QString msg; - QString typeStr; - QString leadIn; - KMyMoneyUtils::categoryTypeE type; - - QMap::ConstIterator it; - - type = KMyMoneyUtils::none; - switch (account.accountGroup()) { - default: - type = KMyMoneyUtils::asset; - type = (KMyMoneyUtils::categoryTypeE)(type | KMyMoneyUtils::liability); - typeStr = i18n("account"); - leadIn = i18n("al"); - break; - - case MyMoneyAccount::Income: - case MyMoneyAccount::Expense: - type = KMyMoneyUtils::income; - type = (KMyMoneyUtils::categoryTypeE)(type | KMyMoneyUtils::expense); - typeStr = i18n("category"); - leadIn = i18n("ei"); - msg = i18n("Category selection"); - break; - } - - QPointer accountSelect = new KAccountSelectDlg(type, "QifImport", kmymoney); - if (!msg.isEmpty()) - accountSelect->setWindowTitle(msg); - - it = m_accountTranslation.constFind(QString(leadIn + MyMoneyFile::AccountSeperator + account.name()).toLower()); - if (it != m_accountTranslation.constEnd()) { - try { - account = file->account(*it); - delete accountSelect; - return; - - } catch (const MyMoneyException &e) { - const QString message(i18n("Account \"%1\" disappeared: %2", account.name(), e.what())); - KMessageBox::error(0, message); - } - } - - // This is so the QPointer to the dialog gets properly destroyed if something throws. - try { - if (!account.name().isEmpty()) { - if (type & (KMyMoneyUtils::income | KMyMoneyUtils::expense)) { - accountId = file->categoryToAccount(account.name()); - } else { - accountId = file->nameToAccount(account.name()); - } - - if (mode == Create) { - if (!accountId.isEmpty()) { - account = file->account(accountId); - delete accountSelect; - return; - - } else { - switch (KMessageBox::questionYesNo(0, - i18nc("The 'type of object' 'x' does not exist", "The %1 '%2' does not exist. Do you " - "want to create it?", typeStr, account.name()))) { - case KMessageBox::Yes: - break; - case KMessageBox::No: - default: - delete accountSelect; - return; - } - } - } else { - accountSelect->setHeader(i18nc("To select account", "Select %1", typeStr)); - if (!accountId.isEmpty()) { - msg = i18n("The %1 %2 currently exists. Do you want " - "to import transactions to this account?", typeStr, account.name()); - - } else { - msg = i18n("The %1 %2 currently does not exist. You can " - "create a new %3 by pressing the Create button " - "or select another %4 manually from the selection box.", typeStr, account.name(), typeStr, typeStr); - } - } - - accountSelect->setDescription(msg); - accountSelect->setAccount(account, accountId); - accountSelect->setMode(mode == Create); - accountSelect->showAbortButton(true); - - // display current entry in widget, the offending line (if any) will be shown in red - QStringList::Iterator it_e; - int i = 0; - for (it_e = m_qifEntry.begin(); it_e != m_qifEntry.end(); ++it_e) { - if (m_extractedLine == i) - accountSelect->m_qifEntry->setTextColor(QColor("red")); - accountSelect->m_qifEntry->append(*it_e); - accountSelect->m_qifEntry->setTextColor(QColor("black")); - ++i; - } - - for (;;) { - if (accountSelect->exec() == QDialog::Accepted) { - if (!accountSelect->selectedAccount().isEmpty()) { - accountId = accountSelect->selectedAccount(); - - m_accountTranslation[QString(leadIn + MyMoneyFile::AccountSeperator + account.name()).toLower()] = accountId; - - // MMAccount::openingBalance() is where the accountSelect dialog has - // stashed the opening balance that the user chose. - MyMoneyAccount importedAccountData(account); - // MyMoneyMoney balance = importedAccountData.openingBalance(); - account = file->account(accountId); - if (! balance.isZero()) { - QString openingtxid = file->openingBalanceTransaction(account); - MyMoneyFileTransaction ft; - if (! openingtxid.isEmpty()) { - MyMoneyTransaction openingtx = file->transaction(openingtxid); - MyMoneySplit split = openingtx.splitByAccount(account.id()); - - if (split.shares() != balance) { - const MyMoneySecurity& sec = file->security(account.currencyId()); - if (KMessageBox::questionYesNo( - KMyMoneyUtils::mainWindow(), - i18n("The %1 account currently has an opening balance of %2. This QIF file reports an opening balance of %3. Would you like to overwrite the current balance with the one from the QIF file?", account.name(), MyMoneyUtils::formatMoney(split.shares(), account, sec), MyMoneyUtils::formatMoney(balance, account, sec)), - i18n("Overwrite opening balance"), - KStandardGuiItem::yes(), - KStandardGuiItem::no(), - "OverwriteOpeningBalance") - == KMessageBox::Yes) { - file->removeTransaction(openingtx); - file->createOpeningBalanceTransaction(account, balance); - } - } - } else { - // Add an opening balance - file->createOpeningBalanceTransaction(account, balance); - } - ft.commit(); - } - break; - } - - } else if (accountSelect->aborted()) - throw MYMONEYEXCEPTION("USERABORT"); - - if (typeStr == i18n("account")) { - KMessageBox::error(0, i18n("You must select or create an account.")); - } else { - KMessageBox::error(0, i18n("You must select or create a category.")); - } - } - } - } catch (...) { - // cleanup the dialog pointer. - delete accountSelect; - throw; - } - delete accountSelect; -} - void MyMoneyQifReader::setProgressCallback(void(*callback)(int, int, const QString&)) { m_progressCallback = callback; } void MyMoneyQifReader::signalProgress(int current, int total, const QString& msg) { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); } void MyMoneyQifReader::processPriceEntry() { /* !Type:Prices "IBM",141 9/16,"10/23/98" ^ !Type:Prices "GMW",21.28," 3/17' 5" ^ !Type:Prices "GMW",71652181.001,"67/128/ 0" ^ Note that Quicken will often put in a price with a bogus date and number. We will ignore prices with bogus dates. Hopefully that will catch all of these. Also note that prices can be in fractional units, e.g. 141 9/16. */ QStringList::const_iterator it_line = m_qifEntry.constBegin(); // Make a price for each line QRegExp priceExp("\"(.*)\",(.*),\"(.*)\""); while (it_line != m_qifEntry.constEnd()) { if (priceExp.indexIn(*it_line) != -1) { MyMoneyStatement::Price price; price.m_strSecurity = priceExp.cap(1); QString pricestr = priceExp.cap(2); QString datestr = priceExp.cap(3); qDebug() << "Price:" << price.m_strSecurity << " / " << pricestr << " / " << datestr; // Only add the price if the date is valid. If invalid, fail silently. See note above. // Also require the price value to not have any slashes. Old prices will be something like // "25 9/16", which we do not support. So we'll skip the price for now. QDate date = m_qifProfile.date(datestr); MyMoneyMoney rate(m_qifProfile.value('P', pricestr)); if (date.isValid() && !rate.isZero()) { price.m_amount = rate; price.m_date = date; d->st.m_listPrices += price; } } ++it_line; } } void MyMoneyQifReader::processSecurityEntry() { /* !Type:Security NVANGUARD 500 INDEX SVFINX TMutual Fund ^ */ MyMoneyStatement::Security security; security.m_strName = extractLine('N'); security.m_strSymbol = extractLine('S'); d->st.m_listSecurities += security; } diff --git a/kmymoney/converter/mymoneyqifreader.h b/kmymoney/plugins/qif/import/mymoneyqifreader.h similarity index 81% rename from kmymoney/converter/mymoneyqifreader.h rename to kmymoney/plugins/qif/import/mymoneyqifreader.h index c25170b1c..4c85b86c4 100644 --- a/kmymoney/converter/mymoneyqifreader.h +++ b/kmymoney/plugins/qif/import/mymoneyqifreader.h @@ -1,383 +1,348 @@ /*************************************************************************** mymoneyqifreader.h - description ------------------- begin : Mon Jan 27 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYQIFREADER_H #define MYMONEYQIFREADER_H // ---------------------------------------------------------------------------- // QT Headers #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Headers // ---------------------------------------------------------------------------- // Project Headers -#include "mymoneyqifprofile.h" #include "mymoneyaccount.h" #include "mymoneytransaction.h" +#include "../config/mymoneyqifprofile.h" class MyMoneyFileTransaction; +class MyMoneyStatement; +class MyMoneyQifProfile; /** * @author Thomas Baumgart */ class MyMoneyQifReader : public QObject { Q_OBJECT friend class Private; private: typedef enum { EntryUnknown = 0, EntryAccount, EntryTransaction, EntryCategory, EntryMemorizedTransaction, EntryInvestmentTransaction, EntrySecurity, EntryPrice, EntryPayee, EntryClass, EntrySkip } QifEntryTypeE; struct qSplit { QString m_strCategoryName; QString m_strMemo; QString m_amount; }; public: MyMoneyQifReader(); ~MyMoneyQifReader(); /** * This method is used to store the filename into the object. * The file should exist. If it does and an external filter * program is specified with the current selected profile, * the file is send through this filter and the result * is stored in the m_tempFile file. * * @param url URL of the file to be imported */ void setURL(const QUrl &url); /** * This method is used to store the name of the profile into the object. * The selected profile will be loaded if it exists. If an external * filter program is specified with the current selected profile, * the file is send through this filter and the result * is stored in the m_tempFile file. * * @param name QString reference to the name of the profile */ void setProfile(const QString& name); /** * This method actually starts the import of data from the selected file * into the MyMoney engine. * * This method also starts the user defined import filter program * defined in the QIF profile. If none is defined, the file is read * as is (actually the UNIX command 'cat -' is used as the filter). * * If data from the filter program is available, the slot * slotReceivedDataFromFilter() will be called. * * Make sure to connect the signal importFinished() to detect when * the import actually ended. Call the method finishImport() to clean * things up and get the overall result of the import. * * @retval true the import was started successfully * @retval false the import could not be started. */ bool startImport(); - /** - * This method must be called once the signal importFinished() has - * been emitted. It will clean up the reader state and determines - * the actual return code of the import. - * - * @retval true Import was successful. - * @retval false Import failed because the filter program terminated - * abnormally or the user aborted the import process. - */ - bool finishImport(); - void setCategoryMapping(bool map); inline const MyMoneyAccount& account() const { return m_account; }; void setProgressCallback(void(*callback)(int, int, const QString&)); private: /** * This method is used to update the progress information. It * checks if an appropriate function is known and calls it. * * For a parameter description see KMyMoneyView::progressCallback(). */ void signalProgress(int current, int total, const QString& = ""); /** * This method scans a transaction contained in * a QIF file formatted as an account record. This * format is used by MS-Money. If the specific data * is not found, then the data in the entry is treated * as a transaction. In this case, the user will be asked to * specify the account to which the transactions should be imported. * The entry data is found in m_qifEntry. * * @param accountType see MyMoneyAccount() for details. Defaults to MyMoneyAccount::Checkings */ void processMSAccountEntry(const MyMoneyAccount::accountTypeE accountType = MyMoneyAccount::Checkings); /** * This method scans the m_qifEntry object as a payee record specified by Quicken */ void processPayeeEntry(); /** * This method scans the m_qifEntry object as an account record specified * by Quicken. In case @p resetAccountId is @p true (the default), the * global account id will be reset. * * The id of the account will be returned. */ const QString processAccountEntry(bool resetAccountId = true); /** * This method scans the m_qifEntry object as a category record specified * by Quicken. */ void processCategoryEntry(); /** * This method scans the m_qifEntry object as a transaction record specified * by Quicken. */ void processTransactionEntry(); /** * This method scans the m_qifEntry object as an investment transaction * record specified by Quicken. */ void processInvestmentTransactionEntry(); /** * This method scans the m_qifEntry object as a price record specified * by Quicken. */ void processPriceEntry(); /** * This method scans the m_qifEntry object as a security record specified * by Quicken. */ void processSecurityEntry(); /** * This method processes the lines previously collected in * the member variable m_qifEntry. If further information * by the user is required to process the entry it will * be collected. */ void processQifEntry(); /** * This method process a line starting with an exclamation mark */ void processQifSpecial(const QString& _line); /** * This method extracts the line beginning with the letter @p id * from the lines contained in the QStringList object @p m_qifEntry. * An empty QString is returned, if the line is not found. * * @param id QChar containing the letter to be found * @param cnt return cnt'th of occurrence of id in lines. cnt defaults to 1. * * @return QString with the remainder of the line or empty if * @p id is not found in @p lines */ const QString extractLine(const QChar& id, int cnt = 1); /** * This method examines each line in the QStringList object @p m_qifEntry, * searching for split entries, which it extracts into a struct qSplit and * stores all splits found in @p listqSplits . */ bool extractSplits(QList& listqSplits) const; enum SelectCreateMode { Create = 0, Select }; - /** - * This method is used to find an account using the account's name - * stored in @p account in the current MyMoneyFile object. If it does not - * exist, the user has the chance to create it or to skip processing - * of this account. - * - * If an account has been selected, account will be set to contain it's data. - * If the skip operation was requested, account will be empty. - * - * Depending on @p mode the bahaviour of this method is slightly different. - * The following table shows the dependencies: - * - * @code - * case mode operation - * ----------------------------------------------------------------------------- - * account with same name exists Create returns immediately - * m_account contains data - * of existing account - * - * account does not exist Create immediately calls dialog - * to create account - * - * account with same name exists Select User will be asked if - * he wants to use the existing - * account or create a new one - * - * account does not exist Select User will be asked to - * select a different account - * or create a new one - * - * @endcode - * - * @param mode Is either Create or Select depending on the above table - * @param account Reference to MyMoneyAccount object - * @param openingBalance the opening balance of the account to be created - * defaults to zero - */ - - void selectOrCreateAccount(const SelectCreateMode mode, MyMoneyAccount& account, const MyMoneyMoney& openingBalance = MyMoneyMoney()); /** * This method looks up the @p searchname account by name and returns its id * if it was found. If it was not found, it creates a new income account using * @p searchname as a name, and returns the id if the newly created account * * @param searchname The name of the account to find or create * @return QString id of the found or created account */ static const QString findOrCreateIncomeAccount(const QString& searchname); /** * This method looks up the @p searchname account by name and returns its id * if it was found. If it was not found, it creates a new expense account using * @p searchname as a name, and returns the id if the newly created account * * @param searchname The name of the account to find or create * @return QString id of the found or created account */ static const QString findOrCreateExpenseAccount(const QString& searchname); + /** + * This methods returns the account from the list of accounts identified by + * an account id or account name including an account hierachy. + * + * The parent account specifies from which account the search should be started. + * In case the parent account does not have an id, the method scans all top-level accounts. + * + * If the account is not found in the list of accounts, MyMoneyAccount() is returned. + * + * @param acc account to find + * @param parent parent account to search from + * @retval found MyMoneyAccount account instance + * @retval MyMoneyAccount() if not found + */ + const MyMoneyAccount& findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const; + /** * This method returns the account id for a given account @a name. In * case @a name references an investment account and @a useBrokerage is @a true * (the default), the id of the corresponding brokerage account will be * returned. In case an account does not exist, it will be created. */ const QString transferAccount(const QString& name, bool useBrokerage = true); // void processQifLine(); void createOpeningBalance(MyMoneyAccount::_accountTypeE accType = MyMoneyAccount::Checkings); signals: - /** - * This signal will be emitted when the import is finished. - */ - void importFinished(); + void statementsReady(QList &); private slots: void slotSendDataToFilter(); void slotReceivedDataFromFilter(); void slotReceivedErrorFromFilter(); void slotProcessData(); /** * This slot is used to be informed about the end of the filtering process. * It emits the signal importFinished() */ void slotImportFinished(); private: void parseReceivedData(const QByteArray& data); /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; QProcess m_filter; QString m_filename; QUrl m_url; MyMoneyQifProfile m_qifProfile; MyMoneyAccount m_account; unsigned long m_transactionsSkipped; unsigned long m_transactionsProcessed; QStringList m_dontAskAgain; QMap m_accountTranslation; QMap m_investmentMap; QFile *m_file; char m_buffer[1024]; QByteArray m_lineBuffer; QStringList m_qifEntry; int m_extractedLine; QString m_qifLine; QStringList m_qifLines; QifEntryTypeE m_entryType; bool m_skipAccount; bool m_processingData; bool m_userAbort; bool m_autoCreatePayee; unsigned long m_pos; unsigned m_linenumber; bool m_warnedInvestment; bool m_warnedSecurity; bool m_warnedPrice; QList m_transactionCache; QList m_data; void (*m_progressCallback)(int, int, const QString&); MyMoneyFileTransaction* m_ft; }; #endif diff --git a/kmymoney/plugins/qif/import/qifimport.json.in b/kmymoney/plugins/qif/import/qifimport.json.in new file mode 100644 index 000000000..c34a9605b --- /dev/null +++ b/kmymoney/plugins/qif/import/qifimport.json.in @@ -0,0 +1,21 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "mte@users.sourceforge.net", + "Name": "Michael Edwardes" + } + ], + "Description": "Add QIF importing to KMyMoney", + "EnabledByDefault": true, + "Icon": "document-import", + "Id": "qifimport", + "License": "GPL", + "Name": "QIF Importer", + "ServiceTypes": [ + "KMyMoney/Plugin" + ], + "Version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@", + "Website": "https://kmymoney.org/plugins.html" + } +} diff --git a/kmymoney/plugins/qif/import/qifimporterplugin.cpp b/kmymoney/plugins/qif/import/qifimporterplugin.cpp new file mode 100644 index 000000000..9eb986fe5 --- /dev/null +++ b/kmymoney/plugins/qif/import/qifimporterplugin.cpp @@ -0,0 +1,87 @@ +/*************************************************************************** + qifimporterplugin.cpp + (based on qifimporterplugin.cpp) + ------------------- + + copyright : (C) 2017 by Łukasz Wojniłowicz + 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 "qifimporterplugin.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kimportdlg.h" +#include "mymoneyqifreader.h" + +QIFImporterPlugin::QIFImporterPlugin() : + KMyMoneyPlugin::Plugin(nullptr, "qifimport"/*must be the same as X-KDE-PluginInfo-Name*/) +{ + setComponentName("kmm_qifimport", i18n("QIF importer")); + setXMLFile("kmm_qifimport.rc"); + createActions(); + // For information, announce that we have been loaded. + qDebug("KMyMoney qifimport plugin loaded"); +} + +QIFImporterPlugin::~QIFImporterPlugin() +{ +} + +void QIFImporterPlugin::createActions() +{ + m_action = actionCollection()->addAction("file_import_qif"); + m_action->setText(i18n("QIF...")); + connect(m_action, &QAction::triggered, this, &QIFImporterPlugin::slotQifImport); +} + +void QIFImporterPlugin::slotQifImport() +{ + m_action->setEnabled(false); + QPointer dlg = new KImportDlg(nullptr); + + if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { + m_qifReader = new MyMoneyQifReader; + connect(m_qifReader, &MyMoneyQifReader::statementsReady, this, &QIFImporterPlugin::slotGetStatements); + + m_qifReader->setURL(dlg->file()); + m_qifReader->setProfile(dlg->profile()); + m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); + if (!m_qifReader->startImport()) + delete m_qifReader; + } + delete dlg; + m_action->setEnabled(true); +} + +bool QIFImporterPlugin::slotGetStatements(QList &statements) +{ + auto ret = true; + foreach (const auto statement, statements) + ret &= statementInterface()->import(statement); + + delete m_qifReader; + return ret; +} + +//#include "qifimporterplugin.moc" diff --git a/kmymoney/plugins/qif/import/qifimporterplugin.h b/kmymoney/plugins/qif/import/qifimporterplugin.h new file mode 100644 index 000000000..c8c49755d --- /dev/null +++ b/kmymoney/plugins/qif/import/qifimporterplugin.h @@ -0,0 +1,63 @@ +/*************************************************************************** + qifimporterplugin.h + ------------------- + copyright : (C) 2017 by Łukasz Wojniłowicz + 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 QIFIMPORTERPLUGIN_H +#define QIFIMPORTERPLUGIN_H + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// QT Includes + +// Project Includes + +#include "kmymoneyplugin.h" + +class MyMoneyQifReader; + +class QIFImporterPlugin : public KMyMoneyPlugin::Plugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kmymoney.plugins.qifimport" FILE "qifimport.json") + +public: + explicit QIFImporterPlugin(); + ~QIFImporterPlugin(); + + QAction *m_action; + +private: + MyMoneyQifReader *m_qifReader; + +private slots: + + /** + * Called when the user wishes to import tab delimeted transactions + * into the current account. An account must be open for this to + * work. Calls KMyMoneyView::slotAccountImportAscii. + * + * @see MyMoneyAccount + */ + void slotQifImport(); + + bool slotGetStatements(QList &statements); + +protected: + void createActions(); +}; + +#endif diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index 05af01181..95bad8a26 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,2213 +1,2211 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart 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 "kmymoneyview.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #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 #ifdef KF5Activities_FOUND #include #endif // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include "kendingbalancedlg.h" #include "kchooseimportexportdlg.h" -#include "kimportdlg.h" -#include "kexportdlg.h" #include "knewloanwizard.h" #include "kcurrencyeditdlg.h" #include "kfindtransactiondlg.h" #include "knewbankdlg.h" #include "mymoneyseqaccessmgr.h" #include "mymoneydatabasemgr.h" #include "imymoneystorageformat.h" #include "mymoneystoragebin.h" #include "mymoneyexception.h" #include "mymoneystoragexml.h" #include "mymoneystoragesql.h" #include "mymoneygncreader.h" #include "mymoneystorageanon.h" #include #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h" #include "kinstitutionsview.h" #include "kpayeesview.h" #include "ktagsview.h" #include "kscheduledview.h" #include "kgloballedgerview.h" #include "simpleledgerview.h" #include "kinvestmentview.h" #include "kreportsview.h" #include "kbudgetview.h" #include "kforecastview.h" #include "konlinejoboutbox.h" #include "kmymoney.h" #include "kmymoneyutils.h" #include "models.h" #include using namespace Icons; static constexpr KCompressionDevice::CompressionType COMPRESSION_TYPE = KCompressionDevice::GZip; static constexpr char recoveryKeyId[] = "0xD2B08440"; KMyMoneyView::KMyMoneyView(KMyMoneyApp *kmymoney) : KPageWidget(nullptr), m_header(0), m_inConstructor(true), m_fileOpen(false), m_fmode(0600), m_lastViewSelected(0) #ifdef KF5Activities_FOUND , m_activityResourceInstance(0) #endif { // this is a workaround for the bug in KPageWidget that causes the header to be shown // for a short while during page switch which causes a kind of bouncing of the page's // content and if the page's content is at it's minimum size then during a page switch // the main window's size is also increased to fit the header that is shown for a sort // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) // in a grid layout so if we find it there remove it for good to avoid the described issues QGridLayout* gridLayout = qobject_cast(layout()); if (gridLayout) { QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout if (headerItem && qobject_cast(headerItem->widget()) != NULL) { gridLayout->removeItem(headerItem); // after we remove the KPageWidget standard header replace it with our own title label m_header = new KMyMoneyTitleLabel(this); m_header->setObjectName("titleLabel"); m_header->setMinimumSize(QSize(100, 30)); m_header->setRightImageFile("pics/titlelabel_background.png"); m_header->setVisible(KMyMoneyGlobalSettings::showTitleBar()); gridLayout->addWidget(m_header, 1, 1); } } newStorage(); m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit connect(kmymoney, SIGNAL(fileLoaded(QUrl)), this, SLOT(slotRefreshViews())); // let the accounts model know which account is being currently reconciled connect(this, SIGNAL(reconciliationStarts(MyMoneyAccount,QDate,MyMoneyMoney)), Models::instance()->accountsModel(), SLOT(slotReconcileAccount(MyMoneyAccount,QDate,MyMoneyMoney))); // Page 0 m_homeView = new KHomeView(); viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home")); viewFrames[View::Home]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewHome])); connect(m_homeView, SIGNAL(ledgerSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_homeView, SIGNAL(scheduleSelected(QString)), this, SLOT(slotScheduleSelected(QString))); connect(m_homeView, SIGNAL(reportSelected(QString)), this, SLOT(slotShowReport(QString))); connect(m_homeView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 1 m_institutionsView = new KInstitutionsView(kmymoney, this); viewFrames[View::Institutions] = m_model->addPage(m_institutionsView, i18n("Institutions")); viewFrames[View::Institutions]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewInstitutions])); // Page 2 m_accountsView = new KAccountsView(kmymoney, this); viewFrames[View::Accounts] = m_model->addPage(m_accountsView, i18n("Accounts")); viewFrames[View::Accounts]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewAccounts])); // Page 3 m_scheduledView = new KScheduledView(); //this is to solve the way long strings are handled differently among versions of KPageWidget viewFrames[View::Schedules] = m_model->addPage(m_scheduledView, i18n("Scheduled transactions")); viewFrames[View::Schedules]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewSchedules])); connect(m_scheduledView, SIGNAL(scheduleSelected(MyMoneySchedule)), kmymoney, SLOT(slotSelectSchedule(MyMoneySchedule))); connect(m_scheduledView, SIGNAL(openContextMenu()), kmymoney, SLOT(slotShowScheduleContextMenu())); connect(m_scheduledView, SIGNAL(enterSchedule()), kmymoney, SLOT(slotScheduleEnter())); connect(m_scheduledView, SIGNAL(skipSchedule()), kmymoney, SLOT(slotScheduleSkip())); connect(m_scheduledView, SIGNAL(editSchedule()), kmymoney, SLOT(slotScheduleEdit())); connect(m_scheduledView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 4 m_categoriesView = new KCategoriesView(kmymoney, this); viewFrames[View::Categories] = m_model->addPage(m_categoriesView, i18n("Categories")); viewFrames[View::Categories]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewCategories])); // Page 5 m_tagsView = new KTagsView(); viewFrames[View::Tags] = m_model->addPage(m_tagsView, i18n("Tags")); viewFrames[View::Tags]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewTags])); connect(kmymoney, SIGNAL(tagCreated(QString)), m_tagsView, SLOT(slotSelectTagAndTransaction(QString))); connect(kmymoney, SIGNAL(tagRename()), m_tagsView, SLOT(slotRenameButtonCliked())); connect(m_tagsView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowTagContextMenu())); connect(m_tagsView, SIGNAL(selectObjects(QList)), kmymoney, SLOT(slotSelectTags(QList))); connect(m_tagsView, SIGNAL(transactionSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_tagsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 6 m_payeesView = new KPayeesView(); viewFrames[View::Payees] = m_model->addPage(m_payeesView, i18n("Payees")); viewFrames[View::Payees]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewPayees])); connect(kmymoney, SIGNAL(payeeCreated(QString)), m_payeesView, SLOT(slotSelectPayeeAndTransaction(QString))); connect(kmymoney, SIGNAL(payeeRename()), m_payeesView, SLOT(slotRenameButtonCliked())); connect(m_payeesView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowPayeeContextMenu())); connect(m_payeesView, SIGNAL(selectObjects(QList)), kmymoney, SLOT(slotSelectPayees(QList))); connect(m_payeesView, SIGNAL(transactionSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_payeesView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 7 m_ledgerView = new KGlobalLedgerView(); viewFrames[View::Ledgers] = m_model->addPage(m_ledgerView, i18n("Ledgers")); viewFrames[View::Ledgers]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewLedgers])); connect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_ledgerView, SIGNAL(openContextMenu()), kmymoney, SLOT(slotShowTransactionContextMenu())); connect(m_ledgerView, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), kmymoney, SLOT(slotSelectTransactions(KMyMoneyRegister::SelectedTransactions))); connect(m_ledgerView, SIGNAL(newTransaction()), kmymoney, SLOT(slotTransactionsNew())); connect(m_ledgerView, SIGNAL(cancelOrEndEdit(bool&)), kmymoney, SLOT(slotTransactionsCancelOrEnter(bool&))); connect(m_ledgerView, SIGNAL(startEdit()), kmymoney, SLOT(slotTransactionsEdit())); connect(m_ledgerView, SIGNAL(endEdit()), kmymoney, SLOT(slotTransactionsEnter())); connect(m_ledgerView, SIGNAL(toggleReconciliationFlag()), kmymoney, SLOT(slotToggleReconciliationFlag())); connect(this, SIGNAL(reconciliationStarts(MyMoneyAccount,QDate,MyMoneyMoney)), m_ledgerView, SLOT(slotSetReconcileAccount(MyMoneyAccount,QDate,MyMoneyMoney))); connect(kmymoney, SIGNAL(selectAllTransactions()), m_ledgerView, SLOT(slotSelectAllTransactions())); connect(m_ledgerView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 8 m_investmentView = new KInvestmentView(); viewFrames[View::Investments] = m_model->addPage(m_investmentView, i18n("Investments")); viewFrames[View::Investments]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewInvestment])); connect(m_investmentView, SIGNAL(accountSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_investmentView, SIGNAL(investmentRightMouseClick()), kmymoney, SLOT(slotShowInvestmentContextMenu())); connect(m_investmentView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 9 m_reportsView = new KReportsView(); viewFrames[View::Reports] = m_model->addPage(m_reportsView, i18n("Reports")); viewFrames[View::Reports]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewReports])); connect(m_reportsView, SIGNAL(ledgerSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_reportsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 10 m_budgetView = new KBudgetView(kmymoney, this); viewFrames[View::Budget] = m_model->addPage(m_budgetView, i18n("Budgets")); viewFrames[View::Budget]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewBudgets])); // Page 11 m_forecastView = new KForecastView(); viewFrames[View::Forecast] = m_model->addPage(m_forecastView, i18n("Forecast")); viewFrames[View::Forecast]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewForecast])); connect(m_forecastView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 12 m_onlineJobOutboxView = new KOnlineJobOutbox(); viewFrames[View::OnlineJobOutbox] = m_model->addPage(m_onlineJobOutboxView, i18n("Outbox")); viewFrames[View::OnlineJobOutbox]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewOutbox])); connect(m_onlineJobOutboxView, SIGNAL(sendJobs(QList)), kmymoney, SLOT(slotOnlineJobSend(QList))); connect(m_onlineJobOutboxView, SIGNAL(editJob(QString)), kmymoney, SLOT(slotEditOnlineJob(QString))); connect(m_onlineJobOutboxView, SIGNAL(newCreditTransfer()), kmymoney, SLOT(slotNewOnlineTransfer())); connect(m_onlineJobOutboxView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); connect(m_onlineJobOutboxView, SIGNAL(showContextMenu(onlineJob)), kmymoney, SLOT(slotShowOnlineJobContextMenu())); SimpleLedgerView* view = new SimpleLedgerView; KPageWidgetItem* frame = m_model->addPage(view, i18n("New ledger")); frame->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentProperties])); connect(this, SIGNAL(fileClosed()), view, SLOT(closeLedgers())); connect(this, SIGNAL(fileOpened()), view, SLOT(openFavoriteLedgers())); //set the model setModel(m_model); setCurrentPage(viewFrames[View::Home]); connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); updateViewType(); m_inConstructor = false; // Initialize kactivities resource instance #ifdef KF5Activities_FOUND m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this); connect(kmymoney, SIGNAL(fileLoaded(QUrl)), m_activityResourceInstance, SLOT(setUri(QUrl))); #endif } KMyMoneyView::~KMyMoneyView() { KMyMoneyGlobalSettings::setLastViewSelected(m_lastViewSelected); removeStorage(); } void KMyMoneyView::showTitleBar(bool show) { if (m_header) m_header->setVisible(show); } void KMyMoneyView::updateViewType() { // set the face type KPageView::FaceType faceType = KPageView::List; switch (KMyMoneySettings::viewType()) { case 0: faceType = KPageView::List; break; case 1: faceType = KPageView::Tree; break; case 2: faceType = KPageView::Tabbed; break; } if (faceType != KMyMoneyView::faceType()) { setFaceType(faceType); if (faceType == KPageView::Tree) { QList views = findChildren(); foreach (QTreeView * view, views) { if (view && (view->parent() == this)) { view->setRootIsDecorated(false); break; } } } } } void KMyMoneyView::slotAccountTreeViewChanged(const AccountsModel::Columns column, const bool show) { struct viewInfo { KRecursiveFilterProxyModel *proxyModel; QList *proxyColumns; }; QList viewInfos; if (m_institutionsView->isLoaded()) viewInfos.append({m_institutionsView->getProxyModel(), m_institutionsView->getProxyColumns()}); if (m_accountsView->isLoaded()) viewInfos.append({m_accountsView->getProxyModel(), m_accountsView->getProxyColumns()}); if (m_categoriesView->isLoaded()) viewInfos.append({m_categoriesView->getProxyModel(), m_categoriesView->getProxyColumns()}); if (m_budgetView->isLoaded()) viewInfos.append({m_budgetView->getProxyModel(), m_budgetView->getProxyColumns()}); if (show) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); if (viewInfos.count() == 1 || KMessageBox::questionYesNo(this, i18n("Do you want to show %1 column on every loaded view?", AccountsModel::getHeaderName(column)), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("ShowColumnOnEveryView")) == KMessageBox::Yes) { foreach(viewInfo view, viewInfos) { if (!view.proxyColumns->contains(column)) { view.proxyColumns->append(column); view.proxyModel->invalidate(); } } } } else { if (viewInfos.count() == 1 || KMessageBox::questionYesNo(this, i18n("Do you want to hide %1 column on every loaded view?", AccountsModel::getHeaderName(column)), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("HideColumnOnEveryView")) == KMessageBox::Yes) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); foreach(viewInfo view, viewInfos) { if (view.proxyColumns->contains(column)) { view.proxyColumns->removeOne(column); view.proxyModel->invalidate(); } } } } } void KMyMoneyView::slotNetBalProChanged(const MyMoneyMoney &val, QLabel *label, const View view) { QString s; const auto isNegative = val.isNegative(); switch (view) { case View::Institutions: case View::Accounts: s = i18n("Net Worth: "); break; case View::Categories: if (isNegative) s = i18n("Loss: "); else s = i18n("Profit: "); break; case View::Budget: s = (i18nc("The balance of the selected budget", "Balance: ")); break; default: return; } // FIXME figure out how to deal with the approximate // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) // s += "~ "; s.replace(QLatin1Char(' '), QLatin1String(" ")); if (isNegative) s.append(QLatin1String("")); const auto sec = MyMoneyFile::instance()->baseCurrency(); QString v(MyMoneyUtils::formatMoney(val, sec)); s.append((v.replace(QLatin1Char(' '), QLatin1String(" ")))); if (isNegative) s.append(QLatin1String("")); label->setFont(KMyMoneyGlobalSettings::listCellFont()); label->setText(s); } bool KMyMoneyView::showPageHeader() const { return false; } void KMyMoneyView::showPage(KPageWidgetItem* pageItem) { // reset all selected items before showing the selected view // but not while we're in our own constructor if (!m_inConstructor && pageItem != currentPage()) { kmymoney->slotResetSelections(); } // pretend we're in the constructor to avoid calling the // above resets. For some reason which I don't know the details // of, KJanusWidget::showPage() calls itself recursively. This // screws up the action handling, as items could have been selected // in the meantime. We prevent this by setting the m_inConstructor // to true and reset it to the previos value when we leave this method. bool prevConstructor = m_inConstructor; m_inConstructor = true; setCurrentPage(pageItem); m_inConstructor = prevConstructor; if (!m_inConstructor) { // fixup some actions that are dependant on the view // this does not work during construction kmymoney->slotUpdateActions(); } } bool KMyMoneyView::canPrint() { bool rc = ( viewFrames[View::Reports] == currentPage() || viewFrames[View::Home] == currentPage() ); return rc; } bool KMyMoneyView::canCreateTransactions(const KMyMoneyRegister::SelectedTransactions& /* list */, QString& tooltip) const { // we can only create transactions in the ledger view so // we check that this is the active page bool rc = (viewFrames[View::Ledgers] == currentPage()); if (rc) rc = m_ledgerView->canCreateTransactions(tooltip); else tooltip = i18n("Creating transactions can only be performed in the ledger view"); return rc; } bool KMyMoneyView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { // we can only modify transactions in the ledger view so // we check that this is the active page bool rc = (viewFrames[View::Ledgers] == currentPage()); if (rc) { rc = m_ledgerView->canModifyTransactions(list, tooltip); } else { tooltip = i18n("Modifying transactions can only be performed in the ledger view"); } return rc; } bool KMyMoneyView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { // we can only duplicate transactions in the ledger view so // we check that this is the active page bool rc = (viewFrames[View::Ledgers] == currentPage()); if (rc) { rc = m_ledgerView->canDuplicateTransactions(list, tooltip); } else { tooltip = i18n("Duplicating transactions can only be performed in the ledger view"); } return rc; } bool KMyMoneyView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { bool rc; // we can only edit transactions in the ledger view so // we check that this is the active page if ((rc = canModifyTransactions(list, tooltip)) == true) { tooltip = i18n("Edit the current selected transactions"); rc = m_ledgerView->canEditTransactions(list, tooltip); } return rc; } bool KMyMoneyView::createNewTransaction() { bool rc = false; KMyMoneyRegister::SelectedTransactions list; QString txt; if (canCreateTransactions(list, txt)) { rc = m_ledgerView->selectEmptyTransaction(); } return rc; } TransactionEditor* KMyMoneyView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) { TransactionEditor* editor = 0; QString txt; if (canEditTransactions(list, txt) || canCreateTransactions(list, txt)) { editor = m_ledgerView->startEdit(list); } return editor; } void KMyMoneyView::newStorage(storageTypeE t) { removeStorage(); MyMoneyFile* file = MyMoneyFile::instance(); if (t == Memory) file->attachStorage(new MyMoneySeqAccessMgr); else file->attachStorage(new MyMoneyDatabaseMgr); } void KMyMoneyView::removeStorage() { MyMoneyFile* file = MyMoneyFile::instance(); IMyMoneyStorage* p = file->storage(); if (p != 0) { file->detachStorage(p); delete p; } } void KMyMoneyView::enableViewsIfFileOpen() { // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this if (viewFrames[View::Accounts]->isEnabled() != m_fileOpen) viewFrames[View::Accounts]->setEnabled(m_fileOpen); if (viewFrames[View::Institutions]->isEnabled() != m_fileOpen) viewFrames[View::Institutions]->setEnabled(m_fileOpen); if (viewFrames[View::Schedules]->isEnabled() != m_fileOpen) viewFrames[View::Schedules]->setEnabled(m_fileOpen); if (viewFrames[View::Categories]->isEnabled() != m_fileOpen) viewFrames[View::Categories]->setEnabled(m_fileOpen); if (viewFrames[View::Payees]->isEnabled() != m_fileOpen) viewFrames[View::Payees]->setEnabled(m_fileOpen); if (viewFrames[View::Tags]->isEnabled() != m_fileOpen) viewFrames[View::Tags]->setEnabled(m_fileOpen); if (viewFrames[View::Budget]->isEnabled() != m_fileOpen) viewFrames[View::Budget]->setEnabled(m_fileOpen); if (viewFrames[View::Ledgers]->isEnabled() != m_fileOpen) viewFrames[View::Ledgers]->setEnabled(m_fileOpen); if (viewFrames[View::Investments]->isEnabled() != m_fileOpen) viewFrames[View::Investments]->setEnabled(m_fileOpen); if (viewFrames[View::Reports]->isEnabled() != m_fileOpen) viewFrames[View::Reports]->setEnabled(m_fileOpen); if (viewFrames[View::Forecast]->isEnabled() != m_fileOpen) viewFrames[View::Forecast]->setEnabled(m_fileOpen); if (viewFrames[View::OnlineJobOutbox]->isEnabled() != m_fileOpen) viewFrames[View::OnlineJobOutbox]->setEnabled(m_fileOpen); emit viewStateChanged(m_fileOpen); } void KMyMoneyView::slotLedgerSelected(const QString& _accId, const QString& transaction) { MyMoneyAccount acc = MyMoneyFile::instance()->account(_accId); QString accId(_accId); switch (acc.accountType()) { case MyMoneyAccount::Stock: // if a stock account is selected, we show the // the corresponding parent (investment) account acc = MyMoneyFile::instance()->account(acc.parentAccountId()); accId = acc.id(); // intentional fall through case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::CreditCard: case MyMoneyAccount::Loan: case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Income: case MyMoneyAccount::Expense: case MyMoneyAccount::Investment: case MyMoneyAccount::Equity: setCurrentPage(viewFrames[View::Ledgers]); m_ledgerView->slotSelectAccount(accId, transaction); break; case MyMoneyAccount::CertificateDep: case MyMoneyAccount::MoneyMarket: case MyMoneyAccount::Currency: qDebug("No ledger view available for account type %d", acc.accountType()); break; default: qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", acc.accountType()); break; } } void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) { showPage(viewFrames[View::Payees]); m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(viewFrames[View::Tags]); m_tagsView->slotSelectTagAndTransaction(tag, account, transaction); } void KMyMoneyView::slotScheduleSelected(const QString& scheduleId) { MyMoneySchedule sched = MyMoneyFile::instance()->schedule(scheduleId); kmymoney->slotSelectSchedule(sched); } void KMyMoneyView::slotShowReport(const QString& reportid) { showPage(viewFrames[View::Reports]); m_reportsView->slotOpenReport(reportid); } void KMyMoneyView::slotShowReport(const MyMoneyReport& report) { showPage(viewFrames[View::Reports]); m_reportsView->slotOpenReport(report); } bool KMyMoneyView::fileOpen() { return m_fileOpen; } void KMyMoneyView::closeFile() { if (m_reportsView) m_reportsView->slotCloseAll(); // disconnect the signals disconnect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->accountsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); disconnect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->institutionsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); disconnect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(dataChanged()), m_homeView, SLOT(slotLoadView())); // 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(); emit kmmFilePlugin(preClose); if (isDatabase()) MyMoneyFile::instance()->storage()->close(); // to log off a database user newStorage(); slotShowHomePage(); emit kmmFilePlugin(postClose); m_fileOpen = false; emit fileClosed(); } void KMyMoneyView::ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } bool KMyMoneyView::readFile(const QUrl &url) { QString filename; m_fileOpen = false; bool isEncrypted = false; IMyMoneyStorageFormat* pReader = 0; if (!url.isValid()) { qDebug("Invalid URL '%s'", qPrintable(url.url())); return false; } // disconnect the current storga manager from the engine MyMoneyFile::instance()->detachStorage(); if (url.scheme() == "sql") { // handle reading of database m_fileType = KmmDb; // get rid of the mode parameter which is now redundant QUrl newUrl(url); // TODO: port to kf5 #if 0 if (QUrlQuery(url).hasQueryItem("mode")) { newUrl.removeQueryItem("mode"); } #endif return (openDatabase(newUrl)); // on error, any message will have been displayed } IMyMoneyStorage *storage = new MyMoneySeqAccessMgr; if (url.isLocalFile()) { filename = url.toLocalFile(); } else { // TODO: port to kf5 #if 0 if (!KIO::NetAccess::download(url, filename, 0)) { KMessageBox::detailedError(this, i18n("Error while loading file '%1'.", url.url()), KIO::NetAccess::lastErrorString(), i18n("File access error")); return false; } #endif } // let's glimps into the file to figure out, if it's one // of the old (uncompressed) or new (compressed) files. QFile file(filename); QFileInfo info(file); if (!info.isFile()) { QString msg = i18n("

%1 is not a KMyMoney file.

", filename); KMessageBox::error(this, msg, i18n("Filetype Error")); return false; } m_fmode = 0600; m_fmode |= info.permission(QFile::ReadGroup) ? 040 : 0; m_fmode |= info.permission(QFile::WriteGroup) ? 020 : 0; m_fmode |= info.permission(QFile::ReadOther) ? 004 : 0; m_fmode |= info.permission(QFile::WriteOther) ? 002 : 0; bool rc = true; // There's a problem with the KFilterDev and KGPGFile classes: // One supports the at(n) member but not ungetch() together with // read() and the other does not provide an at(n) method but // supports read() that considers the ungetch() buffer. QFile // supports everything so this is not a problem. We solve the problem // for now by keeping track of which method can be used. bool haveAt = true; emit kmmFilePlugin(preOpen); if (file.open(QIODevice::ReadOnly)) { QByteArray hdr(2, '\0'); int cnt; cnt = file.read(hdr.data(), 2); file.close(); if (cnt == 2) { QIODevice* qfile = nullptr; if (QString(hdr) == QString("\037\213")) { // gzipped? qfile = new KCompressionDevice(filename, COMPRESSION_TYPE); } else if (QString(hdr) == QString("--") // PGP ASCII armored? || QString(hdr) == QString("\205\001") // PGP binary? || QString(hdr) == QString("\205\002")) { // PGP binary? if (KGPGFile::GPGAvailable()) { qfile = new KGPGFile(filename); haveAt = false; isEncrypted = true; } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("GPG is not available for decryption of file %1", filename))); qfile = new QFile(file.fileName()); } } else { // we can't use file directly, as we delete qfile later on qfile = new QFile(file.fileName()); } if (qfile->open(QIODevice::ReadOnly)) { try { hdr.resize(8); if (qfile->read(hdr.data(), 8) == 8) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 8); // Ok, we got the first block of 8 bytes. Read in the two // unsigned long int's by preserving endianess. This is // achieved by reading them through a QDataStream object qint32 magic0, magic1; QDataStream s(&hdr, QIODevice::ReadOnly); s >> magic0; s >> magic1; // If both magic numbers match (we actually read in the // text 'KMyMoney' then we assume a binary file and // construct a reader for it. Otherwise, we construct // an XML reader object. // // The expression magic0 < 30 is only used to create // a binary reader if we assume an old binary file. This // should be removed at some point. An alternative is to // check the beginning of the file against an pattern // of the XML file (e.g. '?read(hdr.data(), 70) == 70) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 70); QRegExp kmyexp(""); QRegExp gncexp("attachStorage(storage); loadAllCurrencies(); // currency list required for gnc MyMoneyFile::instance()->detachStorage(storage); pReader = new MyMoneyGncReader; m_fileType = GncXML; } } } if (pReader) { pReader->setProgressCallback(&KMyMoneyView::progressCallback); pReader->readFile(qfile, dynamic_cast(storage)); } else { if (m_fileType == KmmBinary) { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.", filename))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains an unknown file format.", filename))); } rc = false; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot read from file %1.", filename))); rc = false; } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot load file %1. Reason: %2", filename, e.what()))); rc = false; } if (pReader) { pReader->setProgressCallback(0); delete pReader; } qfile->close(); } else { KGPGFile *gpgFile = qobject_cast(qfile); if (gpgFile && !gpgFile->errorToString().isEmpty()) { KMessageBox::sorry(this, QString("%1"). arg(i18n("The following error was encountered while decrypting file %1: %2", filename, gpgFile->errorToString()))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); } rc = false; } delete qfile; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); rc = false; } // things are finished, now we connect the storage to the engine // which forces a reload of the cache in the engine with those // objects that are cached MyMoneyFile::instance()->attachStorage(storage); if (rc == false) return rc; // encapsulate transactions to the engine to be able to commit/rollback MyMoneyFileTransaction ft; // make sure we setup the encryption key correctly if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneyGlobalSettings::gpgRecipientList().join(",")); } // make sure we setup the name of the base accounts in translated form try { MyMoneyFile *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 &) { } // if a temporary file was constructed by NetAccess::download, // then it will be removed with the next call. Otherwise, it // stays untouched on the local filesystem // TODO: port to kf5 //KIO::NetAccess::removeTempFile(filename); return initializeStorage(); } void KMyMoneyView::checkAccountName(const MyMoneyAccount& _acc, const QString& name) const { MyMoneyFile* file = MyMoneyFile::instance(); if (_acc.name() != name) { MyMoneyAccount acc(_acc); acc.setName(name); file->modifyAccount(acc); } } bool KMyMoneyView::openDatabase(const QUrl &url) { m_fileOpen = false; // open the database IMyMoneySerialize* pStorage = dynamic_cast(MyMoneyFile::instance()->storage()); MyMoneyDatabaseMgr* pDBMgr = 0; if (! pStorage) { pDBMgr = new MyMoneyDatabaseMgr; pStorage = dynamic_cast(pDBMgr); } QExplicitlySharedDataPointer reader = pStorage->connectToDatabase(url); QUrl dbURL(url); bool retry = true; while (retry) { switch (reader->open(dbURL, QIODevice::ReadWrite)) { case 0: // opened okay retry = false; break; case 1: // permanent error KMessageBox::detailedError(this, i18n("Cannot open database %1\n", dbURL.toDisplayString()), reader->lastError()); if (pDBMgr) { removeStorage(); delete pDBMgr; } return false; case -1: // retryable error if (KMessageBox::warningYesNo(this, reader->lastError(), PACKAGE) == KMessageBox::No) { if (pDBMgr) { removeStorage(); delete pDBMgr; } return false; } else { // TODO: port to kf5 #if 0 QString options = QUrlQuery(dbURL).queryItemValue("options") + ",override"; dbURL.removeQueryItem("mode"); // now redundant dbURL.removeQueryItem("options"); dbURL.addQueryItem("options", options); #endif } } } if (pDBMgr) { removeStorage(); MyMoneyFile::instance()->attachStorage(pDBMgr); } // single user mode; read some of the data into memory // FIXME - readFile no longer relevant? // tried removing it but then got no indication that loading was complete // also, didn't show home page reader->setProgressCallback(&KMyMoneyView::progressCallback); if (!reader->readFile()) { KMessageBox::detailedError(0, i18n("An unrecoverable error occurred while reading the database"), reader->lastError().toLatin1(), i18n("Database malfunction")); return false; } m_fileOpen = true; reader->setProgressCallback(0); return initializeStorage(); } bool KMyMoneyView::initializeStorage() { bool blocked = MyMoneyFile::instance()->signalsBlocked(); MyMoneyFile::instance()->blockSignals(true); // we check, if we have any currency in the file. If not, we load // all the default currencies we know. MyMoneyFileTransaction ft; try { updateCurrencyNames(); ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } // make sure, we have a base currency and all accounts are // also assigned to a currency. QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseId.isEmpty()) { // Stay in this endless loop until we have a base currency, // as without it the application does not work anymore. while (baseId.isEmpty()) { selectBaseCurrency(); try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } } } else { // in some odd intermediate cases there could be files out there // that have a base currency set, but still have accounts that // do not have a base currency assigned. This call will take // care of it. We can safely remove it later. // // Another work-around for this scenario is to remove the base // currency setting from the XML file by removing the line // // // // and restart the application with this file. This will force to // run the above loop. selectBaseCurrency(); } KSharedConfigPtr config = KSharedConfig::openConfig(); KPageWidgetItem* page; KConfigGroup grp = config->group("General Options"); if (KMyMoneyGlobalSettings::startLastViewSelected() != 0) page = viewFrames.value(static_cast(KMyMoneyGlobalSettings::lastViewSelected())); else page = viewFrames[View::Home]; // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true grp = config->group("General Options"); if (grp.readEntry("SkipFix", false) != true) { MyMoneyFileTransaction ft; try { // Check if we have to modify the file before we allow to work with it IMyMoneyStorage* 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(i18n("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); // FIXME: we need to check, if it's necessary to have this // automatic funcitonality // if there's no asset account, then automatically start the // new account wizard // kmymoney->createInitialAccount(); m_fileOpen = true; emit kmmFilePlugin(postOpen); Models::instance()->fileOpened(); // connect the needed signals connect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->accountsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); connect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->institutionsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); connect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); // inform everyone about new data MyMoneyFile::instance()->preloadCache(); MyMoneyFile::instance()->forceDataChanged(); // views can wait since they are going to be refresed in slotRefreshViews connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), m_homeView, SLOT(slotLoadView())); // if we currently see a different page, then select the right one if (page != currentPage()) { showPage(page); } emit fileOpened(); return true; } void KMyMoneyView::saveToLocalFile(const QString& localFile, IMyMoneyStorageFormat* pWriter, bool plaintext, const QString& keyList) { // Check GPG encryption bool encryptFile = true; bool encryptRecover = false; if (!keyList.isEmpty()) { if (!KGPGFile::GPGAvailable()) { KMessageBox::sorry(this, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); encryptFile = false; } else { if (KMyMoneyGlobalSettings::encryptRecover()) { encryptRecover = true; if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { KMessageBox::sorry(this, i18n("

You have selected to encrypt your data also with the KMyMoney recover key, but the key with id

%1

has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the KMyMoney web-site. This time your data will not be encrypted with the KMyMoney recover key.

", QString(recoveryKeyId)), i18n("GPG Key not found")); encryptRecover = false; } } for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { if (!KGPGFile::keyAvailable(key)) { KMessageBox::sorry(this, i18n("

You have specified to encrypt your data for the user-id

%1.

Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.

", key), i18n("GPG Key not found")); encryptFile = false; break; } } if (encryptFile == true) { QString msg = i18n("

You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer No.

"); if (KMessageBox::questionYesNo(this, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { encryptFile = false; } } } } // Create a temporary file if needed QString writeFile = localFile; QTemporaryFile tmpFile; if (QFile::exists(localFile)) { tmpFile.open(); writeFile = tmpFile.fileName(); tmpFile.close(); } /** * @brief Automatically restore settings when scope is left */ struct restorePreviousSettingsHelper { restorePreviousSettingsHelper(mode_t mode) : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()}, m_oldMask{umask((~mode) & 0777u)} { MyMoneyFile::instance()->blockSignals(true); } ~restorePreviousSettingsHelper() { MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); umask(m_oldMask); } const bool m_signalsWereBlocked; const mode_t m_oldMask; } restoreHelper{m_fmode}; MyMoneyFileTransaction ft; MyMoneyFile::instance()->deletePair("kmm-encryption-key"); std::unique_ptr device; if (!keyList.isEmpty() && encryptFile && !plaintext) { std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); if (kgpg) { for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { kgpg->addRecipient(key.toLatin1()); } if (encryptRecover) { kgpg->addRecipient(recoveryKeyId); } MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); device = std::unique_ptr(kgpg.release()); } } else { QFile *file = new QFile(writeFile); // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); } ft.commit(); if (!device || !device->open(QIODevice::WriteOnly)) { throw MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.", localFile)); } pWriter->setProgressCallback(&KMyMoneyView::progressCallback); pWriter->writeFile(device.get(), dynamic_cast(MyMoneyFile::instance()->storage())); device->close(); // Check for errors if possible, only possible for KGPGFile QFileDevice *fileDevice = qobject_cast(device.get()); if (fileDevice && fileDevice->error() != QFileDevice::NoError) { throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } if (writeFile != localFile) { // This simple comparison is possible because the strings are equal if no temporary file was created. // If a temporary file was created, it is made in a way that the name is definitely different. So no // symlinks etc. have to be evaluated. if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } pWriter->setProgressCallback(0); } bool KMyMoneyView::saveFile(const QUrl &url, const QString& keyList) { QString filename = url.path(); if (!fileOpen()) { KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); return false; } emit kmmFilePlugin(preSave); std::unique_ptr storageWriter; // If this file ends in ".ANON.XML" then this should be written using the // anonymous writer. bool plaintext = filename.right(4).toLower() == ".xml"; if (filename.right(9).toLower() == ".anon.xml") { //! @todo C++14: use std::make_unique, also some lines below storageWriter = std::unique_ptr(new MyMoneyStorageANON); } else { storageWriter = std::unique_ptr(new MyMoneyStorageXML); } // actually, url should be the parameter to this function // but for now, this would involve too many changes bool rc = true; try { if (! url.isValid()) { throw MYMONEYEXCEPTION(i18n("Malformed URL '%1'", url.url())); } if (url.isLocalFile()) { filename = url.toLocalFile(); try { const unsigned int nbak = KMyMoneyGlobalSettings::autoBackupCopies(); if (nbak) { KBackup::numberedBackupFile(filename, QString(), QString::fromLatin1("~"), nbak); } saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); } catch (const MyMoneyException &) { throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); } } else { QTemporaryFile tmpfile; tmpfile.open(); // to obtain the name tmpfile.close(); saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); // TODO: port to kf5 //if (!KIO::NetAccess::upload(tmpfile.fileName(), url, 0)) // throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.toDisplayString())); } m_fileType = KmmXML; } catch (const MyMoneyException &e) { KMessageBox::error(this, e.what()); MyMoneyFile::instance()->setDirty(); rc = false; } emit kmmFilePlugin(postSave); return rc; } bool KMyMoneyView::saveAsDatabase(const QUrl &url) { bool rc = false; if (!fileOpen()) { KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); return (rc); } MyMoneyStorageSql *writer = new MyMoneyStorageSql(dynamic_cast(MyMoneyFile::instance()->storage()), url); bool canWrite = false; switch (writer->open(url, QIODevice::WriteOnly)) { case 0: canWrite = true; break; case -1: // dbase already has data, see if he wants to clear it out if (KMessageBox::warningContinueCancel(0, i18n("Database contains data which must be removed before using Save As.\n" "Do you wish to continue?"), "Database not empty") == KMessageBox::Continue) { if (writer->open(url, QIODevice::WriteOnly, true) == 0) canWrite = true; } else { delete writer; return false; } break; } if (canWrite) { writer->setProgressCallback(&KMyMoneyView::progressCallback); if (!writer->writeFile()) { KMessageBox::detailedError(0, i18n("An unrecoverable error occurred while writing to the database.\n" "It may well be corrupt."), writer->lastError().toLatin1(), i18n("Database malfunction")); rc = false; } writer->setProgressCallback(0); rc = true; } else { KMessageBox::detailedError(this, i18n("Cannot open or create database %1.\n" "Retry Save As Database and click Help" " for further info.", url.toDisplayString()), writer->lastError()); } delete writer; return (rc); } bool KMyMoneyView::dirty() { if (!fileOpen()) return false; return MyMoneyFile::instance()->dirty(); } bool KMyMoneyView::startReconciliation(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) { bool ok = true; // we cannot reconcile standard accounts if (MyMoneyFile::instance()->isStandardAccount(account.id())) ok = false; // check if we can reconcile this account // it makes sense for asset and liability accounts only if (ok == true) { if (account.isAssetLiability()) { showPage(viewFrames[View::Ledgers]); // prepare reconciliation mode emit reconciliationStarts(account, reconciliationDate, endingBalance); } else { ok = false; } } return ok; } void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) { emit reconciliationStarts(MyMoneyAccount(), QDate(), MyMoneyMoney()); } void KMyMoneyView::newFile() { closeFile(); m_fileType = KmmXML; // assume native type until saved m_fileOpen = true; } void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) { if (!baseCurrency.id().isEmpty()) { QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseCurrency.id() != baseId) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(baseCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), e.what()), i18n("Set base currency")); } } } } void KMyMoneyView::selectBaseCurrency() { MyMoneyFile* 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", qPrintable(e.what())); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(this); 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", qPrintable(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 &) { } 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()), qPrintable(e.what())); } } } } } void KMyMoneyView::updateCurrencyNames() { MyMoneyFile* 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", qPrintable(e.what())); } } void KMyMoneyView::loadAllCurrencies() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; if (!file->currencyList().isEmpty()) return; QMap ancientCurrencies = file->ancientCurrencies(); try { foreach (auto currency, file->availableCurrencyList()) { file->addCurrency(currency); MyMoneyPrice price = ancientCurrencies.value(currency, MyMoneyPrice()); if (price != MyMoneyPrice()) file->addPrice(price); } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s loading currency", qPrintable(e.what())); } } void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) { if (viewFrames[View::Accounts] != currentPage()) showPage(viewFrames[View::Accounts]); m_accountsView->show(); } void KMyMoneyView::slotRefreshViews() { // turn off sync between ledger and investment view disconnect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), m_ledgerView, SLOT(slotSelectAccount(MyMoneyObject))); disconnect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), m_investmentView, SLOT(slotSelectAccount(MyMoneyObject))); // TODO turn sync between ledger and investment view if selected by user if (KMyMoneyGlobalSettings::syncLedgerInvestment()) { connect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), m_ledgerView, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), m_investmentView, SLOT(slotSelectAccount(MyMoneyObject))); } showTitleBar(KMyMoneyGlobalSettings::showTitleBar()); m_accountsView->slotLoadAccounts(); m_institutionsView->slotLoadAccounts(); m_categoriesView->slotLoadAccounts(); m_payeesView->slotLoadPayees(); m_tagsView->slotLoadTags(); m_ledgerView->slotLoadView(); m_budgetView->slotRefreshView(); m_homeView->slotLoadView(); m_investmentView->slotLoadView(); m_reportsView->slotLoadView(); m_forecastView->slotLoadForecast(); m_scheduledView->slotReloadView(); } void KMyMoneyView::slotShowTransactionDetail(bool detailed) { KMyMoneyGlobalSettings::setShowRegisterDetailed(detailed); slotRefreshViews(); } void KMyMoneyView::progressCallback(int current, int total, const QString& msg) { kmymoney->progressCallback(current, total, msg); } void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex) { // remember the current page m_lastViewSelected = current.row(); // set the current page's title in the header if (m_header) m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); } /* 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 KMyMoneyView::fixFile_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void KMyMoneyView::fixFile_2() { MyMoneyFile* 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. QList::Iterator it_t; int count = 0; for (it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) { if ((*it_t).splitCount() == 2) { QString accountId; QString categoryId; QString accountMemo; QString categoryMemo; const QList& splits = (*it_t).splits(); QList::const_iterator it_s; for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { MyMoneyAccount acc = file->account((*it_s).accountId()); if (acc.isIncomeExpense()) { categoryId = (*it_s).id(); categoryMemo = (*it_s).memo(); } else { accountId = (*it_s).id(); accountMemo = (*it_s).memo(); } } if (!accountId.isEmpty() && !categoryId.isEmpty() && accountMemo != categoryMemo) { MyMoneyTransaction t(*it_t); MyMoneySplit s(t.splitById(categoryId)); s.setMemo(accountMemo); t.modifySplit(s); file->modifyTransaction(t); ++count; } } } qDebug("%d transactions fixed in fixFile_2", count); } void KMyMoneyView::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 (!KMyMoneyGlobalSettings::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) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == MyMoneyAccount::Investment) { for (it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) { if (!list.contains(*it_b)) { missing.append(*it_b); } } } } 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 (!KMyMoneyGlobalSettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.begin(); it_a != list.end(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == MyMoneyAccount::Investment) { for (it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) { if (!list.contains(*it_b)) { missing.append(*it_b); } } } } list += missing; } m_filter.addAccount(list); } #endif void KMyMoneyView::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. */ MyMoneyFile* 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() == MyMoneyAccount::Loan || (*it_a).accountType() == MyMoneyAccount::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() == MyMoneyAccount::Equity) { if ((*it_a).parentAccountId() == equity.id()) { MyMoneyAccount 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 KMyMoneyView::fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; bool updated = false; try { // 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 { MyMoneyAccount 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() != MyMoneySplit::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; MyMoneySplit split = *it_s; split.setReconcileDate(QDate()); split.setReconcileFlag(MyMoneySplit::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", qPrintable(e.what())); } } void KMyMoneyView::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(this, 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("Fix LoanAccount0 not supported anymore"); } } void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { MyMoneyFileTransaction ft; try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } // now search the split that does not have an account reference // and set it up to be the one of the account we just added // to the account pool. Note: the schedule code used to leave // this always the first split, but the loan code leaves it as // the second one. So I thought, searching is a good alternative .... QList::ConstIterator it_s; for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { if ((*it_s).accountId().isEmpty()) { MyMoneySplit s = (*it_s); s.setAccountId(newAccount.id()); t.modifySplit(s); break; } } newSchedule.setTransaction(t); MyMoneyFile::instance()->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.isLoan()) { newAccount.setValue("schedule", newSchedule.id()); MyMoneyFile::instance()->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add schedule: %1", e.what())); } } } void KMyMoneyView::fixTransactions_0() { MyMoneyFile* 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")); kmymoney->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; for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { if (accounts.contains((*it_s).accountId())) { hasDuplicateAccounts = true; qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << (*it_s).accountId(); } else { accounts << (*it_s).accountId(); } if ((*it_s).action() == MyMoneySplit::ActionInterest) { if (interestAccounts.contains((*it_s).accountId()) == 0) { interestAccounts << (*it_s).accountId(); } } } if (hasDuplicateAccounts) { fixDuplicateAccounts_0(t); } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } // scan the transactions and modify loan transactions QList::Iterator it_t; for (it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) { const char *defaultAction = 0; QList splits = (*it_t).splits(); QList::Iterator it_s; QStringList accounts; // check if base commodity is set. if not, set baseCurrency if ((*it_t).commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " has no base currency"; (*it_t).setCommodity(file->baseCurrency().id()); file->modifyTransaction(*it_t); } bool isLoan = false; // Determine default action if ((*it_t).splitCount() == 2) { // check for transfer int accountCount = 0; MyMoneyMoney val; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { MyMoneyAccount acc = file->account((*it_s).accountId()); if (acc.accountGroup() == MyMoneyAccount::Asset || acc.accountGroup() == MyMoneyAccount::Liability) { val = (*it_s).value(); accountCount++; if (acc.accountType() == MyMoneyAccount::Loan || acc.accountType() == MyMoneyAccount::AssetLoan) isLoan = true; } else break; } if (accountCount == 2) { if (isLoan) defaultAction = MyMoneySplit::ActionAmortization; else defaultAction = MyMoneySplit::ActionTransfer; } else { if (val.isNegative()) defaultAction = MyMoneySplit::ActionWithdrawal; else defaultAction = MyMoneySplit::ActionDeposit; } } isLoan = false; for (it_s = splits.begin(); defaultAction == 0 && it_s != splits.end(); ++it_s) { MyMoneyAccount acc = file->account((*it_s).accountId()); MyMoneyMoney val = (*it_s).value(); if (acc.accountGroup() == MyMoneyAccount::Asset || acc.accountGroup() == MyMoneyAccount::Liability) { if (!val.isPositive()) defaultAction = MyMoneySplit::ActionWithdrawal; else defaultAction = MyMoneySplit::ActionDeposit; } } #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) { MyMoneyAccount acc = file->account((*it_s).accountId()); MyMoneyMoney val = (*it_s).value(); if (acc.accountType() == MyMoneyAccount::CreditCard) { if (val < 0 && (*it_s).action() != MyMoneySplit::ActionWithdrawal && (*it_s).action() != MyMoneySplit::ActionTransfer) needModify = true; if (val >= 0 && (*it_s).action() != MyMoneySplit::ActionDeposit && (*it_s).action() != MyMoneySplit::ActionTransfer) 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); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); } splits = (*it_t).splits(); // update local copy qDebug("Fixed credit card assignment in %s", (*it_t).id().data()); } #endif // Check for correct assignment of ActionInterest in all splits // and check if there are any duplicates in this transactions for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { MyMoneyAccount splitAccount = file->account((*it_s).accountId()); if (!accounts.contains((*it_s).accountId())) { accounts << (*it_s).accountId(); } // if this split references an interest account, the action // must be of type ActionInterest if (interestAccounts.contains((*it_s).accountId())) { if ((*it_s).action() != MyMoneySplit::ActionInterest) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " contains an interest account (" << (*it_s).accountId() << ") but does not have ActionInterest"; (*it_s).setAction(MyMoneySplit::ActionInterest); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); qDebug("Fixed interest action in %s", qPrintable((*it_t).id())); } // if it does not reference an interest account, it must not be // of type ActionInterest } else { if ((*it_s).action() == MyMoneySplit::ActionInterest) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " does not contain an interest account so it should not have ActionInterest"; (*it_s).setAction(defaultAction); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); qDebug("Fixed interest action in %s", qPrintable((*it_t).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 ((*it_t).commodity() == splitAccount.currencyId() && (*it_s).value() != (*it_s).shares()) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " " << (*it_s).id() << " uses the transaction currency, but shares != value"; (*it_s).setShares((*it_s).value()); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); } // fix the shares and values to have the correct fraction if (!splitAccount.isInvest()) { try { int fract = splitAccount.fraction(); if ((*it_s).shares() != (*it_s).shares().convert(fract)) { qDebug("adjusting fraction in %s,%s", qPrintable((*it_t).id()), qPrintable((*it_s).id())); (*it_s).setShares((*it_s).shares().convert(fract)); (*it_s).setValue((*it_s).value().convert(fract)); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); } } catch (const MyMoneyException &) { qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); } } } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } kmymoney->slotStatusProgressBar(-1, -1); } void KMyMoneyView::fixDuplicateAccounts_0(MyMoneyTransaction& t) { qDebug("Duplicate account in transaction %s", qPrintable(t.id())); } void KMyMoneyView::slotPrintView() { if (viewFrames[View::Reports] == currentPage()) m_reportsView->slotPrintView(); else if (viewFrames[View::Home] == currentPage()) m_homeView->slotPrintView(); } void KMyMoneyView::slotShowHomePage() { setCurrentPage(viewFrames[View::Home]); } KMyMoneyViewBase* KMyMoneyView::addBasePage(const QString& title, const QString& icon) { KMyMoneyViewBase* viewBase = new KMyMoneyViewBase(this, title, title); connect(viewBase, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); KPageWidgetItem* frm = m_model->addPage(viewBase, title); frm->setIcon(QIcon::fromTheme(icon)); return viewBase; } /* ------------------------------------------------------------------------ */ /* KMyMoneyViewBase */ /* ------------------------------------------------------------------------ */ // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class KMyMoneyViewBase::Private { public: QVBoxLayout* m_viewLayout; }; KMyMoneyViewBase::KMyMoneyViewBase(QWidget* parent, const QString& name, const QString& title) : QWidget(parent), d(new Private) { setAccessibleName(name); setAccessibleDescription(title); d->m_viewLayout = new QVBoxLayout(this); d->m_viewLayout->setSpacing(6); d->m_viewLayout->setMargin(0); } KMyMoneyViewBase::~KMyMoneyViewBase() { delete d; } void KMyMoneyViewBase::addWidget(QWidget* w) { d->m_viewLayout->addWidget(w); } QVBoxLayout* KMyMoneyViewBase::layout() const { return d->m_viewLayout; }