diff --git a/CMakeLists.txt b/CMakeLists.txt index f3e7373d6..cd27ded55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,369 +1,375 @@ # The CMake version we require cmake_minimum_required(VERSION 3.1) # Setting the name of the main project project(KMyMoney VERSION "5.0.80") # 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) list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "K_PLUGIN_FACTORY" "K_PLUGIN_FACTORY_WITH_JSON") if (POLICY CMP0063) cmake_policy(SET CMP0063 NEW) # Policy introduced in CMake version 3.3 endif() if (POLICY CMP0071) # We do not require the old behaviour. It is only set to old, to prevent accidential use of # the new behavour. If the new behaviour becomes important, cmake_minimum_required has to be # set to "3.10". cmake_policy(SET CMP0071 OLD) # Policy introduced in CMake version 3.10 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(CMakeDependentOption) include(GenerateExportHeader) include(KMyMoneyMacros) set(GPG_ENCRYPTION "no") set (OPT_KF5_COMPONENTS DocTools Holidays Contacts Akonadi IdentityManagement Activities) 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 OPTIONAL_COMPONENTS Concurrent) 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(LibAlkimia5 7.0.0 REQUIRED) if(CMAKE_SYSTEM_NAME MATCHES "Windows") include_directories(${GMP_INCLUDE_DIR}) endif() 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) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # 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("unistd.h" HAVE_UNISTD_H) check_include_file("pwd.h" HAVE_PWD_H) check_include_file("windows.h" HAVE_WINDOWS_H) check_include_file("lmcons.h" HAVE_LMCONS_H) check_include_file("process.h" HAVE_PROCESS_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() option(ENABLE_FORECASTVIEW "Enable forecast view" ON) CMAKE_DEPENDENT_OPTION(ENABLE_SQLSTORAGE "Enable SQL storage support." ON "Qt5Sql_FOUND" OFF) # 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.3) 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 LibOFX support find_package(LibOfx) if(LIBOFX_FOUND) option(ENABLE_OFXIMPORTER "Enable OFX Importer" ON) if(CMAKE_SYSTEM_NAME MATCHES "Windows") set(PATH_TO_LIBOFX_HEADER "${LIBOFX_INCLUDE_DIR}/libofx/libofx.h") # Windows doesn't even see the header if it's not full path else() set(PATH_TO_LIBOFX_HEADER "libofx/libofx.h") endif() unset(LIBOFX_HAVE_CLIENTUID) unset(LIBOFX_HAVE_CLIENTUID CACHE) #not doing this will prevent updating below check check_struct_has_member("struct OfxFiLogin" clientuid ${PATH_TO_LIBOFX_HEADER} LIBOFX_HAVE_CLIENTUID) if(NOT ENABLE_OFXIMPORTER) unset(LIBOFX_FOUND CACHE) unset(LIBOFX_HAVE_CLIENTUID CACHE) endif(NOT ENABLE_OFXIMPORTER) else(LIBOFX_FOUND) option(ENABLE_OFXIMPORTER "Enable OFX Importer" OFF) if(ENABLE_OFXIMPORTER) unset(LIBOFX_FOUND CACHE) unset(LIBOFX_HAVE_CLIENTUID CACHE) endif(ENABLE_OFXIMPORTER) endif(LIBOFX_FOUND) # 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) set(Python_ADDITIONAL_VERSIONS 2.7 2.6) find_package(PythonInterp 2.6) find_package(PythonLibs ${PYTHON_VERSION_STRING}) if(PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND) if(NOT PYTHON_VERSION_MAJOR VERSION_LESS 3) unset(PYTHONLIBS_FOUND) unset(PYTHONINTERP_FOUND) message(WARNING "Python 2 required, but Python 3 found.") else() include(FindPythonModule) find_python_module(weboob REQUIRED) endif() endif() CMAKE_DEPENDENT_OPTION(ENABLE_WEBOOB "Enable Weboob plugin" ON "PYTHONLIBS_FOUND;PYTHONINTERP_FOUND;PY_WEBOOB;Qt5Concurrent_FOUND" OFF) # 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) option(ENABLE_GNCIMPORTER "Enable GNC Importer" ON) option(ENABLE_CSVIMPORTER "Enable CSV Importer" ON) option(ENABLE_CSVEXPORTER "Enable CSV Exporter" ON) option(ENABLE_UNFINISHEDFEATURES "For devs only" OFF) # 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' 'DebugKMM' '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_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") # be more pedantic about common symbols set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Wextra") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(IS_GNU 1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op") + + # TODO: remove multiple definitions of payeeIdentifierLoader::createPayeeIdentifierFromSqlDatabase + if(CMAKE_SYSTEM_NAME MATCHES "Windows") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xlinker --allow-multiple-definition") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Xlinker --allow-multiple-definition") + endif() endif() # DebugKMM, Debugfull, Profile set(CMAKE_CXX_FLAGS_DEBUGKMM "-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") # 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() elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:Multiple") 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_DEBUGKMM "${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/CMakeLists.txt b/kmymoney/CMakeLists.txt index cabf58ef7..f06bf1186 100644 --- a/kmymoney/CMakeLists.txt +++ b/kmymoney/CMakeLists.txt @@ -1,188 +1,187 @@ include(ECMAddAppIcon) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/settings/ ${CMAKE_CURRENT_BINARY_DIR}/settings/ ${CMAKE_CURRENT_BINARY_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/widgets/ ${CMAKE_CURRENT_BINARY_DIR}/widgets/ ${CMAKE_CURRENT_SOURCE_DIR}/mymoney/ ${CMAKE_CURRENT_SOURCE_DIR}/mymoney/storage/ ${CMAKE_CURRENT_SOURCE_DIR}/plugins/ ${CMAKE_CURRENT_BINARY_DIR}/plugins/ ${CMAKE_CURRENT_SOURCE_DIR}/views/ ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/converter/ ${CMAKE_CURRENT_BINARY_DIR}/dialogs/settings/ ${CMAKE_CURRENT_BINARY_DIR}/mymoney/storage/ ${CMAKE_CURRENT_BINARY_DIR}/mymoney/ ${CMAKE_CURRENT_SOURCE_DIR}/reports/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/endingbalancedlg/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/endingbalancedlg/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/newinvestmentwizard/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/newinvestmentwizard/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/newloanwizard/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/newloanwizard/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/wizardpages/ ${CMAKE_CURRENT_SOURCE_DIR}/models/ ${CMAKE_CURRENT_BINARY_DIR}/models/ ${CMAKE_CURRENT_SOURCE_DIR}/icons/ ${CMAKE_CURRENT_BINARY_DIR}/icons/ ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/ibanandbic/widgets/ # TODO: this line should be moved to the target it belongs ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/ibanandbic/ ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/nationalaccount/ ${KMyMoney_SOURCE_DIR}/libkgpgfile/ ) add_subdirectory( mymoney ) add_subdirectory( settings ) add_subdirectory( models ) add_subdirectory( plugins ) add_subdirectory( reports ) add_subdirectory( widgets ) add_subdirectory( dialogs ) add_subdirectory( views ) add_subdirectory( converter ) add_subdirectory( wizards ) add_subdirectory( pics ) add_subdirectory( html ) add_subdirectory( templates ) add_subdirectory( misc ) add_subdirectory( payeeidentifier ) add_subdirectory( icons ) if(BUILD_TESTING) add_subdirectory( tests ) endif() set( _HEADERS kmymoneyutils.h ) ########### common code (kmymoney_common) STATIC ############### # will be linked into kmymoney, kmymoneytest, and libkmymoney.so set( kmymoney_common_SRCS kmymoneyutils.cpp kstartuplogo.cpp kcreditswindow.cpp ) add_library(kmymoney_common STATIC ${kmymoney_common_SRCS}) target_link_libraries(kmymoney_common PUBLIC Qt5::Core KF5::ConfigGui KF5::WidgetsAddons Alkimia::alkimia kmm_widgets kmm_mymoney kmm_utils_webconnect kmm_utils_platformtools ) # must build kmymoney/transactionsortoption.h # from transactionsortoption.ui first add_dependencies(kmymoney_common generate_base_ui_srcs kmm_settings) add_dependencies(wizardpages widgets) add_dependencies(dialogs widgets) if(USE_MODELTEST) set( kmymoney_common_LIBS ${kmymoney_common_LIBS} ${QT_QTTEST_LIBRARY}) endif(USE_MODELTEST) # remove these generated files, they are in the way and leftovers from 5.0 if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.h) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.h) endif() if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.cpp) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/kmymoneysettings.cpp) endif() ########### kmymoney executable ############### set( kmymoney_SRCS main.cpp kmymoney.cpp pluginloader.cpp ) qt5_add_dbus_adaptor(kmymoney_SRCS org.kde.kmymoney.xml kmymoney.h KMyMoneyApp) qt5_add_resources(kmymoney_SRCS kmymoney.qrc) # collect application icons file(GLOB_RECURSE KMYMONEY_APP_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/icons/kmymoney/apps/*.png") # add icons to application sources, to have them bundled ecm_add_app_icon(kmymoney_SRCS ICONS ${KMYMONEY_APP_ICONS}) add_executable( kmymoney ${kmymoney_SRCS} ) target_link_libraries(kmymoney views reports kmymoney_base kmymoney_common newuserwizard newaccountwizard newinvestmentwizard newloanwizard endingbalancedlg wizardpages dialogs widgets settings converter models kmm_settings kmm_widgets - kmm_storage kmm_mymoney kgpgfile interfaces kmm_plugin Qt5::Core Qt5::Sql KF5::Archive KF5::ConfigGui KF5::WidgetsAddons KF5::KIOCore KF5::CoreAddons KChart $<$:Qt5::Test> $<$:KF5::Holidays> ) # own plist magic for mac os if(APPLE) # own plist template set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist.in) # the MacOSX bundle display name property (CFBundleDisplayName) is not currently supported by cmake, # so has to be set for all targets in this cmake file set(MACOSX_BUNDLE_DISPLAY_NAME KMyMoney) set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.KMyMoney") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KMyMoney") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_DISPLAY_NAME "KMyMoney") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_INFO_STRING "KMyMoney - Personal Finances Manager") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING "KMyMoney ${KDE_APPLICATIONS_VERSION}") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION "${KDE_APPLICATIONS_VERSION}") set_target_properties(kmymoney PROPERTIES MACOSX_BUNDLE_COPYRIGHT "2000-2016 The KMyMoney Authors") endif() ########### install files ############### install(TARGETS kmymoney ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney ) install(FILES org.kde.kmymoney.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install(FILES org.kde.kmymoney.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) install(FILES x-kmymoney.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) install(FILES tips DESTINATION ${CMAKE_INSTALL_DATADIR}/kmymoney) #UPDATE_XDG_MIMETYPES(${XDG_MIME_INSTALL_DIR}) diff --git a/kmymoney/icons/icons.cpp b/kmymoney/icons/icons.cpp index caaab3981..7fe7db172 100644 --- a/kmymoney/icons/icons.cpp +++ b/kmymoney/icons/icons.cpp @@ -1,404 +1,409 @@ /*************************************************************************** icons.cpp ------------------- begin : Sun Jun 25 2017 copyright : (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "icons.h" #include #include #include #include #include namespace Icons { QHash sStandardIcons; uint qHash(const Icon key, uint seed) { return ::qHash(static_cast(key), seed); } struct iconDescription { Icon baseIcon; Icon overlayIcon; Qt::Corner corner; }; const QHash sComposedIcons { {Icon::EditFindTransaction, {Icon::ViewFinancialTransfer, Icon::EditFind, Qt::BottomRightCorner}}, {Icon::InstitutionNew, {Icon::ViewBank, Icon::ListAdd, Qt::BottomRightCorner}}, {Icon::InstitutionEdit, {Icon::ViewBank, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::InstitutionDelete, {Icon::ViewBank, Icon::EditDelete, Qt::BottomRightCorner}}, {Icon::AccountNew, {Icon::ViewBankAccount, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::AccountFinishReconciliation, {Icon::Merge, Icon::DialogOK, Qt::BottomRightCorner}}, {Icon::AccountEdit, {Icon::ViewBankAccount, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::AccountDelete, {Icon::ViewBankAccount, Icon::EditDelete, Qt::BottomRightCorner}}, {Icon::AccountClose, {Icon::ViewBankAccount, Icon::DialogClose, Qt::BottomRightCorner}}, {Icon::AccountReopen, {Icon::ViewBankAccount, Icon::DialogOK, Qt::BottomRightCorner}}, {Icon::AccountUpdateMenu, {Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}}, {Icon::AccountUpdate, {Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}}, {Icon::AccountUpdateAll, {Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}}, {Icon::AccountCreditTransfer, {Icon::ViewBankAccount, Icon::MailMessageNew, Qt::BottomRightCorner}}, {Icon::CategoryNew, {Icon::ViewFinancialCategories, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::CategoryEdit, {Icon::ViewFinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::CategoryDelete, {Icon::ViewFinancialCategories, Icon::EditDelete, Qt::BottomRightCorner}}, {Icon::ToolUpdatePrices, {Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}}, {Icon::TransactionNew, {Icon::ViewFinancialTransfer, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::TransactionEdit, {Icon::ViewFinancialTransfer, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::TransactionMatch, {Icon::ViewFinancialTransfer, Icon::DocumentImport, Qt::BottomRightCorner}}, {Icon::TransactionAccept, {Icon::ViewFinancialTransfer, Icon::DialogOKApply, Qt::BottomRightCorner}}, {Icon::InvestmentNew, {Icon::ViewInvestment, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::InvestmentEdit, {Icon::ViewInvestment, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::InvestmentDelete, {Icon::ViewInvestment, Icon::EditDelete, Qt::BottomRightCorner}}, {Icon::InvestmentOnlinePrice, {Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}}, {Icon::BudgetNew, {Icon::ViewTimeScheduleCalculus, Icon::ListAdd, Qt::TopRightCorner}}, {Icon::BudgetRename, {Icon::ViewTimeScheduleCalculus, Icon::DocumentEdit, Qt::BottomRightCorner}}, {Icon::BudgetDelete, {Icon::ViewTimeScheduleCalculus, Icon::EditDelete, Qt::BottomRightCorner}}, {Icon::BudgetCopy, {Icon::ViewTimeScheduleCalculus, Icon::EditCopy, Qt::BottomRightCorner}}, {Icon::PriceUpdate, {Icon::ViewCurrencyList, Icon::Download, Qt::BottomRightCorner}} }; QHash getCommonNames(); QHash getKDENames(); QHash getOxygenNames(); QHash getBreezeNames(); QHash getTangoNames(); QHash getCommonNames() { return { {Icon::ListCollapse, QStringLiteral("zoom-out")}, {Icon::ListExpand, QStringLiteral("zoom-in")}, {Icon::ListAdd, QStringLiteral("list-add")}, {Icon::ListRemoveTag, QStringLiteral("list-remove-tag")}, {Icon::ListAddTag, QStringLiteral("list-add-tag")}, {Icon::ListRemoveUser, QStringLiteral("list-remove-user")}, {Icon::ListAddUser, QStringLiteral("list-add-user")}, {Icon::AppointmentNew, QStringLiteral("appointment-new")}, {Icon::KeyEnter, QStringLiteral("input-keyboard")}, {Icon::GoJump, QStringLiteral("go-jump")}, {Icon::EditUndo, QStringLiteral("edit-undo")}, {Icon::EditFind, QStringLiteral("edit-find")}, {Icon::EditRename, QStringLiteral("edit-rename")}, {Icon::EditCopy, QStringLiteral("edit-copy")}, {Icon::EditDelete, QStringLiteral("edit-delete")}, {Icon::EditClear, QStringLiteral("edit-clear")}, {Icon::DialogClose, QStringLiteral("dialog-close")}, {Icon::DialogCancel, QStringLiteral("dialog-cancel")}, {Icon::DialogOK, QStringLiteral("dialog-ok")}, {Icon::DialogOKApply, QStringLiteral("dialog-ok-apply")}, {Icon::DialogWarning, QStringLiteral("dialog-warning")}, {Icon::DialogError, QStringLiteral("dialog-error")}, {Icon::DialogInformation, QStringLiteral("dialog-information")}, {Icon::DocumentClose, QStringLiteral("document-close")}, {Icon::DocumentOpen, QStringLiteral("document-open")}, {Icon::DocumentSave, QStringLiteral("document-save")}, {Icon::DocumentImport, QStringLiteral("format-indent-less")}, {Icon::DocumentNew, QStringLiteral("document-new")}, {Icon::DocumentEdit, QStringLiteral("document-edit")}, {Icon::DocumentProperties, QStringLiteral("document-properties")}, {Icon::DocumentImport, QStringLiteral("format-indent-less")}, {Icon::DocumentExport, QStringLiteral("format-indent-more")}, {Icon::NewsUnsubscribe, QStringLiteral("news-unsubscribe")}, {Icon::NewsSubscribe, QStringLiteral("news-subscribe")}, {Icon::OfficeChartLine, QStringLiteral("account-types-investments")}, {Icon::MediaSkipForward, QStringLiteral("media-skip-forward")}, {Icon::MediaSeekForward, QStringLiteral("media-seek-forward")}, {Icon::MediaPlaybackPause, QStringLiteral("media-playback-pause")}, {Icon::Reconcile, QStringLiteral("reconcile")}, {Icon::Merge, QStringLiteral("reconcile")}, {Icon::Fork, QStringLiteral("fork")}, {Icon::ViewEquity, QStringLiteral("account")}, {Icon::ViewExpense, QStringLiteral("account-types-expense")}, {Icon::ViewIncome, QStringLiteral("account-types-income")}, {Icon::ViewCash, QStringLiteral("account-types-cash")}, {Icon::ViewCreditCard, QStringLiteral("account-types-credit-card")}, {Icon::ViewLoan, QStringLiteral("account-types-loan")}, {Icon::ViewLoanAsset, QStringLiteral("account-types-loan")}, {Icon::ViewSaving, QStringLiteral("account-types-savings")}, {Icon::ViewChecking, QStringLiteral("account-types-checking")}, {Icon::ViewStock, QStringLiteral("account-types-investments")}, {Icon::ViewLiability, QStringLiteral("account-types-liability")}, {Icon::ViewAsset, QStringLiteral("account-types-asset")}, {Icon::ViewOutbox, QStringLiteral("online-banking")}, {Icon::ViewForecast, QStringLiteral("forecast")}, {Icon::ViewBudgets, QStringLiteral("budget")}, {Icon::ViewReports, QStringLiteral("report")}, {Icon::ViewInvestment, QStringLiteral("investment")}, {Icon::ViewLedgers, QStringLiteral("ledger")}, {Icon::ViewPayees, QStringLiteral("payee")}, {Icon::ViewTags, QStringLiteral("bookmark-new")}, {Icon::ViewCategories, QStringLiteral("categories")}, {Icon::ViewSchedules, QStringLiteral("schedule")}, {Icon::ViewAccounts, QStringLiteral("account")}, {Icon::ViewInstitutions, QStringLiteral("institution")}, {Icon::ViewHome, QStringLiteral("home")}, {Icon::ViewClose, QStringLiteral("view-close")}, {Icon::ViewBank, QStringLiteral("bank")}, {Icon::ViewBankAccount, QStringLiteral("account")}, {Icon::ViewTimeScheduleCalculus, QStringLiteral("budget")}, {Icon::ViewCalendar, QStringLiteral("view-calendar")}, {Icon::ViewCurrencyList, QStringLiteral("view-currency-list")}, {Icon::ViewUpcominEvents, QStringLiteral("view-calendar-upcoming-events")}, {Icon::ViewCalendarDay, QStringLiteral("office-calendar")}, {Icon::ViewFinancialTransfer, QStringLiteral("ledger")}, {Icon::ViewFinancialCategories, QStringLiteral("categories")}, {Icon::ViewFinancialList, QStringLiteral("ledger")}, {Icon::ViewFilter, QStringLiteral("view-filter")}, {Icon::SortAscending, QStringLiteral("go-up")}, {Icon::SortDescending, QStringLiteral("go-down")}, {Icon::HideCategories, QStringLiteral("hide-categories")}, {Icon::HideReconciled, QStringLiteral("hide-reconciled")}, {Icon::ViewTransactionDetail, QStringLiteral("edit-find")}, {Icon::ZoomOut, QStringLiteral("zoom-out")}, {Icon::ZoomIn, QStringLiteral("zoom-in")}, {Icon::DocumentProperties, QStringLiteral("document-properties")}, {Icon::TagRename, QStringLiteral("tag-rename")}, {Icon::PayeeMerge, QStringLiteral("merge")}, {Icon::PayeeRename, QStringLiteral("user-properties")}, {Icon::UserProperties, QStringLiteral("system-users")}, {Icon::FileArchiver, QStringLiteral("package")}, {Icon::AccessoriesCalculator, QStringLiteral("accessories-calculator")}, {Icon::MailReceive, QStringLiteral("mail-receive")}, {Icon::MailMessageNew, QStringLiteral("mail-message-new")}, {Icon::MailMessage, QStringLiteral("internet-mail")}, {Icon::SVNUpdate, QStringLiteral("svn-update")}, {Icon::Split, QStringLiteral("transaction-split")}, {Icon::Download, QStringLiteral("go-down")}, {Icon::Tip, QStringLiteral("info")}, {Icon::KMyMoney, QStringLiteral("kmymoney")}, {Icon::FlagGreen, QStringLiteral("reconciled")}, {Icon::AccountClosed, QStringLiteral("account-types-closed")}, {Icon::Unknown, QStringLiteral("unknown")}, {Icon::Configure, QStringLiteral("configure")}, {Icon::Spreadsheet, QStringLiteral("application-vnd.oasis.opendocument.spreadsheet")}, {Icon::ArrowUp, QStringLiteral("arrow-up")}, {Icon::ArrowDown, QStringLiteral("arrow-down")}, {Icon::ArrowLeft, QStringLiteral("arrow-left")}, {Icon::ArrowRight, QStringLiteral("arrow-right")}, {Icon::SystemRun, QStringLiteral("system-run")}, {Icon::TaskAttention, QStringLiteral("task-attention")}, {Icon::TaskOngoing, QStringLiteral("task-ongoing")}, {Icon::TaskComplete, QStringLiteral("task-complete")}, {Icon::TaskReject, QStringLiteral("task-reject")}, {Icon::TaskAccepted, QStringLiteral("task-accepted")}, {Icon::HelpContents, QStringLiteral("help-contents")}, {Icon::Kgpg, QStringLiteral("kgpg")}, {Icon::PreferencesNetwork, QStringLiteral("preferences-system-network")}, {Icon::PreferencesColor, QStringLiteral("preferences-desktop-color")}, {Icon::PreferencesFont, QStringLiteral("preferences-desktop-font")}, {Icon::PreferencesIcon, QStringLiteral("preferences-desktop-icon")}, {Icon::NetworkDisconect, QStringLiteral("network-disconnect")}, {Icon::Folder, QStringLiteral("folder")} }; } QHash getKDENames() { return { {Icon::ViewHome, QStringLiteral("go-home")}, {Icon::KeyEnter, QStringLiteral("key-enter")}, {Icon::Split, QStringLiteral("split")}, {Icon::Reconcile, QStringLiteral("merge")}, {Icon::OfficeChartLine, QStringLiteral("office-chart-line")}, {Icon::Merge, QStringLiteral("merge")}, {Icon::ViewEquity, QStringLiteral("view-bank-account")}, {Icon::ViewSaving, QStringLiteral("view-bank-account-savings")}, {Icon::ViewChecking, QStringLiteral("view-bank-account-checking")}, {Icon::ViewAsset, QStringLiteral("view-bank-account")}, {Icon::ViewBank, QStringLiteral("view-bank")}, {Icon::ViewBankAccount, QStringLiteral("view-bank-account")}, {Icon::ViewTimeScheduleCalculus, QStringLiteral("view-time-schedule-calculus")}, {Icon::ViewBudgets, QStringLiteral("view-time-schedule-calculus")}, {Icon::ViewCalendarDay, QStringLiteral("view-calendar-day")}, {Icon::ViewTransactionDetail, QStringLiteral("zoom-in")}, {Icon::ViewReports, QStringLiteral("office-chart-bar")}, {Icon::ViewPayees, QStringLiteral("system-users")}, {Icon::ViewTags, QStringLiteral("mail-tagged")}, {Icon::ViewSchedules, QStringLiteral("view-pim-calendar")}, {Icon::ViewAccounts, QStringLiteral("view-bank-account")}, {Icon::ViewInstitutions, QStringLiteral("view-bank")}, {Icon::ViewCategories, QStringLiteral("view-categories")}, {Icon::ViewFinancialCategories, QStringLiteral("view-categories")}, {Icon::FileArchiver, QStringLiteral("utilities-file-archiver")}, {Icon::UserProperties, QStringLiteral("user-properties")}, {Icon::SortAscending, QStringLiteral("view-sort-ascending")}, {Icon::SortDescending, QStringLiteral("view-sort-descending")}, {Icon::FlagGreen, QStringLiteral("flag-green")}, {Icon::AccountClosed, QStringLiteral("dialog-close")}, {Icon::MailMessage, QStringLiteral("mail-message")}, {Icon::DocumentImport, QStringLiteral("document-import")}, {Icon::DocumentExport, QStringLiteral("document-export")} }; } QHash getOxygenNames() { return { {Icon::Download, QStringLiteral("download")}, {Icon::Tip, QStringLiteral("ktip")}, {Icon::ViewExpense, QStringLiteral("view-expenses-categories")}, {Icon::ViewIncome, QStringLiteral("view-income-categories")}, {Icon::ViewCreditCard, QStringLiteral("view-credit-card-account")}, {Icon::ViewLoan, QStringLiteral("view-loan")}, {Icon::ViewLoanAsset, QStringLiteral("view-loan-asset")}, {Icon::ViewStock, QStringLiteral("view-stock-account")}, {Icon::ViewLiability, QStringLiteral("view-loan")}, {Icon::ViewForecast, QStringLiteral("view-financial-forecast")}, {Icon::ViewInvestment, QStringLiteral("view-investment")}, {Icon::ViewLedgers, QStringLiteral("view-financial-list")}, {Icon::ViewCategories, QStringLiteral("view-financial-categories")}, {Icon::ViewFinancialCategories, QStringLiteral("view-financial-categories")}, {Icon::ViewFinancialTransfer, QStringLiteral("view-financial-transfer")}, {Icon::ViewFinancialList, QStringLiteral("view-financial-list")}, {Icon::Refresh, QStringLiteral("refresh")}, {Icon::InvestApplet, QStringLiteral("invest-applet")} }; } QHash getBreezeNames() { return { {Icon::ViewExpense, QStringLiteral("view-categories-expenditures")}, {Icon::ViewIncome, QStringLiteral("view-categories-incomes")}, {Icon::ViewCreditCard, QStringLiteral("skrooge_credit_card")}, {Icon::Download, QStringLiteral("edit-download")}, {Icon::Refresh, QStringLiteral("view-refresh")} }; } QHash getTangoNames() { return { {Icon::OfficeChartLine, QStringLiteral("report-line")}, {Icon::ListCollapse, QStringLiteral("list-remove")}, {Icon::ListExpand, QStringLiteral("list-add")}, {Icon::DocumentEdit, QStringLiteral("text-editor")}, {Icon::DialogCancel, QStringLiteral("stop")}, {Icon::DialogOK, QStringLiteral("finish")}, {Icon::EditRename, QStringLiteral("text-editor")}, {Icon::DocumentClose, QStringLiteral("stop")}, {Icon::Configure, QStringLiteral("preferences-system")}, {Icon::ArrowUp, QStringLiteral("go-up")}, {Icon::ArrowDown, QStringLiteral("go-down")}, {Icon::ArrowLeft, QStringLiteral("go-previous")}, {Icon::ArrowRight, QStringLiteral("go-next")}, {Icon::SystemRun, QStringLiteral("media-playback-start")}, {Icon::TaskAttention, QStringLiteral("dialog-warning")} }; } KMM_ICONS_EXPORT void setIconThemeNames(const QString &_themeName) { sStandardIcons = getCommonNames(); + auto isWindows = false; + +#ifdef Q_OS_WIN + isWindows = true; +#endif QStringList kdeThemes {QStringLiteral("oxygen"), QStringLiteral("breeze"), QStringLiteral("breeze-dark")}; QHash iconNames; - if (kdeThemes.contains(_themeName)) { + if (kdeThemes.contains(_themeName) || isWindows) { // on Craft build system there is breeze icon theme, but it's in no way discoverable iconNames = getKDENames(); for (auto it = iconNames.cbegin(); it != iconNames.cend(); ++it) sStandardIcons.insert(it.key(), it.value()); } // get icon replacements for specific theme if (_themeName == kdeThemes.at(0)) iconNames = getOxygenNames(); - else if (_themeName == kdeThemes.at(1) || _themeName == kdeThemes.at(2)) + else if (_themeName == kdeThemes.at(1) || _themeName == kdeThemes.at(2) || isWindows) iconNames = getBreezeNames(); else if (_themeName == QLatin1String("Tango")) iconNames = getTangoNames(); else return; for (auto it = iconNames.cbegin(); it != iconNames.cend(); ++it) sStandardIcons.insert(it.key(), it.value()); } /** * This method overlays an icon over another one, to get a composite one * eg. an icon to add accounts */ QIcon overlayIcon(iconDescription description, const int size = 64) { const auto iconName = sStandardIcons[description.baseIcon]; const auto overlayName = sStandardIcons[description.overlayIcon]; const auto corner = description.corner; QPixmap pxIcon; QString kyIcon = iconName + overlayName; // If found in the cache, return quickly if (QPixmapCache::find(kyIcon, pxIcon)) return pxIcon; // try to retrieve the main icon from cache if (!QPixmapCache::find(iconName, pxIcon)) { pxIcon = QIcon::fromTheme(iconName).pixmap(size); QPixmapCache::insert(iconName, pxIcon); } if (overlayName.isEmpty()) // call from MyMoneyAccount::accountPixmap can have no overlay icon, so handle that appropriately return pxIcon; QPainter pixmapPainter(&pxIcon); QPixmap pxOverlay = QIcon::fromTheme(overlayName).pixmap(size); int x, y; switch (corner) { case Qt::TopLeftCorner: x = 0; y = 0; break; case Qt::TopRightCorner: x = pxIcon.width() / 2; y = 0; break; case Qt::BottomLeftCorner: x = 0; y = pxIcon.height() / 2; break; case Qt::BottomRightCorner: default: x = pxIcon.width() / 2; y = pxIcon.height() / 2; break; } pixmapPainter.drawPixmap(x, y, pxIcon.width() / 2, pxIcon.height() / 2, pxOverlay); //save for later use QPixmapCache::insert(kyIcon, pxIcon); return pxIcon; } KMM_ICONS_EXPORT QIcon get(Icon icon) { #ifdef KMYMONEY_ICON_DIR static bool first_time = true; if (first_time) { first_time = false; QStringList paths = QIcon::themeSearchPaths(); paths.append(QStringLiteral(KMYMONEY_ICON_DIR)); QIcon::setThemeSearchPaths(paths); } #endif if (sComposedIcons.contains(icon)) return overlayIcon(sComposedIcons[icon]); return QIcon::fromTheme(sStandardIcons[icon]); } } diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 58e71a9bb..a88a15e61 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,3311 +1,3315 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoney.h" // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneysettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.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/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.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/kmymoneyaccountselector.h" #include "widgets/kmymoneypayeecombo.h" #include "widgets/onlinejobmessagesview.h" #include "widgets/kmymoneymvccombo.h" #include "views/kmymoneyview.h" #include "views/konlinejoboutbox.h" #include "models/onlinejobmessagesmodel.h" #include "mymoney/mymoneyobject.h" #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyinstitution.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneyaccountloan.h" #include "mymoney/mymoneysecurity.h" #include "mymoney/mymoneypayee.h" #include "mymoney/mymoneytag.h" #include "mymoney/mymoneybudget.h" #include "mymoney/mymoneyreport.h" #include "mymoney/mymoneysplit.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/mymoneytransactionfilter.h" #include "mymoney/onlinejobmessage.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmappinterface.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "tasks/credittransfer.h" #include "icons/icons.h" #include "misc/webconnect.h" #include "storage/mymoneystoragemgr.h" #include #include "transactioneditor.h" #include "konlinetransferform.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" #include "storageenums.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "menuenums.h" #include "misc/platformtools.h" #ifdef KMM_DEBUG #include "mymoney/storage/mymoneystoragedump.h" #include "mymoneytracer.h" #endif using namespace Icons; using namespace eMenu; 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 QHash pActions; QHash pMenus; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), m_ft(0), m_moveToAccountSelector(0), m_statementXMLindex(0), m_balanceWarning(0), m_collectingStatements(false), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(0), m_progressBar(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), m_webConnect(new WebConnect(app)) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void 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; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * @brief Structure of plugins objects by their interfaces */ KMyMoneyPlugin::Container m_plugins; /** * 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; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneySchedule m_selectedSchedule; 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; WebConnect* m_webConnect; // methods void consistencyCheck(bool alwaysDisplayResults); static void setThemedCSS(); 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->setThemedCSS(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::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); { + #ifdef Q_OS_WIN + QString themeName = QLatin1Literal("system") // using QIcon::setThemeName on Craft build system causes icons to disappear + #else QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants + #endif if (!themeName.isEmpty() && themeName != QLatin1Literal("system")) // if it isn't default theme then set it QIcon::setThemeName(themeName); Icons::setIconThemeNames(QIcon::themeName()); // get whatever theme user ends up with and hope our icon names will fit that theme } initStatusBar(); pActions = initActions(); pMenus = initMenus(); d->m_myMoneyView = new KMyMoneyView(this/*the global variable kmymoney is not yet assigned. So we pass it here*/); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, &KMyMoneyView::aboutToChangeView, this, &KMyMoneyApp::slotResetSelections); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg); connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, d->m_plugins, this, guiFactory()); onlineJobAdministration::instance()->setOnlinePlugins(d->m_plugins.extended); d->m_myMoneyView->setOnlinePlugins(d->m_plugins.online); d->m_myMoneyView->setStoragePlugins(d->m_plugins.storage); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(pActions[Action::ViewTransactionDetail], &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); d->m_backupState = BACKUP_IDLE; QLocale locale; int weekStart = locale.firstDayOfWeek(); int weekEnd = weekStart-1; if (weekEnd < Qt::Monday) { weekEnd = Qt::Sunday; } 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())); // connect the WebConnect server connect(d->m_webConnect, SIGNAL(gotUrl(QUrl)), this, SLOT(webConnect(QUrl))); // make sure we have a balance warning object d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(QString()); // 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 KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, d->m_plugins, this, guiFactory()); delete d->m_searchDlg; 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(); } } } QHash KMyMoneyApp::initMenus() { QHash lutMenus; const QHash menuNames { {Menu::Institution, QStringLiteral("institution_context_menu")}, {Menu::Account, QStringLiteral("account_context_menu")}, {Menu::Schedule, QStringLiteral("schedule_context_menu")}, {Menu::Category, QStringLiteral("category_context_menu")}, {Menu::Tag, QStringLiteral("tag_context_menu")}, {Menu::Payee, QStringLiteral("payee_context_menu")}, {Menu::Investment, QStringLiteral("investment_context_menu")}, {Menu::Transaction, QStringLiteral("transaction_context_menu")}, {Menu::MoveTransaction, QStringLiteral("transaction_move_menu")}, {Menu::MarkTransaction, QStringLiteral("transaction_mark_menu")}, {Menu::MarkTransactionContext, QStringLiteral("transaction_context_mark_menu")}, {Menu::OnlineJob, QStringLiteral("onlinejob_context_menu")} }; for (auto it = menuNames.cbegin(); it != menuNames.cend(); ++it) lutMenus.insert(it.key(), qobject_cast(factory()->container(it.value(), this))); return lutMenus; } QHash KMyMoneyApp::initActions() { auto aC = actionCollection(); // ************* // 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. keyboard shortcut */ QHash lutActions; // ************* // Adding all actions // ************* { // struct for creating useless (unconnected) QAction struct actionInfo { Action action; QString name; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileBackup, QStringLiteral("file_backup"), i18n("Backup..."), Icon::Empty}, {Action::FileImportStatement, QStringLiteral("file_import_statement"), i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, QStringLiteral("file_import_template"), i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, QStringLiteral("file_export_template"), i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, QStringLiteral("view_personal_data"), i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump"), i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, QStringLiteral("view_file_info"), i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, QStringLiteral("edit_find_transaction"), i18n("Find transaction..."), Icon::EditFindTransaction}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail"), i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories"), i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts"), i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::NewInstitution, QStringLiteral("institution_new"), i18n("New institution..."), Icon::InstitutionNew}, {Action::EditInstitution, QStringLiteral("institution_edit"), i18n("Edit institution..."), Icon::InstitutionEdit}, {Action::DeleteInstitution, QStringLiteral("institution_delete"), i18n("Delete institution..."), Icon::InstitutionDelete}, // ***************** // The accounts menu // ***************** {Action::NewAccount, QStringLiteral("account_new"), i18n("New account..."), Icon::AccountNew}, {Action::OpenAccount, QStringLiteral("account_open"), i18n("Open ledger"), Icon::ViewFinancialList}, {Action::StartReconciliation, QStringLiteral("account_reconcile"), i18n("Reconcile..."), Icon::Reconcile}, {Action::FinishReconciliation, QStringLiteral("account_reconcile_finish"), i18nc("Finish reconciliation", "Finish"), Icon::AccountFinishReconciliation}, {Action::PostponeReconciliation, QStringLiteral("account_reconcile_postpone"), i18n("Postpone reconciliation"), Icon::MediaPlaybackPause}, {Action::EditAccount, QStringLiteral("account_edit"), i18n("Edit account..."), Icon::AccountEdit}, {Action::DeleteAccount, QStringLiteral("account_delete"), i18n("Delete account..."), Icon::AccountDelete}, {Action::CloseAccount, QStringLiteral("account_close"), i18n("Close account"), Icon::AccountClose}, {Action::ReopenAccount, QStringLiteral("account_reopen"), i18n("Reopen account"), Icon::AccountReopen}, {Action::ReportAccountTransactions, QStringLiteral("account_transaction_report"), i18n("Transaction report"), Icon::ViewFinancialList}, {Action::ChartAccountBalance, QStringLiteral("account_chart"), i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::MapOnlineAccount, QStringLiteral("account_online_map"), i18n("Map account..."), Icon::NewsSubscribe}, {Action::UnmapOnlineAccount, QStringLiteral("account_online_unmap"), i18n("Unmap account..."), Icon::NewsUnsubscribe}, {Action::UpdateAccount, QStringLiteral("account_online_update"), i18n("Update account..."), Icon::AccountUpdate}, {Action::UpdateAllAccounts, QStringLiteral("account_online_update_all"), i18n("Update all accounts..."), Icon::AccountUpdateAll}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"), Icon::AccountCreditTransfer}, // ******************* // The categories menu // ******************* {Action::NewCategory, QStringLiteral("category_new"), i18n("New category..."), Icon::CategoryNew}, {Action::EditCategory, QStringLiteral("category_edit"), i18n("Edit category..."), Icon::CategoryEdit}, {Action::DeleteCategory, QStringLiteral("category_delete"), i18n("Delete category..."), Icon::CategoryDelete}, // ************** // The tools menu // ************** {Action::ToolCurrencies, QStringLiteral("tools_currency_editor"), i18n("Currencies..."), Icon::ViewCurrencyList}, {Action::ToolPrices, QStringLiteral("tools_price_editor"), i18n("Prices..."), Icon::Empty}, {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices"), i18n("Update Stock and Currency Prices..."), Icon::ToolUpdatePrices}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check"), i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, QStringLiteral("tools_performancetest"), i18n("Performance-Test"), Icon::Fork}, {Action::ToolCalculator, QStringLiteral("tools_kcalc"), i18n("Calculator..."), Icon::AccessoriesCalculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages"), i18n("Enable all messages"), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, QStringLiteral("help_show_tip"), i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::NewTransaction, QStringLiteral("transaction_new"), i18nc("New transaction button", "New"), Icon::TransactionNew}, {Action::EditTransaction, QStringLiteral("transaction_edit"), i18nc("Edit transaction button", "Edit"), Icon::TransactionEdit}, {Action::EnterTransaction, QStringLiteral("transaction_enter"), i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::EditSplits, QStringLiteral("transaction_editsplits"), i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::CancelTransaction, QStringLiteral("transaction_cancel"), i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::DeleteTransaction, QStringLiteral("transaction_delete"), i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::DuplicateTransaction, QStringLiteral("transaction_duplicate"), i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::MatchTransaction, QStringLiteral("transaction_match"), i18nc("Button text for match transaction", "Match"),Icon::TransactionMatch}, {Action::AcceptTransaction, QStringLiteral("transaction_accept"), i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::TransactionAccept}, {Action::ToggleReconciliationFlag, QStringLiteral("transaction_mark_toggle"), i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::MarkCleared, QStringLiteral("transaction_mark_cleared"), i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::MarkReconciled, QStringLiteral("transaction_mark_reconciled"), i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::MarkNotReconciled, QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, {Action::SelectAllTransactions, QStringLiteral("transaction_select_all"), i18nc("Select all transactions", "Select all"), Icon::Empty}, {Action::GoToAccount, QStringLiteral("transaction_goto_account"), i18n("Go to account"), Icon::GoJump}, {Action::GoToPayee, QStringLiteral("transaction_goto_payee"), i18n("Go to payee"), Icon::GoJump}, {Action::NewScheduledTransaction, QStringLiteral("transaction_create_schedule"), i18n("Create scheduled transaction..."), Icon::AppointmentNew}, {Action::AssignTransactionsNumber, QStringLiteral("transaction_assign_number"), i18n("Assign next number"), Icon::Empty}, {Action::CombineTransactions, QStringLiteral("transaction_combine"), i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::CopySplits, QStringLiteral("transaction_copy_splits"), i18n("Copy splits"), Icon::Empty}, //Investment {Action::NewInvestment, QStringLiteral("investment_new"), i18n("New investment..."), Icon::InvestmentNew}, {Action::EditInvestment, QStringLiteral("investment_edit"), i18n("Edit investment..."), Icon::InvestmentEdit}, {Action::DeleteInvestment, QStringLiteral("investment_delete"), i18n("Delete investment..."), Icon::InvestmentDelete}, {Action::UpdatePriceOnline, QStringLiteral("investment_online_price_update"), i18n("Online price update..."), Icon::InvestmentOnlinePrice}, {Action::UpdatePriceManually, QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::NewSchedule, QStringLiteral("schedule_new"), i18n("New scheduled transaction"), Icon::AppointmentNew}, {Action::EditSchedule, QStringLiteral("schedule_edit"), i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::DeleteSchedule, QStringLiteral("schedule_delete"), i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::DuplicateSchedule, QStringLiteral("schedule_duplicate"), i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::EnterSchedule, QStringLiteral("schedule_enter"), i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::SkipSchedule, QStringLiteral("schedule_skip"), i18n("Skip next transaction..."), Icon::MediaSeekForward}, //Payees {Action::NewPayee, QStringLiteral("payee_new"), i18n("New payee"), Icon::ListAddUser}, {Action::RenamePayee, QStringLiteral("payee_rename"), i18n("Rename payee"), Icon::PayeeRename}, {Action::DeletePayee, QStringLiteral("payee_delete"), i18n("Delete payee"), Icon::ListRemoveUser}, {Action::MergePayee, QStringLiteral("payee_merge"), i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::NewTag, QStringLiteral("tag_new"), i18n("New tag"), Icon::ListAddTag}, {Action::RenameTag, QStringLiteral("tag_rename"), i18n("Rename tag"), Icon::TagRename}, {Action::DeleteTag, QStringLiteral("tag_delete"), i18n("Delete tag"), Icon::ListRemoveTag}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard"), i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, QStringLiteral("debug_traces"), i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, QStringLiteral("debug_timers"), i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::DeleteOnlineJob, QStringLiteral("onlinejob_delete"), i18n("Remove credit transfer"), Icon::EditDelete}, {Action::EditOnlineJob, QStringLiteral("onlinejob_edit"), i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::LogOnlineJob, QStringLiteral("onlinejob_log"), i18n("Show log"), Icon::Empty}, }; for (const auto& info : actionInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(info.name); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(Icons::get(info.icon)); a->setEnabled(false); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } { // List with slots that get connected here. Other slots get connected in e.g. appropriate views typedef void(KMyMoneyApp::*KMyMoneyAppFunc)(); const QHash actionConnections { // ************* // The File menu // ************* // {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase}, // {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, &KMyMoneyApp::slotFindTransaction}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts}, // ***************** // The accounts menu // ***************** {Action::MapOnlineAccount, &KMyMoneyApp::slotAccountMapOnline}, {Action::UnmapOnlineAccount, &KMyMoneyApp::slotAccountUnmapOnline}, {Action::UpdateAccount, &KMyMoneyApp::slotAccountUpdateOnline}, {Action::UpdateAllAccounts, &KMyMoneyApp::slotAccountUpdateOnlineAll}, // ************** // The tools menu // ************** {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest}, // {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay}, // *************************** // Actions w/o main menu entry // *************************** //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers}, }; for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection) connect(lutActions[connection.key()], &QAction::triggered, this, connection.value()); } // ************* // Setting some of added actions checkable // ************* { // Some 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 }; for (const auto& it : checkableActions) { lutActions[it]->setCheckable(true); lutActions[it]->setEnabled(true); } } // ************* // Setting keyboard shortcuts for some of added actions // ************* { const QVector> actionShortcuts { {qMakePair(Action::EditFindTransaction, Qt::CTRL + Qt::Key_F)}, {qMakePair(Action::ViewTransactionDetail, Qt::CTRL + Qt::Key_T)}, {qMakePair(Action::ViewHideReconciled, Qt::CTRL + Qt::Key_R)}, {qMakePair(Action::ViewHideCategories, Qt::CTRL + Qt::Key_U)}, {qMakePair(Action::ViewShowAll, Qt::CTRL + Qt::SHIFT + Qt::Key_A)}, {qMakePair(Action::StartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::NewTransaction, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::ToggleReconciliationFlag, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::MarkCleared, Qt::CTRL + Qt::ALT+ Qt::Key_Space)}, {qMakePair(Action::MarkReconciled, Qt::CTRL + Qt::SHIFT + Qt::Key_Space)}, {qMakePair(Action::SelectAllTransactions, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::AssignTransactionsNumber, Qt::CTRL + Qt::SHIFT + Qt::Key_N)} }; for(const auto& it : actionShortcuts) aC->setDefaultShortcut(lutActions[it.first], it.second); } // ************* // Misc settings // ************* connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); // Setup transaction detail switch lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory()); lutActions[Action::ViewShowAll]->setChecked(false); // ************* // Adding actions to ActionCollection // ************* actionCollection()->addActions(lutActions.values()); // ************************ // Currently unused actions // ************************ #if 0 new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back"); new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward"); action("go_back")->setEnabled(false); action("go_forward")->setEnabled(false); #endif // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); // reconnect about app entry to dialog with full credits information auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp))); aboutApp->disconnect(); connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits); QMenu *menuContainer; menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentImport)); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentExport)); return lutActions; } #ifdef KMM_DEBUG void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); foreach (const auto it, list) std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl; } #endif bool KMyMoneyApp::isActionToggled(const Action _a) { return pActions[_a]->isChecked(); } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel; statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar; statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked()); KConfigGroup toolbarGrp = d->m_config->group("mainToolBar"); toolBar("mainToolBar")->saveSettings(toolbarGrp); d->m_recentFiles->saveEntries(d->m_config->group("Recent Files")); } void KMyMoneyApp::readOptions() { KConfigGroup grp = d->m_config->group("General Options"); pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions()); pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); updateCaption(true); } int KMyMoneyApp::askSaveOnClose() { int ans; if (KMyMoneySettings::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 auto acc = wizard->account(); if (acc.name().length()) { acc.setInstitutionId(inst.id()); MyMoneyAccount asset = file->asset(); file->addAccount(acc, asset); // create possible opening balance transaction if (!wizard->openingBalance().isZero()) { file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); } } // import the account templates 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(); KMyMoneySettings::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); } } // 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_plugins.importer.constBegin(); while (it_plugin != d->m_plugins.importer.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_plugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } bool KMyMoneyApp::isFileOpenedInAnotherInstance(const QUrl &url) { const auto instances = instanceList(); #ifdef KMM_DBUS // check if there are other instances which might have this file open for (const auto& instance : instances) { QDBusInterface remoteApp(instance, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) qDebug("D-Bus error while calling app->filename()"); else if (reply.value() == url.url()) return true; } #endif return false; } void KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { KMSTATUS(i18n("Loading file...")); if (!isFileOpenedInAnotherInstance(url)) { QUrl newurl = url; if (newurl.scheme() == QLatin1String("sql") || KMyMoneyUtils::fileExists(newurl)) { 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); setEnabled(false); if (d->m_myMoneyView->isDatabase()) { auto pluginFound = false; for (const auto& plugin : d->m_plugins.storage) { if (plugin->formatName().compare(QLatin1String("SQL")) == 0) { rc = plugin->save(d->m_fileName); pluginFound = true; break; } } if(!pluginFound) KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); } else { rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); } setEnabled(true); d->m_autoSaveTimer->stop(); updateCaption(); return rc; } bool KMyMoneyApp::slotFileSaveAs() { bool rc = false; KMSTATUS(i18n("Saving file with a new filename...")); QString selectedKeyName; if (KGPGFile::GPGAvailable() && KMyMoneySettings::writeDataEncrypted()) { // fill the secret key list and combo box QStringList keyList; KGPGFile::secretKeyList(keyList); QPointer dlg = new KGpgKeySelectionDlg(this); dlg->setSecretKeys(keyList, KMyMoneySettings::gpgRecipient()); dlg->setAdditionalKeys(KMyMoneySettings::gpgRecipientList()); const int rc = dlg->exec(); if ((rc == QDialog::Accepted) && (dlg != 0)) { d->m_additionalGpgKeys = dlg->additionalKeys(); selectedKeyName = dlg->secretKey(); } delete dlg; if ((rc != QDialog::Accepted) || (dlg == 0)) { return false; } } 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); if (dlg->exec() == QDialog::Accepted && dlg != 0) { QUrl newURL = dlg->selectedUrls().first(); if (!newURL.fileName().isEmpty()) { d->consistencyCheck(false); 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()) // 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; return false; } 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() { KMyMoneySettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneySettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { KMyMoneySettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG void KMyMoneyApp::slotFileFileInfo() { if (!d->m_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, MyMoneyFile::instance()->storage()); g.close(); } void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = pActions[Action::DebugTimers]->isChecked(); } QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = d->m_statusLabel->text(); d->m_applicationIsReady = false; QString currentMessage = text; if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) { d->m_applicationIsReady = true; currentMessage = i18nc("Application is ready to use", "Ready."); } statusBar()->clearMessage(); d->m_statusLabel->setText(currentMessage); return previousMessage; } void KMyMoneyApp::ready() { slotStatusMsg(QString()); } bool KMyMoneyApp::isReady() { return d->m_applicationIsReady; } void KMyMoneyApp::slotStatusProgressBar(int current, int total) { if (total == -1 && current == -1) { // reset if (d->m_progressTimer) { d->m_progressTimer->start(500); // remove from screen in 500 msec d->m_progressBar->setValue(d->m_progressBar->maximum()); } } else if (total != 0) { // init d->m_progressTimer->stop(); d->m_progressBar->setMaximum(total); d->m_progressBar->setValue(0); d->m_progressBar->show(); } else { // update QTime currentTime = QTime::currentTime(); // only process painting if last update is at least 250 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 250) { d->m_progressBar->setValue(current); d->m_lastUpdate = currentTime; } } } void KMyMoneyApp::slotStatusProgressDone() { d->m_progressTimer->stop(); d->m_progressBar->reset(); d->m_progressBar->hide(); d->m_progressBar->setValue(0); } void KMyMoneyApp::progressCallback(int current, int total, const QString& msg) { if (!msg.isEmpty()) kmymoney->slotStatusMsg(msg); kmymoney->slotStatusProgressBar(current, total); } void KMyMoneyApp::slotFileViewPersonal() { if (!d->m_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->userName()); user.setAddress(editPersonalDataDlg->userStreet()); user.setCity(editPersonalDataDlg->userTown()); user.setState(editPersonalDataDlg->userCountry()); user.setPostcode(editPersonalDataDlg->userPostcode()); user.setTelephone(editPersonalDataDlg->userTelephone()); user.setEmail(editPersonalDataDlg->userEmail()); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", e.what())); } } delete editPersonalDataDlg; } 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; } } } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; if (KMyMoneyUtils::fileExists(url)) { if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) reallySaveFile = false; } return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it auto dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneySettings::self()); connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName) { if(dialogName.compare(QLatin1String("Plugins")) == 0) { KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, d->m_plugins, this, guiFactory()); onlineJobAdministration::instance()->updateActions(); return; } MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); #ifdef ENABLE_UNFINISHEDFEATURES LedgerSeparator::setFirstFiscalDate(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); #endif d->m_myMoneyView->updateViewType(); // update the sql storage module settings // MyMoneyStorageSql::setStartDate(KMyMoneySettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneySettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneySettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneySettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneySettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setThemedCSS(); // 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; } QPointer backupDlg = new KBackupDlg(this); int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBoxChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->mountPoint(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); #ifdef Q_OS_WIN d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); #else // If we don't have to mount a device, we just issue // a dummy command to start the copy operation d->m_proc.setProgram("true"); d->m_proc.start(); #endif } } delete backupDlg; } void KMyMoneyApp::slotBackupMount() { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } bool KMyMoneyApp::slotBackupWriteFile() { QFileInfo fi(d->m_fileName.fileName()); QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix(); 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() { switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_ignoreBackupExitCode || (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) { d->m_ignoreBackupExitCode = false; d->m_backupResult = 0; if (!slotBackupWriteFile()) { d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); slotBackupFinish(); } } else { qDebug("copy exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } slotBackupFinish(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { // QPointer editor = new KGenerateSqlDlg(this); // editor->setObjectName("Generate Database SQL"); // editor->exec(); // delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneySettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::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::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", e.what())); } } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewInvestmentWizard::newInvestment(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewAccountDlg::newCategory(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account) { KNewAccountDlg::newCategory(account, MyMoneyAccount()); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard::newAccount(account); } void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { MyMoneyFile* file = MyMoneyFile::instance(); // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } MyMoneyFileTransaction ft; try { file->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.accountType() == eMyMoney::Account::Type::Loan || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) { newAccount.setValue("schedule", newSchedule.id()); file->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } } QList > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount) { static const int NR_OF_STEPS_LIMIT = 300000; static const int PROGRESSBAR_STEPS = 1000; QList > result = transactions; KMSTATUS(i18n("Running automatic reconciliation")); int progressBarIndex = 0; 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::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::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { KEditScheduleDlg::newSchedule(_t, occurrence); } void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { KMyMoneyUtils::newPayee(newnameBase, id); } void KMyMoneyApp::slotNewFeature() { } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; foreach (const auto split, t.splits()) { stockAccountId = split.accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = split; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; foreach (const auto sAccount, toInvAcc.accountList()) { if (MyMoneyFile::instance()->account(sAccount).currencyId() == stockSecurityId) { newStockAccountId = sAccount; break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); 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::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() { const auto file = MyMoneyFile::instance(); const bool fileOpen = d->m_myMoneyView->fileOpen(); const bool modified = file->dirty(); // const bool importRunning = (d->m_smtReader != 0); auto aC = actionCollection(); // ************* // Disabling actions to be disabled at this point // ************* { static const QVector disabledActions { Action::UpdateAllAccounts }; for (const auto& a : disabledActions) pActions[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::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::NewAccount, fileOpen)}, {qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())}, {qMakePair(Action::NewInstitution, fileOpen)}, // {qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))}, {qMakePair(Action::NewSchedule, fileOpen)}, // {qMakePair(Action::CurrencyNew, fileOpen)}, // {qMakePair(Action::PriceNew, fileOpen)}, }; for (const auto& a : actionStates) pActions[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()); // ************* // Enabling actions based on conditions // ************* QList accList; file->accountList(accList); QList::const_iterator it_a; QMap::const_iterator it_p = d->m_plugins.online.constEnd(); for (it_a = accList.constBegin(); (it_p == d->m_plugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if ((*it_a).hasOnlineMapping()) { // check if provider is available it_p = d->m_plugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); if (it_p != d->m_plugins.online.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { pActions[Action::UpdateAllAccounts]->setEnabled(true); } } } } } void KMyMoneyApp::slotResetSelections() { slotSelectAccount(MyMoneyAccount()); d->m_myMoneyView->slotObjectSelected(MyMoneyAccount()); d->m_myMoneyView->slotObjectSelected(MyMoneyInstitution()); d->m_myMoneyView->slotObjectSelected(MyMoneySchedule()); d->m_myMoneyView->slotObjectSelected(MyMoneyTag()); d->m_myMoneyView->slotTransactionsSelected(KMyMoneyRegister::SelectedTransactions()); slotUpdateActions(); } 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::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); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); dlg->exec(); delete dlg; } 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::setThemedCSS() { const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")}; const QString rcDir("/html/"); const QStringList defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory); // scan the list of directories to find the ones that really // contains all files we look for QString defaultCSSDir; foreach (const auto dir, defaultCSSDirs) { defaultCSSDir = dir; foreach (const auto CSSname, CSSnames) { QFileInfo fileInfo(defaultCSSDir + CSSname); if (!fileInfo.exists()) { defaultCSSDir.clear(); break; } } if (!defaultCSSDir.isEmpty()) { break; } } // make sure we have the local directory where the themed version is stored const QString themedCSSDir = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + rcDir; QDir().mkpath(themedCSSDir); foreach (const auto CSSname, CSSnames) { const QString defaultCSSFilename = defaultCSSDir + CSSname; QFileInfo fileInfo(defaultCSSFilename); if (fileInfo.exists()) { const QString themedCSSFilename = themedCSSDir + CSSname; QFile::remove(themedCSSFilename); if (QFile::copy(defaultCSSFilename, themedCSSFilename)) { QFile cssFile (themedCSSFilename); if (cssFile.open(QIODevice::ReadWrite)) { QTextStream cssStream(&cssFile); auto cssText = cssStream.readAll(); cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive); cssText.replace(QLatin1String("WindowText"), KMyMoneySettings::schemeColor(SchemeColor::WindowText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Window"), KMyMoneySettings::schemeColor(SchemeColor::WindowBackground).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("HighlightText"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Highlight"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlight).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("black"), KMyMoneySettings::schemeColor(SchemeColor::ListGrid).name(), Qt::CaseSensitive); cssStream.seek(0); cssStream << cssText; cssFile.close(); } } } } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneySettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneySettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != eDialogs::ScheduleResultCode::Ignore && rc != eDialogs::ScheduleResultCode::Cancel) { rc = d->m_myMoneyView->enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == eDialogs::ScheduleResultCode::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = eDialogs::ScheduleResultCode::Enter; } } 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(); } QUrl KMyMoneyApp::filenameURL() const { return d->m_fileName; } void KMyMoneyApp::addToRecentFiles(const QUrl& url) { d->m_recentFiles->addUrl(url); } QTimer* KMyMoneyApp::autosaveTimer() { return d->m_autoSaveTimer; } WebConnect* KMyMoneyApp::webConnect() const { return d->m_webConnect; } QList KMyMoneyApp::instanceList() const { QList list; #ifdef KMM_DBUS QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (reply.isValid()) { QStringList apps = reply.value(); QStringList::ConstIterator it; // build a list of service names of all running kmymoney applications without this one for (it = apps.constBegin(); it != apps.constEnd(); ++it) { // please change this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somwhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { uint thisProcPid = platformTools::processId(); if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::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 (WebConnect) //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_plugins.importer.constBegin(); while (it_plugin != d->m_plugins.importer.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_plugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url)) MyMoneyStatementReader::importStatement(url, false, &progressCallback); } // 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().appInterface = new KMyMoneyPlugin::KMMAppInterface(this, this); KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->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(); } void KMyMoneyApp::slotOnlineAccountRequested(const MyMoneyAccount& acc, eMenu::Action action) { d->m_selectedAccount = acc; switch (action) { case eMenu::Action::UnmapOnlineAccount: slotAccountUnmapOnline(); break; case eMenu::Action::MapOnlineAccount: slotAccountMapOnline(); break; case eMenu::Action::UpdateAccount: slotAccountUpdateOnline(); break; case eMenu::Action::UpdateAllAccounts: slotAccountUpdateOnlineAll(); break; default: break; } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (!d->m_selectedAccount.hasOnlineMapping()) 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.hasOnlineMapping()) 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_plugins.online.count()) { case 0: break; case 1: provider = d->m_plugins.online.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_plugins.online.constBegin(); it_p != d->m_plugins.online.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_plugins.online.constFind(provider.toLower()); if (it_p != d->m_plugins.online.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider.toLower(); 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).hasOnlineMapping() || d->m_plugins.online.find((*it_a).onlineBankingSettings().value("provider").toLower()) == d->m_plugins.online.end()) { it_a = accList.erase(it_a); } else ++it_a; } const QVector disabledActions {Action::UpdateAccount, Action::UpdateAllAccounts}; for (const auto& a : disabledActions) pActions[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_plugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); (*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.hasOnlineMapping()) return; const QVector disabledActions {Action::UpdateAccount, Action::UpdateAllAccounts}; for (const auto& a : disabledActions) pActions[a]->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_plugins.online.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider").toLower()); if (it_p != d->m_plugins.online.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::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 = KMyMoneySettings::forecastDays() + KMyMoneySettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); //look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1)) d->m_holidayMap.insert(holidayDate, false); } for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) { //if it is not a processing day, set it to false if (!d->m_processingDays.testBit(date.dayOfWeek())) { d->m_holidayMap.insert(date, false); } else if (!d->m_holidayMap.contains(date)) { //if it is not a holiday nor a weekend, it is a processing day d->m_holidayMap.insert(date, true); } } } #endif } 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(MyMoneyAccount()); q->d->m_myMoneyView->slotObjectSelected(MyMoneyAccount()); q->d->m_myMoneyView->slotObjectSelected(MyMoneyInstitution()); q->d->m_myMoneyView->slotObjectSelected(MyMoneySchedule()); q->d->m_myMoneyView->slotObjectSelected(MyMoneyTag()); q->d->m_myMoneyView->slotTransactionsSelected(KMyMoneyRegister::SelectedTransactions()); // q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(MyMoneyAccount()); 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/misc/CMakeLists.txt b/kmymoney/misc/CMakeLists.txt index 3d3cd2dc8..3f09c7d63 100644 --- a/kmymoney/misc/CMakeLists.txt +++ b/kmymoney/misc/CMakeLists.txt @@ -1,57 +1,57 @@ install(PROGRAMS financequote.pl DESTINATION ${DATA_INSTALL_DIR}/kmymoney/misc) set( kmm_utils_validators_SRCS charvalidator.cpp validators.cpp ) set( kmm_utils_webconnect_SRCS webconnect.cpp ) -if( ${HAVE_UNISTD_H} AND ${HAVE_PWD_H} ) +if( "${HAVE_UNISTD_H}" AND "${HAVE_PWD_H}" ) set( kmm_utils_platformtools_SRCS platformtools_gnu.cpp ) -elseif( ${HAVE_WINDOWS_H} AND ${HAVE_LMCONS_H} AND ${HAVE_PROCESS_H} ) +elseif( "${HAVE_WINDOWS_H}" AND "${HAVE_LMCONS_H}" AND "${HAVE_PROCESS_H}" ) set( kmm_utils_platformtools_SRCS platformtools_nognu.cpp ) else() message(FATAL_ERROR "make sure either windows.h and lmcons.h (on msvc platforms) or unistd.h and pwd.h (on other platforms) are present.") endif() set( kmm_utils_validators_HEADER charvalidator.h validators.h ) set( kmm_utils_webconnect_HEADER webconnect.h ) set( kmm_utils_platformtools_HEADER platformtools.h ) add_library(kmm_utils_validators STATIC ${kmm_utils_validators_SRCS}) add_library(kmm_utils_webconnect STATIC ${kmm_utils_webconnect_SRCS}) add_library(kmm_utils_platformtools STATIC ${kmm_utils_platformtools_SRCS}) target_link_libraries( kmm_utils_validators PUBLIC Qt5::Core Qt5::Gui ) target_link_libraries( kmm_utils_webconnect PUBLIC Qt5::Core Qt5::Network ) target_link_libraries( kmm_utils_platformtools PUBLIC Qt5::Core ) diff --git a/kmymoney/mymoney/CMakeLists.txt b/kmymoney/mymoney/CMakeLists.txt index 0ba9987fc..f07b39af2 100644 --- a/kmymoney/mymoney/CMakeLists.txt +++ b/kmymoney/mymoney/CMakeLists.txt @@ -1,92 +1,112 @@ add_subdirectory( storage ) add_subdirectory( payeeidentifier ) ########### next target ############### set(kmm_mymoney_LIB_SRCS mymoneymoney.cpp mymoneyfinancialcalculator.cpp mymoneytransactionfilter.cpp mymoneyfile.cpp mymoneykeyvaluecontainer.cpp mymoneyobject.cpp mymoneypayeeidentifiercontainer.cpp mymoneysplit.cpp mymoneyinstitution.cpp mymoneyexception.cpp mymoneyinvesttransaction.cpp mymoneyutils.cpp mymoneysecurity.cpp mymoneytransaction.cpp mymoneyschedule.cpp mymoneypayee.cpp mymoneytracer.cpp mymoneytag.cpp mymoneycategory.cpp mymoneycostcenter.cpp mymoneyaccount.cpp mymoneyaccountloan.cpp mymoneyreport.cpp mymoneystatement.cpp mymoneyprice.cpp mymoneybudget.cpp mymoneyforecast.cpp mymoneybalancecache.cpp onlinejob.cpp onlinejobadministration.cpp onlinejobmessage.cpp onlinejobfolder.cpp mymoneycontact.cpp payeeidentifiermodel.cpp ) + # storage_a_SOURCES cannot be set in storage directory on MS Windows + # because, while building kmm_storage, linker reports many undefined symbols + # which are in fact available in kmm_mymoney + set(storage_a_SOURCES + ./storage/imymoneystorageformat.cpp + ./storage/mymoneystoragexml.cpp + ./storage/mymoneystoragemgr.cpp + ./storage/mymoneystorageanon.cpp + ./storage/kmymoneystorageplugin.cpp + ./storage/mymoneystoragenames.cpp + ) + + if(KMM_DEBUG) + list(APPEND storage_a_SOURCES ./storage/mymoneystoragedump.cpp) + endif() + +list(APPEND kmm_mymoney_LIB_SRCS ${storage_a_SOURCES}) set(mymoney_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/kmm_mymoney_export.h mymoneyobject.h mymoneyaccount.h mymoneycategory.h mymoneyexception.h mymoneyfile.h mymoneyfinancialcalculator.h mymoneyinstitution.h mymoneyinvesttransaction.h mymoneykeyvaluecontainer.h mymoneymoney.h mymoneypayee.h mymoneytag.h mymoneyprice.h mymoneyreport.h mymoneyschedule.h mymoneysecurity.h mymoneysplit.h mymoneystatement.h mymoneytransactionfilter.h mymoneytransaction.h mymoneyutils.h mymoneybudget.h mymoneyforecast.h imymoneyprocessingcalendar.h mymoneycostcenter.h mymoneyunittestable.h mymoneypayeeidentifiercontainer.h onlinejob.h onlinejobtyped.h onlinejobmessage.h onlinejobfolder.h ) add_library(kmm_mymoney SHARED ${kmm_mymoney_LIB_SRCS}) +kde_target_enable_exceptions(kmm_mymoney PUBLIC) target_compile_features(kmm_mymoney PUBLIC cxx_nullptr PRIVATE cxx_decltype cxx_lambdas cxx_constexpr cxx_range_for) generate_export_header(kmm_mymoney BASE_NAME kmm_mymoney) target_link_libraries(kmm_mymoney PUBLIC kmm_icons Qt5::Xml Qt5::Core + Qt5::Gui + Qt5::Sql KF5::Service + KF5::I18n Alkimia::alkimia - kmm_storage kmm_payeeidentifier_loader kmm_payeeidentifier payeeidentifier_iban_bic payeeidentifier_nationalAccount # TODO: fix this KF5::XmlGui PRIVATE onlinetask_unavailabletask ) if(KMM_ADDRESSBOOK_FOUND) target_link_libraries(kmm_mymoney PUBLIC KF5::IdentityManagement KF5::AkonadiCore KF5::Contacts) endif() set_target_properties(kmm_mymoney PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) ########### install files ############### install(TARGETS kmm_mymoney ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${mymoney_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) ############## tests #################### if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/kmymoney/mymoney/storage/CMakeLists.txt b/kmymoney/mymoney/storage/CMakeLists.txt index ed4e10e5c..f251b26d4 100644 --- a/kmymoney/mymoney/storage/CMakeLists.txt +++ b/kmymoney/mymoney/storage/CMakeLists.txt @@ -1,44 +1,15 @@ if(BUILD_TESTING) add_subdirectory(tests) endif() set(storage_HEADERS imymoneystorageformat.h mymoneystoragemgr.h kmymoneystorageplugin.h databasestoreableobject.h ) -set(storage_a_SOURCES - imymoneystorageformat.cpp - mymoneystoragexml.cpp - mymoneystoragemgr.cpp - mymoneystorageanon.cpp - kmymoneystorageplugin.cpp - mymoneystoragenames.cpp - ) - -if(KMM_DEBUG) - list(APPEND storage_a_SOURCES mymoneystoragedump.cpp) -endif() - -# This library is actually not needed. It is built just for -# convenience, and then linked into kmm_mymoney. -add_library( kmm_storage STATIC ${storage_a_SOURCES} ) -kde_target_enable_exceptions(kmm_storage PUBLIC) - -target_link_libraries( kmm_storage - PUBLIC - KF5::Service - KF5::I18n - Qt5::Core - Qt5::Xml - Qt5::Sql - Qt5::Gui - Alkimia::alkimia -) - ########### install files ############### install(FILES ${storage_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel ) diff --git a/kmymoney/mymoney/storage/imymoneystorageformat.h b/kmymoney/mymoney/storage/imymoneystorageformat.h index 78cc800c6..728724d8c 100644 --- a/kmymoney/mymoney/storage/imymoneystorageformat.h +++ b/kmymoney/mymoney/storage/imymoneystorageformat.h @@ -1,73 +1,75 @@ /*************************************************************************** imymoneystorageformat.h - description ------------------- begin : Sun Oct 27 2002 copyright : (C) 2000-2002 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 IMYMONEYSTORAGEFORMAT_H #define IMYMONEYSTORAGEFORMAT_H /** * @author Kevin Tambascio (ktambascio@yahoo.com) */ +#include "kmm_mymoney_export.h" + // ---------------------------------------------------------------------------- // QT Includes class QString; class QIODevice; // ---------------------------------------------------------------------------- // Project Includes class MyMoneyStorageMgr; -class IMyMoneyOperationsFormat +class KMM_MYMONEY_EXPORT IMyMoneyOperationsFormat { public: IMyMoneyOperationsFormat(); virtual ~IMyMoneyOperationsFormat(); enum fileVersionDirectionType { Reading = 0, /**< version of file to be read */ Writing = 1 /**< version to be used when writing a file */ }; virtual void readFile(QIODevice* qf, MyMoneyStorageMgr* storage) = 0; // virtual void readStream(QDataStream& s, IMyMoneySerialization* storage) = 0; virtual void writeFile(QIODevice* qf, MyMoneyStorageMgr* storage) = 0; //virtual void writeStream(QDataStream& s, IMyMoneySerialization* storage) = 0; virtual void setProgressCallback(void(*callback)(int, int, const QString&)) = 0; /** * This member is used to store the file version information * obtained while reading a file. */ static unsigned int fileVersionRead; /** * This member is used to store the file version information * to be used when writing a file. */ static unsigned int fileVersionWrite; }; #endif diff --git a/kmymoney/mymoney/storage/kmymoneystorageplugin.h b/kmymoney/mymoney/storage/kmymoneystorageplugin.h index ecbc69697..fd7938ab0 100644 --- a/kmymoney/mymoney/storage/kmymoneystorageplugin.h +++ b/kmymoney/mymoney/storage/kmymoneystorageplugin.h @@ -1,67 +1,69 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KMYMONEYSTORAGEPLUGIN_H #define KMYMONEYSTORAGEPLUGIN_H +#include "kmm_mymoney_export.h" + #include #include class QSqlDatabase; namespace KMyMoneyPlugin { /** * @brief Interface for plugins which store data in an sql database * */ -class storagePlugin : public QObject +class KMM_MYMONEY_EXPORT storagePlugin : public QObject { Q_OBJECT public: /** * This is the constructor needed by KService */ explicit storagePlugin(QObject* parent = 0, const QVariantList& options = QVariantList()); /** * @brief Setup database to make it usable by the plugins * * This method is also called on the first usage in a session. You must check if your changes * were applied before. This also enables the plugin to upgrade the schema if needed. * * You must fill the kmmPluginInfo table with your plugins data. The version column is for your * usage. A higher number means a more recent version. * Use a transaction! */ virtual bool setupDatabase(QSqlDatabase connection) = 0; /** * @brief Remove all data belonging to the plugin from the database */ virtual bool removePluginData(QSqlDatabase connection) = 0; virtual ~storagePlugin(); }; } // end namespace KMyMoneyPlugin Q_DECLARE_INTERFACE(KMyMoneyPlugin::storagePlugin, "org.kmymoney.plugin.storageplugin") #endif // KMYMONEYPLUGIN_STORAGEPLUGIN_H diff --git a/kmymoney/mymoney/storage/mymoneystorageanon.h b/kmymoney/mymoney/storage/mymoneystorageanon.h index 266490a51..cfab8ca7c 100644 --- a/kmymoney/mymoney/storage/mymoneystorageanon.h +++ b/kmymoney/mymoney/storage/mymoneystorageanon.h @@ -1,118 +1,120 @@ /*************************************************************************** mymoneystorageanon.h ------------------- begin : Thu Oct 24 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jone ***************************************************************************/ /*************************************************************************** * * * 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 MYMONEYSTORAGEANON_H #define MYMONEYSTORAGEANON_H +#include "kmm_mymoney_export.h" + // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragexml.h" #include "mymoneymoney.h" class MyMoneyKeyValueContainer; /** * @author Kevin Tambascio (ktambascio@users.sourceforge.net) */ #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects /** * This class provides storage of an anonymized version of the current * file. Any object with an ID (account, transaction, etc) is renamed * with that ID. Any other string value the user typed in is replaced with * x's equal in length to the original string. Any numeric value is * replaced with an arbitrary number which matches the sign of the original. * * The purpose of this class is to give users a way to send a developer * their file without comprimising their financial data. If a user * encounters an error, they should try saving the anonymous version of the * file and see if the error is still there. If so, they should notify the * list of the problem, and then when requested, send the anonymous file * privately to the developer who takes the problem. I still don't think * it's wise to post the file to the public list...maybe I'm just paranoid. * * @author Ace Jones */ -class MyMoneyStorageANON : public MyMoneyStorageXML +class KMM_MYMONEY_EXPORT MyMoneyStorageANON : public MyMoneyStorageXML { public: MyMoneyStorageANON(); virtual ~MyMoneyStorageANON(); protected: void writeUserInformation(QDomElement& userInfo); void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i); void writePayee(QDomElement& payees, const MyMoneyPayee& p); void writeTag(QDomElement& tags, const MyMoneyTag& ta); void writeAccount(QDomElement& accounts, const MyMoneyAccount& p); void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx); void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx); void writeBudget(QDomElement& budgets, const MyMoneyBudget& b); void writeReport(QDomElement& reports, const MyMoneyReport& r); void readFile(QIODevice* s, MyMoneyStorageMgr* storage); void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security); /** Cannot remove prive data from plugins, yet. It is simply doing nothing. */ void writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job); QDomElement findChildElement(const QString& name, const QDomElement& root); private: /** * The list of key-value pairs to not modify */ static QStringList zKvpNoModify; /** * The list of key-value pairs which are numbers to be hidden */ static QStringList zKvpXNumber; QString hideString(const QString&) const; MyMoneyMoney hideNumber(const MyMoneyMoney&) const; void fakeTransaction(MyMoneyTransaction& tn); void fakeBudget(MyMoneyBudget& bn); void fakeKeyValuePair(MyMoneyKeyValueContainer& _kvp); MyMoneyMoney m_factor; }; #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragemgr.h b/kmymoney/mymoney/storage/mymoneystoragemgr.h index f25d28f16..6cd9a1fa7 100644 --- a/kmymoney/mymoney/storage/mymoneystoragemgr.h +++ b/kmymoney/mymoney/storage/mymoneystoragemgr.h @@ -1,1056 +1,1058 @@ /*************************************************************************** mymoneystoragemgr.h - description ------------------- begin : Sun May 5 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGEMGR_H #define MYMONEYSTORAGEMGR_H +#include "kmm_mymoney_export.h" + // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" class QUrl; class QString; class QStringList; class QDate; class QBitArray; class MyMoneyObject; class MyMoneyMoney; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyPayee; class MyMoneyTag; class MyMoneyPrice; class MyMoneyReport; class MyMoneySchedule; class MyMoneyBudget; class MyMoneySplit; class MyMoneyTransaction; class MyMoneyTransactionFilter; class MyMoneyCostCenter; class onlineJob; class MyMoneyStorageSql; template class QMap; template class QList; template struct QPair; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; namespace eMyMoney { namespace Schedule { enum class Type; } } namespace eMyMoney { namespace Schedule { enum class Occurrence; } } namespace eMyMoney { namespace Schedule { enum class PaymentType; } } /** * @author Thomas Baumgart * @author Łukasz Wojniłowicz */ /** * The MyMoneyStorageMgr class represents the storage engine for sequential * files. The actual file type and it's internal storage format (e.g. binary * or XML) is not important. * * The MyMoneyStorageMgr must be loaded by an application using loadAccounts() * method and etc. and can then be accessed through the * account() method and etc.. All data is loaded into memory, modified * and kept there. It is the subject of an outside object to store the * modified data in a persistant storage area using the accountList() method and etc. * As indication, if data has been changed, the retrun value * of the method dirty() can be used. */ class MyMoneyStorageMgrPrivate; -class MyMoneyStorageMgr : public MyMoneyKeyValueContainer +class KMM_MYMONEY_EXPORT MyMoneyStorageMgr : public MyMoneyKeyValueContainer { Q_DISABLE_COPY(MyMoneyStorageMgr) KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyStorageDump; public: MyMoneyStorageMgr(); ~MyMoneyStorageMgr(); // general get functions MyMoneyPayee user() const; QDate creationDate() const; QDate lastModificationDate() const; uint currentFixVersion() const; uint fileFixVersion() const; // general set functions void setUser(const MyMoneyPayee& user); void setCreationDate(const QDate& val); void setLastModificationDate(const QDate& val); void setFileFixVersion(uint v); /** * Returns the account addressed by it's id. * * @param id id of the account to locate. * @return reference to MyMoneyAccount object. An exception is thrown * if the id is unknown */ MyMoneyAccount account(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of an account. * An exception will be thrown upon error conditions. * * @param name QString reference to name of account * * @return MyMoneyAccount reference to object of account */ MyMoneyAccount accountByName(const QString& name) const; /** * This method is used to check whether a given * account id references one of the standard accounts or not. * * @param id account id * @return true if account-id is one of the standards, false otherwise */ bool isStandardAccount(const QString& id) const; /** * This method is used to set the name for the specified standard account * within the storage area. An exception will be thrown, if an error * occurs * * @param id QString reference to one of the standard accounts. Possible * values are: * * @li stdAccNames[stdAccLiability] * @li stdAccNames[stdAccAsset] * @li stdAccNames[stdAccExpense] * @li stdAccNames[stdAccIncome] * @li stdAccNames[stdAccEquity] * * @param name QString reference to the name to be set * */ void setAccountName(const QString& id, const QString& name); /** * This method is used to create a new account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount filled with data */ void addAccount(MyMoneyAccount& account); /** * This method is used to create a new payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void addPayee(MyMoneyPayee& payee); /** * Create now onlineJob */ void addOnlineJob(onlineJob& job); /** * This method is used to retrieve information about a payee * An exception will be thrown upon error conditions. * * @param id QString reference to id of payee * * @return MyMoneyPayee object of payee */ MyMoneyPayee payee(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a payee/receiver. * An exception will be thrown upon error conditions. * * @param payee QString reference to name of payee * * @return MyMoneyPayee reference to object of payee */ MyMoneyPayee payeeByName(const QString& payee) const; /** * This method is used to modify an existing payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void modifyPayee(const MyMoneyPayee& payee); /** * This method is used to remove an existing payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void removePayee(const MyMoneyPayee& payee); /** * This method returns a list of the payees * inside a MyMoneyStorage object * * @return QList containing the payee information */ QList payeeList() const; /** * This method is used to create a new tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void addTag(MyMoneyTag& tag); /** * This method is used to retrieve information about a tag * An exception will be thrown upon error conditions. * * @param id QString reference to id of tag * * @return MyMoneyTag object of tag */ MyMoneyTag tag(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a tag. * An exception will be thrown upon error conditions. * * @param tag QString reference to name of tag * * @return MyMoneyTag reference to object of tag */ MyMoneyTag tagByName(const QString& tag) const; /** * This method is used to modify an existing tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void modifyTag(const MyMoneyTag& tag); /** * This method is used to remove an existing tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void removeTag(const MyMoneyTag& tag); /** * This method returns a list of the tags * inside a MyMoneyStorage object * * @return QList containing the tag information */ QList tagList() const; /** * This method is used to add one account as sub-ordinate to another * (parent) account. The objects passed as arguments will be modified * accordingly. * * @param parent parent account the account should be added to * @param account the account to be added */ void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account); /** * Adds an institution to the storage. A * respective institution-ID will be generated within this record. * The ID is stored as QString in the object passed as argument. * An exception will be thrown upon error conditions. * * @param institution The complete institution information in a * MyMoneyInstitution object */ void addInstitution(MyMoneyInstitution& institution); /** * Adds a transaction to the file-global transaction pool. A respective * transaction-ID will be generated within this record. The ID is stored * as QString in the transaction object. The accounts of the referenced splits * will be updated to have a reference to the transaction just added. * * @param transaction reference to the transaction * @param skipAccountUpdate if set, the transaction lists of the accounts * referenced in the splits are not updated. This is used for * bulk loading a lot of transactions but not during normal operation */ void addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate = false); /** * Modifies an already existing account in the file global account pool. * * An exception will be thrown upon error conditions. * * @param account reference to the new account information * @param skipCheck if @p true, skips the built in consistency check for * the object to be updated. Do not set this parameter * to true. This is only used for the MyMoneyFile::consistencyCheck() * procedure to be able to reload accounts. The default * setting of this parameter is @p false. */ void modifyAccount(const MyMoneyAccount& account, bool skipCheck = false); /** * Modifies an already existing institution in the file global * institution pool. * * An exception will be thrown upon error conditions. * * @param institution The complete new institution information */ void modifyInstitution(const MyMoneyInstitution& institution); /** * This method is used to update a specific transaction in the * transaction pool of the MyMoneyFile object * * An exception will be thrown upon error conditions. * * @param transaction reference to transaction to be changed */ void modifyTransaction(const MyMoneyTransaction& transaction); /** @todo implement */ void modifyOnlineJob(const onlineJob& job); /** * This method re-parents an existing account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount reference to account to be re-parented * @param parent MyMoneyAccount reference to new parent account */ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent); /** * This method is used to remove a transaction from the transaction * pool (journal). * * @param transaction const reference to transaction to be deleted */ void removeTransaction(const MyMoneyTransaction& transaction); /** * Deletes an existing account from the file global account pool * This method only allows to remove accounts that are not * referenced by any split. Use moveSplits() to move splits * to another account. An exception is thrown in case of a * problem. * * @param account reference to the account to be deleted. */ void removeAccount(const MyMoneyAccount& account); /** * Deletes an existing institution from the file global institution pool * Also modifies the accounts that reference this institution as * their institution. * * @param institution institution to be deleted. */ void removeInstitution(const MyMoneyInstitution& institution); onlineJob getOnlineJob(const QString& id) const; /** @todo implement */ ulong onlineJobId() const; void removeOnlineJob(const onlineJob &); /** * This method is used to extract a transaction from the file global * transaction pool through an id. In case of an invalid id, an * exception will be thrown. * * @param id id of transaction as QString. * @return reference to the requested transaction */ MyMoneyTransaction transaction(const QString& id) const; /** * This method is used to extract a transaction from the file global * transaction pool through an index into an account. * * @param account id of the account as QString * @param idx number of transaction in this account * @return reference to MyMoneyTransaction object */ MyMoneyTransaction transaction(const QString& account, const int idx) const; /** * This method is used to determince, if the account with the * given ID is referenced by any split in m_transactionList. * * @param id id of the account to be checked for * @return true if account is referenced, false otherwise */ bool hasActiveSplits(const QString& id) const; /** * This method is used to return the actual balance of an account * without it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date * @return balance of the account as MyMoneyMoney object */ MyMoneyMoney balance(const QString& id, const QDate& date) const; /** * This method is used to return the actual balance of an account * including it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date * @return balance of the account as MyMoneyMoney object */ MyMoneyMoney totalBalance(const QString& id, const QDate& date) const; /** * Returns the institution of a given ID * * @param id id of the institution to locate * @return MyMoneyInstitution object filled with data. If the institution * could not be found, an exception will be thrown */ MyMoneyInstitution institution(const QString& id) const; /** * This method returns an indicator if the storage object has been * changed after it has last been saved to permanent storage. * * @return true if changed, false if not */ bool dirty() const; /** * This method can be used by an external object to force the * storage object to be dirty. This is used e.g. when an upload * to an external destination failed but the previous storage * to a local disk was ok. */ void setDirty(); /** * This method returns a list of the institutions * inside a MyMoneyFile object * * @return QMap containing the institution information */ QList institutionList() const; /** * This method returns a list of accounts inside the storage object. * * @param list reference to QList receiving the account objects * * @note The standard accounts will not be returned */ void accountList(QList& list) const; /** * This method is used to pull a list of transactions from the file * global transaction pool. It returns all those transactions * that match the filter passed as argument. If the filter is empty, * the whole journal will be returned. * The list returned is sorted according to the transactions posting date. * If more than one transaction exists for the same date, the order among * them is undefined. * * The @p list will be cleared by this method. * * @param list reference to list * @param filter MyMoneyTransactionFilter object with the match criteria * * @return set of transactions in form of a QList */ void transactionList(QList& list, MyMoneyTransactionFilter& filter) const; /** * This method is used to pull a list of transactions from the file * global transaction pool. It returns all those transactions * that match the filter passed as argument. If the filter is empty, * the whole journal will be returned. * The list returned is sorted according to the transactions posting date. * If more than one transaction exists for the same date, the order among * them is undefined. * * The @p list will be cleared by this method. * * @param list reference to list * @param filter MyMoneyTransactionFilter object with the match criteria * * @return set of transactions in form of a QList > */ void transactionList(QList< QPair >& list, MyMoneyTransactionFilter& filter) const; /** * Compatibility interface for the previous method. */ QList transactionList(MyMoneyTransactionFilter& filter) const; /** * @brief Return all onlineJobs */ QList onlineJobList() const; /** * @brief Return all cost center objects */ QList< MyMoneyCostCenter > costCenterList() const; /** * @brief Return cost center object by id */ MyMoneyCostCenter costCenter(const QString& id) const; /** * This method returns whether a given transaction is already in memory, to avoid * reloading it from the database */ bool isDuplicateTransaction(const QString& id) const; /** * This method returns the number of transactions currently known to file * in the range 0..MAXUINT * * @param account QString reference to account id. If account is empty + all transactions (the journal) will be counted. If account * is not empty it returns the number of transactions * that have splits in this account. * * @return number of transactions in journal/account */ uint transactionCount(const QString& account) const; QMap transactionCountMap() const; /** * This method returns the number of institutions currently known to file * in the range 0..MAXUINT * * @return number of institutions known to file */ uint institutionCount() const; /** * This method returns the number of accounts currently known to file * in the range 0..MAXUINT * * @return number of accounts currently known inside a MyMoneyFile object */ uint accountCount() const; /** * This method is used to return the standard liability account * @return MyMoneyAccount liability account(group) */ MyMoneyAccount liability() const; /** * This method is used to return the standard asset account * @return MyMoneyAccount asset account(group) */ MyMoneyAccount asset() const; /** * This method is used to return the standard expense account * @return MyMoneyAccount expense account(group) */ MyMoneyAccount expense() const; /** * This method is used to return the standard income account * @return MyMoneyAccount income account(group) */ MyMoneyAccount income() const; /** * This method is used to return the standard equity account * @return MyMoneyAccount equity account(group) */ MyMoneyAccount equity() const; void loadAccounts(const QMap& acc); void loadTransactions(const QMap& map); void loadInstitutions(const QMap& map); void loadPayees(const QMap& map); void loadTags(const QMap& map); void loadSchedules(const QMap& map); void loadSecurities(const QMap& map); void loadCurrencies(const QMap& map); void loadPrices(const MyMoneyPriceList& list); void loadOnlineJobs(const QMap& onlineJobs); void loadCostCenters(const QMap& costCenters); /** * This method is used to set a value in the * KeyValueContainer. For details see MyMoneyKeyValueContainer::setValue(). * * @param key const reference to QString containing the key * @param val const reference to QString containing the value */ void setValue(const QString& key, const QString& val); /** * This method is used to delete a key-value-pair from the * KeyValueContainer identified by the parameter * @p key. For details see MyMoneyKeyValueContainer::deletePair(). * * @param key const reference to QString containing the key */ void deletePair(const QString& key); /** * This method is used to initially store a set of key/value pairs * in the container. It is meant to be used for loading functionality * from permanent storage. See MyMoneyKeyValueContainer::setPairs() * for details * * @param list const QMap containing the set of * key/value pairs to be loaded into the container. * * @note All existing key/value pairs in the container will be deleted. */ void setPairs(const QMap& list); /** * This method is used to add a scheduled transaction to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param sched reference to the MyMoneySchedule object */ void addSchedule(MyMoneySchedule& sched); /** * This method is used to modify an existing MyMoneySchedule * object. Therefor, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ void modifySchedule(const MyMoneySchedule& sched); /** * This method is used to remove an existing MyMoneySchedule object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ void removeSchedule(const MyMoneySchedule& sched); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ MyMoneySchedule schedule(const QString& id) const; /** * This method is used to create a new security object. The ID will be created * automatically. The object passed with the parameter @p security is modified * to contain the assigned id. * * An exception will be thrown upon error conditions. * * @param security MyMoneySecurity filled with data */ void addSecurity(MyMoneySecurity& security); /** * This method is used to modify an existing MyMoneySchedule * object. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be updated */ void modifySecurity(const MyMoneySecurity& security); /** * This method is used to remove an existing MyMoneySecurity object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be removed */ void removeSecurity(const MyMoneySecurity& security); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ MyMoneySecurity security(const QString& id) const; /** * This method returns a list of security objects that the engine has * knowledge of. */ QList securityList() const; /** * This method is used to add a new currency object to the engine. * The ID of the object is the trading symbol, so there is no need for an additional * ID since the symbol is guaranteed to be unique. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneyCurrency object */ void addCurrency(const MyMoneySecurity& currency); /** * This method is used to modify an existing MyMoneyCurrency * object. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneyCurrency object */ void modifyCurrency(const MyMoneySecurity& currency); /** * This method is used to remove an existing MyMoneyCurrency object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneyCurrency object */ void removeCurrency(const MyMoneySecurity& currency); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ MyMoneySecurity currency(const QString& id) const; /** * This method is used to retrieve the list of all currencies * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyCurrency objects. */ QList currencyList() const; /** * This method is used to extract a list of scheduled transactions * according to the filter criteria passed as arguments. * * @param accountId only search for scheduled transactions that reference * accound @p accountId. If accountId is the empty string, * this filter is off. Default is @p QString(). * @param type only schedules of type @p type are searched for. * See eMyMoney::Schedule::Type for details. * Default is eMyMoney::Schedule::Type::Any * @param occurrence only schedules of occurrence type @p occurrence are searched for. * See eMyMoney::Schedule::Occurrence for details. * Default is eMyMoney::Schedule::Occurrence::Any * @param paymentType only schedules of payment method @p paymentType * are searched for. * See eMyMoney::Schedule::PaymentType for details. * Default is eMyMoney::Schedule::PaymentType::Any * @param startDate only schedules with payment dates after @p startDate * are searched for. Default is all dates (QDate()). * @param endDate only schedules with payment dates ending prior to @p endDate * are searched for. Default is all dates (QDate()). * @param overdue if true, only those schedules that are overdue are * searched for. Default is false (all schedules will be returned). * * @return QList list of schedule objects. */ QList scheduleList(const QString& accountId, eMyMoney::Schedule::Type type, eMyMoney::Schedule::Occurrence occurrence, eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, bool overdue) const; QList scheduleListEx(int scheduleTypes, int scheduleOcurrences, int schedulePaymentTypes, QDate startDate, const QStringList& accounts) const; /** * This method is used to retrieve the list of all reports * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyReport objects. */ QList reportList() const; /** * This method is used to add a new report to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param report reference to the MyMoneyReport object */ void addReport(MyMoneyReport& report); /** * This method is used to load a set of reports into the engine. This is * used when loading from storage, and an ID is already known. * * An exception will be thrown upon erroneous situations. * * @param reports reference to the map of MyMoneyReport objects */ void loadReports(const QMap& reports); /** * This method is used to modify an existing MyMoneyReport * object. Therefor, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param report const reference to the MyMoneyReport object to be updated */ void modifyReport(const MyMoneyReport& report); /** * This method returns the number of reports currently known to file * in the range 0..MAXUINT * * @return number of reports known to file */ uint countReports() const; /** * This method is used to retrieve a single MyMoneyReport object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyReport object * @return MyMoneyReport object */ MyMoneyReport report(const QString& id) const; /** * This method is used to remove an existing MyMoneyReport object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param report const reference to the MyMoneyReport object to be updated */ void removeReport(const MyMoneyReport& report); /** * This method is used to retrieve the list of all budgets * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyBudget objects. */ QList budgetList() const; /** * This method is used to add a new budget to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param budget reference to the MyMoneyBudget object */ void addBudget(MyMoneyBudget& budget); /** * This method is used to load a set of budgets into the engine. This is * used when loading from storage, and an ID is already known. * * An exception will be thrown upon erroneous situations. * * @param budgets reference to the map of MyMoneyBudget object */ void loadBudgets(const QMap& budgets); /** * This method is used to retrieve the id to a corresponding * name of a budget * An exception will be thrown upon error conditions. * * @param budget QString reference to name of budget * * @return MyMoneyBudget reference to object of budget */ MyMoneyBudget budgetByName(const QString& budget) const; /** * This method is used to modify an existing MyMoneyBudget * object. Therefore, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param budget const reference to the MyMoneyBudget object to be updated */ void modifyBudget(const MyMoneyBudget& budget); /** * This method returns the number of budgets currently known to file * in the range 0..MAXUINT * * @return number of budgets known to file */ uint countBudgets() const; /** * This method is used to retrieve a single MyMoneyBudget object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyBudget object * @return MyMoneyBudget object */ MyMoneyBudget budget(const QString& id) const; /** * This method is used to remove an existing MyMoneyBudget object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param budget const reference to the MyMoneyBudget object to be updated */ void removeBudget(const MyMoneyBudget& budget); /** * This method adds/replaces a price to/from the price list */ void addPrice(const MyMoneyPrice& price); /** * This method removes a price from the price list */ void removePrice(const MyMoneyPrice& price); /** * This method retrieves a price from the price list. * If @p date is inValid, QDate::currentDate() is assumed. */ MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const; /** * This method returns a list of all price entries. */ MyMoneyPriceList priceList() const; /** * This method checks, if the given @p object is referenced * by another engine object. * * @param obj const reference to object to be checked * @param skipCheck QBitArray with eStorage::Reference bits set for which * the check should be skipped * * @retval false @p object is not referenced * @retval true @p institution is referenced */ bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const; /** * This method recalculates the balances of all accounts * based on the transactions stored in the engine. */ void rebuildAccountBalances(); void startTransaction(); bool commitTransaction(); void rollbackTransaction(); /** * This method will close a database and log the use roff */ void close(); private: MyMoneyStorageMgrPrivate * const d_ptr; Q_DECLARE_PRIVATE(MyMoneyStorageMgr) }; #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragenames.h b/kmymoney/mymoney/storage/mymoneystoragenames.h index 28c1a1c24..438487a55 100644 --- a/kmymoney/mymoney/storage/mymoneystoragenames.h +++ b/kmymoney/mymoney/storage/mymoneystoragenames.h @@ -1,71 +1,73 @@ /*************************************************************************** mymoneystoragenames.h ------------------- begin : Sun Jun 18 2017 copyright : (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGENAMES_H #define MYMONEYSTORAGENAMES_H +#include "kmm_mymoney_export.h" + // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes namespace MyMoneyStorageTags { enum tagNameE { tnInstitutions, tnPayees, tnCostCenters, tnTags, tnAccounts, tnTransactions, tnSchedules, tnSecurities, tnCurrencies, tnPrices, tnReports, tnBudgets, tnOnlineJobs, tnKMMFile, tnFileInfo, tnUser }; extern const QHash tagNames; } namespace MyMoneyStorageNodes { enum ndNameE { nnInstitution, nnPayee, nnCostCenter, nnTag, nnAccount, nnTransaction, nnScheduleTX, nnSecurity, nnCurrency, nnPrice, nnPricePair, nnReport, nnBudget, nnOnlineJob, nnKeyValuePairs, nnEquity }; extern const QHash nodeNames; } namespace MyMoneyStorageAttributes { enum attrNameE { anID, anDate, anCount, anFrom, anTo, anSource, anKey, anValue, anPrice, anName, anEmail, anCountry, anCity, anZipCode, anStreet, anTelephone }; extern const QHash attrNames; } namespace MyMoneyStandardAccounts { enum idNameE { stdAccLiability, stdAccAsset, stdAccExpense, stdAccIncome, stdAccEquity }; -extern const QHash stdAccNames; +extern const KMM_MYMONEY_EXPORT QHash stdAccNames; } #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragexml.h b/kmymoney/mymoney/storage/mymoneystoragexml.h index 186d774d2..d198f9787 100644 --- a/kmymoney/mymoney/storage/mymoneystoragexml.h +++ b/kmymoney/mymoney/storage/mymoneystoragexml.h @@ -1,195 +1,197 @@ /*************************************************************************** mymoneystoragexml.h ------------------- begin : Thu Oct 24 2002 copyright : (C) 2002 by Kevin Tambascio (C) 2004 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGEXML_H #define MYMONEYSTORAGEXML_H +#include "kmm_mymoney_export.h" + // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "imymoneystorageformat.h" /** *@author Kevin Tambascio (ktambascio@users.sourceforge.net) */ #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects class QString; class QIODevice; class QDomElement; class QDomDocument; class QDate; class MyMoneyStorageMgr; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneySchedule; class MyMoneyPayee; class MyMoneyTag; class MyMoneyBudget; class MyMoneyReport; class MyMoneyPrice; class MyMoneyTransaction; class MyMoneyCostCenter; class onlineJob; template class QList; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; -class MyMoneyStorageXML : public IMyMoneyOperationsFormat +class KMM_MYMONEY_EXPORT MyMoneyStorageXML : public IMyMoneyOperationsFormat { friend class MyMoneyXmlContentHandler; public: MyMoneyStorageXML(); virtual ~MyMoneyStorageXML(); enum fileVersionDirectionType { Reading = 0, /**< version of file to be read */ Writing = 1 /**< version to be used when writing a file */ }; protected: void setProgressCallback(void(*callback)(int, int, const QString&)); void signalProgress(int current, int total, const QString& = ""); /** * This method returns the version of the underlying file. It * is used by the MyMoney objects contained in a MyMoneyStorageBin object (e.g. * MyMoneyAccount, MyMoneyInstitution, MyMoneyTransaction, etc.) to * determine the layout used when reading/writing a persistant file. * A parameter is used to determine the direction. * * @param dir information about the direction (reading/writing). The * default is reading. * * @return version QString of file's version * * @see m_fileVersionRead, m_fileVersionWrite */ static unsigned int fileVersion(fileVersionDirectionType dir = Reading); QList readElements(QString groupTag, QString itemTag = QString()); bool readFileInformation(const QDomElement& fileInfo); virtual void writeFileInformation(QDomElement& fileInfo); virtual void writeUserInformation(QDomElement& userInfo); virtual void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i); virtual void writeInstitutions(QDomElement& institutions); virtual void writePrices(QDomElement& prices); virtual void writePricePair(QDomElement& price, const MyMoneyPriceEntries& p); virtual void writePrice(QDomElement& prices, const MyMoneyPrice& p); virtual void writePayees(QDomElement& payees); virtual void writePayee(QDomElement& payees, const MyMoneyPayee& p); virtual void writeTags(QDomElement& tags); virtual void writeTag(QDomElement& tags, const MyMoneyTag& ta); virtual void writeAccounts(QDomElement& accounts); virtual void writeAccount(QDomElement& accounts, const MyMoneyAccount& p); virtual void writeTransactions(QDomElement& transactions); virtual void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx); virtual void writeSchedules(QDomElement& scheduled); virtual void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx); virtual void writeReports(QDomElement& e); virtual void writeReport(QDomElement& report, const MyMoneyReport& r); virtual void writeBudgets(QDomElement& e); virtual void writeBudget(QDomElement& budget, const MyMoneyBudget& b); virtual void writeOnlineJobs(QDomElement& onlineJobs); virtual void writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job); virtual void writeSecurities(QDomElement& securities); virtual void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security); virtual void writeCostCenters(QDomElement& parent); virtual void writeCostCenter(QDomElement& costCenters, const MyMoneyCostCenter& costCenter); virtual void writeCurrencies(QDomElement& currencies); virtual QDomElement writeKeyValuePairs(const QMap pairs); virtual void readFile(QIODevice* s, MyMoneyStorageMgr* storage); virtual void writeFile(QIODevice* s, MyMoneyStorageMgr* storage); bool readUserInformation(const QDomElement& userElement); void readPricePair(const QDomElement& pricePair); const MyMoneyPrice readPrice(const QString& from, const QString& to, const QDomElement& price); QDomElement findChildElement(const QString& name, const QDomElement& root); private: void (*m_progressCallback)(int, int, const QString&); enum elNameE { enAddress, enCreationDate, enLastModifiedDate, enVersion, enFixVersion, enPair }; static const QString getElName(const elNameE _el); protected: MyMoneyStorageMgr *m_storage; QDomDocument *m_doc; private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; /** * This member is used to store the file version information * obtained while reading a file. */ static unsigned int fileVersionRead; /** * This member is used to store the file version information * to be used when writing a file. */ static unsigned int fileVersionWrite; /** * This member keeps the id of the base currency. We need this * temporarily to convert the price history from the old to the * new format. This should go at some time beyond 0.8 (ipwizard) */ QString m_baseCurrencyId; }; #endif diff --git a/kmymoney/plugins/kbanking/kbanking.cpp b/kmymoney/plugins/kbanking/kbanking.cpp index be7774927..bd10be03e 100644 --- a/kmymoney/plugins/kbanking/kbanking.cpp +++ b/kmymoney/plugins/kbanking/kbanking.cpp @@ -1,1522 +1,1522 @@ /*************************************************************************** * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net * * Copyright 2009 Cristian Onet onet.cristian@gmail.com * * Copyright 2010 Thomas Baumgart ipwizard@users.sourceforge.net * * Copyright 2015 Christian David christian-david@web.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) 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 #include "kbanking.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include //! @todo remove @c #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Library Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/onlinejob.h" #include "kbaccountsettings.h" #include "kbmapaccount.h" #include "mymoneyfile.h" #include "onlinejobadministration.h" #include "kmymoneyview.h" #include "kbpickstartdate.h" #include "mymoneyinstitution.h" #include "gwenkdegui.h" #include "gwenhywfarqtoperators.h" #include "aqbankingkmmoperators.h" #include "mymoneystatement.h" #include "statementinterface.h" #include "viewinterface.h" #ifdef KMM_DEBUG // Added an option to open the chipTanDialog from the menu for debugging purposes #include "chiptandialog.h" #endif class KBanking::Private { public: Private() : passwordCacheTimer(nullptr), jobList(), fileId() { QString gwenProxy = QString::fromLocal8Bit(qgetenv("GWEN_PROXY")); if (gwenProxy.isEmpty()) { std::unique_ptr cfg = std::unique_ptr(new KConfig("kioslaverc")); QRegExp exp("(\\w+://)?([^/]{2}.+:\\d+)"); QString proxy; KConfigGroup grp = cfg->group("Proxy Settings"); int type = grp.readEntry("ProxyType", 0); switch (type) { case 0: // no proxy break; case 1: // manual specified proxy = grp.readEntry("httpsProxy"); qDebug("KDE https proxy setting is '%s'", qPrintable(proxy)); if (exp.exactMatch(proxy)) { proxy = exp.cap(2); qDebug("Setting GWEN_PROXY to '%s'", qPrintable(proxy)); - if (setenv("GWEN_PROXY", qPrintable(proxy), 1) == -1) { + if (!qputenv("GWEN_PROXY", qPrintable(proxy))) { qDebug("Unable to setup GWEN_PROXY"); } } break; default: // other currently not supported qDebug("KDE proxy setting of type %d not supported", type); break; } } } /** * KMyMoney asks for accounts over and over again which causes a lot of "Job not supported with this account" error messages. * This function filters messages with that string. */ static int gwenLogHook(GWEN_GUI* gui, const char* domain, GWEN_LOGGER_LEVEL level, const char* message) { Q_UNUSED(gui); Q_UNUSED(domain); Q_UNUSED(level); const char* messageToFilter = "Job not supported with this account"; if (strstr(message, messageToFilter) != 0) return 1; return 0; } QTimer *passwordCacheTimer; QMap jobList; QString fileId; }; KBanking::KBanking(QObject *parent, const QVariantList &args) : OnlinePluginExtended(parent, "kbanking") , d(new Private) , m_configAction(nullptr) , m_importAction(nullptr) , m_kbanking(nullptr) , m_accountSettings(nullptr) { Q_UNUSED(args) qDebug("Plugins: kbanking loaded"); } KBanking::~KBanking() { delete d; qDebug("Plugins: kbanking unloaded"); } void KBanking::plug() { m_kbanking = new KBankingExt(this, "KMyMoney"); d->passwordCacheTimer = new QTimer(this); d->passwordCacheTimer->setSingleShot(true); d->passwordCacheTimer->setInterval(60000); connect(d->passwordCacheTimer, &QTimer::timeout, this, &KBanking::slotClearPasswordCache); if (m_kbanking) { if (AB_Banking_HasConf4(m_kbanking->getCInterface())) { qDebug("KBankingPlugin: No AqB4 config found."); if (AB_Banking_HasConf3(m_kbanking->getCInterface())) { qDebug("KBankingPlugin: No AqB3 config found."); if (!AB_Banking_HasConf2(m_kbanking->getCInterface())) { qDebug("KBankingPlugin: AqB2 config found - converting."); AB_Banking_ImportConf2(m_kbanking->getCInterface()); } } else { qDebug("KBankingPlugin: AqB3 config found - converting."); AB_Banking_ImportConf3(m_kbanking->getCInterface()); } } //! @todo when is gwenKdeGui deleted? gwenKdeGui *gui = new gwenKdeGui(); GWEN_Gui_SetGui(gui->getCInterface()); GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning); if (m_kbanking->init() == 0) { // Tell the host application to load my GUI component setComponentName("kbanking", "KBanking"); setXMLFile("kbanking.rc"); qDebug("Plugins: kbanking pluged"); // get certificate handling and dialog settings management AB_Gui_Extend(gui->getCInterface(), m_kbanking->getCInterface()); // create actions createActions(); // load protocol conversion list loadProtocolConversion(); GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Warning); GWEN_Gui_SetLogHookFn(GWEN_Gui_GetGui(), &KBanking::Private::gwenLogHook); } else { qWarning("Could not initialize KBanking online banking interface"); delete m_kbanking; m_kbanking = 0; } } } void KBanking::unplug() { d->passwordCacheTimer->deleteLater(); if (m_kbanking) { m_kbanking->fini(); delete m_kbanking; qDebug("Plugins: kbanking unpluged"); } } void KBanking::loadProtocolConversion() { if (m_kbanking) { m_protocolConversionMap = { {"aqhbci", "HBCI"}, {"aqofxconnect", "OFX"}, {"aqyellownet", "YellowNet"}, {"aqgeldkarte", "Geldkarte"}, {"aqdtaus", "DTAUS"} }; } } void KBanking::protocols(QStringList& protocolList) const { if (m_kbanking) { std::list list = m_kbanking->getActiveProviders(); std::list::iterator it; for (it = list.begin(); it != list.end(); ++it) { // skip the dummy if (*it == "aqnone") continue; QMap::const_iterator it_m; it_m = m_protocolConversionMap.find((*it).c_str()); if (it_m != m_protocolConversionMap.end()) protocolList << (*it_m); else protocolList << (*it).c_str(); } } } QWidget* KBanking::accountConfigTab(const MyMoneyAccount& acc, QString& name) { const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); name = i18n("Online settings"); if (m_kbanking) { m_accountSettings = new KBAccountSettings(acc, 0); m_accountSettings->loadUi(kvp); return m_accountSettings; } QLabel* label = new QLabel(i18n("KBanking module not correctly initialized"), 0); label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); return label; } MyMoneyKeyValueContainer KBanking::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); kvp["provider"] = objectName().toLower(); if (m_accountSettings) { m_accountSettings->loadKvp(kvp); } return kvp; } void KBanking::createActions() { QAction *settings_aqbanking = actionCollection()->addAction("settings_aqbanking"); settings_aqbanking->setText(i18n("Configure Aq&Banking...")); connect(settings_aqbanking, &QAction::triggered, this, &KBanking::slotSettings); QAction *file_import_aqbanking = actionCollection()->addAction("file_import_aqbanking"); file_import_aqbanking->setText(i18n("AqBanking importer...")); connect(file_import_aqbanking, &QAction::triggered, this, &KBanking::slotImport); Q_CHECK_PTR(viewInterface()); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action("file_import_aqbanking"), &QAction::setEnabled); #ifdef KMM_DEBUG QAction *openChipTanDialog = actionCollection()->addAction("open_chiptan_dialog"); openChipTanDialog->setText("Open ChipTan Dialog"); connect(openChipTanDialog, &QAction::triggered, [&](){ auto dlg = new chipTanDialog(); dlg->setHhdCode("0F04871100030333555414312C32331D"); dlg->setInfoText("

