diff --git a/CMakeLists.txt b/CMakeLists.txt index 61ff6c2e1..bc1a2cac6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,367 +1,369 @@ # 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(PYTHON_VERSION_MAJOR GREATER_EQUAL 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") 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 a7853e0e3..9b23515ae 100644 --- a/kmymoney/CMakeLists.txt +++ b/kmymoney/CMakeLists.txt @@ -1,189 +1,190 @@ include(ECMAddAppIcon) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${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( 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 kmymoneyglobalsettings.h ) ########### settings code (kmm_config) STATIC ############### set( kmm_config_SRCS kmymoneyglobalsettings.cpp ) kconfig_add_kcfg_files( kmm_config_SRCS kmymoneysettings.kcfgc ) +kconfig_add_kcfg_files( kmm_config_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/plugins/forecast/forecastviewsettings.kcfgc ) add_library(kmm_config STATIC ${kmm_config_SRCS}) target_link_libraries(kmm_config KF5::WidgetsAddons KF5::ConfigWidgets Qt5::Sql Alkimia::alkimia) ########### 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_config) add_dependencies(wizardpages widgets) add_dependencies(dialogs widgets) if(USE_MODELTEST) set( kmymoney_common_LIBS ${kmymoney_common_LIBS} ${QT_QTTEST_LIBRARY}) endif(USE_MODELTEST) ########### 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_config 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 kmymoney.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) install(FILES kmymoney.upd DESTINATION ${KCONF_UPDATE_INSTALL_DIR} ) 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/dialogs/settings/CMakeLists.txt b/kmymoney/dialogs/settings/CMakeLists.txt index 9d8256353..56a078047 100644 --- a/kmymoney/dialogs/settings/CMakeLists.txt +++ b/kmymoney/dialogs/settings/CMakeLists.txt @@ -1,54 +1,52 @@ set (libsettings_a_SOURCES ksettingscolors.cpp ksettingsfonts.cpp ksettingsicons.cpp - ksettingsforecast.cpp ksettingsgeneral.cpp ksettingsgpg.cpp ksettingshome.cpp ksettingsonlinequotes.cpp ksettingsregister.cpp ksettingsschedules.cpp ksettingsreports.cpp ksettingskmymoney.cpp ksettingsplugins.cpp ) set (libsettings_a_UI ksettingscolors.ui ksettingsfonts.ui ksettingsicons.ui - ksettingsforecast.ui ksettingsgeneral.ui ksettingsgpg.ui ksettingshome.ui ksettingsonlinequotes.ui ksettingsregister.ui ksettingsschedules.ui ksettingsreports.ui ) ki18n_wrap_ui(libsettings_a_SOURCES ${libsettings_a_UI} ) add_library(settings STATIC ${libsettings_a_SOURCES}) # TODO: cleanup dependencies target_link_libraries(settings PUBLIC KF5::Completion KF5::KIOWidgets KF5::TextWidgets KF5::I18n KF5::IconThemes KF5::Completion KF5::KCMUtils KF5::ItemViews Alkimia::alkimia Qt5::Sql ) if (KF5Holidays_FOUND) target_link_libraries(settings PUBLIC KF5::Holidays) endif() add_dependencies(settings widgets kmm_config) diff --git a/kmymoney/dialogs/settings/ksettingskmymoney.cpp b/kmymoney/dialogs/settings/ksettingskmymoney.cpp index e97c3bb04..882ae4164 100644 --- a/kmymoney/dialogs/settings/ksettingskmymoney.cpp +++ b/kmymoney/dialogs/settings/ksettingskmymoney.cpp @@ -1,92 +1,89 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2016 Christian Dávid * (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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ksettingskmymoney.h" #include #include #include "ksettingsgeneral.h" #include "ksettingsregister.h" #include "ksettingsgpg.h" #include "ksettingscolors.h" #include "ksettingsfonts.h" #include "ksettingsicons.h" #include "ksettingsschedules.h" #include "ksettingsonlinequotes.h" #include "ksettingshome.h" -#include "ksettingsforecast.h" #include "ksettingsreports.h" #include "ksettingsplugins.h" #include "icons.h" using namespace Icons; KSettingsKMyMoney::KSettingsKMyMoney(QWidget *parent, const QString &name, KCoreConfigSkeleton *config) : KConfigDialog(parent, name, config) { // create the pages ... const auto generalPage = new KSettingsGeneral(); const auto registerPage = new KSettingsRegister(); const auto homePage = new KSettingsHome(); const auto schedulesPage = new KSettingsSchedules(); const auto encryptionPage = new KSettingsGpg(); const auto colorsPage = new KSettingsColors(); const auto fontsPage = new KSettingsFonts(); const auto iconsPage = new KSettingsIcons(); const auto onlineQuotesPage = new KSettingsOnlineQuotes(); - const auto forecastPage = new KSettingsForecast(); const auto reportsPage = new KSettingsReports(); const auto pluginsPage = new KSettingsPlugins(); addPage(generalPage, i18nc("General settings", "General"), Icons::get(Icon::SystemRun).name()); addPage(homePage, i18n("Home"), Icons::get(Icon::ViewHome).name()); addPage(registerPage, i18nc("Ledger view settings", "Ledger"), Icons::get(Icon::ViewFinancialList).name()); addPage(schedulesPage, i18n("Scheduled transactions"), Icons::get(Icon::ViewSchedules).name()); addPage(onlineQuotesPage, i18n("Online Quotes"), Icons::get(Icon::PreferencesNetwork).name()); addPage(reportsPage, i18nc("Report settings", "Reports"), Icons::get(Icon::ViewReports).name()); - addPage(forecastPage, i18nc("Forecast settings", "Forecast"), Icons::get(Icon::ViewForecast).name()); addPage(encryptionPage, i18n("Encryption"), Icons::get(Icon::Kgpg).name()); addPage(colorsPage, i18n("Colors"), Icons::get(Icon::PreferencesColor).name()); addPage(fontsPage, i18n("Fonts"), Icons::get(Icon::PreferencesFont).name()); addPage(iconsPage, i18n("Icons"), Icons::get(Icon::PreferencesIcon).name()); addPage(pluginsPage, i18n("Plugins"), Icons::get(Icon::NetworkDisconect).name(), QString(), false); setHelp("details.settings", "kmymoney"); connect(this, &KConfigDialog::rejected, schedulesPage, &KSettingsSchedules::slotResetRegion); connect(this, &KConfigDialog::rejected, iconsPage, &KSettingsIcons::slotResetTheme); connect(this, &KConfigDialog::settingsChanged, generalPage, &KSettingsGeneral::slotUpdateEquitiesVisibility); auto defaultButton = button(QDialogButtonBox::RestoreDefaults); auto applyButton = button(QDialogButtonBox::Apply); connect(this, &KConfigDialog::accepted, pluginsPage, &KSettingsPlugins::slotSavePluginConfiguration); connect(applyButton, &QPushButton::clicked, pluginsPage, &KSettingsPlugins::slotSavePluginConfiguration); connect(defaultButton, &QPushButton::clicked, pluginsPage, &KSettingsPlugins::slotResetToDefaults); connect(pluginsPage, &KSettingsPlugins::changed, this, &KSettingsKMyMoney::slotPluginsChanged); connect(pluginsPage, &KSettingsPlugins::settingsChanged, this, &KConfigDialog::settingsChanged); } void KSettingsKMyMoney::slotPluginsChanged(bool changed) { auto applyButton = button(QDialogButtonBox::Apply); applyButton->setEnabled(changed); } diff --git a/kmymoney/kmymoney.kcfg b/kmymoney/kmymoney.kcfg index 1cd41e752..083ae66e2 100644 --- a/kmymoney/kmymoney.kcfg +++ b/kmymoney/kmymoney.kcfg @@ -1,441 +1,395 @@ false 1 10 true true false false 10 0 20 10 0 60 false false 0 0 999 (None) 0 0 999 false false false false true true 0 0 false false false true true 0 true true false account,cashflow,payee,category,tag,memo,number,date,amount,state number,date,payee,category,tag,memo,payment,deposit,state 8,1,2,3,4,5,6,7,-9,10 1.00 true true false false false false false 255,255,0 152,251,152 255,0,0 0,0,255 255,242,155 255,255,221 false false false false false false false false true true true 0 0 11 1 1 28 QDateTime::fromString("1900-01-01T00:00:00", Qt::ISODate) false system 1,-9,-4 1,-4 1,-9,-4 0 4 false - - - - 0 - - - - 90 - 1 - 999 - - - - 30 - 1 - 999 - - - - 3 - 1 - 999 - - - - 0 - 0 - 31 - - - - 1 - - - - true - - - - true - - - - true - - Bar Banca,Banque Kreditkarte And. Kto. Eröffnungssaldo Rechnung false 10 0 100 2 0 256 2 0 2 diff --git a/kmymoney/kmymoneyglobalsettings.cpp b/kmymoney/kmymoneyglobalsettings.cpp index 4aadda286..0b93c5767 100644 --- a/kmymoney/kmymoneyglobalsettings.cpp +++ b/kmymoney/kmymoneyglobalsettings.cpp @@ -1,174 +1,185 @@ /*************************************************************************** kmymoneyglobalsettings.cpp ------------------- copyright : (C) 2006 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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 "kmymoneyglobalsettings.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyforecast.h" // include kmymoneysettings.cpp here to gain access to s_globalKMyMoneySettings #include "kmymoneysettings.cpp" +#include "forecastviewsettings.h" void KMyMoneyGlobalSettings::injectExternalSettings(KMyMoneySettings* p) { s_globalKMyMoneySettings()->q = p; } QFont KMyMoneyGlobalSettings::listCellFont() { if (useSystemFont()) { return QFontDatabase::systemFont(QFontDatabase::GeneralFont); } else { return KMyMoneySettings::listCellFont(); } } QFont KMyMoneyGlobalSettings::listHeaderFont() { if (useSystemFont()) { QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); font.setBold(true); return font; } else { return KMyMoneySettings::listHeaderFont(); } } QColor KMyMoneyGlobalSettings::schemeColor(const SchemeColor color) { switch(color) { case SchemeColor::ListBackground1: return KColorScheme (QPalette::Active, KColorScheme::View).background(KColorScheme::NormalBackground).color(); case SchemeColor::ListBackground2: return KColorScheme (QPalette::Active, KColorScheme::View).background(KColorScheme::AlternateBackground).color(); case SchemeColor::ListGrid: return KColorScheme (QPalette::Active, KColorScheme::View).foreground(KColorScheme::InactiveText).color(); case SchemeColor::ListHighlightText: return KColorScheme (QPalette::Active, KColorScheme::Selection).foreground(KColorScheme::NormalText).color(); case SchemeColor::ListHighlight: return KColorScheme (QPalette::Active, KColorScheme::Selection).background(KColorScheme::NormalBackground).color(); case SchemeColor::WindowText: return KColorScheme (QPalette::Active, KColorScheme::Window).foreground(KColorScheme::NormalText).color(); case SchemeColor::WindowBackground: return KColorScheme (QPalette::Active, KColorScheme::Window).background(KColorScheme::NormalBackground).color(); case SchemeColor::Positive: return KColorScheme (QPalette::Active, KColorScheme::View).foreground(KColorScheme::PositiveText).color(); case SchemeColor::Negative: return KColorScheme (QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); case SchemeColor::TransactionImported: if (useCustomColors()) return KMyMoneySettings::transactionImportedColor(); else return KColorScheme (QPalette::Active, KColorScheme::Complementary).background(KColorScheme::NeutralBackground).color(); case SchemeColor::TransactionMatched: if (useCustomColors()) return KMyMoneySettings::transactionMatchedColor(); else return KColorScheme (QPalette::Active, KColorScheme::Complementary).background(KColorScheme::PositiveBackground).color(); case SchemeColor::TransactionErroneous: if (useCustomColors()) return KMyMoneySettings::transactionErroneousColor(); else return KColorScheme (QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); case SchemeColor::FieldRequired: if (useCustomColors()) return KMyMoneySettings::fieldRequiredColor(); else return KColorScheme (QPalette::Active, KColorScheme::View).background(KColorScheme::NeutralBackground).color(); case SchemeColor::GroupMarker: if (useCustomColors()) return KMyMoneySettings::groupMarkerColor(); else return KColorScheme (QPalette::Active, KColorScheme::Selection).background(KColorScheme::LinkBackground).color(); case SchemeColor::MissingConversionRate: if (useCustomColors()) return KMyMoneySettings::missingConversionRateColor(); else return KColorScheme (QPalette::Active, KColorScheme::Complementary).foreground(KColorScheme::LinkText).color(); default: return QColor(); } } QStringList KMyMoneyGlobalSettings::itemList() { bool prevValue = self()->useDefaults(true); QStringList all = KMyMoneySettings::itemList().split(',', QString::SkipEmptyParts); self()->useDefaults(prevValue); QStringList list = KMyMoneySettings::itemList().split(',', QString::SkipEmptyParts); // now add all from 'all' that are missing in 'list' QRegExp exp("-?(\\d+)"); QStringList::iterator it_s; for (it_s = all.begin(); it_s != all.end(); ++it_s) { exp.indexIn(*it_s); if (!list.contains(exp.cap(1)) && !list.contains(QString("-%1").arg(exp.cap(1)))) { list << *it_s; } } return list; } int KMyMoneyGlobalSettings::firstFiscalMonth() { return KMyMoneySettings::fiscalYearBegin() + 1; } int KMyMoneyGlobalSettings::firstFiscalDay() { return KMyMoneySettings::fiscalYearBeginDay(); } QDate KMyMoneyGlobalSettings::firstFiscalDate() { QDate date = QDate(QDate::currentDate().year(), firstFiscalMonth(), firstFiscalDay()); if (date > QDate::currentDate()) date = date.addYears(-1); return date; } MyMoneyForecast KMyMoneyGlobalSettings::forecast() { MyMoneyForecast forecast; // override object defaults with those of the application - forecast.setForecastCycles(KMyMoneyGlobalSettings::forecastCycles()); - forecast.setAccountsCycle(KMyMoneyGlobalSettings::forecastAccountCycle()); + forecast.setForecastCycles(ForecastViewSettings::forecastCycles()); + forecast.setAccountsCycle(ForecastViewSettings::forecastAccountCycle()); forecast.setHistoryStartDate(QDate::currentDate().addDays(-forecast.forecastCycles()*forecast.accountsCycle())); forecast.setHistoryEndDate(QDate::currentDate().addDays(-1)); - forecast.setForecastDays(KMyMoneyGlobalSettings::forecastDays()); - forecast.setBeginForecastDay(KMyMoneyGlobalSettings::beginForecastDay()); - forecast.setForecastMethod(KMyMoneyGlobalSettings::forecastMethod()); - forecast.setHistoryMethod(KMyMoneyGlobalSettings::historyMethod()); - forecast.setIncludeFutureTransactions(KMyMoneyGlobalSettings::includeFutureTransactions()); - forecast.setIncludeScheduledTransactions(KMyMoneyGlobalSettings::includeScheduledTransactions()); + forecast.setForecastDays(ForecastViewSettings::forecastDays()); + forecast.setBeginForecastDay(ForecastViewSettings::beginForecastDay()); + forecast.setForecastMethod(ForecastViewSettings::forecastMethod()); + forecast.setHistoryMethod(ForecastViewSettings::historyMethod()); + forecast.setIncludeFutureTransactions(ForecastViewSettings::includeFutureTransactions()); + forecast.setIncludeScheduledTransactions(ForecastViewSettings::includeScheduledTransactions()); return forecast; } + +int KMyMoneyGlobalSettings::forecastDays() +{ + return ForecastViewSettings::forecastDays(); +} + +int KMyMoneyGlobalSettings::forecastAccountCycle() +{ + return ForecastViewSettings::forecastAccountCycle(); +} diff --git a/kmymoney/kmymoneyglobalsettings.h b/kmymoney/kmymoneyglobalsettings.h index 03574c2b7..5bf0bc76d 100644 --- a/kmymoney/kmymoneyglobalsettings.h +++ b/kmymoney/kmymoneyglobalsettings.h @@ -1,82 +1,84 @@ /*************************************************************************** kmymoneyglobalsettings.h ------------------- copyright : (C) 2006 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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 KMYMONEYGLOBALSETTINGS_H #define KMYMONEYGLOBALSETTINGS_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneysettings.h" class MyMoneyForecast; enum class SchemeColor { ListBackground1, ListBackground2, ListGrid, ListHighlightText, ListHighlight, WindowText, WindowBackground, Positive, Negative, TransactionImported, TransactionMatched, TransactionErroneous, FieldRequired, GroupMarker, MissingConversionRate }; class KMyMoneyGlobalSettings : public KMyMoneySettings { public: static void injectExternalSettings(KMyMoneySettings* p); static QColor schemeColor(const SchemeColor color); static QFont listCellFont(); static QFont listHeaderFont(); static QStringList itemList(); /** * returns the number of the first month in the fiscal year */ static int firstFiscalMonth(); /** * returns the number of the first day of the fiscal year */ static int firstFiscalDay(); /** * returns the date of the first day in the current fiscal year */ static QDate firstFiscalDate(); /** * Construct a MyMoneyForecast object setup with all KMyMoneySettings */ static MyMoneyForecast forecast(); + static int forecastDays(); + static int forecastAccountCycle(); }; #endif diff --git a/kmymoney/plugins/CMakeLists.txt b/kmymoney/plugins/CMakeLists.txt index fe0c6c9fe..db07e5d86 100644 --- a/kmymoney/plugins/CMakeLists.txt +++ b/kmymoney/plugins/CMakeLists.txt @@ -1,82 +1,86 @@ add_subdirectory( onlinetasks ) add_subdirectory( ibanbicdata ) add_subdirectory( interfaces ) add_subdirectory( csv ) add_subdirectory( qif ) add_subdirectory( gnc ) add_subdirectory( ofx ) add_subdirectory( icalendar ) add_subdirectory( reconciliationreport ) add_subdirectory( checkprinting ) +if(ENABLE_FORECASTVIEW) + add_subdirectory(forecast) +endif() + if(ENABLE_SQLSTORAGE) add_subdirectory(sql) endif() if (KBANKING_FOUND) add_subdirectory( kbanking ) endif (KBANKING_FOUND) option(ENABLE_SQLCIPHER "Enable SQLCipher plugin" OFF) if (ENABLE_SQLCIPHER) add_subdirectory(sqlcipher) endif(ENABLE_SQLCIPHER) option(ENABLE_ONLINEJOBPLUGINMOCKUP "Enable onlineJob-plugin mockup (only for developers)" OFF) if (ENABLE_ONLINEJOBPLUGINMOCKUP) add_subdirectory(onlinejobpluginmockup) endif() if (ENABLE_WEBOOB) add_subdirectory( weboob ) endif () ########### next target ############### set(kmm_plugin_LIB_SRCS appinterface.cpp importinterface.cpp kmymoneyplugin.cpp statementinterface.cpp viewinterface.cpp onlinepluginextended.cpp interfaceloader.cpp ) set(plugins_HEADERS appinterface.h importinterface.h kmymoneyplugin.h statementinterface.h viewinterface.h ${CMAKE_CURRENT_BINARY_DIR}/kmm_plugin_export.h onlinepluginextended.h ) add_library(kmm_plugin SHARED ${kmm_plugin_LIB_SRCS}) generate_export_header(kmm_plugin BASE_NAME kmm_plugin) target_link_libraries(kmm_plugin PUBLIC KF5::XmlGui KF5::KCMUtils KF5::KIOWidgets Qt5::Gui Qt5::Widgets kmm_mymoney ) set_target_properties(kmm_plugin PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) ########### install files ############### install(TARGETS kmm_plugin ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES ${plugins_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) install(FILES kmymoney-payeeidentifierdelegate.desktop kmymoney-importerplugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) diff --git a/kmymoney/plugins/forecast/CMakeLists.txt b/kmymoney/plugins/forecast/CMakeLists.txt new file mode 100644 index 000000000..f8e69b9af --- /dev/null +++ b/kmymoney/plugins/forecast/CMakeLists.txt @@ -0,0 +1,51 @@ +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/forecastview.json.in ${CMAKE_CURRENT_BINARY_DIR}/forecastview.json @ONLY) + +set(forecastview_SOURCES + forecastview.cpp + kforecastview.cpp + fixedcolumntreeview.cpp + ) + +ki18n_wrap_ui(forecastview_SOURCES kforecastview.ui) +kconfig_add_kcfg_files(forecastview_SOURCES forecastviewsettings.kcfgc) + +add_library(forecastview MODULE ${forecastview_SOURCES} ) + +target_link_libraries(forecastview + PUBLIC + kmm_plugin + KF5::TextWidgets + reports + viewbase +) + +install(TARGETS forecastview + DESTINATION "${KDE_INSTALL_PLUGINDIR}/kmymoney") + +# the KCM module + +set(kcm_forecastview_PART_SRCS + kcm_forecastview.cpp + ) + +kconfig_add_kcfg_files(kcm_forecastview_PART_SRCS forecastviewsettings.kcfgc) + +ki18n_wrap_ui(kcm_forecastview_PART_SRCS forecastviewsettings.ui) + +add_library(kcm_forecastview MODULE ${kcm_forecastview_PART_SRCS}) + +kcoreaddons_desktop_to_json(kcm_forecastview kcm_forecastview.desktop) + +target_link_libraries(kcm_forecastview + KF5::I18n + KF5::ConfigWidgets + KF5::Completion + KF5::KIOWidgets + KF5::CoreAddons + ) + +install(TARGETS kcm_forecastview + DESTINATION "${KDE_INSTALL_PLUGINDIR}/kmymoney") + +install(FILES kcm_forecastview.desktop + DESTINATION "${SERVICES_INSTALL_DIR}") diff --git a/kmymoney/widgets/fixedcolumntreeview.cpp b/kmymoney/plugins/forecast/fixedcolumntreeview.cpp similarity index 100% rename from kmymoney/widgets/fixedcolumntreeview.cpp rename to kmymoney/plugins/forecast/fixedcolumntreeview.cpp diff --git a/kmymoney/widgets/fixedcolumntreeview.h b/kmymoney/plugins/forecast/fixedcolumntreeview.h similarity index 100% rename from kmymoney/widgets/fixedcolumntreeview.h rename to kmymoney/plugins/forecast/fixedcolumntreeview.h diff --git a/kmymoney/plugins/forecast/forecastview.cpp b/kmymoney/plugins/forecast/forecastview.cpp new file mode 100644 index 000000000..29262255e --- /dev/null +++ b/kmymoney/plugins/forecast/forecastview.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + forecastview.cpp + ------------------- + + copyright : (C) 2018 by Łukasz Wojniłowicz + email : lukasz.wojnilowicz@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "forecastview.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "viewinterface.h" +#include "kforecastview.h" +#include "kmymoneyglobalsettings.h" + +ForecastView::ForecastView(QObject *parent, const QVariantList &args) : + KMyMoneyPlugin::Plugin(parent, "forecastview"/*must be the same as X-KDE-PluginInfo-Name*/) +{ + Q_UNUSED(args) + setComponentName("forecastview", i18n("Forecast view")); + // For information, announce that we have been loaded. + qDebug("Plugins: forecastview loaded"); +} + +ForecastView::~ForecastView() +{ + qDebug("Plugins: forecastview unloaded"); +} + +void ForecastView::injectExternalSettings(KMyMoneySettings* p) +{ + KMyMoneyGlobalSettings::injectExternalSettings(p); +} + +void ForecastView::plug() +{ + m_view = new KForecastView; + viewInterface()->addView(m_view, i18n("Forecast"), View::Forecast); +} + +void ForecastView::unplug() +{ + viewInterface()->removeView(View::Forecast); +} + +K_PLUGIN_FACTORY_WITH_JSON(ForecastViewFactory, "forecastview.json", registerPlugin();) + +#include "forecastview.moc" diff --git a/kmymoney/dialogs/settings/ksettingsforecast.h b/kmymoney/plugins/forecast/forecastview.h similarity index 63% copy from kmymoney/dialogs/settings/ksettingsforecast.h copy to kmymoney/plugins/forecast/forecastview.h index fd96f9594..6a5e486d0 100644 --- a/kmymoney/dialogs/settings/ksettingsforecast.h +++ b/kmymoney/plugins/forecast/forecastview.h @@ -1,47 +1,49 @@ /*************************************************************************** - ksettingsforecast.h + forecastview.h ------------------- - copyright : (C) 2007 by Alvaro Soliverez - email : asoliverez@gmail.com - (C) 2017 by Łukasz Wojniłowicz + copyright : (C) 2018 by Łukasz Wojniłowicz + email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ -#ifndef KSETTINGSFORECAST_H -#define KSETTINGSFORECAST_H - -// ---------------------------------------------------------------------------- -// QT Includes - -#include +#ifndef FORECASTVIEW_H +#define FORECASTVIEW_H // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- +// QT Includes + // Project Includes -namespace Ui { class KSettingsForecast; } +#include "kmymoneyplugin.h" -class KSettingsForecast : public QWidget +class KForecastView; + +class ForecastView : public KMyMoneyPlugin::Plugin { Q_OBJECT - Q_DISABLE_COPY(KSettingsForecast) public: - explicit KSettingsForecast(QWidget* parent = nullptr); - ~KSettingsForecast(); + explicit ForecastView(QObject *parent, const QVariantList &args); + ~ForecastView() override; + + void plug() override; + void unplug() override; + + void injectExternalSettings(KMyMoneySettings* p) override; private: - Ui::KSettingsForecast *ui; + KForecastView* m_view; }; -#endif +#endif diff --git a/kmymoney/plugins/forecast/forecastview.json.in b/kmymoney/plugins/forecast/forecastview.json.in new file mode 100644 index 000000000..bb0df8a17 --- /dev/null +++ b/kmymoney/plugins/forecast/forecastview.json.in @@ -0,0 +1,21 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "asoliverez@gmail.com,lukasz.wojnilowicz@gmail.com", + "Name": "Alvaro Soliverez,Łukasz Wojniłowicz" + } + ], + "Description": "Adds forecast view to KMyMoney", + "EnabledByDefault": true, + "Icon": "forecast", + "Id": "forecastview", + "License": "GPL", + "Name": "Forecast view", + "ServiceTypes": [ + "KMyMoney/Plugin" + ], + "Version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@", + "Website": "https://kmymoney.org/plugins.html" + } +} diff --git a/kmymoney/plugins/forecast/forecastviewsettings.kcfg b/kmymoney/plugins/forecast/forecastviewsettings.kcfg new file mode 100644 index 000000000..c925dbe5d --- /dev/null +++ b/kmymoney/plugins/forecast/forecastviewsettings.kcfg @@ -0,0 +1,54 @@ + + + + + + + + 0 + + + + 90 + 1 + 999 + + + + 30 + 1 + 999 + + + + 3 + 1 + 999 + + + + 0 + 0 + 31 + + + + 1 + + + + true + + + + true + + + + true + + + diff --git a/kmymoney/plugins/forecast/forecastviewsettings.kcfgc b/kmymoney/plugins/forecast/forecastviewsettings.kcfgc new file mode 100644 index 000000000..cfcd67f63 --- /dev/null +++ b/kmymoney/plugins/forecast/forecastviewsettings.kcfgc @@ -0,0 +1,6 @@ +# Code generation options for kconfig_compiler +File=forecastviewsettings.kcfg +ClassName=ForecastViewSettings +Singleton=true +Mutators=true +# will create the necessary code for setting those variables diff --git a/kmymoney/dialogs/settings/ksettingsforecast.ui b/kmymoney/plugins/forecast/forecastviewsettings.ui similarity index 98% rename from kmymoney/dialogs/settings/ksettingsforecast.ui rename to kmymoney/plugins/forecast/forecastviewsettings.ui index c71b62f22..de6ab6b68 100644 --- a/kmymoney/dialogs/settings/ksettingsforecast.ui +++ b/kmymoney/plugins/forecast/forecastviewsettings.ui @@ -1,214 +1,214 @@ - KSettingsForecast - + ForecastViewSettings + 0 0 497 379 Forecast Settings 5 5 0 0 Number of Days of Account Cycle: false Day of Month to start Forecast: false Number of Days to Forecast: false 133 21 QSizePolicy::Expanding Qt::Horizontal Forecast Method Scheduled and Future Transactions History-based History-based settings Number of Cycles to use in Forecast: false 130 21 QSizePolicy::Expanding Qt::Horizontal History Forecast Method Simple Moving Average Weighted Moving Average Linear Regression 20 40 QSizePolicy::Expanding Qt::Vertical diff --git a/kmymoney/plugins/forecast/kcm_forecastview.cpp b/kmymoney/plugins/forecast/kcm_forecastview.cpp new file mode 100644 index 000000000..ebd3b9d58 --- /dev/null +++ b/kmymoney/plugins/forecast/kcm_forecastview.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright 2018 Łukasz Wojniłowicz lukasz.wojnilowicz@gmail.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License or (at your option) version 3 or any later version * + * accepted by the membership of KDE e.V. (or its successor approved * + * by the membership of KDE e.V.), which shall act as a proxy * + * defined in Section 14 of version 3 of the license. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see * + ***************************************************************************/ + +#include "kcm_forecastview.h" +#include + +// KDE includes +#include +#include +#include "forecastviewsettings.h" + +ForecastViewSettingsWidget::ForecastViewSettingsWidget(QWidget* parent) : + QWidget(parent) +{ + setupUi(this); +} + +KCMForecastView::KCMForecastView(QWidget *parent, const QVariantList& args) + : KCModule(parent, args) +{ + ForecastViewSettingsWidget* w = new ForecastViewSettingsWidget(this); + addConfig(ForecastViewSettings::self(), w); + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + layout->addWidget(w); + setButtons(NoAdditionalButton); + load(); +} + +KCMForecastView::~KCMForecastView() +{ +} + +K_PLUGIN_FACTORY_WITH_JSON(KCMForecastViewFactory, "kcm_forecastview.json", registerPlugin();) + +#include "kcm_forecastview.moc" diff --git a/kmymoney/plugins/forecast/kcm_forecastview.desktop b/kmymoney/plugins/forecast/kcm_forecastview.desktop new file mode 100644 index 000000000..1618846cb --- /dev/null +++ b/kmymoney/plugins/forecast/kcm_forecastview.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Name=Forecast view configuration +Name[x-test]=xxForecast view configurationxx +Icon=forecast +Type=Service +ServiceTypes=KCModule +Exec=kcmshell5 forecastview +Categories=Qt;KDE;kmymoney; +Keywords=forecastview;kmymoney; +Keywords[x-test]=xxforecastviewxx;xxkmymoneyxx; +X-KDE-Library=kmymoney/kcm_forecastview +X-KDE-ParentApp=forecastview +X-KDE-ParentComponents=forecastview +Comment=Configuration for forecast view plugin +Comment[x-test]=xxConfiguration for forecast view pluginxx diff --git a/kmymoney/plugins/forecast/kcm_forecastview.h b/kmymoney/plugins/forecast/kcm_forecastview.h new file mode 100644 index 000000000..024ff59a4 --- /dev/null +++ b/kmymoney/plugins/forecast/kcm_forecastview.h @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright 2018 Łukasz Wojniłowicz lukasz.wojnilowicz@gmail.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License or (at your option) version 3 or any later version * + * accepted by the membership of KDE e.V. (or its successor approved * + * by the membership of KDE e.V.), which shall act as a proxy * + * defined in Section 14 of version 3 of the license. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see * + ***************************************************************************/ +#ifndef KCM_FORECASTVIEW_H +#define KCM_FORECASTVIEW_H + +#include +#include +#include "ui_forecastviewsettings.h" + +class ForecastViewSettingsWidget : public QWidget, public Ui::ForecastViewSettings +{ + Q_OBJECT +public: + explicit ForecastViewSettingsWidget(QWidget* parent = nullptr); +}; + +class KCMForecastView : public KCModule +{ +public: + explicit KCMForecastView(QWidget* parent, const QVariantList& args); + ~KCMForecastView(); +}; + +#endif + diff --git a/kmymoney/views/kforecastview.cpp b/kmymoney/plugins/forecast/kforecastview.cpp similarity index 100% rename from kmymoney/views/kforecastview.cpp rename to kmymoney/plugins/forecast/kforecastview.cpp diff --git a/kmymoney/views/kforecastview.h b/kmymoney/plugins/forecast/kforecastview.h similarity index 97% rename from kmymoney/views/kforecastview.h rename to kmymoney/plugins/forecast/kforecastview.h index 08300ae2c..514195e4d 100644 --- a/kmymoney/views/kforecastview.h +++ b/kmymoney/plugins/forecast/kforecastview.h @@ -1,71 +1,71 @@ /*************************************************************************** kforecastview.h ------------------- copyright : (C) 2007 by Alvaro Soliverez email : asoliverez@gmail.com (C) 2017 Ł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 KFORECASTVIEW_H #define KFORECASTVIEW_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyviewbase.h" class QTreeWidgetItem; namespace reports { class KReportChartView; } class FixedColumnTreeView; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyForecast; class MyMoneyPrice; /** * @author Alvaro Soliverez * * This class implements the forecast 'view'. */ class KForecastViewPrivate; class KForecastView : public KMyMoneyViewBase { Q_OBJECT public: - explicit KForecastView(QWidget *parent = 0); + explicit KForecastView(QWidget *parent = nullptr); ~KForecastView() override; void setDefaultFocus() override; void refresh() override; protected: void showEvent(QShowEvent* event) override; private: Q_DECLARE_PRIVATE(KForecastView) private Q_SLOTS: void slotTabChanged(int index); void slotManualForecast(); void itemExpanded(QTreeWidgetItem *item); void itemCollapsed(QTreeWidgetItem *item); }; #endif diff --git a/kmymoney/views/kforecastview.ui b/kmymoney/plugins/forecast/kforecastview.ui similarity index 100% rename from kmymoney/views/kforecastview.ui rename to kmymoney/plugins/forecast/kforecastview.ui diff --git a/kmymoney/views/kforecastview_p.h b/kmymoney/plugins/forecast/kforecastview_p.h similarity index 98% rename from kmymoney/views/kforecastview_p.h rename to kmymoney/plugins/forecast/kforecastview_p.h index 313f160d5..c9b7ae357 100644 --- a/kmymoney/views/kforecastview_p.h +++ b/kmymoney/plugins/forecast/kforecastview_p.h @@ -1,1048 +1,1049 @@ /*************************************************************************** kforecastview.cpp ------------------- copyright : (C) 2007 by Alvaro Soliverez email : asoliverez@gmail.com (C) 2017 Ł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 KFORECASTVIEW_P_H #define KFORECASTVIEW_P_H #include "kforecastview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kforecastview.h" +#include "forecastviewsettings.h" #include "kmymoneyviewbase_p.h" #include "mymoneymoney.h" #include "mymoneyforecast.h" #include "mymoneyprice.h" #include "mymoneyutils.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyexception.h" #include "mymoneysecurity.h" #include "kmymoneyglobalsettings.h" #include "mymoneybudget.h" #include "pivottable.h" #include "fixedcolumntreeview.h" #include "kreportchartview.h" #include "reportaccount.h" #include "icons.h" #include "mymoneyenums.h" using namespace reports; using namespace Icons; typedef enum { SummaryView = 0, ListView, AdvancedView, BudgetView, ChartView, // insert new values above this line MaxViewTabs } ForecastViewTab; enum ForecastViewRoles { ForecastRole = Qt::UserRole, /**< The forecast is held in this role.*/ AccountRole = Qt::UserRole + 1, /**< The MyMoneyAccount is stored in this role in column 0.*/ AmountRole = Qt::UserRole + 2, /**< The amount.*/ ValueRole = Qt::UserRole + 3, /**< The value.*/ }; enum EForecastViewType { eSummary = 0, eDetailed, eAdvanced, eBudget, eUndefined }; class KForecastViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KForecastView) public: explicit KForecastViewPrivate(KForecastView *qq) : KMyMoneyViewBasePrivate(), q_ptr(qq), ui(new Ui::KForecastView), m_needLoad(true), m_totalItem(0), m_assetItem(0), m_liabilityItem(0), m_incomeItem(0), m_expenseItem(0), m_chartLayout(0), m_forecastChart(0) { } ~KForecastViewPrivate() { delete ui; } void init() { Q_Q(KForecastView); m_needLoad = false; ui->setupUi(q); m_forecastChart = new KReportChartView(ui->m_tabChart); for (int i = 0; i < MaxViewTabs; ++i) m_needReload[i] = false; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Last Use Settings"); ui->m_tab->setCurrentIndex(grp.readEntry("KForecastView_LastType", 0)); ui->m_forecastButton->setIcon(Icons::get(Icon::ViewForecast)); q->connect(ui->m_tab, &QTabWidget::currentChanged, q, &KForecastView::slotTabChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KForecastView::refresh); q->connect(ui->m_forecastButton, &QAbstractButton::clicked, q, &KForecastView::slotManualForecast); ui->m_forecastList->setUniformRowHeights(true); ui->m_forecastList->setAllColumnsShowFocus(true); ui->m_summaryList->setAllColumnsShowFocus(true); ui->m_budgetList->setAllColumnsShowFocus(true); ui->m_advancedList->setAlternatingRowColors(true); q->connect(ui->m_forecastList, &QTreeWidget::itemExpanded, q, &KForecastView::itemExpanded); q->connect(ui->m_forecastList, &QTreeWidget::itemCollapsed, q, &KForecastView::itemCollapsed); q->connect(ui->m_summaryList, &QTreeWidget::itemExpanded, q, &KForecastView::itemExpanded); q->connect(ui->m_summaryList, &QTreeWidget::itemCollapsed, q, &KForecastView::itemCollapsed); q->connect(ui->m_budgetList, &QTreeWidget::itemExpanded, q, &KForecastView::itemExpanded); q->connect(ui->m_budgetList, &QTreeWidget::itemCollapsed, q, &KForecastView::itemCollapsed); m_forecastChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_chartLayout = ui->m_tabChart->layout(); m_chartLayout->setSpacing(6); m_chartLayout->addWidget(m_forecastChart); loadForecastSettings(); } void loadForecast(ForecastViewTab tab) { if (m_needReload[tab]) { switch (tab) { case ListView: loadListView(); break; case SummaryView: loadSummaryView(); break; case AdvancedView: loadAdvancedView(); break; case BudgetView: loadBudgetView(); break; case ChartView: loadChartView(); break; default: break; } m_needReload[tab] = false; } } void loadListView() { MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); const auto file = MyMoneyFile::instance(); //get the settings from current page forecast.setForecastDays(ui->m_forecastDays->value()); forecast.setAccountsCycle(ui->m_accountsCycle->value()); forecast.setBeginForecastDay(ui->m_beginDay->value()); forecast.setForecastCycles(ui->m_forecastCycles->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); forecast.doForecast(); ui->m_forecastList->clear(); ui->m_forecastList->setColumnCount(0); ui->m_forecastList->setIconSize(QSize(22, 22)); ui->m_forecastList->setSortingEnabled(true); ui->m_forecastList->sortByColumn(0, Qt::AscendingOrder); //add columns QStringList headerLabels; headerLabels << i18n("Account"); //add cycle interval columns headerLabels << i18nc("Today's forecast", "Current"); for (int i = 1; i <= forecast.forecastDays(); ++i) { QDate forecastDate = QDate::currentDate().addDays(i); headerLabels << QLocale().toString(forecastDate, QLocale::LongFormat); } //add variation columns headerLabels << i18n("Total variation"); //set the columns ui->m_forecastList->setHeaderLabels(headerLabels); //add default rows addTotalRow(ui->m_forecastList, forecast); addAssetLiabilityRows(forecast); //load asset and liability forecast accounts loadAccounts(forecast, file->asset(), m_assetItem, eDetailed); loadAccounts(forecast, file->liability(), m_liabilityItem, eDetailed); adjustHeadersAndResizeToContents(ui->m_forecastList); // add the fixed column only if the horizontal scroll bar is visible m_fixedColumnView.reset(ui->m_forecastList->horizontalScrollBar()->isVisible() ? new FixedColumnTreeView(ui->m_forecastList) : 0); } void loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, QTreeWidgetItem* parentItem, int forecastType) { QMap nameIdx; const auto file = MyMoneyFile::instance(); QTreeWidgetItem *forecastItem = 0; //Get all accounts of the right type to calculate forecast const auto accList = account.accountList(); if (accList.isEmpty()) return; foreach (const auto sAccount, accList) { auto subAccount = file->account(sAccount); //only add the account if it is a forecast account or the parent of a forecast account if (includeAccount(forecast, subAccount)) { nameIdx[subAccount.id()] = subAccount.id(); } } QMap::ConstIterator it_nc; for (it_nc = nameIdx.constBegin(); it_nc != nameIdx.constEnd(); ++it_nc) { const MyMoneyAccount subAccount = file->account(*it_nc); MyMoneySecurity currency; if (subAccount.isInvest()) { MyMoneySecurity underSecurity = file->security(subAccount.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(subAccount.currencyId()); } forecastItem = new QTreeWidgetItem(parentItem); forecastItem->setText(0, subAccount.name()); forecastItem->setIcon(0, subAccount.accountPixmap()); forecastItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); forecastItem->setData(0, AccountRole, QVariant::fromValue(subAccount)); forecastItem->setExpanded(true); switch (forecastType) { case eSummary: updateSummary(forecastItem); break; case eDetailed: updateDetailed(forecastItem); break; case EForecastViewType::eBudget: updateBudget(forecastItem); break; default: break; } loadAccounts(forecast, subAccount, forecastItem, forecastType); } } void loadSummaryView() { MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); QList accList; int dropMinimum; int dropZero; const auto file = MyMoneyFile::instance(); //get the settings from current page forecast.setForecastDays(ui->m_forecastDays->value()); forecast.setAccountsCycle(ui->m_accountsCycle->value()); forecast.setBeginForecastDay(ui->m_beginDay->value()); forecast.setForecastCycles(ui->m_forecastCycles->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); forecast.doForecast(); //add columns QStringList headerLabels; headerLabels << i18n("Account"); headerLabels << i18nc("Today's forecast", "Current"); //if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle int daysToBeginDay; if (QDate::currentDate() < forecast.beginForecastDate()) { daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); } else { daysToBeginDay = forecast.accountsCycle(); } for (int i = 0; ((i*forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { int intervalDays = ((i * forecast.accountsCycle()) + daysToBeginDay); headerLabels << i18np("1 day", "%1 days", intervalDays); } //add variation columns headerLabels << i18n("Total variation"); ui->m_summaryList->clear(); //set the columns ui->m_summaryList->setHeaderLabels(headerLabels); ui->m_summaryList->setIconSize(QSize(22, 22)); ui->m_summaryList->setSortingEnabled(true); ui->m_summaryList->sortByColumn(0, Qt::AscendingOrder); //add default rows addTotalRow(ui->m_summaryList, forecast); addAssetLiabilityRows(forecast); loadAccounts(forecast, file->asset(), m_assetItem, eSummary); loadAccounts(forecast, file->liability(), m_liabilityItem, eSummary); adjustHeadersAndResizeToContents(ui->m_summaryList); //Add comments to the advice list ui->m_adviceText->clear(); //Get all accounts of the right type to calculate forecast m_nameIdx.clear(); accList = forecast.accountList(); QList::const_iterator accList_t = accList.constBegin(); for (; accList_t != accList.constEnd(); ++accList_t) { MyMoneyAccount acc = *accList_t; if (m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there m_nameIdx[acc.id()] = acc.id(); } } QMap::ConstIterator it_nc; for (it_nc = m_nameIdx.constBegin(); it_nc != m_nameIdx.constEnd(); ++it_nc) { const MyMoneyAccount& acc = file->account(*it_nc); MyMoneySecurity currency; //change currency to deep currency if account is an investment if (acc.isInvest()) { MyMoneySecurity underSecurity = file->security(acc.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(acc.currencyId()); } //Check if the account is going to be below zero or below the minimal balance in the forecast period QString minimumBalance = acc.value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); //Check if the account is going to be below minimal balance dropMinimum = forecast.daysToMinimumBalance(acc); //Check if the account is going to be below zero in the future dropZero = forecast.daysToZeroBalance(acc); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if (dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch (dropMinimum) { case -1: break; case 0: msg = QString("").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name()); msg += i18n("The balance of %1 is below the minimum balance %2 today.", acc.name(), MyMoneyUtils::formatMoney(minBalance, acc, currency)); msg += QString(""); break; default: msg = QString("").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name()); msg += i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", "The balance of %2 will drop below the minimum balance %3 in %1 days.", dropMinimum - 1, acc.name(), MyMoneyUtils::formatMoney(minBalance, acc, currency)); msg += QString(""); } if (!msg.isEmpty()) { ui->m_adviceText->append(msg); } } // a drop below zero is always shown msg.clear(); switch (dropZero) { case -1: break; case 0: if (acc.accountGroup() == eMyMoney::Account::Type::Asset) { msg = QString("").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name()); msg += i18n("The balance of %1 is below %2 today.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); msg += QString(""); break; } if (acc.accountGroup() == eMyMoney::Account::Type::Liability) { msg = i18n("The balance of %1 is above %2 today.", acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); break; } break; default: if (acc.accountGroup() == eMyMoney::Account::Type::Asset) { msg = QString("").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name()); msg += i18np("The balance of %2 will drop below %3 in %1 day.", "The balance of %2 will drop below %3 in %1 days.", dropZero, acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); msg += QString(""); break; } if (acc.accountGroup() == eMyMoney::Account::Type::Liability) { msg = i18np("The balance of %2 will raise above %3 in %1 day.", "The balance of %2 will raise above %3 in %1 days.", dropZero, acc.name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), acc, currency)); break; } } if (!msg.isEmpty()) { ui->m_adviceText->append(msg); } //advice about trends msg.clear(); MyMoneyMoney accCycleVariation = forecast.accountCycleVariation(acc); if (accCycleVariation < MyMoneyMoney()) { msg = QString("").arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name()); msg += i18n("The account %1 is decreasing %2 per cycle.", acc.name(), MyMoneyUtils::formatMoney(accCycleVariation, acc, currency)); msg += QString(""); } if (!msg.isEmpty()) { ui->m_adviceText->append(msg); } } ui->m_adviceText->show(); } void loadAdvancedView() { const auto file = MyMoneyFile::instance(); QList accList; MyMoneySecurity baseCurrency = file->baseCurrency(); MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); int daysToBeginDay; //get the settings from current page forecast.setForecastDays(ui->m_forecastDays->value()); forecast.setAccountsCycle(ui->m_accountsCycle->value()); forecast.setBeginForecastDay(ui->m_beginDay->value()); forecast.setForecastCycles(ui->m_forecastCycles->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); forecast.doForecast(); //Get all accounts of the right type to calculate forecast m_nameIdx.clear(); accList = forecast.accountList(); QList::const_iterator accList_t = accList.constBegin(); for (; accList_t != accList.constEnd(); ++accList_t) { MyMoneyAccount acc = *accList_t; if (m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there m_nameIdx[acc.id()] = acc.id(); } } //clear the list, including columns ui->m_advancedList->clear(); ui->m_advancedList->setColumnCount(0); ui->m_advancedList->setIconSize(QSize(22, 22)); QStringList headerLabels; //add first column of both lists headerLabels << i18n("Account"); //if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle if (QDate::currentDate() < forecast.beginForecastDate()) { daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); } else { daysToBeginDay = forecast.accountsCycle(); } //add columns for (int i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { headerLabels << i18n("Min Bal %1", i); headerLabels << i18n("Min Date %1", i); } for (int i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { headerLabels << i18n("Max Bal %1", i); headerLabels << i18n("Max Date %1", i); } headerLabels << i18nc("Average balance", "Average"); ui->m_advancedList->setHeaderLabels(headerLabels); QTreeWidgetItem *advancedItem = 0; QMap::ConstIterator it_nc; for (it_nc = m_nameIdx.constBegin(); it_nc != m_nameIdx.constEnd(); ++it_nc) { const MyMoneyAccount& acc = file->account(*it_nc); QString amount; MyMoneyMoney amountMM; MyMoneySecurity currency; //change currency to deep currency if account is an investment if (acc.isInvest()) { MyMoneySecurity underSecurity = file->security(acc.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(acc.currencyId()); } advancedItem = new QTreeWidgetItem(ui->m_advancedList, advancedItem, false); advancedItem->setText(0, acc.name()); advancedItem->setIcon(0, acc.accountPixmap()); int it_c = 1; // iterator for the columns of the listview //get minimum balance list QList minBalanceList = forecast.accountMinimumBalanceDateList(acc); QList::Iterator t_min; for (t_min = minBalanceList.begin(); t_min != minBalanceList.end() ; ++t_min) { QDate minDate = *t_min; amountMM = forecast.forecastBalance(acc, minDate); amount = MyMoneyUtils::formatMoney(amountMM, acc, currency); advancedItem->setText(it_c, amount); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } it_c++; QString dateString = QLocale().toString(minDate, QLocale::ShortFormat); advancedItem->setText(it_c, dateString); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } it_c++; } //get maximum balance list QList maxBalanceList = forecast.accountMaximumBalanceDateList(acc); QList::Iterator t_max; for (t_max = maxBalanceList.begin(); t_max != maxBalanceList.end() ; ++t_max) { QDate maxDate = *t_max; amountMM = forecast.forecastBalance(acc, maxDate); amount = MyMoneyUtils::formatMoney(amountMM, acc, currency); advancedItem->setText(it_c, amount); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } it_c++; QString dateString = QLocale().toString(maxDate, QLocale::ShortFormat); advancedItem->setText(it_c, dateString); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } it_c++; } //get average balance amountMM = forecast.accountAverageBalance(acc); amount = MyMoneyUtils::formatMoney(amountMM, acc, currency); advancedItem->setText(it_c, amount); advancedItem->setTextAlignment(it_c, Qt::AlignRight | Qt::AlignVCenter); if (amountMM.isNegative()) { advancedItem->setForeground(it_c, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } it_c++; } // make sure all data is shown adjustHeadersAndResizeToContents(ui->m_advancedList); ui->m_advancedList->show(); } void loadBudgetView() { const auto file = MyMoneyFile::instance(); MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); //get the settings from current page and calculate this year based on last year QDate historyEndDate = QDate(QDate::currentDate().year() - 1, 12, 31); QDate historyStartDate = historyEndDate.addDays(-ui->m_accountsCycle->value() * ui->m_forecastCycles->value()); QDate forecastStartDate = QDate(QDate::currentDate().year(), 1, 1); QDate forecastEndDate = QDate::currentDate().addDays(ui->m_forecastDays->value()); forecast.setHistoryMethod(ui->m_historyMethod->checkedId()); MyMoneyBudget budget; forecast.createBudget(budget, historyStartDate, historyEndDate, forecastStartDate, forecastEndDate, false); ui->m_budgetList->clear(); ui->m_budgetList->setIconSize(QSize(22, 22)); ui->m_budgetList->setSortingEnabled(true); ui->m_budgetList->sortByColumn(0, Qt::AscendingOrder); //add columns QStringList headerLabels; headerLabels << i18n("Account"); { QDate forecastStartDate = forecast.forecastStartDate(); QDate forecastEndDate = forecast.forecastEndDate(); //add cycle interval columns QDate f_date = forecastStartDate; for (; f_date <= forecastEndDate; f_date = f_date.addMonths(1)) { headerLabels << QDate::longMonthName(f_date.month()); } } //add total column headerLabels << i18nc("Total balance", "Total"); //set the columns ui->m_budgetList->setHeaderLabels(headerLabels); //add default rows addTotalRow(ui->m_budgetList, forecast); addIncomeExpenseRows(forecast); //load income and expense budget accounts loadAccounts(forecast, file->income(), m_incomeItem, EForecastViewType::eBudget); loadAccounts(forecast, file->expense(), m_expenseItem, EForecastViewType::eBudget); adjustHeadersAndResizeToContents(ui->m_budgetList); } void loadChartView() { MyMoneyReport::EDetailLevel detailLevel[4] = { MyMoneyReport::eDetailAll, MyMoneyReport::eDetailTop, MyMoneyReport::eDetailGroup, MyMoneyReport::eDetailTotal }; MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::UserDefined, // overridden by the setDateFilter() call below detailLevel[ui->m_comboDetail->currentIndex()], i18n("Net Worth Forecast"), i18n("Generated Report")); reportCfg.setChartByDefault(true); reportCfg.setChartCHGridLines(false); reportCfg.setChartSVGridLines(false); reportCfg.setChartType(MyMoneyReport::eChartLine); reportCfg.setIncludingSchedules(false); // FIXME: this causes a crash //reportCfg.setColumnsAreDays( true ); reportCfg.setChartDataLabels(false); reportCfg.setConvertCurrency(true); reportCfg.setIncludingForecast(true); reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(ui->m_forecastDays->value())); reports::PivotTable table(reportCfg); table.drawChart(*m_forecastChart); // Adjust the size m_forecastChart->resize(ui->m_tab->width() - 10, ui->m_tab->height()); //m_forecastChart->show(); m_forecastChart->update(); } void loadForecastSettings() { //fill the settings controls - ui->m_forecastDays->setValue(KMyMoneyGlobalSettings::forecastDays()); - ui->m_accountsCycle->setValue(KMyMoneyGlobalSettings::forecastAccountCycle()); - ui->m_beginDay->setValue(KMyMoneyGlobalSettings::beginForecastDay()); - ui->m_forecastCycles->setValue(KMyMoneyGlobalSettings::forecastCycles()); + ui->m_forecastDays->setValue(ForecastViewSettings::forecastDays()); + ui->m_accountsCycle->setValue(ForecastViewSettings::forecastAccountCycle()); + ui->m_beginDay->setValue(ForecastViewSettings::beginForecastDay()); + ui->m_forecastCycles->setValue(ForecastViewSettings::forecastCycles()); ui->m_historyMethod->setId(ui->radioButton11, 0); // simple moving avg ui->m_historyMethod->setId(ui->radioButton12, 1); // weighted moving avg ui->m_historyMethod->setId(ui->radioButton13, 2); // linear regression - ui->m_historyMethod->button(KMyMoneyGlobalSettings::historyMethod())->setChecked(true); - switch (KMyMoneyGlobalSettings::forecastMethod()) { + ui->m_historyMethod->button(ForecastViewSettings::historyMethod())->setChecked(true); + switch (ForecastViewSettings::forecastMethod()) { case 0: ui->m_forecastMethod->setText(i18nc("Scheduled method", "Scheduled")); ui->m_forecastCycles->setDisabled(true); ui->m_historyMethodGroupBox->setDisabled(true); break; case 1: ui->m_forecastMethod->setText(i18nc("History-based method", "History")); ui->m_forecastCycles->setEnabled(true); ui->m_historyMethodGroupBox->setEnabled(true); break; default: ui->m_forecastMethod->setText(i18nc("Unknown forecast method", "Unknown")); break; } } void addAssetLiabilityRows(const MyMoneyForecast& forecast) { const auto file = MyMoneyFile::instance(); m_assetItem = new QTreeWidgetItem(m_totalItem); m_assetItem->setText(0, file->asset().name()); m_assetItem->setIcon(0, file->asset().accountPixmap()); m_assetItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_assetItem->setData(0, AccountRole, QVariant::fromValue(file->asset())); m_assetItem->setExpanded(true); m_liabilityItem = new QTreeWidgetItem(m_totalItem); m_liabilityItem->setText(0, file->liability().name()); m_liabilityItem->setIcon(0, file->liability().accountPixmap()); m_liabilityItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_liabilityItem->setData(0, AccountRole, QVariant::fromValue(file->liability())); m_liabilityItem->setExpanded(true); } void addIncomeExpenseRows(const MyMoneyForecast& forecast) { const auto file = MyMoneyFile::instance(); m_incomeItem = new QTreeWidgetItem(m_totalItem); m_incomeItem->setText(0, file->income().name()); m_incomeItem->setIcon(0, file->income().accountPixmap()); m_incomeItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_incomeItem->setData(0, AccountRole, QVariant::fromValue(file->income())); m_incomeItem->setExpanded(true); m_expenseItem = new QTreeWidgetItem(m_totalItem); m_expenseItem->setText(0, file->expense().name()); m_expenseItem->setIcon(0, file->expense().accountPixmap()); m_expenseItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_expenseItem->setData(0, AccountRole, QVariant::fromValue(file->expense())); m_expenseItem->setExpanded(true); } void addTotalRow(QTreeWidget* forecastList, const MyMoneyForecast& forecast) { const auto file = MyMoneyFile::instance(); m_totalItem = new QTreeWidgetItem(forecastList); QFont font; font.setBold(true); m_totalItem->setFont(0, font); m_totalItem->setText(0, i18nc("Total balance", "Total")); m_totalItem->setIcon(0, file->asset().accountPixmap()); m_totalItem->setData(0, ForecastRole, QVariant::fromValue(forecast)); m_totalItem->setData(0, AccountRole, QVariant::fromValue(file->asset())); m_totalItem->setExpanded(true); } bool includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc) { const auto file = MyMoneyFile::instance(); if (forecast.isForecastAccount(acc)) return true; foreach (const auto sAccount, acc.accountList()) { auto account = file->account(sAccount); if (includeAccount(forecast, account)) return true; } return false; } void adjustHeadersAndResizeToContents(QTreeWidget *widget) { QSize sizeHint(0, widget->sizeHintForRow(0)); QTreeWidgetItem *header = widget->headerItem(); for (int i = 0; i < header->columnCount(); ++i) { if (i > 0) { header->setData(i, Qt::TextAlignmentRole, Qt::AlignRight); // make sure that the row height stays the same even when the column that has icons is not visible if (m_totalItem) { m_totalItem->setSizeHint(i, sizeHint); } } widget->resizeColumnToContents(i); } } void setNegative(QTreeWidgetItem *item, bool isNegative) { if (isNegative) { for (int i = 0; i < item->columnCount(); ++i) { item->setForeground(i, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } } } void showAmount(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount, const MyMoneySecurity& security) { item->setText(column, MyMoneyUtils::formatMoney(amount, security)); item->setTextAlignment(column, Qt::AlignRight | Qt::AlignVCenter); item->setFont(column, item->font(0)); if (amount.isNegative()) { item->setForeground(column, KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative)); } } void adjustParentValue(QTreeWidgetItem *item, int column, const MyMoneyMoney& value) { if (!item) return; item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value() + value)); item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value().convert(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()))); // if the entry has no children, // or it is the top entry // or it is currently not open // we need to display the value of it if (item->childCount() == 0 || !item->parent() || (!item->isExpanded() && item->childCount() > 0) || (item->parent() && !item->parent()->parent())) { if (item->childCount() > 0) item->setText(column, " "); MyMoneyMoney amount = item->data(column, ValueRole).value(); showAmount(item, column, amount, MyMoneyFile::instance()->baseCurrency()); } // now make sure, the upstream accounts also get notified about the value change adjustParentValue(item->parent(), column, value); } void setValue(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount, const QDate& forecastDate) { MyMoneyAccount account = item->data(0, AccountRole).value(); //calculate the balance in base currency for the total row if (account.currencyId() != MyMoneyFile::instance()->baseCurrency().id()) { ReportAccount repAcc = ReportAccount(account.id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(forecastDate); MyMoneyMoney baseAmountMM = amount * curPrice; MyMoneyMoney value = baseAmountMM.convert(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()); item->setData(column, ValueRole, QVariant::fromValue(value)); adjustParentValue(item->parent(), column, value); } else { item->setData(column, ValueRole, QVariant::fromValue(item->data(column, ValueRole).value() + amount)); adjustParentValue(item->parent(), column, amount); } } void setAmount(QTreeWidgetItem* item, int column, const MyMoneyMoney& amount) { item->setData(column, AmountRole, QVariant::fromValue(amount)); item->setTextAlignment(column, Qt::AlignRight | Qt::AlignVCenter); } void updateSummary(QTreeWidgetItem *item) { MyMoneyMoney amountMM; int it_c = 1; // iterator for the columns of the listview const auto file = MyMoneyFile::instance(); int daysToBeginDay; MyMoneyForecast forecast = item->data(0, ForecastRole).value(); if (QDate::currentDate() < forecast.beginForecastDate()) { daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); } else { daysToBeginDay = forecast.accountsCycle(); } MyMoneyAccount account = item->data(0, AccountRole).value(); MyMoneySecurity currency; if (account.isInvest()) { MyMoneySecurity underSecurity = file->security(account.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(account.currencyId()); } //add current balance column QDate summaryDate = QDate::currentDate(); amountMM = forecast.forecastBalance(account, summaryDate); //calculate the balance in base currency for the total row setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, summaryDate); showAmount(item, it_c, amountMM, currency); it_c++; //iterate through all other columns for (QDate summaryDate = QDate::currentDate().addDays(daysToBeginDay); summaryDate <= forecast.forecastEndDate(); summaryDate = summaryDate.addDays(forecast.accountsCycle()), ++it_c) { amountMM = forecast.forecastBalance(account, summaryDate); //calculate the balance in base currency for the total row setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, summaryDate); showAmount(item, it_c, amountMM, currency); } //calculate and add variation per cycle setNegative(item, forecast.accountTotalVariation(account).isNegative()); setAmount(item, it_c, forecast.accountTotalVariation(account)); setValue(item, it_c, forecast.accountTotalVariation(account), forecast.forecastEndDate()); showAmount(item, it_c, forecast.accountTotalVariation(account), currency); } void updateDetailed(QTreeWidgetItem *item) { QString amount; QString vAmount; MyMoneyMoney vAmountMM; const auto file = MyMoneyFile::instance(); MyMoneyAccount account = item->data(0, AccountRole).value(); MyMoneySecurity currency; if (account.isInvest()) { MyMoneySecurity underSecurity = file->security(account.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(account.currencyId()); } int it_c = 1; // iterator for the columns of the listview MyMoneyForecast forecast = item->data(0, ForecastRole).value(); for (QDate forecastDate = QDate::currentDate(); forecastDate <= forecast.forecastEndDate(); ++it_c, forecastDate = forecastDate.addDays(1)) { MyMoneyMoney amountMM = forecast.forecastBalance(account, forecastDate); //calculate the balance in base currency for the total row setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, forecastDate); showAmount(item, it_c, amountMM, currency); } //calculate and add variation per cycle vAmountMM = forecast.accountTotalVariation(account); setAmount(item, it_c, vAmountMM); setValue(item, it_c, vAmountMM, forecast.forecastEndDate()); showAmount(item, it_c, vAmountMM, currency); } void updateBudget(QTreeWidgetItem *item) { MyMoneySecurity currency; MyMoneyMoney tAmountMM; MyMoneyForecast forecast = item->data(0, ForecastRole).value(); const auto file = MyMoneyFile::instance(); int it_c = 1; // iterator for the columns of the listview QDate forecastDate = forecast.forecastStartDate(); MyMoneyAccount account = item->data(0, AccountRole).value(); if (account.isInvest()) { MyMoneySecurity underSecurity = file->security(account.currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security(account.currencyId()); } //iterate columns for (; forecastDate <= forecast.forecastEndDate(); forecastDate = forecastDate.addMonths(1), ++it_c) { MyMoneyMoney amountMM; amountMM = forecast.forecastBalance(account, forecastDate); if (account.accountType() == eMyMoney::Account::Type::Expense) amountMM = -amountMM; tAmountMM += amountMM; setAmount(item, it_c, amountMM); setValue(item, it_c, amountMM, forecastDate); showAmount(item, it_c, amountMM, currency); } //set total column setAmount(item, it_c, tAmountMM); setValue(item, it_c, tAmountMM, forecast.forecastEndDate()); showAmount(item, it_c, tAmountMM, currency); } /** * Get the list of prices for an account * This is used later to create an instance of KMyMoneyAccountTreeForecastItem * */ // QList getAccountPrices(const MyMoneyAccount& acc) // { // const auto file = MyMoneyFile::instance(); // QList prices; // MyMoneySecurity security = file->baseCurrency(); // try { // if (acc.isInvest()) { // security = file->security(acc.currencyId()); // if (security.tradingCurrency() != file->baseCurrency().id()) { // MyMoneySecurity sec = file->security(security.tradingCurrency()); // prices += file->price(sec.id(), file->baseCurrency().id()); // } // } else if (acc.currencyId() != file->baseCurrency().id()) { // if (acc.currencyId() != file->baseCurrency().id()) { // security = file->security(acc.currencyId()); // prices += file->price(acc.currencyId(), file->baseCurrency().id()); // } // } // } catch (const MyMoneyException &e) { // qDebug() << Q_FUNC_INFO << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e.what(); // } // return prices; // } KForecastView *q_ptr; Ui::KForecastView *ui; bool m_needReload[MaxViewTabs]; /** * This member holds the load state of page */ bool m_needLoad; QTreeWidgetItem* m_totalItem; QTreeWidgetItem* m_assetItem; QTreeWidgetItem* m_liabilityItem; QTreeWidgetItem* m_incomeItem; QTreeWidgetItem* m_expenseItem; QLayout* m_chartLayout; reports::KReportChartView* m_forecastChart; QScopedPointer m_fixedColumnView; QMap m_nameIdx; }; #endif diff --git a/kmymoney/dialogs/settings/ksettingsforecast.cpp b/kmymoney/plugins/forecast/ksettingsforecast.cpp similarity index 100% rename from kmymoney/dialogs/settings/ksettingsforecast.cpp rename to kmymoney/plugins/forecast/ksettingsforecast.cpp diff --git a/kmymoney/dialogs/settings/ksettingsforecast.h b/kmymoney/plugins/forecast/ksettingsforecast.h similarity index 100% rename from kmymoney/dialogs/settings/ksettingsforecast.h rename to kmymoney/plugins/forecast/ksettingsforecast.h diff --git a/kmymoney/plugins/interfaces/kmmviewinterface.cpp b/kmymoney/plugins/interfaces/kmmviewinterface.cpp index 542cf38da..4d113f645 100644 --- a/kmymoney/plugins/interfaces/kmmviewinterface.cpp +++ b/kmymoney/plugins/interfaces/kmmviewinterface.cpp @@ -1,80 +1,90 @@ /*************************************************************************** viewinterface.cpp ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net (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 "kmmviewinterface.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyview.h" #include "selectedtransactions.h" KMyMoneyPlugin::KMMViewInterface::KMMViewInterface(KMyMoneyView* view, QObject* parent, const char* name) : ViewInterface(parent, name), m_view(view) { connect(m_view, &KMyMoneyView::accountSelected, this, &ViewInterface::accountSelected); connect(m_view, &KMyMoneyView::transactionsSelected, this, &ViewInterface::transactionsSelected); connect(m_view, &KMyMoneyView::accountReconciled, this, &ViewInterface::accountReconciled); // connect(app, &KMyMoneyApp::institutionSelected, this, &ViewInterface::institutionSelected); connect(m_view, &KMyMoneyView::viewStateChanged, this, &ViewInterface::viewStateChanged); connect(m_view, &KMyMoneyView::kmmFilePlugin, this, &ViewInterface::kmmFilePlugin); } bool KMyMoneyPlugin::KMMViewInterface::readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader) { return m_view->readFile(url, pExtReader); } void KMyMoneyPlugin::KMMViewInterface::slotRefreshViews() { m_view->slotRefreshViews(); } +void KMyMoneyPlugin::KMMViewInterface::addView(KMyMoneyViewBase* view, const QString& name, View idView) +{ + m_view->addView(view, name, idView); +} + +void KMyMoneyPlugin::KMMViewInterface::removeView(View idView) +{ + m_view->removeView(idView); +} + bool KMyMoneyPlugin::KMMViewInterface::fileOpen() { return m_view->fileOpen(); } bool KMyMoneyPlugin::KMMViewInterface::isDatabase() { return m_view->isDatabase(); } //KMyMoneyViewBase* KMyMoneyPlugin::KMMViewInterface::addPage(const QString& item, const QString& icon) //{ // return m_view->addBasePage(item, icon); //} //void KMyMoneyPlugin::KMMViewInterface::addWidget(KMyMoneyViewBase* view, QWidget* w) //{ // if (view && w) // view->addWidget(w); //} diff --git a/kmymoney/plugins/interfaces/kmmviewinterface.h b/kmymoney/plugins/interfaces/kmmviewinterface.h index 26e52291f..7c44eca9f 100644 --- a/kmymoney/plugins/interfaces/kmmviewinterface.h +++ b/kmymoney/plugins/interfaces/kmmviewinterface.h @@ -1,106 +1,108 @@ /*************************************************************************** kmmviewinterface.h ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net (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 KMMVIEWINTERFACE_H #define KMMVIEWINTERFACE_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes class KMyMoneyView; // ---------------------------------------------------------------------------- // Project Includes #include "viewinterface.h" namespace KMyMoneyPlugin { /** * This class represents the implementation of the * ViewInterface. */ class KMMViewInterface : public ViewInterface { Q_OBJECT public: KMMViewInterface(KMyMoneyView* view, QObject* parent, const char* name = 0); ~KMMViewInterface() {} /** * This method returns a pointer to a newly created view * with title @p item and icon @p pixmap. * * @param item Name of view * @param icon name for the icon to be used for the view * * @return pointer to KMyMoneyViewBase object */ // KMyMoneyViewBase* addPage(const QString& item, const QString& icon); /** * This method allows to add a widget to the view * created with addPage() * * @param view pointer to view object * @param w pointer to widget */ // void addWidget(KMyMoneyViewBase* view, QWidget* w); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ bool readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader = nullptr) override; /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen() override; bool isDatabase() override; /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ void slotRefreshViews() override; + void addView(KMyMoneyViewBase* view, const QString& name, View idView) override; + void removeView(View idView) override; private: KMyMoneyView* m_view; }; } // namespace #endif diff --git a/kmymoney/plugins/viewinterface.h b/kmymoney/plugins/viewinterface.h index ef2306e5a..dbf7fa2f0 100644 --- a/kmymoney/plugins/viewinterface.h +++ b/kmymoney/plugins/viewinterface.h @@ -1,156 +1,162 @@ /*************************************************************************** viewinterface.h ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ // krazy:excludeall=dpointer #ifndef VIEWINTERFACE_H #define VIEWINTERFACE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include namespace KMyMoneyRegister { class SelectedTransactions; } +enum class View; + class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySplit; class MyMoneyTransaction; class IMyMoneyOperationsFormat; +class KMyMoneyViewBase; namespace KMyMoneyPlugin { /** * This abstract class represents the ViewInterface to * add new view pages to the JanusWidget of KMyMoney. It * also gives access to the account context menu. */ class KMM_PLUGIN_EXPORT ViewInterface : public QObject { Q_OBJECT public: explicit ViewInterface(QObject* parent, const char* name = 0); virtual ~ViewInterface(); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ virtual bool readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader = nullptr) = 0; /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ virtual bool fileOpen() = 0; virtual bool isDatabase() = 0; /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ virtual void slotRefreshViews() = 0; /** * This method creates a new page in the application. * See KPageWidget::addPage() for details. */ // virtual KMyMoneyViewBase* addPage(const QString& item, const QString& icon) = 0; /** * This method adds a widget to the layout of the view * created with addPage() * * @param view pointer to view widget * @param w widget to be added to @p page */ // virtual void addWidget(KMyMoneyViewBase* view, QWidget* w) = 0; + virtual void addView(KMyMoneyViewBase* view, const QString& name, View idView) = 0; + virtual void removeView(View idView) = 0; + Q_SIGNALS: /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& acc); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ // void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when an account has been successfully reconciled * and all transactions are updated in the engine. It can be used by plugins * to create reconciliation reports. * * @param account the account data * @param date the reconciliation date as provided through the dialog * @param startingBalance the starting balance as provided through the dialog * @param endingBalance the ending balance as provided through the dialog * @param transactionList reference to QList of QPair containing all * transaction/split pairs processed by the reconciliation. */ void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); void viewStateChanged(bool); void kmmFilePlugin(unsigned int); }; } // namespace #endif diff --git a/kmymoney/views/CMakeLists.txt b/kmymoney/views/CMakeLists.txt index a268511ae..c0e26024b 100644 --- a/kmymoney/views/CMakeLists.txt +++ b/kmymoney/views/CMakeLists.txt @@ -1,98 +1,104 @@ +set(viewbase_SOURCES + kmymoneyviewbase.cpp + ) + +add_library(viewbase STATIC ${viewbase_SOURCES}) + +target_link_libraries(viewbase PUBLIC KF5::TextWidgets) + ############# next target (views) STATIC ################### set(libviews_a_SOURCES kaccountsview.cpp kbudgetview.cpp kcategoriesview.cpp - kforecastview.cpp kgloballedgerview.cpp kwelcomepage.cpp khomeview.cpp kinstitutionsview.cpp kinvestmentview.cpp kmymoneyfile.cpp - kmymoneyview.cpp - kmymoneyviewbase.cpp kmymoneyaccountsviewbase.cpp + kmymoneyview.cpp kpayeesview.cpp kreportsview.cpp kscheduledview.cpp tocitem.cpp tocitemgroup.cpp tocitemreport.cpp kscheduletreeitem.cpp ktagsview.cpp konlinejoboutbox.cpp kpayeeidentifierview.cpp payeeidentifierselectiondelegate.cpp kmymoneywebpage.cpp ) if(ENABLE_UNFINISHEDFEATURES) list(APPEND libviews_a_SOURCES simpleledgerview.cpp ledgerviewpage.cpp ledgerview.cpp ledgerdelegate.cpp newspliteditor.cpp newtransactioneditor.cpp newtransactionform.cpp splitdialog.cpp splitdelegate.cpp widgethintframe.cpp ) endif() set(libviews_a_UI kaccountsview.ui kbudgetview.ui kcategoriesview.ui - kforecastview.ui kinstitutionsview.ui kinvestmentview.ui kpayeesview.ui kscheduledview.ui ktagsview.ui konlinejoboutbox.ui kpayeeidentifierview.ui ) if(ENABLE_UNFINISHEDFEATURES) list(APPEND libviews_a_UI simpleledgerview.ui ledgerview.ui ledgerviewpage.ui splitdialog.ui newspliteditor.ui newtransactioneditor.ui newtransactionform.ui ) endif() # The handling of these ui files depends # on libkmymoney.so (the widgets library) ki18n_wrap_ui(libviews_a_SOURCES ${libviews_a_UI}) add_library(views STATIC ${libviews_a_SOURCES}) target_link_libraries(views PUBLIC newaccountwizard KChart KF5::KIOFileWidgets KF5::Notifications KF5::Archive KF5::TextWidgets Qt5::Sql Qt5::PrintSupport Alkimia::alkimia) if(ENABLE_WEBENGINE) target_link_libraries(views PUBLIC Qt5::WebEngineWidgets) else(ENABLE_WEBENGINE) target_link_libraries(views PUBLIC KF5::WebKit) endif(ENABLE_WEBENGINE) if(KF5Activities_FOUND) target_link_libraries(views PRIVATE KF5::Activities) endif() # TODO: Remove this dependency. But it is needed as long as the payee editor uses these objects directly # This should be replaced by virtual methods in a pure abstract object. target_link_libraries( views PUBLIC payeeidentifier_iban_bic payeeidentifier_nationalAccount kmm_mymoney # needed to load payeeIdentifier + viewbase ) # we rely on some of the dialogs to be generated add_dependencies(views dialogs newinvestmentwizard newaccountwizard newloanwizard endingbalancedlg) diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index 4cf06e9b8..14ef12ec3 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,2231 +1,2239 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoneyview.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #ifdef KF5Activities_FOUND #include #endif // ---------------------------------------------------------------------------- // Project Includes #ifdef ENABLE_UNFINISHEDFEATURES #include "simpleledgerview.h" #endif #include "kmymoneyglobalsettings.h" #include "kmymoneytitlelabel.h" #include #include "kcurrencyeditdlg.h" #include "mymoneystoragemgr.h" #include "mymoneystoragebin.h" #include "mymoneyexception.h" #include "mymoneystoragexml.h" #include "mymoneystorageanon.h" #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h" #include "kinstitutionsview.h" #include "kpayeesview.h" #include "ktagsview.h" #include "kscheduledview.h" #include "kgloballedgerview.h" #include "kinvestmentview.h" #include "kreportsview.h" #include "kbudgetview.h" -#include "kforecastview.h" #include "konlinejoboutbox.h" #include "kmymoney.h" #include "models.h" #include "accountsmodel.h" #include "equitiesmodel.h" #include "securitiesmodel.h" #include "icons.h" #include "amountedit.h" #include "kmymoneyaccounttreeview.h" #include "accountsviewproxymodel.h" #include "mymoneyprice.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneyaccount.h" #include "mymoneyinstitution.h" #include "kmymoneyedit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyreport.h" #include "kmymoneyplugin.h" #include "mymoneyenums.h" using namespace Icons; using namespace eMyMoney; static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; static constexpr char recoveryKeyId[] = "0xD2B08440"; typedef void(KMyMoneyView::*KMyMoneyViewFunc)(); KMyMoneyView::KMyMoneyView(KMyMoneyApp *kmymoney) : KPageWidget(nullptr), m_header(0), m_inConstructor(true), m_fileOpen(false), m_fmode(QFileDevice::ReadUser | QFileDevice::WriteUser), m_lastViewSelected(0), m_storagePlugins(nullptr) #ifdef KF5Activities_FOUND , m_activityResourceInstance(0) #endif { // this is a workaround for the bug in KPageWidget that causes the header to be shown // for a short while during page switch which causes a kind of bouncing of the page's // content and if the page's content is at it's minimum size then during a page switch // the main window's size is also increased to fit the header that is shown for a sort // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) // in a grid layout so if we find it there remove it for good to avoid the described issues QGridLayout* gridLayout = qobject_cast(layout()); if (gridLayout) { QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout if (headerItem && qobject_cast(headerItem->widget()) != NULL) { gridLayout->removeItem(headerItem); // after we remove the KPageWidget standard header replace it with our own title label m_header = new KMyMoneyTitleLabel(this); m_header->setObjectName("titleLabel"); m_header->setMinimumSize(QSize(100, 30)); m_header->setRightImageFile("pics/titlelabel_background.png"); m_header->setVisible(KMyMoneyGlobalSettings::showTitleBar()); gridLayout->addWidget(m_header, 1, 1); } } newStorage(); m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit connect(kmymoney, &KMyMoneyApp::fileLoaded, this, &KMyMoneyView::slotRefreshViews); // Page 0 m_homeView = new KHomeView; viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home")); viewFrames[View::Home]->setIcon(Icons::get(Icon::ViewHome)); connect(m_homeView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_homeView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 1 m_institutionsView = new KInstitutionsView; viewFrames[View::Institutions] = m_model->addPage(m_institutionsView, i18n("Institutions")); viewFrames[View::Institutions]->setIcon(Icons::get(Icon::ViewInstitutions)); connect(m_institutionsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_institutionsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 2 m_accountsView = new KAccountsView; viewFrames[View::Accounts] = m_model->addPage(m_accountsView, i18n("Accounts")); viewFrames[View::Accounts]->setIcon(Icons::get(Icon::ViewAccounts)); connect(m_accountsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_accountsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 3 m_scheduledView = new KScheduledView; //this is to solve the way long strings are handled differently among versions of KPageWidget viewFrames[View::Schedules] = m_model->addPage(m_scheduledView, i18n("Scheduled transactions")); viewFrames[View::Schedules]->setIcon(Icons::get(Icon::ViewSchedules)); connect(m_scheduledView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_scheduledView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 4 m_categoriesView = new KCategoriesView; viewFrames[View::Categories] = m_model->addPage(m_categoriesView, i18n("Categories")); viewFrames[View::Categories]->setIcon(Icons::get(Icon::ViewCategories)); connect(m_categoriesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_categoriesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 5 m_tagsView = new KTagsView; viewFrames[View::Tags] = m_model->addPage(m_tagsView, i18n("Tags")); viewFrames[View::Tags]->setIcon(Icons::get(Icon::ViewTags)); connect(m_tagsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_tagsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 6 m_payeesView = new KPayeesView; viewFrames[View::Payees] = m_model->addPage(m_payeesView, i18n("Payees")); viewFrames[View::Payees]->setIcon(Icons::get(Icon::ViewPayees)); connect(m_payeesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_payeesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 7 m_ledgerView = new KGlobalLedgerView; viewFrames[View::Ledgers] = m_model->addPage(m_ledgerView, i18n("Ledgers")); viewFrames[View::Ledgers]->setIcon(Icons::get(Icon::ViewLedgers)); connect(m_ledgerView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_ledgerView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 8 m_investmentView = new KInvestmentView; viewFrames[View::Investments] = m_model->addPage(m_investmentView, i18n("Investments")); viewFrames[View::Investments]->setIcon(Icons::get(Icon::ViewInvestment)); connect(m_investmentView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_investmentView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 9 m_reportsView = new KReportsView; viewFrames[View::Reports] = m_model->addPage(m_reportsView, i18n("Reports")); viewFrames[View::Reports]->setIcon(Icons::get(Icon::ViewReports)); connect(m_reportsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_reportsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 10 m_budgetView = new KBudgetView; viewFrames[View::Budget] = m_model->addPage(m_budgetView, i18n("Budgets")); viewFrames[View::Budget]->setIcon(Icons::get(Icon::ViewBudgets)); connect(m_budgetView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_budgetView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 11 - m_forecastView = new KForecastView; - viewFrames[View::Forecast] = m_model->addPage(m_forecastView, i18n("Forecast")); - viewFrames[View::Forecast]->setIcon(Icons::get(Icon::ViewForecast)); - connect(m_forecastView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); - connect(m_forecastView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); + // KForecastView // Page 12 m_onlineJobOutboxView = new KOnlineJobOutbox; viewFrames[View::OnlineJobOutbox] = m_model->addPage(m_onlineJobOutboxView, i18n("Outbox")); viewFrames[View::OnlineJobOutbox]->setIcon(Icons::get(Icon::ViewOutbox)); connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); connect(m_reportsView, &KReportsView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); connect(m_ledgerView, &KGlobalLedgerView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); connect(m_homeView, &KHomeView::ledgerSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); #ifdef ENABLE_UNFINISHEDFEATURES SimpleLedgerView* view = new SimpleLedgerView(kmymoney, this); KPageWidgetItem* frame = m_model->addPage(view, i18n("New ledger")); frame->setIcon(Icons::get(Icon::DocumentProperties)); #endif //set the model setModel(m_model); setCurrentPage(viewFrames[View::Home]); connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); updateViewType(); m_inConstructor = false; // add fast switching of main views through Ctrl + NUM_X struct pageInfo { View view; KMyMoneyViewFunc callback; QString text; QKeySequence shortcut = QKeySequence(); }; const QVector pageInfos { {View::Home, &KMyMoneyView::slotShowHomePage, i18n("Show home page"), Qt::CTRL + Qt::Key_1}, {View::Institutions, &KMyMoneyView::slotShowInstitutionsPage, i18n("Show institutions page"), Qt::CTRL + Qt::Key_2}, {View::Accounts, &KMyMoneyView::slotShowAccountsPage, i18n("Show accounts page"), Qt::CTRL + Qt::Key_3}, {View::Schedules, &KMyMoneyView::slotShowSchedulesPage, i18n("Show scheduled transactions page"), Qt::CTRL + Qt::Key_4}, {View::Categories, &KMyMoneyView::slotShowCategoriesPage, i18n("Show categories page"), Qt::CTRL + Qt::Key_5}, {View::Tags, &KMyMoneyView::slotShowTagsPage, i18n("Show tags page"), }, {View::Payees, &KMyMoneyView::slotShowPayeesPage, i18n("Show payees page"), Qt::CTRL + Qt::Key_6}, {View::Ledgers, &KMyMoneyView::slotShowLedgersPage, i18n("Show ledgers page"), Qt::CTRL + Qt::Key_7}, {View::Investments, &KMyMoneyView::slotShowInvestmentsPage, i18n("Show investments page"), Qt::CTRL + Qt::Key_8}, {View::Reports, &KMyMoneyView::slotShowReportsPage, i18n("Show reports page"), Qt::CTRL + Qt::Key_9}, {View::Budget, &KMyMoneyView::slotShowBudgetPage, i18n("Show budget page"), }, {View::Forecast, &KMyMoneyView::slotShowForecastPage, i18n("Show forecast page"), }, {View::OnlineJobOutbox, &KMyMoneyView::slotShowOutboxPage, i18n("Show outbox page") } }; QHash lutActions; auto aC = kmymoney->actionCollection(); auto pageCount = 0; foreach (const pageInfo info, pageInfos) { 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(QLatin1String("ShowPage") + QString::number(pageCount++)); a->setText(info.text); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.view, a); // store QAction's pointer for later processing if (!info.shortcut.isEmpty()) aC->setDefaultShortcut(a, info.shortcut); } aC->addActions(lutActions.values()); // Initialize kactivities resource instance #ifdef KF5Activities_FOUND m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this); connect(kmymoney, SIGNAL(fileLoaded(QUrl)), m_activityResourceInstance, SLOT(setUri(QUrl))); #endif } KMyMoneyView::~KMyMoneyView() { KMyMoneyGlobalSettings::setLastViewSelected(m_lastViewSelected); #ifdef KF5Activities_FOUND delete m_activityResourceInstance; #endif removeStorage(); } void KMyMoneyView::slotShowHomePage() { showPage(viewFrames[View::Home]); } void KMyMoneyView::slotShowInstitutionsPage() { showPage(viewFrames[View::Institutions]); m_institutionsView->setDefaultFocus(); } void KMyMoneyView::slotShowAccountsPage() { showPage(viewFrames[View::Accounts]); m_accountsView->setDefaultFocus(); } void KMyMoneyView::slotShowSchedulesPage() { showPage(viewFrames[View::Schedules]); m_scheduledView->setDefaultFocus(); } void KMyMoneyView::slotShowCategoriesPage() { showPage(viewFrames[View::Categories]); m_categoriesView->setDefaultFocus(); } void KMyMoneyView::slotShowTagsPage() { showPage(viewFrames[View::Tags]); m_tagsView->setDefaultFocus(); } void KMyMoneyView::slotShowPayeesPage() { showPage(viewFrames[View::Payees]); m_payeesView->setDefaultFocus(); } void KMyMoneyView::slotShowLedgersPage() { showPage(viewFrames[View::Ledgers]); m_ledgerView->setDefaultFocus(); } void KMyMoneyView::slotShowInvestmentsPage() { showPage(viewFrames[View::Investments]); m_investmentView->setDefaultFocus(); } void KMyMoneyView::slotShowReportsPage() { showPage(viewFrames[View::Reports]); m_reportsView->setDefaultFocus(); } void KMyMoneyView::slotShowBudgetPage() { showPage(viewFrames[View::Budget]); m_budgetView->setDefaultFocus(); } void KMyMoneyView::slotShowForecastPage() { - showPage(viewFrames[View::Forecast]); - m_forecastView->setDefaultFocus(); + if (viewFrames.contains(View::Forecast)) { + showPage(viewFrames[View::Forecast]); + viewBases[View::Forecast]->setDefaultFocus(); + } } void KMyMoneyView::slotShowOutboxPage() { showPage(viewFrames[View::OnlineJobOutbox]); m_onlineJobOutboxView->setDefaultFocus(); } void KMyMoneyView::showTitleBar(bool show) { if (m_header) m_header->setVisible(show); } void KMyMoneyView::updateViewType() { // set the face type KPageView::FaceType faceType = KPageView::List; switch (KMyMoneySettings::viewType()) { case 0: faceType = KPageView::List; break; case 1: faceType = KPageView::Tree; break; case 2: faceType = KPageView::Tabbed; break; } if (faceType != KMyMoneyView::faceType()) { setFaceType(faceType); if (faceType == KPageView::Tree) { QList views = findChildren(); foreach (QTreeView * view, views) { if (view && (view->parent() == this)) { view->setRootIsDecorated(false); break; } } } } } void KMyMoneyView::slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show) { QVector proxyModels {m_institutionsView->getProxyModel(), m_accountsView->getProxyModel(), m_categoriesView->getProxyModel(), m_budgetView->getProxyModel()}; for (auto i = proxyModels.count() - 1; i >= 0; --i) { // weed out unloaded views if (!proxyModels.at(i)) proxyModels.removeAt(i); } QString question; if (show) question = i18n("Do you want to show %1 column on every loaded view?", AccountsModel::getHeaderName(column)); else question = i18n("Do you want to hide %1 column on every loaded view?", AccountsModel::getHeaderName(column)); if (proxyModels.count() == 1 || // no need to ask what to do with other views because they aren't loaded KMessageBox::questionYesNo(this, question, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("ShowColumnOnEveryView")) == KMessageBox::Yes) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); foreach(AccountsViewProxyModel *proxyModel, proxyModels) { if (!proxyModel) continue; proxyModel->setColumnVisibility(column, show); proxyModel->invalidate(); } } else if(show) { // in case we need to show it, we have to make sure to set the visibility // in the base model as well. Otherwise, we don't see the column through the proxy model Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); } } void KMyMoneyView::setOnlinePlugins(QMap& plugins) { m_accountsView->setOnlinePlugins(plugins); m_onlineJobOutboxView->setOnlinePlugins(plugins); } void KMyMoneyView::setStoragePlugins(QMap& plugins) { m_storagePlugins = &plugins; } eDialogs::ScheduleResultCode KMyMoneyView::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { return m_scheduledView->enterSchedule(schedule, autoEnter, extendedKeys); } +void KMyMoneyView::addView(KMyMoneyViewBase* view, const QString& name, View idView) +{ + auto isViewInserted = false; + for (auto i = (int)idView; i < (int)View::None; ++i) { + if (viewFrames.contains((View)i)) { + viewFrames[idView] = m_model->insertPage(viewFrames[(View)i],view, name); + viewBases[idView] = view; + isViewInserted = true; + break; + } + } + + if (!isViewInserted) + viewFrames[idView] = m_model->addPage(view, name); + + auto icon = Icon::ViewForecast; + switch (idView) { + case View::Forecast: + icon = Icon::ViewForecast; + break; + default: + break; + } + viewFrames[idView]->setIcon(Icons::get(icon)); +} + +void KMyMoneyView::removeView(View idView) +{ + m_model->removePage(viewFrames[idView]); + viewFrames.remove(idView); + viewBases.remove(idView); +} + bool KMyMoneyView::showPageHeader() const { return false; } void KMyMoneyView::slotSwitchView(View view) { showPage(viewFrames[view]); } void KMyMoneyView::showPage(KPageWidgetItem* pageItem) { // reset all selected items before showing the selected view // but not while we're in our own constructor if (!m_inConstructor && pageItem != currentPage()) { kmymoney->slotResetSelections(); } // pretend we're in the constructor to avoid calling the // above resets. For some reason which I don't know the details // of, KJanusWidget::showPage() calls itself recursively. This // screws up the action handling, as items could have been selected // in the meantime. We prevent this by setting the m_inConstructor // to true and reset it to the previos value when we leave this method. bool prevConstructor = m_inConstructor; m_inConstructor = true; setCurrentPage(pageItem); m_inConstructor = prevConstructor; if (!m_inConstructor) { // fixup some actions that are dependant on the view // this does not work during construction kmymoney->slotUpdateActions(); } } bool KMyMoneyView::canPrint() { bool rc = ( viewFrames[View::Reports] == currentPage() || viewFrames[View::Home] == currentPage() ); return rc; } void KMyMoneyView::newStorage() { removeStorage(); auto file = MyMoneyFile::instance(); file->attachStorage(new MyMoneyStorageMgr); } void KMyMoneyView::removeStorage() { auto file = MyMoneyFile::instance(); auto p = file->storage(); if (p) { file->detachStorage(p); delete p; } } void KMyMoneyView::enableViewsIfFileOpen() { // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this - if (viewFrames[View::Accounts]->isEnabled() != m_fileOpen) - viewFrames[View::Accounts]->setEnabled(m_fileOpen); - if (viewFrames[View::Institutions]->isEnabled() != m_fileOpen) - viewFrames[View::Institutions]->setEnabled(m_fileOpen); - if (viewFrames[View::Schedules]->isEnabled() != m_fileOpen) - viewFrames[View::Schedules]->setEnabled(m_fileOpen); - if (viewFrames[View::Categories]->isEnabled() != m_fileOpen) - viewFrames[View::Categories]->setEnabled(m_fileOpen); - if (viewFrames[View::Payees]->isEnabled() != m_fileOpen) - viewFrames[View::Payees]->setEnabled(m_fileOpen); - if (viewFrames[View::Tags]->isEnabled() != m_fileOpen) - viewFrames[View::Tags]->setEnabled(m_fileOpen); - if (viewFrames[View::Budget]->isEnabled() != m_fileOpen) - viewFrames[View::Budget]->setEnabled(m_fileOpen); - if (viewFrames[View::Ledgers]->isEnabled() != m_fileOpen) - viewFrames[View::Ledgers]->setEnabled(m_fileOpen); - if (viewFrames[View::Investments]->isEnabled() != m_fileOpen) - viewFrames[View::Investments]->setEnabled(m_fileOpen); - if (viewFrames[View::Reports]->isEnabled() != m_fileOpen) - viewFrames[View::Reports]->setEnabled(m_fileOpen); - if (viewFrames[View::Forecast]->isEnabled() != m_fileOpen) - viewFrames[View::Forecast]->setEnabled(m_fileOpen); - if (viewFrames[View::OnlineJobOutbox]->isEnabled() != m_fileOpen) - viewFrames[View::OnlineJobOutbox]->setEnabled(m_fileOpen); + for (auto i = (int)View::Home; i < (int)View::None; ++i) + if (viewFrames.contains(View(i))) + if (viewFrames[View(i)]->isEnabled() != m_fileOpen) + viewFrames[View(i)]->setEnabled(m_fileOpen); emit viewStateChanged(m_fileOpen); } void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) { showPage(viewFrames[View::Payees]); m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(viewFrames[View::Tags]); m_tagsView->slotSelectTagAndTransaction(tag, account, transaction); } bool KMyMoneyView::fileOpen() { return m_fileOpen; } void KMyMoneyView::closeFile() { if (m_reportsView) m_reportsView->slotCloseAll(); // disconnect the signals disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) Models::instance()->fileClosed(); emit kmmFilePlugin(preClose); if (isDatabase()) MyMoneyFile::instance()->storage()->close(); // to log off a database user newStorage(); slotShowHomePage(); emit kmmFilePlugin(postClose); m_fileOpen = false; emit fileClosed(); } void KMyMoneyView::ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } bool KMyMoneyView::readFile(const QUrl &url, IMyMoneyOperationsFormat* pExtReader) { QString filename; bool downloadedFile = false; m_fileOpen = false; bool isEncrypted = false; IMyMoneyOperationsFormat* pReader = 0; if (!url.isValid()) { qDebug("Invalid URL '%s'", qPrintable(url.url())); return false; } // disconnect the current storga manager from the engine MyMoneyFile::instance()->detachStorage(); if (url.scheme() == QLatin1String("sql")) { // handle reading of database m_fileType = KmmDb; // get rid of the mode parameter which is now redundant QUrl newUrl(url); QUrlQuery query(url); query.removeQueryItem("mode"); newUrl.setQuery(query); auto rc = openDatabase(newUrl); // on error, any message will have been displayed if (!rc) MyMoneyFile::instance()->attachStorage(new MyMoneyStorageMgr); return rc; } auto storage = new MyMoneyStorageMgr; if (url.isLocalFile()) { filename = url.toLocalFile(); } else { downloadedFile = true; KIO::StoredTransferJob *transferjob = KIO::storedGet (url); KJobWidgets::setWindow(transferjob, this); if (! transferjob->exec()) { KMessageBox::detailedError(this, i18n("Error while loading file '%1'.", url.url()), transferjob->errorString(), i18n("File access error")); return false; } QTemporaryFile file; file.setAutoRemove(false); file.open(); file.write(transferjob->data()); filename = file.fileName(); file.close(); } // let's glimps into the file to figure out, if it's one // of the old (uncompressed) or new (compressed) files. QFile file(filename); QFileInfo info(file); if (!info.isFile()) { QString msg = i18n("

