diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ce41419f55..b5102e1e4ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1149 +1,1149 @@ cmake_minimum_required(VERSION 2.8.12) project(calligra) message(STATUS "Using CMake version: ${CMAKE_VERSION}") if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() # ensure out-of-source build string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" isBuildInSource) if(isBuildInSource) message(FATAL_ERROR "Compiling Calligra inside the source folder is not possible.\nPlease refer to the build instruction: https://community.kde.org/Calligra/Building/3\nYou need to clean up the source folder from all build artifacts just created, otherwise further building attempts will fail again: With a git repo, you can use \"git clean -df\" in the toplevel source folder (attention! will remove also uncommited changes to the source code). With sources from a file bundle (like a zip file), delete the source folder and unbundle the sources again.") endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Calligra applications, used to generate calligraversion.h # update these version for every release: set(CALLIGRA_VERSION_STRING "3.1.90") set(CALLIGRA_STABLE_VERSION_MAJOR 3) # 3 for 3.x, 4 for 4.x, etc. set(CALLIGRA_STABLE_VERSION_MINOR 1) # 0 for 3.0, 1 for 3.1, etc. set(CALLIGRA_VERSION_RELEASE 90) # 89 for Alpha, increase for next test releases, set 0 for first Stable, etc. #set(CALLIGRA_ALPHA 1) # uncomment only for Alpha set(CALLIGRA_BETA 1) # uncomment only for Beta #set(CALLIGRA_RC 1) # uncomment only for RC set(CALLIGRA_YEAR 2020) # update every year if(NOT DEFINED CALLIGRA_ALPHA AND NOT DEFINED CALLIGRA_BETA AND NOT DEFINED CALLIGRA_RC) set(CALLIGRA_STABLE 1) # do not edit endif() message(STATUS "Calligra version: ${CALLIGRA_VERSION_STRING}") # Define the generic version of the Calligra libraries here # This makes it easy to advance it when the next Calligra release comes. # 14 was the last GENERIC_CALLIGRA_LIB_VERSION_MAJOR of the previous Calligra series # (2.x) so we're starting with 15 in 3.x series. if(CALLIGRA_STABLE_VERSION_MAJOR EQUAL 3) math(EXPR GENERIC_CALLIGRA_LIB_VERSION_MAJOR "${CALLIGRA_STABLE_VERSION_MINOR} + 15") else() # let's make sure we won't forget to update the "15" message(FATAL_ERROR "Reminder: please update offset == 15 used to compute GENERIC_CALLIGRA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_CALLIGRA_LIB_VERSION "${GENERIC_CALLIGRA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_CALLIGRA_LIB_SOVERSION "${GENERIC_CALLIGRA_LIB_VERSION_MAJOR}") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules") message("Module path:" ${CMAKE_MODULE_PATH}) # fetch git revision for the current build set(CALLIGRA_GIT_SHA1_STRING "") set(CALLIGRA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_revision(GIT_REFSPEC GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1 AND GIT_BRANCH) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(CALLIGRA_GIT_SHA1_STRING ${GIT_SHA1}) set(CALLIGRA_GIT_BRANCH_STRING ${GIT_BRANCH}) endif() if(NOT DEFINED RELEASE_BUILD) # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) if (INDEX EQUAL -1) set(RELEASE_BUILD FALSE) else() set(RELEASE_BUILD TRUE) endif() endif() message(STATUS "Release build: ${RELEASE_BUILD}") # use CPP-11 if (CMAKE_VERSION VERSION_LESS "3.1") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") else () set (CMAKE_CXX_STANDARD 11) endif () ############ ############# ## Options ## ############# ############ option(GHNS "support Get Hot New Stuff" OFF) option(PACKAGERS_BUILD "Build support of multiple CPU architectures in one binary. Should be used by packagers only." ON) ####################### ######################## ## Productset setting ## ######################## ####################### # For predefined productsets see the definitions in CalligraProducts.cmake and # in the files in the folder cmake/productsets. # Finding out the products & features to build is done in 5 steps: # 1. have the user define the products/features wanted, by giving a productset # 2. estimate all additional required products/features # 3. estimate which of the products/features can be build by external deps # 4. find which products/features have been temporarily disabled due to problems # 5. estimate which of the products/features can be build by internal deps # get the special macros include(CalligraProductSetMacros) # get the definitions of products, features and product sets include(CalligraProducts.cmake) set(PRODUCTSET_DEFAULT "ALL") if(NOT PRODUCTSET) set(PRODUCTSET ${PRODUCTSET_DEFAULT} CACHE STRING "Set of products/features to build" FORCE) endif() if (RELEASE_BUILD) set(CALLIGRA_SHOULD_BUILD_STAGING FALSE) if(BUILD_UNMAINTAINED) set(CALLIGRA_SHOULD_BUILD_UNMAINTAINED TRUE) else() set(CALLIGRA_SHOULD_BUILD_UNMAINTAINED FALSE) endif() else () set(CALLIGRA_SHOULD_BUILD_STAGING TRUE) set(CALLIGRA_SHOULD_BUILD_UNMAINTAINED TRUE) endif () # finally choose products/features to build calligra_set_productset(${PRODUCTSET}) ########################## ########################### ## Look for ECM, Qt, KF5 ## ########################### ########################## find_package(ECM 5.19 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) # ECM KDE macros (include first, to have their policies and settings effect all other macros) include(KDEInstallDirs) include(KDECMakeSettings NO_POLICY_SCOPE) include(KDECompilerSettings NO_POLICY_SCOPE) # CMake macros include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(CheckTypeSize) include(CheckIncludeFile) include(GenerateExportHeader) include(FeatureSummary) # ECM macros include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMAddTests) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) # own macros include(MacroBoolTo01) include(MacroOptionalFindPackage) include(MacroEnsureVersion) include(MacroDesktopToJson) set(REQUIRED_KF5_VERSION "5.7.0") find_package(KF5 ${REQUIRED_KF5_VERSION} REQUIRED COMPONENTS Archive Codecs Completion Config ConfigWidgets CoreAddons DBusAddons DocTools GuiAddons I18n IconThemes ItemViews JobWidgets KCMUtils KDELibs4Support KIO Kross Notifications NotifyConfig Parts Sonnet TextWidgets Wallet WidgetsAddons WindowSystem XmlGui ) find_package(KF5Activities) find_package(KF5KHtml) set_package_properties(KF5Activities PROPERTIES TYPE OPTIONAL ) set_package_properties(KF5KHtml PROPERTIES PURPOSE "Required for HTML2ODS import filter" TYPE OPTIONAL ) if(KF5Activities_FOUND) set(HAVE_KACTIVITIES TRUE) endif() if(${KF5_VERSION} VERSION_LESS "5.16.0") set(CALLIGRA_OLD_PLUGIN_METADATA TRUE) endif() set(REQUIRED_QT_VERSION "5.3.0") find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Network PrintSupport Svg Test Widgets Xml ) find_package(Qt5 ${REQUIRED_QT_VERSION} QUIET COMPONENTS DBus OpenGL Quick QuickWidgets Sql ) # Qt5Declarative was removed in Qt 5.6.0 so search for it in a separate call # Including it in a collected find_package(Qt5 ...) call can lead to a fatal not-found error: # * Qt5 (required version >= 5.3.0) find_package(Qt5Declarative ${REQUIRED_QT_VERSION} QUIET) set_package_properties(Qt5DBus PROPERTIES TYPE RECOMMENDED ) set_package_properties(Qt5Declarative PROPERTIES PURPOSE "Required for QtQuick1 components" TYPE RECOMMENDED ) set_package_properties(Qt5OpenGL PROPERTIES PURPOSE "Required for QtQuick1 components" TYPE RECOMMENDED ) set_package_properties(Qt5Quick PROPERTIES PURPOSE "Required for QtQuick2 components" TYPE RECOMMENDED ) set_package_properties(Qt5QuickWidgets PROPERTIES PURPOSE "Required for Calligra Gemini" TYPE RECOMMENDED ) set_package_properties(Qt5Sql PROPERTIES PURPOSE "Optional for Sheets' database connection" TYPE OPTIONAL ) set_package_properties(Qt5WebKit PROPERTIES PURPOSE "Required for Braindump's Web shape" TYPE OPTIONAL ) set(HAVE_OPENGL ${Qt5OpenGL_FOUND}) if (GHNS) find_package(Attica 3.0) find_package(NewStuff) set_package_properties(Attica PROPERTIES DESCRIPTION "Attica is used for Get Hot New Stuff." URL "https://commits.kde.org/attica" TYPE OPTIONAL ) if (NOT LIBATTICA_FOUND) set(GHNS FALSE) else () message(STATUS "WARNING: You are compiling with Get Hot New Stuff enabled. Do not do that when building distribution packages. GHNS is unusable these days until someone starts maintaining it again.") endif () endif () find_package(X11) if(X11_FOUND) find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS X11Extras ) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) else() set(HAVE_X11 FALSE) endif() # use sane compile flags add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_TO_ASCII ) # only with this definition will all the FOO_TEST_EXPORT macro do something # TODO: check if this can be moved to only those places which make use of it, # to reduce global compiler definitions that would trigger a recompile of # everything on a change (like adding/removing tests to/from the build) if(BUILD_TESTING) add_definitions(-DCOMPILING_TESTS) endif() # overcome some platform incompatibilities if(WIN32) if(NOT MINGW) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) endif() set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### find_package(Perl REQUIRED) find_package(ZLIB REQUIRED) add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost REQUIRED COMPONENTS system) # for pigment and stage if (NOT Boost_FOUND) message(FATAL_ERROR "Did not find Boost. Boost is required for the core libraries, stage, sheets.") endif () ########################### ############################ ## Optional dependencies ## ############################ ########################### ## ## Check for OpenEXR ## macro_optional_find_package(OpenEXR) macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) ## ## Test for GNU Scientific Library ## macro_optional_find_package(GSL 1.7) set_package_properties(GSL_FOUND PROPERTIES DESCRIPTION "GNU Scientific Library" URL "https://www.gnu.org/software/gsl" PURPOSE "Required by Sheets' solver plugin" TYPE OPTIONAL ) macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) ## ## Test for Phonon4Qt5 ## find_package(Phonon4Qt5 QUIET) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Abstraction lib for multimedia applications" URL "https://www.kde.org/" PURPOSE "Required by Stage event actions and Videoshape plugin" TYPE OPTIONAL ) ## ## Test for KF5CalendarCore ## find_package(KF5CalendarCore CONFIG QUIET) set_package_properties(KF5CalendarCore PROPERTIES DESCRIPTION "KDE Calendar Library" URL "https://www.kde.org/" PURPOSE "Optionally used by semantic item Event" TYPE OPTIONAL ) ## ## Test for KF5Contacts ## find_package(KF5Contacts CONFIG QUIET) set_package_properties(KF5Contacts PROPERTIES DESCRIPTION "KDE Address book Library" URL "https://www.kde.org/" PURPOSE "Optionally used by semantic item Contact" TYPE OPTIONAL ) ## ## Test for KF5AkonadiCore ## find_package(KF5Akonadi CONFIG QUIET) set_package_properties(KF5Akonadi PROPERTIES DESCRIPTION "Library for general Access to Akonadi" URL "https://www.kde.org/" PURPOSE "Optionally used by semantic items Event and Contact" TYPE OPTIONAL ) ## ## Test for KChart ## -macro_optional_find_package(KChart 2.6.0 QUIET) +macro_optional_find_package(KChart 2.7.0 QUIET) set_package_properties(KChart PROPERTIES DESCRIPTION "Library for creating business charts (part of KDiagram)" URL "https://www.kde.org/" PURPOSE "Required by Chart shape" TYPE RECOMMENDED ) ## ## Test for eigen3 ## macro_optional_find_package(Eigen3) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" PURPOSE "Required by Calligra Sheets" TYPE RECOMMENDED ) ## ## Test for QCA2 ## macro_optional_find_package(Qca-qt5 2.1.0 QUIET) set_package_properties(Qca-qt5 PROPERTIES DESCRIPTION "Qt Cryptographic Architecture" URL "http:/download.kde.org/stable/qca-qt5" PURPOSE "Required for encrypted OpenDocument files and encrypted xls files support (available as a module in kdesupport)" TYPE OPTIONAL ) ## ## Test for soprano ## # QT5TODO: push for released (and maintained) Qt5 version of Soprano, T462, T461 # macro_optional_find_package(Soprano) set(Soprano_FOUND FALSE) set_package_properties(Soprano PROPERTIES DESCRIPTION "RDF handling library" URL "http://soprano.sourceforge.net/" PURPOSE "Required to handle RDF metadata in ODF" TYPE OPTIONAL ) if(NOT Soprano_FOUND) set(SOPRANO_INCLUDE_DIR "") endif() ## ## Test for marble ## # Temporary fix to avoid looking for Marble unneccessary # Its only used in RDF so until soprano is ported there is no use for Marble if (Soprano_FOUND) macro_optional_find_package(Marble CONFIG) set(Marble_FOUND FALSE) set_package_properties(Marble PROPERTIES DESCRIPTION "World Globe Widget library" URL "https://marble.kde.org/" PURPOSE "Required by RDF to show locations on a map" TYPE OPTIONAL ) else() message(STATUS "Soprano not found. Skipped looking for Marble.") endif() ## ## Test for lcms ## macro_optional_find_package(LCMS2) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS, a color management engine" URL "http://www.littlecms.com" PURPOSE "Will be used for color management" TYPE OPTIONAL ) if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() ## ## Test for Vc ## set(HAVE_VC FALSE) if (BUILD_VC) # NOTE: This tampers with cmake variables (at least cmake_minimum_required), so may give build problems set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) if( NOT MSVC) macro_optional_find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" PURPOSE "Required by the pigment for vectorization" TYPE OPTIONAL ) macro_bool_to_01(Vc_FOUND HAVE_VC) macro_bool_to_01(PACKAGERS_BUILD DO_PACKAGERS_BUILD) endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast -fPIC") elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC") endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) if(PACKAGERS_BUILD) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) else() set(${_objs} ${_src}) endif() endmacro() macro(ko_compile_for_all_implementations _objs _src) if(PACKAGERS_BUILD) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) else() set(${_objs} ${_src}) endif() endmacro() if (NOT PACKAGERS_BUILD) # Optimize the whole Calligra for current architecture set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}") endif () endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) else(BUILD_VC) configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) endif(BUILD_VC) if(WIN32) set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} LIBRARY ${INSTALL_TARGETS_DEFAULT_ARGS} ARCHIVE ${INSTALL_TARGETS_DEFAULT_ARGS} ) endif() ## ## Test for Fontconfig ## ## Only test if on non-Windows system if(NOT WIN32 AND NOT APPLE) macro_optional_find_package(Fontconfig) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Library for configuring and customizing font access" URL "http://fontconfig.org" PURPOSE "Required to handle exact font size" TYPE RECOMMENDED ) endif() ## ## Test for Freetype ## ## Only test if on non-Windows system if(NOT WIN32 AND NOT APPLE) macro_optional_find_package(Freetype) set_package_properties(Freetype PROPERTIES DESCRIPTION "A Free, High-Quality, and Portable Font Engine" URL "http://www.freetype.org/" PURPOSE "Required to handle exact font size" TYPE RECOMMENDED ) endif() if(NOT FONTCONFIG_FOUND OR NOT FREETYPE_FOUND) set(FONTCONFIG_INCLUDE_DIR "") set(FREETYPE_INCLUDE_DIRS "") else() add_definitions( -DSHOULD_BUILD_FONT_CONVERSION ) endif() ## ## Test endianess ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test SharedMimeInfo ## macro_optional_find_package(SharedMimeInfo 1.3) set_package_properties(SharedMimeInfo PROPERTIES PURPOSE "Required to determine file types SVM or all of MSOOXML." TYPE RECOMMENDED ) ## ## Test for Okular ## macro_optional_find_package(Okular5 0.99.60 QUIET) set_package_properties(Okular5 PROPERTIES DESCRIPTION "A unified document viewer" URL "https://okular.kde.org/" PURPOSE "Required to build the plugins for Okular" TYPE OPTIONAL ) ## ## Test for librevenge ## macro_optional_find_package(LibRevenge) set_package_properties(LibRevenge PROPERTIES DESCRIPTION "A base library for writing document import filters" URL "http://sf.net/p/libwpd/librevenge/" PURPOSE "Required by various import filters" TYPE OPTIONAL ) ## ## Test for libodfgen ## macro_optional_find_package(LibOdfGen) set_package_properties(LibOdfGen PROPERTIES DESCRIPTION "Open Document Format Generation Library" URL "http://sf.net/p/libwpd/libodfgen/" PURPOSE "Required by various import filters" TYPE OPTIONAL ) ## ## Test for WordPerfect Document Library ## macro_optional_find_package(LibWpd) set_package_properties(LibWpd PROPERTIES DESCRIPTION "WordPerfect Document Library" URL "http://libwpd.sourceforge.net/" PURPOSE "Required by the Words WPD import filter" TYPE OPTIONAL ) ## ## Test for WordPerfect Graphics Library ## macro_optional_find_package(LibWpg) set_package_properties(LibWpg PROPERTIES DESCRIPTION "WordPerfect Graphics Library" URL "http://libwpg.sourceforge.net/" PURPOSE "Required by the Karbon WPG import filter" TYPE OPTIONAL ) ## ## Test for Microsoft Works Document Library ## macro_optional_find_package(LibWps) set_package_properties(LibWps PROPERTIES DESCRIPTION "Microsoft Works Document Library" URL "http://libwps.sourceforge.net/" PURPOSE "Required by the Words WPS import filter" TYPE OPTIONAL ) ## ## Test for Microsoft Visio Document Library ## macro_optional_find_package(LibVisio) set_package_properties(LibVisio PROPERTIES DESCRIPTION "Visio Import Filter Library" URL "https://wiki.documentfoundation.org/DLP/Libraries/libvisio" PURPOSE "Required by the visio import filter" TYPE OPTIONAL ) ## ## Test for Apple Keynote Document Library ## macro_optional_find_package(LibEtonyek) set_package_properties(LibEtonyek PROPERTIES DESCRIPTION "Apple Keynote Document Library" URL "https://wiki.documentfoundation.org/DLP/Libraries/libetonyek" PURPOSE "Required by the Stage keynote import filter" TYPE OPTIONAL ) ## ## Test for qt-poppler ## macro_optional_find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" PURPOSE "Required by the Karbon PDF import filter and CSTester PDF feature" TYPE OPTIONAL ) ## ## Test for qt-poppler not-officially-supported XPDF Headers ## Installing these is off by default in poppler sources, so lets make ## sure they're really there before trying to build the pdf import ## macro_optional_find_package(PopplerXPDFHeaders) set_package_properties(PopplerXPDFHeaders PROPERTIES DESCRIPTION "XPDF headers in the Poppler Qt5 interface library" URL "http://poppler.freedesktop.org" PURPOSE "Required by the Karbon PDF import filter" TYPE OPTIONAL ) ## ## Test for libgit2 ## macro_optional_find_package(Libgit2) ## ## Generate a file for prefix information ## ############################### ################################ ## Add Calligra helper macros ## ################################ ############################### include(MacroCalligraAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # WARNING: make sure that QT_INCLUDES is the first directory to be added to include_directory before # any other include directory # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) set(KOVERSION_INCLUDES ${CMAKE_SOURCE_DIR}/libs/version ${CMAKE_BINARY_DIR}/libs/version ) include_directories(${KOVERSION_INCLUDES}) # koplugin is at the bottom of the stack set(KOPLUGIN_INCLUDES ${CMAKE_SOURCE_DIR}/libs/plugin) set(KUNDO2_INCLUDES ${CMAKE_SOURCE_DIR}/libs/kundo2 ${CMAKE_BINARY_DIR}/libs/kundo2) # koodf is at the bottom of the stack set(KOODF_INCLUDES ${CMAKE_SOURCE_DIR}/libs/odf ${CMAKE_SOURCE_DIR}/libs/store ${CMAKE_BINARY_DIR}/libs/odf ${CMAKE_BINARY_DIR}/libs/store ${KOVERSION_INCLUDES} ) # pigment depends on koplugin and lcms set(PIGMENT_INCLUDES ${KOPLUGIN_INCLUDES} ${KOVERSION_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/pigment ${CMAKE_BINARY_DIR}/libs/pigment ${CMAKE_SOURCE_DIR}/libs/pigment/compositeops ${CMAKE_SOURCE_DIR}/libs/pigment/resources ${Boost_INCLUDE_DIRS} ) # flake depends on koodf and pigment set(FLAKE_INCLUDES ${CMAKE_SOURCE_DIR}/libs/flake ${KOODF_INCLUDES} ${PIGMENT_INCLUDES} ${KUNDO2_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/widgetutils ${CMAKE_SOURCE_DIR}/libs/flake/commands ${CMAKE_SOURCE_DIR}/libs/flake/tools ${CMAKE_SOURCE_DIR}/libs/flake/svg ${CMAKE_BINARY_DIR}/libs/flake) # vectorimage set(VECTORIMAGE_INCLUDES ${CMAKE_SOURCE_DIR}/libs/vectorimage ${CMAKE_SOURCE_DIR}/libs/vectorimage/libemf ${CMAKE_SOURCE_DIR}/libs/vectorimage/libsvm ${CMAKE_SOURCE_DIR}/libs/vectorimage/libwmf) # KoText depends on koplugin, odf set(KOTEXT_INCLUDES ${CMAKE_SOURCE_DIR}/libs/text ${CMAKE_BINARY_DIR}/libs/text ${CMAKE_SOURCE_DIR}/libs/text/changetracker ${CMAKE_SOURCE_DIR}/libs/text/styles ${CMAKE_SOURCE_DIR}/libs/text/opendocument ${SOPRANO_INCLUDE_DIR} ${FLAKE_INCLUDES} ${KOODF_INCLUDES}) # TextLayout depends on kotext set(TEXTLAYOUT_INCLUDES ${KOTEXT_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/textlayout ${CMAKE_BINARY_DIR}/libs/textlayout) # Widgets depends on kotext and flake set(KOWIDGETS_INCLUDES ${KOTEXT_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/widgetutils ${CMAKE_BINARY_DIR}/libs/widgetutils ${CMAKE_SOURCE_DIR}/libs/widgets ${CMAKE_BINARY_DIR}/libs/widgets) # BasicFlakes depends on flake, widgets set(BASICFLAKES_INCLUDES ${KOWIDGETS_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/basicflakes ${CMAKE_SOURCE_DIR}/libs/basicflakes/tools) # komain depends on kotext & flake set(KOMAIN_INCLUDES ${KOWIDGETS_INCLUDES} ${TEXTLAYOUT_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/main ${CMAKE_BINARY_DIR}/libs/main ${CMAKE_SOURCE_DIR}/libs/main/config) set(KORDF_INCLUDES ${KOMAIN_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/rdf ) set(KORDF_LIBS kordf) if(SHOULD_BUILD_FEATURE_SCRIPTING) set(KOKROSS_INCLUDES ${CMAKE_SOURCE_DIR}/libs/kross ${CMAKE_BINARY_DIR}/libs/kross) endif() # kopageapp set(KOPAGEAPP_INCLUDES ${TEXTLAYOUT_INCLUDES} ${PIGMENT_INCLUDES} ${KOMAIN_INCLUDES} ${CMAKE_SOURCE_DIR}/libs/widgets ${CMAKE_SOURCE_DIR}/libs/pageapp ${CMAKE_SOURCE_DIR}/libs/pageapp/commands ${CMAKE_BINARY_DIR}/libs/pageapp ) ############################################# #### filter libraries #### ############################################# # libodf2 set(KOODF2_INCLUDES ${CMAKE_SOURCE_DIR}/filters/libodf2 ${CMAKE_SOURCE_DIR}/filters/libodf2/chart ) # libodfreader set(KOODFREADER_INCLUDES ${CMAKE_SOURCE_DIR}/filters/libodfreader ) ################################################### #################################################### ## Detect which products/features can be compiled ## #################################################### ################################################### if (NOT WIN32) set(NOT_WIN TRUE) endif() if (NOT QT_MAC_USE_COCOA) set(NOT_COCOA TRUE) endif() if (KReport_FOUND AND KREPORT_SCRIPTING) set(KReport_WithScripting_FOUND TRUE) endif() calligra_drop_product_on_bad_condition( FEATURE_RDF Soprano_FOUND "Soprano not found" ) calligra_drop_product_on_bad_condition( PART_SHEETS EIGEN3_FOUND "Eigen devel not found" ) calligra_drop_product_on_bad_condition( OKULAR_GENERATOR_ODP Okular5_FOUND "Okular devel not found" ) calligra_drop_product_on_bad_condition( OKULAR_GENERATOR_ODT Okular5_FOUND "Okular devel not found" ) calligra_drop_product_on_bad_condition( PLUGIN_CHARTSHAPE KChart_FOUND "KChart devel not found" ) calligra_drop_product_on_bad_condition( PLUGIN_VIDEOSHAPE Phonon4Qt5_FOUND "Phonon4Qt5 devel not found" ) calligra_drop_product_on_bad_condition( FILTER_KEY_TO_ODP LIBODFGEN_FOUND "libodfgen devel not found" LIBETONYEK_FOUND "libetonyek devel not found" LIBREVENGE_FOUND "librevenge devel not found" ) calligra_drop_product_on_bad_condition( FILTER_VISIO_TO_ODG LIBODFGEN_FOUND "libodfgen devel not found" LIBVISIO_FOUND "libvisio devel not found" LIBREVENGE_FOUND "librevenge devel not found" ) calligra_drop_product_on_bad_condition( FILTER_WORDPERFECT_TO_ODT LIBODFGEN_FOUND "libodfgen devel not found" LIBWPD_FOUND "libwpd devel not found" LIBWPG_FOUND "libwpg devel not found" LIBREVENGE_FOUND "librevenge devel not found" ) calligra_drop_product_on_bad_condition( FILTER_WORKS_TO_ODT LIBODFGEN_FOUND "libodfgen devel not found" LIBWPS_FOUND "libwps devel not found" LIBREVENGE_FOUND "librevenge devel not found" ) calligra_drop_product_on_bad_condition( FILTER_WPG_TO_SVG LIBWPG_FOUND "libwpg devel not found" LIBREVENGE_FOUND "librevenge devel not found" ) calligra_drop_product_on_bad_condition( FILTER_WPG_TO_ODG LIBODFGEN_FOUND "libodfgen devel not found" LIBWPG_FOUND "libwpg devel not found" LIBREVENGE_FOUND "librevenge devel not found" ) calligra_drop_product_on_bad_condition( FILTER_PDF_TO_SVG NOT_WIN "not supported on Windows" PopplerXPDFHeaders_FOUND "poppler xpdf headers not found" ) calligra_drop_product_on_bad_condition( FILTER_HTML_TO_ODS NOT_WIN "not supported on Windows" NOT_COCOA "not supported with Qt Cocoa" KF5KHtml_FOUND "KF5KHtml devel not found" ) calligra_drop_product_on_bad_condition( FILTER_SHEETS_TO_HTML NOT_WIN "not supported on Windows" NOT_COCOA "not supported with Qt Cocoa" ) calligra_drop_product_on_bad_condition( FILTER_KSPREAD_TO_LATEX NOT_WIN "not supported on Windows" NOT_COCOA "not supported with Qt Cocoa" ) calligra_drop_product_on_bad_condition( APP_BRAINDUMP NOT_WIN "unmaintained on Windows" Qt5WebKitWidgets_FOUND "QWebPage needed for webpage plugin" ) calligra_drop_product_on_bad_condition( PLUGIN_CALLIGRAGEMINI_GIT LIBGIT2_FOUND "libgit2 devel not found" ) calligra_drop_product_on_bad_condition( PART_QTQUICK Qt5OpenGL_FOUND "Qt OpenGL not found" Qt5Declarative_FOUND "QtDeclarative not found" ) calligra_drop_product_on_bad_condition( PART_COMPONENTS Qt5Quick_FOUND "QtQuick not found" ) calligra_drop_product_on_bad_condition( APP_SLIDECOMPARE Qt5OpenGL_FOUND "Qt OpenGL not found" ) ############################################# #### Backward compatibility BUILD_x=off #### ############################################# # workaround: disable directly all products which might be activated by internal # dependencies, but belong to scope of old flag calligra_drop_products_on_old_flag(braindump APP_BRAINDUMP) calligra_drop_products_on_old_flag(karbon APP_KARBON) calligra_drop_products_on_old_flag(sheets PART_SHEETS APP_SHEETS) calligra_drop_products_on_old_flag(stage PART_STAGE APP_STAGE) calligra_drop_products_on_old_flag(words PART_WORDS APP_WORDS) ############################################# #### Temporarily broken products #### ############################################# # If a product does not build due to some temporary brokeness disable it here, # by calling calligra_disable_product with the product id and the reason, # e.g.: # calligra_disable_product(APP_FOO "isn't buildable at the moment") calligra_disable_product(APP_BRAINDUMP "Disabled, will (probably) be removed from Calligra") ############################################# #### Calculate buildable products #### ############################################# calligra_drop_unbuildable_products() ############################################# #### Setup product-depending vars #### ############################################# if(SHOULD_BUILD_FEATURE_RDF) add_definitions( -DSHOULD_BUILD_RDF ) endif() ################### #################### ## Subdirectories ## #################### ################### add_subdirectory(words) add_subdirectory(stage) add_subdirectory(sheets) if(SHOULD_BUILD_APP_KARBON) add_subdirectory(karbon) endif() if(SHOULD_BUILD_APP_BRAINDUMP) add_subdirectory(braindump) endif() if(SHOULD_BUILD_DOC) add_subdirectory(doc) endif() if(SHOULD_BUILD_PART_QTQUICK) add_subdirectory(qtquick) endif() if(SHOULD_BUILD_PART_COMPONENTS) add_subdirectory(components) endif() if(SHOULD_BUILD_GEMINI) add_subdirectory(gemini) endif() # non-app directories are moved here because they can depend on SHOULD_BUILD_{appname} variables set above add_subdirectory(libs) add_subdirectory(interfaces) add_subdirectory(pics) add_subdirectory(plugins) add_subdirectory(servicetypes) add_subdirectory(devtools) add_subdirectory(extras) add_subdirectory(filters) add_subdirectory(data) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) calligra_product_deps_report("product_deps") calligra_log_should_build() add_custom_target(apidox doc/api/gendocs.pl WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) if (SHOULD_BUILD_DEVEL_HEADERS) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h DESTINATION ${INCLUDE_INSTALL_DIR}/calligra COMPONENT Devel) endif() if (BUILD_TESTING) add_subdirectory(tests) endif(BUILD_TESTING) diff --git a/plugins/chartshape/Axis.cpp b/plugins/chartshape/Axis.cpp index 2f1fe5f34b2..44d925bf361 100644 --- a/plugins/chartshape/Axis.cpp +++ b/plugins/chartshape/Axis.cpp @@ -1,2356 +1,2354 @@ /* This file is part of the KDE project Copyright 2007 Johannes Simon Copyright 2009 Inge Wallin Copyright 2017 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "Axis.h" // Qt #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KoChart #include "PlotArea.h" #include "KChartModel.h" #include "DataSet.h" #include "Legend.h" #include "KChartConvertions.h" #include "ChartProxyModel.h" #include "TextLabelDummy.h" #include "ChartLayout.h" #include "OdfLoadingHelper.h" #include "OdfHelper.h" #include "ChartDebug.h" using namespace KoChart; class Axis::Private { public: Private(Axis *axis, AxisDimension dim); ~Private(); void adjustAllDiagrams(); /// Updates the axis position in the chart's layout /// FIXME: We should instead implement a generic layout position method /// and have the layout find out about our position when it changes. void updatePosition(); void registerDiagram(KChart::AbstractDiagram *diagram); KChart::AbstractDiagram *getDiagramAndCreateIfNeeded(ChartType chartType); KChart::AbstractDiagram *getDiagram(ChartType chartType); void deleteDiagram(ChartType chartType); void deleteDiagram(KChart::AbstractDiagram *diagram); void restoreDiagrams(); void createBarDiagram(); void createLineDiagram(); void createAreaDiagram(); void createCircleDiagram(); void createRingDiagram(); void createRadarDiagram(bool filled); void createScatterDiagram(); void createStockDiagram(); void createBubbleDiagram(); void createSurfaceDiagram(); void createGanttDiagram(); void applyAttributesToDataSet(DataSet* set, ChartType newCharttype); // Pointer to Axis that owns this Private instance Axis * const q; PlotArea *plotArea; const AxisDimension dimension; KoShape *title; TextLabelData *titleData; /// FIXME: Unused variable 'id', including id() getter QString id; QList dataSets; qreal majorInterval; int minorIntervalDivisor; bool showInnerMinorTicks; bool showOuterMinorTicks; bool showInnerMajorTicks; bool showOuterMajorTicks; bool logarithmicScaling; bool showMajorGrid; bool showMinorGrid; bool useAutomaticMajorInterval; bool useAutomaticMinorInterval; bool useAutomaticMinimumRange; bool useAutomaticMaximumRange; KChart::CartesianAxis *const kdAxis; KChart::CartesianCoordinatePlane *kdPlane; KChart::PolarCoordinatePlane *kdPolarPlane; KChart::RadarCoordinatePlane *kdRadarPlane; KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat; QList > diagrams; QPointer kdBarDiagram; QPointer kdLineDiagram; QPointer kdAreaDiagram; QPointer kdCircleDiagram; QPointer kdRingDiagram; QPointer kdRadarDiagram; QPointer kdScatterDiagram; QPointer kdStockDiagram; QPointer kdBubbleDiagram; // FIXME BUG: Somehow we need to visualize something for these // missing chart types. We have some alternatives: // 1. Show an empty area // 2. Show a text "unsupported chart type" // 3. Exchange for something else, e.g. a bar chart. // ... More? // // NOTE: Whatever we do, we should always store the data so that // it can be saved back into the file for a perfect // roundtrip. QPointer kdSurfaceDiagram; QPointer kdGanttDiagram; ChartType plotAreaChartType; ChartSubtype plotAreaChartSubType; // If KChart::LineDiagram::centerDataPoints() property is set to true, // the data points drawn in a line (i.e., also an area) diagram start at // an offset of 0.5, that is, in the middle of a column in the diagram. // Set flag to true if at least one dataset is attached to this axis // that belongs to a horizontal bar chart bool centerDataPoints; int gapBetweenBars; int gapBetweenSets; // TODO: Save // See ODF v1.2 $19.12 (chart:display-label) bool showLabels; bool showOverlappingDataLabels; bool isVisible; QString name; QString axisPosition; QString axisLabelsPosition; }; class CartesianAxis : public KChart::CartesianAxis { public: CartesianAxis(KoChart::Axis *_axis) : KChart::CartesianAxis(), axis(_axis) {} virtual ~CartesianAxis() {} const QString customizedLabel(const QString& label) const override { if (KoOdfNumberStyles::NumericStyleFormat *n = axis->numericStyleFormat()) return KoOdfNumberStyles::format(label, *n); return label; } private: KoChart::Axis *axis; }; Axis::Private::Private(Axis *axis, AxisDimension dim) : q(axis) , dimension(dim) , kdAxis(new CartesianAxis(axis)) , kdPlane(0) , kdPolarPlane(0) , kdRadarPlane(0) , numericStyleFormat(0) { centerDataPoints = false; gapBetweenBars = 0; gapBetweenSets = 100; isVisible = true; useAutomaticMajorInterval = true; useAutomaticMinorInterval = true; useAutomaticMinimumRange = true; useAutomaticMaximumRange = true; majorInterval = 2; minorIntervalDivisor = 1; showMajorGrid = false; showMinorGrid = false; logarithmicScaling = false; showInnerMinorTicks = false; showOuterMinorTicks = false; showInnerMajorTicks = false; showOuterMajorTicks = true; showOverlappingDataLabels = false; showLabels = true; title = 0; titleData = 0; KChart::RulerAttributes attr = kdAxis->rulerAttributes(); attr.setShowRulerLine(true); -#if KCHART_VERSION >= ((2<<16)|(6<<8)|(89)) attr.setRulerLinePen(QPen()); -#endif kdAxis->setRulerAttributes(attr); } Axis::Private::~Private() { Q_ASSERT(plotArea); q->removeAxisFromDiagrams(); delete kdBarDiagram; delete kdLineDiagram; delete kdAreaDiagram; delete kdCircleDiagram; delete kdRingDiagram; delete kdRadarDiagram; delete kdScatterDiagram; delete kdStockDiagram; delete kdBubbleDiagram; delete kdSurfaceDiagram; delete kdGanttDiagram; delete numericStyleFormat; delete kdAxis; foreach(DataSet *dataSet, dataSets) dataSet->setAttachedAxis(0); } void Axis::Private::registerDiagram(KChart::AbstractDiagram *diagram) { QObject::connect(plotArea->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), diagram->model(), SLOT(slotColumnsInserted(QModelIndex,int,int))); QObject::connect(diagram, SIGNAL(propertiesChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(layoutChanged(AbstractDiagram*)), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(modelsChanged()), plotArea, SLOT(plotAreaUpdate())); QObject::connect(diagram, SIGNAL(dataHidden()), plotArea, SLOT(plotAreaUpdate())); } KChart::AbstractDiagram *Axis::Private::getDiagramAndCreateIfNeeded(ChartType chartType) { KChart::AbstractDiagram *diagram = 0; switch (chartType) { case BarChartType: if (!kdBarDiagram) createBarDiagram(); diagram = kdBarDiagram; break; case LineChartType: if (!kdLineDiagram) createLineDiagram(); diagram = kdLineDiagram; break; case AreaChartType: if (!kdAreaDiagram) createAreaDiagram(); diagram = kdAreaDiagram; break; case CircleChartType: if (!kdCircleDiagram) createCircleDiagram(); diagram = kdCircleDiagram; break; case RingChartType: if (!kdRingDiagram) createRingDiagram(); diagram = kdRingDiagram; break; case RadarChartType: case FilledRadarChartType: if (!kdRadarDiagram) createRadarDiagram(chartType == FilledRadarChartType); diagram = kdRadarDiagram; break; case ScatterChartType: if (!kdScatterDiagram) createScatterDiagram(); diagram = kdScatterDiagram; break; case StockChartType: if (!kdStockDiagram) createStockDiagram(); diagram = kdStockDiagram; break; case BubbleChartType: if (!kdBubbleDiagram) createBubbleDiagram(); diagram = kdBubbleDiagram; break; case SurfaceChartType: if (!kdSurfaceDiagram) createSurfaceDiagram(); diagram = kdSurfaceDiagram; break; case GanttChartType: if (!kdGanttDiagram) createGanttDiagram(); diagram = kdGanttDiagram; break; default: ; } diagram->setObjectName(q->name()); // for debug adjustAllDiagrams(); debugChartAxis<name()<<"diagram"<coordinatePlane()) { diagram->coordinatePlane()->takeDiagram(diagram); } delete diagram; adjustAllDiagrams(); } void Axis::Private::deleteDiagram(ChartType chartType) { KChart::AbstractDiagram *diagram = getDiagram(chartType); if (diagram) { deleteDiagram(diagram); } } void Axis::Private::createBarDiagram() { Q_ASSERT(kdBarDiagram == 0); kdBarDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdBarDiagram); kdBarDiagram->setModel(model); registerDiagram(kdBarDiagram); // By 'vertical', KChart means the orientation of a chart's bars, // not the orientation of the x axis. kdBarDiagram->setOrientation(plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical); kdBarDiagram->setPen(QPen(Qt::black, 0.0)); kdBarDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdBarDiagram->setType(KChart::BarDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) { kdBarDiagram->setType(KChart::BarDiagram::Percent); kdBarDiagram->setUnitSuffix("%", kdBarDiagram->orientation()); } if (isVisible) { kdBarDiagram->addAxis(kdAxis); q->registerDiagram(kdBarDiagram); } kdPlane->addDiagram(kdBarDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdBarDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdBarDiagram); } } // Set default bar diagram attributes q->setGapBetweenBars(gapBetweenBars); q->setGapBetweenSets(gapBetweenSets); // Propagate existing settings KChart::ThreeDBarAttributes attributes(kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdBarDiagram->setThreeDBarAttributes(attributes); q->plotAreaIsVerticalChanged(); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBarDiagram); } void Axis::Private::createLineDiagram() { Q_ASSERT(kdLineDiagram == 0); kdLineDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdLineDiagram); kdLineDiagram->setModel(model); registerDiagram(kdLineDiagram); kdLineDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdLineDiagram->setType(KChart::LineDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdLineDiagram->setType(KChart::LineDiagram::Percent); if (isVisible) { kdLineDiagram->addAxis(kdAxis); q->registerDiagram(kdLineDiagram); } kdPlane->addDiagram(kdLineDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdLineDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdLineDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdLineDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdLineDiagram->setThreeDLineAttributes(attributes); KChart::LineAttributes lineAttr = kdLineDiagram->lineAttributes(); lineAttr.setMissingValuesPolicy(KChart::LineAttributes::MissingValuesHideSegments); kdLineDiagram->setLineAttributes(lineAttr); plotArea->parent()->legend()->kdLegend()->addDiagram(kdLineDiagram); } void Axis::Private::createAreaDiagram() { Q_ASSERT(kdAreaDiagram == 0); kdAreaDiagram = new KChart::LineDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdAreaDiagram); kdAreaDiagram->setModel(model); registerDiagram(kdAreaDiagram); KChart::LineAttributes attr = kdAreaDiagram->lineAttributes(); // Draw the area under the lines. This makes this diagram an area chart. attr.setDisplayArea(true); kdAreaDiagram->setLineAttributes(attr); kdAreaDiagram->setPen(QPen(Qt::black, 0.0)); // KD Chart by default draws the first data set as last line in a normal // line diagram, we however want the first series to appear in front. kdAreaDiagram->setReverseDatasetOrder(true); kdAreaDiagram->setAllowOverlappingDataValueTexts(showOverlappingDataLabels); if (plotAreaChartSubType == StackedChartSubtype) kdAreaDiagram->setType(KChart::LineDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) { kdAreaDiagram->setType(KChart::LineDiagram::Percent); kdAreaDiagram->setUnitSuffix("%", Qt::Vertical); } if (isVisible) { kdAreaDiagram->addAxis(kdAxis); q->registerDiagram(kdAreaDiagram); } kdPlane->addDiagram(kdAreaDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdAreaDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdAreaDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdAreaDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdAreaDiagram->setThreeDLineAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdAreaDiagram); } void Axis::Private::createCircleDiagram() { Q_ASSERT(kdCircleDiagram == 0); kdCircleDiagram = new KChart::PieDiagram(plotArea->kdChart(), kdPolarPlane); KChartModel *model = new KChartModel(plotArea, kdCircleDiagram); kdCircleDiagram->setModel(model); registerDiagram(kdCircleDiagram); model->setDataDirection(Qt::Horizontal); plotArea->parent()->legend()->kdLegend()->addDiagram(kdCircleDiagram); kdPolarPlane->addDiagram(kdCircleDiagram); // Propagate existing settings KChart::ThreeDPieAttributes attributes(kdCircleDiagram->threeDPieAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdCircleDiagram->setThreeDPieAttributes(attributes); // Initialize with default values that are specified in PlotArea // Note: KChart takes an int here, though ODF defines the offset to be a double. kdPolarPlane->setStartPosition((int)plotArea->angleOffset()); } void Axis::Private::createRingDiagram() { Q_ASSERT(kdRingDiagram == 0); kdRingDiagram = new KChart::RingDiagram(plotArea->kdChart(), kdPolarPlane); KChartModel *model = new KChartModel(plotArea, kdRingDiagram); kdRingDiagram->setModel(model); registerDiagram(kdRingDiagram); model->setDataDirection(Qt::Horizontal); plotArea->parent()->legend()->kdLegend()->addDiagram(kdRingDiagram); kdPolarPlane->addDiagram(kdRingDiagram); // Propagate existing settings KChart::ThreeDPieAttributes attributes(kdRingDiagram->threeDPieAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdRingDiagram->setThreeDPieAttributes(attributes); // Initialize with default values that are specified in PlotArea // Note: KChart takes an int here, though ODF defines the offset to be a double. kdPolarPlane->setStartPosition((int)plotArea->angleOffset()); } void Axis::Private::createRadarDiagram(bool filled) { Q_ASSERT(kdRadarDiagram == 0); //kdRadarDiagramModel->setDataDimensions(2); //kdRadarDiagramModel->setDataDirection(Qt::Horizontal); kdRadarDiagram = new KChart::RadarDiagram(plotArea->kdChart(), kdRadarPlane); KChartModel *model = new KChartModel(plotArea, kdRadarDiagram); kdRadarDiagram->setModel(model); registerDiagram(kdRadarDiagram); kdRadarDiagram->setCloseDatasets(true); if (filled) { // Don't use a solid fill of 1.0 but a more transparent one so the // grid and the data-value-labels are still visible plus it provides // a better look (other areas can still be seen) even if it's slightly // different from what OO.org does. kdRadarDiagram->setFillAlpha(0.4); } #if 0 // Stacked and Percent not supported by KChart. if (plotAreaChartSubType == StackedChartSubtype) kdRadarDiagram->setType(KChart::PolarDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdRadarDiagram->setType(KChart::PolarDiagram::Percent); #endif plotArea->parent()->legend()->kdLegend()->addDiagram(kdRadarDiagram); kdRadarPlane->addDiagram(kdRadarDiagram); } void Axis::Private::createScatterDiagram() { Q_ASSERT(kdScatterDiagram == 0); Q_ASSERT(plotArea); kdScatterDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdScatterDiagram); kdScatterDiagram->setModel(model); registerDiagram(kdScatterDiagram); model->setDataDimensions(2); kdScatterDiagram->setPen(Qt::NoPen); if (isVisible) { kdScatterDiagram->addAxis(kdAxis); q->registerDiagram(kdScatterDiagram); } kdPlane->addDiagram(kdScatterDiagram); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdScatterDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdScatterDiagram); } } // Propagate existing settings KChart::ThreeDLineAttributes attributes(kdScatterDiagram->threeDLineAttributes()); attributes.setEnabled(plotArea->isThreeD()); attributes.setThreeDBrushEnabled(plotArea->isThreeD()); kdScatterDiagram->setThreeDLineAttributes(attributes); plotArea->parent()->legend()->kdLegend()->addDiagram(kdScatterDiagram); } void Axis::Private::createStockDiagram() { Q_ASSERT(kdStockDiagram == 0); kdStockDiagram = new KChart::StockDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdStockDiagram); kdStockDiagram->setModel(model); switch (plotAreaChartSubType) { case HighLowCloseChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::HighLowClose); break; case OpenHighLowCloseChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::OpenHighLowClose); break; case CandlestickChartSubtype: kdStockDiagram->setType(KChart::StockDiagram::Candlestick); break; } registerDiagram(kdStockDiagram); model->setDataDimensions(numDimensions(StockChartType)); #if 0 // Stacked and Percent not supported by KChart. if (plotAreaChartSubType == StackedChartSubtype) kdStockDiagram->setType(KChart::StockDiagram::Stacked); else if (plotAreaChartSubType == PercentChartSubtype) kdStockDiagram->setType(KChart::StockDiagram::Percent); #endif if (isVisible) { kdStockDiagram->addAxis(kdAxis); q->registerDiagram(kdStockDiagram); } kdPlane->addDiagram(kdStockDiagram); Q_ASSERT(plotArea); foreach (Axis *axis, plotArea->axes()) { if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdStockDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(kdStockDiagram); } } plotArea->parent()->legend()->kdLegend()->addDiagram(kdStockDiagram); q->updateKChartStockAttributes(); } void Axis::Private::createBubbleDiagram() { Q_ASSERT(kdBubbleDiagram == 0); Q_ASSERT(plotArea); kdBubbleDiagram = new KChart::Plotter(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdBubbleDiagram); kdBubbleDiagram->setModel(model); registerDiagram(kdBubbleDiagram); model->setDataDimensions(2); kdPlane->addDiagram(kdBubbleDiagram); foreach (Axis *axis, plotArea->axes()) { //if (axis->dimension() == XAxisDimension) if (axis->isVisible() && axis->dimension() == XAxisDimension) { kdBubbleDiagram->addAxis(axis->kdAxis()); q->registerDiagram(kdBubbleDiagram); } } // disable the connecting line KChart::LineAttributes la = kdBubbleDiagram->lineAttributes(); la.setVisible(false); kdBubbleDiagram->setLineAttributes(la); plotArea->parent()->legend()->kdLegend()->addDiagram(kdBubbleDiagram); } void Axis::Private::createSurfaceDiagram() { Q_ASSERT(!kdSurfaceDiagram); // This is a so far a by KChart unsupported chart type. // Fall back to bar diagram for now. kdSurfaceDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdSurfaceDiagram); kdSurfaceDiagram->setModel(model); registerDiagram(kdSurfaceDiagram); plotArea->parent()->legend()->kdLegend()->addDiagram(kdSurfaceDiagram); kdPlane->addDiagram(kdSurfaceDiagram); } void Axis::Private::createGanttDiagram() { // This is a so far a by KChart unsupported chart type (through KDGantt got merged into KChart with 2.3) Q_ASSERT(!kdGanttDiagram); // This is a so far a by KChart unsupported chart type. // Fall back to bar diagram for now. kdGanttDiagram = new KChart::BarDiagram(plotArea->kdChart(), kdPlane); KChartModel *model = new KChartModel(plotArea, kdGanttDiagram); kdGanttDiagram->setModel(model); registerDiagram(kdGanttDiagram); plotArea->parent()->legend()->kdLegend()->addDiagram(kdGanttDiagram); kdPlane->addDiagram(kdGanttDiagram); } /** * Automatically adjusts the diagram so that all currently displayed * diagram types fit together. */ void Axis::Private::adjustAllDiagrams() { // If at least one dataset is attached that belongs to a // horizontal bar chart, set centerDataPoints to true. centerDataPoints = kdBarDiagram != 0; if (kdLineDiagram) kdLineDiagram->setCenterDataPoints(centerDataPoints); if (kdAreaDiagram) kdAreaDiagram->setCenterDataPoints(centerDataPoints); } // ================================================================ // class Axis // FIXME: make it possible to create an axis without parent, and // when it is removed, actually remove it from parent (signals and all) Axis::Axis(PlotArea *parent, AxisDimension dimension) : d(new Private(this, dimension)) { Q_ASSERT(parent); parent->addAxis(this); d->plotArea = parent; d->kdAxis->setObjectName(name()); KChart::BackgroundAttributes batt(d->kdAxis->backgroundAttributes()); batt.setBrush(QBrush(Qt::white)); d->kdAxis->setBackgroundAttributes(batt); setFontSize(8.0); // also sets MeasureCalculationModeAbsolute d->kdPlane = parent->kdCartesianPlane(this); d->kdPolarPlane = parent->kdPolarPlane(); d->kdRadarPlane = parent->kdRadarPlane(); d->plotAreaChartType = d->plotArea->chartType(); d->plotAreaChartSubType = d->plotArea->chartSubType(); setOdfAxisPosition("start"); setOdfAxisLabelsPosition("near-axis"); KoShapeFactoryBase *textShapeFactory = KoShapeRegistry::instance()->value(TextShapeId); if (textShapeFactory) d->title = textShapeFactory->createDefaultShape(parent->parent()->resourceManager()); if (d->title) { d->titleData = qobject_cast(d->title->userData()); if (d->titleData == 0) { d->titleData = new TextLabelData; d->title->setUserData(d->titleData); } QFont font = d->titleData->document()->defaultFont(); font.setPointSizeF(9); d->titleData->document()->setDefaultFont(font); } else { d->title = new TextLabelDummy; d->titleData = new TextLabelData; KoTextDocumentLayout *documentLayout = new KoTextDocumentLayout(d->titleData->document()); d->titleData->document()->setDocumentLayout(documentLayout); d->title->setUserData(d->titleData); } d->title->setSize(QSizeF(CM_TO_POINT(3), CM_TO_POINT(0.75))); d->plotArea->parent()->addShape(d->title); d->plotArea->parent()->setClipped(d->title, true); d->plotArea->parent()->setInheritsTransform(d->title, true); d->title->setDeletable(false); d->title->setZIndex(5); d->title->setToolDelegates(QSet()<parent()<title); // Enable chart tool d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize); d->title->setAdditionalStyleAttribute("chart:auto-position", "true"); d->title->setAllowedInteraction(KoShape::ShearingAllowed, false); d->title->setVisible(false); // Needed to avoid problems when creating secondary axes (Axis creation needs review/refactoring) connect(d->plotArea, SIGNAL(angleOffsetChanged(qreal)), this, SLOT(setAngleOffset(qreal))); connect(d->plotArea, SIGNAL(holeSizeChanged(qreal)), this, SLOT(setHoleSize(qreal))); d->updatePosition(); } Axis::~Axis() { Q_ASSERT(d->plotArea); d->plotArea->parent()->KoShapeContainer::removeShape(d->title); Q_ASSERT(d->title); delete d->title; delete d; } PlotArea* Axis::plotArea() const { return d->plotArea; } KoShape *Axis::title() const { return d->title; } QString Axis::titleText() const { return d->titleData->document()->toPlainText(); } bool Axis::showLabels() const { return d->showLabels; } bool Axis::showOverlappingDataLabels() const { return d->showOverlappingDataLabels; } QString Axis::id() const { return d->id; } AxisDimension Axis::dimension() const { return d->dimension; } QList Axis::dataSets() const { return d->dataSets; } bool Axis::attachDataSet(DataSet *dataSet) { Q_ASSERT(!d->dataSets.contains(dataSet)); if (d->dataSets.contains(dataSet)) return false; d->dataSets.append(dataSet); if (dimension() == YAxisDimension) { dataSet->setAttachedAxis(this); ChartType chartType = dataSet->chartType(); if (chartType == LastChartType) chartType = d->plotAreaChartType; KChart::AbstractDiagram *diagram = d->getDiagramAndCreateIfNeeded(chartType); Q_ASSERT(diagram); KChartModel *model = dynamic_cast(diagram->model()); Q_ASSERT(model); model->addDataSet(dataSet); layoutPlanes(); requestRepaint(); } return true; } bool Axis::detachDataSet(DataSet *dataSet, bool silent) { Q_ASSERT(d->dataSets.contains(dataSet)); if (!d->dataSets.contains(dataSet)) return false; d->dataSets.removeAll(dataSet); if (dimension() == YAxisDimension) { ChartType chartType = dataSet->chartType(); if (chartType == LastChartType) chartType = d->plotAreaChartType; KChart::AbstractDiagram *oldDiagram = d->getDiagram(chartType); Q_ASSERT(oldDiagram); KChartModel *oldModel = dynamic_cast(oldDiagram->model()); Q_ASSERT(oldModel); const int rowCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); // If there's only as many rows as needed for *one* // dataset, that means that the dataset we're removing is // the last one in the model --> delete model if (rowCount == oldModel->dataDimensions()) d->deleteDiagram(chartType); else oldModel->removeDataSet(dataSet, silent); dataSet->setKdChartModel(0); dataSet->setAttachedAxis(0); if (!silent) { layoutPlanes(); requestRepaint(); } } return true; } void Axis::clearDataSets() { QList list = d->dataSets; foreach(DataSet *dataSet, list) detachDataSet(dataSet, true); } bool Axis::showRuler() const { return d->kdAxis->rulerAttributes().showRulerLine(); } void Axis::setShowRuler(bool show) { KChart::RulerAttributes attr = d->kdAxis->rulerAttributes(); attr.setShowRulerLine(!attr.showRulerLine()); d->kdAxis->setRulerAttributes(attr); } qreal Axis::majorInterval() const { return d->majorInterval; } void Axis::setMajorInterval(qreal interval) { // Don't overwrite if automatic interval is being requested (for // interval = 0) if (interval != 0.0) { d->majorInterval = interval; d->useAutomaticMajorInterval = false; } else d->useAutomaticMajorInterval = true; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridStepWidth(interval); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridStepWidth(interval); d->kdPolarPlane->setGridAttributes(true, attributes); // FIXME: Hide minor tick marks more appropriately if (!d->showMinorGrid && interval != 0.0) setMinorInterval(interval); requestRepaint(); } qreal Axis::minorInterval() const { return (d->majorInterval / (qreal)d->minorIntervalDivisor); } void Axis::setMinorInterval(qreal interval) { if (interval == 0.0) setMinorIntervalDivisor(0); else setMinorIntervalDivisor(int(qRound(d->majorInterval / interval))); } int Axis::minorIntervalDivisor() const { return d->minorIntervalDivisor; } void Axis::setMinorIntervalDivisor(int divisor) { // A divisor of 0.0 means automatic minor interval calculation if (divisor != 0) { d->minorIntervalDivisor = divisor; d->useAutomaticMinorInterval = false; } else d->useAutomaticMinorInterval = true; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridSubStepWidth((divisor != 0) ? (d->majorInterval / divisor) : 0.0); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } bool Axis::useAutomaticMajorInterval() const { return d->useAutomaticMajorInterval; } bool Axis::useAutomaticMinorInterval() const { return d->useAutomaticMinorInterval; } void Axis::setUseAutomaticMajorInterval(bool automatic) { d->useAutomaticMajorInterval = automatic; // A value of 0.0 will activate automatic intervals, // but not change d->majorInterval setMajorInterval(automatic ? 0.0 : majorInterval()); } void Axis::setUseAutomaticMinorInterval(bool automatic) { d->useAutomaticMinorInterval = automatic; // A value of 0.0 will activate automatic intervals, // but not change d->minorIntervalDivisor setMinorInterval(automatic ? 0.0 : minorInterval()); } bool Axis::showInnerMinorTicks() const { return d->showInnerMinorTicks; } bool Axis::showOuterMinorTicks() const { return d->showOuterMinorTicks; } bool Axis::showInnerMajorTicks() const { return d->showInnerMinorTicks; } bool Axis::showOuterMajorTicks() const { return d->showOuterMajorTicks; } void Axis::setShowInnerMinorTicks(bool showTicks) { d->showInnerMinorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowOuterMinorTicks(bool showTicks) { d->showOuterMinorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMinorTickMarks(d->showInnerMinorTicks || d->showOuterMinorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowInnerMajorTicks(bool showTicks) { d->showInnerMajorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setShowOuterMajorTicks(bool showTicks) { d->showOuterMajorTicks = showTicks; KChart::RulerAttributes attr = kdAxis()->rulerAttributes(); attr.setShowMajorTickMarks(d->showInnerMajorTicks || d->showOuterMajorTicks); kdAxis()->setRulerAttributes(attr); } void Axis::setScalingLogarithmic(bool logarithmicScaling) { d->logarithmicScaling = logarithmicScaling; if (dimension() != YAxisDimension) return; d->kdPlane->setAxesCalcModeY(d->logarithmicScaling ? KChart::AbstractCoordinatePlane::Logarithmic : KChart::AbstractCoordinatePlane::Linear); d->kdPlane->layoutPlanes(); requestRepaint(); } bool Axis::scalingIsLogarithmic() const { return d->logarithmicScaling; } bool Axis::showMajorGrid() const { return d->showMajorGrid; } void Axis::setShowMajorGrid(bool showGrid) { d->showMajorGrid = showGrid; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setGridVisible(d->showMajorGrid); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setGridVisible(d->showMajorGrid); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } bool Axis::showMinorGrid() const { return d->showMinorGrid; } void Axis::setShowMinorGrid(bool showGrid) { d->showMinorGrid = showGrid; // KChart KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); attributes.setSubGridVisible(d->showMinorGrid); d->kdPlane->setGridAttributes(orientation(), attributes); attributes = d->kdPolarPlane->gridAttributes(true); attributes.setSubGridVisible(d->showMinorGrid); d->kdPolarPlane->setGridAttributes(true, attributes); requestRepaint(); } void Axis::setTitleText(const QString &text) { d->titleData->document()->setPlainText(text); } void Axis::setShowLabels(bool show) { d->showLabels = show; KChart::TextAttributes textAttr = d->kdAxis->textAttributes(); textAttr.setVisible(show); d->kdAxis->setTextAttributes(textAttr); } void Axis::setShowOverlappingDataLabels(bool show) { d->showOverlappingDataLabels = show; } Qt::Orientation Axis::orientation() const { bool chartIsVertical = d->plotArea->isVertical(); bool horizontal = d->dimension == (chartIsVertical ? YAxisDimension : XAxisDimension); return horizontal ? Qt::Horizontal : Qt::Vertical; } bool Axis::loadOdf(const KoXmlElement &axisElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); OdfLoadingHelper *helper = (OdfLoadingHelper*)context.sharedData(OdfLoadingHelperId); bool reverseAxis = false; d->title->setVisible(false); QPen gridPen(Qt::NoPen); QPen subGridPen(Qt::NoPen); d->showMajorGrid = false; d->showMinorGrid = false; d->showInnerMinorTicks = false; d->showOuterMinorTicks = false; d->showInnerMajorTicks = false; d->showOuterMajorTicks = true; // Use automatic interval calculation by default setMajorInterval(0.0); setMinorInterval(0.0); if (!axisElement.isNull()) { QString styleName = axisElement.attributeNS(KoXmlNS::chart, "style-name", QString()); const KoXmlElement *stylElement = stylesReader.findStyle(styleName, "chart"); if (stylElement) { const QString dataStyleName = stylElement->attributeNS(KoXmlNS::style, "data-style-name", QString()); if (!dataStyleName.isEmpty() && stylesReader.dataFormats().contains(dataStyleName)) { delete d->numericStyleFormat; d->numericStyleFormat = new KoOdfNumberStyles::NumericStyleFormat(stylesReader.dataFormats()[dataStyleName].first); } } KoXmlElement n; forEachElement (n, axisElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "title") { OdfHelper::loadOdfTitle(d->title, n, context); // title shall *always* have AutoResize d->titleData->setResizeMethod(KoTextShapeDataBase::AutoResize); } else if (n.localName() == "grid") { bool major = false; if (n.hasAttributeNS(KoXmlNS::chart, "class")) { const QString className = n.attributeNS(KoXmlNS::chart, "class"); if (className == "major") major = true; } else { warnChart << "Error: Axis' element contains no valid class. It must be either \"major\" or \"minor\"."; continue; } if (major) { d->showMajorGrid = true; } else { d->showMinorGrid = true; } if (n.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::style, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color")) { const QString strokeColor = styleStack.property(KoXmlNS::svg, "stroke-color"); //d->showMajorGrid = true; if (major) gridPen = QPen(QColor(strokeColor)); else subGridPen = QPen(QColor(strokeColor)); } } } else if (n.localName() == "categories") { if (n.hasAttributeNS(KoXmlNS::table, "cell-range-address")) { const CellRegion region = CellRegion(helper->tableSource, n.attributeNS(KoXmlNS::table, "cell-range-address")); helper->categoryRegionSpecifiedInXAxis = true; plotArea()->proxyModel()->setCategoryDataRegion(region); } } } if (axisElement.hasAttributeNS(KoXmlNS::chart, "axis-name")) { const QString name = axisElement.attributeNS(KoXmlNS::chart, "name", QString()); setName(name); } // NOTE: chart:dimension already handled by PlotArea before and passed // explicitly in the constructor. } else { warnChartOdf<<"No axis element"; } if (axisElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(axisElement, KoXmlNS::chart, "style-name", "chart"); KoCharacterStyle charStyle; charStyle.loadOdf(&axisElement, context); setFont(charStyle.font()); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "logarithmic") && styleStack.property(KoXmlNS::chart, "logarithmic") == "true") { setScalingLogarithmic(true); } if (styleStack.hasProperty(KoXmlNS::chart, "reverse-direction") && styleStack.property(KoXmlNS::chart, "reverse-direction") == "true") { reverseAxis = true; } if (styleStack.hasProperty(KoXmlNS::chart, "interval-major")) setMajorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-major"))); if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor-divisor")) setMinorIntervalDivisor(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor-divisor"))); else if (styleStack.hasProperty(KoXmlNS::chart, "interval-minor")) setMinorInterval(KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "interval-minor"))); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-inner")) setShowInnerMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-inner") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-minor-outer")) setShowOuterMinorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-minor-outer") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-inner")) setShowInnerMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-inner") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "tick-marks-major-outer")) setShowOuterMajorTicks(styleStack.property(KoXmlNS::chart, "tick-marks-major-outer") == "true"); if (styleStack.hasProperty(KoXmlNS::chart, "display-label")) setShowLabels(styleStack.property(KoXmlNS::chart, "display-label") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "text-overlap")) setShowOverlappingDataLabels(styleStack.property(KoXmlNS::chart, "text-overlap") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "visible")) setVisible(styleStack.property(KoXmlNS::chart, "visible") != "false"); if (styleStack.hasProperty(KoXmlNS::chart, "minimum")) { const qreal minimum = styleStack.property(KoXmlNS::chart, "minimum").toDouble(); const qreal maximum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().second : d->kdPlane->horizontalRange().second; if (orientation() == Qt::Vertical) d->kdPlane->setVerticalRange(qMakePair(minimum, maximum)); else d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum)); d->useAutomaticMinimumRange = false; } if (styleStack.hasProperty(KoXmlNS::chart, "maximum")) { const qreal minimum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().first : d->kdPlane->horizontalRange().first; const qreal maximum = styleStack.property(KoXmlNS::chart, "maximum").toDouble(); if (orientation() == Qt::Vertical) d->kdPlane->setVerticalRange(qMakePair(minimum, maximum)); else d->kdPlane->setHorizontalRange(qMakePair(minimum, maximum)); d->useAutomaticMaximumRange = false; } /*if (styleStack.hasProperty(KoXmlNS::chart, "origin")) { const qreal origin = KoUnit::parseValue(styleStack.property(KoXmlNS::chart, "origin")); }*/ styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "axis-position")) { d->axisPosition = styleStack.property(KoXmlNS::chart, "axis-position"); } if (styleStack.hasProperty(KoXmlNS::chart, "axis-label-position")) { d->axisLabelsPosition = styleStack.property(KoXmlNS::chart, "axis-label-position"); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); setFontSize(fontSize); } if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { QString fontColorString = styleStack.property(KoXmlNS::fo, "font-color"); QColor color(fontColorString); if (color.isValid()) { KChart::TextAttributes tatt = kdAxis()->textAttributes(); QPen pen = tatt.pen(); pen.setColor(color); tatt.setPen(pen); kdAxis()->setTextAttributes(tatt); } } if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { QString fontFamilyString = styleStack.property(KoXmlNS::fo, "font-family"); if (!fontFamilyString.isEmpty()) { QFont f = this->font(); f.setFamily(fontFamilyString); setFont(f); } } if (styleStack.hasProperty(KoXmlNS::fo, "font-style")) { QString fontStyle = styleStack.property(KoXmlNS::fo, "font-style"); if (fontStyle == "italic") { QFont f = this->font(); f.setItalic(true); setFont(f); } else if (fontStyle == "oblique") { // TODO } } if (styleStack.hasProperty(KoXmlNS::fo, "font-weight")) { QString fontWeight = styleStack.property(KoXmlNS::fo, "font-weight"); //fo:font-weight attribute are normal, bold, 100, 200, 300, 400, 500, 600, 700, 800 or 900. if (fontWeight == "bold") { QFont f = this->font(); f.setBold(true); setFont(f); } else { // TODO } } } else { warnChartOdf<<"Axis element has no style information"; setShowLabels(KoOdfWorkaround::fixMissingStyle_DisplayLabel(axisElement, context)); } KChart::GridAttributes gridAttr = d->kdPlane->gridAttributes(orientation()); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); d->kdPlane->setGridAttributes(orientation(), gridAttr); gridAttr = d->kdPolarPlane->gridAttributes(orientation()); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); // if (plotArea()->chartType() == RadarChartType || plotArea()->chartType() == FilledRadarChartType) // d->kdPolarPlane->setGridAttributes(false, gridAttr); // else d->kdPolarPlane->setGridAttributes(true, gridAttr); gridAttr = d->kdRadarPlane->globalGridAttributes(); gridAttr.setGridVisible(d->showMajorGrid); gridAttr.setSubGridVisible(d->showMinorGrid); if (gridPen.style() != Qt::NoPen) gridAttr.setGridPen(gridPen); if (subGridPen.style() != Qt::NoPen) gridAttr.setSubGridPen(subGridPen); d->kdRadarPlane->setGlobalGridAttributes(gridAttr); KChart::TextAttributes ta(d->kdRadarPlane->textAttributes()); ta.setVisible(helper->categoryRegionSpecifiedInXAxis); ta.setFont(font()); ta.setFontSize(50); d->kdRadarPlane->setTextAttributes(ta); if (reverseAxis) { KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); if (plane) { if (orientation() == Qt::Horizontal) plane->setHorizontalRangeReversed(reverseAxis); else // Qt::Vertical plane->setVerticalRangeReversed(reverseAxis); } } // Style of axis is still in styleStack if (!loadOdfChartSubtypeProperties(axisElement, context)) { return false; } if (titleText().isEmpty()) { // do not allow visible empty text d->title->setVisible(false); } requestRepaint(); debugChartOdf<<"Loaded axis:"<name = name; } // NOTE: only used during save/load to enable attaching axis to datasets QString Axis::name() const { if (!d->name.isEmpty()) { return d->name; } QString name; switch(dimension()) { case XAxisDimension: name = QLatin1Char('x'); break; case YAxisDimension: name = QLatin1Char('y'); break; case ZAxisDimension: name = QLatin1Char('z'); break; } int i = 1; foreach (Axis *axis, d->plotArea->axes()) { if (axis == this) break; if (axis->dimension() == dimension()) i++; } if (i == 1) name = "primary-" + name; else if (i == 2) name = "secondary-" + name; // Usually, there's not more than two axes of the same dimension. // But use a fallback name here nevertheless. else name = QString::number(i) + '-' + name; return name; } void Axis::saveOdf(KoShapeSavingContext &context) { KoXmlWriter &bodyWriter = context.xmlWriter(); KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:axis"); KoGenStyle axisStyle(KoGenStyle::ChartAutoStyle, "chart"); axisStyle.addProperty("chart:logarithmic", scalingIsLogarithmic()); axisStyle.addProperty("chart:reverse-direction", axisDirectionReversed()); if (!d->axisPosition.isEmpty()) { axisStyle.addProperty("chart:axis-position", d->axisPosition); } if (!d->axisLabelsPosition.isEmpty()) { axisStyle.addProperty("chart:axis-label-position", d->axisLabelsPosition); } axisStyle.addProperty("chart:tick-marks-minor-inner", showInnerMinorTicks()); axisStyle.addProperty("chart:tick-marks-minor-outer", showOuterMinorTicks()); axisStyle.addProperty("chart:tick-marks-major-inner", showInnerMajorTicks()); axisStyle.addProperty("chart:tick-marks-major-outer", showOuterMajorTicks()); axisStyle.addProperty("chart:display-label", showLabels()); axisStyle.addProperty("chart:text-overlap", showOverlappingDataLabels()); axisStyle.addProperty("chart:visible", isVisible()); if (dimension() == YAxisDimension) { axisStyle.addProperty("chart:gap-width", d->gapBetweenSets); axisStyle.addProperty("chart:overlap", -d->gapBetweenBars); } if (!d->useAutomaticMinimumRange) { const qreal minimum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().first : d->kdPlane->horizontalRange().first; axisStyle.addProperty("chart:minimum", (int)minimum); } if (!d->useAutomaticMaximumRange) { const qreal maximum = orientation() == Qt::Vertical ? d->kdPlane->verticalRange().second : d->kdPlane->horizontalRange().second; axisStyle.addProperty("chart:maximum", (int)maximum); } //axisStyle.addPropertyPt("chart:origin", origin); KChart::TextAttributes tatt = kdAxis()->textAttributes(); QPen pen = tatt.pen(); axisStyle.addProperty("fo:font-color", pen.color().name(), KoGenStyle::TextType); axisStyle.addProperty("fo:font-family", tatt.font().family(), KoGenStyle::TextType); axisStyle.addPropertyPt("fo:font-size", fontSize(), KoGenStyle::TextType); if (font().bold()) { axisStyle.addProperty("fo:font-weight", "bold" , KoGenStyle::TextType); // TODO support other weights } if (font().italic()) { axisStyle.addProperty("fo:font-style", "italic" , KoGenStyle::TextType); // TODO oblique } const QString styleName = mainStyles.insert(axisStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); // TODO scale: logarithmic/linear // TODO visibility if (dimension() == XAxisDimension) bodyWriter.addAttribute("chart:dimension", "x"); else if (dimension() == YAxisDimension) bodyWriter.addAttribute("chart:dimension", "y"); bodyWriter.addAttribute("chart:name", name()); OdfHelper::saveOdfTitle(d->title, bodyWriter, "chart:title", context); if (plotArea()->proxyModel()->categoryDataRegion().isValid()) { bodyWriter.startElement("chart:categories"); bodyWriter.addAttribute("table:cell-range-address", plotArea()->proxyModel()->categoryDataRegion().toString()); bodyWriter.endElement(); } if (showMajorGrid()) saveOdfGrid(context, OdfMajorGrid); if (showMinorGrid()) saveOdfGrid(context, OdfMinorGrid); bodyWriter.endElement(); // chart:axis } void Axis::saveOdfGrid(KoShapeSavingContext &context, OdfGridClass gridClass) { KoXmlWriter &bodyWriter = context.xmlWriter(); KoGenStyles &mainStyles = context.mainStyles(); KoGenStyle gridStyle(KoGenStyle::GraphicAutoStyle, "chart"); KChart::GridAttributes attributes = d->kdPlane->gridAttributes(orientation()); QPen gridPen = (gridClass == OdfMinorGrid ? attributes.subGridPen() : attributes.gridPen()); KoOdfGraphicStyles::saveOdfStrokeStyle(gridStyle, mainStyles, gridPen); bodyWriter.startElement("chart:grid"); bodyWriter.addAttribute("chart:class", gridClass == OdfMinorGrid ? "minor" : "major"); bodyWriter.addAttribute("chart:style-name", mainStyles.insert(gridStyle, "ch")); bodyWriter.endElement(); // chart:grid } void Axis::update() const { if (d->kdBarDiagram) { d->kdBarDiagram->doItemsLayout(); d->kdBarDiagram->update(); } if (d->kdLineDiagram) { d->kdLineDiagram->doItemsLayout(); d->kdLineDiagram->update(); } if (d->kdStockDiagram) { d->kdStockDiagram->doItemsLayout(); d->kdStockDiagram->update(); } d->plotArea->parent()->requestRepaint(); } KChart::CartesianAxis *Axis::kdAxis() const { return d->kdAxis; } KChart::AbstractCoordinatePlane *Axis::kdPlane() const { return d->kdPlane; } void Axis::plotAreaChartTypeChanged(ChartType newChartType) { if (dimension() != YAxisDimension) return; // Return if there's nothing to do if (newChartType == d->plotAreaChartType) return; if (d->dataSets.isEmpty()) { d->plotAreaChartType = newChartType; return; } //qDebug() << "changed ChartType"; ChartType oldChartType = d->plotAreaChartType; debugChartAxis<"<kdRadarDiagram->setFillAlpha(0); } else if (newChartType == FilledRadarChartType && oldChartType == RadarChartType) { d->kdRadarDiagram->setFillAlpha(0.4); } else { KChart::AbstractDiagram *newDiagram = d->getDiagram(newChartType); if (newDiagram) { debugChartAxis<<"already exists:"<deleteDiagram(newDiagram); } newDiagram = d->getDiagramAndCreateIfNeeded(newChartType); KChartModel *newModel = dynamic_cast(newDiagram->model()); // FIXME: This causes a crash on unimplemented types. We should // handle that in some other way. Q_ASSERT(newModel); foreach (DataSet *dataSet, d->dataSets) { //if (dataSet->chartType() != LastChartType) { dataSet->setChartType(LastChartType); dataSet->setChartSubType(NoChartSubtype); //} } KChart::AbstractDiagram *oldDiagram = d->getDiagram(oldChartType); Q_ASSERT(oldDiagram); // We need to know the old model so that we can remove the data sets // from the old model that we added to the new model. KChartModel *oldModel = dynamic_cast(oldDiagram->model()); Q_ASSERT(oldModel); foreach (DataSet *dataSet, d->dataSets) { if (dataSet->chartType() != LastChartType) { continue; } newModel->addDataSet(dataSet); const int dataSetCount = oldModel->dataDirection() == Qt::Vertical ? oldModel->columnCount() : oldModel->rowCount(); if (dataSetCount == oldModel->dataDimensions()) { d->deleteDiagram(oldChartType); } else { oldModel->removeDataSet(dataSet); } } } d->plotAreaChartType = newChartType; layoutPlanes(); requestRepaint(); } void Axis::plotAreaChartSubTypeChanged(ChartSubtype subType) { d->plotAreaChartSubType = subType; if (d->kdBarDiagram) { d->kdBarDiagram->setUnitSuffix("", d->kdBarDiagram->orientation()); } switch (d->plotAreaChartType) { case BarChartType: if (d->kdBarDiagram) { KChart::BarDiagram::BarType type; switch (subType) { case StackedChartSubtype: type = KChart::BarDiagram::Stacked; break; case PercentChartSubtype: type = KChart::BarDiagram::Percent; d->kdBarDiagram->setUnitSuffix("%", d->kdBarDiagram->orientation()); break; default: type = KChart::BarDiagram::Normal; } d->kdBarDiagram->setType(type); } break; case LineChartType: if (d->kdLineDiagram) { KChart::LineDiagram::LineType type; switch (subType) { case StackedChartSubtype: type = KChart::LineDiagram::Stacked; break; case PercentChartSubtype: type = KChart::LineDiagram::Percent; d->kdLineDiagram->setUnitSuffix("%", Qt::Vertical); break; default: type = KChart::LineDiagram::Normal; } d->kdLineDiagram->setType(type); } break; case AreaChartType: if (d->kdAreaDiagram) { KChart::LineDiagram::LineType type; switch (subType) { case StackedChartSubtype: type = KChart::LineDiagram::Stacked; break; case PercentChartSubtype: type = KChart::LineDiagram::Percent; d->kdAreaDiagram->setUnitSuffix("%", Qt::Vertical); break; default: type = KChart::LineDiagram::Normal; } d->kdAreaDiagram->setType(type); } break; case RadarChartType: case FilledRadarChartType: #if 0 // FIXME: Stacked and Percent not supported by KChart if (d->kdRadarDiagram) { KChart::PolarDiagram::PolarType type; switch (subType) { case StackedChartSubtype: type = KChart::PolarDiagram::Stacked; break; case PercentChartSubtype: type = KChart::PolarDiagram::Percent; break; default: type = KChart::PolarDiagram::Normal; } d->kdRadarDiagram->setType(type); } #endif break; case StockChartType: if (d->kdStockDiagram) { KChart::StockDiagram::Type type; switch (subType) { case CandlestickChartSubtype: type = KChart::StockDiagram::Candlestick; break; case OpenHighLowCloseChartSubtype: type = KChart::StockDiagram::OpenHighLowClose; break; default: type = KChart::StockDiagram::HighLowClose; break; } d->kdStockDiagram->setType(type); } break; default:; // FIXME: Implement more chart types } Q_FOREACH(DataSet* set, d->dataSets) { set->setChartType(d->plotAreaChartType); set->setChartSubType(subType); } } void Axis::plotAreaIsVerticalChanged() { if (d->kdBarDiagram) { d->kdBarDiagram->setOrientation(d->plotArea->isVertical() ? Qt::Horizontal : Qt::Vertical); } updateKChartAxisPosition(); } void Axis::Private::updatePosition() { // // Is the first x or y axis? // bool first = (dimension == XAxisDimension) ? plotArea->xAxis() == q // : plotArea->yAxis() == q; // // Position position; // ItemType type = GenericItemType; // if (q->orientation() == Qt::Horizontal) { // position = first ? BottomPosition : TopPosition; // type = first ? XAxisTitleType : SecondaryXAxisTitleType; // } else { // position = first ? StartPosition : EndPosition; // type = first ? YAxisTitleType : SecondaryYAxisTitleType; // } // // KChart // kdAxis->setPosition(PositionToKChartAxisPosition(position)); // ChartLayout *layout = plotArea->parent()->layout(); // layout->setPosition(title, position, type); // layout->layout(); // // q->requestRepaint(); } void Axis::registerAxis(Axis *axis) { if (d->kdBarDiagram) { d->kdBarDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdBarDiagram); } if (d->kdLineDiagram) { d->kdLineDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdLineDiagram); } if (d->kdAreaDiagram) { d->kdAreaDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdAreaDiagram); } if (d->kdScatterDiagram) { d->kdScatterDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdScatterDiagram); } if (d->kdStockDiagram) { d->kdStockDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdStockDiagram); } if (d->kdBubbleDiagram) { d->kdBubbleDiagram->addAxis(axis->kdAxis()); axis->registerDiagram(d->kdBubbleDiagram); } // FIXME: Add all diagrams here } void Axis::registerDiagram(KChart::AbstractCartesianDiagram *diagram) { if (!d->diagrams.contains(diagram)) { d->diagrams << diagram; } } void Axis::Private::restoreDiagrams() { diagrams.removeAll(nullptr); for (KChart::AbstractCartesianDiagram *diag : diagrams) { diag->addAxis(kdAxis); } } void Axis::removeAxisFromDiagrams(bool clear) { // HACK to remove an x-axis from a y-axis diagram d->diagrams.removeAll(nullptr); for (KChart::AbstractCartesianDiagram *diag : d->diagrams) { diag->takeAxis(d->kdAxis); } if (clear) { d->diagrams.clear(); } } void Axis::setThreeD(bool threeD) { // FIXME: Setting KD Chart attributes does not belong here. They should be // determined dynamically somehow. // KChart if (d->kdBarDiagram) { KChart::ThreeDBarAttributes attributes(d->kdBarDiagram->threeDBarAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdBarDiagram->setThreeDBarAttributes(attributes); } if (d->kdLineDiagram) { KChart::ThreeDLineAttributes attributes(d->kdLineDiagram->threeDLineAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdLineDiagram->setThreeDLineAttributes(attributes); } if (d->kdAreaDiagram) { KChart::ThreeDLineAttributes attributes(d->kdAreaDiagram->threeDLineAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdAreaDiagram->setThreeDLineAttributes(attributes); } if (d->kdCircleDiagram) { KChart::ThreeDPieAttributes attributes(d->kdCircleDiagram->threeDPieAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdCircleDiagram->setThreeDPieAttributes(attributes); } if (d->kdRingDiagram) { KChart::ThreeDPieAttributes attributes(d->kdRingDiagram->threeDPieAttributes()); attributes.setEnabled(threeD); attributes.setDepth(15.0); attributes.setThreeDBrushEnabled(threeD); d->kdRingDiagram->setThreeDPieAttributes(attributes); } // The following types don't support 3D, at least not in KChart: // scatter, radar, stock, bubble, surface, gantt requestRepaint(); } void Axis::requestRepaint() const { d->plotArea->requestRepaint(); } void Axis::layoutPlanes() { d->kdPlane->layoutPlanes(); d->kdPolarPlane->layoutPlanes(); d->kdRadarPlane->layoutPlanes(); } int Axis::gapBetweenBars() const { return d->gapBetweenBars; } void Axis::setGapBetweenBars(int percent) { // This method is also used to override KChart's default attributes. // Do not just return and do nothing if value doesn't differ from stored one. d->gapBetweenBars = percent; if (d->kdBarDiagram) { KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes(); attributes.setBarGapFactor((float)percent / 100.0); d->kdBarDiagram->setBarAttributes(attributes); } requestRepaint(); } int Axis::gapBetweenSets() const { return d->gapBetweenSets; } void Axis::setGapBetweenSets(int percent) { // This method is also used to override KChart's default attributes. // Do not just return and do nothing if value doesn't differ from stored one. d->gapBetweenSets = percent; if (d->kdBarDiagram) { KChart::BarAttributes attributes = d->kdBarDiagram->barAttributes(); attributes.setGroupGapFactor((float)percent / 100.0); d->kdBarDiagram->setBarAttributes(attributes); } requestRepaint(); } void Axis::setAngleOffset(qreal angle) { // only set if we already have a diagram else the value will be picked up on creating the diagram if (d->kdPolarPlane->diagram()) { d->kdPolarPlane->setStartPosition(angle); requestRepaint(); } } void Axis::setHoleSize(qreal value) { //TODO KChart does not support } QFont Axis::font() const { return d->kdAxis->textAttributes().font(); } void Axis::setFont(const QFont &font) { // Set the KChart axis to use this font KChart::TextAttributes attr = d->kdAxis->textAttributes(); attr.setFont(font); d->kdAxis->setTextAttributes(attr); } qreal Axis::fontSize() const { return d->kdAxis->textAttributes().fontSize().value(); } void Axis::setFontSize(qreal size) { // KChart has its own fontsize storage, it does not use QFont KChart::TextAttributes attributes = d->kdAxis->textAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdAxis->setTextAttributes(attributes); // Keep font in sync QFont f = font(); f.setPointSizeF(size); setFont(f); } bool Axis::isVisible() const { return d->isVisible; } void Axis::setVisible(bool visible) { debugChartAxis<isVisible<<"->"<kdBarDiagram; d->isVisible = visible; if (visible) { d->restoreDiagrams(); } else { removeAxisFromDiagrams(); } } KoOdfNumberStyles::NumericStyleFormat *Axis::numericStyleFormat() const { return d->numericStyleFormat; } void Axis::SetNumericStyleFormat(KoOdfNumberStyles::NumericStyleFormat *numericStyleFormat) const { delete d->numericStyleFormat; d->numericStyleFormat = numericStyleFormat; } void Axis::setOdfAxisPosition(const QString &odfpos) { d->axisPosition = odfpos; } QString Axis::odfAxisPosition() const { return d->axisPosition; } void Axis::updateKChartAxisPosition() { if (!isCartesian(d->plotArea->chartType())) { debugChartAxis<plotArea->chartType(); return; } KChart::CartesianAxis::Position pos; if (d->plotArea->xAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Left; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Right; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } else { pos = KChart::CartesianAxis::Bottom; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Top; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->yAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Bottom; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Top; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } else { pos = KChart::CartesianAxis::Left; if (d->axisPosition == "end") { pos = KChart::CartesianAxis::Right; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->secondaryXAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Right; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Left; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Left ? KChart::CartesianAxis::Right : KChart::CartesianAxis::Left; } } else { pos = KChart::CartesianAxis::Top; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Bottom; } Axis *yAxis = d->plotArea->yAxis(); if (yAxis && yAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Top ? KChart::CartesianAxis::Bottom : KChart::CartesianAxis::Top; } } d->kdAxis->setPosition(pos); } else if (d->plotArea->secondaryYAxis() == this) { if (d->plotArea->isVertical()) { pos = KChart::CartesianAxis::Top; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Bottom; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Bottom ? KChart::CartesianAxis::Top : KChart::CartesianAxis::Bottom; } } else { pos = KChart::CartesianAxis::Right; if (d->axisPosition == "start") { pos = KChart::CartesianAxis::Left; } Axis *xAxis = d->plotArea->xAxis(); if (xAxis && xAxis->axisDirectionReversed()) { pos = pos == KChart::CartesianAxis::Right ? KChart::CartesianAxis::Left : KChart::CartesianAxis::Right; } } d->kdAxis->setPosition(pos); } debugChartAxis<kdAxis<kdAxis->isAbscissa(); d->plotArea->plotAreaUpdate(); } CartesianAxis::Position Axis::kchartAxisPosition() const { return d->kdAxis->position(); } CartesianAxis::Position Axis::actualAxisPosition() const { CartesianAxis::Position pos = d->kdAxis->position(); if (d->plotArea->isVertical()) { switch (pos) { case KChart::CartesianAxis::Bottom: pos = KChart::CartesianAxis::Left; break; case KChart::CartesianAxis::Top: pos = KChart::CartesianAxis::Right; break; case KChart::CartesianAxis::Left: pos = KChart::CartesianAxis::Bottom; break; case KChart::CartesianAxis::Right: pos = KChart::CartesianAxis::Top; break; } } return pos; } bool Axis::axisDirectionReversed() const { bool reversed = false; KChart::CartesianCoordinatePlane *plane = dynamic_cast(kdPlane()); if (plane) { if (orientation() == Qt::Horizontal) reversed = plane->isHorizontalRangeReversed(); else // Qt::Vertical reversed = plane->isVerticalRangeReversed(); } return reversed; } void Axis::setOdfAxisLabelsPosition(const QString &odfpos) { d->axisLabelsPosition = odfpos; } QString Axis::odfAxisLabelsPosition() const { return d->axisLabelsPosition; } void Axis::updateKChartStockAttributes() { if (d->kdStockDiagram) { d->kdStockDiagram->setLowHighLinePen(d->plotArea->stockRangeLinePen()); d->kdStockDiagram->setUpTrendCandlestickBrush(d->plotArea->stockGainBrush()); d->kdStockDiagram->setDownTrendCandlestickBrush(d->plotArea->stockLossBrush()); d->kdStockDiagram->setUpTrendCandlestickPen(d->plotArea->stockRangeLinePen()); d->kdStockDiagram->setDownTrendCandlestickPen(d->plotArea->stockRangeLinePen()); } } QDebug operator<<(QDebug dbg, KoChart::Axis *a) { dbg.nospace().noquote() <<"Axis["<name()<<']'; return dbg.space().quote(); } diff --git a/plugins/chartshape/Legend.cpp b/plugins/chartshape/Legend.cpp index a4794248fc8..092d2808805 100644 --- a/plugins/chartshape/Legend.cpp +++ b/plugins/chartshape/Legend.cpp @@ -1,591 +1,559 @@ /* This file is part of the KDE project Copyright 2007 Johannes Simon Copyright 2010 Inge Wallin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "Legend.h" // Qt #include #include #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include "KChartConvertions.h" #include "kchart_version.h" // KoChart #include "PlotArea.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "OdfLoadingHelper.h" #include "OdfHelper.h" using namespace KoChart; class Legend::Private { public: Private(); ~Private(); ChartShape *shape; // Properties of the Legend QString title; LegendExpansion expansion; Position position; QFont font; QFont titleFont; QColor fontColor; Qt::Alignment alignment; KoShapeStroke *lineBorder; // The connection to KChart KChart::Legend *kdLegend; QImage image; mutable bool pixmapRepaintRequested; QSizeF lastSize; QPointF lastZoomLevel; }; Legend::Private::Private() { lineBorder = new KoShapeStroke(0.5, Qt::black); expansion = HighLegendExpansion; alignment = Qt::AlignCenter; pixmapRepaintRequested = true; position = EndPosition; } Legend::Private::~Private() { delete lineBorder; } Legend::Legend(ChartShape *parent) : QObject(parent) , d(new Private()) { Q_ASSERT(parent); setShapeId("ChartShapeLegend"); d->shape = parent; d->kdLegend = new KChart::Legend(); d->kdLegend->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); // we use the shape to display frame and background KChart::FrameAttributes frameAttr = d->kdLegend->frameAttributes(); frameAttr.setVisible(false); d->kdLegend->setFrameAttributes(frameAttr); setTitleFontSize(10); setTitle(QString()); setFontSize(8); update(); parent->addShape(this); setAllowedInteraction(KoShape::ResizeAllowed, false); setAllowedInteraction(KoShape::RotationAllowed, false); connect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); connect (parent, SIGNAL(chartTypeChanged(ChartType, ChartType)), this, SLOT(slotChartTypeChanged(ChartType))); } Legend::~Legend() { delete d->kdLegend; delete d; } QString Legend::title() const { return d->title; } void Legend::setTitle(const QString &title) { d->title = title; d->kdLegend->setTitleText(title); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } QFont Legend::font() const { return d->font; } void Legend::setFont(const QFont &font) { d->font = font; // KChart KChart::TextAttributes attributes = d->kdLegend->textAttributes(); attributes.setFont(font); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } qreal Legend::fontSize() const { return d->font.pointSizeF(); } void Legend::setFontSize(qreal size) { d->font.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdLegend->textAttributes(); KChart::Measure m = attributes.fontSize(); m.setValue(size); attributes.setFontSize(m); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } void Legend::setFontColor(const QColor &color) { KChart::TextAttributes attributes = d->kdLegend->textAttributes(); QPen pen = attributes.pen(); pen.setColor(color); attributes.setPen(pen); d->kdLegend->setTextAttributes(attributes); d->pixmapRepaintRequested = true; } QColor Legend::fontColor() const { KChart::TextAttributes attributes = d->kdLegend->textAttributes(); QPen pen = attributes.pen(); return pen.color(); } QFont Legend::titleFont() const { return d->titleFont; } void Legend::setTitleFont(const QFont &font) { d->titleFont = font; // KChart KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); attributes.setFont(font); d->kdLegend->setTitleTextAttributes(attributes); d->pixmapRepaintRequested = true; } qreal Legend::titleFontSize() const { return d->titleFont.pointSizeF(); } void Legend::setTitleFontSize(qreal size) { d->titleFont.setPointSizeF(size); // KChart KChart::TextAttributes attributes = d->kdLegend->titleTextAttributes(); attributes.setFontSize(KChart::Measure(size, KChartEnums::MeasureCalculationModeAbsolute)); d->kdLegend->setTitleTextAttributes(attributes); d->pixmapRepaintRequested = true; } LegendExpansion Legend::expansion() const { return d->expansion; } void Legend::setExpansion(LegendExpansion expansion) { d->expansion = expansion; d->kdLegend->setOrientation(LegendExpansionToQtOrientation(expansion)); d->pixmapRepaintRequested = true; emit updateConfigWidget(); } Qt::Alignment Legend::alignment() const { return d->alignment; } void Legend::setAlignment(Qt::Alignment alignment) { d->alignment = alignment; } Position Legend::legendPosition() const { return d->position; } void Legend::setLegendPosition(Position position) { d->position = position; d->pixmapRepaintRequested = true; } // Note that size is controlled by the KChart::Legend // via the propertyChanged() signal // so size will change dependent on amount of data void Legend::setSize(const QSizeF &newSize) { KoShape::setSize(newSize); } void Legend::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(d->lastSize).toSize(); d->image = QImage(paintRectSize, QImage::Format_ARGB32); QPainter pixmapPainter(&d->image); pixmapPainter.setRenderHints(painter.renderHints()); pixmapPainter.setRenderHint(QPainter::Antialiasing, false); // Scale the painter's coordinate system to fit the current zoom level. applyConversion(pixmapPainter, converter); d->kdLegend->paint(&pixmapPainter); } void Legend::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { //painter.save(); // First of all, scale the painter's coordinate system to fit the current zoom level applyConversion(painter, converter); // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); //clipRect.intersected(paintRect); painter.setClipRect(paintRect, Qt::IntersectClip); // Get the current zoom level QPointF zoomLevel; converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); // Only repaint the pixmap if it is scheduled, the zoom level changed or the shape was resized /*if ( d->pixmapRepaintRequested || d->lastZoomLevel != zoomLevel || d->lastSize != size()) { // TODO: What if two zoom levels are constantly being requested? // At the moment, this *is* the case, due to the fact // that the shape is also rendered in the page overview // in Stage // Every time the window is hidden and shown again, a repaint is // requested --> laggy performance, especially when quickly // switching through windows d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); paintPixmap(painter, converter); }*/ // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } -#if KCHART_VERSION >= ((2<<16)|(6<<8)|(89)) disconnect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); // KChart thinks in pixels, Calligra in pt ScreenConversions::scaleFromPtToPx(painter); const QRect rect = ScreenConversions::scaleFromPtToPx(paintRect, painter); // KChart works with its logicalDpi which may differ from ours if set with --dpi ScreenConversions::scaleToWidgetDpi(d->kdLegend, painter); d->kdLegend->paint(&painter, rect); connect (d->kdLegend, SIGNAL(propertiesChanged()), this, SLOT(slotKdLegendChanged())); -#else - // KChart thinks in pixels, Calligra in pt - // KChart also for non-QWidget painting devices cares for the logicalDpi - // Other than PlotArea we do not control the output size via the paint method, - // so here have to resize the legend temporarily. - // Printing should only result in 1 paint call, so this should not happen too often. - // TODO: something in KChart seems broken in general on printer output, also seen in kchart examples - // so legend in print is still broken :/ - const QSize sizePx = d->kdLegend->size(); - const QSize newSizePx = ScreenConversions::scaleFromPtToPx(size(), painter); - const bool isPainterDifferentDpi = (sizePx != newSizePx); - if (isPainterDifferentDpi) { - // temporarily set a size matching the painterdevice - d->kdLegend->resize(newSizePx); - d->kdLegend->resizeLayout(newSizePx); - } - - ScreenConversions::scaleFromPtToPx(painter); - - d->kdLegend->paint(&painter); - - if (isPainterDifferentDpi) { - // restore screen-dpi size - d->kdLegend->resize(sizePx); - d->kdLegend->resizeLayout(sizePx); - } - - //painter.restore(); - // Paint the cached pixmap - //painter.drawImage(0, 0, d->image); -#endif } // ---------------------------------------------------------------- // loading and saving bool Legend::loadOdf(const KoXmlElement &legendElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.clear(); // FIXME: If the style isn't present we shouldn't care about it at all // and move everything related to the legend style in this if clause if (legendElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { context.odfLoadingContext().fillStyleStack(legendElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); } if (!legendElement.isNull()) { int attributesToLoad = OdfAllAttributes; QString lp = legendElement.attributeNS(KoXmlNS::chart, "legend-position", QString()); // Note: load position even if it might not be used loadOdfAttributes(legendElement, context, attributesToLoad); QString lalign = legendElement.attributeNS(KoXmlNS::chart, "legend-align", QString()); if (legendElement.hasAttributeNS(KoXmlNS::style, "legend-expansion")) { QString lexpansion = legendElement.attributeNS(KoXmlNS::style, "legend-expansion", QString()); if (lexpansion == "wide") setExpansion(WideLegendExpansion); else if (lexpansion == "high") setExpansion(HighLegendExpansion); else setExpansion(BalancedLegendExpansion); } if (lalign == "start") { setAlignment(Qt::AlignLeft); } else if (lalign == "end") { setAlignment(Qt::AlignRight); } else { setAlignment(Qt::AlignCenter); // default } if (lp == "start") { setLegendPosition(StartPosition); } else if (lp == "top") { setLegendPosition(TopPosition); } else if (lp == "bottom") { setLegendPosition(BottomPosition); } else if (lp == "end") { setLegendPosition(EndPosition); } else if (lp == "top-start") { setLegendPosition(TopStartPosition); } else if (lp == "bottom-start") { setLegendPosition(BottomStartPosition); } else if (lp == "top-end") { setLegendPosition(TopEndPosition); } else if (lp == "bottom-end") { setLegendPosition(BottomEndPosition); } else { setLegendPosition(FloatingPosition); } if (legendElement.hasAttributeNS(KoXmlNS::office, "title")) { setTitle(legendElement.attributeNS(KoXmlNS::office, "title", QString())); } styleStack.setTypeProperties("text"); if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) { QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family"); QFont font = d->font; font.setFamily(fontFamily); setFont(font); } if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size")); setFontSize(fontSize); } if (styleStack.hasProperty(KoXmlNS::fo, "font-color")) { QColor color = styleStack.property(KoXmlNS::fo, "font-color"); if (color.isValid()) { setFontColor(color); } } } else { // No legend element, use default legend. setLegendPosition(EndPosition); setAlignment(Qt::AlignCenter); } d->pixmapRepaintRequested = true; return true; } void Legend::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); bodyWriter.startElement("chart:legend"); saveOdfAttributes(context, OdfPosition); // Legend specific attributes QString lp = PositionToString(d->position); if (!lp.isEmpty()) { bodyWriter.addAttribute("chart:legend-position", lp); } QString lalign; switch (d->alignment) { case Qt::AlignLeft: lalign = "start"; break; case Qt::AlignRight: lalign = "end"; break; case Qt::AlignCenter: lalign = "center"; break; default: break; } if (!lalign.isEmpty()) { bodyWriter.addAttribute("chart:legend-align", lalign); } // Legend style FIXME: Check if more styling then just the font goes here. KoGenStyle style(KoGenStyle::ChartAutoStyle, "chart", 0); OdfHelper::saveOdfFont(style, d->font, d->fontColor); bodyWriter.addAttribute("chart:style-name", saveStyle(style, context)); QString lexpansion; switch (expansion()) { case WideLegendExpansion: lexpansion = "wide"; break; case HighLegendExpansion: lexpansion = "high"; break; case BalancedLegendExpansion: lexpansion = "balanced"; break; }; bodyWriter.addAttribute("style:legend-expansion", lexpansion); if (!title().isEmpty()) bodyWriter.addAttribute("office:title", title()); bodyWriter.endElement(); // chart:legend } KChart::Legend *Legend::kdLegend() const { // There has to be a valid KChart instance of this legend Q_ASSERT(d->kdLegend); return d->kdLegend; } void Legend::rebuild() { d->kdLegend->forceRebuild(); update(); } void Legend::update() const { d->pixmapRepaintRequested = true; KoShape::update(); } void Legend::slotKdLegendChanged() { // FIXME: Update legend properly by implementing all *DataChanged() slots // in KChartModel. Right now, only yDataChanged() is implemented. //d->kdLegend->forceRebuild(); QSizeF size = ScreenConversions::scaleFromPxToPt(d->kdLegend->sizeHint()); setSize(ScreenConversions::fromWidgetDpi(d->kdLegend, size)); update(); } void Legend::slotChartTypeChanged(ChartType chartType) { // TODO: Once we support markers, this switch will have to be // more clever. switch (chartType) { case LineChartType: case ScatterChartType: d->kdLegend->setLegendStyle(KChart::Legend::MarkersAndLines); break; default: d->kdLegend->setLegendStyle(KChart::Legend::MarkersOnly); break; } } diff --git a/plugins/chartshape/PlotArea.cpp b/plugins/chartshape/PlotArea.cpp index dbeccd48d9f..55aca8bb217 100644 --- a/plugins/chartshape/PlotArea.cpp +++ b/plugins/chartshape/PlotArea.cpp @@ -1,1432 +1,1429 @@ /* This file is part of the KDE project Copyright 2007-2008 Johannes Simon Copyright 2009-2010 Inge Wallin Copyright 2018 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "PlotArea.h" // Qt #include #include #include #include #include #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include // KChart #include #include #include #include #include #include #include #include #include // Attribute Classes #include #include #include #include #include // Diagram Classes #include #include #include #include #include // KoChart #include "Legend.h" #include "Surface.h" #include "Axis.h" #include "DataSet.h" #include "ChartProxyModel.h" #include "ScreenConversions.h" #include "ChartLayout.h" #include "ChartDebug.h" using namespace KoChart; const int MAX_PIXMAP_SIZE = 1000; Q_DECLARE_METATYPE(QPointer) typedef QList CoordinatePlaneList; class PlotArea::Private { public: Private(PlotArea *q, ChartShape *parent); ~Private(); void initAxes(); void updateAxesPosition(); CoordinatePlaneList coordinatePlanesForChartType(ChartType type); void autoHideAxisTitles(); PlotArea *q; // The parent chart shape ChartShape *shape; // ---------------------------------------------------------------- // Parts and properties of the chart ChartType chartType; ChartSubtype chartSubtype; Surface *wall; Surface *floor; // Only used in 3D charts // The axes QList axes; QList automaticallyHiddenAxisTitles; // 3D properties bool threeD; Ko3dScene *threeDScene; // ---------------------------------------------------------------- // Data specific to each chart type // 1. Bar charts // FIXME: OpenOffice stores these attributes in the axes' elements. // The specs don't say anything at all about what elements can have // these style attributes. // chart:vertical attribute: see ODF v1.2,19.63 bool vertical; // 2. Polar charts (pie/ring) qreal angleOffset; // in degrees qreal holeSize; // ---------------------------------------------------------------- // The embedded KD Chart // The KD Chart parts KChart::Chart *const kdChart; KChart::CartesianCoordinatePlane *const kdCartesianPlanePrimary; KChart::CartesianCoordinatePlane *const kdCartesianPlaneSecondary; KChart::PolarCoordinatePlane *const kdPolarPlane; KChart::RadarCoordinatePlane *const kdRadarPlane; QList kdDiagrams; // Caching: We can rerender faster if we cache KChart's output QImage image; bool paintPixmap; QPointF lastZoomLevel; QSizeF lastSize; mutable bool pixmapRepaintRequested; QPen stockRangeLinePen; QBrush stockGainBrush; QBrush stockLossBrush; QString symbolType; QString symbolName; DataSet::ValueLabelType valueLabelType; }; PlotArea::Private::Private(PlotArea *q, ChartShape *parent) : q(q) , shape(parent) // Default type: normal bar chart , chartType(BarChartType) , chartSubtype(NormalChartSubtype) , wall(0) , floor(0) , threeD(false) , threeDScene(0) // By default, x and y axes are not swapped. , vertical(false) // OpenOffice.org's default. It means the first pie slice starts at the // very top (and then going counter-clockwise). , angleOffset(90.0) , holeSize(50.0) // KCharts approx default // KD Chart stuff , kdChart(new KChart::Chart()) , kdCartesianPlanePrimary(new KChart::CartesianCoordinatePlane(kdChart)) , kdCartesianPlaneSecondary(new KChart::CartesianCoordinatePlane(kdChart)) , kdPolarPlane(new KChart::PolarCoordinatePlane(kdChart)) , kdRadarPlane(new KChart::RadarCoordinatePlane(kdChart)) // Cache , paintPixmap(true) , pixmapRepaintRequested(true) , symbolType("automatic") { kdCartesianPlanePrimary->setObjectName("primary"); kdCartesianPlaneSecondary->setObjectName("secondary"); // --- Prepare Primary Cartesian Coordinate Plane --- KChart::GridAttributes gridAttributes; gridAttributes.setGridVisible(false); gridAttributes.setGridGranularitySequence(KChartEnums::GranularitySequence_10_50); kdCartesianPlanePrimary->setGlobalGridAttributes(gridAttributes); // --- Prepare Secondary Cartesian Coordinate Plane --- kdCartesianPlaneSecondary->setGlobalGridAttributes(gridAttributes); // --- Prepare Polar Coordinate Plane --- KChart::GridAttributes polarGridAttributes; polarGridAttributes.setGridVisible(false); kdPolarPlane->setGlobalGridAttributes(polarGridAttributes); // --- Prepare Radar Coordinate Plane --- KChart::GridAttributes radarGridAttributes; polarGridAttributes.setGridVisible(true); kdRadarPlane->setGlobalGridAttributes(radarGridAttributes); // By default we use a cartesian chart (bar chart), so the polar planes // are not needed yet. They will be added on demand in setChartType(). kdChart->takeCoordinatePlane(kdPolarPlane); kdChart->takeCoordinatePlane(kdRadarPlane); shape->proxyModel()->setDataDimensions(1); stockRangeLinePen.setWidthF(2.0); stockGainBrush = QBrush(QColor(Qt::white)); stockLossBrush = QBrush(QColor(Qt::black)); } PlotArea::Private::~Private() { // remove first to avoid crash while (!kdChart->coordinatePlanes().isEmpty()) { kdChart->takeCoordinatePlane(kdChart->coordinatePlanes().last()); } qDeleteAll(axes); delete kdCartesianPlanePrimary; delete kdCartesianPlaneSecondary; delete kdPolarPlane; delete kdRadarPlane; delete kdChart; delete wall; delete floor; delete threeDScene; } void PlotArea::Private::initAxes() { // The category data region is anchored to an axis and will be set on addAxis if the // axis defines the Axis::categoryDataRegion(). So, clear it now. q->proxyModel()->setCategoryDataRegion(CellRegion()); // Remove all old axes while(!axes.isEmpty()) { Axis *axis = axes.takeLast(); Q_ASSERT(axis); if (axis->title()) automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // There need to be at least these two axes. Their constructor will // automatically add them to the plot area as child shape. new Axis(q, XAxisDimension); Axis *yAxis = new Axis(q, YAxisDimension); yAxis->setShowMajorGrid(true); updateAxesPosition(); } void PlotArea::Private::updateAxesPosition() { debugChartAxis<updateKChartAxisPosition(); } } PlotArea::PlotArea(ChartShape *parent) : QObject() , KoShape() , d(new Private(this, parent)) { setShapeId("ChartShapePlotArea"); // NB! used by defaulttool/ChartResizeStrategy.cpp Q_ASSERT(d->shape); Q_ASSERT(d->shape->proxyModel()); setAdditionalStyleAttribute("chart:auto-position", "true"); setAdditionalStyleAttribute("chart:auto-size", "true"); connect(d->shape->proxyModel(), SIGNAL(modelReset()), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(proxyModelStructureChanged())); connect(d->shape->proxyModel(), SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(plotAreaUpdate())); connect(d->shape->proxyModel(), SIGNAL(dataChanged()), this, SLOT(plotAreaUpdate())); } PlotArea::~PlotArea() { delete d; } void PlotArea::plotAreaInit() { d->kdChart->resize(size().toSize()); d->kdChart->replaceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdCartesianPlaneSecondary->setReferenceCoordinatePlane(d->kdCartesianPlanePrimary); d->kdChart->addCoordinatePlane(d->kdCartesianPlaneSecondary); KChart::FrameAttributes attr = d->kdChart->frameAttributes(); attr.setVisible(false); d->kdChart->setFrameAttributes(attr); d->wall = new Surface(this); //d->floor = new Surface(this); d->initAxes(); addAxesTitlesToLayout(); } void PlotArea::proxyModelStructureChanged() { if (proxyModel()->isLoading()) return; Q_ASSERT(xAxis()); Q_ASSERT(yAxis()); QMap attachedAxes; QList dataSets = proxyModel()->dataSets(); // Remember to what y axis each data set belongs foreach(DataSet *dataSet, dataSets) attachedAxes.insert(dataSet, dataSet->attachedAxis()); // Proxy structure and thus data sets changed, drop old state and // clear all axes of data sets foreach(Axis *axis, axes()) axis->clearDataSets(); // Now add the new list of data sets to the axis they belong to foreach(DataSet *dataSet, dataSets) { xAxis()->attachDataSet(dataSet); // If they weren't assigned to a y axis before, use default y axis if (attachedAxes[dataSet]) attachedAxes[dataSet]->attachDataSet(dataSet); else yAxis()->attachDataSet(dataSet); } } ChartProxyModel *PlotArea::proxyModel() const { return d->shape->proxyModel(); } QList PlotArea::axes() const { return d->axes; } QList PlotArea::dataSets() const { return proxyModel()->dataSets(); } Axis *PlotArea::xAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) return axis; } return 0; } Axis *PlotArea::yAxis() const { foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) return axis; } return 0; } Axis *PlotArea::secondaryXAxis() const { bool firstXAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == XAxisDimension) { if (firstXAxisFound) return axis; else firstXAxisFound = true; } } return 0; } Axis *PlotArea::secondaryYAxis() const { bool firstYAxisFound = false; foreach(Axis *axis, d->axes) { if (axis->dimension() == YAxisDimension) { if (firstYAxisFound) return axis; else firstYAxisFound = true; } } return 0; } ChartType PlotArea::chartType() const { return d->chartType; } ChartSubtype PlotArea::chartSubType() const { return d->chartSubtype; } bool PlotArea::isThreeD() const { return d->threeD; } bool PlotArea::isVertical() const { return d->chartType == BarChartType && d->vertical; } Ko3dScene *PlotArea::threeDScene() const { return d->threeDScene; } qreal PlotArea::angleOffset() const { return d->angleOffset; } qreal PlotArea::holeSize() const { return d->holeSize; } void PlotArea::setHoleSize(qreal value) { d->holeSize = value; } // FIXME: this should add the axxis as a child (set axis->parent()) bool PlotArea::addAxis(Axis *axis) { if (d->axes.contains(axis)) { warnChart << "PlotArea::addAxis(): Trying to add already added axis."; return false; } if (!axis) { warnChart << "PlotArea::addAxis(): Pointer to axis is NULL!"; return false; } d->axes.append(axis); if (axis->dimension() == XAxisDimension) { // let each axis know about the other axis foreach (Axis *_axis, d->axes) { if (_axis->isVisible()) _axis->registerAxis(axis); } } requestRepaint(); return true; } bool PlotArea::removeAxis(Axis *axis) { bool removed = takeAxis(axis); if (removed) { // This also removes the axis' title, which is a shape as well delete axis; } return removed; } // FIXME: this should remove the axis as a child (set axis->parent()) bool PlotArea::takeAxis(Axis *axis) { if (!d->axes.contains(axis)) { warnChart << "PlotArea::takeAxis(): Trying to remove non-added axis."; return false; } if (!axis) { warnChart << "PlotArea::takeAxis(): Pointer to axis is NULL!"; return false; } if (axis->title()) { d->automaticallyHiddenAxisTitles.removeAll(axis->title()); } d->axes.removeAll(axis); axis->removeAxisFromDiagrams(true); requestRepaint(); return true; } CoordinatePlaneList PlotArea::Private::coordinatePlanesForChartType(ChartType type) { CoordinatePlaneList result; switch (type) { case BarChartType: case LineChartType: case AreaChartType: case ScatterChartType: case GanttChartType: case SurfaceChartType: case StockChartType: case BubbleChartType: result.append(kdCartesianPlanePrimary); result.append(kdCartesianPlaneSecondary); break; case CircleChartType: case RingChartType: result.append(kdPolarPlane); break; case RadarChartType: case FilledRadarChartType: result.append(kdRadarPlane); break; case LastChartType: Q_ASSERT("There's no coordinate plane for LastChartType"); break; } Q_ASSERT(!result.isEmpty()); return result; } void PlotArea::Private::autoHideAxisTitles() { automaticallyHiddenAxisTitles.clear(); foreach (Axis *axis, axes) { if (axis->title()->isVisible()) { axis->title()->setVisible(false); automaticallyHiddenAxisTitles.append(axis->title()); } } } void PlotArea::setChartType(ChartType type) { if (d->chartType == type) return; // Lots of things to do if the old and new types of coordinate // systems don't match. if (!isPolar(d->chartType) && isPolar(type)) { d->autoHideAxisTitles(); } else if (isPolar(d->chartType) && !isPolar(type)) { foreach (KoShape *title, d->automaticallyHiddenAxisTitles) { title->setVisible(true); } d->automaticallyHiddenAxisTitles.clear(); } CellRegion region = d->shape->proxyModel()->cellRangeAddress(); if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->setManualControl(false); xAxis()->clearDataSets(); yAxis()->clearDataSets(); if (secondaryYAxis()) { secondaryYAxis()->clearDataSets(); } if (secondaryXAxis()) { secondaryXAxis()->clearDataSets(); } } CoordinatePlaneList planesToRemove; // First remove secondary cartesian plane as it references the primary // plane, otherwise KChart will come down crashing on us. Note that // removing a plane that's not in the chart is not a problem. planesToRemove << d->kdCartesianPlaneSecondary << d->kdCartesianPlanePrimary << d->kdPolarPlane << d->kdRadarPlane; foreach(KChart::AbstractCoordinatePlane *plane, planesToRemove) d->kdChart->takeCoordinatePlane(plane); CoordinatePlaneList newPlanes = d->coordinatePlanesForChartType(type); foreach(KChart::AbstractCoordinatePlane *plane, newPlanes) d->kdChart->addCoordinatePlane(plane); Q_ASSERT(d->kdChart->coordinatePlanes() == newPlanes); d->chartType = type; foreach (Axis *axis, d->axes) { axis->plotAreaChartTypeChanged(type); } if (type == CircleChartType || type == RingChartType) { d->shape->proxyModel()->reset(region); } if (type != BarChartType) { setVertical(false); // Only supported by bar charts } requestRepaint(); } void PlotArea::setChartSubType(ChartSubtype subType) { d->chartSubtype = subType; foreach (Axis *axis, d->axes) { axis->plotAreaChartSubTypeChanged(subType); } } void PlotArea::setThreeD(bool threeD) { d->threeD = threeD; foreach(Axis *axis, d->axes) axis->setThreeD(threeD); requestRepaint(); } void PlotArea::setVertical(bool vertical) { d->vertical = vertical; foreach(Axis *axis, d->axes) axis->plotAreaIsVerticalChanged(); } // ---------------------------------------------------------------- // loading and saving bool PlotArea::loadOdf(const KoXmlElement &plotAreaElement, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); // The exact position defined in ODF overwrites the default layout position // NOTE: Do not do this as it means functionallity changes just because you save and load. // I don't think odf has an element/attribute that can hold this type of info. // Also afaics libreoffice do not do this. // if (plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") || // plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")) // { // parent()->layout()->setPosition(this, FloatingPosition); // } bool autoPosition = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "x") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "y")); bool autoSize = !(plotAreaElement.hasAttributeNS(KoXmlNS::svg, "width") && plotAreaElement.hasAttributeNS(KoXmlNS::svg, "height")); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); loadOdfAttributes(plotAreaElement, context, OdfAllAttributes); // First step is to clear all old axis instances. while (!d->axes.isEmpty()) { Axis *axis = d->axes.takeLast(); Q_ASSERT(axis); // Clear this axis of all data sets, deleting any diagram associated with it. axis->clearDataSets(); if (axis->title()) d->automaticallyHiddenAxisTitles.removeAll(axis->title()); delete axis; } // Now find out about things that are in the plotarea style. // // These things include chart subtype, special things for some // chart types like line charts, stock charts, etc. // // Note that this has to happen BEFORE we create a axis and call // there loadOdf method cause the axis will evaluate settings // like the PlotArea::isVertical boolean. bool candleStick = false; if (plotAreaElement.hasAttributeNS(KoXmlNS::chart, "style-name")) { styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); styleStack.setTypeProperties("chart"); if (styleStack.hasProperty(KoXmlNS::chart, "auto-position")) { autoPosition |= styleStack.property(KoXmlNS::chart, "auto-position") == "true"; } else { // To be backwards compatible we set auto-position to true as this was the original behaviour // and is the way LO works autoPosition = true; } if (styleStack.hasProperty(KoXmlNS::chart, "auto-size")) { autoSize |= styleStack.property(KoXmlNS::chart, "auto-size") == "true" ; } else { // To be backwards compatible we set auto-size to true as this was the original behaviour // and is the way LO works autoSize = true; } // ring and pie if (styleStack.hasProperty(KoXmlNS::chart, "angle-offset")) { bool ok; const qreal angleOffset = styleStack.property(KoXmlNS::chart, "angle-offset").toDouble(&ok); if (ok) { setAngleOffset(angleOffset); } } // ring if (styleStack.hasProperty(KoXmlNS::chart, "hole-size")) { bool ok; const qreal value = styleStack.property(KoXmlNS::chart, "hole-size").toDouble(&ok); if (ok) { setHoleSize(value); } } // Check for 3D. if (styleStack.hasProperty(KoXmlNS::chart, "three-dimensional")) setThreeD(styleStack.property(KoXmlNS::chart, "three-dimensional") == "true"); d->threeDScene = load3dScene(plotAreaElement); // Set subtypes stacked or percent. // These are valid for Bar, Line, Area and Radar types. if (styleStack.hasProperty(KoXmlNS::chart, "percentage") && styleStack.property(KoXmlNS::chart, "percentage") == "true") { setChartSubType(PercentChartSubtype); } else if (styleStack.hasProperty(KoXmlNS::chart, "stacked") && styleStack.property(KoXmlNS::chart, "stacked") == "true") { setChartSubType(StackedChartSubtype); } // Data specific to bar charts if (styleStack.hasProperty(KoXmlNS::chart, "vertical")) setVertical(styleStack.property(KoXmlNS::chart, "vertical") == "true"); // Data specific to stock charts if (styleStack.hasProperty(KoXmlNS::chart, "japanese-candle-stick")) { candleStick = styleStack.property(KoXmlNS::chart, "japanese-candle-stick") == "true"; } // Special properties for various chart types #if 0 switch () { case BarChartType: if (styleStack) ; } #endif styleStack.clear(); context.odfLoadingContext().fillStyleStack(plotAreaElement, KoXmlNS::chart, "style-name", "chart"); } setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false"); setAdditionalStyleAttribute("chart:auto-size", autoSize ? "true" : "false"); // Now create and load the axis from the ODF. This needs to happen // AFTER we did set some of the basic settings above so the axis // can use those basic settings to evaluate it's own settings // depending on them. This is especially required for the // PlotArea::isVertical() boolean flag else things will go wrong. KoXmlElement n; forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "axis") { if (!n.hasAttributeNS(KoXmlNS::chart, "dimension")) { // We have to know what dimension the axis is supposed to be.. qInfo()<loadOdf(n, context); } } // Two axes are mandatory, check that we have them. if (!xAxis()) { Axis *xAxis = new Axis(this, XAxisDimension); xAxis->setVisible(false); } if (!yAxis()) { Axis *yAxis = new Axis(this, YAxisDimension); yAxis->setVisible(false); } // Now, after the axes, load the datasets. // Note that this only contains properties of the datasets, the // actual data is not stored here. // // FIXME: Isn't the proxy model a strange place to store this data? proxyModel()->loadOdf(plotAreaElement, context, d->chartType); // Now load the surfaces (wall and possibly floor) // FIXME: Use named tags instead of looping? forEachElement (n, plotAreaElement) { if (n.namespaceURI() != KoXmlNS::chart) continue; if (n.localName() == "wall") { d->wall->loadOdf(n, context); } else if (n.localName() == "floor") { // The floor is not always present, so allocate it if needed. // FIXME: Load floor, even if we don't really support it yet // and save it back to ODF. //if (!d->floor) // d->floor = new Surface(this); //d->floor->loadOdf(n, context); } else if (n.localName() == "stock-gain-marker") { styleStack.clear(); context.odfLoadingContext().fillStyleStack(n, KoXmlNS::chart, "style-name", "chart"); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::draw, "fill")) { d->stockGainBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockGainBrush; } else { warnChartOdf<stockLossBrush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, styleStack.property(KoXmlNS::draw, "fill"), stylesReader); debugChartOdf<stockLossBrush; } else { warnChartOdf<stockRangeLinePen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, styleStack.property(KoXmlNS::draw, "stroke"), stylesReader); debugChartOdf<stockRangeLinePen; } else { warnChartOdf<chartType == StockChartType) { // The number of data sets determines stock chart subtype if (proxyModel()->rowCount() > 3) { if (candleStick) { setChartSubType(CandlestickChartSubtype); } else { setChartSubType(OpenHighLowCloseChartSubtype); } } } // Connect axes to datasets and cleanup foreach(DataSet *ds, d->shape->proxyModel()->dataSets()) { foreach(Axis *axis, d->axes) { if (axis->name() == ds->axisName()) { axis->attachDataSet(ds); } } } debugChartOdf<chartType<chartSubtype<axes; if (isPolar(d->chartType)) { d->autoHideAxisTitles(); } foreach(Axis *axis, d->axes) { axis->setName(QString()); } // update kchart axis position for all axes d->updateAxesPosition(); // add axes titles to layout addAxesTitlesToLayout(); return true; } void PlotArea::saveOdf(KoShapeSavingContext &context) const { KoXmlWriter &bodyWriter = context.xmlWriter(); //KoGenStyles &mainStyles = context.mainStyles(); bodyWriter.startElement("chart:plot-area"); KoGenStyle plotAreaStyle(KoGenStyle::ChartAutoStyle, "chart"); // Data direction const Qt::Orientation direction = proxyModel()->dataDirection(); plotAreaStyle.addProperty("chart:series-source", (direction == Qt::Horizontal) ? "rows" : "columns"); // Save chart subtype saveOdfSubType(bodyWriter, plotAreaStyle); // Save extra stuff (like auto-position) QMap::const_iterator it(additionalStyleAttributes().constBegin()); for (; it != additionalStyleAttributes().constEnd(); ++it) { plotAreaStyle.addProperty(it.key(), it.value(), KoGenStyle::ChartType); } // save graphic-properties and insert style bodyWriter.addAttribute("chart:style-name", saveStyle(plotAreaStyle, context)); const QSizeF s(size()); const QPointF p(position()); bodyWriter.addAttributePt("svg:width", s.width()); bodyWriter.addAttributePt("svg:height", s.height()); bodyWriter.addAttributePt("svg:x", p.x()); bodyWriter.addAttributePt("svg:y", p.y()); CellRegion cellRangeAddress = d->shape->proxyModel()->cellRangeAddress(); bodyWriter.addAttribute("table:cell-range-address", cellRangeAddress.toString()); // About the data: // Save if the first row / column contain headers. QString dataSourceHasLabels; if (proxyModel()->firstRowIsLabel()) { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "both"; else dataSourceHasLabels = "row"; } else { if (proxyModel()->firstColumnIsLabel()) dataSourceHasLabels = "column"; else dataSourceHasLabels = "none"; } // Note: this is saved in the plotarea attributes and not the style. bodyWriter.addAttribute("chart:data-source-has-labels", dataSourceHasLabels); if (d->threeDScene) { d->threeDScene->saveOdfAttributes(bodyWriter); } if (d->chartType == StockChartType) { QString styleName; bodyWriter.startElement("chart:stock-gain-marker"); KoGenStyle stockGainStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockGainStyle, context.mainStyles(), d->stockGainBrush); styleName = context.mainStyles().insert(stockGainStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-gain-marker bodyWriter.startElement("chart:stock-loss-marker"); KoGenStyle stockLossStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfFillStyle(stockLossStyle, context.mainStyles(), d->stockLossBrush); styleName = context.mainStyles().insert(stockLossStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-loss-marker bodyWriter.startElement("chart:stock-range-line"); KoGenStyle stockRangeStyle(KoGenStyle::ChartAutoStyle, "chart"); KoOdfGraphicStyles::saveOdfStrokeStyle(stockRangeStyle, context.mainStyles(), d->stockRangeLinePen); styleName = context.mainStyles().insert(stockRangeStyle, "ch"); bodyWriter.addAttribute("chart:style-name", styleName); bodyWriter.endElement(); // chart:stock-range-line } // Done with the attributes, start writing the children. // Save the axes. foreach(Axis *axis, d->axes) { axis->saveOdf(context); } if (d->threeDScene) { d->threeDScene->saveOdfChildren(bodyWriter); } // Save data series d->shape->proxyModel()->saveOdf(context); // Save the floor and wall of the plotarea. d->wall->saveOdf(context, "chart:wall"); //if (d->floor) // d->floor->saveOdf(context, "chart:floor"); bodyWriter.endElement(); // chart:plot-area } void PlotArea::saveOdfSubType(KoXmlWriter& xmlWriter, KoGenStyle& plotAreaStyle) const { Q_UNUSED(xmlWriter); switch (d->chartType) { case BarChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); } // Data specific to bar charts if (d->vertical) plotAreaStyle.addProperty("chart:vertical", "true"); // Don't save this if zero, because that's the default. //plotAreaStyle.addProperty("chart:lines-used", 0); // FIXME: for now break; case LineChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } // FIXME: What does this mean? plotAreaStyle.addProperty("chart:symbol-type", "automatic"); break; case AreaChartType: switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } if (d->threeD) { plotAreaStyle.addProperty("chart:three-dimensional", "true"); // FIXME: Save all 3D attributes too. } break; case CircleChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); break; case RingChartType: plotAreaStyle.addProperty("chart:angle-offset", QString::number(d->angleOffset)); plotAreaStyle.addProperty("chart:hole-size", QString::number(d->holeSize)); break; case ScatterChartType: // FIXME break; case RadarChartType: case FilledRadarChartType: // Save subtype of the Radar chart. switch(d->chartSubtype) { case NoChartSubtype: case NormalChartSubtype: break; case StackedChartSubtype: plotAreaStyle.addProperty("chart:stacked", "true"); break; case PercentChartSubtype: plotAreaStyle.addProperty("chart:percentage", "true"); break; } break; case StockChartType: { switch(d->chartSubtype) { case NoChartSubtype: case HighLowCloseChartSubtype: case OpenHighLowCloseChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "false"); break; case CandlestickChartSubtype: plotAreaStyle.addProperty("chart:japanese-candle-stick", "true"); break; } } case BubbleChartType: case SurfaceChartType: case GanttChartType: // FIXME break; // This is not a valid type, but needs to be handled to avoid // a warning from gcc. case LastChartType: default: // FIXME break; } } void PlotArea::setAngleOffset(qreal angle) { d->angleOffset = angle; emit angleOffsetChanged(angle); } ChartShape *PlotArea::parent() const { // There has to be a valid parent Q_ASSERT(d->shape); return d->shape; } KChart::CartesianCoordinatePlane *PlotArea::kdCartesianPlane(Axis *axis) const { if (axis) { Q_ASSERT(d->axes.contains(axis)); // Only a secondary y axis gets the secondary plane if (axis->dimension() == YAxisDimension && axis != yAxis()) return d->kdCartesianPlaneSecondary; } return d->kdCartesianPlanePrimary; } KChart::PolarCoordinatePlane *PlotArea::kdPolarPlane() const { return d->kdPolarPlane; } KChart::RadarCoordinatePlane *PlotArea::kdRadarPlane() const { return d->kdRadarPlane; } KChart::Chart *PlotArea::kdChart() const { return d->kdChart; } bool PlotArea::registerKdDiagram(KChart::AbstractDiagram *diagram) { if (d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.append(diagram); return true; } bool PlotArea::deregisterKdDiagram(KChart::AbstractDiagram *diagram) { if (!d->kdDiagrams.contains(diagram)) return false; d->kdDiagrams.removeAll(diagram); return true; } // HACK to get kdChart to recognize secondary planes void PlotArea::registerKdPlane(KChart::AbstractCoordinatePlane *plane) { int pos = d->kdChart->coordinatePlanes().indexOf(plane); if (pos >= 1) { // secondary plane d->kdChart->takeCoordinatePlane(plane); d->kdChart->insertCoordinatePlane(pos, plane); } else if (pos < 0) { d->kdChart->addCoordinatePlane(plane); } } void PlotArea::plotAreaUpdate() { parent()->legend()->update(); if (d->chartType == StockChartType) { updateKChartStockAttributes(); } requestRepaint(); foreach(Axis* axis, d->axes) axis->update(); KoShape::update(); } void PlotArea::requestRepaint() const { d->pixmapRepaintRequested = true; } void PlotArea::paintPixmap(QPainter &painter, const KoViewConverter &converter) { // Adjust the size of the painting area to the current zoom level const QSize paintRectSize = converter.documentToView(size()).toSize(); const QSize plotAreaSize = size().toSize(); const int borderX = 4; const int borderY = 4; // Only use a pixmap with sane sizes d->paintPixmap = false;//paintRectSize.width() < MAX_PIXMAP_SIZE || paintRectSize.height() < MAX_PIXMAP_SIZE; if (d->paintPixmap) { d->image = QImage(paintRectSize, QImage::Format_RGB32); // Copy the painter's render hints, such as antialiasing QPainter pixmapPainter(&d->image); pixmapPainter.setRenderHints(painter.renderHints()); pixmapPainter.setRenderHint(QPainter::Antialiasing, false); // scale the painter's coordinate system to fit the current zoom level applyConversion(pixmapPainter, converter); d->kdChart->paint(&pixmapPainter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } else { d->kdChart->paint(&painter, QRect(QPoint(borderX, borderY), QSize(plotAreaSize.width() - 2 * borderX, plotAreaSize.height() - 2 * borderY))); } } void PlotArea::paint(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintContext) { //painter.save(); // First of all, scale the painter's coordinate system to fit the current zoom level applyConversion(painter, converter); // Calculate the clipping rect QRectF paintRect = QRectF(QPointF(0, 0), size()); painter.setClipRect(paintRect, Qt::IntersectClip); // Paint the background if (background()) { QPainterPath p; p.addRect(paintRect); background()->paint(painter, converter, paintContext, p); } // Get the current zoom level QPointF zoomLevel; converter.zoom(&zoomLevel.rx(), &zoomLevel.ry()); // Only repaint the pixmap if it is scheduled, the zoom level // changed or the shape was resized. /*if ( d->pixmapRepaintRequested || d->lastZoomLevel != zoomLevel || d->lastSize != size() || !d->paintPixmap) { // TODO (js): What if two zoom levels are constantly being // requested? At the moment, this *is* the case, // due to the fact that the shape is also rendered // in the page overview in Stage. Every time // the window is hidden and shown again, a repaint // is requested --> laggy performance, especially // when quickly switching through windows. // // ANSWER (iw): what about having a small mapping between size // in pixels and pixmaps? The size could be 2 or // at most 3. We could manage the replacing // using LRU. paintPixmap(painter, converter); d->pixmapRepaintRequested = false; d->lastZoomLevel = zoomLevel; d->lastSize = size(); }*/ painter.setRenderHint(QPainter::Antialiasing, false); // KChart thinks in pixels, Calligra in pt ScreenConversions::scaleFromPtToPx(painter); // Only paint the actual chart if there is a certain minimal size, // because otherwise kdchart will crash. QRect kdchartRect = ScreenConversions::scaleFromPtToPx(paintRect, painter); // Turn off clipping so that border (or "frame") drawn by KChart::Chart // is not not cut off. painter.setClipping(false); if (kdchartRect.width() > 10 && kdchartRect.height() > 10) { -#if KCHART_VERSION < 0x020689 - painter.setPen(QPen()); // ruler line needs a pen to be shown -#endif d->kdChart->paint(&painter, kdchartRect); } //painter.restore(); // Paint the cached pixmap if we got a GO from paintPixmap() //if (d->paintPixmap) // painter.drawImage(0, 0, d->image); } void PlotArea::relayout() const { d->kdCartesianPlanePrimary->relayout(); d->kdCartesianPlaneSecondary->relayout(); d->kdPolarPlane->relayout(); d->kdRadarPlane->relayout(); update(); } void PlotArea::addTitleToLayout() { addAxesTitlesToLayout(); // for now } void PlotArea::addAxesTitlesToLayout() { ChartLayout *layout = d->shape->layout(); Axis *axis = xAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), XAxisTitleType); } axis = yAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), YAxisTitleType); } axis = secondaryXAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryXAxisTitleType); } axis = secondaryYAxis(); if (axis) { layout->remove(axis->title()); layout->setItemType(axis->title(), SecondaryYAxisTitleType); } } void PlotArea::setStockRangeLinePen(const QPen &pen) { d->stockRangeLinePen = pen; } QPen PlotArea::stockRangeLinePen() const { return d->stockRangeLinePen; } void PlotArea::setStockGainBrush(const QBrush &brush) { d->stockGainBrush = brush; } QBrush PlotArea::stockGainBrush() const { return d->stockGainBrush; } void PlotArea::setStockLossBrush(const QBrush &brush) { d->stockLossBrush = brush; } QBrush PlotArea::stockLossBrush() const { return d->stockLossBrush; } void PlotArea::updateKChartStockAttributes() { for (Axis *a : d->axes) { a->updateKChartStockAttributes(); } } DataSet::ValueLabelType PlotArea::valueLabelType() const { return d->valueLabelType; } QString PlotArea::symbolType() const { return d->symbolType; } void PlotArea::setSymbolType(const QString &type) { d->symbolType = type; } QString PlotArea::symbolName() const { return d->symbolName; } void PlotArea::setSymbolName(const QString &name) { d->symbolName = name; } void PlotArea::setValueLabelType(const DataSet::ValueLabelType &type) { d->valueLabelType = type; }