Test Graphic for debugging

The encoded data is

Account Number: 335554
Amount: 1,23

"); connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater); connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater); dlg->show(); }); #endif } void KBanking::slotSettings() { if (m_kbanking) { GWEN_DIALOG* dlg = AB_SetupDialog_new(m_kbanking->getCInterface()); if (dlg == NULL) { DBG_ERROR(0, "Could not create setup dialog."); return; } if (GWEN_Gui_ExecDialog(dlg, 0) == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); return; } GWEN_Dialog_free(dlg); } } bool KBanking::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { bool rc = false; if (m_kbanking && !acc.id().isEmpty()) { m_kbanking->askMapAccount(acc); // at this point, the account should be mapped // so we search it and setup the account reference in the KMyMoney object AB_ACCOUNT* ab_acc; ab_acc = aqbAccount(acc); if (ab_acc) { MyMoneyAccount a(acc); setupAccountReference(a, ab_acc); settings = a.onlineBankingSettings(); rc = true; } } return rc; } AB_ACCOUNT* KBanking::aqbAccount(const MyMoneyAccount& acc) const { if (m_kbanking == 0) { return 0; } // certainly looking for an expense or income account does not make sense at this point // so we better get out right away if (acc.isIncomeExpense()) { return 0; } AB_ACCOUNT *ab_acc = AB_Banking_GetAccountByAlias(m_kbanking->getCInterface(), m_kbanking->mappingId(acc).toUtf8().data()); // if the account is not found, we temporarily scan for the 'old' mapping (the one w/o the file id) // in case we find it, we setup the new mapping in addition on the fly. if (!ab_acc && acc.isAssetLiability()) { ab_acc = AB_Banking_GetAccountByAlias(m_kbanking->getCInterface(), acc.id().toUtf8().data()); if (ab_acc) { qDebug("Found old mapping for '%s' but not new. Setup new mapping", qPrintable(acc.name())); m_kbanking->setAccountAlias(ab_acc, m_kbanking->mappingId(acc).toUtf8().constData()); // TODO at some point in time, we should remove the old mapping } } return ab_acc; } AB_ACCOUNT* KBanking::aqbAccount(const QString& accountId) const { MyMoneyAccount account = MyMoneyFile::instance()->account(accountId); return aqbAccount(account); } QString KBanking::stripLeadingZeroes(const QString& s) const { QString rc(s); QRegExp exp("^(0*)([^0].*)"); if (exp.exactMatch(s)) { rc = exp.cap(2); } return rc; } void KBanking::setupAccountReference(const MyMoneyAccount& acc, AB_ACCOUNT* ab_acc) { MyMoneyKeyValueContainer kvp; if (ab_acc) { QString accountNumber = stripLeadingZeroes(AB_Account_GetAccountNumber(ab_acc)); QString routingNumber = stripLeadingZeroes(AB_Account_GetBankCode(ab_acc)); QString val = QString("%1-%2").arg(routingNumber, accountNumber); if (val != acc.onlineBankingSettings().value("kbanking-acc-ref")) { MyMoneyKeyValueContainer kvp; // make sure to keep our own previous settings const QMap& vals = acc.onlineBankingSettings().pairs(); QMap::const_iterator it_p; for (it_p = vals.begin(); it_p != vals.end(); ++it_p) { if (QString(it_p.key()).startsWith("kbanking-")) { kvp.setValue(it_p.key(), *it_p); } } kvp.setValue("kbanking-acc-ref", val); kvp.setValue("provider", objectName().toLower()); setAccountOnlineParameters(acc, kvp); } } else { // clear the connection setAccountOnlineParameters(acc, kvp); } } bool KBanking::accountIsMapped(const MyMoneyAccount& acc) { return aqbAccount(acc) != 0; } bool KBanking::updateAccount(const MyMoneyAccount& acc) { return updateAccount(acc, false); } bool KBanking::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { if (!m_kbanking) return false; bool rc = false; if (!acc.id().isEmpty()) { AB_JOB *job = 0; int rv; /* get AqBanking account */ AB_ACCOUNT *ba = aqbAccount(acc); // Update the connection between the KMyMoney account and the AqBanking equivalent. // If the account is not found anymore ba == 0 and the connection is removed. setupAccountReference(acc, ba); if (!ba) { KMessageBox::error(0, i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", acc.name()), i18n("Account Not Mapped")); } else { bool enqueJob = true; if (acc.onlineBankingSettings().value("kbanking-txn-download") != "no") { /* create getTransactions job */ job = AB_JobGetTransactions_new(ba); rv = AB_Job_CheckAvailability(job); if (rv) { DBG_ERROR(0, "Job \"GetTransactions\" is not available (%d)", rv); KMessageBox::error(0, i18n("" "The update job is not supported by the " "bank/account/backend.\n" ""), i18n("Job not Available")); AB_Job_free(job); job = 0; } if (job) { int days = AB_JobGetTransactions_GetMaxStoreDays(job); QDate qd; if (days > 0) { GWEN_TIME *ti1; GWEN_TIME *ti2; ti1 = GWEN_CurrentTime(); ti2 = GWEN_Time_fromSeconds(GWEN_Time_Seconds(ti1) - (60 * 60 * 24 * days)); GWEN_Time_free(ti1); ti1 = ti2; int year, month, day; if (GWEN_Time_GetBrokenDownDate(ti1, &day, &month, &year)) { DBG_ERROR(0, "Bad date"); qd = QDate(); } else qd = QDate(year, month + 1, day); GWEN_Time_free(ti1); } // get last statement request date from application account object // and start from a few days before if the date is valid QDate lastUpdate = QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate); if (lastUpdate.isValid()) lastUpdate = lastUpdate.addDays(-3); int dateOption = acc.onlineBankingSettings().value("kbanking-statementDate").toInt(); switch (dateOption) { case 0: // Ask user break; case 1: // No date qd = QDate(); break; case 2: // Last download qd = lastUpdate; break; case 3: // First possible // qd is already setup break; } // the pick start date option dialog is needed in // case the dateOption is 0 or the date option is > 1 // and the qd is invalid if (dateOption == 0 || (dateOption > 1 && !qd.isValid())) { QPointer psd = new KBPickStartDate(m_kbanking, qd, lastUpdate, acc.name(), lastUpdate.isValid() ? 2 : 3, 0, true); if (psd->exec() == QDialog::Accepted) { qd = psd->date(); } else { enqueJob = false; } delete psd; } if (enqueJob) { if (qd.isValid()) { GWEN_TIME *ti1; ti1 = GWEN_Time_new(qd.year(), qd.month() - 1, qd.day(), 0, 0, 0, 0); AB_JobGetTransactions_SetFromTime(job, ti1); GWEN_Time_free(ti1); } rv = m_kbanking->enqueueJob(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } } AB_Job_free(job); } } if (enqueJob) { /* create getBalance job */ job = AB_JobGetBalance_new(ba); rv = AB_Job_CheckAvailability(job); if (!rv) rv = m_kbanking->enqueueJob(job); else rv = 0; AB_Job_free(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } else { rc = true; emit queueChanged(); } } } } // make sure we have at least one job in the queue before sending it if (!moreAccounts && m_kbanking->getEnqueuedJobs().size() > 0) executeQueue(); return rc; } void KBanking::executeQueue() { if (m_kbanking && m_kbanking->getEnqueuedJobs().size() > 0) { AB_IMEXPORTER_CONTEXT *ctx; ctx = AB_ImExporterContext_new(); int rv = m_kbanking->executeQueue(ctx); if (!rv) { m_kbanking->importContext(ctx, 0); } else { DBG_ERROR(0, "Error: %d", rv); } AB_ImExporterContext_free(ctx); } } /** @todo improve error handling, e.g. by adding a .isValid to nationalTransfer * @todo use new onlineJob system */ void KBanking::sendOnlineJob(QList& jobs) { Q_CHECK_PTR(m_kbanking); m_onlineJobQueue.clear(); QList unhandledJobs; if (!jobs.isEmpty()) { foreach (onlineJob job, jobs) { if (sepaOnlineTransfer::name() == job.task()->taskName()) { onlineJobTyped typedJob(job); enqueTransaction(typedJob); job = typedJob; } else { job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "KBanking", "Cannot handle this request")); unhandledJobs.append(job); } m_onlineJobQueue.insert(m_kbanking->mappingId(job), job); } executeQueue(); } jobs = m_onlineJobQueue.values() + unhandledJobs; m_onlineJobQueue.clear(); } QStringList KBanking::availableJobs(QString accountId) { try { MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId); QString id = MyMoneyFile::instance()->value("kmm-id"); if(id != d->fileId) { d->jobList.clear(); d->fileId = id; } } catch (const MyMoneyException&) { // Exception usually means account was not found return QStringList(); } if(d->jobList.contains(accountId)) { return d->jobList[accountId]; } QStringList list; AB_ACCOUNT* abAccount = aqbAccount(accountId); if (!abAccount) { return list; } // Check availableJobs // sepa transfer AB_JOB* abJob = AB_JobSepaTransfer_new(abAccount); if (AB_Job_CheckAvailability(abJob) == 0) list.append(sepaOnlineTransfer::name()); AB_Job_free(abJob); d->jobList[accountId] = list; return list; } /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbJobDeleter { public: static void cleanup(AB_JOB* job) { AB_Job_free(job); } }; /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbAccountDeleter { public: static void cleanup(AB_ACCOUNT* account) { AB_Account_free(account); } }; IonlineTaskSettings::ptr KBanking::settings(QString accountId, QString taskName) { AB_ACCOUNT* abAcc = aqbAccount(accountId); if (abAcc == 0) return IonlineTaskSettings::ptr(); if (sepaOnlineTransfer::name() == taskName) { // Get limits for sepaonlinetransfer QScopedPointer abJob(AB_JobSepaTransfer_new(abAcc)); if (AB_Job_CheckAvailability(abJob.data()) != 0) return IonlineTaskSettings::ptr(); const AB_TRANSACTION_LIMITS* limits = AB_Job_GetFieldLimits(abJob.data()); return AB_TransactionLimits_toSepaOnlineTaskSettings(limits).dynamicCast(); } return IonlineTaskSettings::ptr(); } bool KBanking::enqueTransaction(onlineJobTyped& job) { /* get AqBanking account */ const QString accId = job.constTask()->responsibleAccount(); AB_ACCOUNT *abAccount = aqbAccount(accId); if (!abAccount) { job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Warning, "KBanking", i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", MyMoneyFile::instance()->account(accId).name()))); return false; } //setupAccountReference(acc, ba); // needed? AB_JOB *abJob = AB_JobSepaTransfer_new(abAccount); int rv = AB_Job_CheckAvailability(abJob); if (rv) { qDebug("AB_ERROR_OFFSET is %i", AB_ERROR_OFFSET); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "AqBanking", QString("Sepa credit transfers for account \"%1\" are not available, error code %2.").arg(MyMoneyFile::instance()->account(accId).name(), rv) ) ); return false; } AB_TRANSACTION *AbTransaction = AB_Transaction_new(); // Recipient payeeIdentifiers::ibanBic beneficiaryAcc = job.constTask()->beneficiaryTyped(); AB_Transaction_SetRemoteName(AbTransaction, GWEN_StringList_fromQString(beneficiaryAcc.ownerName())); AB_Transaction_SetRemoteIban(AbTransaction, beneficiaryAcc.electronicIban().toUtf8().constData()); AB_Transaction_SetRemoteBic(AbTransaction, beneficiaryAcc.fullStoredBic().toUtf8().constData()); // Origin Account AB_Transaction_SetLocalAccount(AbTransaction, abAccount); // Purpose QStringList qPurpose = job.constTask()->purpose().split('\n'); GWEN_STRINGLIST *purpose = GWEN_StringList_fromQStringList(qPurpose); AB_Transaction_SetPurpose(AbTransaction, purpose); GWEN_StringList_free(purpose); // Reference // AqBanking duplicates the string. This should be safe. AB_Transaction_SetEndToEndReference(AbTransaction, job.constTask()->endToEndReference().toUtf8().constData()); // Other Fields AB_Transaction_SetTextKey(AbTransaction, job.constTask()->textKey()); AB_Transaction_SetValue(AbTransaction, AB_Value_fromMyMoneyMoney(job.constTask()->value())); /** @todo LOW remove Debug info */ qDebug() << "SetTransaction: " << AB_Job_SetTransaction(abJob, AbTransaction); GWEN_DB_NODE *gwenNode = AB_Job_GetAppData(abJob); GWEN_DB_SetCharValue(gwenNode, GWEN_DB_FLAGS_DEFAULT, "kmmOnlineJobId", m_kbanking->mappingId(job).toLatin1().constData()); qDebug() << "Enqueue: " << m_kbanking->enqueueJob(abJob); //delete localAcc; return true; } void KBanking::startPasswordTimer() { if (d->passwordCacheTimer->isActive()) d->passwordCacheTimer->stop(); d->passwordCacheTimer->start(); } void KBanking::slotClearPasswordCache() { m_kbanking->clearPasswordCache(); } void KBanking::slotImport() { if (!m_kbanking->interactiveImport()) qWarning("Error on import dialog"); } bool KBanking::importStatement(const MyMoneyStatement& s) { return statementInterface()->import(s); } MyMoneyAccount KBanking::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void KBanking::setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps) const { return statementInterface()->setAccountOnlineParameters(acc, kvps); } KBankingExt::KBankingExt(KBanking* parent, const char* appname, const char* fname) : AB_Banking(appname, fname) , m_parent(parent) , _jobQueue(0) { m_sepaKeywords = {QString::fromUtf8("SEPA-BASISLASTSCHRIFT"), QString::fromUtf8("SEPA-ÜBERWEISUNG")}; } int KBankingExt::init() { int rv = AB_Banking::init(); if (rv < 0) return rv; rv = onlineInit(); if (rv) { fprintf(stderr, "Error on online init (%d).\n", rv); AB_Banking::fini(); return rv; } _jobQueue = AB_Job_List2_new(); return 0; } int KBankingExt::fini() { if (_jobQueue) { AB_Job_List2_FreeAll(_jobQueue); _jobQueue = 0; } const int rv = onlineFini(); if (rv) { AB_Banking::fini(); return rv; } return AB_Banking::fini(); } int KBankingExt::executeQueue(AB_IMEXPORTER_CONTEXT *ctx) { m_parent->startPasswordTimer(); int rv = AB_Banking::executeJobs(_jobQueue, ctx); if (rv != 0) { qDebug() << "Sending queue by aqbanking got error no " << rv; } /** check result of each job */ AB_JOB_LIST2_ITERATOR* jobIter = AB_Job_List2_First(_jobQueue); if (jobIter) { AB_JOB* abJob = AB_Job_List2Iterator_Data(jobIter); while (abJob) { GWEN_DB_NODE* gwenNode = AB_Job_GetAppData(abJob); if (gwenNode == 0) { qWarning("Executed AB_Job without KMyMoney id"); abJob = AB_Job_List2Iterator_Next(jobIter); break; } QString jobIdent = QString::fromUtf8(GWEN_DB_GetCharValue(gwenNode, "kmmOnlineJobId", 0, "")); onlineJob job = m_parent->m_onlineJobQueue.value(jobIdent); if (job.isNull()) { // It should not be possiblie that this will happen (only if AqBanking fails heavily). //! @todo correct exception text qWarning("Executed a job which was not in queue. Please inform the KMyMoney developers."); abJob = AB_Job_List2Iterator_Next(jobIter); continue; } AB_JOB_STATUS abStatus = AB_Job_GetStatus(abJob); if (abStatus == AB_Job_StatusSent || abStatus == AB_Job_StatusPending || abStatus == AB_Job_StatusFinished || abStatus == AB_Job_StatusError || abStatus == AB_Job_StatusUnknown) job.setJobSend(); if (abStatus == AB_Job_StatusFinished) job.setBankAnswer(onlineJob::acceptedByBank); else if (abStatus == AB_Job_StatusError || abStatus == AB_Job_StatusUnknown) job.setBankAnswer(onlineJob::sendingError); job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Debug, "KBanking", "Job was processed")); m_parent->m_onlineJobQueue.insert(jobIdent, job); abJob = AB_Job_List2Iterator_Next(jobIter); } AB_Job_List2Iterator_free(jobIter); } AB_JOB_LIST2 *oldQ = _jobQueue; _jobQueue = AB_Job_List2_new(); AB_Job_List2_FreeAll(oldQ); emit m_parent->queueChanged(); m_parent->startPasswordTimer(); return rv; } void KBankingExt::clearPasswordCache() { /* clear password DB */ GWEN_Gui_SetPasswordStatus(NULL, NULL, GWEN_Gui_PasswordStatus_Remove, 0); } std::list KBankingExt::getEnqueuedJobs() { AB_JOB_LIST2 *ll; std::list rl; ll = _jobQueue; if (ll && AB_Job_List2_GetSize(ll)) { AB_JOB *j; AB_JOB_LIST2_ITERATOR *it; it = AB_Job_List2_First(ll); assert(it); j = AB_Job_List2Iterator_Data(it); assert(j); while (j) { rl.push_back(j); j = AB_Job_List2Iterator_Next(it); } AB_Job_List2Iterator_free(it); } return rl; } int KBankingExt::enqueueJob(AB_JOB *j) { assert(_jobQueue); assert(j); AB_Job_Attach(j); AB_Job_List2_PushBack(_jobQueue, j); return 0; } int KBankingExt::dequeueJob(AB_JOB *j) { assert(_jobQueue); AB_Job_List2_Remove(_jobQueue, j); AB_Job_free(j); emit m_parent->queueChanged(); return 0; } void KBankingExt::transfer() { //m_parent->transfer(); } bool KBankingExt::askMapAccount(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); QString bankId; QString accountId; // extract some information about the bank. if we have a sortcode // (BLZ) we display it, otherwise the name is enough. try { const MyMoneyInstitution &bank = file->institution(acc.institutionId()); bankId = bank.name(); if (!bank.sortcode().isEmpty()) bankId = bank.sortcode(); } catch (const MyMoneyException &e) { // no bank assigned, we just leave the field emtpy } // extract account information. if we have an account number // we show it, otherwise the name will be displayed accountId = acc.number(); if (accountId.isEmpty()) accountId = acc.name(); // do the mapping. the return value of this method is either // true, when the user mapped the account or false, if he // decided to quit the dialog. So not really a great thing // to present some more information. KBMapAccount *w; w = new KBMapAccount(this, bankId.toUtf8().constData(), accountId.toUtf8().constData()); if (w->exec() == QDialog::Accepted) { AB_ACCOUNT *a; a = w->getAccount(); assert(a); DBG_NOTICE(0, "Mapping application account \"%s\" to " "online account \"%s/%s\"", qPrintable(acc.name()), AB_Account_GetBankCode(a), AB_Account_GetAccountNumber(a)); // TODO remove the following line once we don't need backward compatibility setAccountAlias(a, acc.id().toUtf8().constData()); qDebug("Setup mapping to '%s'", acc.id().toUtf8().constData()); setAccountAlias(a, mappingId(acc).toUtf8().constData()); qDebug("Setup mapping to '%s'", mappingId(acc).toUtf8().constData()); delete w; return true; } delete w; return false; } QString KBankingExt::mappingId(const MyMoneyObject& object) const { QString id = MyMoneyFile::instance()->storageId() + QLatin1Char('-') + object.id(); // AqBanking does not handle the enclosing parens, so we remove it id.remove('{'); id.remove('}'); return id; } bool KBankingExt::interactiveImport() { AB_IMEXPORTER_CONTEXT *ctx; GWEN_DIALOG *dlg; int rv; ctx = AB_ImExporterContext_new(); dlg = AB_ImporterDialog_new(getCInterface(), ctx, NULL); if (dlg == NULL) { DBG_ERROR(0, "Could not create importer dialog."); AB_ImExporterContext_free(ctx); return false; } rv = GWEN_Gui_ExecDialog(dlg, 0); if (rv == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } if (!importContext(ctx, 0)) { DBG_ERROR(0, "Error on importContext"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return true; } const AB_ACCOUNT_STATUS* KBankingExt::_getAccountStatus(AB_IMEXPORTER_ACCOUNTINFO *ai) { const AB_ACCOUNT_STATUS *ast; const AB_ACCOUNT_STATUS *best; best = 0; ast = AB_ImExporterAccountInfo_GetFirstAccountStatus(ai); while (ast) { if (!best) best = ast; else { const GWEN_TIME *tiBest; const GWEN_TIME *ti; tiBest = AB_AccountStatus_GetTime(best); ti = AB_AccountStatus_GetTime(ast); if (!tiBest) { best = ast; } else { if (ti) { double d; /* we have two times, compare them */ d = GWEN_Time_Diff(ti, tiBest); if (d > 0) /* newer */ best = ast; } } } ast = AB_ImExporterAccountInfo_GetNextAccountStatus(ai); } /* while */ return best; } void KBankingExt::_xaToStatement(MyMoneyStatement &ks, const MyMoneyAccount& acc, const AB_TRANSACTION *t) { const GWEN_STRINGLIST *sl; QString s; QString memo; const char *p; const AB_VALUE *val; const GWEN_TIME *ti; const GWEN_TIME *startTime = 0; MyMoneyStatement::Transaction kt; unsigned long h; kt.m_fees = MyMoneyMoney(); // bank's transaction id p = AB_Transaction_GetFiId(t); if (p) kt.m_strBankID = QString("ID ") + QString::fromUtf8(p); // payee s.truncate(0); sl = AB_Transaction_GetRemoteName(t); if (sl) { GWEN_STRINGLISTENTRY *se; se = GWEN_StringList_FirstEntry(sl); while (se) { p = GWEN_StringListEntry_Data(se); assert(p); s += QString::fromUtf8(p); se = GWEN_StringListEntry_Next(se); } // while } kt.m_strPayee = s; // memo // The variable 's' contains the old method of extracting // the memo which added a linefeed after each part received // from AqBanking. The new variable 'memo' does not have // this inserted linefeed. We keep the variable 's' to // construct the hash-value to retrieve the reference s.truncate(0); sl = AB_Transaction_GetPurpose(t); if (sl) { GWEN_STRINGLISTENTRY *se; bool insertLineSep = false; se = GWEN_StringList_FirstEntry(sl); while (se) { p = GWEN_StringListEntry_Data(se); assert(p); if (insertLineSep) s += '\n'; insertLineSep = true; s += QString::fromUtf8(p).trimmed(); memo += QString::fromUtf8(p).trimmed(); se = GWEN_StringListEntry_Next(se); } // while // Sparda / Netbank hack: the software these banks use stores // parts of the payee name in the beginning of the purpose field // in case the payee name exceeds the 27 character limit. This is // the case, when one of the strings listed in m_sepaKeywords is part // of the purpose fields but does not start at the beginning. In this // case, the part leading up to the keyword is to be treated as the // tail of the payee. Also, a blank is inserted after the keyword. QSet::const_iterator itk; for (itk = m_sepaKeywords.constBegin(); itk != m_sepaKeywords.constEnd(); ++itk) { int idx = s.indexOf(*itk); if (idx >= 0) { if (idx > 0) { // re-add a possibly removed blank to name if (kt.m_strPayee.length() < 27) kt.m_strPayee += ' '; kt.m_strPayee += s.left(idx); s = s.mid(idx); } s = QString("%1 %2").arg(*itk).arg(s.mid((*itk).length())); // now do the same for 'memo' except for updating the payee idx = memo.indexOf(*itk); if (idx >= 0) { if (idx > 0) { memo = memo.mid(idx); } } memo = QString("%1 %2").arg(*itk).arg(memo.mid((*itk).length())); break; } } // in case we have some SEPA fields filled with information // we add them to the memo field p = AB_Transaction_GetEndToEndReference(t); if (p) { s += QString(", EREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("EREF: %1").arg(p)); } p = AB_Transaction_GetCustomerReference(t); if (p) { s += QString(", CREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CREF: %1").arg(p)); } p = AB_Transaction_GetMandateId(t); if (p) { s += QString(", MREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("MREF: %1").arg(p)); } p = AB_Transaction_GetCreditorSchemeId(t); if (p) { s += QString(", CRED: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CRED: %1").arg(p)); } p = AB_Transaction_GetOriginatorIdentifier(t); if (p) { s += QString(", DEBT: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("DEBT: %1").arg(p)); } } kt.m_strMemo = memo; // calculate the hash code and start with the payee info // and append the memo field h = MyMoneyTransaction::hash(kt.m_strPayee.trimmed()); h = MyMoneyTransaction::hash(s, h); // see, if we need to extract the payee from the memo field const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); QString rePayee = kvp.value("kbanking-payee-regexp"); if (!rePayee.isEmpty() && kt.m_strPayee.isEmpty()) { QString reMemo = kvp.value("kbanking-memo-regexp"); QStringList exceptions = kvp.value("kbanking-payee-exceptions").split(';', QString::SkipEmptyParts); bool needExtract = true; QStringList::const_iterator it_s; for (it_s = exceptions.constBegin(); needExtract && it_s != exceptions.constEnd(); ++it_s) { QRegExp exp(*it_s, Qt::CaseInsensitive); if (exp.indexIn(kt.m_strMemo) != -1) { needExtract = false; } } if (needExtract) { QRegExp expPayee(rePayee, Qt::CaseInsensitive); QRegExp expMemo(reMemo, Qt::CaseInsensitive); if (expPayee.indexIn(kt.m_strMemo) != -1) { kt.m_strPayee = expPayee.cap(1); if (expMemo.indexIn(kt.m_strMemo) != -1) { kt.m_strMemo = expMemo.cap(1); } } } } kt.m_strPayee = kt.m_strPayee.trimmed(); // date ti = AB_Transaction_GetDate(t); if (!ti) ti = AB_Transaction_GetValutaDate(t); if (ti) { int year, month, day; if (!startTime) startTime = ti; else { if (GWEN_Time_Diff(ti, startTime) < 0) startTime = ti; } if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) { kt.m_datePosted = QDate(year, month + 1, day); } } else { DBG_WARN(0, "No date for transaction"); } // value val = AB_Transaction_GetValue(t); if (val) { if (ks.m_strCurrency.isEmpty()) { p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } else { p = AB_Value_GetCurrency(val); if (p) s = p; if (ks.m_strCurrency.toLower() != s.toLower()) { // TODO: handle currency difference DBG_ERROR(0, "Mixed currencies currently not allowed"); } } kt.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val)); // The initial implementation of this feature was based on // a denominator of 100. Since the denominator might be // different nowadays, we make sure to use 100 for the // duplicate detection QString tmpVal = kt.m_amount.formatMoney(100, false); tmpVal.remove(QRegExp("[,\\.]")); tmpVal += QLatin1String("/100"); h = MyMoneyTransaction::hash(tmpVal, h); } else { DBG_WARN(0, "No value for transaction"); } if (startTime) { int year, month, day; if (!GWEN_Time_GetBrokenDownDate(startTime, &day, &month, &year)) { QDate d(year, month + 1, day); if (!ks.m_dateBegin.isValid()) ks.m_dateBegin = d; else if (d < ks.m_dateBegin) ks.m_dateBegin = d; if (!ks.m_dateEnd.isValid()) ks.m_dateEnd = d; else if (d > ks.m_dateEnd) ks.m_dateEnd = d; } } else { DBG_WARN(0, "No date in current transaction"); } // add information about remote account to memo in case we have something const char *remoteAcc = AB_Transaction_GetRemoteAccountNumber(t); const char *remoteBankCode = AB_Transaction_GetRemoteBankCode(t); if (remoteAcc && remoteBankCode) { kt.m_strMemo += QString("\n%1/%2").arg(remoteBankCode, remoteAcc); } // make hash value unique in case we don't have one already if (kt.m_strBankID.isEmpty()) { QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(kt.m_datePosted.toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); QMap::const_iterator it; it = m_hashMap.constFind(hash); if (it == m_hashMap.constEnd()) { m_hashMap[hash] = true; break; } ++idx; } kt.m_strBankID = QString("%1-%2").arg(acc.id()).arg(hash); } // store transaction ks.m_listTransactions += kt; } bool KBankingExt::importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t /*flags*/) { const char *p; DBG_INFO(0, "Importing account..."); // account number MyMoneyStatement ks; p = AB_ImExporterAccountInfo_GetAccountNumber(ai); if (p) { ks.m_strAccountNumber = m_parent->stripLeadingZeroes(p); } p = AB_ImExporterAccountInfo_GetBankCode(ai); if (p) { ks.m_strRoutingNumber = m_parent->stripLeadingZeroes(p); } MyMoneyAccount kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber)); ks.m_accountId = kacc.id(); // account name p = AB_ImExporterAccountInfo_GetAccountName(ai); if (p) ks.m_strAccountName = p; // account type switch (AB_ImExporterAccountInfo_GetType(ai)) { case AB_AccountType_Bank: ks.m_eType = eMyMoney::Statement::Type::Savings; break; case AB_AccountType_CreditCard: ks.m_eType = eMyMoney::Statement::Type::CreditCard; break; case AB_AccountType_Checking: ks.m_eType = eMyMoney::Statement::Type::Checkings; break; case AB_AccountType_Savings: ks.m_eType = eMyMoney::Statement::Type::Savings; break; case AB_AccountType_Investment: ks.m_eType = eMyMoney::Statement::Type::Investment; break; case AB_AccountType_Cash: ks.m_eType = eMyMoney::Statement::Type::None; break; default: ks.m_eType = eMyMoney::Statement::Type::None; } // account status const AB_ACCOUNT_STATUS* ast = _getAccountStatus(ai); if (ast) { const AB_BALANCE *bal; bal = AB_AccountStatus_GetBookedBalance(ast); if (!bal) bal = AB_AccountStatus_GetNotedBalance(ast); if (bal) { const AB_VALUE* val = AB_Balance_GetValue(bal); if (val) { DBG_INFO(0, "Importing balance"); ks.m_closingBalance = AB_Value_toMyMoneyMoney(val); p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } const GWEN_TIME* ti = AB_Balance_GetTime(bal); if (ti) { int year, month, day; if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) ks.m_dateEnd = QDate(year, month + 1, day); } else { DBG_WARN(0, "No time for balance"); } } else { DBG_WARN(0, "No account balance"); } } else { DBG_WARN(0, "No account status"); } // clear hash map m_hashMap.clear(); // get all transactions const AB_TRANSACTION* t = AB_ImExporterAccountInfo_GetFirstTransaction(ai); while (t) { _xaToStatement(ks, kacc, t); t = AB_ImExporterAccountInfo_GetNextTransaction(ai); } // import them if (!m_parent->importStatement(ks)) { if (KMessageBox::warningYesNo(0, i18n("Error importing statement. Do you want to continue?"), i18n("Critical Error")) == KMessageBox::No) { DBG_ERROR(0, "User aborted"); return false; } } return true; } K_PLUGIN_FACTORY_WITH_JSON(KBankingFactory, "kbanking.json", registerPlugin();) #include "kbanking.moc"