%1 is not a KMyMoney file.

", filename); KMessageBox::error(this, msg, i18n("Filetype Error")); return false; } m_fmode = QFileDevice::ReadUser | QFileDevice::WriteUser; m_fmode |= info.permissions(); bool rc = true; // There's a problem with the KFilterDev and KGPGFile classes: // One supports the at(n) member but not ungetch() together with // read() and the other does not provide an at(n) method but // supports read() that considers the ungetch() buffer. QFile // supports everything so this is not a problem. We solve the problem // for now by keeping track of which method can be used. bool haveAt = true; emit kmmFilePlugin(preOpen); if (file.open(QIODevice::ReadOnly)) { QByteArray hdr(2, '\0'); int cnt; cnt = file.read(hdr.data(), 2); file.close(); if (cnt == 2) { QIODevice* qfile = nullptr; if (QString(hdr) == QString("\037\213")) { // gzipped? qfile = new KCompressionDevice(filename, COMPRESSION_TYPE); } else if (QString(hdr) == QString("--") // PGP ASCII armored? || QString(hdr) == QString("\205\001") // PGP binary? || QString(hdr) == QString("\205\002")) { // PGP binary? if (KGPGFile::GPGAvailable()) { qfile = new KGPGFile(filename); haveAt = false; isEncrypted = true; } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("GPG is not available for decryption of file %1", filename))); qfile = new QFile(file.fileName()); } } else { // we can't use file directly, as we delete qfile later on qfile = new QFile(file.fileName()); } if (qfile->open(QIODevice::ReadOnly)) { try { hdr.resize(8); if (qfile->read(hdr.data(), 8) == 8) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 8); // Ok, we got the first block of 8 bytes. Read in the two // unsigned long int's by preserving endianess. This is // achieved by reading them through a QDataStream object qint32 magic0, magic1; QDataStream s(&hdr, QIODevice::ReadOnly); s >> magic0; s >> magic1; // If both magic numbers match (we actually read in the // text 'KMyMoney' then we assume a binary file and // construct a reader for it. Otherwise, we construct // an XML reader object. // // The expression magic0 < 30 is only used to create // a binary reader if we assume an old binary file. This // should be removed at some point. An alternative is to // check the beginning of the file against an pattern // of the XML file (e.g. '?read(hdr.data(), 70) == 70) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 70); QRegExp kmyexp(""); QRegExp gncexp("attachStorage(storage); loadAllCurrencies(); // currency list required for gnc MyMoneyFile::instance()->detachStorage(storage); pReader = pExtReader; m_fileType = GncXML; } } } } if (pReader) { pReader->setProgressCallback(&KMyMoneyView::progressCallback); pReader->readFile(qfile, storage); } else { if (m_fileType == KmmBinary) { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.", filename))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains an unknown file format.", filename))); } rc = false; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot read from file %1.", filename))); rc = false; } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot load file %1. Reason: %2", filename, e.what()))); rc = false; } if (pReader) { pReader->setProgressCallback(0); delete pReader; } qfile->close(); } else { KGPGFile *gpgFile = qobject_cast(qfile); if (gpgFile && !gpgFile->errorToString().isEmpty()) { KMessageBox::sorry(this, QString("%1"). arg(i18n("The following error was encountered while decrypting file %1: %2", filename, gpgFile->errorToString()))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); } rc = false; } delete qfile; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); rc = false; } // things are finished, now we connect the storage to the engine // which forces a reload of the cache in the engine with those // objects that are cached MyMoneyFile::instance()->attachStorage(storage); if (rc == false) return rc; // encapsulate transactions to the engine to be able to commit/rollback MyMoneyFileTransaction ft; // make sure we setup the encryption key correctly if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneyGlobalSettings::gpgRecipientList().join(",")); } // make sure we setup the name of the base accounts in translated form try { MyMoneyFile *file = MyMoneyFile::instance(); checkAccountName(file->asset(), i18n("Asset")); checkAccountName(file->liability(), i18n("Liability")); checkAccountName(file->income(), i18n("Income")); checkAccountName(file->expense(), i18n("Expense")); checkAccountName(file->equity(), i18n("Equity")); ft.commit(); } catch (const MyMoneyException &) { } // if a temporary file was downloaded, then it will be removed // with the next call. Otherwise, it stays untouched on the local // filesystem. if (downloadedFile) { QFile::remove(filename); } return initializeStorage(); } void KMyMoneyView::checkAccountName(const MyMoneyAccount& _acc, const QString& name) const { auto file = MyMoneyFile::instance(); if (_acc.name() != name) { MyMoneyAccount acc(_acc); acc.setName(name); file->modifyAccount(acc); } } bool KMyMoneyView::openDatabase(const QUrl &url) { m_fileOpen = false; // open the database auto pStorage = MyMoneyFile::instance()->storage(); if (!pStorage) pStorage = new MyMoneyStorageMgr; auto rc = false; auto pluginFound = false; if (m_storagePlugins) { for (const auto& plugin : *m_storagePlugins) { if (plugin->formatName().compare(QLatin1String("SQL")) == 0) { rc = plugin->open(pStorage, url); pluginFound = true; break; } } } if(!pluginFound) KMessageBox::error(this, i18n("Couldn't find suitable plugin to read your storage.")); if(!rc) { removeStorage(); delete pStorage; return false; } if (pStorage) { removeStorage(); MyMoneyFile::instance()->attachStorage(pStorage); } m_fileOpen = true; return initializeStorage(); } bool KMyMoneyView::initializeStorage() { bool blocked = MyMoneyFile::instance()->signalsBlocked(); MyMoneyFile::instance()->blockSignals(true); // we check, if we have any currency in the file. If not, we load // all the default currencies we know. MyMoneyFileTransaction ft; try { updateCurrencyNames(); ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } // make sure, we have a base currency and all accounts are // also assigned to a currency. QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug() << e.what(); } if (baseId.isEmpty()) { // Stay in this endless loop until we have a base currency, // as without it the application does not work anymore. while (baseId.isEmpty()) { selectBaseCurrency(); try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug() << e.what(); } } } else { // in some odd intermediate cases there could be files out there // that have a base currency set, but still have accounts that // do not have a base currency assigned. This call will take // care of it. We can safely remove it later. // // Another work-around for this scenario is to remove the base // currency setting from the XML file by removing the line // // // // and restart the application with this file. This will force to // run the above loop. selectBaseCurrency(); } // setup the standard precision AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KSharedConfigPtr config = KSharedConfig::openConfig(); KPageWidgetItem* page; KConfigGroup grp = config->group("General Options"); if (KMyMoneyGlobalSettings::startLastViewSelected() != 0) page = viewFrames.value(static_cast(KMyMoneyGlobalSettings::lastViewSelected())); else page = viewFrames[View::Home]; // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true grp = config->group("General Options"); if (grp.readEntry("SkipFix", false) != true) { MyMoneyFileTransaction ft; try { // Check if we have to modify the file before we allow to work with it auto s = MyMoneyFile::instance()->storage(); while (s->fileFixVersion() < s->currentFixVersion()) { qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); switch (s->fileFixVersion()) { case 0: fixFile_0(); s->setFileFixVersion(1); break; case 1: fixFile_1(); s->setFileFixVersion(2); break; case 2: fixFile_2(); s->setFileFixVersion(3); break; case 3: fixFile_3(); s->setFileFixVersion(4); break; // add new levels above. Don't forget to increase currentFixVersion() for all // the storage backends this fix applies to default: throw MYMONEYEXCEPTION(i18n("Unknown fix level in input file")); } } ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } } else { qDebug("Skipping automatic transaction fix!"); } MyMoneyFile::instance()->blockSignals(blocked); // FIXME: we need to check, if it's necessary to have this // automatic funcitonality // if there's no asset account, then automatically start the // new account wizard // kmymoney->createInitialAccount(); m_fileOpen = true; emit kmmFilePlugin(postOpen); Models::instance()->fileOpened(); // connect the needed signals connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); // inform everyone about new data MyMoneyFile::instance()->preloadCache(); MyMoneyFile::instance()->forceDataChanged(); // views can wait since they are going to be refresed in slotRefreshViews connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); // if we currently see a different page, then select the right one if (page != currentPage()) { showPage(page); } emit fileOpened(); return true; } void KMyMoneyView::saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList) { // Check GPG encryption bool encryptFile = true; bool encryptRecover = false; if (!keyList.isEmpty()) { if (!KGPGFile::GPGAvailable()) { KMessageBox::sorry(this, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); encryptFile = false; } else { if (KMyMoneyGlobalSettings::encryptRecover()) { encryptRecover = true; if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { KMessageBox::sorry(this, i18n("

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

%1

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

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

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

%1.

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

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

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

"); if (KMessageBox::questionYesNo(this, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { encryptFile = false; } } } } // Create a temporary file if needed QString writeFile = localFile; QTemporaryFile tmpFile; if (QFile::exists(localFile)) { tmpFile.open(); writeFile = tmpFile.fileName(); tmpFile.close(); } /** * @brief Automatically restore settings when scope is left */ struct restorePreviousSettingsHelper { restorePreviousSettingsHelper() : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()} { MyMoneyFile::instance()->blockSignals(true); } ~restorePreviousSettingsHelper() { MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); } const bool m_signalsWereBlocked; } restoreHelper; MyMoneyFileTransaction ft; MyMoneyFile::instance()->deletePair("kmm-encryption-key"); std::unique_ptr device; if (!keyList.isEmpty() && encryptFile && !plaintext) { std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); if (kgpg) { for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { kgpg->addRecipient(key.toLatin1()); } if (encryptRecover) { kgpg->addRecipient(recoveryKeyId); } MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); device = std::unique_ptr(kgpg.release()); } } else { QFile *file = new QFile(writeFile); // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); } ft.commit(); if (!device || !device->open(QIODevice::WriteOnly)) { throw MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.", localFile)); } pWriter->setProgressCallback(&KMyMoneyView::progressCallback); pWriter->writeFile(device.get(), MyMoneyFile::instance()->storage()); device->close(); // Check for errors if possible, only possible for KGPGFile QFileDevice *fileDevice = qobject_cast(device.get()); if (fileDevice && fileDevice->error() != QFileDevice::NoError) { throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } if (writeFile != localFile) { // This simple comparison is possible because the strings are equal if no temporary file was created. // If a temporary file was created, it is made in a way that the name is definitely different. So no // symlinks etc. have to be evaluated. if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } QFile::setPermissions(localFile, m_fmode); pWriter->setProgressCallback(0); } bool KMyMoneyView::saveFile(const QUrl &url, const QString& keyList) { QString filename = url.path(); if (!fileOpen()) { KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); return false; } emit kmmFilePlugin(preSave); std::unique_ptr storageWriter; // If this file ends in ".ANON.XML" then this should be written using the // anonymous writer. bool plaintext = filename.right(4).toLower() == ".xml"; if (filename.right(9).toLower() == ".anon.xml") { //! @todo C++14: use std::make_unique, also some lines below storageWriter = std::unique_ptr(new MyMoneyStorageANON); } else { storageWriter = std::unique_ptr(new MyMoneyStorageXML); } // actually, url should be the parameter to this function // but for now, this would involve too many changes bool rc = true; try { if (! url.isValid()) { throw MYMONEYEXCEPTION(i18n("Malformed URL '%1'", url.url())); } if (url.isLocalFile()) { filename = url.toLocalFile(); try { const unsigned int nbak = KMyMoneyGlobalSettings::autoBackupCopies(); if (nbak) { KBackup::numberedBackupFile(filename, QString(), QString::fromLatin1("~"), nbak); } saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); } catch (const MyMoneyException &) { throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); } } else { QTemporaryFile tmpfile; tmpfile.open(); // to obtain the name tmpfile.close(); saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); Q_CONSTEXPR int permission = -1; QFile file(tmpfile.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); if (!putjob->exec()) { throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'.
%2", url.toDisplayString(), putjob->errorString())); } file.close(); } m_fileType = KmmXML; } catch (const MyMoneyException &e) { KMessageBox::error(this, e.what()); MyMoneyFile::instance()->setDirty(); rc = false; } emit kmmFilePlugin(postSave); return rc; } bool KMyMoneyView::dirty() { if (!fileOpen()) return false; return MyMoneyFile::instance()->dirty(); } void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) { Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); m_ledgerView->slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); } void KMyMoneyView::newFile() { closeFile(); m_fileType = KmmXML; // assume native type until saved m_fileOpen = true; } void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) { if (!baseCurrency.id().isEmpty()) { QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseCurrency.id() != baseId) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(baseCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), e.what()), i18n("Set base currency")); } } AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); } } void KMyMoneyView::selectBaseCurrency() { auto file = MyMoneyFile::instance(); // check if we have a base currency. If not, we need to select one QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); dlg->exec(); delete dlg; } try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (!baseId.isEmpty()) { // check that all accounts have a currency QList list; file->accountList(list); QList::Iterator it; // don't forget those standard accounts list << file->asset(); list << file->liability(); list << file->income(); list << file->expense(); list << file->equity(); for (it = list.begin(); it != list.end(); ++it) { QString cid; try { if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); } catch (const MyMoneyException &) { } if (cid.isEmpty()) { (*it).setCurrencyId(baseId); MyMoneyFileTransaction ft; try { file->modifyAccount(*it); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), qPrintable(e.what())); } } } } } void KMyMoneyView::updateCurrencyNames() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QList storedCurrencies = MyMoneyFile::instance()->currencyList(); QList availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); QStringList currencyIDs; foreach (auto currency, availableCurrencies) currencyIDs.append(currency.id()); try { foreach (auto currency, storedCurrencies) { int i = currencyIDs.indexOf(currency.id()); if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { currency.setName(availableCurrencies.at(i).name()); file->modifyCurrency(currency); } } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s updating currency names", qPrintable(e.what())); } } void KMyMoneyView::loadAllCurrencies() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; if (!file->currencyList().isEmpty()) return; QMap ancientCurrencies = file->ancientCurrencies(); try { foreach (auto currency, file->availableCurrencyList()) { file->addCurrency(currency); MyMoneyPrice price = ancientCurrencies.value(currency, MyMoneyPrice()); if (price != MyMoneyPrice()) file->addPrice(price); } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s loading currency", qPrintable(e.what())); } } void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) { if (viewFrames[View::Accounts] != currentPage()) showPage(viewFrames[View::Accounts]); m_accountsView->show(); } void KMyMoneyView::slotRefreshViews() { // turn off sync between ledger and investment view disconnect(m_investmentView, &KInvestmentView::accountSelected, m_ledgerView, static_cast(&KGlobalLedgerView::slotSelectAccount)); disconnect(m_ledgerView, &KGlobalLedgerView::objectSelected, m_investmentView, static_cast(&KInvestmentView::slotSelectAccount)); // TODO turn sync between ledger and investment view if selected by user if (KMyMoneyGlobalSettings::syncLedgerInvestment()) { connect(m_investmentView, &KInvestmentView::accountSelected, m_ledgerView, static_cast(&KGlobalLedgerView::slotSelectAccount)); connect(m_ledgerView, &KGlobalLedgerView::objectSelected, m_investmentView, static_cast(&KInvestmentView::slotSelectAccount)); } showTitleBar(KMyMoneyGlobalSettings::showTitleBar()); m_accountsView->refresh(); m_institutionsView->refresh(); m_categoriesView->refresh(); m_payeesView->refresh(); m_tagsView->refresh(); m_ledgerView->refresh(); m_budgetView->refresh(); m_homeView->refresh(); m_investmentView->refresh(); m_reportsView->refresh(); - m_forecastView->refresh(); m_scheduledView->refresh(); + for (auto i = (int)View::Home; i < (int)View::None; ++i) + if (viewBases.contains(View(i))) + viewBases[View(i)]->refresh(); m_payeesView->slotClosePayeeIdentifierSource(); } void KMyMoneyView::slotShowTransactionDetail(bool detailed) { KMyMoneyGlobalSettings::setShowRegisterDetailed(detailed); slotRefreshViews(); } void KMyMoneyView::progressCallback(int current, int total, const QString& msg) { kmymoney->progressCallback(current, total, msg); } void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex) { // remember the current page m_lastViewSelected = current.row(); // set the current page's title in the header if (m_header) m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); } /* DO NOT ADD code to this function or any of it's called ones. Instead, create a new function, fixFile_n, and modify the initializeStorage() logic above to call it */ void KMyMoneyView::fixFile_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void KMyMoneyView::fixFile_2() { auto file = MyMoneyFile::instance(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); // scan the transactions and modify transactions with two splits // which reference an account and a category to have the memo text // of the account. auto count = 0; foreach (const auto transaction, transactionList) { if (transaction.splitCount() == 2) { QString accountId; QString categoryId; QString accountMemo; QString categoryMemo; foreach (const auto split, transaction.splits()) { auto acc = file->account(split.accountId()); if (acc.isIncomeExpense()) { categoryId = split.id(); categoryMemo = split.memo(); } else { accountId = split.id(); accountMemo = split.memo(); } } if (!accountId.isEmpty() && !categoryId.isEmpty() && accountMemo != categoryMemo) { MyMoneyTransaction t(transaction); MyMoneySplit s(t.splitById(categoryId)); s.setMemo(accountMemo); t.modifySplit(s); file->modifyTransaction(t); ++count; } } } qDebug("%d transactions fixed in fixFile_2", count); } void KMyMoneyView::fixFile_1() { // we need to fix reports. If the account filter list contains // investment accounts, we need to add the stock accounts to the list // as well if we don't have the expert mode enabled if (!KMyMoneyGlobalSettings::expertMode()) { try { QList reports = MyMoneyFile::instance()->reportList(); QList::iterator it_r; for (it_r = reports.begin(); it_r != reports.end(); ++it_r) { QStringList list; (*it_r).accounts(list); QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } if (!missing.isEmpty()) { (*it_r).addAccount(missing); MyMoneyFile::instance()->modifyReport(*it_r); } } } catch (const MyMoneyException &) { } } } #if 0 if (!m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; m_accountsView->selectedItems(list); // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected if (!KMyMoneyGlobalSettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.begin(); it_a != list.end(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } list += missing; } m_filter.addAccount(list); } #endif void KMyMoneyView::fixFile_0() { /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the * file, it is really a warning. So I'm going to print a debug warning, and * then go track them down when I see them to figure out how they got saved * out needing fixing anyway. */ auto file = MyMoneyFile::instance(); QList accountList; file->accountList(accountList); QList::Iterator it_a; QList scheduleList = file->scheduleList(); QList::Iterator it_s; MyMoneyAccount equity = file->equity(); MyMoneyAccount asset = file->asset(); bool equityListEmpty = equity.accountList().count() == 0; for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { if ((*it_a).accountType() == Account::Type::Loan || (*it_a).accountType() == Account::Type::AssetLoan) { fixLoanAccount_0(*it_a); } // until early before 0.8 release, the equity account was not saved to // the file. If we have an equity account with no sub-accounts but // find and equity account that has equity() as it's parent, we reparent // this account. Need to move it to asset() first, because otherwise // MyMoneyFile::reparent would act as NOP. if (equityListEmpty && (*it_a).accountType() == Account::Type::Equity) { if ((*it_a).parentAccountId() == equity.id()) { auto acc = *it_a; // tricky, force parent account to be empty so that we really // can re-parent it acc.setParentAccountId(QString()); file->reparentAccount(acc, equity); qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); } } } for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { fixSchedule_0(*it_s); } fixTransactions_0(); } void KMyMoneyView::fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; bool updated = false; try { // Check if the splits contain valid data and set it to // be valid. for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) { // the first split is always the account on which this transaction operates // and if the transaction commodity is not set, we take this if (it_s == splitList.constBegin() && t.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; try { auto acc = MyMoneyFile::instance()->account((*it_s).accountId()); t.setCommodity(acc.currencyId()); updated = true; } catch (const MyMoneyException &) { } } // make sure the account exists. If not, remove the split try { MyMoneyFile::instance()->account((*it_s).accountId()); } catch (const MyMoneyException &) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; t.removeSplit(*it_s); updated = true; } if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; MyMoneySplit split = *it_s; split.setReconcileDate(QDate()); split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); t.modifySplit(split); updated = true; } // the schedule logic used to operate only on the value field. // This is now obsolete. if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) { MyMoneySplit split = *it_s; split.setShares(split.value()); t.modifySplit(split); updated = true; } } // If there have been changes, update the schedule and // the engine data. if (updated) { sched.setTransaction(t); MyMoneyFile::instance()->modifySchedule(sched); } } catch (const MyMoneyException &e) { qWarning("Unable to update broken schedule: %s", qPrintable(e.what())); } } void KMyMoneyView::fixLoanAccount_0(MyMoneyAccount acc) { if (acc.value("final-payment").isEmpty() || acc.value("term").isEmpty() || acc.value("periodic-payment").isEmpty() || acc.value("loan-amount").isEmpty() || acc.value("interest-calculation").isEmpty() || acc.value("schedule").isEmpty() || acc.value("fixed-interest").isEmpty()) { KMessageBox::information(this, i18n("

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

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

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

" , acc.name()), i18n("Account problem")); throw MYMONEYEXCEPTION("Fix LoanAccount0 not supported anymore"); } } void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { MyMoneyFileTransaction ft; try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } // now search the split that does not have an account reference // and set it up to be the one of the account we just added // to the account pool. Note: the schedule code used to leave // this always the first split, but the loan code leaves it as // the second one. So I thought, searching is a good alternative .... foreach (const auto split, t.splits()) { if (split.accountId().isEmpty()) { MyMoneySplit s = split; s.setAccountId(newAccount.id()); t.modifySplit(s); break; } } newSchedule.setTransaction(t); MyMoneyFile::instance()->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.isLoan()) { newAccount.setValue("schedule", newSchedule.id()); MyMoneyFile::instance()->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add schedule: %1", e.what())); } } } void KMyMoneyView::fixTransactions_0() { auto file = MyMoneyFile::instance(); QList scheduleList = file->scheduleList(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); QList::Iterator it_x; QStringList interestAccounts; KMSTATUS(i18n("Fix transactions")); kmymoney->slotStatusProgressBar(0, scheduleList.count() + transactionList.count()); int cnt = 0; // scan the schedules to find interest accounts for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) { MyMoneyTransaction t = (*it_x).transaction(); QList::ConstIterator it_s; QStringList accounts; bool hasDuplicateAccounts = false; foreach (const auto split, t.splits()) { if (accounts.contains(split.accountId())) { hasDuplicateAccounts = true; qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId(); } else { accounts << split.accountId(); } if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { if (interestAccounts.contains(split.accountId()) == 0) { interestAccounts << split.accountId(); } } } if (hasDuplicateAccounts) { fixDuplicateAccounts_0(t); } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } // scan the transactions and modify loan transactions for (auto& transaction : transactionList) { QString defaultAction; QList splits = transaction.splits(); QStringList accounts; // check if base commodity is set. if not, set baseCurrency if (transaction.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency"; transaction.setCommodity(file->baseCurrency().id()); file->modifyTransaction(transaction); } bool isLoan = false; // Determine default action if (transaction.splitCount() == 2) { // check for transfer int accountCount = 0; MyMoneyMoney val; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); if (acc.accountGroup() == Account::Type::Asset || acc.accountGroup() == Account::Type::Liability) { val = split.value(); accountCount++; if (acc.accountType() == Account::Type::Loan || acc.accountType() == Account::Type::AssetLoan) isLoan = true; } else break; } if (accountCount == 2) { if (isLoan) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer); } else { if (val.isNegative()) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); } } isLoan = false; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); MyMoneyMoney val = split.value(); if (acc.accountGroup() == Account::Type::Asset || acc.accountGroup() == Account::Type::Liability) { if (!val.isPositive()) { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); break; } else { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); break; } } } #if 0 // Check for correct actions in transactions referencing credit cards bool needModify = false; // The action fields are actually not used anymore in the ledger view logic // so we might as well skip this whole thing here! for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) { auto acc = file->account((*it_s).accountId()); MyMoneyMoney val = (*it_s).value(); if (acc.accountType() == Account::Type::CreditCard) { if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; } } // (Ace) Extended the #endif down to cover this conditional, because as-written // it will ALWAYS be skipped. if (needModify == true) { for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { (*it_s).setAction(defaultAction); transaction.modifySplit(*it_s); file->modifyTransaction(transaction); } splits = transaction.splits(); // update local copy qDebug("Fixed credit card assignment in %s", transaction.id().data()); } #endif // Check for correct assignment of ActionInterest in all splits // and check if there are any duplicates in this transactions for (auto& split : splits) { MyMoneyAccount splitAccount = file->account(split.accountId()); if (!accounts.contains(split.accountId())) { accounts << split.accountId(); } // if this split references an interest account, the action // must be of type ActionInterest if (interestAccounts.contains(split.accountId())) { if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest"; split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } // if it does not reference an interest account, it must not be // of type ActionInterest } else { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest"; split.setAction(defaultAction); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } } // check that for splits referencing an account that has // the same currency as the transactions commodity the value // and shares field are the same. if (transaction.commodity() == splitAccount.currencyId() && split.value() != split.shares()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value"; split.setShares(split.value()); transaction.modifySplit(split); file->modifyTransaction(transaction); } // fix the shares and values to have the correct fraction if (!splitAccount.isInvest()) { try { int fract = splitAccount.fraction(); if (split.shares() != split.shares().convert(fract)) { qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id())); split.setShares(split.shares().convert(fract)); split.setValue(split.value().convert(fract)); transaction.modifySplit(split); file->modifyTransaction(transaction); } } catch (const MyMoneyException &) { qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); } } } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } kmymoney->slotStatusProgressBar(-1, -1); } void KMyMoneyView::fixDuplicateAccounts_0(MyMoneyTransaction& t) { qDebug("Duplicate account in transaction %s", qPrintable(t.id())); } void KMyMoneyView::slotPrintView() { if (viewFrames[View::Reports] == currentPage()) m_reportsView->slotPrintView(); else if (viewFrames[View::Home] == currentPage()) m_homeView->slotPrintView(); } void KMyMoneyView::resetViewSelection(const View) { emit aboutToChangeView(); } void KMyMoneyView::connectView(const View view) { KMyMoneyAccountTreeView *treeView; switch (view) { case View::Home: disconnect(m_homeView, &KHomeView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_homeView, &KHomeView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_homeView, &KHomeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); break; case View::Accounts: disconnect(m_accountsView, &KAccountsView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_accountsView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_accountsView, &KAccountsView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(Models::instance()->accountsModel(), &AccountsModel::netWorthChanged, m_accountsView, &KAccountsView::slotNetWorthChanged); break; case View::Schedules: disconnect(m_scheduledView, &KScheduledView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_scheduledView, &KScheduledView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(m_scheduledView, &KScheduledView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_scheduledView, &KScheduledView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); break; case View::Institutions: disconnect(m_institutionsView, &KInstitutionsView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_institutionsView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_institutionsView, &KInstitutionsView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(Models::instance()->institutionsModel(), &AccountsModel::netWorthChanged, m_institutionsView, &KInstitutionsView::slotNetWorthChanged); break; case View::Categories: disconnect(m_categoriesView, &KCategoriesView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_categoriesView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_categoriesView, &KCategoriesView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(Models::instance()->institutionsModel(), &AccountsModel::profitChanged, m_categoriesView, &KCategoriesView::slotProfitChanged); break; case View::Tags: disconnect(m_tagsView, &KTagsView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_tagsView, &KTagsView::transactionSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); break; case View::Payees: disconnect(m_payeesView, &KTagsView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_payeesView, &KPayeesView::transactionSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); break; case View::Ledgers: disconnect(m_ledgerView, &KGlobalLedgerView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_ledgerView, &KGlobalLedgerView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(m_ledgerView, &KGlobalLedgerView::openPayeeRequested, this, &KMyMoneyView::slotPayeeSelected); connect(m_ledgerView, &KGlobalLedgerView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_ledgerView, &KGlobalLedgerView::transactionsSelected, this, &KMyMoneyView::slotTransactionsSelected); connect(m_ledgerView, &KGlobalLedgerView::transactionsContextMenuRequested, this, &KMyMoneyView::slotTransactionsMenuRequested); connect(m_ledgerView, &KGlobalLedgerView::statusProgress, this, &KMyMoneyView::statusProgress); connect(m_ledgerView, &KGlobalLedgerView::statusMsg, this, &KMyMoneyView::statusMsg); connect(m_ledgerView, &KGlobalLedgerView::accountReconciled, this, &KMyMoneyView::accountReconciled); connect(m_ledgerView, &KGlobalLedgerView::enterOverdueSchedulesRequested, m_scheduledView, &KScheduledView::slotEnterOverdueSchedules); connect(m_scheduledView, &KScheduledView::enterOverdueSchedulesFinished, m_ledgerView, &KGlobalLedgerView::slotContinueReconciliation); break; case View::Budget: disconnect(m_budgetView, &KBudgetView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_budgetView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_budgetView, &KBudgetView::objectSelected, this, &KMyMoneyView::slotObjectSelected); break; case View::Investments: disconnect(m_investmentView, &KInvestmentView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_investmentView, &KInvestmentView::accountSelected, kmymoney, &KMyMoneyApp::slotSelectAccount); connect(m_investmentView, &KInvestmentView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_investmentView, &KInvestmentView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); break; case View::Reports: disconnect(m_reportsView, &KReportsView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_reportsView, &KReportsView::transactionSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); break; - case View::Forecast: - disconnect(m_forecastView, &KForecastView::aboutToShow, this, &KMyMoneyView::connectView); - break; - case View::OnlineJobOutbox: disconnect(m_onlineJobOutboxView, &KOnlineJobOutbox::aboutToShow, this, &KMyMoneyView::connectView); break; default: break; } } void KMyMoneyView::slotOpenObjectRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); // check if we can open this account // currently it make's sense for asset and liability accounts if (!MyMoneyFile::instance()->isStandardAccount(acc.id())) m_ledgerView->slotLedgerSelected(acc.id(), QString()); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { // const auto& inst = static_cast(obj); m_institutionsView->slotEditInstitution(); } else if (typeid(obj) == typeid(MyMoneySchedule)) { m_scheduledView->slotEditSchedule(); } else if (typeid(obj) == typeid(MyMoneyReport)) { const auto& rep = static_cast(obj); m_reportsView->slotOpenReport(rep); } } void KMyMoneyView::slotObjectSelected(const MyMoneyObject& obj) { // carrying some slots over to views isn't easy for all slots... // ...so calls to kmymoney still must be here if (typeid(obj) == typeid(MyMoneyAccount)) { kmymoney->slotSelectAccount(obj); m_investmentView->updateActions(obj); m_categoriesView->updateActions(obj); m_accountsView->updateActions(obj); m_ledgerView->updateActions(obj); m_reportsView->updateActions(obj); m_onlineJobOutboxView->updateActions(obj); // for plugin only const auto& acc = static_cast(obj); if (!acc.isIncomeExpense() && !MyMoneyFile::instance()->isStandardAccount(acc.id())) emit accountSelected(acc); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { m_institutionsView->updateActions(obj); } else if (typeid(obj) == typeid(MyMoneySchedule)) { kmymoney->slotUpdateActions(); m_scheduledView->updateActions(obj); } } void KMyMoneyView::slotContextMenuRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); if (acc.isInvest()) { m_investmentView->slotShowInvestmentMenu(acc); return; } else if (acc.isIncomeExpense()) { m_categoriesView->slotShowCategoriesMenu(acc); } else { m_accountsView->slotShowAccountMenu(acc); } } else if (typeid(obj) == typeid(MyMoneyInstitution)) { const auto& inst = static_cast(obj); m_institutionsView->slotShowInstitutionsMenu(inst); } else if (typeid(obj) == typeid(MyMoneySchedule)) { const auto& sch = static_cast(obj); m_scheduledView->slotShowScheduleMenu(sch); } } void KMyMoneyView::slotTransactionsMenuRequested(const KMyMoneyRegister::SelectedTransactions& list) { Q_UNUSED(list) m_ledgerView->slotShowTransactionMenu(MyMoneySplit()); } void KMyMoneyView::slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list) { m_ledgerView->updateLedgerActions(list); emit transactionsSelected(list); // for plugins } diff --git a/kmymoney/views/kmymoneyview.h b/kmymoney/views/kmymoneyview.h index c27f0ffc3..0ef62427c 100644 --- a/kmymoney/views/kmymoneyview.h +++ b/kmymoney/views/kmymoneyview.h @@ -1,556 +1,560 @@ /*************************************************************************** kmymoneyview.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEYVIEW_H #define KMYMONEYVIEW_H #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "selectedtransactions.h" #ifdef KF5Activities_FOUND namespace KActivities { class ResourceInstance; } #endif namespace eAccountsModel { enum class Column; } namespace eMenu { enum class Action; } namespace KMyMoneyPlugin { class OnlinePlugin; } namespace KMyMoneyPlugin { class StoragePlugin; } namespace eDialogs { enum class ScheduleResultCode; } +namespace Icons { enum class Icon; } class KMyMoneyApp; class KHomeView; class KAccountsView; class KCategoriesView; class KInstitutionsView; class KPayeesView; class KTagsView; class KBudgetView; class KScheduledView; class KGlobalLedgerView; class IMyMoneyOperationsFormat; class MyMoneyTransaction; class KInvestmentView; class KReportsView; class MyMoneySchedule; class MyMoneySecurity; class MyMoneyReport; class TransactionEditor; -class KForecastView; class KOnlineJobOutbox; class KMyMoneyTitleLabel; class MyMoneyAccount; class MyMoneyMoney; class MyMoneyObject; class QLabel; +class KMyMoneyViewBase; /** * This class represents the view of the MyMoneyFile which contains * Banks/Accounts/Transactions, Recurring transactions (or Bills & Deposits) * and scripts (yet to be implemented). Each different aspect of the file * is represented by a tab within the view. * * @author Michael Edwardes 2001 Copyright 2000-2001 * * @short Handles the view of the MyMoneyFile. */ enum class View; class KMyMoneyView : public KPageWidget { Q_OBJECT public: // file actions for plugin enum fileActions { preOpen, postOpen, preSave, postSave, preClose, postClose }; private: enum menuID { AccountNew = 1, AccountOpen, AccountReconcile, AccountEdit, AccountDelete, AccountOnlineMap, AccountOnlineUpdate, AccountOfxConnect, CategoryNew }; enum storageTypeE { Memory = 0, Database } _storageType; KPageWidgetModel* m_model; KHomeView *m_homeView; KAccountsView *m_accountsView; KInstitutionsView *m_institutionsView; KCategoriesView *m_categoriesView; KPayeesView *m_payeesView; KTagsView *m_tagsView; KBudgetView *m_budgetView; KScheduledView *m_scheduledView; KGlobalLedgerView *m_ledgerView; KInvestmentView *m_investmentView; KReportsView* m_reportsView; - KForecastView* m_forecastView; KOnlineJobOutbox* m_onlineJobOutboxView; - QHash viewFrames; + QHash viewFrames; + QHash viewBases; KMyMoneyTitleLabel* m_header; bool m_inConstructor; bool m_fileOpen; QFileDevice::Permissions m_fmode; int m_lastViewSelected; QMap* m_storagePlugins; // Keep a note of the file type typedef enum _fileTypeE { KmmBinary = 0, // native, binary KmmXML, // native, XML KmmDb, // SQL database /* insert new native file types above this line */ MaxNativeFileType, /* and non-native types below */ GncXML // Gnucash XML } fileTypeE; fileTypeE m_fileType; #ifdef KF5Activities_FOUND private: KActivities::ResourceInstance * m_activityResourceInstance; #endif private: void ungetString(QIODevice *qfile, char * buf, int len); /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency(); /** * This method attaches an empty storage object to the MyMoneyFile * object. It calls removeStorage() to remove a possibly attached * storage object. */ void newStorage(); /** * This method removes an attached storage from the MyMoneyFile * object. */ void removeStorage(); void viewAccountList(const QString& selectAccount); // Show the accounts view static void progressCallback(int current, int total, const QString&); /** */ void fixFile_0(); void fixFile_1(); void fixFile_2(); void fixFile_3(); /** */ void fixLoanAccount_0(MyMoneyAccount acc); /** */ void fixTransactions_0(); void fixSchedule_0(MyMoneySchedule sched); void fixDuplicateAccounts_0(MyMoneyTransaction& t); void createSchedule(MyMoneySchedule s, MyMoneyAccount& a); void checkAccountName(const MyMoneyAccount& acc, const QString& name) const; public: /** * The constructor for KMyMoneyView. Just creates all the tabs for the * different aspects of the MyMoneyFile. */ explicit KMyMoneyView(KMyMoneyApp *kmymoney); /** * Destructor */ ~KMyMoneyView(); /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen(); /** * Closes the open MyMoneyFile and frees all the allocated memory, I hope ! */ void closeFile(); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ bool readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader = nullptr); /** * Saves the data into permanent storage using the XML format. * * @param url The URL to save into. * If no protocol is specified, file:// is assumed. * @param keyList QString containing a comma separated list of keys * to be used for encryption. If @p keyList is empty, * the file will be saved unencrypted (the default) * * @retval false save operation failed * @retval true save operation was successful */ bool saveFile(const QUrl &url, const QString& keyList = QString()); /** * Call this to find out if the currently open file is native KMM * * @retval true file is native * @retval false file is foreign */ bool isNativeFile() { return (m_fileOpen && (m_fileType < MaxNativeFileType)); } /** * Call this to find out if the currently open file is a sql database * * @retval true file is database * @retval false file is serial */ bool isDatabase() { return (m_fileOpen && ((m_fileType == KmmDb))); } /** * Call this to see if the MyMoneyFile contains any unsaved data. * * @retval true if any data has been modified but not saved * @retval false otherwise */ bool dirty(); /** * Close the currently opened file and create an empty new file. * * @see MyMoneyFile */ void newFile(); /** * This method enables the state of all views (except home view) according * to an open file. */ void enableViewsIfFileOpen(); void addWidget(QWidget* w); void showPage(KPageWidgetItem* pageItem); /** * check if the current view allows to print something * * @retval true Yes, view allows to print * @retval false No, view cannot print */ bool canPrint(); void finishReconciliation(const MyMoneyAccount& account); /** * This method updates names of currencies from file to localized names */ void updateCurrencyNames(); /** * This method loads all known currencies and saves them to the storage */ void loadAllCurrencies(); void showTitleBar(bool show); /** * This method changes the view type according to the settings. */ void updateViewType(); void slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show); void setOnlinePlugins(QMap& plugins); void setStoragePlugins(QMap& plugins); // TODO: remove that function /** * ugly proxy function */ eDialogs::ScheduleResultCode enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys); + void addView(KMyMoneyViewBase* view, const QString& name, View idView); + void removeView(View idView); + protected: /** * Overwritten because KMyMoney has it's custom header. */ virtual bool showPageHeader() const; public Q_SLOTS: /** * This slot writes information about the page passed as argument @a current * in the kmymoney.rc file so that it can be selected automatically when * the application is started again. * * @param current QModelIndex of the current page item * @param previous QModelIndex of the previous page item */ void slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous); /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ void slotRefreshViews(); /** * Called, whenever the payees view should pop up and a specific * transaction in an account should be shown. * * @param payeeId The ID of the payee to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotPayeeSelected(const QString& payeeId, const QString& accountId, const QString& transactionId); /** * Called, whenever the tags view should pop up and a specific * transaction in an account should be shown. * * @param tagId The ID of the tag to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotTagSelected(const QString& tagId, const QString& accountId, const QString& transactionId); /** * This slot prints the current view. */ void slotPrintView(); /** * Called when the user changes the detail * setting of the transaction register * * @param detailed if true, the register is shown with all details */ void slotShowTransactionDetail(bool detailed); /** * Informs respective views about selected object, so they can * update action states and current object. * @param obj Account, Category, Investment, Stock, Institution */ void slotObjectSelected(const MyMoneyObject& obj); void slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list); private Q_SLOTS: /** * This slots switches the view to the specific page */ void slotShowHomePage(); void slotShowInstitutionsPage(); void slotShowAccountsPage(); void slotShowSchedulesPage(); void slotShowCategoriesPage(); void slotShowTagsPage(); void slotShowPayeesPage(); void slotShowLedgersPage(); void slotShowInvestmentsPage(); void slotShowReportsPage(); void slotShowBudgetPage(); void slotShowForecastPage(); void slotShowOutboxPage(); /** * Opens object in ledgers or edits in case of institution * @param obj Account, Category, Investment, Stock, Institution */ void slotOpenObjectRequested(const MyMoneyObject& obj); /** * Opens context menu based on objects's type * @param obj Account, Category, Investment, Stock, Institution */ void slotContextMenuRequested(const MyMoneyObject& obj); void slotTransactionsMenuRequested(const KMyMoneyRegister::SelectedTransactions& list); void slotSwitchView(View view); protected Q_SLOTS: /** * eventually replace this with KMyMoneyApp::slotCurrencySetBase(). * it contains the same code * * @deprecated */ void slotSetBaseCurrency(const MyMoneySecurity& baseCurrency); private: /** * This method is called from readFile to open a database file which * is to be processed in 'proper' database mode, i.e. in-place updates * * @param dbaseURL pseudo-QUrl representation of database * * @retval true Database opened successfully * @retval false Could not open or read database */ bool openDatabase(const QUrl &dbaseURL); /** * This method is used after a file or database has been * read into storage, and performs various initialization tasks * * @retval true all went okay * @retval false an exception occurred during this process */ bool initializeStorage(); /** * This method is used by saveFile() to store the data * either directly in the destination file if it is on * the local file system or in a temporary file when * the final destination is reached over a network * protocol (e.g. FTP) * * @param localFile the name of the local file * @param writer pointer to the formatter * @param plaintext whether to override any compression & encryption settings * @param keyList QString containing a comma separated list of keys to be used for encryption * If @p keyList is empty, the file will be saved unencrypted * * @note This method will close the file when it is written. */ void saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* writer, bool plaintext = false, const QString& keyList = QString()); /** * Internal method used by slotAccountNew() and slotAccountCategory(). */ void accountNew(const bool createCategory); void resetViewSelection(const View); void connectView(const View); Q_SIGNALS: /** * This signal is emitted whenever a view is selected. * The parameter @p view is identified as one of KMyMoneyView::viewID. */ void viewActivated(int view); /** * This signal is emitted whenever a new view is about to be selected. */ void aboutToChangeView(); void accountSelectedForContextMenu(const MyMoneyAccount& acc); void viewStateChanged(bool enabled); /** * This signal is emitted to inform the kmmFile plugin when various file actions * occur. The Action parameter distinguishes between them. */ void kmmFilePlugin(unsigned int action); /** * This signal is emitted after a data source has been closed */ void fileClosed(); /** * This signal is emitted after a data source has been opened */ void fileOpened(); /** * @brief proxy signal */ void statusMsg(const QString& txt); /** * @brief proxy signal */ void statusProgress(int cnt, int base); void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); }; #endif diff --git a/kmymoney/widgets/CMakeLists.txt b/kmymoney/widgets/CMakeLists.txt index 0ab41f8b0..3afb98465 100644 --- a/kmymoney/widgets/CMakeLists.txt +++ b/kmymoney/widgets/CMakeLists.txt @@ -1,230 +1,229 @@ ########### create links ############### set(kmymoney_STAT_HEADERS kaccounttemplateselector.h kbudgetvalues.h kguiutils.h kmymoneyaccountcombo.h kmymoneyaccountcompletion.h kmymoneyaccountselector.h kmymoneycategory.h kmymoneycombo.h kmymoneymvccombo.h kmymoneycompletion.h kmymoneycurrencyselector.h kmymoneydateinput.h kmymoneyedit.h kmymoneylineedit.h kmymoneyselector.h kmymoneytitlelabel.h register.h registeritem.h groupmarker.h fancydategroupmarker.h scheduledtransaction.h selectedtransaction.h selectedtransactions.h stdtransactiondownloaded.h stdtransactionmatched.h transactioneditorcontainer.h transactionform.h transaction.h investtransaction.h stdtransaction.h transactionsortoption.h reporttabimpl.h reportcontrolimpl.h kmymoneyvalidationfeedback.h onlinejobmessagesview.h kmymoneydateedit.h amountedit.h amountvalidator.h creditdebithelper.h ) ########### Shared widget library ########### set(kmm_widgets_sources widgets_config.cpp kmymoneydateinput.cpp kmymoneyvalidationfeedback.cpp styleditemdelegateforwarder.cpp kmymoneyedit.cpp kmymoneylineedit.cpp kmymoneytextedit.cpp kmymoneytextedithighlighter.cpp kmymoneymvccombo.cpp kmymoneygeneralcombo.cpp kmymoneyactivitycombo.cpp kmymoneycashflowcombo.cpp kmymoneyfrequencycombo.cpp kmymoneyoccurrencecombo.cpp kmymoneyoccurrenceperiodcombo.cpp kmymoneypayeecombo.cpp kmymoneyperiodcombo.cpp kmymoneyreconcilecombo.cpp kmymoneytagcombo.cpp ktagcontainer.cpp ktaglabel.cpp kmymoneyselector.cpp kmymoneycalculator.cpp ktreewidgetfilterlinewidget.cpp kguiutils.cpp onlinejobmessagesview.cpp kmymoneydateedit.cpp kmymoneymoneyvalidator.cpp amountedit.cpp amountvalidator.cpp creditdebithelper.cpp ) ki18n_wrap_ui(kmm_widgets_sources kmymoneyvalidationfeedback.ui onlinejobmessagesview.ui ) add_library(kmm_widgets SHARED ${kmm_widgets_sources}) target_link_libraries(kmm_widgets PUBLIC KF5::TextWidgets KF5::KIOWidgets KF5::Completion KF5::Notifications KF5::ItemViews KF5::I18n Qt5::Gui Qt5::Sql Qt5::Core Alkimia::alkimia kmm_config kmm_mymoney ) set_target_properties(kmm_widgets PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} COMPILE_FLAGS "-DKMM_BUILD_WIDGETS_LIB" ) generate_export_header(kmm_widgets) install(TARGETS kmm_widgets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### Basic Widget Library (kmymoney_base) STATIC ################# # Common sources for libkmymoney.so and libwidgets.a that do not # contain the KMM_DESIGNER flag set(_uncritial_common_sources - fixedcolumntreeview.cpp kbudgetvalues.cpp kmymoneyaccountcombo.cpp kmymoneyaccountselector.cpp kmymoneyaccounttreeview.cpp accountsviewproxymodel.cpp budgetviewproxymodel.cpp kmymoneycombo.cpp kmymoneycompletion.cpp kmymoneytitlelabel.cpp kmymoneydateedit.cpp kpricetreeitem.cpp registeritem.cpp registerfilter.cpp scheduledtransaction.cpp selectedtransaction.cpp selectedtransactions.cpp stdtransactiondownloaded.cpp stdtransactionmatched.cpp transactionform.cpp tabbar.cpp transactionformitemdelegate.cpp transactionsortoption.cpp ) # sources that contain the KMM_DESIGNER flag set (_critial_common_sources kaccounttemplateselector.cpp kmymoneycurrencyselector.cpp kmymoneyaccountcompletion.cpp kmymoneycategory.cpp groupmarker.cpp groupmarkers.cpp fancydategroupmarker.cpp fancydategroupmarkers.cpp register.cpp itemptrvector.cpp qwidgetcontainer.cpp registeritemdelegate.cpp transaction.cpp stdtransaction.cpp investtransaction.cpp transactioneditorcontainer.cpp ) set (kmymoney_base_UI kbudgetvalues.ui transactionsortoption.ui kaccounttemplateselector.ui ) ki18n_wrap_ui(kmymoney_base_ui_srcs ${kmymoney_base_UI}) set(_uncritial_common_sources ${_uncritial_common_sources} ${kmymoney_base_ui_srcs}) # in order to use add_dependencies, we need to add this custom target # for all generated header files. # (see http://www.vtk.org/Wiki/CMake_FAQ#How_can_I_add_a_dependency_to_a_source_file_which_is_generated_in_a_subdirectory.3F ) add_custom_target(generate_base_ui_srcs DEPENDS ${kmymoney_base_ui_srcs}) # We can compile the uncritical sources without KMM_DESIGNER flags add_library(kmymoney_base STATIC ${_uncritial_common_sources}) # TODO: fix dependencies target_link_libraries(kmymoney_base KF5::XmlGui KF5::TextWidgets KF5::IconThemes KF5::I18n KF5::ConfigWidgets KF5::ConfigCore KF5::Completion KF5::Service Qt5::Gui Qt5::Widgets Qt5::Sql Qt5::Xml Alkimia::alkimia) add_dependencies(kmymoney_base kmm_config) ########### QtDesigner Widget Library (kmymoneywidgets) ################# # we never link against this library, # but it is needed for uic and QtDesigner if( USE_QT_DESIGNER ) set(kmymoneywidgets_PART_SRCS ${CMAKE_CURRENT_BINARY_DIR}/kmymoneywidgets.cpp) kde4_add_widget_files(kmymoneywidgets_PART_SRCS kmymoney.widgets) set(kmymoneywidgets_PART_SRCS ${_critial_common_sources} ${kmymoneywidgets_PART_SRCS}) add_library(kmymoneywidgets MODULE ${kmymoneywidgets_PART_SRCS}) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # The option -DKMM_DESIGNER will leave away any code that breaks uic. set_target_properties(kmymoneywidgets PROPERTIES COMPILE_FLAGS "-DKMM_DESIGNER") # The qt-designer widget library shouldn't need to link against the # dialogs and converter libraries. If a widget references something # from one of these libraries it is most likely due to code that needs # to be excluded with a KMM_DESIGNER ifndef. target_link_libraries(kmymoneywidgets kmymoney_base kmm_mymoney kmymoney_common kmm_config models) install(TARGETS kmymoneywidgets DESTINATION ${QT_PLUGINS_DIR}/designer ) endif( USE_QT_DESIGNER ) ########### Widget Library (widgets) STATIC ################# set(libwidgets_a_SOURCES ${_critial_common_sources} kmymoneybriefschedule.cpp registersearchline.cpp transactioneditorcontainer.cpp reporttabimpl.cpp reportcontrolimpl.cpp daterangedlg.cpp ) set(libwidgets_a_UI kmymoneybriefschedule.ui reportcontrol.ui reporttabgeneral.ui reporttabrowcolquery.ui reporttabrowcolpivot.ui reporttabrange.ui reporttabchart.ui reporttabcapitalgain.ui reporttabperformance.ui daterangedlg.ui ) # using uic on the above UI files DEPENDS on libkmymoney.so. If uic # does not find libkmymoney.so, gcc will fail compiling # kmymoneyreportconfigtab2decl.cpp and throw errors like "invalid use # of undefined type `struct KMyMoneyGeneralCombo'" ki18n_wrap_ui(widgets_ui_srcs ${libwidgets_a_UI}) add_custom_target(generate_widgets_ui_srcs DEPENDS ${widgets_ui_srcs}) add_library(widgets STATIC ${libwidgets_a_SOURCES} ${widgets_ui_srcs} ) target_link_libraries(widgets KF5::XmlGui kmymoney_base) add_dependencies(widgets kmm_config) ########### install files ############### install(FILES ${kmymoney_STAT_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kmm_widgets_export.h DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel)