diff --git a/CMakeLists.txt b/CMakeLists.txt index 1531062f344..b7f71214b04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,1211 +1,1213 @@ 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() # 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.0.89") set(CALLIGRA_STABLE_VERSION_MAJOR 3) # 3 for 3.x, 4 for 4.x, etc. set(CALLIGRA_STABLE_VERSION_MINOR 0) # 0 for 3.0, 1 for 3.1, etc. set(CALLIGRA_VERSION_RELEASE 89) # 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 2017) # 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}") ############ ############# ## 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() find_package(KF5Holidays 5.3.3) set_package_properties(KF5Holidays PROPERTIES DESCRIPTION "Library for generation of public holidays" PURPOSE "Required by Plan" TYPE RECOMMENDED ) 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 WebKit # WebKitWidgets ) message("**********************************************************************************************************************") message("**********************************************************************************************************************") message("Qt WebKitWidgets is required for Stage's html export preview. This will need porting, as that module no longer exists.") message("**********************************************************************************************************************") message("**********************************************************************************************************************") # 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_package_properties(Qt5WebKitWidget PROPERTIES PURPOSE "Required for Stage" TYPE RECOMMENDED ) if(Qt5WebKit_FOUND) add_definitions( -DCAN_USE_QTWEBKIT ) endif() 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://projects.kde.org/projects/kdesupport/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_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -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 "Required by Plan Ical export and 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 KF5AkonadiContact ## find_package(KF5AkonadiContact CONFIG QUIET) set_package_properties(KF5AkonadiContact PROPERTIES DESCRIPTION "Library for Accessing Contacts stored in Akonadi" URL "https://www.kde.org/" PURPOSE "Optionally used by Plan" 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 KGantt ## macro_optional_find_package(KGantt 2.6.0 QUIET) set_package_properties(KGantt PROPERTIES DESCRIPTION "Library for creating Gantt diagrams (part of KDiagram)" URL "https://www.kde.org/" PURPOSE "Required by Plan" TYPE RECOMMENDED ) ## ## Test for KChart ## macro_optional_find_package(KChart 2.6.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 and Plan" TYPE RECOMMENDED ) +#### Disable kreport for now, reconsidder when it is stable #### ## ## Test for KReport ## -macro_optional_find_package(KReport 3.0 QUIET) -set_package_properties(KReport PROPERTIES - DESCRIPTION "A framework for the creation and generation of reports in multiple formats" - URL "https://community.kde.org/KReport" - PURPOSE "Required for KReports support in Plan" - TYPE RECOMMENDED -) +# macro_optional_find_package(KReport 3.0 QUIET) +# set_package_properties(KReport PROPERTIES +# DESCRIPTION "A framework for the creation and generation of reports in multiple formats" +# URL "https://community.kde.org/KReport" +# PURPOSE "Required for KReports support in Plan" +# TYPE OPTIONAL +# ) +# +# ## +# ## Test for KPropertyWidgets +# ## +# macro_optional_find_package(KPropertyWidgets 3.0 QUIET) +# set_package_properties(KPropertyWidgets PROPERTIES +# DESCRIPTION "A property editing framework with editor widget" +# URL "https://community.kde.org/KProperty" +# PURPOSE "Required for KReports support in Plan" +# TYPE OPTIONAL +# ) -## -## Test for KPropertyWidgets -## -macro_optional_find_package(KPropertyWidgets 3.0 QUIET) -set_package_properties(KPropertyWidgets PROPERTIES - DESCRIPTION "A property editing framework with editor widget" - URL "https://community.kde.org/KProperty" - PURPOSE "Required for KReports support in Plan" - 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 ## macro_optional_find_package(Marble CONFIG) 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 ) ## ## 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(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) 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} ) 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) 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 Flow 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_STAGE # Qt5WebKitWidgets_FOUND "Qt5WebKitWidgets devel 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" ) calligra_drop_product_on_bad_condition( APP_PLAN KGantt_FOUND "KGantt devel not found" KChart_FOUND "KChart devel not found" # KReport_WithScripting_FOUND "KReport with scripting support not found" # KPropertyWidgets_FOUND "KPropertyWidgets not found" ) 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(flow APP_FLOW) calligra_drop_products_on_old_flag(karbon APP_KARBON) calligra_drop_products_on_old_flag(plan APP_PLAN) 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") ############################################# #### 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) if (SHOULD_BUILD_APP_FLOW) add_subdirectory(flow) endif () add_subdirectory(stage) if(SHOULD_BUILD_APP_PLAN) add_subdirectory(plan) endif() 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/braindump/data/org.kde.braindump.appdata.xml b/braindump/data/org.kde.braindump.appdata.xml index 4516c29f001..b827ebe9e3b 100644 --- a/braindump/data/org.kde.braindump.appdata.xml +++ b/braindump/data/org.kde.braindump.appdata.xml @@ -1,90 +1,93 @@ org.kde.braindump.desktop CC0-1.0 GPL-2.0+ Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump Braindump BrainDump Braindump Braindump Braindump Braindump Braindump xxBraindumpxx 脑图 Notes and ideas gathering Sakupljanje poruka i ideja Notes i recull d'idees Notes i recull d'idees Shromažďování nápadů poznámek Notizen und Ideen sammeln Συλλογή σημειώσεων και ιδεών Notes and ideas gathering Recopilación de notas e ideas + Recolección de notas e ideas Note e raccolta di idee Notities en ideeën verzamelen Zbieranie uwag i pomysłów Recolha de notas e ideias + Juntando notas e ideias + Zbieranie poznámok a nápadov Anteckningar och idéinsamling Notları ve fikirleri Toplama Програма для впорядковування нотаток та ідей xxNotes and ideas gatheringxx

Braindump is a tool to dump and organize the content of your brain (ideas, drawings, images, texts…) to your computer. It works by allowing to create and edit whiteboards, which are infinite canvas on which you can add texts, images, charts, drawings, etc.

Braindump je alat za slanje i organizaciju sadržaja vašeg mozga (ideja, crteža, slika, teksta...) na računar. Radi tako što vam dopušta da kreirate i uređujete table koja su beskonačna platna na koje možete dodati tekstove, slike, dijagrame, crteže itd.

El Braindump és una eina per a bolcar i organitzar el contingut del cervell (idees, dibuixos, imatges, texts…) a l'ordinador. Funciona permetent crear i editar pissarres, que tenen llenços infinits en els quals es pot afegir texts, imatges, diagrames, dibuixos, etc.

El Braindump és una eina per a bolcar i organitzar el contingut del cervell (idees, dibuixos, imatges, texts…) a l'ordinador. Funciona permetent crear i editar pissarres, que tenen llenços infinits en els quals es pot afegir texts, imatges, diagrames, dibuixos, etc.

Το Braindump είναι ένα εργαλείο αποθήκευσης και οργάνωσης του περιεχομένου του εγκεφάλου σου (ιδέες, σχήματα, εικόνες, κείμενα…) στον υπολογιστή σου. Λειτουργεί με το να επιτρέπει τη δημιουργία και την επεξεργασία λευκοπινάκων, οι οποίοι αποτελούν έναν απεριόριστο καμβά για την προσθήκη κειμένων, εικόνων, διαγραμμάτων, σχημάτων, κλπ.

Braindump is a tool to dump and organise the content of your brain (ideas, drawings, images, texts…) to your computer. It works by allowing to create and edit whiteboards, which are infinite canvas on which you can add texts, images, charts, drawings, etc.

Braindump es una herramienta para volcar y organizar el contenido de su cerebro (ideas, dibujos, imágenes, texto...) en su equipo. Permite crear y editar pizarras en blanco, es decir, lienzos infinitos en los que puede añadir textos, imágenes, diagramas, dibujos, etc.

Braindump on tööriist sinu ajus peituva sisu (mõtted, joonised, pildid, tekst jms) esitamiseks ja korraldamiseks arvutis. See võimaldab luua ja muuta nõndanimetatud valgetahvleid, mis kujutavad endast piiramata lõuendeid, kuhu lisada teksti, pilte, skeeme, diagramme, joonistusi ja kõike muud vajalikku.

Braindump on työkalu aivojesi sisällön (ajatusten, piirrosten, kuvien, tekstin…) purkamiseksi ja järjestämiseksi. Se toimii antamalla sinun luoda ja muokata valkotauluja, jotka ovat rajaton kangas tekstillesi, kuvillesi, kaavioillesi, piirroksillesi jne.

Braindump est un outil pour mettre en ordre le contenu de votre cerveau (idées, dessins, images, textes…) sur votre ordinateur. Il vous permet de créer et d'éditer des tableaux blancs illimités sur lesquels vous pouvez ajouter des textes, des images, des graphiques, des dessins, etc.

Braindump é unha ferramenta para envorcar e organizar o que pasa pola súa cabeza (ideas, debuxos, imaxes, textos…) no seu computador. Permítelle crear e editar encerados, que son lenzos infinitos nos que pode engadir textos, imaxes, gráficos, debuxos, etc.

Braindump es un instrumento pro discargar e organizar le contento de tu cerebro (ideas, designos, imagines, textos...) in tu computator. Illo functiona per permitter crear e modificar tabula blanc, que es telas infinite sur le qual on pote adder textos, imagines, diagrammas, designos, etc.

Braindump è uno strumento per estrarre e organizzare il contenuto del tuo cervello (idee, disegni, immagini, testi...) sul tuo computer. Ti consente di creare e modificare lavagne bianche, che sono tele infinite sulle quali puoi aggiungere testi, immagini, grafici, disegni, ecc.

Braindump is een hulpmiddel om de inhoud van uw brein te dumpen en te organiseren (ideeën, tekeningen, afbeeldingen, teksten…) naar uw computer. Het werkt door u instaat te stellen whiteboards te bewerken, die oneindige werkvelden zijn waarop u teksten, afbeeldingen, grafieken, tekeningen, etc. kunt toevoegen.

Braindump jest narzędziem do zrzucania i porządkowania zawartości twojego umysłu (pomysły, rysunki, obrazy, teksty...) na komputer. Działa przez umożliwianie tworzenia i edytowania białych tablic, które są bezgranicznym płótnem, na które można dodawać teksty, obrazy, wykresy, rysunki, itp.

O Braindump é uma ferramenta para descarregar e organizar o conteúdo do seu cérebro (ideias, desenhos, imagens, textos, ...) para o seu computador. Permite criar e editar quadros, que são uma "tela" infinita onde poderá adicionar textos, imagens, gráficos, desenhos, etc.

Braindump é uma ferramenta para descarregar e organizar o conteúdo do seu cérebro (ideias, desenhos, imagens, textos, ...) para o seu computador. Permite-lhe criar e editar quadros, que são uma espécie de "tela infinita" onde poderá adicionar textos, imagens, gráficos, desenhos, etc.

Braindump je nástroj na prenos a organizáciu obsahu vášho mozgu (nápady, kresby, obrázky, texty...) do vášho počítača. Umožňuje vytvárať a upravovať tabule, ktoré sú neurčitým plátnom, kam môžete pridávať texty, obrázky, grafy, kresby atď.

Braindump är ett verktyg för att hämta och organisera innehållet i din hjärna (idéer, ritningar, bilder, texter …) på datorn. Det fungerar genom att låta dig skapa och redigera skrivtavlor, som är oändliga ytor där du kan lägga till texter, bilder, diagram, ritningar, etc.

Braindump, aklınızdaki içerikleri (fikirler, çizimler, resimler, metinler...) bilgisayarına dökmenize ve organize etmenize yardımcı olan bir araçtır. Metin, resim, çizelge, çizimler, vb. ekleyebileceğiniz sonsuz tuval olan beyaz tahtalar oluşturul düzenlemenize izin vererek çalışır.

Braindump — програма для запису та упорядкування ваших думок (ідей, креслень, зображень, фрагментів тексту…) за допомогою вашого комп’ютера. У цій програмі ви можете створювати та редагувати так звані «дошки» — нескінченні полотна, на які можна додавати фрагменти тексту, зображення, діаграми, креслення тощо.

xxBraindump is a tool to dump and organize the content of your brain (ideas, drawings, images, texts…) to your computer. It works by allowing to create and edit whiteboards, which are infinite canvas on which you can add texts, images, charts, drawings, etc.xx

脑图是一个在计算机中记录和组织大脑内容的工具。可以创建和编辑白板,有无限的大小,可以添加文字、图片、图表和绘画等。

http://www.calligra.org/braindump/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=braindump http://www.calligra.org/wp-content/uploads/2011/03/projection-900x548.png KDE braindump
diff --git a/karbon/data/org.kde.karbon.appdata.xml b/karbon/data/org.kde.karbon.appdata.xml index d10227dc942..e439039d8f4 100644 --- a/karbon/data/org.kde.karbon.appdata.xml +++ b/karbon/data/org.kde.karbon.appdata.xml @@ -1,386 +1,395 @@ org.kde.karbon.desktop CC0-1.0 GPL-2.0+ Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon Karbon xxKarbonxx Karbon Scalable Graphics Grafika koja se može skalirati Gràfics escalables Gràfics escalables Škálovatelná grafika Skalierbare Vektorgrafik Scalable Graphics Gráficos escalables Vektorgraafika Skaalautuva grafiikka Graphisme vectoriel - Editor de imaxes vectoriais + Gráficos de tamaño variábel Graphicos scalabile Grafica scalabile 画像の寸法 Vectorafbeeldingen Skalowalna grafika Gráficos Vectoriais Imagens vetoriais Škálovateľná grafika Skalbar grafik Програма для роботи з масштабованою графікою xxScalable Graphicsxx 可缩放图像

Karbon is a vector drawing application with a user interface that is easy to use, highly customizable and extensible. That makes Karbon a great application for users starting to explore the world of vector graphics as well as for artists wanting to create breathtaking vector art.

Karbon je vektorska aplikacija za crtanje s korisničkim sučeljem koje je jednostavno za korištenje , vrlo prilagodljivo i proširivo . To čini Karbon odličnom aplikacijom za korisnike koji počinju istraživati ​​svijet vektorske grafike , kao i za umjetnike koji žele stvoriti prekrasnu vektorsku umjetnost .

El Karbon és una aplicació de dibuix vectorial amb una interfície d'usuari senzilla d'utilitzar, que es pot personalitzar i ampliar molt. Això converteix el Karbon en una gran aplicació per usuaris que comencin a explorar el món dels gràfics vectorials i també pels artistes que volen crear art vectorial que deixi bocabadat.

El Karbon és una aplicació de dibuix vectorial amb una interfície d'usuari senzilla d'utilitzar, que es pot personalitzar i ampliar molt. Això converteix el Karbon en una gran aplicació per usuaris que comencen a explorar el món dels gràfics vectorials i també pels artistes que volen crear art vectorial que deixi bocabadat.

Karbon is a vector drawing application with a user interface that is easy to use, highly customisable and extensible. That makes Karbon a great application for users starting to explore the world of vector graphics as well as for artists wanting to create breathtaking vector art.

Karbon es una aplicación de dibujo vectorial que dispone de una interfaz de usuario intuitiva, altamente personalizable y extensible. Todo ello hace que Karbon sea una gran aplicación para aquellos usuarios que empiezan a explorar el mundo de los gráficos vectoriales, así como para los artistas que desean crear impresionantes piezas de arte vectorial.

Karbon on vektorgraafikarakendus hõlpsasti kasutatava, äärmiselt kohandatava ja laiendatava kasutajaliidesega. See muudab Karboni imeheaks abivahendiks algajale, kes alles uudistab vektorgraafika maailma, aga ka kunstnikele, kes soovivad luua hingematvalt kaunist vektorgraafikat.

Karbon on vektorigrafiikkasovellus, jonka käyttöliittymä on helppo ja laajasti mukautettavissa ja laajennettavissa. Karbon on näin loistava sovellus vektorigrafiikan maailmaan vasta tutustuville käyttäjille samoin kuin henkeäsalpaavaa vektoritaidetta luoville taiteilijoille.

Karbon est une application de dessin vectoriel avec une interface facile à utiliser, personnalisable et extensible. Cela fait de Karbon une application majeure pour les utilisateurs s'initiant au dessin vectoriel et pour les artistes voulant créer des œuvres à couper le souffle.

Karbon é un aplicativo de debuxo vectorial con unha interface de usuario fácil de usar, personalizar e ampliar. Iso fai de Karbon un gran aplicativo para usuarios que comezan a explorar o mundo das imaxes vectoriais, pero tamén para artistas que queren crear impresionantes obras de arte vectoriais.

Karbon è un'applicazione di disegno vettoriale con un'interfaccia utente facile da utilizzare, altamente personalizzabile ed estendibile. Ciò rende Karbon un'ottima applicazione per gli utenti che iniziano a esplorare il mondo della grafica vettoriale così come per gli artisti che vogliono creare straordinarie opere vettoriali.

Karbon is een toepassing voor tekenen met vectoren met een gebruikersinterface dat gemakkelijk is te gebruiken, zeer goed aan te passen en uit te breiden. Dit maakt Karbon een geweldige toepassing voor gebruikers die de wereld van grafiek met vectoren aan het verkennen zijn evenals voor kunstenaars die adembenemende vectorkunst willen maken.

Karbon jest programem do rysowania wektorowego z układem sterowania, który jest łatwy w użyciu, wysoce dostosowywalny i rozszerzalny. To czyni Karbon wspaniałym programem dla użytkowników zaczynających poznawanie świata grafiki wektorowej, a artystów zachęca do tworzenie sztuki wektorowej zapierającej dech w piersiach.

O Karbon é uma aplicação de desenho vectorial com uma interface simples de usar, facilmente configurável e modular. Isto torna o Karbon uma óptima aplicação para os utilizadores que começam a explorar o mundo dos gráficos vectoriais, assim como para os artistas que desejam criar arte vectorial espectacular.

Karbon é um aplicativo de desenho vetorial com uma interface simples de usar, facilmente configurável e expansível. Isso torna o Karbon um ótimo aplicativo para usuários que começam a explorar o mundo dos gráficos vetoriais, assim como para artistas que desejam criar arte vetorial espetacular.

Karbon je vektorová kresliaca aplikácia s používateľským rozhraním jednoduchým na používanie, veľmi prispôsobiteľná a rozšíriteľná. Toto robí z Karbonu výbornú aplikáciu pre používateľov, ktorí začínajú objavovať sveet vektorovej grafiky, ako aj pre umelcov, ktorí chcú vytvárať dych vyrážajúce vektorové umenie.

Karbon är ett vektorritprogram med ett användargränssnitt som är lätt att använda, ytterst anpassningsbart och utökningsbart. Det gör Karbon till ett utmärkt program för användare som börjar utforska vektorgrafikvärlden samt för konstnärer som vill skapa hänförande vektorkonst.

Karbon — програма для векторного малювання з простим, гнучким та придатним до розширення інтерфейсом користувача. Це робить Karbon чудовою програмою для користувачів, які починають знайомитися зі світом векторної графіки, а також для художників, які хочуть створити захопливі векторні твори.

xxKarbon is a vector drawing application with a user interface that is easy to use, highly customizable and extensible. That makes Karbon a great application for users starting to explore the world of vector graphics as well as for artists wanting to create breathtaking vector art.xx

Karbon 是一个矢量绘图程序,具有易用,高度可定制和可扩展的用户界面。它是一个理想的工具,帮助用户探索矢量图形,帮助艺术家创作令人惊叹的矢量艺术作品。

Features:

Svojstva:

Característiques:

Característiques:

Vlastnosti:

Funktionen:

Features:

Características:

Omadused:

Ominaisuuksia:

Fonctionnalités :

Funcionalidades:

Characteristicas

Funzionalità:

機能:

Mogelijkheden:

Możliwości:

Funcionalidades:

Funcionalidades:

Funkcie:

Funktioner:

Можливості:

xxFeatures:xx

功能:

  • Loading support for ODG, SVG, WPG, WMF, EPS/PS
  • Učitavanje podrške za ODG, SVG, WPG, WMF, EPS/PS
  • Permet llegir ODG, SVG, WPG, WMF, EPS/PS
  • Permet llegir ODG, SVG, WPG, WMF, EPS/PS
  • Unterstützung für das Laden von ODG, SVG, WPG, WMF, EPS/PS
  • Loading support for ODG, SVG, WPG, WMF, EPS/PS
  • Admite la carga de ODG, SVG, WPG, WMF, EPS/PS
  • ODG, SVG, WPG, WMF, EPS/PS laadimise toetamine
  • Lukutuki ODG-, SVG-, WPG-, WMF- ja EPS/PS-muodoista
  • Lecture des formats ODG, SVG, WPG, WMF, EPS/PS
  • Capacidade para cargar ODG, SVG, WPG, WMF e EPS/PS.
  • Cargante supporto pro ODG, SVG, WPG, WMF, EPS/PS
  • Supporto per il caricamento di ODG, SVG, WPG, WMF, EPS/PS
  • 読み込み可能な形式: ODG, SVG, WPG, WMF, EPS/PS
  • Ondersteuning voor laden van ODG, SVG, WPG, WMF, EPS/PS
  • Obsługa wczytywania dla ODG, SVG, WPG, WMF, EPS/PS
  • Suporte do carregamento de imagens em ODG, SVG, WPG, WMF, EPS/PS
  • Suporte para carregamento de arquivos ODG, SVG, WPG, WMF e EPS/PS
  • Podpora čítania pre ODG, SVG, WPG, WMF, EPS/PS
  • Stöd för att läsa in ODG, SVG, WPG, WMF, EPS/PS
  • Підтримка завантаження даних ODG, SVG, WPG, WMF, EPS/PS.
  • xxLoading support for ODG, SVG, WPG, WMF, EPS/PSxx
  • 加载支持 ODG,SVG,WPG,WMF,EPS/PS
  • Writing support for ODG, SVG, PNG, PDF, WMF
  • Pisanje podrške za ODG, SVG, WPG, WMF, EPS/PS
  • Permet gravar ODG, SVG, PNG, PDF, WMF
  • Permet gravar ODG, SVG, PNG, PDF, WMF
  • Unterstützung für das Schreiben von ODG, SVG, PNG, PDF, WMF
  • Writing support for ODG, SVG, PNG, PDF, WMF
  • Admite la escritura de ODG, SVG, PNG, PDF, WMF
  • ODG, SVG, PNG, PDF, WMF kirjutamine toetamine
  • Kirjoitustuki ODG-, SVG-, PNG-, PDF- ja WMF-muotoihin
  • Écriture des formats ODG, SVG, PNG, PDF, WMF
  • Capacidade de xeración de ODG, SVG, PNG, PDF e WMF.
  • Supporto de scriber pro ODG, SVG, PNG, PDF, WMF
  • Supporto per la scrittura di ODG, SVG, PNG, PDF, WMF
  • 書き込み可能な形式: ODG, SVG, PNG, PDF, WMF
  • Ondersteuning voor schrijven van ODG, SVG, PNG, PDF, WMF
  • Obsługa zapisywania dla ODG, SVG, PNG, PDF, WMF
  • Suporte da gravação de imagens em ODG, SVG, PNG, PDF, WMF
  • Suporte para gravação de arquivos ODG, SVG, PNG, PDF e WMF
  • Podpora zápisu pre ODG, SVG, PNG, PDF, WMF
  • Stöd för att skriva ut ODG, SVG, PNG, PDF, WMF
  • Підтримка запису у форматах ODG, SVG, PNG, PDF, WMF.
  • xxWriting support for ODG, SVG, PNG, PDF, WMFxx
  • 写入支持 ODG,SVG,PNG,PDF,WMF
  • Customizable user interface with freely placeable toolbars and dockers
  • Prilagodljiv korisnički interfejs sa gdje se moze slobodno staviti alatne trake i dockers
  • Interfície d'usuari que es pot personalitzar que permet situar lliurement barres d'eines i acobladors
  • Interfície d'usuari que es pot personalitzar que permet situar lliurement barres d'eines i acobladors
  • Anpassungsfähige Bedienungsoberfläche mit frei platzierbaren Werkzeugleisten und andockbaren Fenstern
  • Customisable user interface with freely placeable toolbars and dockers
  • Interfaz de usuario personalizable con barras de herramientas y paneles que se pueden posicionar libremente en cualquier lugar.
  • Kohandatav kasutajaliides vabalt liigutatavate tööriistaribade ja dokkidega
  • Mukautettava käyttöliittymä, jonka työkalurivit ja telakat ovat vapaasti sijoitettavissa
  • Interface utilisateur personnalisable avec des barres d'outil librement déplaçables.
  • Interface de usuario personalizábel con barras de ferramentas e docas que poden recolocarse libremente.
  • Interfacie de usator personalisabile con barra de instrumentos e stivatores (dockers) liberemente positionabile
  • Interfaccia utente personalizzabile con barre degli strumenti posizionabili liberamente e aree di aggancio
  • Aan te passen gebruikersinterface met vrij te plaatsen werkbalken en verankeringen
  • Układ sterowania z dowolnie umieszczanymi paskami narzędzi i dokowaniami
  • Interface de utilizador configurável com barras e áreas acopláveis livres
  • Interface de usuário configurável com barras de ferramentas e áreas acopláveis que podem ser facilmente ajustadas
  • Nastaviteľné používateľské rozhranie s voľne umiestniteľnými panelmi nástrojov a dockermi
  • Anpassningsbart användargränssnitt med verktygsrader och paneler som kan placeras fritt
  • Придатний до налаштовування інтерфейс користувача з довільним розташуванням панелей інструментів та бічних панелей.
  • xxCustomizable user interface with freely placeable toolbars and dockersxx
  • 可自定义用户界面,包含可自由放置的工具栏和停靠栏
  • Layer docker for easy handling of complex documents including preview thumbnails, support for grouping shapes via drag and drop, controlling visibility of shapes or locking
  • Sloj lučki radnik za jednostavno rukovanje složenim dokumentima , uključujući pregled sličica , podršku za grupiranje oblika putem drag and drop , kontroliranje vidljivost oblika ili zaključavanje
  • Acoblador de capa per a una gestió senzilla de documents complexos incloent la vista prèvia de miniatures, permet agrupar formes via arrossegar i deixar anar, controlar la visibilitat de les formes o el bloqueig.
  • Acoblador de capa per a una gestió senzilla de documents complexos incloent la vista prèvia de miniatures, permet agrupar formes via arrossegar i deixar anar, controlar la visibilitat de les formes o el bloqueig.
  • Layer docker for easy handling of complex documents including preview thumbnails, support for grouping shapes via drag and drop, controlling visibility of shapes or locking
  • Panel de capas para un sencillo manejo de documentos complejos, que incluye vista previa de miniaturas y agrupación de figuras mediante la operación de arrastrar y soltar, controlando la visibilidad de las figuras o su bloqueo.
  • Kihtide dokk keerukamate dokumentide hõlpsaks töötlemiseks, kaasa arvatud pisipiltide eelvaatlus, kujundite rühmitamise toetamine ainult lohistamisega, kujundite nähtavuse ja lukustamise määramine
  • Monimutkaisten tiedostojen helppoon hallintaan tasotelakka, joka sisältää esikatselukuvat, muotojen ryhmittelyn tuen vetämällä ja pudottamalla sekä muotojen näkyvyyden ja lukitsemisen hallinnan
  • Conteneurs de calques pour la manipulation facile de documents complexes incluant les vignettes d'aperçu, la prise en charge du groupement de formes par glisser-déposer, contrôle de la visibilité des formes et verrouillage
  • Doca de capas para xestionar facilmente documentos complexos. Inclúe miniaturas de vista previa, permite agrupar formas arrastrando e soltando, controlar a visibilidade das formas ou bloquealas.
  • Area di aggancio di livelli per una facile gestione di documenti complessi incluse miniature di anteprima, supporto di forme raggruppate tramite il trascinamento e rilascio, controllo della visibilità delle forme e blocco
  • Laagverankering voor gemakkelijke behandeling van complexe documenten inclusief voorbeeldminiaturen, ondersteuning voor groeperen van vormen via slepen en laten vallen, besturing van zichtbaarheid van vormen of vastzetten
  • Dokowanie warstw dla łatwiejszej obsługi złożonych dokumentów zawierający: podgląd miniatur, obsługę grupowania kształtów poprzez przeciągnij i upuść, sterowanie widocznością kształtów czy blokowanie.
  • Área de camadas para o tratamento de documentos complexos, incluindo miniaturas da antevisão, suporte para o agrupamento de formas por arrastamento, o controlo da visibilidade das formas ou o seu bloqueio
  • Área de camadas para tratamento de documentos complexos, incluindo miniaturas, suporte a agrupamento de formas com arrastar e soltar, controle de visibilidade das formas ou seu bloqueio
  • Docker vrstiev pre ľahkú manipuláciu so zložitými dokumentami vrátane náhľadu miniatúr, podpora pre zoskupovanie tvarov cez drag and drop, ovládanie viditeľnosti tvarov alebo zamykanie
  • Lagerpanel för enkel hantering av komplexa dokument inklusive miniatyrbilder för förhandsgranskning, stöd för att gruppera former via drag och släpp, styra eller låsning synlighet av former
  • Панель шарів для полегшення обробки складних документів з мініатюрами об’єктів, підтримка групування форм перетягуванням зі скиданням, керування показом форм та блокуванням.
  • xxLayer docker for easy handling of complex documents including preview thumbnails, support for grouping shapes via drag and drop, controlling visibility of shapes or lockingxx
  • +
  • 图层停靠栏用于方便处理复杂文档,包括缩略图预览,支持拖拽组合形状,控制形状的可见性或锁定。
  • Advanced path editing tool with great on-canvas editing capabilities
  • Alat za napredno uređivanje puta s velikom mogućnosti uređivanja na platnu
  • Eina avançada d'edició de camins amb una gran capacitat d'edició en el llenç
  • Eina avançada d'edició de camins amb una gran capacitat d'edició en el llenç
  • Advanced path editing tool with great on-canvas editing capabilities
  • Herramienta de edición avanzada de rutas con grandes capacidades de edición en el lienzo
  • Täiustatud kompleksjoone muutmise tööriist suurepäraste otse lõuendil redigeerimise võimalustega
  • Edistynyt polunmuokkaustyökalu, jolla on erinomaiset muokkausominaisuudet
  • Outil d'édition de chemin avancé avec des capacités d'édition dans les canevas performantes
  • Ferramenta avanzada de edición de camiños cunha gran funcionalidade de edición sobre o lenzo.
  • Strumento avanzato di modifica di tracciati con grandi capacità di modifica sul posto
  • Geavanceerde padbewerkingsgereedschap met prima bewerkingsmiddelen op het werkblad
  • Zaawansowane narzędzia edytowania ścieżki z szerokimi możliwościami edytowania na płótnie
  • Ferramenta de edição de caminhos avançada, com grandes capacidade de edição no local
  • Ferramenta avançada para edição de caminhos, com ótima capacidade de edição no local
  • Pokročilý nástroj na úpravu ciest s výbornými schopnosťami editácie na plátne
  • Avancerat konturredigeringsverktyg med utmärkta redigeringsmöjligheter direkt på duken
  • Інструмент редагування контурів з чудовими можливостями редагування безпосередньо на полотні.
  • xxAdvanced path editing tool with great on-canvas editing capabilitiesxx
  • +
  • 高级路径编辑工具,以及强大的画布编辑能力
  • Various drawing tools for creating path shapes including a draw path tool, a pencil tool as well as a calligraphy drawing tool
  • Razni alati za crtanje za kreiranje put oblike uključujući i put alat nacrtati ,olovka alat , kao i kaligrafija alat za crtanje
  • Diverses eines de dibuix per crear formes de camí incloent una eina de dibuix de camins, una eina de llapis i també una eina de dibuix de cal·ligrafia.
  • Diverses eines de dibuix per crear formes de camí incloent una eina de dibuix de camins, una eina de llapis i també una eina de dibuix de cal·ligrafia.
  • Various drawing tools for creating path shapes including a draw path tool, a pencil tool as well as a calligraphy drawing tool
  • Diversas herramientas de dibujo para crear figuras con rutas, entre las que se incluyen una herramienta de dibujo de rutas, una herramienta de pincel y una herramienta de dibujo caligráfico.
  • Mitmesugused kompleksjoontest kujundite loomise vahendid, sealhulgas kompleksjoone joonistamise tööriist, pliiatsitööriist ning kalligraafia loomise tööriist
  • Eri piirrostyökalut polkumuotojen luontiin: polunpiirto-, kynä- ja kalligrafinen piirrostyökalu
  • Outils de dessin variés incluant un outil de création de chemin, un crayon ainsi qu'un outil pour la calligraphie
  • Varias ferramentas de debuxo para crear formas de camiños, incluíndo unha ferramenta de debuxo de camiños, unha ferramenta de lapis, e unha ferramenta de debuxo caligráfico.
  • Vari strumenti di disegno per creare percorsi e forme che include uno strumento di tracciati, un matita e uno strumento calligrafico
  • Verschillende tekengereedschappen voor het maken van padvormen inclusief een tekenhulpmiddel voor paden, een potloodhulpmiddel evenals een tekengereedschap voor kalligrafie
  • Różne narzędzia rysownicze do tworzenia kształtów ścieżek uwzględniając w tym: narzędzie rysowania ścieżki, narzędzie ołówka, a także kaligraficzne narzędzia rysownicze
  • Diversas ferramentas de desenho para criar formas de caminhos, incluindo uma ferramenta de desenho de caminhos, uma ferramenta de lápis e uma ferramenta de desenho caligráfico
  • Diversas ferramentas de desenho para criar formas de caminhos, incluindo uma ferramenta de desenho de caminhos, uma ferramenta de lápis e uma ferramenta de desenho caligráfico
  • Rôzne kresliace nástroje na vytváranie tvarov ciest vrátane nástroja na kreslenie cesty, nástroja ceruzky ako aj kaligrafického kresliaceho nástroja
  • Diverse ritverktyg för att skapa konturformer som inkluderar ett konturritverktyg, ett pennverktyg samt ett kalligrafiskt ritverktyg
  • Різноманітні інструменти малювання для створення контурів, зокрема інструмент малювання контуру, олівець та інструмент каліграфічного малювання.
  • xxVarious drawing tools for creating path shapes including a draw path tool, a pencil tool as well as a calligraphy drawing toolxx
  • +
  • 多种绘图工具用于创作路径形状,包括路径绘制工具,铅笔工具和书法绘制工具
  • Gradient and pattern tools for easy on-canvas editing of gradient and pattern styles
  • Gradijent i uzorak alata za jednostavno na - platnu uređivanje gradijent i uzorak stilova
  • Eines de degradats i patrons per a una edició senzilla sobre el llenç d'estils de degradats i patrons
  • Eines de degradats i patrons per a una edició senzilla sobre el llenç d'estils de degradats i patrons
  • Gradient and pattern tools for easy on-canvas editing of gradient and pattern styles
  • Herramientas de degradados y patrones para una sencilla edición de estilos de degradados y patrones en el lienzo
  • Ülemineku- ja mustritööriistad üleminekute ja mustrite stiilide hõlpsaks redigeerimiseks otse lõuendil
  • Työkalut liukuvärien ja täyttökuvioiden helppoon muokkaukseen
  • Outils de gradient et de tramage pour l'édition facile de styles de gradients et de trames
  • Ferramentas de degradados e padróns para editar degradados e estilos de padróns facilmente sobre o lenzo.
  • Strumenti di gradienti e motivi per una facile modifica sul posto di stili di gradienti e motivi
  • Hulpmiddelen voor gradiënten en patronen voor gemakkelijk werken op het werkblad van gradiënten en patroonstijlen
  • Narzędzia gradientu i wzorca dla łatwiejszego ich edytowania na płótnie
  • Ferramentas de gradientes e padrões para uma edição simples no local dos estilos dos gradientes e dos padrões
  • Ferramentas de gradientes e padrões para fácil edição no local dos estilos dos gradientes e dos padrões
  • Nástroje na prechody a vzory pre ľahké editovanie na plátne štýlov prechodov a vzorov
  • Tonings- och mönsterverktyg för enkelt redigering av tonings- och mönsterstilar direkt på duken
  • Інструменти градієнта і візерунка для полегшення редагування на полотні областей з заповненням градієнтом і візерунком.
  • xxGradient and pattern tools for easy on-canvas editing of gradient and pattern stylesxx
  • +
  • 渐变和图案工具可以轻松地在画布上编辑渐变和图案样式
  • Top notch snapping facilities for guided drawing and editing (e.g. snapping to grid, guide lines, path nodes, bounding boxes, orthogonal positions, intersections of path shapes or extensions of lines and paths)
  • Vrhunska snapping objekti za vođene crtanje i uređivanje ( npr snapping na mrežu , vodič linije , staze čvorova , granični kutije , okomita pozicija , preplitanja put oblikuje ili proširenja linija i staze )
  • Capacitats òptimes d'ajust per dibuix guiat i edició (p.e. ajust a graella, línies de guia, nodes de camins, caixes contenidores, posicions ortogonals, interseccions de formes de camí o extensions de línies i camins)
  • Capacitats òptimes d'ajust per dibuix guiat i edició (p.e. ajust a graella, línies de guia, nodes de camins, caixes contenidores, posicions ortogonals, interseccions de formes de camí o extensions de línies i camins)
  • Top notch snapping facilities for guided drawing and editing (e.g. snapping to grid, guide lines, path nodes, bounding boxes, orthogonal positions, intersections of path shapes or extensions of lines and paths)
  • Capacidades de ajuste óptimas para el dibujo y la edición guiados (por ejemplo, ajustar a la rejilla, líneas guías, nodos de ruta, cuadros delimitadores, posiciones ortogonales, intersecciones de formas de rutas o extensiones de líneas y rutas)
  • Äärmiselt tulusad haardetööriistad juhtjoonte järgi joonistamiseks ja redigeerimiseks (nt tõmme alusvõrgule, juhtjooned, kompleksjoone sõlmed, piirdekastid, kompleksjoontest kujundite lõikekohad jms)
  • Huippulaatuinen kiinnitysominaisuus ohjattuun piirtämiseen ja muokkaukseen (esim. kiinnitys ruudukkoon, apuviivoihin, rajauslaatikkoon, kohtisuoraan asemaan, leikkaukseen, polkumuotoon tai viivojen ja polkujen jatkeisiin)
  • Fonctionnalités d'alignement top-niveau pour guider le dessin et l'édition (p.ex. alignement sur la grille, chemin nodal, boîtes, positions orthogonales, intersections de formes ou extensions de lignes ou de formes)
  • Funcionalidades de axuste de última xeración para editar e debuxar de maneira guiada (por exemplo, axustar á grade, liñas de guía, nodos de camiños, caixas de límites, posicións ortogonais, cruce de formas de camiños ou extensións de liñas e camiños).
  • Capacità di alto livello di regolazione per disegno e modifica guidata (ad es. aggancio alla griglia, linee guida, nodi di tracciato, riquadri collegati, posizioni ortgonali, intersezioni di forme di tracciato o estensioni di linee e tracciati)
  • Excellente vastklik faciliteiten voor geleid tekenen en bewerken (bijv. vastklikken aan raster, gidslijnen, padpunten, begrenzingsvakken, orthogonale posities, kruispunten van padvormen of extensies van lijnen en paden)
  • Możliwości przyciągania z najwyższej półki rysowania i edytowania ze wspomaganiem (np. przyciąganie do siatki, -prowadnic, węzłów ścieżki, pól ograniczających, prostopadłych położeń, przecięć kształtów ścieżek lub wydłużeń linii i ścieżek)
  • Capacidades de ajuste óptimas para o desenho e edição guiado (p.ex., ajustes à grelha, linhas-guias, nós de caminhos, áreas envolventes, posições ortogonais, intersecções de formas de caminhos ou extensões de linhas e caminhos)
  • Capacidades de melhor ajuste para desenho e edição guiado (p.ex., ajustes à grade, linhas-guias, nós de caminhos, caixas delimitadoras, posições ortogonais, intersecções de formas de caminhos ou extensões de linhas e caminhos)
  • Schopnosti prichytenia na vrch pre sprevádzané kreslenie a editovanie (napr. prichytenie k mriežke, pomocné čiary, uzly cesty, hraničné boxy, ortogonálne polohy, priesečníky tvarov cesty alebo rozšírenia čiar a ciest)
  • Väldigt bra låsfunktioner för att rita och redigera med styrning (t.ex. låsa till rutnät, styrlinjer, konturnoder, omgivande rutor, ortogonala positioner, korsningar av konturformer eller förlängningar av linjer och konturer)
  • Можливості використання прилипання та малювання за напрямними (прилипання до сітки, напрямні, вузли контурів, обмежувальні рамки, ортогональне розташування, перетини та розширення ліній і контурів).
  • xxTop notch snapping facilities for guided drawing and editing (e.g. snapping to grid, guide lines, path nodes, bounding boxes, orthogonal positions, intersections of path shapes or extensions of lines and paths)xx
  • +
  • 顶端对齐工具可以引导绘制和编辑(比如吸附到网格,参考线,路径节点,边界框,正交位置,路径焦点或线的延长线)
  • Includes many predefined shapes including basic shapes like stars, circle/ellipse, rectangle, image
  • Uključuje mnoge predefinisane oblike uključujući i osnovne oblike poput zvijezda , krugova / elipsa, pravougaonika, slika
  • Inclou moltes formes predefinides bàsiques com estels, cercles/el·lipses, rectangles, imatges
  • Inclou moltes formes predefinides bàsiques com estels, cercles/el·lipses, rectangles, imatges
  • Enthält viele vordefinierte Objekte einschließlich einfacher Objekte wie Sterne, Kreise, Ellipsen, Rechtecke und Bilder
  • Includes many predefined shapes including basic shapes like stars, circle/ellipse, rectangle, image
  • Incluye muchas formas predefinidas, entre otras figuras básicas como estrellas, círculos/elipses, rectángulos, imágenes
  • Hulk valmiskujundeid, sealhulgas tähed, ringid ja ellipsid, ristkülikud, pildid
  • Lukuisia esimääritettyjä muotoja (perusmuodot kuten tähdet, ympyrä ja soikio, nelikulmio, kuva)
  • Inclut de nombreuses formes prédéfinies, telles que les étoiles, cercles/ellipses, rectangles, images
  • Inclúe moitas formas predefinidas, entre elas formas básicas como estrelas, círculos, elipses, rectángulos e imaxes.
  • Include molte forme predefinite tra cui forme base come stelle, cerchi/ellissi, rettangoli e immagini
  • Omvat vele voorgedefinieerde vormen inclusief basisvormen als ster, cirkel/ellips, rechthoek, afbeelding
  • Zawiera wiele uprzednio stworzonych kształtów, do których można zaliczyć gwiazdy, okręgi, elipsy, prostokąty, obrazy
  • Inclui diversas formas predefinidas, incluindo formas básicas como as estrelas, círculos/elipses, rectângulos, imagens
  • Inclui diversas formas predefinidas, incluindo formas básicas como estrelas, círculos/elipses, retângulos e imagens
  • Obsahuje mnoho preddefinovaných tvarov vrátane základných tvarov ako hviezdy, kružnica/elipsa, obdĺžnik, obrázok
  • Innehåller många fördefinierade former inklusive grundläggande former som stjärnor, cirkel/ellips, rektangel, bild
  • Передбачено багато готових форм, зокрема базові форми зірки, кола або еліпса, прямокутника та зображення.
  • xxIncludes many predefined shapes including basic shapes like stars, circle/ellipse, rectangle, imagexx
  • +
  • 包含许多预定义形状,如星形,圆形/椭圆,矩形,图像
  • Artistic text shape with support for following path outlines (i.e. text on path)
  • Umjetnički oblik tekst s podrškom za sljedeći put naglašava ( tj tekst na putu )
  • Formes de text artístic que permeten camins de contorns de seguiment (p.e. text en camins)
  • Formes de text artístic que permeten camins de contorns de seguiment (p.e. text en camins)
  • Artistic text shape with support for following path outlines (i.e. text on path)
  • Forma de texto artístico con soporte para contornos de seguimiento de rutas (es decir, texto sobre una ruta)
  • Kunstiline tekstikujund, mis toetab kompleksjooni, st võimaldab asetada teksti kompleksjoonele
  • Taiteelliset tekstimuodot polunseurantatuella
  • Formes de texte artistiques prenant par exemple en charge la création de texte le long d'une courbe
  • Forma de texto artístico que permite seguir o contorno de camiños (é dicir, texto sobre camiños).
  • Forme di testo artistico con supporto per le bordature che seguono il tracciato (ad es. testo su tracciato)
  • Artistieke tekstvormen met ondersteuning voor het volden van paden (dwz. tekst op een pad)
  • Kształty artystycznego tekstu z obsługą dla następujących zarysów ścieżek (tj. tekst na ścieżce)
  • Forma de texto artístico com suporte para caminhos de seguimento (i.e., texto ao longo de um caminho)
  • Forma de texto artístico com suporte para caminhos de seguimento (isto é, texto no caminho)
  • Umelecký textový tvar s podporou pre nasledovacie obrysy cesty (napr. text na ceste)
  • Artistisk textform med stöd för att följa konturlinjer (t.ex. text på en kontur)
  • Форма художнього тексту для розташування тексту вздовж контуру.
  • xxArtistic text shape with support for following path outlines (i.e. text on path)xx
  • +
  • 艺术字形状,支持跟随路径轮廓(即路径上的文本)
  • Complex path operations and effects like boolean set operations, path flattening, rounding and refining as well as whirl/pinch effects
  • Kompleksne staze poslovanja i učinci poput Booleova postavljanje poslovanja , put ravnanje , zaokruživanje i rafiniranje kao vrtlog / za hvatanje učinci
  • Operacions complexes de camins i efectes com operacions de conjunts booleans, aplanament de camins, arrodoniment i afinació i també efectes de gir i pessic
  • Operacions complexes de camins i efectes com operacions de conjunts booleans, aplanament de camins, arrodoniment i afinació i també efectes de gir i pessic
  • Complex path operations and effects like boolean set operations, path flattening, rounding and refining as well as whirl/pinch effects
  • Operaciones de ruta complejas y efectos como operaciones booleanas con conjuntos, aplanamiento de rutas, redondeo y refinación así como también efectos de arremolinar/apretar
  • Ka keerukamad kompleksjoone operatsioonid ja efektid, näiteks tõeväärtustega operatsioonid, kompleksjoonte lamendamine, ümardamine ja täpsustamine, keerise efekt jms
  • Monimutkaiset polku-operaatiot ja -tehosteet kuten totuusarvo-operaatiot, polun tasoitus, pyöristys ja hienosäätö sekä pyörre- ja nipistystehosteet
  • Opérations de chemins complexes et effets tels que les opérateurs booléens, mise à plat et ajustements, de même que les effets de spirale et de pincement
  • Operacións de camiños complexas e efectos como operacións de grupos de valores booleanos; aplanamento, arredondamento e refinamento de camiños; así como efectos de xiro e picada.
  • Operazioni complesse su tracciati ed effetti del tipo operazioni su insiemi booleani, appiattimento di tracciati, arrotondamento e affinamento così come effetti di vortice/pizzico
  • Complexe bewerkingen van paden en effecten zoals booleaanse set bewerkingen, afvlakken van paden, afronding en verfijning evenals wervel/put-effecten
  • Złożone działania i efekty na ścieżce takie jak działania boolean, spłaszczanie ścieżki, zaokrąglanie, a także efekty polepszające wraz z efektami wiru/rozdmuchania
  • Opções complexas com caminhos e efeitos como as operações booleanas com conjuntos, o alisamento de caminhos, o arredondamento e a afinação, para além da ondulação/embate
  • Operações complexas com caminhos e efeitos, como operações booleanas com conjuntos, o nivelamento de caminhos, o arredondamento e refinamento, assim como efeitos de ondulação/aperto
  • Komplexné cestné operácie a efekty ako logické operácie, splošťovanie cesty, zaobľovanie a zjemňovanie ako aj efekty vírov.
  • Komplexa konturoperationer och effekter som Booleska mängdoperationer, konturutplattning, rundning och förfining samt virvla- och klämeffekter
  • Складні дії над контурами та ефекти, зокрема набір булівських операцій, спрощення контуру, заокруглення та покращення, ефекти вихору та втягування
  • xxComplex path operations and effects like boolean set operations, path flattening, rounding and refining as well as whirl/pinch effectsxx
  • +
  • 复杂路径操作和效果,例如布尔运算,路径扁平化,简化和优化,以及旋转和揉捏特效
  • Extensible by writing plugins for new tools, shapes and dockers
  • Extensible pisanjem dodataka za novim alatima , oblika i modnog brenda Dockers
  • Ampliable mitjançant l'escriptura de connectors per eines noves, formes i acobladors
  • Ampliable mitjançant l'escriptura de connectors per eines noves, formes i acobladors
  • Erweiterbar durch das Schreiben von Modulen für neue Werkzeuge, Objekte und andockbaren Dialogen
  • Extensible by writing plugins for new tools, shapes and dockers
  • Extensible mediante la escritura de complementos para nuevas herramientas, formas y paneles
  • Laiendamisvõimalus pluginate kirjutamise abil uute tööriistade, kujundite ja dokkide tarbeks
  • Laajennettavissa kirjoittamalla liitännäisiä uusille työkaluille, muodoille ja telakoille
  • Extensible par l'écriture de modules pour de nouveaux outils, formes et conteneurs
  • Permite ampliar as súas funcionalidades mediante complementos de ferramentas novas, de formas e de docas.
  • Espandibile tramite la scrittura di estensioni per nuovi strumenti, forme e aree di aggancio
  • Uit te breiden dor het schrijven van plug-ins voor nieuwe hulpmiddelen, vormen en verankering
  • Rozszerzalne wtyczki zapisu dla nowych narzędzi, kształtów i dokowań
  • Modular, através da criação de 'plugins' para novas ferramentas, formas e áreas acopláveis
  • Expansível através da criação de plugins para novas ferramentas, formas e áreas acopláveis
  • Rozšíriteľný pomocou písania pluginov pre nové nástroje, tvary a dockery
  • Utökningsbart genom att skriva insticksprogram för nya verktyg, former och paneler
  • Можливість розширення додатками нових інструментів, форм та панелей.
  • xxExtensible by writing plugins for new tools, shapes and dockersxx
  • +
  • 可通过工具,形状和停靠栏插件扩展功能
http://www.calligra.org/karbon/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=karbon http://kde.org/images/screenshots/karbon14.png KDE calligrakarbon
diff --git a/karbon/data/org.kde.karbon.desktop b/karbon/data/org.kde.karbon.desktop index 24153876c69..ad42809c0cd 100644 --- a/karbon/data/org.kde.karbon.desktop +++ b/karbon/data/org.kde.karbon.desktop @@ -1,156 +1,156 @@ [Desktop Entry] Type=Application Name=Karbon Name[bs]=Karbon Name[ca]=Karbon Name[ca@valencia]=Karbon Name[cs]=Karbon Name[da]=Karbon Name[de]=Karbon Name[el]=Karbon Name[en_GB]=Karbon Name[es]=Karbon Name[et]=Karbon Name[eu]=Karbon Name[fi]=Karbon Name[fr]=Karbon Name[gl]=Karbon Name[hu]=Karbon Name[ia]=Karbon Name[it]=Karbon Name[ja]=Karbon Name[kk]=Karbon Name[ko]=Karbon Name[lt]=Karbon Name[mr]=कार्बन Name[nb]=Karbon Name[nl]=Karbon Name[pl]=Karbon Name[pt]=Karbon Name[pt_BR]=Karbon Name[ru]=Karbon Name[sk]=Karbon Name[sl]=Karbon Name[sv]=Karbon Name[tr]=Karbon Name[ug]=Karbon Name[uk]=Karbon Name[x-test]=xxKarbonxx Exec=karbon %U GenericName=Scalable Graphics GenericName[af]=Skaal veranderbare Grafieka GenericName[bg]=Векторна графика GenericName[bs]=Grafika koja se može skalirati GenericName[ca]=Gràfics escalables GenericName[ca@valencia]=Gràfics escalables GenericName[cs]=Škálovatelná grafika GenericName[cy]=Graffeg Graddadwy GenericName[da]=Skalérbar grafik GenericName[de]=Vektorgrafik GenericName[el]=Διανυσματικά γραφικά GenericName[en_GB]=Scalable Graphics GenericName[eo]=Skaleblaj vektorgrafikoj GenericName[es]=Gráficos escalables GenericName[et]=Vektorgraafika GenericName[eu]=Grafiko eskalagarriak GenericName[fi]=Skaalautuva grafiikka GenericName[fr]=Graphiques vectoriels GenericName[fy]=GenericFektorôfbyldings GenericName[ga]=Grafaic Inscálaithe -GenericName[gl]=Editor de imaxes vectoriais +GenericName[gl]=Gráficos de tamaño variábel GenericName[he]=גרפיקה מדורגת GenericName[hi]=स्केलेबल ग्राफिक्स GenericName[hne]=स्केलेबल ग्राफिक्स GenericName[hr]=Skalabilna grafika GenericName[hu]=Vektoros rajzoló GenericName[ia]=Graphicos scalabile GenericName[is]=Scalable Graphics GenericName[it]=Grafica vettoriale GenericName[ja]=スケーラブルグラフィックス GenericName[kk]=Масштабтауға келетін суреттер GenericName[ko]=크기 조정이 가능한 그래픽 GenericName[lt]=Kintamo dydžio grafika GenericName[lv]=Mērogojama Grafika GenericName[mr]=स्केलेबल ग्राफिक्स GenericName[ms]=Grafik Boleh-skala GenericName[nb]=Skalerbar grafikk GenericName[nds]=Vektorgrafik GenericName[ne]=मापनयोग्य ग्राफिक्स GenericName[nl]=Vectorafbeeldingen GenericName[pl]=Skalowalna grafika GenericName[pt]=Imagens Escaláveis GenericName[pt_BR]=Imagens vetoriais GenericName[ro]=Grafică scalabilă GenericName[ru]=Векторные рисунки GenericName[se]=Skálehahtti grafihkka GenericName[sk]=Škálovateľná grafika GenericName[sl]=Raztegljiva grafika GenericName[sv]=Skalbar grafik GenericName[ta]=அளக்கக்கூடிய வண்ணங்கள் GenericName[tg]=Тасвирҳои вектор GenericName[tr]=Oranlanabilir Grafikler GenericName[uk]=Масштабовна графіка GenericName[uz]=Vektor grafikasi GenericName[uz@cyrillic]=Вектор графикаси GenericName[wa]=Imådjes metåves al schåle GenericName[xh]=Imizobo Ekalishekayo GenericName[x-test]=xxScalable Graphicsxx GenericName[zh_CN]=可缩放图像 GenericName[zh_TW]=可縮放向量圖 MimeType=application/vnd.oasis.opendocument.graphics;application/x-karbon;image/x-eps;application/postscript;image/svg+xml;image/svg+xml-compressed;image/x-wmf;application/pdf;image/x-xfig; # kghostview has 6 for application/postscript, we need to be under that... InitialPreference=5 X-KDE-ServiceTypes=Calligra/Application Icon=calligrakarbon X-KDE-NativeMimeType=application/vnd.oasis.opendocument.graphics X-Calligra-DefaultMimeTypes=application/vnd.oasis.opendocument.graphics,application/x-karbon,image/x-eps,application/postscript,image/svg+xml,image/svg+xml-compressed,image/x-wmf,application/pdf, Categories=Qt;KDE;Graphics;Office; Comment=Create scalable vector drawings Comment[bg]=Създаване на векторни графики Comment[bs]=Kreiraj vektorski crtež koji se može skalirati Comment[ca]=Crea dibuixos de vectors escalables Comment[ca@valencia]=Crea dibuixos de vectors escalables Comment[cs]=Vytvářejte vektorové kresby Comment[da]=Tegn skalerbare vektortegninger Comment[de]=Skalierbare Vektorzeichungen erstellen Comment[el]=Δημιουργία διανυσματικών γραφικών Comment[en_GB]=Create scalable vector drawings Comment[es]=Crear dibujos vectoriales escalables Comment[et]=Skaleeritavate vektrojoonistuste loomine Comment[eu]=Sortu bektore-marrazki eskalagarriak Comment[fi]=Luo vektorigrafiikkapiirroksia Comment[fr]=Créer des tracés vectoriels Comment[fy]=Te skalen fektor ôfbyldings oanmeitsje Comment[ga]=Cruthaigh líníochtaí veicteoireacha inscálaithe -Comment[gl]=Debuxe e edite imaxes vectoriais. +Comment[gl]=Cree debuxos vectoriais de tamaño variábel. Comment[he]=יצירת גרפיקה מדורגת עם וקטורים Comment[hi]=स्केलेबल वेक्टर ड्राइंग बनाएँ Comment[hne]=स्केलेबल वेक्टर ड्राइंग बनाव Comment[hu]=Átméretezhető vektorgrafikák létrehozása Comment[it]=Crea grafica vettoriale riscalabile Comment[ja]=スケーラブルベクター描画を作成 Comment[kk]=Масштабтауға болатын векторлы суретті салу Comment[ko]=크기 조정이 가능한 벡터 그래픽 만들기 Comment[lv]=Veido mērogojamus vektoru zīmējumus Comment[mr]=स्केलेबल व्हेक्टर चित्रे निर्माण करा Comment[nb]=Lag skalerbare vektortegninger Comment[nds]=Skaleerbor Vektorgrafiken opstellen Comment[ne]=मापनयोग्य भेक्टर रेखाचित्र सिर्जना गर्नुहोस् Comment[nl]=Schaalbare vectorafbeeldingen aanmaken Comment[pl]=Twórz skalowalne rysunki wektorowe Comment[pt]=Criar desenhos vectoriais Comment[pt_BR]=Criar desenhos vetoriais Comment[ru]=Создать векторный рисунок Comment[sk]=Vytvoriť scalable vector drawings Comment[sl]=Ustvarite raztegljive vektorske risbe Comment[sv]=Skapa skalbar vektorgrafik Comment[tg]=Эҷоди нақшаи тасвирҳои вектор Comment[tr]=Boyutlandırılabilir vektör çizimler oluştur Comment[uk]=Створити зображення з масштабовних векторів Comment[wa]=Ahiver des dessinaedjes pa royes metåves al schåle Comment[x-test]=xxCreate scalable vector drawingsxx Comment[zh_CN]=创建可缩放矢量绘图 Comment[zh_TW]=建立可調整向量圖 X-DocPath=http://userbase.kde.org/Special:MyLanguage/Karbon X-DBUS-StartupType=Multi X-DBUS-ServiceName=org.kde.karbon diff --git a/karbon/stencils/Flags/czechia.desktop b/karbon/stencils/Flags/czechia.desktop index b58a0816788..a5458a4a77b 100644 --- a/karbon/stencils/Flags/czechia.desktop +++ b/karbon/stencils/Flags/czechia.desktop @@ -1,20 +1,22 @@ [Desktop Entry] Name=Czechia Name[ca]=Txèquia Name[ca@valencia]=Txèquia Name[cs]=Česká Republika Name[de]=Tschechien Name[en_GB]=Czechia Name[es]=Chequia Name[fr]=Tchéquie +Name[gl]=Chequia Name[it]=Cechia Name[nb]=Tsjekkia Name[nl]=Tsjechië Name[nn]=Tsjekkia Name[pl]=Czechy Name[pt]=República Checa Name[pt_BR]=República Tcheca +Name[sk]=Česko Name[sv]=Tjeckien Name[tr]=Çekya Name[uk]=Чехія Name[x-test]=xxCzechiaxx diff --git a/libs/flake/KoConnectionShapeFactory.h b/libs/flake/KoConnectionShapeFactory.h index 1d4cd8eece7..72c27bc725c 100644 --- a/libs/flake/KoConnectionShapeFactory.h +++ b/libs/flake/KoConnectionShapeFactory.h @@ -1,40 +1,41 @@ /* This file is part of the KDE project * Copyright (C) 2007 Boudewijn Rempt * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2007 Jan Hambrecht * * 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. */ #ifndef CONNECTIONSHAPEFACTORY_H #define CONNECTIONSHAPEFACTORY_H #include class KoShape; class KoConnectionShapeFactory : public KoShapeFactoryBase { +Q_OBJECT public: KoConnectionShapeFactory(); ~KoConnectionShapeFactory() {} virtual KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const; virtual bool supports(const KoXmlElement &e, KoShapeLoadingContext &context) const; virtual QList createShapeOptionPanels(); }; #endif diff --git a/libs/flake/KoGradientBackground.cpp b/libs/flake/KoGradientBackground.cpp index c6f4ccf2a98..561383dd5be 100644 --- a/libs/flake/KoGradientBackground.cpp +++ b/libs/flake/KoGradientBackground.cpp @@ -1,154 +1,154 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * 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. */ #include "KoGradientBackground.h" #include "KoShapeBackground_p.h" #include "KoFlake.h" #include #include #include #include #include #include #include #include #include class KoGradientBackgroundPrivate : public KoShapeBackgroundPrivate { public: KoGradientBackgroundPrivate() : gradient(0) {} QGradient *gradient; QTransform matrix; }; KoGradientBackground::KoGradientBackground(QGradient * gradient, const QTransform &matrix) : KoShapeBackground(*(new KoGradientBackgroundPrivate())) { Q_D(KoGradientBackground); d->gradient = gradient; d->matrix = matrix; Q_ASSERT(d->gradient); Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode); } KoGradientBackground::KoGradientBackground(const QGradient & gradient, const QTransform &matrix) : KoShapeBackground(*(new KoGradientBackgroundPrivate())) { Q_D(KoGradientBackground); d->gradient = KoFlake::cloneGradient(&gradient); d->matrix = matrix; Q_ASSERT(d->gradient); Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode); } KoGradientBackground::~KoGradientBackground() { Q_D(KoGradientBackground); delete d->gradient; } void KoGradientBackground::setTransform(const QTransform &matrix) { Q_D(KoGradientBackground); d->matrix = matrix; } QTransform KoGradientBackground::transform() const { Q_D(const KoGradientBackground); return d->matrix; } void KoGradientBackground::setGradient(const QGradient &gradient) { Q_D(KoGradientBackground); delete d->gradient; d->gradient = KoFlake::cloneGradient(&gradient); Q_ASSERT(d->gradient); Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode); } const QGradient * KoGradientBackground::gradient() const { Q_D(const KoGradientBackground); return d->gradient; } void KoGradientBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { Q_D(const KoGradientBackground); if (!d->gradient) return; QBrush brush(*d->gradient); brush.setTransform(d->matrix); painter.setBrush(brush); painter.drawPath(fillPath); } void KoGradientBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoGradientBackground); if (!d->gradient) return; QBrush brush(*d->gradient); brush.setTransform(d->matrix); KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), brush); } bool KoGradientBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { Q_D(KoGradientBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "gradient") { QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.stylesReader(), shapeSize); const QGradient * gradient = brush.gradient(); if (gradient) { d->gradient = KoFlake::cloneGradient(gradient); d->matrix = brush.transform(); //Gopalakrishna Bhat: If the brush has transparency then we ignore the draw:opacity property and use the brush transparency. // Brush will have transparency if the svg:linearGradient stop point has stop-opacity property otherwise it is opaque if (brush.isOpaque() && styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacityPercent = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacityPercent.isEmpty() && opacityPercent.right(1) == "%") { - float opacity = qMin(opacityPercent.left(opacityPercent.length() - 1).toDouble(), 100.0) / 100; + float opacity = qMin(opacityPercent.leftRef(opacityPercent.length() - 1).toDouble(), 100.0) / 100; QGradientStops stops; foreach(QGradientStop stop, d->gradient->stops()) { stop.second.setAlphaF(opacity); stops << stop; } d->gradient->setStops(stops); } } return true; } } return false; } diff --git a/libs/flake/KoHatchBackground.cpp b/libs/flake/KoHatchBackground.cpp index 7df3dc09695..35f1bb46cda 100644 --- a/libs/flake/KoHatchBackground.cpp +++ b/libs/flake/KoHatchBackground.cpp @@ -1,233 +1,233 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Thorsten Zachmann * * 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. */ #include "KoHatchBackground.h" #include "KoColorBackground_p.h" #include #include #include #include #include #include #include #include #include #include #include class KoHatchBackgroundPrivate : public KoColorBackgroundPrivate { public: KoHatchBackgroundPrivate() : angle(0.0) , distance(1.0) , style(KoHatchBackground::Single) {} QColor lineColor; int angle; qreal distance; KoHatchBackground::HatchStyle style; QString name; }; KoHatchBackground::KoHatchBackground() : KoColorBackground(*(new KoHatchBackgroundPrivate())) { } void KoHatchBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const { Q_D(const KoHatchBackground); if (d->color.isValid()) { // paint background color if set by using the color background KoColorBackground::paint(painter, converter, context, fillPath); } const QRectF targetRect = fillPath.boundingRect(); painter.save(); painter.setClipPath(fillPath); QPen pen(d->lineColor); // we set the pen width to 0.5 pt for the hatch. This is not defined in the spec. pen.setWidthF(0.5); painter.setPen(pen); QVector lines; // The different styles are handled by painting the lines multiple times with a different // angel offset as basically it just means we paint the lines also at a different angle. // This are the angle offsets we need to apply to the different lines of a style. // -90 is for single, 0 for the 2nd line in double and -45 for the 3th line in triple. const int angleOffset[] = {-90, 0, -45 }; // The number of loops is defined by the style. int loops = (d->style == Single) ? 1 : (d->style == Double) ? 2 : 3; for (int i = 0; i < loops; ++i) { int angle = d->angle - angleOffset[i]; qreal cosAngle = ::cos(angle/180.0*M_PI); // if cos is nearly 0 the lines are horizontal. Use a special case for that if (qAbs(cosAngle) > 0.00001) { qreal xDiff = tan(angle/180.0*M_PI) * targetRect.height(); // calculate the distance we need to increase x when creating the lines so that the // distance between the lines is also correct for rotated lines. qreal xOffset = qAbs(d->distance / cosAngle); // if the lines go to the right we need to start more to the left. Get the correct start. qreal xStart = 0; while (-xDiff < xStart) { xStart -= xOffset; } // if the lines go to the left we need to stop more at the right. Get the correct end offset qreal xEndOffset = 0; if (xDiff < 0) { while (xDiff < -xEndOffset) { xEndOffset += xOffset; } } // create line objects. lines.reserve(lines.size() + int((targetRect.width() + xEndOffset - xStart) / xOffset) + 1); for (qreal x = xStart; x < targetRect.width() + xEndOffset; x += xOffset) { lines.append(QLineF(x, 0, x + xDiff, targetRect.height())); } } else { // horizontal lines lines.reserve(lines.size() + int(targetRect.height()/d->distance) + 1); for (qreal y = 0; y < targetRect.height(); y += d->distance) { lines.append(QLineF(0, y, targetRect.width(), y)); } } } painter.drawLines(lines); painter.restore(); } void KoHatchBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoHatchBackground); KoGenStyle::Type type = style.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; style.addProperty("draw:fill", "hatch", propertyType); style.addProperty("draw:fill-hatch-name", saveHatchStyle(context), propertyType); bool fillHatchSolid = d->color.isValid(); style.addProperty("draw:fill-hatch-solid", fillHatchSolid, propertyType); if (fillHatchSolid) { style.addProperty("draw:fill-color", d->color.name(), propertyType); } } QString KoHatchBackground::saveHatchStyle(KoShapeSavingContext &context) const { Q_D(const KoHatchBackground); KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/); hatchStyle.addAttribute("draw:display-name", d->name); hatchStyle.addAttribute("draw:color", d->lineColor.name()); hatchStyle.addAttributePt("draw:distance", d->distance); hatchStyle.addAttribute("draw:rotation", QString("%1").arg(d->angle * 10)); switch (d->style) { case Single: hatchStyle.addAttribute("draw:style", "single"); break; case Double: hatchStyle.addAttribute("draw:style", "double"); break; case Triple: hatchStyle.addAttribute("draw:style", "triple"); break; } return context.mainStyles().insert(hatchStyle, "hatch"); } bool KoHatchBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) { // Q_D(KoHatchBackground); Q_UNUSED(shapeSize); KoStyleStack &styleStack = context.styleStack(); QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "hatch") { QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name"); debugFlake << " hatch style is :" << style; - KoXmlElement* draw = context.stylesReader().drawStyles("hatch")[style]; + KoXmlElement* draw = context.stylesReader().drawStyles("hatch").value(style); if (draw) { debugFlake << "Hatch style found for:" << style; QString angle = draw->attributeNS(KoXmlNS::draw, "rotation", QString("0")); if (angle.at(angle.size()-1).isLetter()) { d->angle = KoUnit::parseAngle(angle); } else { // OO saves the angle value without unit and multiplied by a factor of 10 d->angle = int(angle.toInt() / 10); } debugFlake << "angle :" << d->angle; d->name = draw->attributeNS(KoXmlNS::draw, "display-name"); // use 2mm as default, just in case it is not given in a document so we show something sensible. d->distance = KoUnit::parseValue(draw->attributeNS(KoXmlNS::draw, "distance", "2mm")); bool fillHatchSolid = styleStack.property(KoXmlNS::draw, "fill-hatch-solid") == QLatin1String("true"); if (fillHatchSolid) { QString fillColor = styleStack.property(KoXmlNS::draw, "fill-color"); if (!fillColor.isEmpty()) { d->color.setNamedColor(fillColor); } else { d->color =QColor(); } } else { d->color = QColor(); } d->lineColor.setNamedColor(draw->attributeNS(KoXmlNS::draw, "color", QString("#000000"))); QString style = draw->attributeNS(KoXmlNS::draw, "style", QString()); if (style == "double") { d->style = Double; } else if (style == "triple") { d->style = Triple; } else { d->style = Single; } } return true; } return false; } diff --git a/libs/flake/KoOdfGradientBackground.cpp b/libs/flake/KoOdfGradientBackground.cpp index 43664f5a7b4..19a98557c87 100644 --- a/libs/flake/KoOdfGradientBackground.cpp +++ b/libs/flake/KoOdfGradientBackground.cpp @@ -1,369 +1,372 @@ /* This file is part of the KDE project * * Copyright (C) 2011 Lukáš Tvrdý * * 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. */ #include "KoOdfGradientBackground.h" #include "KoShapeBackground_p.h" #include "KoShapeSavingContext.h" #include #include #include #include #include #include #include #include #include #include class KoOdfGradientBackgroundPrivate : public KoShapeBackgroundPrivate { public: KoOdfGradientBackgroundPrivate() : style(), cx(0), cy(0), startColor(), endColor(), angle(0), border(0), opacity(1.0) {}; ~KoOdfGradientBackgroundPrivate(){}; //data QString style; int cx; int cy; QColor startColor; QColor endColor; qreal angle; qreal border; qreal opacity; mutable QImage buffer; }; KoOdfGradientBackground::KoOdfGradientBackground() : KoShapeBackground(*(new KoOdfGradientBackgroundPrivate())) { } KoOdfGradientBackground::~KoOdfGradientBackground() { } bool KoOdfGradientBackground::loadOdf(const KoXmlElement& e) { Q_D(KoOdfGradientBackground); d->style = e.attributeNS(KoXmlNS::draw, "style", QString()); //TODO: support ellipsoid here too if ((d->style != "rectangular") && (d->style != "square")) { return false; } d->cx = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cx", QString()).remove('%')); d->cy = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cy", QString()).remove('%')); d->border = qBound(0.0,0.01 * e.attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble(),1.0); d->startColor = QColor(e.attributeNS(KoXmlNS::draw, "start-color", QString())); d->startColor.setAlphaF((0.01 * e.attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble())); d->endColor = QColor(e.attributeNS(KoXmlNS::draw, "end-color", QString())); d->endColor.setAlphaF(0.01 * e.attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble()); d->angle = e.attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10; return true; } void KoOdfGradientBackground::saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const { Q_D(const KoOdfGradientBackground); KoGenStyle::Type type = styleFill.type(); KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle || type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle ) ? KoGenStyle::DefaultType : KoGenStyle::GraphicType; KoGenStyle gradientStyle(KoGenStyle::GradientStyle); gradientStyle.addAttribute("draw:style", d->style); // draw:style="square" gradientStyle.addAttribute("draw:cx", QString("%1%").arg(d->cx)); gradientStyle.addAttribute("draw:cy", QString("%1%").arg(d->cy)); gradientStyle.addAttribute("draw:start-color", d->startColor.name()); gradientStyle.addAttribute("draw:end-color", d->endColor.name()); gradientStyle.addAttribute("draw:start-intensity", QString("%1%").arg(qRound(d->startColor.alphaF() * 100)) ); gradientStyle.addAttribute("draw:end-intensity", QString("%1%").arg(qRound(d->endColor.alphaF() * 100)) ); gradientStyle.addAttribute("draw:angle", QString("%1").arg(d->angle * 10)); gradientStyle.addAttribute("draw:border", QString("%1%").arg(qRound(d->border * 100.0))); QString gradientStyleName = mainStyles.insert(gradientStyle, "gradient"); styleFill.addProperty("draw:fill", "gradient", propertyType); styleFill.addProperty("draw:fill-gradient-name", gradientStyleName, propertyType); if (d->opacity <= 1.0) { styleFill.addProperty("draw:opacity", QString("%1%").arg(d->opacity * 100.0), propertyType); } } void KoOdfGradientBackground::paint(QPainter& painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath& fillPath) const { Q_D(const KoOdfGradientBackground); QRectF targetRect = fillPath.boundingRect(); QRectF pixels = painter.transform().mapRect(QRectF(0,0,targetRect.width(), targetRect.height())); QSize currentSize( qCeil(pixels.size().width()), qCeil(pixels.size().height()) ); if (d->buffer.isNull() || d->buffer.size() != currentSize){ d->buffer = QImage(currentSize, QImage::Format_ARGB32_Premultiplied); if (d->style == "square") { renderSquareGradient(d->buffer); } else { renderRectangleGradient(d->buffer); } } painter.setClipPath(fillPath); painter.setOpacity(d->opacity); painter.drawImage(targetRect, d->buffer, QRectF(QPointF(0,0), d->buffer.size())); } void KoOdfGradientBackground::fillStyle(KoGenStyle& style, KoShapeSavingContext& context) { saveOdf(style, context.mainStyles()); } bool KoOdfGradientBackground::loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) { Q_UNUSED(shapeSize); Q_D(KoOdfGradientBackground); KoStyleStack &styleStack = context.styleStack(); if (!styleStack.hasProperty(KoXmlNS::draw, "fill")) { return false; } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "gradient") { if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) { QString opacity = styleStack.property(KoXmlNS::draw, "opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") { - d->opacity = qMin(opacity.left(opacity.length() - 1).toDouble(), 100.0) / 100; + d->opacity = qMin(opacity.leftRef(opacity.length() - 1).toDouble(), 100.0) / 100; } } QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name"); - KoXmlElement * e = context.stylesReader().drawStyles("gradient")[styleName]; - return loadOdf(*e); + auto gradient = context.stylesReader().drawStyles("gradient"); + auto it = gradient.constFind(styleName); + if (it != gradient.constEnd() && it.value()) { + return loadOdf(*it.value()); + } } return false; } void KoOdfGradientBackground::renderSquareGradient(QImage& buffer) const { Q_D(const KoOdfGradientBackground); buffer.fill(d->startColor.rgba()); QPainter painter(&buffer); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, false); int width = buffer.width(); int height = buffer.height(); qreal gradientCenterX = qRound(width * d->cx * 0.01); qreal gradientCenterY = qRound(height * d->cy * 0.01); qreal centerX = width * 0.5; qreal centerY = height * 0.5; qreal areaCenterX = qRound(centerX); qreal areaCenterY = qRound(centerY); QTransform m; m.translate(gradientCenterX, gradientCenterY); m.rotate(-d->angle); m.scale(1.0 - d->border, 1.0 - d->border); m.translate(-gradientCenterX, -gradientCenterY); m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); painter.setTransform(m); QLinearGradient linearGradient; linearGradient.setColorAt(1, d->startColor); linearGradient.setColorAt(0, d->endColor); // from center going North linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(centerX, 0); painter.setBrush(linearGradient); painter.drawRect(0, 0, width, centerY); // from center going South linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(0, centerY, width, centerY); // clip the East and West portion QPainterPath clip; clip.moveTo(width, 0); clip.lineTo(width, height); clip.lineTo(0, 0); clip.lineTo(0, height); clip.closeSubpath(); painter.setClipPath(clip); // from center going East linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(centerX, 0, width, height); // from center going West linearGradient.setFinalStop( 0, centerY); painter.setBrush(linearGradient); painter.drawRect(0, 0, centerX, height); } void KoOdfGradientBackground::renderRectangleGradient(QImage& buffer) const { Q_D(const KoOdfGradientBackground); buffer.fill(d->startColor.rgba()); QPainter painter(&buffer); painter.setPen(Qt::NoPen); painter.setRenderHint(QPainter::Antialiasing, false); int width = buffer.width(); int height = buffer.height(); qreal gradientCenterX = qRound(width * d->cx * 0.01); qreal gradientCenterY = qRound(height * d->cy * 0.01); qreal centerX = width * 0.5; qreal centerY = height * 0.5; qreal areaCenterY = qRound(centerY); qreal areaCenterX = qRound(centerX); QTransform m; m.translate(gradientCenterX, gradientCenterY); // m.rotate(-d->angle); // OOo rotates the gradient differently m.scale(1.0 - d->border, 1.0 - d->border); m.translate(-gradientCenterX, -gradientCenterY); m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY); painter.setTransform(m); QLinearGradient linearGradient; linearGradient.setColorAt(1, d->startColor); linearGradient.setColorAt(0, d->endColor); // render background QPainterPath clipPath; if (width < height) { QRectF west(0,0,centerX, height); QRectF east(centerX, 0, centerX, height); linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(0, centerY); painter.setBrush(linearGradient); painter.drawRect(west); linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(east); QRectF north(0,0,width, centerX); QRectF south(0,height - centerX, width, centerX); clipPath.moveTo(0,0); clipPath.lineTo(width, 0); clipPath.lineTo(centerX, centerX); clipPath.closeSubpath(); clipPath.moveTo(width, height); clipPath.lineTo(0, height); clipPath.lineTo(centerX, south.y()); clipPath.closeSubpath(); linearGradient.setStart(centerX, centerX); linearGradient.setFinalStop(centerX, 0); painter.setClipPath(clipPath); painter.setBrush(linearGradient); painter.drawRect(north); linearGradient.setStart(centerX, south.y()); linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(south); } else { QRectF north(0,0,width, centerY); QRectF south(0, centerY, width, centerY); linearGradient.setStart(centerX, centerY); linearGradient.setFinalStop(centerX, 0); painter.setBrush(linearGradient); painter.drawRect(north); linearGradient.setFinalStop(centerX, height); painter.setBrush(linearGradient); painter.drawRect(south); QRectF west(0,0,centerY, height); QRectF east(width - centerY, 0, centerY, height); clipPath.moveTo(0,0); clipPath.lineTo(centerY, centerY); clipPath.lineTo(0,height); clipPath.closeSubpath(); clipPath.moveTo(width, height); clipPath.lineTo(east.x(), centerY); clipPath.lineTo(width,0); clipPath.closeSubpath(); linearGradient.setStart(centerY, centerY); linearGradient.setFinalStop(0, centerY); painter.setClipPath(clipPath); painter.setBrush(linearGradient); painter.drawRect(west); linearGradient.setStart(east.x(), centerY); linearGradient.setFinalStop(width, centerY); painter.setBrush(linearGradient); painter.drawRect(east); } } void KoOdfGradientBackground::debug() const { Q_D(const KoOdfGradientBackground); qDebug() << "cx,cy: "<< d->cx << d->cy; qDebug() << "style" << d->style; qDebug() << "colors" << d->startColor << d->endColor; qDebug() << "angle:" << d->angle; qDebug() << "border" << d->border; } diff --git a/libs/flake/KoPathShapeFactory.h b/libs/flake/KoPathShapeFactory.h index 462f3fac350..4c9383f3b42 100644 --- a/libs/flake/KoPathShapeFactory.h +++ b/libs/flake/KoPathShapeFactory.h @@ -1,43 +1,44 @@ /* This file is part of the KDE project Copyright (C) 2006 Rob Buis Copyright (C) 2006 Thomas Zander 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. */ #ifndef KOPATHSHAPEFACTORY_H #define KOPATHSHAPEFACTORY_H #include "KoShapeFactoryBase.h" #include "KoXmlReader.h" class KoShape; /// Factory for path shapes. class FLAKE_EXPORT KoPathShapeFactory : public KoShapeFactoryBase { +Q_OBJECT public: /// constructor explicit KoPathShapeFactory(const QStringList&); ~KoPathShapeFactory() {} virtual KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const; bool supports(const KoXmlElement &element, KoShapeLoadingContext &context) const; /// reimplemented virtual void newDocumentResourceManager(KoDocumentResourceManager *manager) const; }; #endif diff --git a/libs/flake/KoPatternBackground.cpp b/libs/flake/KoPatternBackground.cpp index 669a2b5969e..405a66d0161 100644 --- a/libs/flake/KoPatternBackground.cpp +++ b/libs/flake/KoPatternBackground.cpp @@ -1,485 +1,485 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * 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. */ #include "KoPatternBackground.h" #include "KoShapeBackground_p.h" #include "KoShapeSavingContext.h" #include "KoImageData.h" #include "KoImageCollection.h" #include #include #include #include #include #include #include #include #include #include #include #include class KoPatternBackgroundPrivate : public KoShapeBackgroundPrivate { public: KoPatternBackgroundPrivate() : repeat(KoPatternBackground::Tiled) , refPoint(KoPatternBackground::TopLeft) , imageCollection(0) , imageData(0) { } ~KoPatternBackgroundPrivate() { delete imageData; } QSizeF targetSize() const { QSizeF size = imageData->imageSize(); if (targetImageSizePercent.width() > 0.0) size.setWidth(0.01 * targetImageSizePercent.width() * size.width()); else if (targetImageSize.width() > 0.0) size.setWidth(targetImageSize.width()); if (targetImageSizePercent.height() > 0.0) size.setHeight(0.01 * targetImageSizePercent.height() * size.height()); else if (targetImageSize.height() > 0.0) size.setHeight(targetImageSize.height()); return size; } QPointF offsetFromRect(const QRectF &fillRect, const QSizeF &imageSize) const { QPointF offset; switch (refPoint) { case KoPatternBackground::TopLeft: offset = fillRect.topLeft(); break; case KoPatternBackground::Top: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::TopRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.top()); break; case KoPatternBackground::Left: offset.setX(fillRect.left()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Center: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::Right: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.center().y() - 0.5 * imageSize.height()); break; case KoPatternBackground::BottomLeft: offset.setX(fillRect.left()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::Bottom: offset.setX(fillRect.center().x() - 0.5 * imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; case KoPatternBackground::BottomRight: offset.setX(fillRect.right() - imageSize.width()); offset.setY(fillRect.bottom() - imageSize.height()); break; default: break; } if (refPointOffsetPercent.x() > 0.0) offset += QPointF(0.01 * refPointOffsetPercent.x() * imageSize.width(), 0); if (refPointOffsetPercent.y() > 0.0) offset += QPointF(0, 0.01 * refPointOffsetPercent.y() * imageSize.height()); return offset; } QTransform matrix; KoPatternBackground::PatternRepeat repeat; KoPatternBackground::ReferencePoint refPoint; QSizeF targetImageSize; QSizeF targetImageSizePercent; QPointF refPointOffsetPercent; QPointF tileRepeatOffsetPercent; KoImageCollection * imageCollection; KoImageData * imageData; }; // ---------------------------------------------------------------- KoPatternBackground::KoPatternBackground(KoImageCollection * imageCollection) : KoShapeBackground(*(new KoPatternBackgroundPrivate())) { Q_D(KoPatternBackground); d->imageCollection = imageCollection; Q_ASSERT(d->imageCollection); } KoPatternBackground::~KoPatternBackground() { - Q_D(KoPatternBackground); + //Q_D(KoPatternBackground); } void KoPatternBackground::setTransform(const QTransform &matrix) { Q_D(KoPatternBackground); d->matrix = matrix; } QTransform KoPatternBackground::transform() const { Q_D(const KoPatternBackground); return d->matrix; } void KoPatternBackground::setPattern(const QImage &pattern) { Q_D(KoPatternBackground); delete d->imageData; d->imageData = d->imageCollection->createImageData(pattern); } void KoPatternBackground::setPattern(KoImageData *imageData) { Q_D(KoPatternBackground); delete d->imageData; d->imageData = imageData; } QImage KoPatternBackground::pattern() const { Q_D(const KoPatternBackground); if (d->imageData) return d->imageData->image(); return QImage(); } void KoPatternBackground::setRepeat(PatternRepeat repeat) { Q_D(KoPatternBackground); d->repeat = repeat; } KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const { Q_D(const KoPatternBackground); return d->repeat; } KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const { Q_D(const KoPatternBackground); return d->refPoint; } void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint) { Q_D(KoPatternBackground); d->refPoint = referencePoint; } QPointF KoPatternBackground::referencePointOffset() const { Q_D(const KoPatternBackground); return d->refPointOffsetPercent; } void KoPatternBackground::setReferencePointOffset(const QPointF &offset) { Q_D(KoPatternBackground); qreal ox = qMax(qreal(0.0), qMin(qreal(100.0), offset.x())); qreal oy = qMax(qreal(0.0), qMin(qreal(100.0), offset.y())); d->refPointOffsetPercent = QPointF(ox, oy); } QPointF KoPatternBackground::tileRepeatOffset() const { Q_D(const KoPatternBackground); return d->tileRepeatOffsetPercent; } void KoPatternBackground::setTileRepeatOffset(const QPointF &offset) { Q_D(KoPatternBackground); d->tileRepeatOffsetPercent = offset; } QSizeF KoPatternBackground::patternDisplaySize() const { Q_D(const KoPatternBackground); return d->targetSize(); } void KoPatternBackground::setPatternDisplaySize(const QSizeF &size) { Q_D(KoPatternBackground); d->targetImageSizePercent = QSizeF(); d->targetImageSize = size; } QSizeF KoPatternBackground::patternOriginalSize() const { Q_D(const KoPatternBackground); return d->imageData->imageSize(); } void KoPatternBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const { Q_D(const KoPatternBackground); if (! d->imageData) return; painter.save(); if (d->repeat == Tiled) { // calculate scaling of pixmap QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); qreal scaleX = targetSize.width() / imageSize.width(); qreal scaleY = targetSize.height() / imageSize.height(); QRectF targetRect = fillPath.boundingRect(); // undo scaling on target rectangle targetRect.setWidth(targetRect.width() / scaleX); targetRect.setHeight(targetRect.height() / scaleY); // determine pattern offset QPointF offset = d->offsetFromRect(targetRect, imageSize); // create matrix for pixmap scaling QTransform matrix; matrix.scale(scaleX, scaleY); painter.setClipPath(fillPath); painter.setWorldTransform(matrix, true); painter.drawTiledPixmap(targetRect, d->imageData->pixmap(imageSize.toSize()), -offset); } else if (d->repeat == Original) { QRectF sourceRect(QPointF(0, 0), d->imageData->imageSize()); QRectF targetRect(QPoint(0, 0), d->targetSize()); targetRect.moveCenter(fillPath.boundingRect().center()); painter.setClipPath(fillPath); painter.drawPixmap(targetRect, d->imageData->pixmap(sourceRect.size().toSize()), sourceRect); } else if (d->repeat == Stretched) { painter.setClipPath(fillPath); // undo conversion of the scaling so that we can use a nicely scaled image of the correct size qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); zoomX = zoomX ? 1 / zoomX : zoomX; zoomY = zoomY ? 1 / zoomY : zoomY; painter.scale(zoomX, zoomY); QRectF targetRect = converter.documentToView(fillPath.boundingRect()); painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize())); } painter.restore(); } void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoPatternBackground); if (! d->imageData) return; switch (d->repeat) { case Original: style.addProperty("style:repeat", "no-repeat"); break; case Tiled: style.addProperty("style:repeat", "repeat"); break; case Stretched: style.addProperty("style:repeat", "stretch"); break; } if (d->repeat == Tiled) { QString refPointId = "top-left"; switch (d->refPoint) { case TopLeft: refPointId = "top-left"; break; case Top: refPointId = "top"; break; case TopRight: refPointId = "top-right"; break; case Left: refPointId = "left"; break; case Center: refPointId = "center"; break; case Right: refPointId = "right"; break; case BottomLeft: refPointId = "bottom-left"; break; case Bottom: refPointId = "bottom"; break; case BottomRight: refPointId = "bottom-right"; break; } style.addProperty("draw:fill-image-ref-point", refPointId); if (d->refPointOffsetPercent.x() > 0.0) style.addProperty("draw:fill-image-ref-point-x", QString("%1%").arg(d->refPointOffsetPercent.x())); if (d->refPointOffsetPercent.y() > 0.0) style.addProperty("draw:fill-image-ref-point-y", QString("%1%").arg(d->refPointOffsetPercent.y())); } if (d->repeat != Stretched) { QSizeF targetSize = d->targetSize(); QSizeF imageSize = d->imageData->imageSize(); if (targetSize.height() != imageSize.height()) style.addPropertyPt("draw:fill-image-height", targetSize.height()); if (targetSize.width() != imageSize.width()) style.addPropertyPt("draw:fill-image-width", targetSize.width()); } KoGenStyle patternStyle(KoGenStyle::FillImageStyle /*no family name*/); patternStyle.addAttribute("xlink:show", "embed"); patternStyle.addAttribute("xlink:actuate", "onLoad"); patternStyle.addAttribute("xlink:type", "simple"); patternStyle.addAttribute("xlink:href", context.imageHref(d->imageData)); QString patternStyleName = context.mainStyles().insert(patternStyle, "picture"); style.addProperty("draw:fill", "bitmap"); style.addProperty("draw:fill-image-name", patternStyleName); context.addDataCenter(d->imageCollection); } bool KoPatternBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &) { Q_D(KoPatternBackground); KoStyleStack &styleStack = context.styleStack(); if (! styleStack.hasProperty(KoXmlNS::draw, "fill")) return false; QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle != "bitmap") return false; QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name"); - KoXmlElement* e = context.stylesReader().drawStyles("fill-image")[styleName]; + KoXmlElement* e = context.stylesReader().drawStyles("fill-image").value(styleName); if (! e) return false; const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString()); if (href.isEmpty()) return false; delete d->imageData; d->imageData = d->imageCollection->createImageData(href, context.store()); if (! d->imageData) return false; // read the pattern repeat style QString style = styleStack.property(KoXmlNS::style, "repeat"); if (style == "stretch") d->repeat = Stretched; else if (style == "no-repeat") d->repeat = Original; else d->repeat = Tiled; if (style != "stretch") { // optional attributes which can override original image size if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height")) { QString height = styleStack.property(KoXmlNS::draw, "fill-image-height"); if (height.endsWith('%')) d->targetImageSizePercent.setHeight(height.remove('%').toDouble()); else d->targetImageSize.setHeight(KoUnit::parseValue(height)); } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) { QString width = styleStack.property(KoXmlNS::draw, "fill-image-width"); if (width.endsWith('%')) d->targetImageSizePercent.setWidth(width.remove('%').toDouble()); else d->targetImageSize.setWidth(KoUnit::parseValue(width)); } } if (style == "repeat") { if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) { // align pattern to the given size QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point"); if (align == "top-left") d->refPoint = TopLeft; else if (align == "top") d->refPoint = Top; else if (align == "top-right") d->refPoint = TopRight; else if (align == "left") d->refPoint = Left; else if (align == "center") d->refPoint = Center; else if (align == "right") d->refPoint = Right; else if (align == "bottom-left") d->refPoint = BottomLeft; else if (align == "bottom") d->refPoint = Bottom; else if (align == "bottom-right") d->refPoint = BottomRight; } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) { QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x"); d->refPointOffsetPercent.setX(pointX.remove('%').toDouble()); } if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) { QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y"); d->refPointOffsetPercent.setY(pointY.remove('%').toDouble()); } if (styleStack.hasProperty(KoXmlNS::draw, "tile-repeat-offset")) { QString repeatOffset = styleStack.property(KoXmlNS::draw, "tile-repeat-offset"); QStringList tokens = repeatOffset.split('%'); if (tokens.count() == 2) { QString direction = tokens[1].simplified(); if (direction == "horizontal") d->tileRepeatOffsetPercent.setX(tokens[0].toDouble()); else if (direction == "vertical") d->tileRepeatOffsetPercent.setY(tokens[0].toDouble()); } } } return true; } QRectF KoPatternBackground::patternRectFromFillSize(const QSizeF &size) { Q_D(KoPatternBackground); QRectF rect; switch (d->repeat) { case Tiled: rect.setTopLeft(d->offsetFromRect(QRectF(QPointF(), size), d->targetSize())); rect.setSize(d->targetSize()); break; case Original: rect.setLeft(0.5 * (size.width() - d->targetSize().width())); rect.setTop(0.5 * (size.height() - d->targetSize().height())); rect.setSize(d->targetSize()); break; case Stretched: rect.setTopLeft(QPointF(0.0, 0.0)); rect.setSize(size); break; } return rect; } diff --git a/libs/flake/KoResourceManager_p.cpp b/libs/flake/KoResourceManager_p.cpp index 36b5f2c07d3..ff2c01b173d 100644 --- a/libs/flake/KoResourceManager_p.cpp +++ b/libs/flake/KoResourceManager_p.cpp @@ -1,136 +1,135 @@ /* Copyright (c) 2006, 2011 Boudewijn Rempt (boud@valdyas.org) Copyright (C) 2007, 2010 Thomas Zander Copyright (c) 2008 Carlos Licea Copyright (c) 2011 Jan Hambrecht 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. */ #include "KoResourceManager_p.h" #include #include #include "KoShape.h" void KoResourceManager::setResource(int key, const QVariant &value) { if (m_resources.contains(key)) { if (m_resources.value(key) == value) return; m_resources[key] = value; } else { m_resources.insert(key, value); } } QVariant KoResourceManager::resource(int key) const { if (!m_resources.contains(key)) { QVariant empty; return empty; } else return m_resources.value(key); } void KoResourceManager::setResource(int key, const KoColor &color) { QVariant v; v.setValue(color); setResource(key, v); } void KoResourceManager::setResource(int key, KoShape *shape) { QVariant v; v.setValue(shape); setResource(key, v); } void KoResourceManager::setResource(int key, const KoUnit &unit) { QVariant v; v.setValue(unit); setResource(key, v); } KoColor KoResourceManager::koColorResource(int key) const { if (! m_resources.contains(key)) { KoColor empty; return empty; } return resource(key).value(); } KoShape *KoResourceManager::koShapeResource(int key) const { if (! m_resources.contains(key)) return 0; return resource(key).value(); } KoUnit KoResourceManager::unitResource(int key) const { return resource(key).value(); } bool KoResourceManager::boolResource(int key) const { if (! m_resources.contains(key)) return false; return m_resources[key].toBool(); } int KoResourceManager::intResource(int key) const { if (! m_resources.contains(key)) return 0; return m_resources[key].toInt(); } QString KoResourceManager::stringResource(int key) const { if (! m_resources.contains(key)) { QString empty; return empty; } return qvariant_cast(resource(key)); } QSizeF KoResourceManager::sizeResource(int key) const { if (! m_resources.contains(key)) { QSizeF empty; return empty; } return qvariant_cast(resource(key)); } bool KoResourceManager::hasResource(int key) const { return m_resources.contains(key); } void KoResourceManager::clearResource(int key) { if (! m_resources.contains(key)) return; m_resources.remove(key); - QVariant empty; } diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp index 37b94805c94..acbcaffed8a 100644 --- a/libs/flake/KoShape.cpp +++ b/libs/flake/KoShape.cpp @@ -1,2337 +1,2337 @@ /* This file is part of the KDE project Copyright (C) 2006 C. Boemann Rasmussen Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2006-2010 Thorsten Zachmann Copyright (C) 2007-2009,2011 Jan Hambrecht CopyRight (C) 2010 Boudewijn Rempt 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. */ #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeContainer.h" #include "KoShapeLayer.h" #include "KoShapeContainerModel.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "KoInsets.h" #include "KoShapeStrokeModel.h" #include "KoShapeBackground.h" #include "KoColorBackground.h" #include "KoHatchBackground.h" #include "KoGradientBackground.h" #include "KoPatternBackground.h" #include "KoShapeManager.h" #include "KoShapeUserData.h" #include "KoShapeApplicationData.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoViewConverter.h" #include "KoShapeStroke.h" #include "KoShapeShadow.h" #include "KoClipPath.h" #include "KoPathShape.h" #include "KoEventAction.h" #include "KoEventActionRegistry.h" #include "KoOdfWorkaround.h" #include "KoFilterEffectStack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoOdfGradientBackground.h" // KoShapePrivate KoShapePrivate::KoShapePrivate(KoShape *shape) : q_ptr(shape), size(50, 50), parent(0), userData(0), appData(0), stroke(0), shadow(0), border(0), clipPath(0), filterEffectStack(0), transparency(0.0), zIndex(0), runThrough(0), visible(true), printable(true), geometryProtected(false), keepAspect(false), selectable(true), deletable(true), detectCollision(false), protectContent(false), textRunAroundSide(KoShape::BiggestRunAroundSide), textRunAroundDistanceLeft(0.0), textRunAroundDistanceTop(0.0), textRunAroundDistanceRight(0.0), textRunAroundDistanceBottom(0.0), textRunAroundThreshold(0.0), textRunAroundContour(KoShape::ContourFull), anchor(0), minimumHeight(0.0) { connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint); connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint); connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint); connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint); connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } KoShapePrivate::~KoShapePrivate() { Q_Q(KoShape); if (parent) parent->removeShape(q); foreach(KoShapeManager *manager, shapeManagers) { manager->remove(q); manager->removeAdditional(q); } delete userData; delete appData; if (stroke && !stroke->deref()) delete stroke; if (shadow && !shadow->deref()) delete shadow; if (filterEffectStack && !filterEffectStack->deref()) delete filterEffectStack; delete clipPath; qDeleteAll(eventActions); } void KoShapePrivate::shapeChanged(KoShape::ChangeType type) { Q_Q(KoShape); if (parent) parent->model()->childChanged(q, type); q->shapeChanged(type); foreach(KoShape * shape, dependees) shape->shapeChanged(type, q); } void KoShapePrivate::updateStroke() { Q_Q(KoShape); if (stroke == 0) return; KoInsets insets; stroke->strokeInsets(q, insets); QSizeF inner = q->size(); // update left q->update(QRectF(-insets.left, -insets.top, insets.left, inner.height() + insets.top + insets.bottom)); // update top q->update(QRectF(-insets.left, -insets.top, inner.width() + insets.left + insets.right, insets.top)); // update right q->update(QRectF(inner.width(), -insets.top, insets.right, inner.height() + insets.top + insets.bottom)); // update bottom q->update(QRectF(-insets.left, inner.height(), inner.width() + insets.left + insets.right, insets.bottom)); } void KoShapePrivate::addShapeManager(KoShapeManager *manager) { shapeManagers.insert(manager); } void KoShapePrivate::removeShapeManager(KoShapeManager *manager) { shapeManagers.remove(manager); } void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toRelative(point.position, shapeSize); point.position.rx() = qBound(0.0, point.position.x(), 1.0); point.position.ry() = qBound(0.0, point.position.y(), 1.0); break; case KoConnectionPoint::AlignRight: point.position.rx() -= shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() -= shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() -= shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() -= shapeSize.width(); point.position.ry() -= shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() -= 0.5 * shapeSize.width(); point.position.ry() -= 0.5 * shapeSize.height(); break; } } void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const { switch(point.alignment) { case KoConnectionPoint::AlignNone: point.position = KoFlake::toAbsolute(point.position, shapeSize); break; case KoConnectionPoint::AlignRight: point.position.rx() += shapeSize.width(); case KoConnectionPoint::AlignLeft: point.position.ry() = 0.5*shapeSize.height(); break; case KoConnectionPoint::AlignBottom: point.position.ry() += shapeSize.height(); case KoConnectionPoint::AlignTop: point.position.rx() = 0.5*shapeSize.width(); break; case KoConnectionPoint::AlignTopLeft: // nothing to do here break; case KoConnectionPoint::AlignTopRight: point.position.rx() += shapeSize.width(); break; case KoConnectionPoint::AlignBottomLeft: point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignBottomRight: point.position.rx() += shapeSize.width(); point.position.ry() += shapeSize.height(); break; case KoConnectionPoint::AlignCenter: point.position.rx() += 0.5 * shapeSize.width(); point.position.ry() += 0.5 * shapeSize.height(); break; } } // static QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString value; if (styleStack.hasProperty(KoXmlNS::draw, property)) { value = styleStack.property(KoXmlNS::draw, property); } return value; } // ======== KoShape KoShape::KoShape() : d_ptr(new KoShapePrivate(this)) { notifyChanged(); } KoShape::KoShape(KoShapePrivate &dd) : d_ptr(&dd) { } KoShape::~KoShape() { Q_D(KoShape); d->shapeChanged(Deleted); delete d_ptr; } void KoShape::scale(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform scaleMatrix; scaleMatrix.translate(pos.x(), pos.y()); scaleMatrix.scale(sx, sy); scaleMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * scaleMatrix; notifyChanged(); d->shapeChanged(ScaleChanged); } void KoShape::rotate(qreal angle) { Q_D(KoShape); QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height())); QTransform rotateMatrix; rotateMatrix.translate(center.x(), center.y()); rotateMatrix.rotate(angle); rotateMatrix.translate(-center.x(), -center.y()); d->localMatrix = d->localMatrix * rotateMatrix; notifyChanged(); d->shapeChanged(RotationChanged); } void KoShape::shear(qreal sx, qreal sy) { Q_D(KoShape); QPointF pos = position(); QTransform shearMatrix; shearMatrix.translate(pos.x(), pos.y()); shearMatrix.shear(sx, sy); shearMatrix.translate(-pos.x(), -pos.y()); d->localMatrix = d->localMatrix * shearMatrix; notifyChanged(); d->shapeChanged(ShearChanged); } void KoShape::setSize(const QSizeF &newSize) { Q_D(KoShape); QSizeF oldSize(size()); // always set size, as d->size and size() may vary d->size = newSize; if (oldSize == newSize) return; notifyChanged(); d->shapeChanged(SizeChanged); } void KoShape::setPosition(const QPointF &newPosition) { Q_D(KoShape); QPointF currentPos = position(); if (newPosition == currentPos) return; QTransform translateMatrix; translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y()); d->localMatrix = d->localMatrix * translateMatrix; notifyChanged(); d->shapeChanged(PositionChanged); } bool KoShape::hitTest(const QPointF &position) const { Q_D(const KoShape); if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); QRectF bb(QPointF(), size()); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (bb.contains(point)) return true; // if there is no shadow we can as well just leave if (! d->shadow) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - d->shadow->offset()); return bb.contains(point); } QRectF KoShape::boundingRect() const { Q_D(const KoShape); QTransform transform = absoluteTransformation(0); QRectF bb = outlineRect(); if (d->stroke) { KoInsets insets; d->stroke->strokeInsets(this, insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } bb = transform.mapRect(bb); if (d->shadow) { KoInsets insets; d->shadow->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (d->filterEffectStack) { QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect()); bb |= transform.mapRect(clipRect); } return bb; } QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const { Q_D(const KoShape); QTransform matrix; // apply parents matrix to inherit any transformations done there. KoShapeContainer * container = d->parent; if (container) { if (container->inheritsTransform(this)) { // We do need to pass the converter here, otherwise the parent's // translation is not inherited. matrix = container->absoluteTransformation(converter); } else { QSizeF containerSize = container->size(); QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height()); if (converter) containerPos = converter->documentToView(containerPos); matrix.translate(containerPos.x(), containerPos.y()); } } if (converter) { QPointF pos = d->localMatrix.map(QPointF()); QPointF trans = converter->documentToView(pos) - pos; matrix.translate(trans.x(), trans.y()); } return d->localMatrix * matrix; } void KoShape::applyAbsoluteTransformation(const QTransform &matrix) { QTransform globalMatrix = absoluteTransformation(0); // the transformation is relative to the global coordinate system // but we want to change the local matrix, so convert the matrix // to be relative to the local coordinate system QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted(); applyTransformation(transformMatrix); } void KoShape::applyTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix * d->localMatrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } void KoShape::setTransformation(const QTransform &matrix) { Q_D(KoShape); d->localMatrix = matrix; notifyChanged(); d->shapeChanged(GenericMatrixChange); } QTransform KoShape::transformation() const { Q_D(const KoShape); return d->localMatrix; } KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy() { return ChildZDefault; } bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2) { // First sort according to runThrough which is sort of a master level KoShape *parentShapeS1 = s1->parent(); KoShape *parentShapeS2 = s2->parent(); int runThrough1 = s1->runThrough(); int runThrough2 = s2->runThrough(); while (parentShapeS1) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough1 = parentShapeS1->runThrough(); } else { runThrough1 = runThrough1 + parentShapeS1->runThrough(); } parentShapeS1 = parentShapeS1->parent(); } while (parentShapeS2) { if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { runThrough2 = parentShapeS2->runThrough(); } else { runThrough2 = runThrough2 + parentShapeS2->runThrough(); } parentShapeS2 = parentShapeS2->parent(); } if (runThrough1 > runThrough2) { return false; } if (runThrough1 < runThrough2) { return true; } // If on the same runThrough level then the zIndex is all that matters. // // We basically walk up through the parents until we find a common base parent // To do that we need two loops where the inner loop walks up through the parents // of s2 every time we step up one parent level on s1 // // We don't update the index value until after we have seen that it's not a common base // That way we ensure that two children of a common base are sorted according to their respective // z value bool foundCommonParent = false; int index1 = s1->zIndex(); int index2 = s2->zIndex(); parentShapeS1 = s1; parentShapeS2 = s2; while (parentShapeS1 && !foundCommonParent) { parentShapeS2 = s2; index2 = parentShapeS2->zIndex(); while (parentShapeS2) { if (parentShapeS2 == parentShapeS1) { foundCommonParent = true; break; } if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) { index2 = parentShapeS2->zIndex(); } parentShapeS2 = parentShapeS2->parent(); } if (!foundCommonParent) { if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) { index1 = parentShapeS1->zIndex(); } parentShapeS1 = parentShapeS1->parent(); } } // If the one shape is a parent/child of the other then sort so. if (s1 == parentShapeS2) { return true; } if (s2 == parentShapeS1) { return false; } // If we went that far then the z-Index is used for sorting. return index1 < index2; } void KoShape::setParent(KoShapeContainer *parent) { Q_D(KoShape); if (d->parent == parent) return; KoShapeContainer *oldParent = d->parent; d->parent = 0; // avoids recursive removing if (oldParent) oldParent->removeShape(this); if (parent && parent != this) { d->parent = parent; parent->addShape(this); } notifyChanged(); d->shapeChanged(ParentChanged); } int KoShape::zIndex() const { Q_D(const KoShape); return d->zIndex; } void KoShape::update() const { Q_D(const KoShape); if (!d->shapeManagers.empty()) { QRectF rect(boundingRect()); foreach(KoShapeManager * manager, d->shapeManagers) { manager->update(rect, this, true); } } } void KoShape::update(const QRectF &rect) const { if (rect.isEmpty() && !rect.isNull()) { return; } Q_D(const KoShape); if (!d->shapeManagers.empty() && isVisible()) { QRectF rc(absoluteTransformation(0).mapRect(rect)); foreach(KoShapeManager * manager, d->shapeManagers) { manager->update(rc); } } } QPainterPath KoShape::outline() const { QPainterPath path; path.addRect(outlineRect()); return path; } QRectF KoShape::outlineRect() const { const QSizeF s = size(); return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)), qMax(s.height(), qreal(0.0001)))); } QPainterPath KoShape::shadowOutline() const { Q_D(const KoShape); if (d->fill) { return outline(); } return QPainterPath(); } QPointF KoShape::absolutePosition(KoFlake::Position anchor) const { QPointF point; switch (anchor) { case KoFlake::TopLeftCorner: break; case KoFlake::TopRightCorner: point = QPointF(size().width(), 0.0); break; case KoFlake::BottomLeftCorner: point = QPointF(0.0, size().height()); break; case KoFlake::BottomRightCorner: point = QPointF(size().width(), size().height()); break; case KoFlake::CenteredPosition: point = QPointF(size().width() / 2.0, size().height() / 2.0); break; } return absoluteTransformation(0).map(point); } void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::Position anchor) { Q_D(KoShape); QPointF currentAbsPosition = absolutePosition(anchor); QPointF translate = newPosition - currentAbsPosition; QTransform translateMatrix; translateMatrix.translate(translate.x(), translate.y()); applyAbsoluteTransformation(translateMatrix); notifyChanged(); d->shapeChanged(PositionChanged); } void KoShape::copySettings(const KoShape *shape) { Q_D(KoShape); d->size = shape->size(); d->connectors.clear(); foreach(const KoConnectionPoint &point, shape->connectionPoints()) addConnectionPoint(point); d->zIndex = shape->zIndex(); d->visible = shape->isVisible(); // Ensure printable is true by default if (!d->visible) d->printable = true; else d->printable = shape->isPrintable(); d->geometryProtected = shape->isGeometryProtected(); d->protectContent = shape->isContentProtected(); d->selectable = shape->isSelectable(); d->keepAspect = shape->keepAspectRatio(); d->localMatrix = shape->d_ptr->localMatrix; d->deletable = shape->isDeletable(); } void KoShape::notifyChanged() { Q_D(KoShape); foreach(KoShapeManager * manager, d->shapeManagers) { manager->notifyShapeChanged(this); } } void KoShape::setUserData(KoShapeUserData *userData) { Q_D(KoShape); delete d->userData; d->userData = userData; } KoShapeUserData *KoShape::userData() const { Q_D(const KoShape); return d->userData; } void KoShape::setApplicationData(KoShapeApplicationData *appData) { Q_D(KoShape); // appdata is deleted by the application. d->appData = appData; } KoShapeApplicationData *KoShape::applicationData() const { Q_D(const KoShape); return d->appData; } bool KoShape::hasTransparency() const { Q_D(const KoShape); if (! d->fill) return true; else return d->fill->hasTransparency() || d->transparency > 0.0; } void KoShape::setTransparency(qreal transparency) { Q_D(KoShape); d->transparency = qBound(0.0, transparency, 1.0); } qreal KoShape::transparency(bool recursive) const { Q_D(const KoShape); if (!recursive || !parent()) { return d->transparency; } else { const qreal parentOpacity = 1.0-parent()->transparency(recursive); const qreal childOpacity = 1.0-d->transparency; return 1.0-(parentOpacity*childOpacity); } } KoInsets KoShape::strokeInsets() const { Q_D(const KoShape); KoInsets answer; if (d->stroke) d->stroke->strokeInsets(this, answer); return answer; } qreal KoShape::rotation() const { Q_D(const KoShape); // try to extract the rotation angle out of the local matrix // if it is a pure rotation matrix // check if the matrix has shearing mixed in if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10) return std::numeric_limits::quiet_NaN(); // check if the matrix has scaling mixed in if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10) return std::numeric_limits::quiet_NaN(); // calculate the angle from the matrix elements qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI; if (angle < 0.0) angle += 360.0; return angle; } QSizeF KoShape::size() const { Q_D(const KoShape); return d->size; } QPointF KoShape::position() const { Q_D(const KoShape); QPointF center(0.5*size().width(), 0.5*size().height()); return d->localMatrix.map(center) - center; } int KoShape::addConnectionPoint(const KoConnectionPoint &point) { Q_D(KoShape); // get next glue point id int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint; if (d->connectors.size()) nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1); KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[nextConnectionPointId] = p; return nextConnectionPointId; } bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point) { Q_D(KoShape); if (connectionPointId < 0) return false; const bool insertPoint = !hasConnectionPoint(connectionPointId); switch(connectionPointId) { case KoConnectionPoint::TopConnectionPoint: case KoConnectionPoint::RightConnectionPoint: case KoConnectionPoint::BottomConnectionPoint: case KoConnectionPoint::LeftConnectionPoint: { KoConnectionPoint::PointId id = static_cast(connectionPointId); d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id); break; } default: { KoConnectionPoint p = point; d->convertFromShapeCoordinates(p, size()); d->connectors[connectionPointId] = p; break; } } if(!insertPoint) d->shapeChanged(ConnectionPointChanged); return true; } bool KoShape::hasConnectionPoint(int connectionPointId) const { Q_D(const KoShape); return d->connectors.contains(connectionPointId); } KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const { Q_D(const KoShape); KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint()); // convert glue point to shape coordinates d->convertToShapeCoordinates(p, size()); return p; } KoConnectionPoints KoShape::connectionPoints() const { Q_D(const KoShape); QSizeF s = size(); KoConnectionPoints points = d->connectors; KoConnectionPoints::iterator point = points.begin(); KoConnectionPoints::iterator lastPoint = points.end(); // convert glue points to shape coordinates for(; point != lastPoint; ++point) { d->convertToShapeCoordinates(point.value(), s); } return points; } void KoShape::removeConnectionPoint(int connectionPointId) { Q_D(KoShape); d->connectors.remove(connectionPointId); d->shapeChanged(ConnectionPointChanged); } void KoShape::clearConnectionPoints() { Q_D(KoShape); d->connectors.clear(); } void KoShape::addEventAction(KoEventAction *action) { Q_D(KoShape); d->eventActions.insert(action); } void KoShape::removeEventAction(KoEventAction *action) { Q_D(KoShape); d->eventActions.remove(action); } QSet KoShape::eventActions() const { Q_D(const KoShape); return d->eventActions; } KoShape::TextRunAroundSide KoShape::textRunAroundSide() const { Q_D(const KoShape); return d->textRunAroundSide; } void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought) { Q_D(KoShape); if (side == RunThrough) { if (runThrought == Background) { setRunThrough(-1); } else { setRunThrough(1); } } else { setRunThrough(0); } if ( d->textRunAroundSide == side) { return; } d->textRunAroundSide = side; notifyChanged(); d->shapeChanged(TextRunAroundChanged); } qreal KoShape::textRunAroundDistanceTop() const { Q_D(const KoShape); return d->textRunAroundDistanceTop; } void KoShape::setTextRunAroundDistanceTop(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceTop = distance; } qreal KoShape::textRunAroundDistanceLeft() const { Q_D(const KoShape); return d->textRunAroundDistanceLeft; } void KoShape::setTextRunAroundDistanceLeft(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceLeft = distance; } qreal KoShape::textRunAroundDistanceRight() const { Q_D(const KoShape); return d->textRunAroundDistanceRight; } void KoShape::setTextRunAroundDistanceRight(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceRight = distance; } qreal KoShape::textRunAroundDistanceBottom() const { Q_D(const KoShape); return d->textRunAroundDistanceBottom; } void KoShape::setTextRunAroundDistanceBottom(qreal distance) { Q_D(KoShape); d->textRunAroundDistanceBottom = distance; } qreal KoShape::textRunAroundThreshold() const { Q_D(const KoShape); return d->textRunAroundThreshold; } void KoShape::setTextRunAroundThreshold(qreal threshold) { Q_D(KoShape); d->textRunAroundThreshold = threshold; } KoShape::TextRunAroundContour KoShape::textRunAroundContour() const { Q_D(const KoShape); return d->textRunAroundContour; } void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour) { Q_D(KoShape); d->textRunAroundContour = contour; } void KoShape::setAnchor(KoShapeAnchor *anchor) { Q_D(KoShape); d->anchor = anchor; } KoShapeAnchor *KoShape::anchor() const { Q_D(const KoShape); return d->anchor; } void KoShape::setMinimumHeight(qreal height) { Q_D(KoShape); d->minimumHeight = height; } qreal KoShape::minimumHeight() const { Q_D(const KoShape); return d->minimumHeight; } void KoShape::setBackground(QSharedPointer fill) { Q_D(KoShape); d->fill = fill; d->shapeChanged(BackgroundChanged); notifyChanged(); } QSharedPointer KoShape::background() const { Q_D(const KoShape); return d->fill; } void KoShape::setZIndex(int zIndex) { Q_D(KoShape); if (d->zIndex == zIndex) return; d->zIndex = zIndex; notifyChanged(); } int KoShape::runThrough() { Q_D(const KoShape); return d->runThrough; } void KoShape::setRunThrough(short int runThrough) { Q_D(KoShape); d->runThrough = runThrough; } void KoShape::setVisible(bool on) { Q_D(KoShape); int _on = (on ? 1 : 0); if (d->visible == _on) return; d->visible = _on; } bool KoShape::isVisible(bool recursive) const { Q_D(const KoShape); if (! recursive) return d->visible; if (recursive && ! d->visible) return false; KoShapeContainer * parentShape = parent(); while (parentShape) { if (! parentShape->isVisible()) return false; parentShape = parentShape->parent(); } return true; } void KoShape::setPrintable(bool on) { Q_D(KoShape); d->printable = on; } bool KoShape::isPrintable() const { Q_D(const KoShape); if (d->visible) return d->printable; else return false; } void KoShape::setSelectable(bool selectable) { Q_D(KoShape); d->selectable = selectable; } bool KoShape::isSelectable() const { Q_D(const KoShape); return d->selectable; } void KoShape::setGeometryProtected(bool on) { Q_D(KoShape); d->geometryProtected = on; } bool KoShape::isGeometryProtected() const { Q_D(const KoShape); return d->geometryProtected; } void KoShape::setContentProtected(bool protect) { Q_D(KoShape); d->protectContent = protect; } bool KoShape::isContentProtected() const { Q_D(const KoShape); return d->protectContent; } void KoShape::setDeletable(bool deletable) { Q_D(KoShape); d->deletable = deletable; } bool KoShape::isDeletable() const { Q_D(const KoShape); return d->deletable; } KoShapeContainer *KoShape::parent() const { Q_D(const KoShape); return d->parent; } void KoShape::setKeepAspectRatio(bool keepAspect) { Q_D(KoShape); d->keepAspect = keepAspect; } bool KoShape::keepAspectRatio() const { Q_D(const KoShape); return d->keepAspect; } QString KoShape::shapeId() const { Q_D(const KoShape); return d->shapeId; } void KoShape::setShapeId(const QString &id) { Q_D(KoShape); d->shapeId = id; } void KoShape::setCollisionDetection(bool detect) { Q_D(KoShape); d->detectCollision = detect; } bool KoShape::collisionDetection() { Q_D(KoShape); return d->detectCollision; } KoShapeStrokeModel *KoShape::stroke() const { Q_D(const KoShape); return d->stroke; } void KoShape::setStroke(KoShapeStrokeModel *stroke) { Q_D(KoShape); if (stroke) stroke->ref(); d->updateStroke(); if (d->stroke) d->stroke->deref(); d->stroke = stroke; d->updateStroke(); d->shapeChanged(StrokeChanged); notifyChanged(); } void KoShape::setShadow(KoShapeShadow *shadow) { Q_D(KoShape); if (d->shadow) d->shadow->deref(); d->shadow = shadow; if (d->shadow) { d->shadow->ref(); // TODO update changed area } d->shapeChanged(ShadowChanged); notifyChanged(); } KoShapeShadow *KoShape::shadow() const { Q_D(const KoShape); return d->shadow; } void KoShape::setBorder(KoBorder *border) { Q_D(KoShape); if (d->border) { // The shape owns the border. delete d->border; } d->border = border; d->shapeChanged(BorderChanged); notifyChanged(); } KoBorder *KoShape::border() const { Q_D(const KoShape); return d->border; } void KoShape::setClipPath(KoClipPath *clipPath) { Q_D(KoShape); d->clipPath = clipPath; d->shapeChanged(ClipPathChanged); notifyChanged(); } KoClipPath * KoShape::clipPath() const { Q_D(const KoShape); return d->clipPath; } QTransform KoShape::transform() const { Q_D(const KoShape); return d->localMatrix; } QString KoShape::name() const { Q_D(const KoShape); return d->name; } void KoShape::setName(const QString &name) { Q_D(KoShape); d->name = name; } void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const { Q_UNUSED(converter); Q_UNUSED(asynchronous); } bool KoShape::isEditable() const { Q_D(const KoShape); if (!d->visible || d->geometryProtected) return false; if (d->parent && d->parent->isChildLocked(this)) return false; return true; } // painting void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter) { Q_UNUSED(converter); KoBorder *bd = border(); if (!bd) { return; } QRectF borderRect = QRectF(QPointF(0, 0), size()); // Paint the border. bd->paint(painter, borderRect, KoBorder::PaintInsideLine); } // loading & saving methods QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { Q_D(const KoShape); // and fill the style KoShapeStrokeModel *sm = stroke(); if (sm) { sm->fillStyle(style, context); } else { style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType); } KoShapeShadow *s = shadow(); if (s) s->fillStyle(style, context); QSharedPointer bg = background(); if (bg) { bg->fillStyle(style, context); } else { style.addProperty("draw:fill", "none", KoGenStyle::GraphicType); } KoBorder *b = border(); if (b) { b->saveOdf(style); } if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) { style.setAutoStyleInStylesDotXml(true); } QString value; if (isGeometryProtected()) { value = "position size"; } if (isContentProtected()) { if (! value.isEmpty()) value += ' '; value += "content"; } if (!value.isEmpty()) { style.addProperty("style:protect", value, KoGenStyle::GraphicType); } QMap::const_iterator it(d->additionalStyleAttributes.constBegin()); for (; it != d->additionalStyleAttributes.constEnd(); ++it) { style.addProperty(it.key(), it.value()); } if (parent() && parent()->isClipped(this)) { /* * In Calligra clipping is done using a parent shape which can be rotated, sheared etc * and even non-square. So the ODF interoperability version we write here is really * just a very simple version of that... */ qreal top = -position().y(); qreal left = -position().x(); qreal right = parent()->size().width() - size().width() - left; qreal bottom = parent()->size().height() - size().height() - top; style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)") .arg(top, 10, 'f').arg(right, 10, 'f') .arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType); } QString wrap; switch (textRunAroundSide()) { case BiggestRunAroundSide: wrap = "biggest"; break; case LeftRunAroundSide: wrap = "left"; break; case RightRunAroundSide: wrap = "right"; break; case EnoughRunAroundSide: wrap = "dynamic"; break; case BothRunAroundSide: wrap = "parallel"; break; case NoRunAround: wrap = "none"; break; case RunThrough: wrap = "run-through"; break; } style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType); switch (textRunAroundContour()) { case ContourBox: style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType); break; case ContourFull: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType); break; case ContourOutside: style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType); style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType); break; } style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType); if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight()) && (textRunAroundDistanceTop() == textRunAroundDistanceBottom()) && (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) { style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); } else { style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType); style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType); } return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr"); } void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); d->fill.clear(); if (d->stroke && !d->stroke->deref()) { delete d->stroke; d->stroke = 0; } if (d->shadow && !d->shadow->deref()) { delete d->shadow; d->shadow = 0; } setBackground(loadOdfFill(context)); setStroke(loadOdfStroke(element, context)); setShadow(d->loadOdfShadow(context)); setBorder(d->loadOdfBorder(context)); QString protect(styleStack.property(KoXmlNS::style, "protect")); setGeometryProtected(protect.contains("position") || protect.contains("size")); setContentProtected(protect.contains("content")); QString margin = styleStack.property(KoXmlNS::fo, "margin"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-left"); if (!margin.isEmpty()) { setTextRunAroundDistanceLeft(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-top"); if (!margin.isEmpty()) { setTextRunAroundDistanceTop(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-right"); if (!margin.isEmpty()) { setTextRunAroundDistanceRight(KoUnit::parseValue(margin)); } margin = styleStack.property(KoXmlNS::fo, "margin-bottom"); if (!margin.isEmpty()) { setTextRunAroundDistanceBottom(KoUnit::parseValue(margin)); } QString wrap; if (styleStack.hasProperty(KoXmlNS::style, "wrap")) { wrap = styleStack.property(KoXmlNS::style, "wrap"); } else { // no value given in the file, but guess biggest wrap = "biggest"; } if (wrap == "none") { setTextRunAroundSide(KoShape::NoRunAround); } else if (wrap == "run-through") { QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background"); if (runTrought == "background") { setTextRunAroundSide(KoShape::RunThrough, KoShape::Background); } else { setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground); } } else { if (wrap == "biggest") setTextRunAroundSide(KoShape::BiggestRunAroundSide); else if (wrap == "left") setTextRunAroundSide(KoShape::LeftRunAroundSide); else if (wrap == "right") setTextRunAroundSide(KoShape::RightRunAroundSide); else if (wrap == "dynamic") setTextRunAroundSide(KoShape::EnoughRunAroundSide); else if (wrap == "parallel") setTextRunAroundSide(KoShape::BothRunAroundSide); } if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) { QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold"); if (!wrapThreshold.isEmpty()) { setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold)); } } if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") { if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") { setTextRunAroundContour(KoShape::ContourFull); } else { setTextRunAroundContour(KoShape::ContourOutside); } } else { setTextRunAroundContour(KoShape::ContourBox); } } bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes) { Q_D(KoShape); if (attributes & OdfPosition) { QPointF pos(position()); if (element.hasAttributeNS(KoXmlNS::svg, "x")) pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "y")) pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); setPosition(pos); } if (attributes & OdfSize) { QSizeF s(size()); if (element.hasAttributeNS(KoXmlNS::svg, "width")) s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); if (element.hasAttributeNS(KoXmlNS::svg, "height")) s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); setSize(s); } if (attributes & OdfLayer) { if (element.hasAttributeNS(KoXmlNS::draw, "layer")) { KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer")); if (layer) { setParent(layer); } } } if (attributes & OdfId) { KoElementReference ref; ref.loadOdf(element); if (ref.isValid()) { context.addShapeId(this, ref.toString()); } } if (attributes & OdfZIndex) { if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) { setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt()); } else { setZIndex(context.zIndex()); } } if (attributes & OdfName) { if (element.hasAttributeNS(KoXmlNS::draw, "name")) { setName(element.attributeNS(KoXmlNS::draw, "name")); } } if (attributes & OdfStyle) { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.save(); if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic"); } if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) { context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation"); } loadStyle(element, context); styleStack.restore(); } if (attributes & OdfTransformation) { QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString()); if (! transform.isEmpty()) applyAbsoluteTransformation(parseOdfTransform(transform)); } if (attributes & OdfAdditionalAttributes) { QSet additionalAttributeData = KoShapeLoadingContext::additionalAttributeData(); foreach(const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) { if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) { QString value = element.attributeNS(attributeData.ns, attributeData.tag); //debugFlake << "load additional attribute" << attributeData.tag << value; setAdditionalAttribute(attributeData.name, value); } } } if (attributes & OdfCommonChildElements) { const KoXmlElement eventActionsElement(KoXml::namedItemNS(element, KoXmlNS::office, "event-listeners")); if (!eventActionsElement.isNull()) { d->eventActions = KoEventActionRegistry::instance()->createEventActionsFromOdf(eventActionsElement, context); } // load glue points (connection points) loadOdfGluePoints(element, context); } return true; } QSharedPointer KoShape::loadOdfFill(KoShapeLoadingContext &context) const { QString fill = KoShapePrivate::getStyleProperty("fill", context); QSharedPointer bg; if (fill == "solid") { bg = QSharedPointer(new KoColorBackground()); } else if (fill == "hatch") { bg = QSharedPointer(new KoHatchBackground()); } else if (fill == "gradient") { QString styleName = KoShapePrivate::getStyleProperty("fill-gradient-name", context); - KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName]; + KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient").value(styleName); QString style; if (e) { style = e->attributeNS(KoXmlNS::draw, "style", QString()); } if ((style == "rectangular") || (style == "square")) { bg = QSharedPointer(new KoOdfGradientBackground()); } else { QGradient *gradient = new QLinearGradient(); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); bg = QSharedPointer(new KoGradientBackground(gradient)); } } else if (fill == "bitmap") { bg = QSharedPointer(new KoPatternBackground(context.imageCollection())); #ifndef NWORKAROUND_ODF_BUGS } else if (fill.isEmpty()) { bg = QSharedPointer(KoOdfWorkaround::fixBackgroundColor(this, context)); return bg; #endif } else { return QSharedPointer(0); } if (!bg->loadStyle(context.odfLoadingContext(), size())) { return QSharedPointer(0); } return bg; } KoShapeStrokeModel *KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader(); QString stroke = KoShapePrivate::getStyleProperty("stroke", context); if (stroke == "solid" || stroke == "dash") { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader); KoShapeStroke *stroke = new KoShapeStroke(); if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) { QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient"); QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size()); stroke->setLineBrush(brush); } else { stroke->setColor(pen.color()); } #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); return stroke; #ifndef NWORKAROUND_ODF_BUGS } else if (stroke.isEmpty()) { QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader); if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) { KoShapeStroke *stroke = new KoShapeStroke(); #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixPenWidth(pen, context); #endif stroke->setLineWidth(pen.widthF()); stroke->setJoinStyle(pen.joinStyle()); stroke->setLineStyle(pen.style(), pen.dashPattern()); stroke->setCapStyle(pen.capStyle()); stroke->setColor(pen.color()); return stroke; } #endif } return 0; } KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context); if (shadowStyle == "visible" || shadowStyle == "hidden") { KoShapeShadow *shadow = new KoShapeShadow(); QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color")); qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x")); qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y")); shadow->setOffset(QPointF(offsetX, offsetY)); qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius")); shadow->setBlur(blur); QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity"); if (! opacity.isEmpty() && opacity.right(1) == "%") - shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0); + shadowColor.setAlphaF(opacity.leftRef(opacity.length() - 1).toFloat() / 100.0); shadow->setColor(shadowColor); shadow->setVisible(shadowStyle == "visible"); return shadow; } return 0; } KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const { KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); KoBorder *border = new KoBorder(); if (border->loadOdf(styleStack)) { return border; } delete border; return 0; } void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_D(KoShape); KoXmlElement child; bool hasCenterGluePoint = false; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "glue-point") continue; // NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated // all use of draw:id for xml:id, it didn't specify that here, so it // doesn't support xml:id (and so, maybe, shouldn't use KoElementReference. const QString id = child.attributeNS(KoXmlNS::draw, "id", QString()); const int index = id.toInt(); // connection point in center should be default but odf doesn't support, // in new shape, first custom point is in center, it's okay to replace that point // with point from xml now, we'll add it back later if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint || (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) { warnFlake << "glue-point with no or invalid id"; continue; } QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified(); QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified(); if(xStr.isEmpty() || yStr.isEmpty()) { warnFlake << "glue-point with invald position"; continue; } KoConnectionPoint connector; const QString align = child.attributeNS(KoXmlNS::draw, "align", QString()); if (align.isEmpty()) { #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixGluePointPosition(xStr, context); KoOdfWorkaround::fixGluePointPosition(yStr, context); #endif if(!xStr.endsWith('%') || !yStr.endsWith('%')) { warnFlake << "glue-point with invald position"; continue; } // x and y are relative to drawing object center connector.position.setX(xStr.remove('%').toDouble()/100.0); connector.position.setY(yStr.remove('%').toDouble()/100.0); // convert position to be relative to top-left corner connector.position += QPointF(0.5, 0.5); connector.position.rx() = qBound(0.0, connector.position.x(), 1.0); connector.position.ry() = qBound(0.0, connector.position.y(), 1.0); } else { // absolute distances to the edge specified by align connector.position.setX(KoUnit::parseValue(xStr)); connector.position.setY(KoUnit::parseValue(yStr)); if (align == "top-left") { connector.alignment = KoConnectionPoint::AlignTopLeft; } else if (align == "top") { connector.alignment = KoConnectionPoint::AlignTop; } else if (align == "top-right") { connector.alignment = KoConnectionPoint::AlignTopRight; } else if (align == "left") { connector.alignment = KoConnectionPoint::AlignLeft; } else if (align == "center") { connector.alignment = KoConnectionPoint::AlignCenter; } else if (align == "right") { connector.alignment = KoConnectionPoint::AlignRight; } else if (align == "bottom-left") { connector.alignment = KoConnectionPoint::AlignBottomLeft; } else if (align == "bottom") { connector.alignment = KoConnectionPoint::AlignBottom; } else if (align == "bottom-right") { connector.alignment = KoConnectionPoint::AlignBottomRight; } debugFlake << "using alignment" << align; } const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString()); if (!escape.isEmpty()) { if (escape == "horizontal") { connector.escapeDirection = KoConnectionPoint::HorizontalDirections; } else if (escape == "vertical") { connector.escapeDirection = KoConnectionPoint::VerticalDirections; } else if (escape == "left") { connector.escapeDirection = KoConnectionPoint::LeftDirection; } else if (escape == "right") { connector.escapeDirection = KoConnectionPoint::RightDirection; } else if (escape == "up") { connector.escapeDirection = KoConnectionPoint::UpDirection; } else if (escape == "down") { connector.escapeDirection = KoConnectionPoint::DownDirection; } debugFlake << "using escape direction" << escape; } d->connectors[index] = connector; debugFlake << "loaded glue-point" << index << "at position" << connector.position; if (d->connectors[index].position == QPointF(0.5, 0.5)) { hasCenterGluePoint = true; debugFlake << "center glue-point found at id " << index; } } if (!hasCenterGluePoint) { d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter); } debugFlake << "shape has now" << d->connectors.count() << "glue-points"; } void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor) { Q_D(KoShape); KoXmlElement child; forEachElement(child, element) { if (child.namespaceURI() != KoXmlNS::draw) continue; if (child.localName() != "contour-polygon") continue; debugFlake << "shape loads contour-polygon"; KoPathShape *ps = new KoPathShape(); ps->loadContourOdf(child, context, scaleFactor); ps->setTransformation(transformation()); KoClipData *cd = new KoClipData(ps); KoClipPath *clipPath = new KoClipPath(this, cd); d->clipPath = clipPath; } } QTransform KoShape::parseOdfTransform(const QString &transform) { QTransform matrix; // Split string for handling 1 transform statement at a time QStringList subtransforms = transform.split(')', QString::SkipEmptyParts); QStringList::ConstIterator it = subtransforms.constBegin(); QStringList::ConstIterator end = subtransforms.constEnd(); for (; it != end; ++it) { QStringList subtransform = (*it).split('(', QString::SkipEmptyParts); subtransform[0] = subtransform[0].trimmed().toLower(); subtransform[1] = subtransform[1].simplified(); QRegExp reg("[,( ]"); QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts); if (subtransform[0].startsWith(';') || subtransform[0].startsWith(',')) subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); QString cmd = subtransform[0].toLower(); if (cmd == "rotate") { QTransform rotMatrix; if (params.count() == 3) { qreal x = KoUnit::parseValue(params[1]); qreal y = KoUnit::parseValue(params[2]); rotMatrix.translate(x, y); // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); rotMatrix.translate(-x, -y); } else { // oo2 rotates by radians rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI); } matrix = matrix * rotMatrix; } else if (cmd == "translate") { QTransform moveMatrix; if (params.count() == 2) { qreal x = KoUnit::parseValue(params[0]); qreal y = KoUnit::parseValue(params[1]); moveMatrix.translate(x, y); } else // Spec : if only one param given, assume 2nd param to be 0 moveMatrix.translate(KoUnit::parseValue(params[0]) , 0); matrix = matrix * moveMatrix; } else if (cmd == "scale") { QTransform scaleMatrix; if (params.count() == 2) scaleMatrix.scale(params[0].toDouble(), params[1].toDouble()); else // Spec : if only one param given, assume uniform scaling scaleMatrix.scale(params[0].toDouble(), params[0].toDouble()); matrix = matrix * scaleMatrix; } else if (cmd == "skewx") { QPointF p = absolutePosition(KoFlake::TopLeftCorner); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(tan(-params[0].toDouble()), 0.0F); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "skewy") { QPointF p = absolutePosition(KoFlake::TopLeftCorner); QTransform shearMatrix; shearMatrix.translate(p.x(), p.y()); shearMatrix.shear(0.0F, tan(-params[0].toDouble())); shearMatrix.translate(-p.x(), -p.y()); matrix = matrix * shearMatrix; } else if (cmd == "matrix") { QTransform m; if (params.count() >= 6) { m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0, params[2].toDouble(), params[3].toDouble(), 0, KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1); } matrix = matrix * m; } } return matrix; } void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const { Q_D(const KoShape); if (attributes & OdfStyle) { KoGenStyle style; // all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape if (context.isSet(KoShapeSavingContext::PresentationShape)) { style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation"); context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context)); } else { style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic"); context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context)); } } if (attributes & OdfId) { if (context.isSet(KoShapeSavingContext::DrawId)) { KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter); ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId); } } if (attributes & OdfName) { if (! name().isEmpty()) context.xmlWriter().addAttribute("draw:name", name()); } if (attributes & OdfLayer) { KoShape *parent = d->parent; while (parent) { if (dynamic_cast(parent)) { context.xmlWriter().addAttribute("draw:layer", parent->name()); break; } parent = parent->parent(); } } if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) { context.xmlWriter().addAttribute("draw:z-index", zIndex()); } if (attributes & OdfSize) { QSizeF s(size()); if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size // clipping in ODF is done using a combination of visual size and content cliprect. // A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out // of proportion in this case). If we then add a fo:clip like; // fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left) // our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box. // TODO do this properly by subtracting rects s = parent()->size(); } context.xmlWriter().addAttributePt("svg:width", s.width()); context.xmlWriter().addAttributePt("svg:height", s.height()); } // The position is implicitly stored in the transformation matrix // if the transformation is saved as well if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) { const QPointF p(position() * context.shapeOffset(this)); context.xmlWriter().addAttributePt("svg:x", p.x()); context.xmlWriter().addAttributePt("svg:y", p.y()); } if (attributes & OdfTransformation) { QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this); if (! matrix.isIdentity()) { if (qAbs(matrix.m11() - 1) < 1E-5 // 1 && qAbs(matrix.m12()) < 1E-5 // 0 && qAbs(matrix.m21()) < 1E-5 // 0 && qAbs(matrix.m22() - 1) < 1E-5) { // 1 context.xmlWriter().addAttributePt("svg:x", matrix.dx()); context.xmlWriter().addAttributePt("svg:y", matrix.dy()); } else { QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)") .arg(matrix.m11(), 0, 'f', 11) .arg(matrix.m12(), 0, 'f', 11) .arg(matrix.m21(), 0, 'f', 11) .arg(matrix.m22(), 0, 'f', 11) .arg(matrix.dx(), 0, 'f', 11) .arg(matrix.dy(), 0, 'f', 11); context.xmlWriter().addAttribute("draw:transform", m); } } } if (attributes & OdfViewbox) { const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); } if (attributes & OdfAdditionalAttributes) { QMap::const_iterator it(d->additionalAttributes.constBegin()); for (; it != d->additionalAttributes.constEnd(); ++it) { context.xmlWriter().addAttribute(it.key().toUtf8(), it.value()); } } } void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const { Q_D(const KoShape); // save event listeners see ODF 9.2.21 Event Listeners if (d->eventActions.size() > 0) { context.xmlWriter().startElement("office:event-listeners"); foreach(KoEventAction * action, d->eventActions) { action->saveOdf(context); } context.xmlWriter().endElement(); } // save glue points see ODF 9.2.19 Glue Points if(d->connectors.count()) { KoConnectionPoints::const_iterator cp = d->connectors.constBegin(); KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd(); for(; cp != lastCp; ++cp) { // do not save default glue points if(cp.key() < 4) continue; context.xmlWriter().startElement("draw:glue-point"); context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key())); if (cp.value().alignment == KoConnectionPoint::AlignNone) { // convert to percent from center const qreal x = cp.value().position.x() * 100.0 - 50.0; const qreal y = cp.value().position.y() * 100.0 - 50.0; context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x)); context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y)); } else { context.xmlWriter().addAttributePt("svg:x", cp.value().position.x()); context.xmlWriter().addAttributePt("svg:y", cp.value().position.y()); } QString escapeDirection; switch(cp.value().escapeDirection) { case KoConnectionPoint::HorizontalDirections: escapeDirection = "horizontal"; break; case KoConnectionPoint::VerticalDirections: escapeDirection = "vertical"; break; case KoConnectionPoint::LeftDirection: escapeDirection = "left"; break; case KoConnectionPoint::RightDirection: escapeDirection = "right"; break; case KoConnectionPoint::UpDirection: escapeDirection = "up"; break; case KoConnectionPoint::DownDirection: escapeDirection = "down"; break; default: // fall through break; } if(!escapeDirection.isEmpty()) { context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection); } QString alignment; switch(cp.value().alignment) { case KoConnectionPoint::AlignTopLeft: alignment = "top-left"; break; case KoConnectionPoint::AlignTop: alignment = "top"; break; case KoConnectionPoint::AlignTopRight: alignment = "top-right"; break; case KoConnectionPoint::AlignLeft: alignment = "left"; break; case KoConnectionPoint::AlignCenter: alignment = "center"; break; case KoConnectionPoint::AlignRight: alignment = "right"; break; case KoConnectionPoint::AlignBottomLeft: alignment = "bottom-left"; break; case KoConnectionPoint::AlignBottom: alignment = "bottom"; break; case KoConnectionPoint::AlignBottomRight: alignment = "bottom-right"; break; default: // fall through break; } if(!alignment.isEmpty()) { context.xmlWriter().addAttribute("draw:align", alignment); } context.xmlWriter().endElement(); } } } void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const { Q_D(const KoShape); debugFlake << "shape saves contour-polygon"; if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) { // This will loose data as odf can only save one set of contour wheras // svg loading and at least karbon editing can produce more than one // TODO, FIXME see if we can save more than one clipshape to odf - d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize); + d->clipPath->clipPathShapes().constFirst()->saveContourOdf(context, originalSize); } } // end loading & saving methods // static void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter) { qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); } QPointF KoShape::shapeToDocument(const QPointF &point) const { return absoluteTransformation(0).map(point); } QRectF KoShape::shapeToDocument(const QRectF &rect) const { return absoluteTransformation(0).mapRect(rect); } QPointF KoShape::documentToShape(const QPointF &point) const { return absoluteTransformation(0).inverted().map(point); } QRectF KoShape::documentToShape(const QRectF &rect) const { return absoluteTransformation(0).inverted().mapRect(rect); } bool KoShape::addDependee(KoShape *shape) { Q_D(KoShape); if (! shape) return false; // refuse to establish a circular dependency if (shape->hasDependee(this)) return false; if (! d->dependees.contains(shape)) d->dependees.append(shape); return true; } void KoShape::removeDependee(KoShape *shape) { Q_D(KoShape); int index = d->dependees.indexOf(shape); if (index >= 0) d->dependees.removeAt(index); } bool KoShape::hasDependee(KoShape *shape) const { Q_D(const KoShape); return d->dependees.contains(shape); } QList KoShape::dependees() const { Q_D(const KoShape); return d->dependees; } void KoShape::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } KoSnapData KoShape::snapData() const { return KoSnapData(); } void KoShape::setAdditionalAttribute(const QString &name, const QString &value) { Q_D(KoShape); d->additionalAttributes.insert(name, value); } void KoShape::removeAdditionalAttribute(const QString &name) { Q_D(KoShape); d->additionalAttributes.remove(name); } bool KoShape::hasAdditionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.contains(name); } QString KoShape::additionalAttribute(const QString &name) const { Q_D(const KoShape); return d->additionalAttributes.value(name); } void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value) { Q_D(KoShape); d->additionalStyleAttributes.insert(name, value); } void KoShape::removeAdditionalStyleAttribute(const char *name) { Q_D(KoShape); d->additionalStyleAttributes.remove(name); } KoFilterEffectStack *KoShape::filterEffectStack() const { Q_D(const KoShape); return d->filterEffectStack; } void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack) { Q_D(KoShape); if (d->filterEffectStack) d->filterEffectStack->deref(); d->filterEffectStack = filterEffectStack; if (d->filterEffectStack) { d->filterEffectStack->ref(); } notifyChanged(); } QSet KoShape::toolDelegates() const { Q_D(const KoShape); return d->toolDelegates; } void KoShape::setToolDelegates(const QSet &delegates) { Q_D(KoShape); d->toolDelegates = delegates; } QString KoShape::hyperLink () const { Q_D(const KoShape); return d->hyperLink; } void KoShape::setHyperLink(const QString &hyperLink) { Q_D(KoShape); d->hyperLink = hyperLink; } KoShapePrivate *KoShape::priv() { Q_D(KoShape); return d; } diff --git a/libs/flake/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp index 2c0636d4c48..54387a26c5c 100644 --- a/libs/flake/KoShapeContainer.cpp +++ b/libs/flake/KoShapeContainer.cpp @@ -1,253 +1,253 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * 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. */ #include "KoShapeContainer.h" #include "KoShapeContainer_p.h" #include "KoShapeContainerModel.h" #include "KoShapeStrokeModel.h" #include "KoShapeContainerDefaultModel.h" #include "KoShapeSavingContext.h" #include #include #include KoShapeContainerPrivate::KoShapeContainerPrivate(KoShapeContainer *q) : KoShapePrivate(q), model(0) { } KoShapeContainerPrivate::~KoShapeContainerPrivate() { delete model; } KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model) : KoShape(*(new KoShapeContainerPrivate(this))) { Q_D(KoShapeContainer); d->model = model; } KoShapeContainer::KoShapeContainer(KoShapeContainerPrivate &dd) : KoShape(dd) { } KoShapeContainer::~KoShapeContainer() { Q_D(KoShapeContainer); if (d->model) { foreach(KoShape *shape, d->model->shapes()) { delete shape; } } } void KoShapeContainer::addShape(KoShape *shape) { Q_D(KoShapeContainer); Q_ASSERT(shape); if (shape->parent() == this && shapes().contains(shape)) return; // TODO add a method to create a default model depending on the shape container if (d->model == 0) d->model = new KoShapeContainerDefaultModel(); if (shape->parent() && shape->parent() != this) shape->parent()->removeShape(shape); d->model->add(shape); shape->setParent(this); } void KoShapeContainer::removeShape(KoShape *shape) { Q_D(KoShapeContainer); Q_ASSERT(shape); if (d->model == 0) return; d->model->remove(shape); shape->setParent(0); KoShapeContainer * grandparent = parent(); if (grandparent) { grandparent->model()->childChanged(this, KoShape::ChildChanged); } } void KoShapeContainer::removeAllShapes() { Q_D(KoShapeContainer); if (d->model == 0) return; for(int i = d->model->shapes().count() - 1; i >= 0; --i) { - KoShape *shape = d->model->shapes()[i]; + KoShape *shape = d->model->shapes().at(i); d->model->remove(shape); shape->setParent(0); } KoShapeContainer * grandparent = parent(); if (grandparent) { grandparent->model()->childChanged(this, KoShape::ChildChanged); } } int KoShapeContainer::shapeCount() const { Q_D(const KoShapeContainer); if (d->model == 0) return 0; return d->model->count(); } bool KoShapeContainer::isChildLocked(const KoShape *child) const { Q_D(const KoShapeContainer); if (d->model == 0) return false; return d->model->isChildLocked(child); } void KoShapeContainer::setClipped(const KoShape *child, bool clipping) { Q_D(KoShapeContainer); if (d->model == 0) return; d->model->setClipped(child, clipping); } void KoShapeContainer::setInheritsTransform(const KoShape *shape, bool inherit) { Q_D(KoShapeContainer); if (d->model == 0) return; d->model->setInheritsTransform(shape, inherit); } bool KoShapeContainer::inheritsTransform(const KoShape *shape) const { Q_D(const KoShapeContainer); if (d->model == 0) return false; return d->model->inheritsTransform(shape); } void KoShapeContainer::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) { Q_D(KoShapeContainer); painter.save(); paintComponent(painter, converter, paintcontext); painter.restore(); if (d->model == 0 || d->model->count() == 0) return; QList sortedObjects = d->model->shapes(); qSort(sortedObjects.begin(), sortedObjects.end(), KoShape::compareShapeZIndex); // Do the following to revert the absolute transformation of the container // that is re-applied in shape->absoluteTransformation() later on. The transformation matrix // of the container has already been applied once before this function is called. QTransform baseMatrix = absoluteTransformation(&converter).inverted() * painter.transform(); // clip the children to the parent outline. QTransform m; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); m.scale(zoomX, zoomY); painter.setClipPath(m.map(outline()), Qt::IntersectClip); QRectF toPaintRect = converter.viewToDocument(painter.clipRegion().boundingRect()); toPaintRect = transform().mapRect(toPaintRect); // We'll use this clipRect to see if our child shapes lie within it. // Because shape->boundingRect() uses absoluteTransformation(0) we'll // use that as well to have the same (absolute) reference transformation // of our and the child's bounding boxes. QTransform absTrans = absoluteTransformation(0); QRectF clipRect = absTrans.map(outline()).boundingRect(); foreach(KoShape *shape, sortedObjects) { //debugFlake <<"KoShapeContainer::painting shape:" << shape->shapeId() <<"," << shape->boundingRect(); if (!shape->isVisible()) continue; if (!isClipped(shape)) // the shapeManager will have to draw those, or else we can't do clipRects continue; // don't try to draw a child shape that is not in the clipping rect of the painter. if (!clipRect.intersects(shape->boundingRect())) continue; painter.save(); painter.setTransform(shape->absoluteTransformation(&converter) * baseMatrix); shape->paint(painter, converter, paintcontext); painter.restore(); if (shape->stroke()) { painter.save(); painter.setTransform(shape->absoluteTransformation(&converter) * baseMatrix); shape->stroke()->paint(shape, painter, converter); painter.restore(); } } } void KoShapeContainer::shapeChanged(ChangeType type, KoShape* shape) { Q_UNUSED(shape); Q_D(KoShapeContainer); if (d->model == 0) return; if (!(type == RotationChanged || type == ScaleChanged || type == ShearChanged || type == SizeChanged || type == PositionChanged || type == GenericMatrixChange)) return; d->model->containerChanged(this, type); foreach(KoShape *shape, d->model->shapes()) shape->notifyChanged(); } bool KoShapeContainer::isClipped(const KoShape *child) const { Q_D(const KoShapeContainer); if (d->model == 0) // throw exception?? return false; return d->model->isClipped(child); } void KoShapeContainer::update() const { Q_D(const KoShapeContainer); KoShape::update(); if (d->model) foreach(KoShape *shape, d->model->shapes()) shape->update(); } QList KoShapeContainer::shapes() const { Q_D(const KoShapeContainer); if (d->model == 0) return QList(); return d->model->shapes(); } KoShapeContainerModel *KoShapeContainer::model() const { Q_D(const KoShapeContainer); return d->model; } diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp index 1832af91bfc..7c03862b73c 100644 --- a/libs/flake/KoShapeGroup.cpp +++ b/libs/flake/KoShapeGroup.cpp @@ -1,250 +1,250 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * 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. */ #include "KoShapeGroup.h" #include "KoShapeContainerModel.h" #include "KoShapeContainer_p.h" #include "KoShapeLayer.h" #include "SimpleShapeContainerModel.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoXmlWriter.h" #include "KoXmlReader.h" #include "KoShapeRegistry.h" #include "KoShapeStrokeModel.h" #include "KoShapeShadow.h" #include "KoInsets.h" #include #include class ShapeGroupContainerModel : public SimpleShapeContainerModel { public: ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {} ~ShapeGroupContainerModel() {} virtual void add(KoShape *child) { SimpleShapeContainerModel::add(child); m_group->invalidateSizeCache(); } virtual void remove(KoShape *child) { SimpleShapeContainerModel::remove(child); m_group->invalidateSizeCache(); } virtual void childChanged(KoShape *shape, KoShape::ChangeType type) { SimpleShapeContainerModel::childChanged(shape, type); //debugFlake << type; switch (type) { case KoShape::PositionChanged: case KoShape::RotationChanged: case KoShape::ScaleChanged: case KoShape::ShearChanged: case KoShape::SizeChanged: case KoShape::GenericMatrixChange: case KoShape::ParameterChanged: case KoShape::ClipPathChanged : m_group->invalidateSizeCache(); break; default: break; } } private: // members KoShapeGroup * m_group; }; class KoShapeGroupPrivate : public KoShapeContainerPrivate { public: KoShapeGroupPrivate(KoShapeGroup *q) : KoShapeContainerPrivate(q) { model = new ShapeGroupContainerModel(q); } ~KoShapeGroupPrivate() { } mutable bool sizeCached; }; KoShapeGroup::KoShapeGroup() : KoShapeContainer(*(new KoShapeGroupPrivate(this))) { setSize(QSizeF(0, 0)); } KoShapeGroup::~KoShapeGroup() { } void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) { Q_UNUSED(painter); Q_UNUSED(converter); } bool KoShapeGroup::hitTest(const QPointF &position) const { Q_UNUSED(position); return false; } QSizeF KoShapeGroup::size() const { Q_D(const KoShapeGroup); //debugFlake << "size" << d->size; if (!d->sizeCached) { QRectF bound; foreach(KoShape *shape, shapes()) { if (bound.isEmpty()) bound = shape->transformation().mapRect(shape->outlineRect()); else bound |= shape->transformation().mapRect(shape->outlineRect()); } d->size = bound.size(); d->sizeCached = true; debugFlake << "recalculated size" << d->size; } return d->size; } QRectF KoShapeGroup::boundingRect() const { bool first = true; QRectF groupBound; foreach(KoShape* shape, shapes()) { if (first) { groupBound = shape->boundingRect(); first = false; } else { groupBound = groupBound.united(shape->boundingRect()); } } if (shadow()) { KoInsets insets; shadow()->insets(insets); groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } return groupBound; } void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:g"); saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes); context.xmlWriter().addAttributePt("svg:y", position().y()); QList shapes = this->shapes(); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); foreach(KoShape* shape, shapes) { shape->saveOdf(context); } saveOdfCommonChildElements(context); context.xmlWriter().endElement(); } bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { Q_D(KoShapeGroup); loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes | OdfCommonChildElements); KoXmlElement child; - QMap usedLayers; + QHash usedLayers; forEachElement(child, element) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { KoShapeLayer *layer = dynamic_cast(shape->parent()); if (layer) { usedLayers[layer]++; } addShape(shape); } } KoShapeLayer *parent = 0; int maxUseCount = 0; // find most used layer and use this as parent for the group - for (QMap::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { + for (QHash::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) { if (it.value() > maxUseCount) { maxUseCount = it.value(); parent = it.key(); } } setParent(parent); QRectF bound; bool boundInitialized = false; foreach(KoShape * shape, shapes()) { if (! boundInitialized) { bound = shape->boundingRect(); boundInitialized = true; } else bound = bound.united(shape->boundingRect()); } setSize(bound.size()); d->sizeCached = true; setPosition(bound.topLeft()); foreach(KoShape * shape, shapes()) shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft()); return true; } void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape) { Q_UNUSED(shape); KoShapeContainer::shapeChanged(type, shape); switch (type) { case KoShape::StrokeChanged: { KoShapeStrokeModel *str = stroke(); if (str) { if (str->deref()) delete str; setStroke(0); } break; } default: break; } } void KoShapeGroup::invalidateSizeCache() { Q_D(KoShapeGroup); d->sizeCached = false; } diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp index b43c7016feb..7951349491b 100644 --- a/libs/flake/KoShapeManager.cpp +++ b/libs/flake/KoShapeManager.cpp @@ -1,591 +1,591 @@ /* This file is part of the KDE project Copyright (C) 2006-2008 Thorsten Zachmann Copyright (C) 2006-2010 Thomas Zander Copyright (C) 2009-2010 Jan Hambrecht 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. */ #include "KoShapeManager.h" #include "KoShapeManager_p.h" #include "KoSelection.h" #include "KoToolManager.h" #include "KoPointerEvent.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoCanvasBase.h" #include "KoShapeContainer.h" #include "KoShapeStrokeModel.h" #include "KoShapeGroup.h" #include "KoToolProxy.h" #include "KoShapeManagerPaintingStrategy.h" #include "KoShapeShadow.h" #include "KoShapeLayer.h" #include "KoFilterEffect.h" #include "KoFilterEffectStack.h" #include "KoFilterEffectRenderContext.h" #include "KoShapeBackground.h" #include #include "KoClipPath.h" #include "KoShapePaintingContext.h" #include #include #include void KoShapeManager::Private::updateTree() { // for detecting collisions between shapes. DetectCollision detector; bool selectionModified = false; bool anyModified = false; foreach(KoShape *shape, aggregate4update) { if (shapeIndexesBeforeUpdate.contains(shape)) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); selectionModified = selectionModified || selection->isSelected(shape); anyModified = true; } foreach (KoShape *shape, aggregate4update) { tree.remove(shape); QRectF br(shape->boundingRect()); strategy->adapt(shape, br); tree.insert(br, shape); } // do it again to see which shapes we intersect with _after_ moving. foreach (KoShape *shape, aggregate4update) detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]); aggregate4update.clear(); shapeIndexesBeforeUpdate.clear(); detector.fireSignals(); if (selectionModified) { selection->updateSizeAndPosition(); emit q->selectionContentChanged(); } if (anyModified) { emit q->contentChanged(); } } void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { QList shapes = group->shapes(); qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); foreach(KoShape *child, shapes) { // we paint recursively here, so we do not have to check recursively for visibility if (!child->isVisible()) continue; KoShapeGroup *childGroup = dynamic_cast(child); if (childGroup) { paintGroup(childGroup, painter, converter, paintContext); } else { painter.save(); strategy->paint(child, painter, converter, paintContext); painter.restore(); } } } KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList &shapes) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); setShapes(shapes); } KoShapeManager::KoShapeManager(KoCanvasBase *canvas) : d(new Private(this, canvas)) { Q_ASSERT(d->canvas); // not optional. connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); } KoShapeManager::~KoShapeManager() { foreach(KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } foreach(KoShape *shape, d->additionalShapes) { shape->priv()->removeShapeManager(this); } delete d; } void KoShapeManager::setShapes(const QList &shapes, Repaint repaint) { //clear selection d->selection->deselectAll(); foreach(KoShape *shape, d->shapes) { shape->priv()->removeShapeManager(this); } d->aggregate4update.clear(); d->tree.clear(); d->shapes.clear(); foreach(KoShape *shape, shapes) { addShape(shape, repaint); } } void KoShapeManager::addShape(KoShape *shape, Repaint repaint) { if (d->shapes.contains(shape)) return; shape->priv()->addShapeManager(this); d->shapes.append(shape); if (! dynamic_cast(shape) && ! dynamic_cast(shape)) { QRectF br(shape->boundingRect()); d->tree.insert(br, shape); } if (repaint == PaintShapeOnAdd) { shape->update(); } // add the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { addShape(containerShape, repaint); } } Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); } void KoShapeManager::addAdditional(KoShape *shape) { if (shape) { if (d->additionalShapes.contains(shape)) { return; } shape->priv()->addShapeManager(this); d->additionalShapes.append(shape); } } void KoShapeManager::remove(KoShape *shape) { Private::DetectCollision detector; detector.detect(d->tree, shape, shape->zIndex()); detector.fireSignals(); shape->update(); shape->priv()->removeShapeManager(this); d->selection->deselect(shape); d->aggregate4update.remove(shape); d->tree.remove(shape); d->shapes.removeAll(shape); // remove the children of a KoShapeContainer KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach (KoShape *containerShape, container->shapes()) { remove(containerShape); } } // This signal is used in the annotation shape. // FIXME: Is this really what we want? (and shouldn't it be called shapeDeleted()?) - shapeRemoved(shape); + emit shapeRemoved(shape); } void KoShapeManager::removeAdditional(KoShape *shape) { if (shape) { shape->priv()->removeShapeManager(this); d->additionalShapes.removeAll(shape); } } void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint) { d->updateTree(); painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off. painter.setBrush(Qt::NoBrush); QList unsortedShapes; if (painter.hasClipping()) { QRectF rect = converter.viewToDocument(painter.clipRegion().boundingRect()); unsortedShapes = d->tree.intersects(rect); } else { unsortedShapes = shapes(); warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!"; } // filter all hidden shapes from the list // also filter shapes with a parent which has filter effects applied QList sortedShapes; foreach (KoShape *shape, unsortedShapes) { if (!shape->isVisible(true)) continue; bool addShapeToList = true; // check if one of the shapes ancestors have filter effects KoShapeContainer *parent = shape->parent(); while (parent) { // parent must be part of the shape manager to be taken into account if (!d->shapes.contains(parent)) break; if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) { addShapeToList = false; break; } parent = parent->parent(); } if (addShapeToList) { sortedShapes.append(shape); } else if (parent) { sortedShapes.append(parent); } } qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); foreach (KoShape *shape, sortedShapes) { if (shape->parent() != 0 && shape->parent()->isClipped(shape)) continue; painter.save(); // apply shape clipping KoClipPath::applyClipping(shape, painter, converter); // let the painting strategy paint the shape KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->strategy->paint(shape, painter, converter, paintContext); painter.restore(); } #ifdef CALLIGRA_RTREE_DEBUG // paint tree qreal zx = 0; qreal zy = 0; converter.zoom(&zx, &zy); painter.save(); painter.scale(zx, zy); d->tree.paint(painter); painter.restore(); #endif if (! forPrint) { KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME d->selection->paint(painter, converter, paintContext); } } void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { qreal transparency = shape->transparency(true); if (transparency > 0.0) { painter.setOpacity(1.0-transparency); } if (shape->shadow()) { painter.save(); shape->shadow()->paint(shape, painter, converter); painter.restore(); } if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) { painter.save(); shape->paint(painter, converter, paintContext); painter.restore(); if (shape->stroke()) { painter.save(); shape->stroke()->paint(shape, painter, converter); painter.restore(); } } else { // There are filter effects, then we need to prerender the shape on an image, to filter it QRectF shapeBound(QPointF(), shape->size()); // First step, compute the rectangle used for the image QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound); // convert clip region to view coordinates QRectF zoomedClipRegion = converter.documentToView(clipRegion); // determine the offset of the clipping rect from the shapes origin QPointF clippingOffset = zoomedClipRegion.topLeft(); // Initialize the buffer image QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied); sourceGraphic.fill(qRgba(0,0,0,0)); QHash imageBuffers; QSet requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs(); if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) { // Init the buffer painter QPainter imagePainter(&sourceGraphic); imagePainter.translate(-1.0f*clippingOffset); imagePainter.setPen(Qt::NoPen); imagePainter.setBrush(Qt::NoBrush); imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing)); // Paint the shape on the image KoShapeGroup *group = dynamic_cast(shape); if (group) { // the childrens matrix contains the groups matrix as well // so we have to compensate for that before painting the children imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true); d->paintGroup(group, imagePainter, converter, paintContext); } else { imagePainter.save(); shape->paint(imagePainter, converter, paintContext); imagePainter.restore(); if (shape->stroke()) { imagePainter.save(); shape->stroke()->paint(shape, imagePainter, converter); imagePainter.restore(); } imagePainter.end(); } } if (requiredStdInputs.contains("SourceAlpha")) { QImage sourceAlpha = sourceGraphic; sourceAlpha.fill(qRgba(0,0,0,255)); sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel()); imageBuffers.insert("SourceAlpha", sourceAlpha); } if (requiredStdInputs.contains("FillPaint")) { QImage fillPaint = sourceGraphic; if (shape->background()) { QPainter fillPainter(&fillPaint); QPainterPath fillPath; fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1)); shape->background()->paint(fillPainter, converter, paintContext, fillPath); } else { fillPaint.fill(qRgba(0,0,0,0)); } imageBuffers.insert("FillPaint", fillPaint); } imageBuffers.insert("SourceGraphic", sourceGraphic); imageBuffers.insert(QString(), sourceGraphic); KoFilterEffectRenderContext renderContext(converter); renderContext.setShapeBoundingBox(shapeBound); QImage result; QList filterEffects = shape->filterEffectStack()->filterEffects(); // Filter foreach (KoFilterEffect *filterEffect, filterEffects) { QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound); filterRegion = converter.documentToView(filterRegion); QRect subRegion = filterRegion.translated(-clippingOffset).toRect(); // set current filter region renderContext.setFilterRegion(subRegion & sourceGraphic.rect()); if (filterEffect->maximalInputCount() <= 1) { QList inputs = filterEffect->inputs(); QString input = inputs.count() ? inputs.first() : QString(); // get input image from image buffers and apply the filter effect QImage image = imageBuffers.value(input); if (!image.isNull()) { result = filterEffect->processImage(imageBuffers.value(input), renderContext); } } else { QVector inputImages; foreach(const QString &input, filterEffect->inputs()) { QImage image = imageBuffers.value(input); if (!image.isNull()) inputImages.append(imageBuffers.value(input)); } // apply the filter effect if (filterEffect->inputs().count() == inputImages.count()) result = filterEffect->processImages(inputImages, renderContext); } // store result of effect imageBuffers.insert(filterEffect->output(), result); } KoFilterEffect *lastEffect = filterEffects.last(); // Paint the result painter.save(); painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output())); painter.restore(); } } KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes) { d->updateTree(); QList sortedShapes(d->tree.contains(position)); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); KoShape *firstUnselectedShape = 0; for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible(true)) continue; if (! shape->hitTest(position)) continue; switch (selection) { case KoFlake::ShapeOnTop: if (shape->isSelectable()) return shape; case KoFlake::Selected: if (d->selection->isSelected(shape)) return shape; break; case KoFlake::Unselected: if (! d->selection->isSelected(shape)) return shape; break; case KoFlake::NextUnselected: // we want an unselected shape if (d->selection->isSelected(shape)) continue; // memorize the first unselected shape if (! firstUnselectedShape) firstUnselectedShape = shape; // check if the shape above is selected if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1))) return shape; break; } } // if we want the next unselected below a selected but there was none selected, // return the first found unselected shape if (selection == KoFlake::NextUnselected && firstUnselectedShape) return firstUnselectedShape; if (d->selection->hitTest(position)) return d->selection; return 0; // missed everything } QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes) { d->updateTree(); QList intersectedShapes(d->tree.intersects(rect)); for (int count = intersectedShapes.count() - 1; count >= 0; count--) { KoShape *shape = intersectedShapes.at(count); if (omitHiddenShapes && ! shape->isVisible(true)) { intersectedShapes.removeAt(count); } else { const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline()); if (! outline.intersects(rect) && ! outline.contains(rect)) { intersectedShapes.removeAt(count); } } } return intersectedShapes; } void KoShapeManager::update(QRectF &rect, const KoShape *shape, bool selectionHandles) { d->canvas->updateCanvas(rect); if (selectionHandles && d->selection->isSelected(shape)) { if (d->canvas->toolProxy()) d->canvas->toolProxy()->repaintDecorations(); } } void KoShapeManager::notifyShapeChanged(KoShape *shape) { Q_ASSERT(shape); if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) { return; } const bool wasEmpty = d->aggregate4update.isEmpty(); d->aggregate4update.insert(shape); d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex()); KoShapeContainer *container = dynamic_cast(shape); if (container) { foreach(KoShape *child, container->shapes()) notifyShapeChanged(child); } if (wasEmpty && !d->aggregate4update.isEmpty()) QTimer::singleShot(100, this, SLOT(updateTree())); emit shapeChanged(shape); } QList KoShapeManager::shapes() const { return d->shapes; } QList KoShapeManager::topLevelShapes() const { QList shapes; // get all toplevel shapes foreach(KoShape *shape, d->shapes) { if (shape->parent() == 0) { shapes.append(shape); } } return shapes; } KoSelection *KoShapeManager::selection() const { return d->selection; } void KoShapeManager::suggestChangeTool(KoPointerEvent *event) { QList shapes; KoShape *clicked = shapeAt(event->point); if (clicked) { if (! selection()->isSelected(clicked)) { selection()->deselectAll(); selection()->select(clicked); } shapes.append(clicked); } QList shapes2; foreach (KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes2.append(shape); } else { foreach (KoShape *delegatedShape, delegates) { shapes2.append(delegatedShape); } } } KoToolManager::instance()->switchToolRequested( KoToolManager::instance()->preferredToolForSelection(shapes2)); } void KoShapeManager::setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy) { delete d->strategy; d->strategy = strategy; } KoCanvasBase *KoShapeManager::canvas() { return d->canvas; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoShapeManager.cpp" diff --git a/libs/flake/KoShapeRegistry.cpp b/libs/flake/KoShapeRegistry.cpp index 3b771f84a8d..fe1f647f38c 100644 --- a/libs/flake/KoShapeRegistry.cpp +++ b/libs/flake/KoShapeRegistry.cpp @@ -1,346 +1,346 @@ /* This file is part of the KDE project * Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org) * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2006,2008-2010 Thorsten Zachmann * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 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 "KoShapeRegistry.h" #include "KoPathShapeFactory.h" #include "KoConnectionShapeFactory.h" #include "KoShapeLoadingContext.h" #include "KoShapeSavingContext.h" #include "KoShapeGroup.h" #include "KoShapeLayer.h" #include "KoUnavailShape.h" #include "SvgShapeFactory.h" #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoShapeRegistry, s_instance) class Q_DECL_HIDDEN KoShapeRegistry::Private { public: void insertFactory(KoShapeFactoryBase *factory); void init(KoShapeRegistry *q); KoShape *createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const; // Map namespace,tagname to priority:factory QHash, QMultiMap > factoryMap; }; KoShapeRegistry::KoShapeRegistry() : d(new Private()) { } KoShapeRegistry::~KoShapeRegistry() { qDeleteAll(doubleEntries()); qDeleteAll(values()); delete d; } void KoShapeRegistry::Private::init(KoShapeRegistry *q) { KoPluginLoader::PluginsConfig config; config.whiteList = "FlakePlugins"; config.blacklist = "FlakePluginsDisabled"; config.group = "calligra"; KoPluginLoader::load(QStringLiteral("calligra/flakes"), config); config.whiteList = "ShapePlugins"; config.blacklist = "ShapePluginsDisabled"; KoPluginLoader::load(QStringLiteral("calligra/shapes"), config); // Also add our hard-coded basic shapes q->add(new KoPathShapeFactory(QStringList())); q->add(new KoConnectionShapeFactory()); // As long as there is no shape dealing with embedded svg images // we add the svg shape factory here by default q->add(new SvgShapeFactory); // Now all shape factories are registered with us, determine their // assocated odf tagname & priority and prepare ourselves for // loading ODF. QList factories = q->values(); for (int i = 0; i < factories.size(); ++i) { insertFactory(factories[i]); } } KoShapeRegistry* KoShapeRegistry::instance() { if (!s_instance.exists()) { s_instance->d->init(s_instance); } return s_instance; } void KoShapeRegistry::addFactory(KoShapeFactoryBase * factory) { add(factory); d->insertFactory(factory); } void KoShapeRegistry::Private::insertFactory(KoShapeFactoryBase *factory) { const QList > odfElements(factory->odfElements()); if (odfElements.isEmpty()) { debugFlake << "Shape factory" << factory->id() << " does not have OdfNamespace defined, ignoring"; } else { int priority = factory->loadingPriority(); for (QList >::const_iterator it(odfElements.begin()); it != odfElements.end(); ++it) { foreach (const QString &elementName, (*it).second) { QPair p((*it).first, elementName); QMultiMap & priorityMap = factoryMap[p]; priorityMap.insert(priority, factory); debugFlake << "Inserting factory" << factory->id() << " for" << p << " with priority " << priority << " into factoryMap making " << priorityMap.size() << " entries. "; } } } } KoShape * KoShapeRegistry::createShapeFromOdf(const KoXmlElement & e, KoShapeLoadingContext & context) const { debugFlake << "Going to check for" << e.namespaceURI() << ":" << e.tagName(); KoShape * shape = 0; // Handle the case where the element is a draw:frame differently from other cases. if (e.tagName() == "frame" && e.namespaceURI() == KoXmlNS::draw) { // If the element is in a frame, the frame is already added by the // application and we only want to create a shape from the // embedded element. The very first shape we create is accepted. // // FIXME: we might want to have some code to determine which is // the "best" of the creatable shapes. // The logic is thus: // First attempt to check whether we can in fact load the first child, // and only use a Shape if the first child is accepted. If this is not the case, then // use the UnavailShape which ensures data integrity and that the fallback views are not // edited and subsequently saved back (as they would then no longer be a true // representation of the data they are supposed to be views of). // The reason is that all subsequent children will be fallbacks, in order of preference. if (e.hasChildNodes()) { // if we don't ignore white spaces it can be that the first child is not a element so look for the first element KoXmlNode node = e.firstChild(); KoXmlElement element; while (!node.isNull() && element.isNull()) { element = node.toElement(); node = node.nextSibling(); } if (!element.isNull()) { // Check for draw:object if (element.tagName() == "object" && element.namespaceURI() == KoXmlNS::draw && element.hasChildNodes()) { // Loop through the elements and find the first one // that is handled by any shape. KoXmlNode n = element.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { debugFlake << "trying for element " << n.toElement().tagName(); shape = d->createShapeInternal(e, context, n.toElement()); break; } } if (shape) debugFlake << "Found a shape for draw:object"; else debugFlake << "Found NO shape shape for draw:object"; } else { // If not draw:object, e.g draw:image or draw:plugin shape = d->createShapeInternal(e, context, element); } } if (shape) { debugFlake << "A shape supporting the requested type was found."; } else { // If none of the registered shapes could handle the frame // contents, create an UnavailShape. This should never fail. debugFlake << "No shape found; Creating an unavail shape"; KoUnavailShape *uShape = new KoUnavailShape(); uShape->setShapeId(KoUnavailShape_SHAPEID); //FIXME: Add creating/setting the collection here(?) uShape->loadOdf(e, context); // Check whether we can load a shape to fit the current object. KoXmlElement child; KoShape *childShape = 0; bool first = true; forEachElement(child, e) { // no need to try to load the first element again as it was already tried before and we could not load it if (first) { first = false; continue; } debugFlake << "--------------------------------------------------------"; debugFlake << "Attempting to check if we can fall back ability to the item" << child.nodeName(); childShape = d->createShapeInternal(e, context, child); if (childShape) { debugFlake << "Shape was found! Adding as child of unavail shape and stopping search"; uShape->addShape(childShape); childShape->setPosition(QPointF(qreal(0.0), qreal(0.0))); // The embedded shape is just there to show the preview image. // We don't want the user to be able to manipulate the picture // in any way, so we disable the tools of the shape. This can // be done in a hacky way (courtesy of Jaham) by setting its // shapeID to "". childShape->setShapeId(""); break; } } if (!childShape) debugFlake << "Failed to find fallback for the unavail shape named " << e.tagName(); shape = uShape; } } } // Hardwire the group shape into the loading as it should not appear // in the shape selector else if (e.localName() == "g" && e.namespaceURI() == KoXmlNS::draw) { KoShapeGroup * group = new KoShapeGroup(); context.odfLoadingContext().styleStack().save(); bool loaded = group->loadOdf(e, context); context.odfLoadingContext().styleStack().restore(); if (loaded) { shape = group; } else { delete group; } } else { shape = d->createShapeInternal(e, context, e); } if (shape) { context.shapeLoaded(shape); } return shape; } KoShape *KoShapeRegistry::Private::createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const { // Pair of namespace, tagname QPair p = QPair(element.namespaceURI(), element.tagName()); // Remove duplicate lookup. if (!factoryMap.contains(p)) return 0; QMultiMap priorityMap = factoryMap.value(p); QList factories = priorityMap.values(); #ifndef NDEBUG debugFlake << "Supported factories for=" << p; foreach (KoShapeFactoryBase *f, factories) debugFlake << f->id() << f->name(); #endif // Loop through all shape factories. If any of them supports this // element, then we let the factory create a shape from it. This // may fail because the element itself is too generic to draw any // real conclusions from it - we actually have to try to load it. // An example of this is the draw:image element which have // potentially hundreds of different image formats to support, // including vector formats. // // If it succeeds, then we use this shape, if it fails, then just // try the next. // // Higher numbers are more specific, map is sorted by keys. for (int i = factories.size() - 1; i >= 0; --i) { KoShapeFactoryBase * factory = factories[i]; if (factory->supports(element, context)) { KoShape *shape = factory->createShapeFromOdf(fullElement, context); if (shape) { debugFlake << "Shape found for factory " << factory->id() << factory->name(); // we return the top-level most shape as thats the one that we'll have to // add to the KoShapeManager for painting later (and also to avoid memory leaks) // but don't go past a KoShapeLayer as KoShape adds those from the context // during loading and those are already added. while (shape->parent() && dynamic_cast(shape->parent()) == 0) shape = shape->parent(); return shape; } // Maybe a shape with a lower priority can load our // element, but this attempt has failed. } else { debugFlake << "No support for" << p << "by" << factory->id(); } } return 0; } QList KoShapeRegistry::factoriesForElement(const QString &nameSpace, const QString &elementName) { // Pair of namespace, tagname QPair p = QPair(nameSpace, elementName); QMultiMap priorityMap = d->factoryMap.value(p); QList shapeFactories; // sort list by priority - foreach(KoShapeFactoryBase *f, priorityMap.values()) { + foreach(KoShapeFactoryBase *f, priorityMap) { shapeFactories.prepend(f); } return shapeFactories; } diff --git a/libs/flake/KoToolBase.cpp b/libs/flake/KoToolBase.cpp index e50d22ac59d..cb4795d19c7 100644 --- a/libs/flake/KoToolBase.cpp +++ b/libs/flake/KoToolBase.cpp @@ -1,420 +1,420 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2010 Thomas Zander * Copyright (C) 2011 Jan Hambrecht * * 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. */ #include "KoToolBase.h" #include "KoToolBase_p.h" #include "KoCanvasBase.h" #include "KoPointerEvent.h" #include "KoDocumentResourceManager.h" #include "KoCanvasResourceManager.h" #include "KoViewConverter.h" #include "KoShapeController.h" #include "KoShapeBasedDocumentBase.h" #include "KoToolSelection.h" #include #include #include #include #include #include #include KoToolBase::KoToolBase(KoCanvasBase *canvas) : d_ptr(new KoToolBasePrivate(this, canvas)) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::KoToolBase(KoToolBasePrivate &dd) : d_ptr(&dd) { Q_D(KoToolBase); d->connectSignals(); } KoToolBase::~KoToolBase() { - Q_D(const KoToolBase); + //Q_D(const KoToolBase); // Enable this to easily generate action files for tools // if (d->actionCollection.size() > 0) { // QDomDocument doc; // QDomElement e = doc.createElement("Actions"); // e.setAttribute("name", toolId()); // e.setAttribute("version", "1"); // doc.appendChild(e); // foreach(QAction *ac, d->actionCollection.values()) { // QAction *action = qobject_cast(ac); // if (action) { // QDomElement a = doc.createElement("Action"); // a.setAttribute("name", action->objectName()); // a.setAttribute("icon", action->icon().name()); // a.setAttribute("text" , action->text()); // a.setAttribute("whatsThis" , action->whatsThis()); // a.setAttribute("toolTip" , action->toolTip()); // a.setAttribute("iconText" , action->iconText()); // a.setAttribute("shortcut" , action->shortcut(QAction::ActiveShortcut).toString()); // a.setAttribute("defaultShortcut" , action->shortcut(QAction::DefaultShortcut).toString()); // a.setAttribute("isCheckable" , QString((action->isChecked() ? "true" : "false"))); // a.setAttribute("statusTip", action->statusTip()); // e.appendChild(a); // } // else { // qDebug() << "Got a QAction:" << ac->objectName(); // } // } // QFile f(toolId() + ".action"); // f.open(QFile::WriteOnly); // f.write(doc.toString().toUtf8()); // f.close(); // } // else { // qDebug() << "Tool" << toolId() << "has no actions"; // } qDeleteAll(d_ptr->optionWidgets); delete d_ptr; } /// Ultimately only called from Calligra Sheets void KoToolBase::updateShapeController(KoShapeBasedDocumentBase *shapeController) { if (shapeController) { KoDocumentResourceManager *scrm = shapeController->resourceManager(); if (scrm) { connect(scrm, SIGNAL(resourceChanged(int,QVariant)), this, SLOT(documentResourceChanged(int,QVariant))); } } } void KoToolBase::deactivate() { } void KoToolBase::canvasResourceChanged(int key, const QVariant & res) { Q_UNUSED(key); Q_UNUSED(res); } void KoToolBase::documentResourceChanged(int key, const QVariant &res) { Q_UNUSED(key); Q_UNUSED(res); } bool KoToolBase::wantsAutoScroll() const { return true; } void KoToolBase::mouseDoubleClickEvent(KoPointerEvent *event) { event->ignore(); } void KoToolBase::mouseTripleClickEvent(KoPointerEvent *event) { event->ignore(); } void KoToolBase::shortcutOverrideEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::keyPressEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::keyReleaseEvent(QKeyEvent *e) { e->ignore(); } void KoToolBase::wheelEvent(KoPointerEvent * e) { e->ignore(); } void KoToolBase::touchEvent(QTouchEvent *event) { event->ignore(); } QVariant KoToolBase::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &) const { Q_D(const KoToolBase); if (d->canvas->canvasWidget() == 0) return QVariant(); switch (query) { case Qt::ImMicroFocus: return QRect(d->canvas->canvasWidget()->width() / 2, 0, 1, d->canvas->canvasWidget()->height()); case Qt::ImFont: return d->canvas->canvasWidget()->font(); default: return QVariant(); } } void KoToolBase::inputMethodEvent(QInputMethodEvent * event) { if (! event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); } event->accept(); } void KoToolBase::customPressEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customReleaseEvent(KoPointerEvent * event) { event->ignore(); } void KoToolBase::customMoveEvent(KoPointerEvent * event) { event->ignore(); } bool KoToolBase::wantsTouch() const { return false; } void KoToolBase::useCursor(const QCursor &cursor) { Q_D(KoToolBase); d->currentCursor = cursor; emit cursorChanged(d->currentCursor); } QList > KoToolBase::optionWidgets() { Q_D(KoToolBase); if (d->optionWidgets.empty()) { d->optionWidgets = createOptionWidgets(); } return d->optionWidgets; } void KoToolBase::addAction(const QString &name, QAction *action) { Q_D(KoToolBase); if (action->objectName().isEmpty()) { action->setObjectName(name); } d->actionCollection.insert(name, action); } QHash KoToolBase::actions() const { Q_D(const KoToolBase); return d->actionCollection; } QAction *KoToolBase::action(const QString &name) const { Q_D(const KoToolBase); return d->actionCollection.value(name); } QWidget * KoToolBase::createOptionWidget() { return 0; } QList > KoToolBase::createOptionWidgets() { QList > ow; if (QWidget *widget = createOptionWidget()) { if (widget->objectName().isEmpty()) { widget->setObjectName(toolId()); } ow.append(widget); } return ow; } void KoToolBase::setToolId(const QString &id) { Q_D(KoToolBase); d->toolId = id; } QString KoToolBase::toolId() const { Q_D(const KoToolBase); return d->toolId; } QCursor KoToolBase::cursor() const { Q_D(const KoToolBase); return d->currentCursor; } void KoToolBase::deleteSelection() { } void KoToolBase::cut() { copy(); deleteSelection(); } QList KoToolBase::popupActionList() const { Q_D(const KoToolBase); return d->popupActionList; } void KoToolBase::setPopupActionList(const QList &list) { Q_D(KoToolBase); d->popupActionList = list; } KoCanvasBase * KoToolBase::canvas() const { Q_D(const KoToolBase); return d->canvas; } void KoToolBase::setStatusText(const QString &statusText) { emit statusTextChanged(statusText); } uint KoToolBase::handleRadius() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->handleRadius(); } else { return 3; } } uint KoToolBase::grabSensitivity() const { Q_D(const KoToolBase); if(d->canvas->shapeController()->resourceManager()) { return d->canvas->shapeController()->resourceManager()->grabSensitivity(); } else { return 3; } } QRectF KoToolBase::handleGrabRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*grabSensitivity(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } QRectF KoToolBase::handlePaintRect(const QPointF &position) const { Q_D(const KoToolBase); const KoViewConverter * converter = d->canvas->viewConverter(); uint handleSize = 2*handleRadius(); QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize)); r.moveCenter(position); return r; } void KoToolBase::setTextMode(bool value) { Q_D(KoToolBase); d->isInTextMode=value; } QStringList KoToolBase::supportedPasteMimeTypes() const { return QStringList(); } bool KoToolBase::paste() { return false; } void KoToolBase::copy() const { } void KoToolBase::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } void KoToolBase::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); } void KoToolBase::dropEvent(QDropEvent *event, const QPointF &point) { Q_UNUSED(event); Q_UNUSED(point); } bool KoToolBase::hasSelection() { KoToolSelection *sel = selection(); return (sel && sel->hasSelection()); } KoToolSelection *KoToolBase::selection() { return 0; } void KoToolBase::repaintDecorations() { } bool KoToolBase::isInTextMode() const { Q_D(const KoToolBase); return d->isInTextMode; } diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp index d135af0921b..dcffde6d4d7 100644 --- a/libs/flake/KoToolManager.cpp +++ b/libs/flake/KoToolManager.cpp @@ -1,1129 +1,1129 @@ /* This file is part of the KDE project * * Copyright (c) 2005-2010 Boudewijn Rempt * Copyright (C) 2006-2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * Copyright (C) 2008 Jan Hambrecht * * 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. */ // flake #include "KoToolManager.h" #include "KoToolManager_p.h" #include "KoToolRegistry.h" #include "KoToolProxy.h" #include "KoToolProxy_p.h" #include "KoSelection.h" #include "KoCanvasController.h" #include "KoCanvasControllerWidget.h" #include "KoShape.h" #include "KoShapeLayer.h" #include "KoShapeRegistry.h" #include "KoShapeManager.h" #include "KoCanvasBase.h" #include "KoInputDeviceHandlerRegistry.h" #include "KoInputDeviceHandlerEvent.h" #include "KoPointerEvent.h" #include "tools/KoCreateShapesTool.h" #include "tools/KoZoomTool.h" #include "tools/KoPanTool.h" #include "FlakeDebug.h" // KF5 #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KoToolManager, s_instance) class Q_DECL_HIDDEN KoToolAction::Private { public: ToolHelper* toolHelper; }; KoToolAction::KoToolAction(ToolHelper* toolHelper) : QObject(toolHelper) , d(new Private) { d->toolHelper = toolHelper; } KoToolAction::~KoToolAction() { delete d; } void KoToolAction::trigger() { d->toolHelper->activate(); } QString KoToolAction::iconText() const { return d->toolHelper->iconText(); } QString KoToolAction::toolTip() const { return d->toolHelper->toolTip(); } QString KoToolAction::id() const { return d->toolHelper->id(); } QString KoToolAction::iconName() const { return d->toolHelper->iconName(); } QKeySequence KoToolAction::shortcut() const { return d->toolHelper->shortcut(); } QString KoToolAction::section() const { return d->toolHelper->toolType(); } int KoToolAction::priority() const { return d->toolHelper->priority(); } int KoToolAction::buttonGroupId() const { return d->toolHelper->uniqueId(); } QString KoToolAction::visibilityCode() const { return d->toolHelper->activationShapeId(); } class CanvasData { public: CanvasData(KoCanvasController *cc, const KoInputDevice &id) : activeTool(0), canvas(cc), inputDevice(id), dummyToolWidget(0), dummyToolLabel(0) { } ~CanvasData() { // the dummy tool widget does not necessarily have a parent and we create it, so we delete it. delete dummyToolWidget; } void activateToolActions() { disabledDisabledActions.clear(); disabledActions.clear(); disabledCanvasShortcuts.clear(); // we do several things here // 1. enable the actions of the active tool // 2. disable conflicting actions // 3. replace conflicting actions in the action collection KActionCollection *canvasActionCollection = canvas->actionCollection(); QHash toolActions = activeTool->actions(); QHash::const_iterator it(toolActions.constBegin()); for (; it != toolActions.constEnd(); ++it) { if (canvasActionCollection) { QString toolActionID = it.key(); QAction *toolAction = it.value(); QAction * action = qobject_cast(canvasActionCollection->action(it.key())); if (action) { canvasActionCollection->takeAction(action); if (action != it.value()) { if (action->isEnabled()) { action->setEnabled(false); disabledActions.append(action); } else { disabledDisabledActions.append(action); } } } foreach(QAction *a, canvasActionCollection->actions()) { QAction *canvasAction = dynamic_cast(a); if (canvasAction && !canvasAction->shortcut().toString().isEmpty() && canvasAction->shortcut() == toolAction->shortcut()) { warnFlake << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString(); disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString(); canvasAction->setShortcut(QKeySequence()); } } canvasActionCollection->addAction(toolActionID, toolAction); } it.value()->setEnabled(true); } canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime. } void deactivateToolActions() { if (!activeTool) return; // disable actions of active tool foreach(QAction *action, activeTool->actions()) { action->setEnabled(false); } // enable actions which where disabled on activating the active tool // and re-add them to the action collection KActionCollection *ac = canvas->actionCollection(); foreach(QPointer action, disabledDisabledActions) { if (action) { if (ac) { ac->addAction(action->objectName(), action); } } } disabledDisabledActions.clear(); foreach(QPointer action, disabledActions) { if (action) { action->setEnabled(true); if(ac) { ac->addAction(action->objectName(), action); } } } disabledActions.clear(); QMap, QString>::const_iterator it(disabledCanvasShortcuts.constBegin()); for (; it != disabledCanvasShortcuts.constEnd(); ++it) { QAction *action = it.key(); QString shortcut = it.value(); action->setShortcut(shortcut); } disabledCanvasShortcuts.clear(); } KoToolBase *activeTool; // active Tool QString activeToolId; // the id of the active Tool QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to. QHash allTools; // all the tools that are created for this canvas. QStack stack; // stack of temporary tools KoCanvasController *const canvas; const KoInputDevice inputDevice; QWidget *dummyToolWidget; // the widget shown in the toolDocker. QLabel *dummyToolLabel; QList > disabledActions; ///< disabled conflicting actions QList > disabledDisabledActions; ///< disabled conflicting actions that were already disabled QMap, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides }; KoToolManager::Private::Private(KoToolManager *qq) : q(qq), canvasData(0), layerExplicitlyDisabled(false) { } KoToolManager::Private::~Private() { qDeleteAll(tools); } // helper method. CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device) { QHash toolsHash; foreach(ToolHelper *tool, tools) { QPair toolPair = q->createTools(controller, tool); if (toolPair.second) { // only if a real tool was created toolsHash.insert(toolPair.first, toolPair.second); } } KoCreateShapesTool *createShapesTool = dynamic_cast(toolsHash.value(KoCreateShapesTool_ID)); Q_ASSERT(createShapesTool); QString id = KoShapeRegistry::instance()->keys()[0]; createShapesTool->setShapeId(id); CanvasData *cd = new CanvasData(controller, device); cd->allTools = toolsHash; return cd; } void KoToolManager::Private::setup() { if (tools.size() > 0) return; KoShapeRegistry::instance(); KoToolRegistry *registry = KoToolRegistry::instance(); foreach(const QString & id, registry->keys()) { ToolHelper *t = new ToolHelper(registry->value(id)); tools.append(t); } // connect to all tools so we can hear their button-clicks foreach(ToolHelper *tool, tools) connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*))); // load pluggable input devices KoInputDeviceHandlerRegistry::instance(); } void KoToolManager::Private::connectActiveTool() { if (canvasData->activeTool) { connect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)), q, SLOT(updateCursor(QCursor))); connect(canvasData->activeTool, SIGNAL(activateTool(QString)), q, SLOT(switchToolRequested(QString))); connect(canvasData->activeTool, SIGNAL(activateTemporary(QString)), q, SLOT(switchToolTemporaryRequested(QString))); connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); connect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)), q, SIGNAL(changedStatusText(QString))); } // we expect the tool to emit a cursor on activation. updateCursor(Qt::ForbiddenCursor); } void KoToolManager::Private::disconnectActiveTool() { if (canvasData->activeTool) { canvasData->deactivateToolActions(); // repaint the decorations before we deactivate the tool as it might deleted // data needed for the repaint canvasData->activeTool->deactivate(); disconnect(canvasData->activeTool, SIGNAL(cursorChanged(QCursor)), q, SLOT(updateCursor(QCursor))); disconnect(canvasData->activeTool, SIGNAL(activateTool(QString)), q, SLOT(switchToolRequested(QString))); disconnect(canvasData->activeTool, SIGNAL(activateTemporary(QString)), q, SLOT(switchToolTemporaryRequested(QString))); disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested())); disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(QString)), q, SIGNAL(changedStatusText(QString))); } // emit a empty status text to clear status text from last active tool emit q->changedStatusText(QString()); } void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary) { Q_ASSERT(tool); if (canvasData == 0) return; if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID) return; disconnectActiveTool(); canvasData->activeTool = tool; connectActiveTool(); postSwitchTool(temporary); } void KoToolManager::Private::switchTool(const QString &id, bool temporary) { Q_ASSERT(canvasData); if (!canvasData) return; if (canvasData->activeTool && temporary) canvasData->stack.push(canvasData->activeToolId); canvasData->activeToolId = id; KoToolBase *tool = canvasData->allTools.value(id); if (! tool) { return; } foreach(ToolHelper *th, tools) { if (th->id() == id) { canvasData->activationShapeId = th->activationShapeId(); break; } } switchTool(tool, temporary); } void KoToolManager::Private::postSwitchTool(bool temporary) { #ifndef NDEBUG int canvasCount = 1; foreach(QList list, canvasses) { bool first = true; foreach(CanvasData *data, list) { if (first) { debugFlake << "Canvas" << canvasCount++; } debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : ""); first = false; } } #endif Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase::ToolActivation toolActivation; if (temporary) toolActivation = KoToolBase::TemporaryActivation; else toolActivation = KoToolBase::DefaultActivation; QSet shapesToOperateOn; if (canvasData->activeTool && canvasData->activeTool->canvas() && canvasData->activeTool->canvas()->shapeManager()) { KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection(); Q_ASSERT(selection); foreach(KoShape *shape, selection->selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { // no delegates, just the orig shape shapesToOperateOn << shape; } else { shapesToOperateOn += delegates; } } } if (canvasData->canvas->canvas()) { // Caller of postSwitchTool expect this to be called to update the selected tool updateToolForProxy(); canvasData->activeTool->activate(toolActivation, shapesToOperateOn); KoCanvasBase *canvas = canvasData->canvas->canvas(); canvas->updateInputMethodInfo(); } else { canvasData->activeTool->activate(toolActivation, shapesToOperateOn); } QList > optionWidgetList = canvasData->activeTool->optionWidgets(); if (optionWidgetList.empty()) { // no option widget. QWidget *toolWidget; QString title; foreach(ToolHelper *tool, tools) { if (tool->id() == canvasData->activeTool->toolId()) { title = tool->toolTip(); break; } } toolWidget = canvasData->dummyToolWidget; if (toolWidget == 0) { toolWidget = new QWidget(); toolWidget->setObjectName("DummyToolWidget"); QVBoxLayout *layout = new QVBoxLayout(toolWidget); layout->setMargin(3); canvasData->dummyToolLabel = new QLabel(toolWidget); layout->addWidget(canvasData->dummyToolLabel); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding)); toolWidget->setLayout(layout); canvasData->dummyToolWidget = toolWidget; } canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title)); optionWidgetList.append(toolWidget); } // Activate the actions for the currently active tool canvasData->activateToolActions(); emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool)); KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (canvasControllerWidget) { canvasControllerWidget->setToolOptionWidgets(optionWidgetList); } } void KoToolManager::Private::switchCanvasData(CanvasData *cd) { Q_ASSERT(cd); KoCanvasBase *oldCanvas = 0; KoInputDevice oldInputDevice; if (canvasData) { oldCanvas = canvasData->canvas->canvas(); oldInputDevice = canvasData->inputDevice; if (canvasData->activeTool) { disconnectActiveTool(); } KoToolProxy *proxy = proxies.value(oldCanvas); Q_ASSERT(proxy); proxy->setActiveTool(0); } canvasData = cd; inputDevice = canvasData->inputDevice; if (canvasData->activeTool) { connectActiveTool(); postSwitchTool(false); } if (oldInputDevice != canvasData->inputDevice) { emit q->inputDeviceChanged(canvasData->inputDevice); } if (oldCanvas != canvasData->canvas->canvas()) { emit q->changedCanvas(canvasData->canvas->canvas()); } } void KoToolManager::Private::toolActivated(ToolHelper *tool) { Q_ASSERT(tool); Q_ASSERT(canvasData); if (!canvasData) return; KoToolBase *t = canvasData->allTools.value(tool->id()); Q_ASSERT(t); canvasData->activeToolId = tool->id(); canvasData->activationShapeId = tool->activationShapeId(); switchTool(t, false); } void KoToolManager::Private::detachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); // check if we are removing the active canvas controller if (canvasData && canvasData->canvas == controller) { KoCanvasController *newCanvas = 0; // try to find another canvas controller beside the one we are removing foreach(KoCanvasController* canvas, canvasses.keys()) { if (canvas != controller) { // yay found one newCanvas = canvas; break; } } if (newCanvas) { switchCanvasData(canvasses.value(newCanvas).first()); } else { KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (canvasControllerWidget) { canvasControllerWidget->setToolOptionWidgets(QList >()); } // as a last resort just set a blank one canvasData = 0; } } KoToolProxy *proxy = proxies.value(controller->canvas()); if (proxy) proxy->setActiveTool(0); QList tools; foreach(CanvasData *canvasData, canvasses.value(controller)) { foreach(KoToolBase *tool, canvasData->allTools) { if (! tools.contains(tool)) { tools.append(tool); } } delete canvasData; } foreach(KoToolBase *tool, tools) { uniqueToolIds.remove(tool); delete tool; } canvasses.remove(controller); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::attachCanvas(KoCanvasController *controller) { Q_ASSERT(controller); CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse()); // switch to new canvas as the active one. switchCanvasData(cd); inputDevice = cd->inputDevice; QList canvasses_; canvasses_.append(cd); canvasses[controller] = canvasses_; KoToolProxy *tp = proxies[controller->canvas()]; if (tp) tp->priv()->setCanvasController(controller); if (cd->activeTool == 0) { // no active tool, so we activate the highest priority main tool int highestPriority = INT_MAX; ToolHelper * helper = 0; foreach(ToolHelper * th, tools) { if (th->toolType() == KoToolFactoryBase::mainToolType()) { if (th->priority() < highestPriority) { highestPriority = qMin(highestPriority, th->priority()); helper = th; } } } if (helper) toolActivated(helper); } Connector *connector = new Connector(controller->canvas()->shapeManager()); connect(connector, SIGNAL(selectionChanged(QList)), q, SLOT(selectionChanged(QList))); connect(controller->canvas()->shapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), q, SLOT(currentLayerChanged(const KoShapeLayer*))); emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0); } void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to) { Q_UNUSED(from); // no canvas anyway or no focus set anyway? if (!canvasData || to == 0) { return; } // Check if this app is about QWidget-based KoCanvasControllerWidget canvasses // XXX: Focus handling for non-qwidget based canvases! KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas); if (!canvasControllerWidget) { return; } // canvasWidget is set as focusproxy for KoCanvasControllerWidget, // so all focus checks are to be done against canvasWidget objects // focus returned to current canvas? if (to == canvasData->canvas->canvas()->canvasWidget()) { // nothing to do return; } // if the 'to' is one of our canvasWidgets, then switch. // for code simplicity the current canvas will be checked again, // but would have been caught already in the lines above, so no issue KoCanvasController *newCanvas = 0; foreach(KoCanvasController* canvas, canvasses.keys()) { if (canvas->canvas()->canvasWidget() == to) { newCanvas = canvas; break; } } // none of our canvasWidgets got focus? if (newCanvas == 0) { return; } // switch to canvasdata matching inputdevice used last with this app instance foreach(CanvasData *data, canvasses.value(newCanvas)) { if (data->inputDevice == inputDevice) { switchCanvasData(data); return; } } // if no such inputDevice for this canvas, then simply fallback to first one switchCanvasData(canvasses.value(newCanvas).first()); } void KoToolManager::Private::updateCursor(const QCursor &cursor) { Q_ASSERT(canvasData); Q_ASSERT(canvasData->canvas); Q_ASSERT(canvasData->canvas->canvas()); canvasData->canvas->canvas()->setCursor(cursor); } void KoToolManager::Private::selectionChanged(const QList &shapes) { QList types; foreach(KoShape *shape, shapes) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { // no delegates, just the orig shape delegates << shape; } foreach (KoShape *shape2, delegates) { Q_ASSERT(shape2); if (! types.contains(shape2->shapeId())) { types.append(shape2->shapeId()); } } } // check if there is still a shape selected the active tool can work on // there needs to be at least one shape for a tool without an activationShapeId // to work // if not change the current tool to the default tool if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0) && canvasData->activationShapeId != "flake/always" && canvasData->activationShapeId != "flake/edit") { bool currentToolWorks = false; foreach (const QString &type, types) { if (canvasData->activationShapeId.split(',').contains(type)) { currentToolWorks = true; break; } } if (!currentToolWorks) { switchTool(KoInteractionTool_ID, false); } } emit q->toolCodesSelected(types); } void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer) { emit q->currentLayerChanged(canvasData->canvas, layer); layerExplicitlyDisabled = layer && !layer->isEditable(); updateToolForProxy(); debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled; } void KoToolManager::Private::updateToolForProxy() { KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas()); if(!proxy) return; bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always")); proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0); } void KoToolManager::Private::switchInputDevice(const KoInputDevice &device) { Q_ASSERT(canvasData); if (!canvasData) return; if (inputDevice == device) return; if (inputDevice.isMouse() && device.isMouse()) return; if (device.isMouse() && !inputDevice.isMouse()) { // we never switch back to mouse from a tablet input device, so the user can use the // mouse to edit the settings for a tool activated by a tablet. See bugs // https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501. // We do continue to switch between tablet devices, thought. return; } QList items = canvasses[canvasData->canvas]; // disable all actions for all tools in the all canvasdata objects for this canvas. foreach(CanvasData *cd, items) { foreach(KoToolBase* tool, cd->allTools) { foreach(QAction * action, tool->actions()) { action->setEnabled(false); } } } // search for a canvasdata object for the current input device foreach(CanvasData *cd, items) { if (cd->inputDevice == device) { switchCanvasData(cd); if (!canvasData->activeTool) { switchTool(KoInteractionTool_ID, false); } return; } } // still here? That means we need to create a new CanvasData instance with the current InputDevice. CanvasData *cd = createCanvasData(canvasData->canvas, device); // switch to new canvas as the active one. QString oldTool = canvasData->activeToolId; items.append(cd); canvasses[cd->canvas] = items; switchCanvasData(cd); q->switchToolRequested(oldTool); } void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas) { proxies.insert(canvas, proxy); foreach(KoCanvasController *controller, canvasses.keys()) { if (controller->canvas() == canvas) { proxy->priv()->setCanvasController(controller); break; } } } void KoToolManager::Private::switchToolByShortcut(QKeyEvent *event) { if (event->key() == Qt::Key_Space && event->modifiers() == 0) { switchTool(KoPanTool_ID, true); } else if (event->key() == Qt::Key_Escape && event->modifiers() == 0) { switchTool(KoInteractionTool_ID, false); } } // ******** KoToolManager ********** KoToolManager::KoToolManager() : QObject(), d(new Private(this)) { connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(movedFocus(QWidget*,QWidget*))); } KoToolManager::~KoToolManager() { delete d; } QList KoToolManager::toolActionList() const { QList answer; answer.reserve(d->tools.count()); foreach(ToolHelper *tool, d->tools) { if (tool->id() == KoCreateShapesTool_ID) continue; // don't show this one. answer.append(tool->toolAction()); } return answer; } void KoToolManager::requestToolActivation(KoCanvasController * controller) { if (d->canvasses.contains(controller)) { QString activeToolId = d->canvasses.value(controller).first()->activeToolId; foreach(ToolHelper * th, d->tools) { if (th->id() == activeToolId) { d->toolActivated(th); break; } } } } KoInputDevice KoToolManager::currentInputDevice() const { return d->inputDevice; } void KoToolManager::registerTools(KActionCollection *ac, KoCanvasController *controller) { Q_ASSERT(controller); Q_ASSERT(ac); d->setup(); if (!d->canvasses.contains(controller)) { return; } // Actions available during the use of individual tools CanvasData *cd = d->canvasses.value(controller).first(); foreach(KoToolBase *tool, cd->allTools) { QHash actions = tool->actions(); QHash::const_iterator action(actions.constBegin()); for (; action != actions.constEnd(); ++action) { if (!ac->action(action.key())) ac->addAction(action.key(), action.value()); } } // Actions used to switch tools via shortcuts foreach(ToolHelper * th, d->tools) { if (ac->action(th->id())) { continue; } ShortcutToolAction* action = th->createShortcutToolAction(ac); ac->addAction(th->id(), action); } } void KoToolManager::addController(KoCanvasController *controller) { Q_ASSERT(controller); - if (d->canvasses.keys().contains(controller)) + if (d->canvasses.contains(controller)) return; d->setup(); d->attachCanvas(controller); connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*))); connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); } void KoToolManager::removeCanvasController(KoCanvasController *controller) { Q_ASSERT(controller); disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*))); disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*))); d->detachCanvas(controller); } void KoToolManager::attemptCanvasControllerRemoval(QObject* controller) { KoCanvasControllerProxyObject* controllerActual = qobject_cast(controller); if (controllerActual) { removeCanvasController(controllerActual->canvasController()); } } void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController) { - if (!d->canvasses.keys().contains(canvasController)) + if (!d->canvasses.contains(canvasController)) return; QList canvasses = d->canvasses[canvasController]; foreach(CanvasData *canvas, canvasses) { - foreach(KoToolBase *tool, canvas->allTools.values()) { + foreach(KoToolBase *tool, canvas->allTools) { tool->updateShapeController(shapeController); } } } void KoToolManager::switchToolRequested(const QString & id) { Q_ASSERT(d->canvasData); if (!d->canvasData) return; while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack d->canvasData->stack.pop(); d->switchTool(id, false); } void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id) { if (!d->canvasData) return; d->switchInputDevice(id); } void KoToolManager::switchToolTemporaryRequested(const QString &id) { d->switchTool(id, true); } void KoToolManager::switchBackRequested() { if (!d->canvasData) return; if (d->canvasData->stack.isEmpty()) { // default to changing to the interactionTool d->switchTool(KoInteractionTool_ID, false); return; } d->switchTool(d->canvasData->stack.pop(), false); } KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const { Q_ASSERT(canvas); foreach(KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) { KoCreateShapesTool *createTool = dynamic_cast (d->canvasData->allTools.value(KoCreateShapesTool_ID)); Q_ASSERT(createTool /* ID changed? */); return createTool; } } Q_ASSERT(0); // this should not happen return 0; } KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const { Q_ASSERT(canvas); foreach(KoCanvasController *controller, d->canvasses.keys()) { if (controller->canvas() == canvas) return d->canvasData->allTools.value(id); } return 0; } KoCanvasController *KoToolManager::activeCanvasController() const { if (! d->canvasData) return 0; return d->canvasData->canvas; } QString KoToolManager::preferredToolForSelection(const QList &shapes) { QList types; foreach(KoShape *shape, shapes) if (! types.contains(shape->shapeId())) types.append(shape->shapeId()); QString toolType = KoInteractionTool_ID; int prio = INT_MAX; foreach(ToolHelper *helper, d->tools) { if (helper->priority() >= prio) continue; if (helper->toolType() == KoToolFactoryBase::mainToolType()) continue; bool toolWillWork = false; foreach (const QString &type, types) { if (helper->activationShapeId().split(',').contains(type)) { toolWillWork = true; break; } } if (toolWillWork) { toolType = helper->id(); prio = helper->priority(); } } return toolType; } void KoToolManager::injectDeviceEvent(KoInputDeviceHandlerEvent * event) { if (d->canvasData && d->canvasData->canvas->canvas()) { if (static_cast(event->type()) == KoInputDeviceHandlerEvent::ButtonPressed) d->canvasData->activeTool->customPressEvent(event->pointerEvent()); else if (static_cast(event->type()) == KoInputDeviceHandlerEvent::ButtonReleased) d->canvasData->activeTool->customReleaseEvent(event->pointerEvent()); else if (static_cast(event->type()) == KoInputDeviceHandlerEvent::PositionChanged) d->canvasData->activeTool->customMoveEvent(event->pointerEvent()); } } void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory) { ToolHelper *tool = new ToolHelper(toolFactory); // make sure all plugins are loaded as otherwise we will not load them d->setup(); d->tools.append(tool); // connect to all tools so we can hear their button-clicks connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*))); // now create tools for all existing canvases foreach(KoCanvasController *controller, d->canvasses.keys()) { // this canvascontroller is unknown, which is weird if (!d->canvasses.contains(controller)) { continue; } // create a tool for all canvasdata objects (i.e., all input devices on this canvas) foreach (CanvasData *cd, d->canvasses[controller]) { QPair toolPair = createTools(controller, tool); if (toolPair.second) { cd->allTools.insert(toolPair.first, toolPair.second); } } // Then create a button for the toolbox for this canvas if (tool->id() == KoCreateShapesTool_ID) { continue; } emit addedTool(tool->toolAction(), controller); } } QPair KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool) { // XXX: maybe this method should go into the private class? QHash origHash; if (d->canvasses.contains(controller)) { origHash = d->canvasses.value(controller).first()->allTools; } if (origHash.contains(tool->id())) { return QPair(tool->id(), origHash.value(tool->id())); } debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority(); KoToolBase *tl = tool->createTool(controller->canvas()); if (tl) { d->uniqueToolIds.insert(tl, tool->uniqueId()); tl->setObjectName(tool->id()); foreach(QAction *action, tl->actions()) { action->setEnabled(false); } } KoZoomTool *zoomTool = dynamic_cast(tl); if (zoomTool) { zoomTool->setCanvasController(controller); } KoPanTool *panTool = dynamic_cast(tl); if (panTool) { panTool->setCanvasController(controller); } return QPair(tool->id(), tl); } KoToolManager* KoToolManager::instance() { return s_instance; } QString KoToolManager::activeToolId() const { if (!d->canvasData) return QString(); return d->canvasData->activeToolId; } KoToolManager::Private *KoToolManager::priv() { return d; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoToolManager.cpp" diff --git a/libs/flake/commands/KoShapeReorderCommand.cpp b/libs/flake/commands/KoShapeReorderCommand.cpp index a495b774b4b..77dbc496ed9 100644 --- a/libs/flake/commands/KoShapeReorderCommand.cpp +++ b/libs/flake/commands/KoShapeReorderCommand.cpp @@ -1,181 +1,181 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * * 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. */ #include "KoShapeReorderCommand.h" #include "KoShape.h" #include "KoShape_p.h" #include "KoShapeManager.h" #include "KoShapeContainer.h" #include #include #include class KoShapeReorderCommandPrivate { public: KoShapeReorderCommandPrivate(const QList &s, QList &ni) : shapes(s), newIndexes(ni) { } QList shapes; QList previousIndexes; QList newIndexes; }; KoShapeReorderCommand::KoShapeReorderCommand(const QList &shapes, QList &newIndexes, KUndo2Command *parent) : KUndo2Command(parent), d(new KoShapeReorderCommandPrivate(shapes, newIndexes)) { Q_ASSERT(shapes.count() == newIndexes.count()); foreach (KoShape *shape, shapes) d->previousIndexes.append(shape->zIndex()); setText(kundo2_i18n("Reorder shapes")); } KoShapeReorderCommand::~KoShapeReorderCommand() { delete d; } void KoShapeReorderCommand::redo() { KUndo2Command::redo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setZIndex(d->newIndexes.at(i)); d->shapes.at(i)->update(); } } void KoShapeReorderCommand::undo() { KUndo2Command::undo(); for (int i = 0; i < d->shapes.count(); i++) { d->shapes.at(i)->update(); d->shapes.at(i)->setZIndex(d->previousIndexes.at(i)); d->shapes.at(i)->update(); } } -static void prepare(KoShape *s, QMap > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move) +static void prepare(KoShape *s, QHash > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move) { KoShapeContainer *parent = s->parent(); - QMap >::iterator it(newOrder.find(parent)); + QHash >::iterator it(newOrder.find(parent)); if (it == newOrder.end()) { QList children; if (parent != 0) { children = parent->shapes(); } else { // get all toplevel shapes children = manager->topLevelShapes(); } qSort(children.begin(), children.end(), KoShape::compareShapeZIndex); // the append and prepend are needed so that the raise/lower of all shapes works as expected. children.append(0); children.prepend(0); it = newOrder.insert(parent, children); } QList & shapes(newOrder[parent]); int index = shapes.indexOf(s); if (index != -1) { shapes.removeAt(index); switch (move) { case KoShapeReorderCommand::BringToFront: index = shapes.size(); break; case KoShapeReorderCommand::RaiseShape: if (index < shapes.size()) { ++index; } break; case KoShapeReorderCommand::LowerShape: if (index > 0) { --index; } break; case KoShapeReorderCommand::SendToBack: index = 0; break; } shapes.insert(index,s); } } // static KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent) { QList newIndexes; QList changedShapes; - QMap > newOrder; + QHash > newOrder; QList sortedShapes(shapes); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); if (move == BringToFront || move == LowerShape) { for (int i = 0; i < sortedShapes.size(); ++i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } else { for (int i = sortedShapes.size() - 1; i >= 0; --i) { prepare(sortedShapes.at(i), newOrder, manager, move); } } - QMap >::ConstIterator newIt(newOrder.constBegin()); + QHash >::ConstIterator newIt(newOrder.constBegin()); for (; newIt!= newOrder.constEnd(); ++newIt) { QList order(newIt.value()); order.removeAll(0); int index = -KoShapePrivate::MaxZIndex - 1; // set minimum zIndex int pos = 0; for (; pos < order.size(); ++pos) { if (order[pos]->zIndex() > index) { index = order[pos]->zIndex(); } else { break; } } if (pos == order.size()) { //nothing needs to be done continue; } else if (pos <= order.size() / 2) { // new index for the front int startIndex = order[pos]->zIndex() - pos; for (int i = 0; i < pos; ++i) { changedShapes.append(order[i]); newIndexes.append(startIndex++); } } else { //new index for the end for (int i = pos; i < order.size(); ++i) { changedShapes.append(order[i]); newIndexes.append(++index); } } } Q_ASSERT(changedShapes.count() == newIndexes.count()); return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent); } diff --git a/libs/flake/svg/SvgShapeFactory.h b/libs/flake/svg/SvgShapeFactory.h index 75b4c185310..a19b56d4e29 100644 --- a/libs/flake/svg/SvgShapeFactory.h +++ b/libs/flake/svg/SvgShapeFactory.h @@ -1,42 +1,43 @@ /* This file is part of the KDE project * Copyright (C) 2011 Jan Hambrecht * * 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. */ #ifndef SVGSHAPEFACTORY_H #define SVGSHAPEFACTORY_H #include "flake_export.h" #include "KoShapeFactoryBase.h" /// Use this shape factory to load embedded svg files from odf class FLAKE_EXPORT SvgShapeFactory : public KoShapeFactoryBase { +Q_OBJECT public: SvgShapeFactory(); ~SvgShapeFactory(); // reimplemented from KoShapeFactoryBase virtual bool supports(const KoXmlElement &element, KoShapeLoadingContext &context) const; // reimplemented from KoShapeFactoryBase virtual KoShape *createShapeFromOdf(const KoXmlElement &element, KoShapeLoadingContext &context); /// Adds an instance of this factory to the shape registry, if not already registered static void addToRegistry(); }; #endif // SVGSHAPEFACTORY_H diff --git a/libs/flake/svg/SvgStyleWriter.cpp b/libs/flake/svg/SvgStyleWriter.cpp index 828e454f7ee..d81093772f5 100644 --- a/libs/flake/svg/SvgStyleWriter.cpp +++ b/libs/flake/svg/SvgStyleWriter.cpp @@ -1,358 +1,357 @@ /* This file is part of the KDE project Copyright (C) 2002 Lars Siebold Copyright (C) 2002-2003,2005 Rob Buis Copyright (C) 2002,2005-2006 David Faure Copyright (C) 2002 Werner Trobin Copyright (C) 2002 Lennart Kudling Copyright (C) 2004 Nicolas Goutte Copyright (C) 2005 Boudewijn Rempt Copyright (C) 2005 Raphael Langerhorst Copyright (C) 2005 Thomas Zander Copyright (C) 2005,2007-2008 Jan Hambrecht Copyright (C) 2006 Inge Wallin Copyright (C) 2006 Martin Pfeiffer Copyright (C) 2006 Gábor Lehel Copyright (C) 2006 Laurent Montel Copyright (C) 2006 Christian Mueller Copyright (C) 2006 Ariya Hidayat Copyright (C) 2010 Thorsten Zachmann 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. */ #include "SvgStyleWriter.h" #include "SvgSavingContext.h" #include "SvgUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void SvgStyleWriter::saveSvgStyle(KoShape *shape, SvgSavingContext &context) { saveSvgFill(shape, context); saveSvgStroke(shape, context); saveSvgEffects(shape, context); saveSvgClipping(shape, context); if (! shape->isVisible()) context.shapeWriter().addAttribute("display", "none"); if (shape->transparency() > 0.0) context.shapeWriter().addAttribute("opacity", 1.0 - shape->transparency()); } void SvgStyleWriter::saveSvgFill(KoShape *shape, SvgSavingContext &context) { if (! shape->background()) { context.shapeWriter().addAttribute("fill", "none"); } - QBrush fill(Qt::NoBrush); QSharedPointer cbg = qSharedPointerDynamicCast(shape->background()); if (cbg) { context.shapeWriter().addAttribute("fill", cbg->color().name()); if (cbg->color().alphaF() < 1.0) context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF()); } QSharedPointer gbg = qSharedPointerDynamicCast(shape->background()); if (gbg) { QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context); context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")"); } QSharedPointer pbg = qSharedPointerDynamicCast(shape->background()); if (pbg) { const QString patternId = saveSvgPattern(pbg, shape, context); context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")"); } KoPathShape * path = dynamic_cast(shape); if (path && shape->background()) { // non-zero is default, so only write fillrule if evenodd is set if (path->fillRule() == Qt::OddEvenFill) context.shapeWriter().addAttribute("fill-rule", "evenodd"); } } void SvgStyleWriter::saveSvgStroke(KoShape *shape, SvgSavingContext &context) { const KoShapeStroke * line = dynamic_cast(shape->stroke()); if (! line) return; QString strokeStr("none"); if (line->lineBrush().gradient()) { QString gradientId = saveSvgGradient(line->lineBrush().gradient(), line->lineBrush().transform(), context); strokeStr = "url(#" + gradientId + ")"; } else { strokeStr = line->color().name(); } if (!strokeStr.isEmpty()) context.shapeWriter().addAttribute("stroke", strokeStr); if (line->color().alphaF() < 1.0) context.shapeWriter().addAttribute("stroke-opacity", line->color().alphaF()); context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(line->lineWidth())); if (line->capStyle() == Qt::FlatCap) context.shapeWriter().addAttribute("stroke-linecap", "butt"); else if (line->capStyle() == Qt::RoundCap) context.shapeWriter().addAttribute("stroke-linecap", "round"); else if (line->capStyle() == Qt::SquareCap) context.shapeWriter().addAttribute("stroke-linecap", "square"); if (line->joinStyle() == Qt::MiterJoin) { context.shapeWriter().addAttribute("stroke-linejoin", "miter"); context.shapeWriter().addAttribute("stroke-miterlimit", line->miterLimit()); } else if (line->joinStyle() == Qt::RoundJoin) context.shapeWriter().addAttribute("stroke-linejoin", "round"); else if (line->joinStyle() == Qt::BevelJoin) context.shapeWriter().addAttribute("stroke-linejoin", "bevel"); // dash if (line->lineStyle() > Qt::SolidLine) { qreal dashFactor = line->lineWidth(); if (line->dashOffset() != 0) context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * line->dashOffset()); QString dashStr; const QVector dashes = line->lineDashes(); int dashCount = dashes.size(); for (int i = 0; i < dashCount; ++i) { if (i > 0) dashStr += ","; dashStr += QString("%1").arg(dashes[i] * dashFactor); } context.shapeWriter().addAttribute("stroke-dasharray", dashStr); } } void SvgStyleWriter::saveSvgEffects(KoShape *shape, SvgSavingContext &context) { KoFilterEffectStack * filterStack = shape->filterEffectStack(); if (!filterStack) return; QList filterEffects = filterStack->filterEffects(); if (!filterEffects.count()) return; const QString uid = context.createUID("filter"); filterStack->save(context.styleWriter(), uid); context.shapeWriter().addAttribute("filter", "url(#" + uid + ")"); } void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context) { KoClipPath *clipPath = shape->clipPath(); if (!clipPath) return; const QSizeF shapeSize = shape->outlineRect().size(); KoPathShape *path = KoPathShape::createShapeFromPainterPath(clipPath->pathForSize(shapeSize)); if (!path) return; path->close(); const QString uid = context.createUID("clippath"); context.styleWriter().startElement("clipPath"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("clipPathUnits", "userSpaceOnUse"); context.styleWriter().startElement("path"); context.styleWriter().addAttribute("d", path->toString(path->absoluteTransformation(0)*context.userSpaceTransform())); context.styleWriter().endElement(); // path context.styleWriter().endElement(); // clipPath context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")"); if (clipPath->clipRule() != Qt::WindingFill) context.shapeWriter().addAttribute("clip-rule", "evenodd"); } void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context) { foreach(const QGradientStop &stop, colorStops) { context.styleWriter().startElement("stop"); context.styleWriter().addAttribute("stop-color", stop.second.name()); context.styleWriter().addAttribute("offset", stop.first); context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF()); context.styleWriter().endElement(); } } QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context) { if (! gradient) return QString(); Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode); const QString spreadMethod[3] = { QString("pad"), QString("reflect"), QString("repeat") }; const QString uid = context.createUID("gradient"); if (gradient->type() == QGradient::LinearGradient) { const QLinearGradient * g = static_cast(gradient); context.styleWriter().startElement("linearGradient"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform)); context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox"); context.styleWriter().addAttribute("x1", g->start().x()); context.styleWriter().addAttribute("y1", g->start().y()); context.styleWriter().addAttribute("x2", g->finalStop().x()); context.styleWriter().addAttribute("y2", g->finalStop().y()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } else if (gradient->type() == QGradient::RadialGradient) { const QRadialGradient * g = static_cast(gradient); context.styleWriter().startElement("radialGradient"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform)); context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox"); context.styleWriter().addAttribute("cx", g->center().x()); context.styleWriter().addAttribute("cy", g->center().y()); context.styleWriter().addAttribute("fx", g->focalPoint().x()); context.styleWriter().addAttribute("fy", g->focalPoint().y()); context.styleWriter().addAttribute("r", g->radius()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } else if (gradient->type() == QGradient::ConicalGradient) { const QConicalGradient * g = static_cast(gradient); context.styleWriter().startElement("conicalGradient"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform)); context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox"); context.styleWriter().addAttribute("cx", g->center().x()); context.styleWriter().addAttribute("cy", g->center().y()); context.styleWriter().addAttribute("a", g->angle()); context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]); // color stops saveSvgColorStops(gradient->stops(), context); context.styleWriter().endElement(); } return uid; } QString SvgStyleWriter::saveSvgPattern(QSharedPointer pattern, KoShape *shape, SvgSavingContext &context) { const QString uid = context.createUID("pattern"); const QSizeF shapeSize = shape->size(); const QSizeF patternSize = pattern->patternDisplaySize(); const QSize imageSize = pattern->pattern().size(); // calculate offset in point QPointF offset = pattern->referencePointOffset(); offset.rx() = 0.01 * offset.x() * patternSize.width(); offset.ry() = 0.01 * offset.y() * patternSize.height(); // now take the reference point into account switch (pattern->referencePoint()) { case KoPatternBackground::TopLeft: break; case KoPatternBackground::Top: offset += QPointF(0.5 * shapeSize.width(), 0.0); break; case KoPatternBackground::TopRight: offset += QPointF(shapeSize.width(), 0.0); break; case KoPatternBackground::Left: offset += QPointF(0.0, 0.5 * shapeSize.height()); break; case KoPatternBackground::Center: offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height()); break; case KoPatternBackground::Right: offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height()); break; case KoPatternBackground::BottomLeft: offset += QPointF(0.0, shapeSize.height()); break; case KoPatternBackground::Bottom: offset += QPointF(0.5 * shapeSize.width(), shapeSize.height()); break; case KoPatternBackground::BottomRight: offset += QPointF(shapeSize.width(), shapeSize.height()); break; } offset = shape->absoluteTransformation(0).map(offset); context.styleWriter().startElement("pattern"); context.styleWriter().addAttribute("id", uid); context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x())); context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y())); if (pattern->repeat() == KoPatternBackground::Stretched) { context.styleWriter().addAttribute("width", "100%"); context.styleWriter().addAttribute("height", "100%"); context.styleWriter().addAttribute("patternUnits", "objectBoundingBox"); } else { context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width())); context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height())); context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse"); } context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(imageSize.width()).arg(imageSize.height())); //*m_defs << " patternContentUnits=\"userSpaceOnUse\""; context.styleWriter().startElement("image"); context.styleWriter().addAttribute("x", "0"); context.styleWriter().addAttribute("y", "0"); context.styleWriter().addAttribute("width", QString("%1px").arg(imageSize.width())); context.styleWriter().addAttribute("height", QString("%1px").arg(imageSize.height())); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (pattern->pattern().save(&buffer, "PNG")) { QMimeDatabase db; const QString mimeType = db.mimeTypeForData(ba).name(); context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64()); } context.styleWriter().endElement(); // image context.styleWriter().endElement(); // pattern return uid; } diff --git a/libs/flake/tests/TestKoShapeFactory.cpp b/libs/flake/tests/TestKoShapeFactory.cpp index 6ae31d9ad9b..cbbb9aa74dc 100644 --- a/libs/flake/tests/TestKoShapeFactory.cpp +++ b/libs/flake/tests/TestKoShapeFactory.cpp @@ -1,127 +1,127 @@ /* This file is part of the KDE project * Copyright (c) 2007 Boudewijn Rempt (boud@valdyas.org) * * 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. */ #include "TestKoShapeFactory.h" #include #include #include #include #include #include #include #include #include void TestKoShapeFactory::testCreateFactory() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); QVERIFY(factory != 0); delete factory; } void TestKoShapeFactory::testSupportsKoXmlElement() { } void TestKoShapeFactory::testPriority() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); QVERIFY(factory->loadingPriority() == 0); delete factory; } void TestKoShapeFactory::testCreateDefaultShape() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); KoShape *shape = factory->createDefaultShape(); QVERIFY(shape != 0); delete shape; delete factory; } void TestKoShapeFactory::testCreateShape() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); KoShape *shape = factory->createShape(0); QVERIFY(shape != 0); delete shape; delete factory; } void TestKoShapeFactory::testOdfElement() { KoShapeFactoryBase * factory = new KoPathShapeFactory(QStringList()); - QVERIFY(factory->odfElements().front().second.contains("path")); - QVERIFY(factory->odfElements().front().second.contains("line")); - QVERIFY(factory->odfElements().front().second.contains("polyline")); - QVERIFY(factory->odfElements().front().second.contains("polygon")); - QVERIFY(factory->odfElements().front().first == KoXmlNS::draw); + QVERIFY(factory->odfElements().constFirst().second.contains("path")); + QVERIFY(factory->odfElements().constFirst().second.contains("line")); + QVERIFY(factory->odfElements().constFirst().second.contains("polyline")); + QVERIFY(factory->odfElements().constFirst().second.contains("polygon")); + QVERIFY(factory->odfElements().constFirst().first == KoXmlNS::draw); QBuffer xmldevice; xmldevice.open(QIODevice::WriteOnly); QTextStream xmlstream(&xmldevice); xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmlstream << ""; xmldevice.close(); KoXmlDocument doc; QString errorMsg; int errorLine = 0; int errorColumn = 0; QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true); QCOMPARE(errorMsg.isEmpty(), true); QCOMPARE(errorLine, 0); QCOMPARE(errorColumn, 0); KoXmlElement contentElement = doc.documentElement(); KoXmlElement bodyElement = contentElement.firstChild().toElement(); // XXX: When loading is implemented, these no doubt have to be // sensibly filled. KoOdfStylesReader stylesReader; KoOdfLoadingContext odfContext(stylesReader, 0); KoShapeLoadingContext shapeContext(odfContext, 0); KoXmlElement textElement = bodyElement.firstChild().firstChild().toElement(); QVERIFY(textElement.tagName() == "p"); QCOMPARE(factory->supports(textElement, shapeContext), false); KoXmlElement pathElement = bodyElement.firstChild().lastChild().toElement(); QVERIFY(pathElement.tagName() == "path"); QCOMPARE(factory->supports(pathElement, shapeContext), true); KoShape *shape = factory->createDefaultShape(); QVERIFY(shape); QVERIFY(shape->loadOdf(pathElement, shapeContext)); delete shape; delete factory; } QTEST_GUILESS_MAIN(TestKoShapeFactory) diff --git a/libs/flake/tests/TestSnapStrategy.cpp b/libs/flake/tests/TestSnapStrategy.cpp index bb868ff1a9c..16015c2b2e2 100644 --- a/libs/flake/tests/TestSnapStrategy.cpp +++ b/libs/flake/tests/TestSnapStrategy.cpp @@ -1,811 +1,805 @@ /* Copyright (C) 2012 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "TestSnapStrategy.h" #include #include "KoSnapStrategy.h" #include "KoPathShape.h" #include "KoSnapProxy.h" #include "KoShapeBasedDocumentBase.h" #include "MockShapes.h" #include "KoPathPoint.h" //#include #include #include void TestSnapStrategy::testOrthogonalSnap() { //Test case one - expected not to snap OrthogonalSnapStrategy toTest; const QPointF paramMousePosition; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); //the shapeManager() function of this will be called KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxy(&aKoSnapGuide); //param proxy will have no shapes hence it will not snap qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePosition, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - makes sure the there are shapes in the fakeShapeControllerBase thus it should snap OrthogonalSnapStrategy toTestTwo; //paramMousePosition must be within paramSnapDistance of the points in firstSnapPointList const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; //This call will be made on the paramProxy: proxy->pointsFromShape(shape) which in turn //will make this call shape->snapData().snapPoints(); so the shapes have to have snapPoints //In order to have snapPoints we have to use the call //shape->snapData().setSnapPoints() for each fakeShape, where we send in a const //QVector &snapPoints in order to have snapPoints to iterate - which is the only //way to change the value of minHorzDist and minVertDist in KoSnapStrategy.cpp so it //differs from HUGE_VAL - i.e. gives us the true value for the snap function. //creating the lists of points //example QVector pts; pts.push_back(QPointF(0.2, 0.3)); pts.push_back(QPointF(0.5, 0.7)); MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); //the shapeManager() function of this will be called KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); //the call that will be made to the snap guide created is m_snapGuide->canvas()->shapeManager()->shapes(); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //param proxy will have shapes now //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; bool didSnapTwo = toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testNodeSnap() { //Test case one - expected to not snap NodeSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Test case two - exercising the branches by putting a shape and snap points into the ShapeManager NodeSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testExtensionSnap() { //bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) ExtensionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Second test case - testing the snap by providing ShapeManager with a shape that has snap points and a path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testIntersectionSnap() { //Testing so it does not work without a path IntersectionSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Exercising the working snap by providing the shape manager with three path shapes //In order for this test to work the shapeManager has to have more than one fakeShape in it //(requirement in QList KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes) //And both shapes have to be not-visible IntersectionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; - QVector firstSnapPointList; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); //pathShapeOne.snapData().setSnapPoints(firstSnapPointList); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; - QVector secondSnapPointList; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); //pathShapeTwo.snapData().setSnapPoints(secondSnapPointList); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; - QVector thirdSnapPointList; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testGridSnap() { //This test is the default case - meant to fail since the grid of the SnapGuide is not set GridSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //This test tests the snapping by providing the SnapGuide with a grid to snap against GridSnapStrategy toTestTwo; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testBoundingBoxSnap() { //Tests so the snap does not work when there is no shape with a path BoundingBoxSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //tests the snap by providing three path shapes to the shape manager BoundingBoxSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *ShapeManager = fakeKoCanvasBaseTwo.shapeManager(); qreal paramSnapDistanceTwo = 8; KoPathShape pathShapeOne; - QVector firstSnapPointList; pathShapeOne.moveTo(QPointF(1,2)); pathShapeOne.lineTo(QPointF(2,2)); pathShapeOne.lineTo(QPointF(3,2)); pathShapeOne.lineTo(QPointF(4,2)); pathShapeOne.isVisible(true); ShapeManager->addShape(&pathShapeOne); KoPathShape pathShapeTwo; - QVector secondSnapPointList; pathShapeTwo.moveTo(QPointF(1,1)); pathShapeTwo.lineTo(QPointF(2,2)); pathShapeTwo.lineTo(QPointF(3,3)); pathShapeTwo.lineTo(QPointF(4,4)); pathShapeTwo.isVisible(true); ShapeManager->addShape(&pathShapeTwo); KoPathShape pathShapeThree; - QVector thirdSnapPointList; pathShapeThree.moveTo(QPointF(5,5)); pathShapeThree.lineTo(QPointF(6,6)); pathShapeThree.lineTo(QPointF(7,7)); pathShapeThree.lineTo(QPointF(8,8)); pathShapeThree.isVisible(true); ShapeManager->addShape(&pathShapeThree); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testLineGuideSnap() { //Testing so the snap does not work without horizontal and vertial lines LineGuideSnapStrategy toTest; const QPointF paramMousePos; MockShapeController fakeShapeControllerBase; MockCanvas fakeKoCanvasBase(&fakeShapeControllerBase); KoSnapGuide aKoSnapGuide(&fakeKoCanvasBase); KoSnapProxy paramProxy(&aKoSnapGuide); qreal paramSnapDistance = 0; bool didSnap = toTest.snap(paramMousePos, ¶mProxy, paramSnapDistance); QVERIFY(!didSnap); //Test case that covers the path of the snap by providing horizontal and vertical lines for the GuidesData LineGuideSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoGuidesData guidesData; QList horzLines; horzLines.push_back(2); horzLines.push_back(3); horzLines.push_back(4); horzLines.push_back(5); QList vertLines; vertLines.push_back(1); vertLines.push_back(2); vertLines.push_back(3); vertLines.push_back(4); guidesData.setHorizontalGuideLines(horzLines); guidesData.setVerticalGuideLines(vertLines); fakeKoCanvasBaseTwo.setGuidesData(&guidesData); qreal paramSnapDistanceTwo = 8; KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); bool didSnapTwo = toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); QVERIFY(didSnapTwo); } void TestSnapStrategy::testOrhogonalDecoration() { //Making sure the decoration is created but is empty OrthogonalSnapStrategy toTestTwo; const QPointF paramMousePositionTwo(3,3); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); MockShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); //Make sure at least one point in firstSnapPointList is within this distance of //paramMousePoint to trigger the branches if (dx < minHorzDist && dx < maxSnapDistance) //and if (dy < minVertDist && dy < maxSnapDistance) WHICH IS WHERE minVertDist and minHorzDist //ARE CHANGED FROM HUGE_VAL qreal paramSnapDistanceTwo = 4; toTestTwo.snap(paramMousePositionTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter irrelevantParameter; QPainterPath resultingDecoration = toTestTwo.decoration(irrelevantParameter); QVERIFY( resultingDecoration.isEmpty() ); } void TestSnapStrategy::testNodeDecoration() { //Tests so the decoration returns a rect which is inside the "standard outer rect" NodeSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5, -5.5, 11, 11); QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testExtensionDecoration() { //Tests the decoration is exercised by providing it with path //fakeShapeOne needs at least one subpath that is open in order to change the values of minDistances //which in turn opens the path where it is possible to get a true bool value back from the snap function // KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex); in KoPathShape needs to be called ExtensionSnapStrategy toTestTwo; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoShapeManager *fakeShapeManager = fakeKoCanvasBaseTwo.shapeManager(); KoPathShape fakeShapeOne; QVector firstSnapPointList; firstSnapPointList.push_back(QPointF(1,2)); firstSnapPointList.push_back(QPointF(2,2)); firstSnapPointList.push_back(QPointF(3,2)); firstSnapPointList.push_back(QPointF(4,2)); qreal paramSnapDistanceTwo = 4; fakeShapeOne.snapData().setSnapPoints(firstSnapPointList); fakeShapeOne.isVisible(true); QPointF firstPoint(0,2); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); fakeShapeOne.moveTo(firstPoint); fakeShapeOne.lineTo(secondPoint); fakeShapeOne.lineTo(thirdPoint); fakeShapeOne.lineTo(fourthPoint); fakeShapeManager->addShape(&fakeShapeOne); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); toTestTwo.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); const KoViewConverter aConverter; QPainterPath resultingDecoration = toTestTwo.decoration(aConverter); QPointF resultDecorationLastPoint = resultingDecoration.currentPosition(); QVERIFY( resultDecorationLastPoint == QPointF(0,2) ); } void TestSnapStrategy::testIntersectionDecoration() { //Tests the decoration by making sure that the returned rect is within the "standard outer rect" IntersectionSnapStrategy toTest; KoViewConverter irrelevantParameter; QRectF originalRect = QRectF(-5.5,-5.5,11,11); //std outer rect QPainterPath resultingDecoration = toTest.decoration(irrelevantParameter); QRectF rectInsidePath = resultingDecoration.boundingRect(); QVERIFY(originalRect==rectInsidePath); } void TestSnapStrategy::testGridDecoration() { //Tests the decoration by making sure the path returned has the calculated endpoint GridSnapStrategy toTest; const QPointF paramMousePosTwo(40,60); MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); fakeKoCanvasBaseTwo.setHorz(10); fakeKoCanvasBaseTwo.setVert(8); KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); qreal paramSnapDistanceTwo = 8; toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(40, 56); //the snapped position is 40, 56 because horz 10 - so 40 is right on the gridline, and 56 because 7*8 = 56 which is within 8 of 60 QPointF originalEndPoint(snappedPos + QPointF(0, unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testBoundingBoxDecoration() { //tests the decoration by making sure the returned path has the pre-calculated end point BoundingBoxSnapStrategy toTest; KoViewConverter viewConverter; QSizeF unzoomedSize = viewConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(0,0); QPointF originalEndPoint(snappedPos + QPointF(unzoomedSize.width(), -unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(viewConverter); QVERIFY( resultingDecoration.currentPosition() == originalEndPoint ); } void TestSnapStrategy::testLineGuideDecoration() { //tests the decoration by making sure there are horizontal and vertical lines in the guidesData LineGuideSnapStrategy toTest; const QPointF paramMousePosTwo; MockShapeController fakeShapeControllerBaseTwo; MockCanvas fakeKoCanvasBaseTwo(&fakeShapeControllerBaseTwo); KoGuidesData guidesData; //firstSnapPointList.push_back( QList horzLines; horzLines.push_back(2); horzLines.push_back(3); horzLines.push_back(4); horzLines.push_back(5); QList vertLines; vertLines.push_back(1); vertLines.push_back(2); vertLines.push_back(3); vertLines.push_back(4); guidesData.setHorizontalGuideLines(horzLines); guidesData.setVerticalGuideLines(vertLines); fakeKoCanvasBaseTwo.setGuidesData(&guidesData); qreal paramSnapDistanceTwo = 8; KoSnapGuide aKoSnapGuideTwo(&fakeKoCanvasBaseTwo); KoSnapProxy paramProxyTwo(&aKoSnapGuideTwo); toTest.snap(paramMousePosTwo, ¶mProxyTwo, paramSnapDistanceTwo); KoViewConverter parameterConverter; QSizeF unzoomedSize = parameterConverter.viewToDocument(QSizeF(5, 5)); QPointF snappedPos(1,2); QPointF originalEndPointOne(snappedPos + QPointF(unzoomedSize.width(), 0)); QPointF originalEndPointTwo(snappedPos + QPointF(0, unzoomedSize.height())); QPainterPath resultingDecoration = toTest.decoration(parameterConverter); QVERIFY( (resultingDecoration.currentPosition() == originalEndPointOne) || (resultingDecoration.currentPosition() == originalEndPointTwo ) ); } void TestSnapStrategy::testSquareDistance() { //tests that it does not work without setting the points OrthogonalSnapStrategy toTest; QPointF p1; QPointF p2; qreal resultingRealOne = toTest.squareDistance(p1, p2); QVERIFY(resultingRealOne == 0); //tests that the returned value is as expected for positive values OrthogonalSnapStrategy toTestTwo; QPointF p1_2(2,2); QPointF p2_2(1,1); qreal resultingRealTwo = toTestTwo.squareDistance(p1_2, p2_2); QVERIFY(resultingRealTwo == 2); //tests that the returned value is as expected for positive and negative values OrthogonalSnapStrategy toTestThree; QPointF p1_3(2,2); QPointF p2_3(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_3, p2_3); QVERIFY(resultingRealThree == 32); //tests that the returned value is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_4(2,2); QPointF p2_4(2,2); qreal resultingRealFour = toTestFour.squareDistance(p1_4, p2_4); QVERIFY(resultingRealFour == 0); } void TestSnapStrategy::testScalarProduct() { //Tests so the scalarProduct cannot be calculated unless the points are set OrthogonalSnapStrategy toTest; QPointF p1_5; QPointF p2_5; qreal resultingRealOne = toTest.squareDistance(p1_5, p2_5); QVERIFY(resultingRealOne == 0 ); //tests that the product is correctly calculated for positive point values OrthogonalSnapStrategy toTestTwo; QPointF p1_6(2,2); QPointF p2_6(3,3); qreal resultingRealTwo = toTestTwo.squareDistance(p1_6, p2_6); QVERIFY(resultingRealTwo == 2 ); //tests that the product is correctly calculated for positive and negative point values OrthogonalSnapStrategy toTestThree; QPointF p1_7(2,2); QPointF p2_7(-2,-2); qreal resultingRealThree = toTestThree.squareDistance(p1_7, p2_7); QVERIFY(resultingRealThree == 32); //tests so the product is 0 when the points are the same OrthogonalSnapStrategy toTestFour; QPointF p1_8(1,1); QPointF p2_8(1,1); qreal resultingRealFour = toTestFour.squareDistance(p1_8, p2_8); QVERIFY(resultingRealFour == 0); //tests so there is nothing fishy when using origo OrthogonalSnapStrategy toTestFive; QPointF p1_9(1,1); QPointF p2_9(0,0); qreal resultingRealFive = toTestFive.squareDistance(p1_9, p2_9); QVERIFY(resultingRealFive == 2); } //------------------------------------------------------------------ void TestSnapStrategy::testSnapToExtension() { /* toTest.snapToExtension(paramPosition, ¶mPoint, paramMatrix); qDebug() << direction << " is the returned direction for this point in TestSnapStrategy::testSnapToExtension()"; QCOMPARE(direction, ); */ } void TestSnapStrategy::testProject() { //tests for positive point values but backwards leaning line ExtensionSnapStrategy toTestOne; qreal toCompWithOne = -1; QPointF lineStart(4,4); QPointF lineEnd(2,2); QPointF comparisonPoint(6,6); qreal resultingRealOne = toTestOne.project(lineStart, lineEnd, comparisonPoint); QCOMPARE(resultingRealOne, toCompWithOne); //testing for for negative point values ExtensionSnapStrategy toTestTwo; qreal toCompWithTwo = -4; QPointF lineStart_2(-2,-2); QPointF lineEnd_2(-4,-4); QPointF comparisonPoint_2(6,6); qreal resultingRealTwo = toTestTwo.project(lineStart_2, lineEnd_2, comparisonPoint_2); QCOMPARE(resultingRealTwo, toCompWithTwo); //testing for negative and positive point values ExtensionSnapStrategy toTestThree; qreal toCompWithThree = (10*(6/sqrt(72.0)) + 10*(6/sqrt(72.0))) / sqrt(72.0); //diffLength = sqrt(72), scalar = (10*(6/sqrt(72)) + 10*(6/sqrt(72))) QPointF lineStart_3(-2,-2); QPointF lineEnd_3(4, 4); QPointF comparisonPoint_3(8,8); qreal resultingRealThree = toTestThree.project(lineStart_3, lineEnd_3, comparisonPoint_3); QCOMPARE(resultingRealThree, toCompWithThree); //Below we test the formula itself for the dot-product by using values we know return t=0.5 //Formula for how to use the t value is: //ProjectionPoint = lineStart*(1-resultingReal) + resultingReal*lineEnd; (this is the formula used in BoundingBoxSnapStrategy::squareDistanceToLine()) //Note: The angle of the line from projection point to comparison point is always 90 degrees ExtensionSnapStrategy toTestFour; qreal toCompWithFour = 0.5; QPointF lineStart_4(2,1); QPointF lineEnd_4(6,3); QPointF comparisonPoint_4(3,4); qreal resultingRealFour = toTestFour.project(lineStart_4, lineEnd_4, comparisonPoint_4); QCOMPARE(resultingRealFour, toCompWithFour); } void TestSnapStrategy::testExtensionDirection() { /* TEST CASE 0 Supposed to return null */ ExtensionSnapStrategy toTestOne; KoPathShape uninitiatedPathShape; KoPathPoint::PointProperties normal = KoPathPoint::Normal; const QPointF initiatedPoint0(0,0); KoPathPoint initiatedPoint(&uninitiatedPathShape, initiatedPoint0, normal); QMatrix initiatedMatrixParam(1,1,1,1,1,1); const QTransform initiatedMatrix(initiatedMatrixParam); QPointF direction2 = toTestOne.extensionDirection( &initiatedPoint, initiatedMatrix); QVERIFY(direction2.isNull()); /* TEST CASE 1 tests a point that: - is the first in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has no previous point = expected returning an empty QPointF */ ExtensionSnapStrategy toTestTwo; QPointF expectedPointTwo(0,0); KoPathShape shapeOne; QPointF firstPoint(0,1); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); shapeOne.moveTo(firstPoint); shapeOne.lineTo(secondPoint); shapeOne.lineTo(thirdPoint); shapeOne.lineTo(fourthPoint); QPointF paramPositionTwo(0,1); KoPathPoint paramPointTwo; paramPointTwo.setPoint(paramPositionTwo); paramPointTwo.setParent(&shapeOne); const QTransform paramTransMatrix(1,2,3,4,5,6); QPointF directionTwo = toTestTwo.extensionDirection( ¶mPointTwo, paramTransMatrix); QCOMPARE(directionTwo, expectedPointTwo); /* TEST CASE 2 tests a point that: - is the second in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has a previous point = expected returning an */ ExtensionSnapStrategy toTestThree; QPointF expectedPointThree(0,0); QPointF paramPositionThree(1,1); KoPathPoint paramPointThree; paramPointThree.setPoint(paramPositionThree); paramPointThree.setParent(&shapeOne); QPointF directionThree = toTestThree.extensionDirection( ¶mPointThree, paramTransMatrix); QCOMPARE(directionThree, expectedPointThree); } void TestSnapStrategy::testSquareDistanceToLine() { BoundingBoxSnapStrategy toTestOne; const QPointF lineA(4,1); const QPointF lineB(6,3); const QPointF point(5,8); QPointF pointOnLine(0,0); qreal result = toTestOne.squareDistanceToLine(lineA, lineB, point, pointOnLine); //Should be HUGE_VAL because scalar > diffLength QVERIFY(result == HUGE_VAL); BoundingBoxSnapStrategy toTestTwo; QPointF lineA2(4,4); QPointF lineB2(4,4); QPointF point2(5,8); QPointF pointOnLine2(0,0); qreal result2 = toTestTwo.squareDistanceToLine(lineA2, lineB2, point2, pointOnLine2); //Should be HUGE_VAL because lineA2 == lineB2 QVERIFY(result2 == HUGE_VAL); BoundingBoxSnapStrategy toTestThree; QPointF lineA3(6,4); QPointF lineB3(8,6); QPointF point3(2,2); QPointF pointOnLine3(0,0); qreal result3 = toTestThree.squareDistanceToLine(lineA3, lineB3, point3, pointOnLine3); //Should be HUGE_VAL because scalar < 0.0 QVERIFY(result3 == HUGE_VAL); BoundingBoxSnapStrategy toTestFour; QPointF lineA4(2,2); QPointF lineB4(8,6); QPointF point4(3,4); QPointF pointOnLine4(0,0); QPointF diff(6,4); //diff = lineB3 - point3 = 6,4 //diffLength = sqrt(52) //scalar = (1*(6/sqrt(52)) + 2*(4/sqrt(52))); //pointOnLine = lineA + scalar / diffLength * diff; lineA + ((1*(6/sqrt(52)) + 2*(4/sqrt(52))) / sqrt(52)) * 6,4; QPointF distToPointOnLine = (lineA4 + ((1*(6/sqrt(52.0)) + 2*(4/sqrt(52.0))) / sqrt(52.0)) * diff)-point4; qreal toCompWithFour = distToPointOnLine.x()*distToPointOnLine.x()+distToPointOnLine.y()*distToPointOnLine.y(); qreal result4 = toTestFour.squareDistanceToLine(lineA4, lineB4, point4, pointOnLine4); //Normal case with example data QVERIFY(qFuzzyCompare(result4, toCompWithFour)); } QTEST_MAIN(TestSnapStrategy) diff --git a/libs/flake/tools/KoCreateShapesTool.h b/libs/flake/tools/KoCreateShapesTool.h index 580a520f01f..f387b4ba61d 100644 --- a/libs/flake/tools/KoCreateShapesTool.h +++ b/libs/flake/tools/KoCreateShapesTool.h @@ -1,87 +1,88 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * * 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. */ #ifndef KOCREATESHAPESTOOL_H #define KOCREATESHAPESTOOL_H #include "KoInteractionTool.h" #include "flake_export.h" #include class KoCanvasBase; class KoProperties; class KoCreateShapesToolPrivate; #define KoCreateShapesTool_ID "CreateShapesTool" /** * A tool to create shapes with. */ class FLAKE_EXPORT KoCreateShapesTool : public KoInteractionTool { +Q_OBJECT public: /** * Create a new tool; typically not called by applications, only by the KoToolManager * @param canvas the canvas this tool works for. */ explicit KoCreateShapesTool(KoCanvasBase *canvas); /// destructor virtual ~KoCreateShapesTool(); virtual void mouseReleaseEvent(KoPointerEvent *event); virtual void activate(ToolActivation toolActivation, const QSet &shapes); void paint(QPainter &painter, const KoViewConverter &converter); /** * Each shape-type has an Id; as found in KoShapeFactoryBase::id().id(), to choose which * shape this controller should actually create; set the id before the user starts to * create the new shape. * @param id the SHAPEID of the to be generated shape */ void setShapeId(const QString &id); /** * return the shape Id that is to be created. * @return the shape Id that is to be created. */ QString shapeId() const; /** * Set the shape properties that the create controller will use for the next shape it will * create. The tool does not take ownership of the object. * @param properties the properties or 0 if the default shape should be created. */ void setShapeProperties(const KoProperties *properties); /** * return the properties to be used for creating the next shape * @return the properties to be used for creating the next shape */ const KoProperties *shapeProperties(); protected: virtual KoInteractionStrategy *createStrategy(KoPointerEvent *event); private: Q_DECLARE_PRIVATE(KoCreateShapesTool) }; #endif diff --git a/libs/flake/tools/KoPanTool.h b/libs/flake/tools/KoPanTool.h index 262aa2a03d6..64a35672761 100644 --- a/libs/flake/tools/KoPanTool.h +++ b/libs/flake/tools/KoPanTool.h @@ -1,76 +1,77 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * 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. */ #ifndef KOPANTOOL_H #define KOPANTOOL_H #include "KoToolBase.h" #include class KoCanvasController; #define KoPanTool_ID "PanTool" /** * This is the tool that allows you to move the canvas by dragging it and 'panning' around. */ class KoPanTool : public KoToolBase { +Q_OBJECT public: /** * Constructor. * @param canvas the canvas this tool works on. */ explicit KoPanTool(KoCanvasBase *canvas); /// reimplemented from superclass virtual bool wantsAutoScroll() const; /// reimplemented from superclass virtual void mousePressEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseMoveEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseReleaseEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void keyPressEvent(QKeyEvent *event); /// reimplemented from superclass virtual void paint(QPainter &, const KoViewConverter &) {} /// reimplemented from superclass virtual void activate(ToolActivation toolActivation, const QSet &shapes); /// reimplemented method virtual void customMoveEvent(KoPointerEvent *event); /// reimplemented method virtual void mouseDoubleClickEvent(KoPointerEvent *event); /// set the canvasController this tool works on. void setCanvasController(KoCanvasController *controller) { m_controller = controller; } private: QPointF documentToViewport(const QPointF &p); KoCanvasController *m_controller; QPointF m_lastPosition; bool m_temporary; Q_DECLARE_PRIVATE(KoToolBase) }; #endif diff --git a/libs/flake/tools/KoPathSegmentChangeStrategy.cpp b/libs/flake/tools/KoPathSegmentChangeStrategy.cpp index 9e1551dd367..f288e0cbd38 100644 --- a/libs/flake/tools/KoPathSegmentChangeStrategy.cpp +++ b/libs/flake/tools/KoPathSegmentChangeStrategy.cpp @@ -1,165 +1,165 @@ /* This file is part of the KDE project * Copyright (C) 2009 Jan Hambrecht * * 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. */ #include "KoPathSegmentChangeStrategy.h" #include "KoPathShape.h" #include "KoPathPoint.h" #include "KoPathTool.h" #include "KoSnapGuide.h" #include "commands/KoPathControlPointMoveCommand.h" #include "commands/KoPathSegmentTypeCommand.h" #include #include #include #include KoPathSegmentChangeStrategy::KoPathSegmentChangeStrategy(KoPathTool *tool, const QPointF &pos, const KoPathPointData &segment, qreal segmentParam) : KoInteractionStrategy(tool) , m_originalPosition(pos) , m_lastPosition(pos) , m_tool(tool) , m_segmentParam(segmentParam) , m_pointData1(segment) , m_pointData2(segment) { // The following value is a bit arbitrary, it would be more mathematically correct to use // "std::numeric_limits::epsilon()", but if the value is too small, when the user // click near a control point it is relatively easy to create a path shape of almost // infinite size, which blocks the application for a long period of time. A bigger value // is mathematically uncorrect, but it avoids to block application, it also avoid to create // an huge path shape by accident, and anyway, but it does not prevent the user to create one // if they choose so. const qreal eps = 1e-2; // force segment parameter range to avoid division by zero m_segmentParam = qBound(eps, m_segmentParam, qreal(1.0)-eps); m_path = segment.pathShape; m_segment = m_path->segmentByIndex(segment.pointIndex); m_pointData2.pointIndex = m_path->pathPointIndex(m_segment.second()); m_originalSegmentDegree = m_segment.degree(); } KoPathSegmentChangeStrategy::~KoPathSegmentChangeStrategy() { } void KoPathSegmentChangeStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect()); QPointF snappedPosition = m_tool->canvas()->snapGuide()->snap(mouseLocation, modifiers); m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect()); QPointF localPos = m_path->documentToShape(snappedPosition); if (m_segment.degree() == 1) { // line segment is converted to a curve KoPathSegmentTypeCommand cmd(m_pointData1, KoPathSegmentTypeCommand::Curve); cmd.redo(); } QPointF move1, move2; if (m_segment.degree() == 2) { // interpolate quadratic segment between segment start, mouse position and segment end KoPathSegment ipol = KoPathSegment::interpolate(m_segment.first()->point(), localPos, m_segment.second()->point(), m_segmentParam); if (ipol.isValid()) { - move1 = move2 = ipol.controlPoints()[1] - m_segment.controlPoints()[1]; + move1 = move2 = ipol.controlPoints().at(1) - m_segment.controlPoints().at(1); } } else if (m_segment.degree() == 3) { /* * method from inkscape, original method and idea borrowed from Simon Budig * and the GIMP * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative() * * feel good is an arbitrary parameter that distributes the delta between handles * if t of the drag point is less than 1/6 distance form the endpoint only * the corresponding handle is adjusted. This matches the behavior in GIMP */ const qreal t = m_segmentParam; qreal feel_good; if (t <= 1.0 / 6.0) feel_good = 0; else if (t <= 0.5) feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2; else if (t <= 5.0 / 6.0) feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5; else feel_good = 1; QPointF lastLocalPos = m_path->documentToShape(m_lastPosition); QPointF delta = localPos - lastLocalPos; move2 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta; move1 = (feel_good/(3*t*t*(1-t))) * delta; } m_path->update(); if(m_segment.first()->activeControlPoint2()) { KoPathControlPointMoveCommand cmd(m_pointData1, move2, KoPathPoint::ControlPoint2); cmd.redo(); } if(m_segment.second()->activeControlPoint1()) { KoPathControlPointMoveCommand cmd(m_pointData2, move1, KoPathPoint::ControlPoint1); cmd.redo(); } m_path->normalize(); m_path->update(); m_ctrlPoint1Move += move1; m_ctrlPoint2Move += move2; // save last mouse position m_lastPosition = mouseLocation; } void KoPathSegmentChangeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); } KUndo2Command* KoPathSegmentChangeStrategy::createCommand() { m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect()); bool hasControlPoint1 = m_segment.second()->activeControlPoint1(); bool hasControlPoint2 = m_segment.first()->activeControlPoint2(); KUndo2Command * cmd = new KUndo2Command(kundo2_i18n("Change Segment")); if (m_originalSegmentDegree == 1) { m_segment.first()->removeControlPoint2(); m_segment.second()->removeControlPoint1(); new KoPathSegmentTypeCommand(m_pointData1, KoPathSegmentTypeCommand::Curve, cmd); } if (hasControlPoint2) { QPointF oldCtrlPointPos = m_segment.first()->controlPoint2()-m_ctrlPoint2Move; m_segment.first()->setControlPoint2(oldCtrlPointPos); new KoPathControlPointMoveCommand(m_pointData1, m_ctrlPoint2Move, KoPathPoint::ControlPoint2, cmd); } if (hasControlPoint1) { QPointF oldCtrlPointPos = m_segment.second()->controlPoint1()-m_ctrlPoint1Move; m_segment.second()->setControlPoint1(oldCtrlPointPos); new KoPathControlPointMoveCommand(m_pointData2, m_ctrlPoint1Move, KoPathPoint::ControlPoint1, cmd); } return cmd; } diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp index a41bd54645c..30ab5ab2b7a 100644 --- a/libs/flake/tools/KoPathTool.cpp +++ b/libs/flake/tools/KoPathTool.cpp @@ -1,968 +1,968 @@ /* This file is part of the KDE project * Copyright (C) 2006-2012 Jan Hambrecht * Copyright (C) 2006,2007 Thorsten Zachmann * Copyright (C) 2007, 2010 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * * 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. */ #include "KoPathTool.h" #include "KoToolBase_p.h" #include "KoPathShape_p.h" #include "KoPathToolHandle.h" #include "KoCanvasBase.h" #include "KoShapeManager.h" #include "KoDocumentResourceManager.h" #include "KoViewConverter.h" #include "KoSelection.h" #include "KoPointerEvent.h" #include "commands/KoPathPointTypeCommand.h" #include "commands/KoPathPointInsertCommand.h" #include "commands/KoPathPointRemoveCommand.h" #include "commands/KoPathSegmentTypeCommand.h" #include "commands/KoPathBreakAtPointCommand.h" #include "commands/KoPathSegmentBreakCommand.h" #include "commands/KoParameterToPathCommand.h" #include "commands/KoSubpathJoinCommand.h" #include "commands/KoPathPointMergeCommand.h" #include "KoParameterShape.h" #include "KoPathPoint.h" #include "KoPathPointRubberSelectStrategy.h" #include "KoPathSegmentChangeStrategy.h" #include "KoPathConnectionPointStrategy.h" #include "KoParameterChangeStrategy.h" #include "PathToolOptionWidget.h" #include "KoConnectionShape.h" #include "KoSnapGuide.h" #include "KoShapeController.h" #include #include #include #include #include #include #include #include static const unsigned char needle_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e, 0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00 }; static const unsigned char needle_move_bits[] = { 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e, 0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00 }; // helper function to calculate the squared distance between two points qreal squaredDistance(const QPointF& p1, const QPointF &p2) { qreal dx = p1.x()-p2.x(); qreal dy = p1.y()-p2.y(); return dx*dx + dy*dy; } struct KoPathTool::PathSegment { PathSegment() : path(0), segmentStart(0), positionOnSegment(0) { } bool isValid() { return path && segmentStart; } KoPathShape *path; KoPathPoint *segmentStart; qreal positionOnSegment; }; KoPathTool::KoPathTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_pointSelection(this) , m_activeHandle(0) , m_handleRadius(3) , m_activeSegment(0) , m_currentStrategy(0) { QActionGroup *points = new QActionGroup(this); // m_pointTypeGroup->setExclusive(true); m_actionPathPointCorner = new QAction(koIcon("node-type-cusp"), i18n("Corner point"), this); addAction("pathpoint-corner", m_actionPathPointCorner); m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner); points->addAction(m_actionPathPointCorner); m_actionPathPointSmooth = new QAction(koIcon("node-type-smooth"), i18n("Smooth point"), this); addAction("pathpoint-smooth", m_actionPathPointSmooth); m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth); points->addAction(m_actionPathPointSmooth); m_actionPathPointSymmetric = new QAction(koIcon("node-type-symmetric"), i18n("Symmetric Point"), this); addAction("pathpoint-symmetric", m_actionPathPointSymmetric); m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric); points->addAction(m_actionPathPointSymmetric); m_actionCurvePoint = new QAction(koIcon("format-node-curve"), i18n("Make curve point"), this); addAction("pathpoint-curve", m_actionCurvePoint); connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve())); m_actionLinePoint = new QAction(koIcon("format-node-line"), i18n("Make line point"), this); addAction("pathpoint-line", m_actionLinePoint); connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine())); m_actionLineSegment = new QAction(koIcon("format-segment-line"), i18n("Segment to Line"), this); m_actionLineSegment->setShortcut(Qt::Key_F); addAction("pathsegment-line", m_actionLineSegment); connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine())); m_actionCurveSegment = new QAction(koIcon("format-segment-curve"), i18n("Segment to Curve"), this); m_actionCurveSegment->setShortcut(Qt::Key_C); addAction("pathsegment-curve", m_actionCurveSegment); connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve())); m_actionAddPoint = new QAction(koIcon("format-insert-node"), i18n("Insert point"), this); addAction("pathpoint-insert", m_actionAddPoint); m_actionAddPoint->setShortcut(Qt::Key_Insert); connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints())); m_actionRemovePoint = new QAction(koIcon("format-remove-node"), i18n("Remove point"), this); m_actionRemovePoint->setShortcut(Qt::Key_Backspace); addAction("pathpoint-remove", m_actionRemovePoint); connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints())); m_actionBreakPoint = new QAction(koIcon("format-break-node"), i18n("Break at point"), this); addAction("path-break-point", m_actionBreakPoint); connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint())); m_actionBreakSegment = new QAction(koIcon("format-disconnect-node"), i18n("Break at segment"), this); addAction("path-break-segment", m_actionBreakSegment); connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment())); m_actionJoinSegment = new QAction(koIcon("format-connect-node"), i18n("Join with segment"), this); m_actionJoinSegment->setShortcut(Qt::Key_J); addAction("pathpoint-join", m_actionJoinSegment); connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints())); m_actionMergePoints = new QAction(koIcon("format-join-node"), i18n("Merge points"), this); addAction("pathpoint-merge", m_actionMergePoints); connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints())); m_actionConvertToPath = new QAction(koIcon("format-convert-to-path"), i18n("To Path"), this); m_actionConvertToPath->setShortcut(Qt::Key_P); addAction("convert-to-path", m_actionConvertToPath); connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath())); connect(points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*))); connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged())); QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits); QBitmap m = b.createHeuristicMask(false); m_selectCursor = QCursor(b, m, 2, 0); b = QBitmap::fromData(QSize(16, 16), needle_move_bits); m = b.createHeuristicMask(false); m_moveCursor = QCursor(b, m, 2, 0); } KoPathTool::~KoPathTool() { delete m_activeHandle; delete m_activeSegment; delete m_currentStrategy; } QList > KoPathTool::createOptionWidgets() { QList > list; PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this); connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int))); updateOptionsWidget(); toolOptions->setWindowTitle(i18n("Line/Curve")); list.append(toolOptions); return list; } void KoPathTool::pointTypeChanged(QAction *type) { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point) { if (point->activeControlPoint1() && point->activeControlPoint2()) { pointToChange.append(*it); } } } if (!pointToChange.isEmpty()) { KoPathPointTypeCommand *cmd = new KoPathPointTypeCommand(pointToChange, static_cast(type->data().toInt())); d->canvas->addCommand(cmd); updateActions(); } } } void KoPathTool::insertPoints() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (!segments.isEmpty()) { KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, 0.5); d->canvas->addCommand(cmd); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); } } } void KoPathTool::removePoints() { Q_D(KoToolBase); // TODO finish current action or should this not possible during actions??? if (m_pointSelection.size() > 0) { KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController()); PointHandle *pointHandle = dynamic_cast(m_activeHandle); if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) { delete m_activeHandle; m_activeHandle = 0; } m_pointSelection.clear(); d->canvas->addCommand(cmd); } } void KoPathTool::pointToLine() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (point->activeControlPoint1() || point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line)); updateActions(); } } } void KoPathTool::pointToCurve() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { QList selectedPoints = m_pointSelection.selectedPointsData(); QList pointToChange; QList::const_iterator it(selectedPoints.constBegin()); for (; it != selectedPoints.constEnd(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2())) pointToChange.append(*it); } if (! pointToChange.isEmpty()) { d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve)); updateActions(); } } } void KoPathTool::segmentToLine() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line)); updateActions(); } } } void KoPathTool::segmentToCurve() { Q_D(KoToolBase); if (m_pointSelection.size() > 1) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() > 0) { d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve)); updateActions(); } } } void KoPathTool::convertToPath() { Q_D(KoToolBase); QList shapesToConvert; foreach(KoShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) shapesToConvert.append(parameterShape); } if (shapesToConvert.count()) d->canvas->addCommand(new KoParameterToPathCommand(shapesToConvert)); updateOptionsWidget(); } void KoPathTool::joinPoints() { Q_D(KoToolBase); if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList pd(m_pointSelection.selectedPointsData()); const KoPathPointData & pd1 = pd.at(0); const KoPathPointData & pd2 = pd.at(1); KoPathShape * pathShape = pd1.pathShape; if (!pathShape->isClosedSubpath(pd1.pointIndex.first) && (pd1.pointIndex.second == 0 || pd1.pointIndex.second == pathShape->subpathPointCount(pd1.pointIndex.first) - 1) && !pathShape->isClosedSubpath(pd2.pointIndex.first) && (pd2.pointIndex.second == 0 || pd2.pointIndex.second == pathShape->subpathPointCount(pd2.pointIndex.first) - 1)) { KoSubpathJoinCommand *cmd = new KoSubpathJoinCommand(pd1, pd2); d->canvas->addCommand(cmd); } updateActions(); } } void KoPathTool::mergePoints() { Q_D(KoToolBase); if (m_pointSelection.objectCount() != 1 || m_pointSelection.size() != 2) return; QList pointData = m_pointSelection.selectedPointsData(); const KoPathPointData & pd1 = pointData.at(0); const KoPathPointData & pd2 = pointData.at(1); const KoPathPointIndex & index1 = pd1.pointIndex; const KoPathPointIndex & index2 = pd2.pointIndex; KoPathShape * path = pd1.pathShape; // check if subpaths are already closed if (path->isClosedSubpath(index1.first) || path->isClosedSubpath(index2.first)) return; // check if first point is an endpoint if (index1.second != 0 && index1.second != path->subpathPointCount(index1.first)-1) return; // check if second point is an endpoint if (index2.second != 0 && index2.second != path->subpathPointCount(index2.first)-1) return; // now we can start merging the endpoints KoPathPointMergeCommand *cmd = new KoPathPointMergeCommand(pd1, pd2); d->canvas->addCommand(cmd); updateActions(); } void KoPathTool::breakAtPoint() { Q_D(KoToolBase); if (m_pointSelection.hasSelection()) { d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData())); updateActions(); } } void KoPathTool::breakAtSegment() { Q_D(KoToolBase); // only try to break a segment when 2 points of the same object are selected if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) { QList segments(m_pointSelection.selectedSegmentsData()); if (segments.size() == 1) { d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0))); updateActions(); } } } void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter) { Q_D(KoToolBase); painter.setRenderHint(QPainter::Antialiasing, true); // use different colors so that it is also visible on a background of the same color painter.setBrush(Qt::white); //TODO make configurable painter.setPen(QPen(Qt::blue, 0)); foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { painter.save(); painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform()); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { parameterShape->paintHandles(painter, converter, m_handleRadius); } else { shape->paintPoints(painter, converter, m_handleRadius); } painter.restore(); } if (m_currentStrategy) { painter.save(); m_currentStrategy->paint(painter, converter); painter.restore(); } painter.setBrush(Qt::green); // TODO make color configurable painter.setPen(QPen(Qt::blue, 0)); m_pointSelection.paint(painter, converter); painter.setBrush(Qt::red); // TODO make color configurable painter.setPen(QPen(Qt::blue, 0)); if (m_activeHandle) { if (m_activeHandle->check(m_pointSelection.selectedShapes())) { m_activeHandle->paint(painter, converter); } else { delete m_activeHandle; m_activeHandle = 0; } } if (m_currentStrategy) { painter.save(); KoShape::applyConversion(painter, converter); d->canvas->snapGuide()->paint(painter, converter); painter.restore(); } } void KoPathTool::repaintDecorations() { foreach(KoShape *shape, m_pointSelection.selectedShapes()) { repaint(shape->boundingRect()); } m_pointSelection.repaint(); updateOptionsWidget(); } void KoPathTool::mousePressEvent(KoPointerEvent *event) { // we are moving if we hit a point and use the left mouse button event->ignore(); if (m_activeHandle) { m_currentStrategy = m_activeHandle->handleMousePress(event); event->accept(); } else { if (event->button() & Qt::LeftButton) { // check if we hit a path segment if (m_activeSegment && m_activeSegment->isValid()) { KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart); KoPathPointData data(m_activeSegment->path, index); m_currentStrategy = new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment); event->accept(); delete m_activeSegment; m_activeSegment = 0; } else { if ((event->modifiers() & Qt::ControlModifier) == 0) { m_pointSelection.clear(); } // start rubberband selection Q_ASSERT(m_currentStrategy == 0); m_currentStrategy = new KoPathPointRubberSelectStrategy(this, event->point); event->accept(); } } } } void KoPathTool::mouseMoveEvent(KoPointerEvent *event) { if (event->button() & Qt::RightButton) return; if (m_currentStrategy) { m_lastPoint = event->point; m_currentStrategy->handleMouseMove(event->point, event->modifiers()); // repaint new handle positions m_pointSelection.repaint(); if (m_activeHandle) m_activeHandle->repaint(); return; } delete m_activeSegment; m_activeSegment = 0; foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF roi = handleGrabRect(shape->documentToShape(event->point)); KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) { int handleId = parameterShape->handleIdAt(roi); if (handleId != -1) { useCursor(m_moveCursor); emit statusTextChanged(i18n("Drag to move handle.")); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; if (KoConnectionShape * connectionShape = dynamic_cast(parameterShape)) { //qDebug() << "handleId" << handleId; m_activeHandle = new ConnectionHandle(this, connectionShape, handleId); m_activeHandle->repaint(); return; } else { //qDebug() << "handleId" << handleId; m_activeHandle = new ParameterHandle(this, parameterShape, handleId); m_activeHandle->repaint(); return; } } } else { QList points = shape->pointsAt(roi); if (! points.empty()) { // find the nearest control point from all points within the roi KoPathPoint * bestPoint = 0; KoPathPoint::PointType bestPointType = KoPathPoint::Node; qreal minDistance = HUGE_VAL; foreach(KoPathPoint *p, points) { // the node point must be hit if the point is not selected yet if (! m_pointSelection.contains(p) && ! roi.contains(p->point())) continue; // check for the control points first as otherwise it is no longer // possible to change the control points when they are the same as the point if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) { qreal dist = squaredDistance(roi.center(), p->controlPoint1()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint1; minDistance = dist; } } if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) { qreal dist = squaredDistance(roi.center(), p->controlPoint2()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::ControlPoint2; minDistance = dist; } } // check the node point at last qreal dist = squaredDistance(roi.center(), p->point()); if (dist < minDistance) { bestPoint = p; bestPointType = KoPathPoint::Node; minDistance = dist; } } if (! bestPoint) return; useCursor(m_moveCursor); if (bestPointType == KoPathPoint::Node) emit statusTextChanged(i18n("Drag to move point. Shift click to change point type.")); else emit statusTextChanged(i18n("Drag to move control point.")); PointHandle *prev = dynamic_cast(m_activeHandle); if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType) return; // no change; if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = new PointHandle(this, bestPoint, bestPointType); m_activeHandle->repaint(); return; } } } useCursor(m_selectCursor); if (m_activeHandle) m_activeHandle->repaint(); delete m_activeHandle; m_activeHandle = 0; PathSegment *hoveredSegment = segmentAtPoint(event->point); if(hoveredSegment) { useCursor(Qt::PointingHandCursor); emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point.")); m_activeSegment = hoveredSegment; } else { uint selectedPointCount = m_pointSelection.size(); if (selectedPointCount == 0) emit statusTextChanged(""); else if (selectedPointCount == 1) emit statusTextChanged(i18n("Press B to break path at selected point.")); else emit statusTextChanged(i18n("Press B to break path at selected segments.")); } } void KoPathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { const bool hadNoSelection = !m_pointSelection.hasSelection(); m_currentStrategy->finishInteraction(event->modifiers()); KUndo2Command *command = m_currentStrategy->createCommand(); if (command) d->canvas->addCommand(command); if (hadNoSelection && dynamic_cast(m_currentStrategy) && !m_pointSelection.hasSelection()) { // the click didn't do anything at all. Allow it to be used by others. event->ignore(); } delete m_currentStrategy; m_currentStrategy = 0; if (m_pointSelection.selectedShapes().count() == 1) - emit pathChanged(m_pointSelection.selectedShapes().first()); + emit pathChanged(m_pointSelection.selectedShapes().constFirst()); else emit pathChanged(0); } } void KoPathTool::keyPressEvent(QKeyEvent *event) { Q_D(KoToolBase); if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers()); } break; case Qt::Key_Escape: m_currentStrategy->cancelInteraction(); delete m_currentStrategy; m_currentStrategy = 0; break; default: event->ignore(); return; } } else { switch (event->key()) { // TODO move these to the actions in the constructor. case Qt::Key_I: { KoDocumentResourceManager *rm = d->canvas->shapeController()->resourceManager(); int handleRadius = rm->handleRadius(); if (event->modifiers() & Qt::ControlModifier) handleRadius--; else handleRadius++; rm->setHandleRadius(handleRadius); break; } #ifndef NDEBUG case Qt::Key_D: if (m_pointSelection.objectCount() == 1) { QList selectedPoints = m_pointSelection.selectedPointsData(); KoPathShapePrivate *p = static_cast(selectedPoints[0].pathShape->priv()); p->debugPath(); } break; #endif case Qt::Key_B: if (m_pointSelection.size() == 1) breakAtPoint(); else if (m_pointSelection.size() >= 2) breakAtSegment(); break; default: event->ignore(); return; } } event->accept(); } void KoPathTool::keyReleaseEvent(QKeyEvent *event) { if (m_currentStrategy) { switch (event->key()) { case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Shift: case Qt::Key_Meta: if (! event->isAutoRepeat()) { m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier); } break; default: break; } } event->accept(); } void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_D(KoToolBase); event->ignore(); // check if we are doing something else at the moment if (m_currentStrategy) return; PathSegment *s = segmentAtPoint(event->point); if (!s) return; if (s->isValid()) { QList segments; segments.append(KoPathPointData(s->path, s->path->pathPointIndex(s->segmentStart))); KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, s->positionOnSegment); d->canvas->addCommand(cmd); foreach (KoPathPoint * p, cmd->insertedPoints()) { m_pointSelection.add(p, false); } updateActions(); event->accept(); } delete s; } KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point) { Q_D(KoToolBase); // TODO: use global click proximity once added to the canvas resource provider const int clickProximity = 5; // convert click proximity to point using the current zoom level QPointF clickOffset = d->canvas->viewConverter()->viewToDocument(QPointF(clickProximity, clickProximity)); // the max allowed distance from a segment const qreal maxSquaredDistance = clickOffset.x()*clickOffset.x(); PathSegment *segment = new PathSegment; foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { KoParameterShape * parameterShape = dynamic_cast(shape); if (parameterShape && parameterShape->isParametricShape()) continue; // convert document point to shape coordinates QPointF p = shape->documentToShape(point); // our region of interest, i.e. a region around our mouse position QRectF roi(p - clickOffset, p + clickOffset); qreal minSqaredDistance = HUGE_VAL; // check all segments of this shape which intersect the region of interest QList segments = shape->segmentsAt(roi); foreach (const KoPathSegment &s, segments) { qreal nearestPointParam = s.nearestPoint(p); QPointF nearestPoint = s.pointAt(nearestPointParam); QPointF diff = p - nearestPoint; qreal squaredDistance = diff.x()*diff.x() + diff.y()*diff.y(); // are we within the allowed distance ? if (squaredDistance > maxSquaredDistance) continue; // are we closer to the last closest point ? if (squaredDistance < minSqaredDistance) { segment->path = shape; segment->segmentStart = s.first(); segment->positionOnSegment = nearestPointParam; } } } if (!segment->isValid()) { delete segment; segment = 0; } return segment; } void KoPathTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_D(KoToolBase); Q_UNUSED(toolActivation); // retrieve the actual global handle radius m_handleRadius = handleRadius(); d->canvas->snapGuide()->reset(); repaintDecorations(); QList selectedShapes; foreach(KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (shape->isEditable() && pathShape) { // as the tool is just in activation repaintDecorations does not yet get called // so we need to use repaint of the tool and it is only needed to repaint the // current canvas repaint(pathShape->boundingRect()); selectedShapes.append(pathShape); } } if (selectedShapes.isEmpty()) { emit done(); return; } m_pointSelection.setSelectedShapes(selectedShapes); useCursor(m_selectCursor); connect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate())); updateOptionsWidget(); updateActions(); } void KoPathTool::activate() { Q_D(KoToolBase); QSet shapes; foreach(KoShape *shape, d->canvas->shapeManager()->selection()->selectedShapes()) { QSet delegates = shape->toolDelegates(); if (delegates.isEmpty()) { shapes << shape; } else { shapes += delegates; } } activate(DefaultActivation, shapes); } void KoPathTool::updateOptionsWidget() { PathToolOptionWidget::Types type; QList selectedShapes = m_pointSelection.selectedShapes(); foreach(KoPathShape *shape, selectedShapes) { KoParameterShape * parameterShape = dynamic_cast(shape); type |= parameterShape && parameterShape->isParametricShape() ? PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath; } if (selectedShapes.count() == 1) emit pathChanged(selectedShapes.first()); else emit pathChanged(0); emit typeChanged(type); } void KoPathTool::updateActions() { const bool hasPointsSelected = m_pointSelection.hasSelection(); m_actionPathPointCorner->setEnabled(hasPointsSelected); m_actionPathPointSmooth->setEnabled(hasPointsSelected); m_actionPathPointSymmetric->setEnabled(hasPointsSelected); m_actionRemovePoint->setEnabled(hasPointsSelected); m_actionBreakPoint->setEnabled(hasPointsSelected); m_actionCurvePoint->setEnabled(hasPointsSelected); m_actionLinePoint->setEnabled(hasPointsSelected); bool hasSegmentsSelected = false; if (hasPointsSelected && m_pointSelection.size() > 1) hasSegmentsSelected = !m_pointSelection.selectedSegmentsData().isEmpty(); m_actionAddPoint->setEnabled(hasSegmentsSelected); m_actionLineSegment->setEnabled(hasSegmentsSelected); m_actionCurveSegment->setEnabled(hasSegmentsSelected); const uint objectCount = m_pointSelection.objectCount(); const uint pointCount = m_pointSelection.size(); m_actionBreakSegment->setEnabled(objectCount == 1 && pointCount == 2); m_actionJoinSegment->setEnabled(objectCount == 1 && pointCount == 2); m_actionMergePoints->setEnabled(objectCount == 1 && pointCount == 2); } void KoPathTool::deactivate() { Q_D(KoToolBase); disconnect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate())); m_pointSelection.clear(); m_pointSelection.setSelectedShapes(QList()); delete m_activeHandle; m_activeHandle = 0; delete m_activeSegment; m_activeSegment = 0; delete m_currentStrategy; m_currentStrategy = 0; d->canvas->snapGuide()->reset(); } void KoPathTool::documentResourceChanged(int key, const QVariant & res) { if (key == KoDocumentResourceManager::HandleRadius) { int oldHandleRadius = m_handleRadius; m_handleRadius = res.toUInt(); // repaint with the bigger of old and new handle radius int maxRadius = qMax(m_handleRadius, oldHandleRadius); foreach(KoPathShape *shape, m_pointSelection.selectedShapes()) { QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect(); repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius)); } } } void KoPathTool::pointSelectionChanged() { Q_D(KoToolBase); updateActions(); d->canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList()); emit selectionChanged(m_pointSelection.hasSelection()); } void KoPathTool::repaint(const QRectF &repaintRect) { Q_D(KoToolBase); //debugFlake <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius; // widen border to take antialiasing into account qreal radius = m_handleRadius + 1; d->canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius)); } void KoPathTool::deleteSelection() { removePoints(); } KoToolSelection * KoPathTool::selection() { return &m_pointSelection; } diff --git a/libs/flake/tools/KoZoomTool.h b/libs/flake/tools/KoZoomTool.h index 4a63c1e4aa0..02bc5d15e22 100644 --- a/libs/flake/tools/KoZoomTool.h +++ b/libs/flake/tools/KoZoomTool.h @@ -1,77 +1,78 @@ /* This file is part of the KDE project * * Copyright (C) 2006-2007 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * * 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. */ #ifndef KOZOOMTOOL_H #define KOZOOMTOOL_H #include "KoInteractionTool.h" #include class KoCanvasBase; class KoCanvasController; /// \internal class KoZoomTool : public KoInteractionTool { +Q_OBJECT public: /** * Create a new tool; typically not called by applications, only by the KoToolManager * @param canvas the canvas this tool works for. */ explicit KoZoomTool(KoCanvasBase *canvas); /// reimplemented method virtual void wheelEvent(KoPointerEvent *event); /// reimplemented method virtual void mouseReleaseEvent(KoPointerEvent *event); /// reimplemented method virtual void mouseMoveEvent(KoPointerEvent *event); /// reimplemented method virtual void keyPressEvent(QKeyEvent *event); /// reimplemented method virtual void keyReleaseEvent(QKeyEvent *event); /// reimplemented method virtual void activate(ToolActivation toolActivation, const QSet &shapes); /// reimplemented method virtual void mouseDoubleClickEvent(KoPointerEvent *event); void setCanvasController(KoCanvasController *controller) { m_controller = controller; } void setZoomInMode(bool zoomIn); protected: QWidget *createOptionWidget(); private: virtual KoInteractionStrategy *createStrategy(KoPointerEvent *event); void updateCursor(bool swap); KoCanvasController *m_controller; QCursor m_inCursor; QCursor m_outCursor; bool m_temporary; bool m_zoomInMode; }; #endif diff --git a/libs/kundo2/kundo2stack.cpp b/libs/kundo2/kundo2stack.cpp index b11aabe74ce..b4a01b10bc0 100644 --- a/libs/kundo2/kundo2stack.cpp +++ b/libs/kundo2/kundo2stack.cpp @@ -1,1441 +1,1441 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "kundo2stack.h" #include "kundo2stack_p.h" #include "kundo2group.h" #include #include #ifndef QT_NO_UNDOCOMMAND /*! \class KUndo2Command \brief The KUndo2Command class is the base class of all commands stored on a KUndo2QStack. \since 4.2 For an overview of Qt's Undo Framework, see the \l{Overview of Qt's Undo Framework}{overview document}. A KUndo2Command represents a single editing action on a document; for example, inserting or deleting a block of text in a text editor. KUndo2Command can apply a change to the document with redo() and undo the change with undo(). The implementations for these functions must be provided in a derived class. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 0 A KUndo2Command has an associated text(). This is a short string describing what the command does. It is used to update the text properties of the stack's undo and redo actions; see KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). KUndo2Command objects are owned by the stack they were pushed on. KUndo2QStack deletes a command if it has been undone and a new command is pushed. For example: \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 1 In effect, when a command is pushed, it becomes the top-most command on the stack. To support command compression, KUndo2Command has an id() and the virtual function mergeWith(). These functions are used by KUndo2QStack::push(). To support command macros, a KUndo2Command object can have any number of child commands. Undoing or redoing the parent command will cause the child commands to be undone or redone. A command can be assigned to a parent explicitly in the constructor. In this case, the command will be owned by the parent. The parent in this case is usually an empty command, in that it doesn't provide its own implementation of undo() and redo(). Instead, it uses the base implementations of these functions, which simply call undo() or redo() on all its children. The parent should, however, have a meaningful text(). \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 2 Another way to create macros is to use the convenience functions KUndo2QStack::beginMacro() and KUndo2QStack::endMacro(). \sa KUndo2QStack */ /*! Constructs a KUndo2Command object with the given \a parent and \a text. If \a parent is not 0, this command is appended to parent's child list. The parent command then owns this command and will delete it in its destructor. \sa ~KUndo2Command() */ KUndo2Command::KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent): m_hasParent(parent != 0), m_timedID(0), m_endOfCommand(QTime::currentTime()) { d = new KUndo2CommandPrivate; if (parent != 0) { parent->d->child_list.append(this); } setText(text); setTime(); } /*! Constructs a KUndo2Command object with parent \a parent. If \a parent is not 0, this command is appended to parent's child list. The parent command then owns this command and will delete it in its destructor. \sa ~KUndo2Command() */ KUndo2Command::KUndo2Command(KUndo2Command *parent): m_hasParent(parent != 0),m_timedID(0) { d = new KUndo2CommandPrivate; if (parent != 0) parent->d->child_list.append(this); setTime(); } /*! Destroys the KUndo2Command object and all child commands. \sa KUndo2Command() */ KUndo2Command::~KUndo2Command() { qDeleteAll(d->child_list); delete d; } /*! Returns the ID of this command. A command ID is used in command compression. It must be an integer unique to this command's class, or -1 if the command doesn't support compression. If the command supports compression this function must be overridden in the derived class to return the correct ID. The base implementation returns -1. KUndo2QStack::push() will only try to merge two commands if they have the same ID, and the ID is not -1. \sa mergeWith(), KUndo2QStack::push() */ int KUndo2Command::id() const { return -1; } /*! Attempts to merge this command with \a command. Returns true on success; otherwise returns false. If this function returns true, calling this command's redo() must have the same effect as redoing both this command and \a command. Similarly, calling this command's undo() must have the same effect as undoing \a command and this command. KUndo2QStack will only try to merge two commands if they have the same id, and the id is not -1. The default implementation returns false. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 3 \sa id() KUndo2QStack::push() */ bool KUndo2Command::mergeWith(const KUndo2Command *command) { Q_UNUSED(command); return false; } /*! Applies a change to the document. This function must be implemented in the derived class. Calling KUndo2QStack::push(), KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to undefined beahavior. The default implementation calls redo() on all child commands. \sa undo() */ void KUndo2Command::redo() { for (int i = 0; i < d->child_list.size(); ++i) d->child_list.at(i)->redo(); } /*! Reverts a change to the document. After undo() is called, the state of the document should be the same as before redo() was called. This function must be implemented in the derived class. Calling KUndo2QStack::push(), KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to undefined beahavior. The default implementation calls undo() on all child commands in reverse order. \sa redo() */ void KUndo2Command::undo() { for (int i = d->child_list.size() - 1; i >= 0; --i) d->child_list.at(i)->undo(); } /*! Returns a short text string describing what this command does; for example, "insert text". The text is used when the text properties of the stack's undo and redo actions are updated. \sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction() */ QString KUndo2Command::actionText() const { if(d->actionText!=0) return d->actionText; else return QString(); } /*! Returns a short text string describing what this command does; for example, "insert text". The text is used when the text properties of the stack's undo and redo actions are updated. \sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction() */ KUndo2MagicString KUndo2Command::text() const { return d->text; } /*! Sets the command's text to be the \a text specified. The specified text should be a short user-readable string describing what this command does. \sa text() KUndo2QStack::createUndoAction() KUndo2QStack::createRedoAction() */ void KUndo2Command::setText(const KUndo2MagicString &undoText) { d->text = undoText; d->actionText = undoText.toSecondaryString(); } /*! \since 4.4 Returns the number of child commands in this command. \sa child() */ int KUndo2Command::childCount() const { return d->child_list.count(); } /*! \since 4.4 Returns the child command at \a index. \sa childCount(), KUndo2QStack::command() */ const KUndo2Command *KUndo2Command::child(int index) const { if (index < 0 || index >= d->child_list.count()) return 0; return d->child_list.at(index); } bool KUndo2Command::hasParent() { return m_hasParent; } int KUndo2Command::timedId() { return m_timedID; } void KUndo2Command::setTimedID(int value) { m_timedID = value; } bool KUndo2Command::timedMergeWith(KUndo2Command *other) { if(other->timedId() == this->timedId() && other->timedId()!=-1 ) m_mergeCommandsVector.append(other); else return false; return true; } void KUndo2Command::setTime() { m_timeOfCreation = QTime::currentTime(); } QTime KUndo2Command::time() { return m_timeOfCreation; } void KUndo2Command::setEndTime() { m_endOfCommand = QTime::currentTime(); } QTime KUndo2Command::endTime() { return m_endOfCommand; } void KUndo2Command::undoMergedCommands() { undo(); if (!mergeCommandsVector().isEmpty()) { QVectorIterator it(mergeCommandsVector()); it.toFront(); while (it.hasNext()) { KUndo2Command* cmd = it.next(); cmd->undoMergedCommands(); } } } void KUndo2Command::redoMergedCommands() { if (!mergeCommandsVector().isEmpty()) { QVectorIterator it(mergeCommandsVector()); it.toBack(); while (it.hasPrevious()) { KUndo2Command* cmd = it.previous(); cmd->redoMergedCommands(); } } redo(); } QVector KUndo2Command::mergeCommandsVector() { return m_mergeCommandsVector; } bool KUndo2Command::isMerged() { return !m_mergeCommandsVector.isEmpty(); } KUndo2CommandExtraData* KUndo2Command::extraData() const { return d->extraData.data(); } void KUndo2Command::setExtraData(KUndo2CommandExtraData *data) { d->extraData.reset(data); } #endif // QT_NO_UNDOCOMMAND #ifndef QT_NO_UNDOSTACK /*! \class KUndo2QStack \brief The KUndo2QStack class is a stack of KUndo2Command objects. \since 4.2 For an overview of Qt's Undo Framework, see the \l{Overview of Qt's Undo Framework}{overview document}. An undo stack maintains a stack of commands that have been applied to a document. New commands are pushed on the stack using push(). Commands can be undone and redone using undo() and redo(), or by triggering the actions returned by createUndoAction() and createRedoAction(). KUndo2QStack keeps track of the \a current command. This is the command which will be executed by the next call to redo(). The index of this command is returned by index(). The state of the edited object can be rolled forward or back using setIndex(). If the top-most command on the stack has already been redone, index() is equal to count(). KUndo2QStack provides support for undo and redo actions, command compression, command macros, and supports the concept of a \e{clean state}. \section1 Undo and Redo Actions KUndo2QStack provides convenient undo and redo QAction objects, which can be inserted into a menu or a toolbar. When commands are undone or redone, KUndo2QStack updates the text properties of these actions to reflect what change they will trigger. The actions are also disabled when no command is available for undo or redo. These actions are returned by KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). \section1 Command Compression and Macros Command compression is useful when several commands can be compressed into a single command that can be undone and redone in a single operation. For example, when a user types a character in a text editor, a new command is created. This command inserts the character into the document at the cursor position. However, it is more convenient for the user to be able to undo or redo typing of whole words, sentences, or paragraphs. Command compression allows these single-character commands to be merged into a single command which inserts or deletes sections of text. For more information, see KUndo2Command::mergeWith() and push(). A command macro is a sequence of commands, all of which are undone and redone in one go. Command macros are created by giving a command a list of child commands. Undoing or redoing the parent command will cause the child commands to be undone or redone. Command macros may be created explicitly by specifying a parent in the KUndo2Command constructor, or by using the convenience functions beginMacro() and endMacro(). Although command compression and macros appear to have the same effect to the user, they often have different uses in an application. Commands that perform small changes to a document may be usefully compressed if there is no need to individually record them, and if only larger changes are relevant to the user. However, for commands that need to be recorded individually, or those that cannot be compressed, it is useful to use macros to provide a more convenient user experience while maintaining a record of each command. \section1 Clean State KUndo2QStack supports the concept of a clean state. When the document is saved to disk, the stack can be marked as clean using setClean(). Whenever the stack returns to this state through undoing and redoing commands, it emits the signal cleanChanged(). This signal is also emitted when the stack leaves the clean state. This signal is usually used to enable and disable the save actions in the application, and to update the document's title to reflect that it contains unsaved changes. \sa KUndo2Command, KUndo2View */ #ifndef QT_NO_ACTION KUndo2Action::KUndo2Action(const QString &textTemplate, const QString &defaultText, QObject *parent) : QAction(parent) { m_textTemplate = textTemplate; m_defaultText = defaultText; } void KUndo2Action::setPrefixedText(const QString &text) { if (text.isEmpty()) setText(m_defaultText); else setText(m_textTemplate.arg(text)); } #endif // QT_NO_ACTION /*! \internal Sets the current index to \a idx, emitting appropriate signals. If \a clean is true, makes \a idx the clean index as well. */ void KUndo2QStack::setIndex(int idx, bool clean) { bool was_clean = m_index == m_clean_index; if (m_lastMergedIndex <= idx) { m_lastMergedSetCount = idx - m_lastMergedIndex; } else { m_lastMergedSetCount = 1; m_lastMergedIndex = idx-1; } if(idx == 0){ m_lastMergedSetCount = 0; m_lastMergedIndex = 0; } if (idx != m_index) { m_index = idx; emit indexChanged(m_index); emit canUndoChanged(canUndo()); emit undoTextChanged(undoText()); emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } if (clean) m_clean_index = m_index; bool is_clean = m_index == m_clean_index; if (is_clean != was_clean) emit cleanChanged(is_clean); } void KUndo2QStack::purgeRedoState() { bool macro = !m_macro_stack.isEmpty(); if (macro) return; bool redoStateChanged = false; bool cleanStateChanged = false; while (m_index < m_command_list.size()) { delete m_command_list.takeLast(); redoStateChanged = true; } if (m_clean_index > m_index) { m_clean_index = -1; // we've deleted the clean state cleanStateChanged = true; } if (redoStateChanged) { emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } if (cleanStateChanged) { emit cleanChanged(isClean()); } } /*! \internal If the number of commands on the stack exceedes the undo limit, deletes commands from the bottom of the stack. Returns true if commands were deleted. */ bool KUndo2QStack::checkUndoLimit() { if (m_undo_limit <= 0 || !m_macro_stack.isEmpty() || m_undo_limit >= m_command_list.count()) return false; int del_count = m_command_list.count() - m_undo_limit; for (int i = 0; i < del_count; ++i) delete m_command_list.takeFirst(); m_index -= del_count; if (m_clean_index != -1) { if (m_clean_index < del_count) m_clean_index = -1; // we've deleted the clean command else m_clean_index -= del_count; } return true; } /*! Constructs an empty undo stack with the parent \a parent. The stack will initially be in the clean state. If \a parent is a KUndo2Group object, the stack is automatically added to the group. \sa push() */ KUndo2QStack::KUndo2QStack(QObject *parent) : QObject(parent), m_index(0), m_clean_index(0), m_group(0), m_undo_limit(0), m_useCumulativeUndoRedo(false), m_lastMergedSetCount(0), m_lastMergedIndex(0) { setTimeT1(5); setTimeT2(1); setStrokesN(2); #ifndef QT_NO_UNDOGROUP if (KUndo2Group *group = qobject_cast(parent)) group->addStack(this); #endif } /*! Destroys the undo stack, deleting any commands that are on it. If the stack is in a KUndo2Group, the stack is automatically removed from the group. \sa KUndo2QStack() */ KUndo2QStack::~KUndo2QStack() { #ifndef QT_NO_UNDOGROUP if (m_group != 0) m_group->removeStack(this); #endif clear(); } /*! Clears the command stack by deleting all commands on it, and returns the stack to the clean state.{ } Commands are not undone or redone; the state of the edited object remains unchanged. This function is usually used when the contents of the document are abandoned. \sa KUndo2QStack() */ void KUndo2QStack::clear() { if (m_command_list.isEmpty()) return; bool was_clean = isClean(); m_macro_stack.clear(); qDeleteAll(m_command_list); m_command_list.clear(); m_index = 0; m_clean_index = 0; emit indexChanged(0); emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); if (!was_clean) emit cleanChanged(true); } /*! Pushes \a cmd on the stack or merges it with the most recently executed command. In either case, executes \a cmd by calling its redo() function. If \a cmd's id is not -1, and if the id is the same as that of the most recently executed command, KUndo2QStack will attempt to merge the two commands by calling KUndo2Command::mergeWith() on the most recently executed command. If KUndo2Command::mergeWith() returns true, \a cmd is deleted and false is returned. In all other cases \a cmd is simply pushed on the stack and true is returned. If commands were undone before \a cmd was pushed, the current command and all commands above it are deleted. Hence \a cmd always ends up being the top-most on the stack. Once a command is pushed, the stack takes ownership of it. There are no getters to return the command, since modifying it after it has been executed will almost always lead to corruption of the document's state. \sa KUndo2Command::id() KUndo2Command::mergeWith() */ bool KUndo2QStack::push(KUndo2Command *cmd) { cmd->redoMergedCommands(); cmd->setEndTime(); bool macro = !m_macro_stack.isEmpty(); KUndo2Command *cur = 0; if (macro) { KUndo2Command *macro_cmd = m_macro_stack.last(); if (!macro_cmd->d->child_list.isEmpty()) cur = macro_cmd->d->child_list.last(); } else { if (m_index > 0) cur = m_command_list.at(m_index - 1); while (m_index < m_command_list.size()) delete m_command_list.takeLast(); if (m_clean_index > m_index) m_clean_index = -1; // we've deleted the clean state } bool try_merge = cur != 0 && cur->id() != -1 && cur->id() == cmd->id() && (macro || m_index != m_clean_index); /*! *Here we are going to try to merge several commands together using the QVector field in the commands using *3 parameters. N : Number of commands that should remain individual at the top of the stack. T1 : Time lapsed between current command and previously merged command -- signal to *merge throughout the stack. T2 : Time lapsed between two commands signalling both commands belong to the same set *Whenever a KUndo2Command is initialized -- it consists of a start-time and when it is pushed --an end time. *Every time a command is pushed -- it checks whether the command pushed was pushed after T1 seconds of the last merged command *Then the merging begins with each group depending on the time in between each command (T2). * *@TODO : Currently it is not able to merge two merged commands together. */ if (!macro && m_command_list.size() > 1 && cmd->timedId() != -1 && m_useCumulativeUndoRedo) { KUndo2Command* lastcmd = m_command_list.last(); if (qAbs(cmd->time().msecsTo(lastcmd->endTime())) < m_timeT2 * 1000) { m_lastMergedSetCount++; } else { m_lastMergedSetCount = 0; m_lastMergedIndex = m_index-1; } if (lastcmd->timedId() == -1){ m_lastMergedSetCount = 0; m_lastMergedIndex = m_index; } if (m_lastMergedSetCount > m_strokesN) { KUndo2Command* toMerge = m_command_list.at(m_lastMergedIndex); if (toMerge && m_command_list.size() >= m_lastMergedIndex + 1 && m_command_list.at(m_lastMergedIndex + 1)) { if(toMerge->timedMergeWith(m_command_list.at(m_lastMergedIndex + 1))){ m_command_list.removeAt(m_lastMergedIndex + 1); } m_lastMergedSetCount--; m_lastMergedIndex = m_command_list.indexOf(toMerge); } } m_index = m_command_list.size(); if(m_lastMergedIndextime().msecsTo(m_command_list.at(m_lastMergedIndex)->endTime()) < -m_timeT1 * 1000) { //T1 time elapsed QListIterator it(m_command_list); it.toBack(); m_lastMergedSetCount = 1; while (it.hasPrevious()) { KUndo2Command* curr = it.previous(); KUndo2Command* lastCmdInCurrent = curr; if (!lastcmd->mergeCommandsVector().isEmpty()) { - if (qAbs(lastcmd->mergeCommandsVector().last()->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent && lastcmd != curr) { + if (qAbs(lastcmd->mergeCommandsVector().constLast()->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent && lastcmd != curr) { if(lastcmd->timedMergeWith(curr)){ if (m_command_list.contains(curr)) { m_command_list.removeOne(curr); } } } else { lastcmd = curr; //end of a merge set } } else { if (qAbs(lastcmd->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent &&lastcmd!=curr) { if(lastcmd->timedMergeWith(curr)){ if (m_command_list.contains(curr)){ m_command_list.removeOne(curr); } } } else { lastcmd = curr; //end of a merge set } } } m_lastMergedIndex = m_command_list.size()-1; } } m_index = m_command_list.size(); } if (try_merge && cur->mergeWith(cmd)) { delete cmd; cmd = 0; if (!macro) { emit indexChanged(m_index); emit canUndoChanged(canUndo()); emit undoTextChanged(undoText()); emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } } else { if (macro) { m_macro_stack.last()->d->child_list.append(cmd); } else { m_command_list.append(cmd); if(checkUndoLimit()) { m_lastMergedIndex = m_index - m_strokesN; } setIndex(m_index + 1, false); } } return cmd; } /*! Marks the stack as clean and emits cleanChanged() if the stack was not already clean. Whenever the stack returns to this state through the use of undo/redo commands, it emits the signal cleanChanged(). This signal is also emitted when the stack leaves the clean state. \sa isClean(), cleanIndex() */ void KUndo2QStack::setClean() { if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::setClean(): cannot set clean in the middle of a macro"); return; } setIndex(m_index, true); } /*! If the stack is in the clean state, returns true; otherwise returns false. \sa setClean() cleanIndex() */ bool KUndo2QStack::isClean() const { if (!m_macro_stack.isEmpty()) return false; return m_clean_index == m_index; } /*! Returns the clean index. This is the index at which setClean() was called. A stack may not have a clean index. This happens if a document is saved, some commands are undone, then a new command is pushed. Since push() deletes all the undone commands before pushing the new command, the stack can't return to the clean state again. In this case, this function returns -1. \sa isClean() setClean() */ int KUndo2QStack::cleanIndex() const { return m_clean_index; } /*! Undoes the command below the current command by calling KUndo2Command::undo(). Decrements the current command index. If the stack is empty, or if the bottom command on the stack has already been undone, this function does nothing. \sa redo() index() */ void KUndo2QStack::undo() { if (m_index == 0) return; if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::undo(): cannot undo in the middle of a macro"); return; } int idx = m_index - 1; m_command_list.at(idx)->undoMergedCommands(); setIndex(idx, false); } /*! Redoes the current command by calling KUndo2Command::redo(). Increments the current command index. If the stack is empty, or if the top command on the stack has already been redone, this function does nothing. \sa undo() index() */ void KUndo2QStack::redo() { if (m_index == m_command_list.size()) return; if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::redo(): cannot redo in the middle of a macro"); return; } m_command_list.at(m_index)->redoMergedCommands(); setIndex(m_index + 1, false); } /*! Returns the number of commands on the stack. Macro commands are counted as one command. \sa index() setIndex() command() */ int KUndo2QStack::count() const { return m_command_list.size(); } /*! Returns the index of the current command. This is the command that will be executed on the next call to redo(). It is not always the top-most command on the stack, since a number of commands may have been undone. \sa undo() redo() count() */ int KUndo2QStack::index() const { return m_index; } /*! Repeatedly calls undo() or redo() until the current command index reaches \a idx. This function can be used to roll the state of the document forwards of backwards. indexChanged() is emitted only once. \sa index() count() undo() redo() */ void KUndo2QStack::setIndex(int idx) { if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::setIndex(): cannot set index in the middle of a macro"); return; } if (idx < 0) idx = 0; else if (idx > m_command_list.size()) idx = m_command_list.size(); int i = m_index; while (i < idx) { m_command_list.at(i++)->redoMergedCommands(); notifySetIndexChangedOneCommand(); } while (i > idx) { m_command_list.at(--i)->undoMergedCommands(); notifySetIndexChangedOneCommand(); } setIndex(idx, false); } /** * Called by setIndex after every command execution. It is needed by * Krita to insert barriers between different kind of commands */ void KUndo2QStack::notifySetIndexChangedOneCommand() { } /*! Returns true if there is a command available for undo; otherwise returns false. This function returns false if the stack is empty, or if the bottom command on the stack has already been undone. Synonymous with index() == 0. \sa index() canRedo() */ bool KUndo2QStack::canUndo() const { if (!m_macro_stack.isEmpty()) return false; return m_index > 0; } /*! Returns true if there is a command available for redo; otherwise returns false. This function returns false if the stack is empty or if the top command on the stack has already been redone. Synonymous with index() == count(). \sa index() canUndo() */ bool KUndo2QStack::canRedo() const { if (!m_macro_stack.isEmpty()) return false; return m_index < m_command_list.size(); } /*! Returns the text of the command which will be undone in the next call to undo(). \sa KUndo2Command::text() redoActionText() undoItemText() */ QString KUndo2QStack::undoText() const { if (!m_macro_stack.isEmpty()) return QString(); if (m_index > 0 && m_command_list.at(m_index-1)!=0) return m_command_list.at(m_index - 1)->actionText(); return QString(); } /*! Returns the text of the command which will be redone in the next call to redo(). \sa KUndo2Command::text() undoActionText() redoItemText() */ QString KUndo2QStack::redoText() const { if (!m_macro_stack.isEmpty()) return QString(); if (m_index < m_command_list.size()) return m_command_list.at(m_index)->actionText(); return QString(); } #ifndef QT_NO_ACTION /*! Creates an undo QAction object with the given \a parent. Triggering this action will cause a call to undo(). The text of this action is the text of the command which will be undone in the next call to undo(), prefixed by the specified \a prefix. If there is no command available for undo, this action will be disabled. If \a prefix is empty, the default prefix "Undo" is used. \sa createRedoAction(), canUndo(), KUndo2Command::text() */ QAction *KUndo2QStack::createUndoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Undo %1"), i18nc("Default text for undo action", "Undo"), parent); result->setEnabled(canUndo()); result->setPrefixedText(undoText()); connect(this, SIGNAL(canUndoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(undoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(undo())); return result; } /*! Creates an redo QAction object with the given \a parent. Triggering this action will cause a call to redo(). The text of this action is the text of the command which will be redone in the next call to redo(), prefixed by the specified \a prefix. If there is no command available for redo, this action will be disabled. If \a prefix is empty, the default prefix "Redo" is used. \sa createUndoAction(), canRedo(), KUndo2Command::text() */ QAction *KUndo2QStack::createRedoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Redo %1"), i18nc("Default text for redo action", "Redo"), parent); result->setEnabled(canRedo()); result->setPrefixedText(redoText()); connect(this, SIGNAL(canRedoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(redoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(redo())); return result; } #endif // QT_NO_ACTION /*! Begins composition of a macro command with the given \a text description. An empty command described by the specified \a text is pushed on the stack. Any subsequent commands pushed on the stack will be appended to the empty command's children until endMacro() is called. Calls to beginMacro() and endMacro() may be nested, but every call to beginMacro() must have a matching call to endMacro(). While a macro is composed, the stack is disabled. This means that: \list \i indexChanged() and cleanChanged() are not emitted, \i canUndo() and canRedo() return false, \i calling undo() or redo() has no effect, \i the undo/redo actions are disabled. \endlist The stack becomes enabled and appropriate signals are emitted when endMacro() is called for the outermost macro. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 4 This code is equivalent to: \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 5 \sa endMacro() */ void KUndo2QStack::beginMacro(const KUndo2MagicString &text) { KUndo2Command *cmd = new KUndo2Command(); cmd->setText(text); if (m_macro_stack.isEmpty()) { while (m_index < m_command_list.size()) delete m_command_list.takeLast(); if (m_clean_index > m_index) m_clean_index = -1; // we've deleted the clean state m_command_list.append(cmd); } else { m_macro_stack.last()->d->child_list.append(cmd); } m_macro_stack.append(cmd); if (m_macro_stack.count() == 1) { emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); } } /*! Ends composition of a macro command. If this is the outermost macro in a set nested macros, this function emits indexChanged() once for the entire macro command. \sa beginMacro() */ void KUndo2QStack::endMacro() { if (m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::endMacro(): no matching beginMacro()"); return; } m_macro_stack.removeLast(); if (m_macro_stack.isEmpty()) { checkUndoLimit(); setIndex(m_index + 1, false); } } /*! \since 4.4 Returns a const pointer to the command at \a index. This function returns a const pointer, because modifying a command, once it has been pushed onto the stack and executed, almost always causes corruption of the state of the document, if the command is later undone or redone. \sa KUndo2Command::child() */ const KUndo2Command *KUndo2QStack::command(int index) const { if (index < 0 || index >= m_command_list.count()) return 0; return m_command_list.at(index); } /*! Returns the text of the command at index \a idx. \sa beginMacro() */ QString KUndo2QStack::text(int idx) const { if (idx < 0 || idx >= m_command_list.size()) return QString(); return m_command_list.at(idx)->text().toString(); } /*! \property KUndo2QStack::undoLimit \brief the maximum number of commands on this stack. \since 4.3 When the number of commands on a stack exceedes the stack's undoLimit, commands are deleted from the bottom of the stack. Macro commands (commands with child commands) are treated as one command. The default value is 0, which means that there is no limit. This property may only be set when the undo stack is empty, since setting it on a non-empty stack might delete the command at the current index. Calling setUndoLimit() on a non-empty stack prints a warning and does nothing. */ void KUndo2QStack::setUndoLimit(int limit) { if (!m_command_list.isEmpty()) { qWarning("KUndo2QStack::setUndoLimit(): an undo limit can only be set when the stack is empty"); return; } if (limit == m_undo_limit) return; m_undo_limit = limit; checkUndoLimit(); } int KUndo2QStack::undoLimit() const { return m_undo_limit; } /*! \property KUndo2QStack::active \brief the active status of this stack. An application often has multiple undo stacks, one for each opened document. The active stack is the one associated with the currently active document. If the stack belongs to a KUndo2Group, calls to KUndo2Group::undo() or KUndo2Group::redo() will be forwarded to this stack when it is active. If the KUndo2Group is watched by a KUndo2View, the view will display the contents of this stack when it is active. If the stack does not belong to a KUndo2Group, making it active has no effect. It is the programmer's responsibility to specify which stack is active by calling setActive(), usually when the associated document window receives focus. \sa KUndo2Group */ void KUndo2QStack::setActive(bool active) { #ifdef QT_NO_UNDOGROUP Q_UNUSED(active); #else if (m_group != 0) { if (active) m_group->setActiveStack(this); else if (m_group->activeStack() == this) m_group->setActiveStack(0); } #endif } bool KUndo2QStack::isActive() const { #ifdef QT_NO_UNDOGROUP return true; #else return m_group == 0 || m_group->activeStack() == this; #endif } void KUndo2QStack::setUseCumulativeUndoRedo(bool value) { m_useCumulativeUndoRedo = value; } bool KUndo2QStack::useCumulativeUndoRedo() { return m_useCumulativeUndoRedo; } void KUndo2QStack::setTimeT1(double value) { m_timeT1 = value; } double KUndo2QStack::timeT1() { return m_timeT1; } void KUndo2QStack::setTimeT2(double value) { m_timeT2 = value; } double KUndo2QStack::timeT2() { return m_timeT2; } int KUndo2QStack::strokesN() { return m_strokesN; } void KUndo2QStack::setStrokesN(int value) { m_strokesN = value; } QAction* KUndo2Stack::createRedoAction(KActionCollection* actionCollection, const QString& actionName) { QAction* action = KUndo2QStack::createRedoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(KStandardAction::name(KStandardAction::Redo)); } else { action->setObjectName(actionName); } action->setIcon(koIcon("edit-redo")); action->setIconText(i18n("Redo")); action->setShortcuts(KStandardShortcut::redo()); actionCollection->addAction(action->objectName(), action); return action; } QAction* KUndo2Stack::createUndoAction(KActionCollection* actionCollection, const QString& actionName) { QAction* action = KUndo2QStack::createUndoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(KStandardAction::name(KStandardAction::Undo)); } else { action->setObjectName(actionName); } action->setIcon(koIcon("edit-undo")); action->setIconText(i18n("Undo")); action->setShortcuts(KStandardShortcut::undo()); actionCollection->addAction(action->objectName(), action); return action; } /*! \fn void KUndo2QStack::indexChanged(int idx) This signal is emitted whenever a command modifies the state of the document. This happens when a command is undone or redone. When a macro command is undone or redone, or setIndex() is called, this signal is emitted only once. \a idx specifies the index of the current command, ie. the command which will be executed on the next call to redo(). \sa index() setIndex() */ /*! \fn void KUndo2QStack::cleanChanged(bool clean) This signal is emitted whenever the stack enters or leaves the clean state. If \a clean is true, the stack is in a clean state; otherwise this signal indicates that it has left the clean state. \sa isClean() setClean() */ /*! \fn void KUndo2QStack::undoTextChanged(const QString &undoText) This signal is emitted whenever the value of undoText() changes. It is used to update the text property of the undo action returned by createUndoAction(). \a undoText specifies the new text. */ /*! \fn void KUndo2QStack::canUndoChanged(bool canUndo) This signal is emitted whenever the value of canUndo() changes. It is used to enable or disable the undo action returned by createUndoAction(). \a canUndo specifies the new value. */ /*! \fn void KUndo2QStack::redoTextChanged(const QString &redoText) This signal is emitted whenever the value of redoText() changes. It is used to update the text property of the redo action returned by createRedoAction(). \a redoText specifies the new text. */ /*! \fn void KUndo2QStack::canRedoChanged(bool canRedo) This signal is emitted whenever the value of canRedo() changes. It is used to enable or disable the redo action returned by createRedoAction(). \a canRedo specifies the new value. */ KUndo2Stack::KUndo2Stack(QObject *parent): KUndo2QStack(parent) { } #endif // QT_NO_UNDOSTACK diff --git a/libs/kundo2/kundo2stack.h b/libs/kundo2/kundo2stack.h index 0f288352c90..c35c8f141cb 100644 --- a/libs/kundo2/kundo2stack.h +++ b/libs/kundo2/kundo2stack.h @@ -1,258 +1,259 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2STACK_H #define KUNDO2STACK_H #include #include #include #include #include #include #include "kundo2_export.h" class QAction; class KUndo2CommandPrivate; class KUndo2Group; class KActionCollection; #ifndef QT_NO_UNDOCOMMAND #include "kundo2magicstring.h" #include "kundo2commandextradata.h" class KUNDO2_EXPORT KUndo2Command { KUndo2CommandPrivate *d; int timedID; public: explicit KUndo2Command(KUndo2Command *parent = 0); explicit KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent = 0); virtual ~KUndo2Command(); virtual void undo(); virtual void redo(); QString actionText() const; KUndo2MagicString text() const; void setText(const KUndo2MagicString &text); virtual int id() const; virtual int timedId(); virtual void setTimedID(int timedID); virtual bool mergeWith(const KUndo2Command *other); virtual bool timedMergeWith(KUndo2Command *other); int childCount() const; const KUndo2Command *child(int index) const; bool hasParent(); virtual void setTime(); virtual QTime time(); virtual void setEndTime(); virtual QTime endTime(); virtual QVector mergeCommandsVector(); virtual bool isMerged(); virtual void undoMergedCommands(); virtual void redoMergedCommands(); /** * \return user-defined object associated with the command * * \see setExtraData() */ KUndo2CommandExtraData* extraData() const; /** * The user can assign an arbitrary object associated with the * command. The \p data object is owned by the command. If you assign * the object twice, the first one will be destroyed. */ void setExtraData(KUndo2CommandExtraData *data); private: Q_DISABLE_COPY(KUndo2Command) friend class KUndo2QStack; bool m_hasParent; int m_timedID; QTime m_timeOfCreation; QTime m_endOfCommand; QVector m_mergeCommandsVector; }; #endif // QT_NO_UNDOCOMMAND #ifndef QT_NO_UNDOSTACK class KUNDO2_EXPORT KUndo2QStack : public QObject { Q_OBJECT // Q_DECLARE_PRIVATE(KUndo2QStack) Q_PROPERTY(bool active READ isActive WRITE setActive) Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit) public: explicit KUndo2QStack(QObject *parent = 0); virtual ~KUndo2QStack(); void clear(); bool push(KUndo2Command *cmd); bool canUndo() const; bool canRedo() const; QString undoText() const; QString redoText() const; int count() const; int index() const; QString actionText(int idx) const; QString text(int idx) const; #ifndef QT_NO_ACTION QAction *createUndoAction(QObject *parent) const; QAction *createRedoAction(QObject *parent) const; #endif // QT_NO_ACTION bool isActive() const; bool isClean() const; int cleanIndex() const; void beginMacro(const KUndo2MagicString &text); void endMacro(); void setUndoLimit(int limit); int undoLimit() const; const KUndo2Command *command(int index) const; void setUseCumulativeUndoRedo(bool value); bool useCumulativeUndoRedo(); void setTimeT1(double value); double timeT1(); void setTimeT2(double value); double timeT2(); int strokesN(); void setStrokesN(int value); public Q_SLOTS: void setClean(); virtual void setIndex(int idx); virtual void undo(); virtual void redo(); void setActive(bool active = true); void purgeRedoState(); Q_SIGNALS: void indexChanged(int idx); void cleanChanged(bool clean); void canUndoChanged(bool canUndo); void canRedoChanged(bool canRedo); void undoTextChanged(const QString &undoActionText); void redoTextChanged(const QString &redoActionText); protected: virtual void notifySetIndexChangedOneCommand(); private: // from QUndoStackPrivate QList m_command_list; QList m_macro_stack; int m_index; int m_clean_index; KUndo2Group *m_group; int m_undo_limit; bool m_useCumulativeUndoRedo; double m_timeT1; double m_timeT2; int m_strokesN; int m_lastMergedSetCount; int m_lastMergedIndex; // also from QUndoStackPrivate void setIndex(int idx, bool clean); bool checkUndoLimit(); Q_DISABLE_COPY(KUndo2QStack) friend class KUndo2Group; }; class KUNDO2_EXPORT KUndo2Stack : public KUndo2QStack { +Q_OBJECT public: explicit KUndo2Stack(QObject *parent = 0); // functions from KUndoStack QAction* createRedoAction(KActionCollection* actionCollection, const QString& actionName = QString()); QAction* createUndoAction(KActionCollection* actionCollection, const QString& actionName = QString()); }; #endif // QT_NO_UNDOSTACK #endif // KUNDO2STACK_H diff --git a/libs/odf/tests/CMakeLists.txt b/libs/odf/tests/CMakeLists.txt index 703c7f1136f..e7de0e2d7a7 100644 --- a/libs/odf/tests/CMakeLists.txt +++ b/libs/odf/tests/CMakeLists.txt @@ -1,56 +1,60 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${KOODF_INCLUDES} ) # call: koodf_add_unit_test( LINK_LIBRARIES [ [...]] [GUI]) macro(KOODF_ADD_UNIT_TEST _TEST_NAME) ecm_add_test( ${ARGN} TEST_NAME "${_TEST_NAME}" NAME_PREFIX "libs-koodf-" ) endmacro() ########### next target ############### koodf_add_unit_test(TestKoGenStyles TestKoGenStyles.cpp LINK_LIBRARIES koodf KF5::I18n Qt5::Test) ########### next target ############### koodf_add_unit_test(TestOdfSettings TestOdfSettings.cpp LINK_LIBRARIES koodf KF5::I18n Qt5::Test) ########### next target ############### koodf_add_unit_test(TestKoOdfLoadingContext TestKoOdfLoadingContext.cpp LINK_LIBRARIES koodf KF5::I18n Qt5::Test) ########### next target ############### koodf_add_unit_test(TestXmlWriter TestXmlWriter.cpp LINK_LIBRARIES koodf Qt5::Test) ########### next target ############### koodf_add_unit_test(TestXmlReader TestXmlReader.cpp LINK_LIBRARIES koodf Qt5::Test) ########### next target ############### koodf_add_unit_test(TestXmlReaderWithoutSpaces TestXmlReaderWithoutSpaces.cpp LINK_LIBRARIES koodf Qt5::Test) ########### next target ############### koodf_add_unit_test(kodomtest kodomtest.cpp LINK_LIBRARIES koodf Qt5::Test) ########### next target ############### koodf_add_unit_test(TestStorage TestStorage.cpp LINK_LIBRARIES koodf KF5::I18n Qt5::Test) ########### next target ############### koodf_add_unit_test(TestKoUnit TestKoUnit.cpp LINK_LIBRARIES koodf Qt5::Test) ########### next target ############### koodf_add_unit_test(TestNumberStyle TestNumberStyle.cpp LINK_LIBRARIES koodf Qt5::Test) ########### next target ############### koodf_add_unit_test(TestKoElementReference TestKoElementReference.cpp LINK_LIBRARIES koodf Qt5::Test) +########### next target ############### + +koodf_add_unit_test(TestWriteStyleXml TestWriteStyleXml.cpp LINK_LIBRARIES koodf Qt5::Test) + ########### end ############### diff --git a/libs/odf/tests/TestKoGenStyles.cpp b/libs/odf/tests/TestKoGenStyles.cpp index 00170ae92a1..4f991703585 100644 --- a/libs/odf/tests/TestKoGenStyles.cpp +++ b/libs/odf/tests/TestKoGenStyles.cpp @@ -1,377 +1,378 @@ /* This file is part of the KDE project Copyright (C) 2004-2006 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2008 Girish Ramakrishnan Copyright (C) 2009 Thomas Zander 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. */ #include "TestKoGenStyles.h" #include #include #include #include #include #include #define TEST_BEGIN(publicId,systemId) \ { \ QByteArray cstr; \ QBuffer buffer( &cstr ); \ buffer.open( QIODevice::WriteOnly ); \ { \ KoXmlWriter writer( &buffer ); \ writer.startDocument( "r", publicId, systemId ); \ writer.startElement( "r" ) #define TEST_END_QTTEST(expected) \ writer.endElement(); \ writer.endDocument(); \ } \ buffer.putChar( '\0' ); /*null-terminate*/ \ QString expectedFull = QString::fromLatin1( "\n" ); \ expectedFull += expected; \ QString s1 = QString::fromLatin1( cstr ); \ QCOMPARE( expectedFull, s1 ); \ } void TestKoGenStyles::testLookup() { debugOdf ; KoGenStyles coll; QMap map1; map1.insert("map1key", "map1value"); QMap map2; map2.insert("map2key1", "map2value1"); map2.insert("map2key2", "map2value2"); QBuffer buffer; buffer.open(QIODevice::WriteOnly); KoXmlWriter childWriter(&buffer); childWriter.startElement("child"); childWriter.addAttribute("test:foo", "bar"); childWriter.endElement(); QString childContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size()); QBuffer buffer2; buffer2.open(QIODevice::WriteOnly); KoXmlWriter childWriter2(&buffer2); childWriter2.startElement("child2"); childWriter2.addAttribute("test:foo", "bar"); childWriter2.endElement(); QString childContents2 = QString::fromUtf8(buffer2.buffer(), buffer2.buffer().size()); KoGenStyle first(KoGenStyle::ParagraphAutoStyle, "paragraph"); first.addAttribute("style:master-page-name", "Standard"); first.addProperty("style:page-number", "0"); first.addProperty("style:foobar", "2", KoGenStyle::TextType); first.addStyleMap(map1); first.addStyleMap(map2); first.addChildElement("test", childContents); first.addChildElement("test", childContents2, KoGenStyle::TextType); QString firstName = coll.insert(first); debugOdf << "The first style got assigned the name" << firstName; QVERIFY(!firstName.isEmpty()); QCOMPARE(first.type(), KoGenStyle::ParagraphAutoStyle); KoGenStyle second(KoGenStyle::ParagraphAutoStyle, "paragraph"); second.addAttribute("style:master-page-name", "Standard"); second.addProperty("style:page-number", "0"); second.addProperty("style:foobar", "2", KoGenStyle::TextType); second.addStyleMap(map1); second.addStyleMap(map2); second.addChildElement("test", childContents); second.addChildElement("test", childContents2, KoGenStyle::TextType); QString secondName = coll.insert(second); debugOdf << "The second style got assigned the name" << secondName; QCOMPARE(firstName, secondName); // check that sharing works QCOMPARE(first, second); // check that operator== works :) const KoGenStyle* s = coll.style(firstName, "paragraph"); // check insert of existing style QVERIFY(s != 0); QCOMPARE(*s, first); s = coll.style("foobarblah", "paragraph"); // check insert of non-existing style QVERIFY(s == 0); KoGenStyle third(KoGenStyle::ParagraphAutoStyle, "paragraph", secondName); // inherited style third.addProperty("style:margin-left", "1.249cm"); third.addProperty("style:page-number", "0"); // same as parent third.addProperty("style:foobar", "3", KoGenStyle::TextType); // different from parent QCOMPARE(third.parentName(), secondName); QString thirdName = coll.insert(third, "P"); debugOdf << "The third style got assigned the name" << thirdName; QVERIFY(thirdName != firstName); QVERIFY(!thirdName.isEmpty()); KoGenStyle user(KoGenStyle::ParagraphStyle, "paragraph"); // differs from third since it doesn't inherit second, and has a different type user.addProperty("style:margin-left", "1.249cm"); QString userStyleName = coll.insert(user, "User", KoGenStyles::DontAddNumberToName); debugOdf << "The user style got assigned the name" << userStyleName; QCOMPARE(userStyleName, QString("User")); KoGenStyle sameAsParent(KoGenStyle::ParagraphAutoStyle, "paragraph", secondName); // inherited style sameAsParent.addAttribute("style:master-page-name", "Standard"); sameAsParent.addProperty("style:page-number", "0"); sameAsParent.addProperty("style:foobar", "2", KoGenStyle::TextType); sameAsParent.addStyleMap(map1); sameAsParent.addStyleMap(map2); sameAsParent.addChildElement("test", childContents); sameAsParent.addChildElement("test", childContents2, KoGenStyle::TextType); QString sapName = coll.insert(sameAsParent, "foobar"); debugOdf << "The 'same as parent' style got assigned the name" << sapName; QCOMPARE(sapName, secondName); QCOMPARE(coll.styles().count(), 3); // OK, now add a style marked as for styles.xml; it looks like the above style, but // since it's marked for styles.xml it shouldn't be shared with it. KoGenStyle headerStyle(KoGenStyle::ParagraphAutoStyle, "paragraph"); headerStyle.addAttribute("style:master-page-name", "Standard"); headerStyle.addProperty("style:page-number", "0"); headerStyle.addProperty("style:foobar", "2", KoGenStyle::TextType); headerStyle.addStyleMap(map1); headerStyle.addStyleMap(map2); headerStyle.setAutoStyleInStylesDotXml(true); + coll.insert(headerStyle, "foobar"); QCOMPARE(coll.styles().count(), 4); //QCOMPARE(coll.styles(KoGenStyle::ParagraphAutoStyle).count(), 2); //QCOMPARE(coll.styles(KoGenStyle::ParagraphStyle).count(), 1); // XML for first/second style TEST_BEGIN(0, 0); first.writeStyle(&writer, coll, "style:style", firstName, "style:paragraph-properties"); TEST_END_QTTEST("\n \n \n" " \n \n \n" " \n \n" " \n \n" " \n\n"); // XML for third style TEST_BEGIN(0, 0); third.writeStyle(&writer, coll, "style:style", thirdName, "style:paragraph-properties"); TEST_END_QTTEST("\n \n" " \n" " \n \n\n"); } void TestKoGenStyles::testLookupFlags() { KoGenStyles coll; KoGenStyle first(KoGenStyle::ParagraphAutoStyle, "paragraph"); first.addAttribute("style:master-page-name", "Standard"); first.addProperty("style:page-number", "0"); QString styleName = coll.insert(first, "P", KoGenStyles::DontAddNumberToName); QCOMPARE(styleName, QString("P")); styleName = coll.insert(first, "P"); QCOMPARE(styleName, QString("P")); KoGenStyle second(KoGenStyle::ParagraphAutoStyle, "paragraph"); second.addProperty("fo:text-align", "left"); styleName = coll.insert(second, "P"); QCOMPARE(styleName, QString("P1")); styleName = coll.insert(second, "P", KoGenStyles::AllowDuplicates); QCOMPARE(styleName, QString("P2")); styleName = coll.insert(second, "P", KoGenStyles::AllowDuplicates); QCOMPARE(styleName, QString("P3")); styleName = coll.insert(second, "P", KoGenStyles::AllowDuplicates | KoGenStyles::DontAddNumberToName); QCOMPARE(styleName, QString("P4")); } void TestKoGenStyles::testWriteStyle() { debugOdf; KoGenStyles coll; QBuffer buffer; buffer.open(QIODevice::WriteOnly); KoXmlWriter styleChildWriter(&buffer); styleChildWriter.startElement("styleChild"); styleChildWriter.addAttribute("foo", "bar"); styleChildWriter.endElement(); QString styleChildContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size()); KoGenStyle style(KoGenStyle::ParagraphStyle, "paragraph"); style.addProperty("style:foo", "bar"); style.addProperty("style:paragraph", "property", KoGenStyle::ParagraphType); style.addProperty("style:graphic", "property", KoGenStyle::GraphicType); style.addProperty("styleChild", styleChildContents, KoGenStyle::StyleChildElement); QString styleName = coll.insert(style, "P"); // XML for style TEST_BEGIN(0, 0); style.writeStyle(&writer, coll, "style:style", styleName, "style:paragraph-properties"); TEST_END_QTTEST("\n \n \n \n \n \n\n"); KoGenStyle pageLayoutStyle(KoGenStyle::PageLayoutStyle); pageLayoutStyle.addProperty("style:print-orientation", "portrait"); QString pageLayoutStyleName = coll.insert(pageLayoutStyle, "pm"); // XML for page layout style TEST_BEGIN(0, 0); pageLayoutStyle.writeStyle(&writer, coll, "style:page-layout", pageLayoutStyleName, "style:page-layout-properties"); TEST_END_QTTEST("\n \n \n \n\n"); KoGenStyle listStyle(KoGenStyle::ListStyle); QString listStyleName = coll.insert(listStyle, "L"); // XML for list layout style TEST_BEGIN(0, 0); listStyle.writeStyle(&writer, coll, "text:list-style", listStyleName, 0); TEST_END_QTTEST("\n \n\n"); } void TestKoGenStyles::testDefaultStyle() { debugOdf ; /* Create a default style, * and then an auto style with exactly the same attributes * -> the insert gives the default style. * * Also checks how the default style gets written out to XML. */ KoGenStyles coll; KoGenStyle defaultStyle(KoGenStyle::ParagraphStyle, "paragraph"); defaultStyle.addAttribute("style:master-page-name", "Standard"); defaultStyle.addProperty("myfont", "isBold"); defaultStyle.setDefaultStyle(true); QString defaultStyleName = coll.insert(defaultStyle); // default styles don't get a name QVERIFY(defaultStyleName.isEmpty()); QCOMPARE(defaultStyle.type(), KoGenStyle::ParagraphStyle); QVERIFY(defaultStyle.isDefaultStyle()); KoGenStyle anotherStyle(KoGenStyle::ParagraphStyle, "paragraph"); anotherStyle.addAttribute("style:master-page-name", "Standard"); anotherStyle.addProperty("myfont", "isBold"); QString anotherStyleName = coll.insert(anotherStyle); QVERIFY(anotherStyleName != defaultStyleName); QCOMPARE(coll.styles().count(), 1); // XML for default style TEST_BEGIN(0, 0); defaultStyle.writeStyle(&writer, coll, "style:default-style", defaultStyleName, "style:paragraph-properties"); TEST_END_QTTEST("\n \n \n \n\n"); // The Calligra Sheets case: not writing out all properties, only if they differ // from the default style. // KoGenStyles doesn't fetch info from the parent style when testing // for equality, so Calligra Sheets uses isEmpty() to check for equality-to-parent. KoGenStyle dataStyle(KoGenStyle::ParagraphStyle, "paragraph", defaultStyleName); QVERIFY(dataStyle.isEmpty()); // and then it doesn't look up the auto style, but rather uses the parent style directly. } void TestKoGenStyles:: testUserStyles() { debugOdf ; /* Two user styles with exactly the same attributes+properties will not get merged, since * they don't have exactly the same attributes after all: the display-name obviously differs :) */ KoGenStyles coll; KoGenStyle user1(KoGenStyle::ParagraphStyle, "paragraph"); user1.addAttribute("style:display-name", "User 1"); user1.addProperty("myfont", "isBold"); QString user1StyleName = coll.insert(user1, "User1", KoGenStyles::DontAddNumberToName); debugOdf << "The user style got assigned the name" << user1StyleName; QCOMPARE(user1StyleName, QString("User1")); KoGenStyle user2(KoGenStyle::ParagraphStyle, "paragraph"); user2.addAttribute("style:display-name", "User 2"); user2.addProperty("myfont", "isBold"); QString user2StyleName = coll.insert(user2, "User2", KoGenStyles::DontAddNumberToName); debugOdf << "The user style got assigned the name" << user2StyleName; QCOMPARE(user2StyleName, QString("User2")); // And now, what if the data uses that style? // This is like sameAsParent in the other test, but this time the // parent is a STYLE_USER... KoGenStyle dataStyle(KoGenStyle::ParagraphAutoStyle, "paragraph", user2StyleName); dataStyle.addProperty("myfont", "isBold"); QString dataStyleName = coll.insert(dataStyle, "DataStyle"); debugOdf << "The auto style got assigned the name" << dataStyleName; QCOMPARE(dataStyleName, QString("User2")); // it found the parent as equal // Let's do the opposite test, just to make sure KoGenStyle dataStyle2(KoGenStyle::ParagraphAutoStyle, "paragraph", user2StyleName); dataStyle2.addProperty("myfont", "isNotBold"); QString dataStyle2Name = coll.insert(dataStyle2, "DataStyle"); debugOdf << "The different auto style got assigned the name" << dataStyle2Name; QCOMPARE(dataStyle2Name, QString("DataStyle1")); QCOMPARE(coll.styles().count(), 3); // XML for user style 1 TEST_BEGIN(0, 0); user1.writeStyle(&writer, coll, "style:style", user1StyleName, "style:paragraph-properties"); TEST_END_QTTEST("\n \n \n \n\n"); // XML for user style 2 TEST_BEGIN(0, 0); user2.writeStyle(&writer, coll, "style:style", user2StyleName, "style:paragraph-properties"); TEST_END_QTTEST("\n \n \n \n\n"); } void TestKoGenStyles::testStylesDotXml() { debugOdf ; KoGenStyles coll; // Check that an autostyle-in-style.xml and an autostyle-in-content.xml // don't get the same name. It confuses KoGenStyle's named-based maps. KoGenStyle headerStyle(KoGenStyle::ParagraphAutoStyle, "paragraph"); headerStyle.addAttribute("style:master-page-name", "Standard"); headerStyle.addProperty("style:page-number", "0"); headerStyle.setAutoStyleInStylesDotXml(true); QString headerStyleName = coll.insert(headerStyle, "P"); QCOMPARE(headerStyleName, QString("P1")); //debugOdf << coll; KoGenStyle first(KoGenStyle::ParagraphAutoStyle, "paragraph"); first.addAttribute("style:master-page-name", "Standard"); QString firstName = coll.insert(first, "P"); debugOdf << "The auto style got assigned the name" << firstName; QCOMPARE(firstName, QString("P2")); // anything but not P1. } QTEST_MAIN(TestKoGenStyles) diff --git a/libs/odf/tests/TestWriteStyleXml.cpp b/libs/odf/tests/TestWriteStyleXml.cpp new file mode 100644 index 00000000000..2bea11d3a62 --- /dev/null +++ b/libs/odf/tests/TestWriteStyleXml.cpp @@ -0,0 +1,75 @@ +/* This file is part of the KDE project + * Copyright (C) 2017 Jos van den Oever + * + * 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. + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace writeodf; + +class TestWriteStyleXml : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testWriteRegionLeft(); +}; + +void TestWriteStyleXml::testWriteRegionLeft() +{ + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + { + KoXmlWriter writer(&buffer); + writer.startDocument(0); + office_document_styles styles(&writer); + styles.addAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0"); + styles.addAttribute("xmlns:style", "urn:oasis:names:tc:opendocument:xmlns:style:1.0"); + styles.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0"); + office_master_styles master_styles(styles.add_office_master_styles()); + style_master_page master_page(master_styles.add_style_master_page("Standard", "Layout")); + style_header header(master_page.add_style_header()); + style_region_left left(header.add_style_region_left()); + text_p p(left.add_text_p()); + p.addTextNode("left"); + } + const QString r = buffer.buffer(); + const QString e = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " left\n" + " \n" + " \n" + " \n" + " \n" + ""; + QCOMPARE(r, e); +} + +QTEST_GUILESS_MAIN(TestWriteStyleXml) +#include + diff --git a/libs/odf/tests/kodomtest.cpp b/libs/odf/tests/kodomtest.cpp index a439c9449b5..81600062df6 100644 --- a/libs/odf/tests/kodomtest.cpp +++ b/libs/odf/tests/kodomtest.cpp @@ -1,123 +1,127 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure 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. */ #include "kodomtest.h" #include "KoXmlReader.h" #include -static QString const KoXmlNS_office("urn:oasis:names:tc:opendocument:xmlns:office:1.0"); -static QString const KoXmlNS_text("urn:oasis:names:tc:opendocument:xmlns:text:1.0"); +static QString const KoXmlNS_office() { + return QStringLiteral("urn:oasis:names:tc:opendocument:xmlns:office:1.0"); +} +static QString const KoXmlNS_text() { + return QStringLiteral("urn:oasis:names:tc:opendocument:xmlns:text:1.0"); +} //static void debugElemNS( const QDomElement& elem ) //{ // qDebug( "nodeName=%s tagName=%s localName=%s prefix=%s namespaceURI=%s", elem.nodeName().latin1(), elem.tagName().latin1(), elem.localName().latin1(), elem.prefix().latin1(), elem.namespaceURI().latin1() ); //} void KoDomTest::initTestCase() { const QByteArray xml = QByteArray("\n" "\n" "

foobar

2nd

\n" "
\n"; QVERIFY(m_doc.setContent(xml, true /* namespace processing */)); } void KoDomTest::testQDom() { KoXmlElement docElem = m_doc.documentElement(); //debugElemNS( docElem ); QCOMPARE(docElem.nodeName(), QString("o:document-content")); QCOMPARE(docElem.tagName(), QString("document-content")); QCOMPARE(docElem.localName(), QString("document-content")); QCOMPARE(docElem.prefix(), QString("o")); - QCOMPARE(docElem.namespaceURI(), KoXmlNS_office); + QCOMPARE(docElem.namespaceURI(), KoXmlNS_office()); - KoXmlElement elem = KoXml::namedItemNS(docElem, KoXmlNS_office.toUtf8(), "body"); + KoXmlElement elem = KoXml::namedItemNS(docElem, KoXmlNS_office().toUtf8(), "body"); //debugElemNS( elem ); QCOMPARE(elem.tagName(), QString("body")); QCOMPARE(elem.localName(), QString("body")); QCOMPARE(elem.prefix(), QString("o")); - QCOMPARE(elem.namespaceURI(), KoXmlNS_office); + QCOMPARE(elem.namespaceURI(), KoXmlNS_office()); KoXmlNode n = elem.firstChild(); for (; !n.isNull() ; n = n.nextSibling()) { if (!n.isElement()) continue; KoXmlElement e = n.toElement(); //debugElemNS( e ); QCOMPARE(e.tagName(), QString("p")); QCOMPARE(e.localName(), QString("p")); QVERIFY(e.prefix().isEmpty()); - QCOMPARE(e.namespaceURI(), KoXmlNS_text); + QCOMPARE(e.namespaceURI(), KoXmlNS_text()); } } void KoDomTest::testKoDom() { - KoXmlElement docElem = KoXml::namedItemNS(m_doc, KoXmlNS_office.toUtf8(), "document-content"); + KoXmlElement docElem = KoXml::namedItemNS(m_doc, KoXmlNS_office().toUtf8(), "document-content"); QCOMPARE(docElem.isNull(), false); QCOMPARE(docElem.localName(), QString("document-content")); - QCOMPARE(docElem.namespaceURI(), KoXmlNS_office); + QCOMPARE(docElem.namespaceURI(), KoXmlNS_office()); - KoXmlElement body = KoXml::namedItemNS(docElem, KoXmlNS_office.toUtf8(), "body"); + KoXmlElement body = KoXml::namedItemNS(docElem, KoXmlNS_office().toUtf8(), "body"); QCOMPARE(body.isNull(), false); QCOMPARE(body.localName(), QString("body")); - QCOMPARE(body.namespaceURI(), KoXmlNS_office); + QCOMPARE(body.namespaceURI(), KoXmlNS_office()); - KoXmlElement p = KoXml::namedItemNS(body, KoXmlNS_text.toUtf8(), "p"); + KoXmlElement p = KoXml::namedItemNS(body, KoXmlNS_text().toUtf8(), "p"); QCOMPARE(p.isNull(), false); QCOMPARE(p.localName(), QString("p")); - QCOMPARE(p.namespaceURI(), KoXmlNS_text); + QCOMPARE(p.namespaceURI(), KoXmlNS_text()); - const KoXmlElement officeStyle = KoXml::namedItemNS(docElem, KoXmlNS_office.toUtf8(), "styles"); + const KoXmlElement officeStyle = KoXml::namedItemNS(docElem, KoXmlNS_office().toUtf8(), "styles"); QCOMPARE(officeStyle.isNull(), false); // Look for a non-existing element - KoXmlElement notexist = KoXml::namedItemNS(body, KoXmlNS_text.toUtf8(), "notexist"); + KoXmlElement notexist = KoXml::namedItemNS(body, KoXmlNS_text().toUtf8(), "notexist"); QVERIFY(notexist.isNull()); int count = 0; KoXmlElement elem; forEachElement(elem, body) { QCOMPARE(elem.localName(), QString("p")); - QCOMPARE(elem.namespaceURI(), KoXmlNS_text); + QCOMPARE(elem.namespaceURI(), KoXmlNS_text()); ++count; } QCOMPARE(count, 2); // Attributes // ### Qt bug: it doesn't work if using style-name instead of text:style-name in the XML - const QString styleName = p.attributeNS(KoXmlNS_text, "style-name", QString()); + const QString styleName = p.attributeNS(KoXmlNS_text(), "style-name", QString()); // qDebug( "%s", qPrintable( styleName ) ); QCOMPARE(styleName, QString("L1")); } QTEST_MAIN(KoDomTest) diff --git a/libs/odf/writeodf/odfwriter.h b/libs/odf/writeodf/odfwriter.h index 0bf110569ce..0911ae26265 100644 --- a/libs/odf/writeodf/odfwriter.h +++ b/libs/odf/writeodf/odfwriter.h @@ -1,135 +1,134 @@ /* This file is part of the KDE project Copyright (C) 2013 Jos van den Oever 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. */ #ifndef ODFWRITER_H #define ODFWRITER_H #include #include #include #include class Duration { private: QTime m_time; public: explicit Duration(const QTime& t) :m_time(t) {} QString toString() const { return m_time.toString("'PT'hh'H'mm'M'ss'S'"); } }; class OdfWriter { private: void operator=(const OdfWriter&); protected: OdfWriter(KoXmlWriter* xml_, const char* tag, bool indent) :child(0), parent(0), xml(xml_) { xml->startElement(tag, indent); } OdfWriter(OdfWriter* p, const char* tag, bool indent) :child(0), parent(p), xml(parent->xml) { if (parent->child) { parent->child->end(); } parent->child = this; xml->startElement(tag, indent); } ~OdfWriter() { end(); } void endChild() { if (child) { child->parent = 0; child->end(); child = 0; } } - // in c++11, we would use a move constructor instead of a copy constructor - OdfWriter(const OdfWriter&o) :child(o.child), parent(o.parent), xml(o.xml) { + OdfWriter(const OdfWriter&& o) :child(o.child), parent(o.parent), xml(o.xml) { // disable o and make the parent refer to this new copy o.xml = 0; if (parent && parent->child == &o) { parent->child = this; } } public: void end() { if (xml) { endChild(); xml->endElement(); if (parent) { parent->child = 0; } xml = 0; } } void addTextNode(const QString& str) { endChild(); xml->addTextNode(str); } void addAttribute(const char* name, const char* value) { Q_ASSERT(!child); xml->addAttribute(name, value); } void addAttribute(const char* name, const QString& value) { Q_ASSERT(!child); xml->addAttribute(name, value); } void addAttribute(const char* name, quint64 value) { Q_ASSERT(!child); xml->addAttribute(name, QString::number(value)); } void addAttribute(const char* name, const QUrl& value) { Q_ASSERT(!child); xml->addAttribute(name, value.toString()); } void addAttribute(const char* name, const QDate& value) { Q_ASSERT(!child); xml->addAttribute(name, value.toString(Qt::ISODate)); } void addAttribute(const char* name, const QTime& value) { Q_ASSERT(!child); xml->addAttribute(name, value.toString(Qt::ISODate)); } void addAttribute(const char* name, const QDateTime& value) { Q_ASSERT(!child); xml->addAttribute(name, value.toString(Qt::ISODate)); } void addAttribute(const char* name, const QStringList& value) { Q_ASSERT(!child); xml->addAttribute(name, value.join(QChar(' '))); } void addAttribute(const char* name, const Duration& value) { Q_ASSERT(!child); xml->addAttribute(name, value.toString()); } void addProcessingInstruction(const char* cstr) { endChild(); xml->addProcessingInstruction(cstr); } template void addCompleteElement(T cstr) { endChild(); xml->addCompleteElement(cstr); } private: OdfWriter* child; OdfWriter* parent; protected: mutable KoXmlWriter* xml; }; #endif diff --git a/libs/pigment/KoColorConversionSystem.cpp b/libs/pigment/KoColorConversionSystem.cpp index 98ebdbfa435..40dc0184416 100644 --- a/libs/pigment/KoColorConversionSystem.cpp +++ b/libs/pigment/KoColorConversionSystem.cpp @@ -1,521 +1,521 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "KoColorConversionSystem.h" #include "KoColorConversionSystem_p.h" #include #include #include "KoColorConversionAlphaTransformation.h" #include "KoColorConversionTransformation.h" #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoCopyColorConversionTransformation.h" #include "KoMultipleColorConversionTransformation.h" KoColorConversionSystem::KoColorConversionSystem() : d(new Private) { // Create the Alpha 8bit d->alphaNode = new Node; d->alphaNode->modelId = AlphaColorModelID.id(); d->alphaNode->depthId = Integer8BitsColorDepthID.id(); d->alphaNode->crossingCost = 1000000; d->alphaNode->isInitialized = true; d->alphaNode->isGray = true; // <- FIXME: it's a little bit hacky as alpha doesn't really have color information d->graph.insert(NodeKey(d->alphaNode->modelId, d->alphaNode->depthId, "default"), d->alphaNode); Vertex* v = createVertex(d->alphaNode, d->alphaNode); v->setFactoryFromSrc(new KoCopyColorConversionTransformationFactory(AlphaColorModelID.id(), Integer8BitsColorDepthID.id(), "default")); } KoColorConversionSystem::~KoColorConversionSystem() { qDeleteAll(d->graph); qDeleteAll(d->vertexes); delete d; } void KoColorConversionSystem::connectToEngine(Node* _node, Node* _engine) { Vertex* v1 = createVertex(_node, _engine); Vertex* v2 = createVertex(_engine, _node); v1->conserveColorInformation = !_node->isGray; v2->conserveColorInformation = !_node->isGray; v1->conserveDynamicRange = _engine->isHdr; v2->conserveDynamicRange = _engine->isHdr; } KoColorConversionSystem::Node* KoColorConversionSystem::insertEngine(const KoColorSpaceEngine* engine) { NodeKey key(engine->id(), engine->id(), engine->id()); Node* n = new Node; n->modelId = engine->id(); n->depthId = engine->id(); n->profileName = engine->id(); n->referenceDepth = 64; // engine don't have reference depth, d->graph.insert(key, n); n->init(engine); return n; } void KoColorConversionSystem::insertColorSpace(const KoColorSpaceFactory* csf) { dbgPigment << "Inserting color space " << csf->name() << " (" << csf->id() << ") Model: " << csf->colorModelId() << " Depth: " << csf->colorDepthId() << " into the CCS"; const QList profiles = KoColorSpaceRegistry::instance()->profilesFor(csf); QString modelId = csf->colorModelId().id(); QString depthId = csf->colorDepthId().id(); if (profiles.isEmpty()) { // There is no profile for this CS, create a node without profile name if the color engine isn't icc-based if (csf->colorSpaceEngine() != "icc") { Node* n = nodeFor(modelId, depthId, "default"); n->init(csf); } else { dbgPigment << "Cannot add node for " << csf->name() << ", since there are no profiles available"; } } else { // Initialise the nodes foreach(const KoColorProfile* profile, profiles) { Node* n = nodeFor(modelId, depthId, profile->name()); n->init(csf); if (!csf->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(csf->colorSpaceEngine()); Q_ASSERT(engine); NodeKey engineKey(engine->id(), engine->id(), engine->id()); Node* engineNode = 0; QHash::ConstIterator it = d->graph.constFind(engineKey); if (it != d->graph.constEnd()) { engineNode = it.value(); } else { engineNode = insertEngine(engine); } connectToEngine(n, engineNode); } } } // Construct a link for "custom" transformation const QList cctfs = csf->colorConversionLinks(); foreach(KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } void KoColorConversionSystem::insertColorProfile(const KoColorProfile* _profile) { dbgPigmentCCS << _profile->name(); const QList< const KoColorSpaceFactory* >& factories = KoColorSpaceRegistry::instance()->colorSpacesFor(_profile); foreach(const KoColorSpaceFactory* factory, factories) { QString modelId = factory->colorModelId().id(); QString depthId = factory->colorDepthId().id(); Node* n = nodeFor(modelId, depthId, _profile->name()); n->init(factory); if (!factory->colorSpaceEngine().isEmpty()) { KoColorSpaceEngine* engine = KoColorSpaceEngineRegistry::instance()->get(factory->colorSpaceEngine()); Q_ASSERT(engine); Node* engineNode = d->graph[ NodeKey(engine->id(), engine->id(), engine->id())]; Q_ASSERT(engineNode); connectToEngine(n, engineNode); } const QList cctfs = factory->colorConversionLinks(); foreach(KoColorConversionTransformationFactory* cctf, cctfs) { Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile()); Q_ASSERT(srcNode); Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile()); Q_ASSERT(dstNode); if (srcNode == n || dstNode == n) { // Check if the two nodes are already connected Vertex* v = vertexBetween(srcNode, dstNode); // If the vertex doesn't already exist, then create it if (!v) { v = createVertex(srcNode, dstNode); } Q_ASSERT(v); // we should have one now if (dstNode->modelId == modelId && dstNode->depthId == depthId) { v->setFactoryFromDst(cctf); } if (srcNode->modelId == modelId && srcNode->depthId == depthId) { v->setFactoryFromSrc(cctf); } } } } } const KoColorSpace* KoColorConversionSystem::defaultColorSpaceForNode(const Node* node) const { return KoColorSpaceRegistry::instance()->colorSpace(node->modelId, node->depthId, node->profileName); } KoColorConversionSystem::Node* KoColorConversionSystem::createNode(const QString& _modelId, const QString& _depthId, const QString& _profileName) { Node* n = new Node; n->modelId = _modelId; n->depthId = _depthId; n->profileName = _profileName; d->graph.insert(NodeKey(_modelId, _depthId, _profileName), n); Q_ASSERT(vertexBetween(d->alphaNode, n) == 0); // The two color spaces should not be connected yet Vertex* vFromAlpha = createVertex(d->alphaNode, n); vFromAlpha->setFactoryFromSrc(new KoColorConversionFromAlphaTransformationFactory(_modelId, _depthId, _profileName)); Q_ASSERT(vertexBetween(n, d->alphaNode) == 0); // The two color spaces should not be connected yet Vertex* vToAlpha = createVertex(n, d->alphaNode); vToAlpha->setFactoryFromDst(new KoColorConversionToAlphaTransformationFactory(_modelId, _depthId, _profileName)); return n; } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorSpace* _colorSpace) const { const KoColorProfile* profile = _colorSpace->profile(); return nodeFor(_colorSpace->colorModelId().id(), _colorSpace->colorDepthId().id(), profile ? profile->name() : "default"); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) const { //dbgPigmentCCS << "Look for node: " << _colorModelId << " " << _colorDepthId << " " << _profileName; return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const NodeKey& key) const { //dbgPigmentCCS << "Look for node: " << key.modelId << " " << key.depthId << " " << key.profileName << " " << d->graph.value(key); return d->graph.value(key); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) { return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName)); } KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const KoColorConversionSystem::NodeKey& key) { QHash::ConstIterator it = d->graph.constFind(key); if (it != d->graph.constEnd()) { return it.value(); } else { return createNode(key.modelId, key.depthId, key.profileName); } } QList KoColorConversionSystem::nodesFor(const QString& _modelId, const QString& _depthId) { QList nodes; foreach(Node* node, d->graph) { if (node->modelId == _modelId && node->depthId == _depthId) { nodes << node; } } return nodes; } KoColorConversionTransformation* KoColorConversionSystem::createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*srcColorSpace == *dstColorSpace) { return new KoCopyColorConversionTransformation(srcColorSpace); } Q_ASSERT(srcColorSpace); Q_ASSERT(dstColorSpace); dbgPigmentCCS << srcColorSpace->id() << (srcColorSpace->profile() ? srcColorSpace->profile()->name() : "default"); dbgPigmentCCS << dstColorSpace->id() << (dstColorSpace->profile() ? dstColorSpace->profile()->name() : "default"); Path path = findBestPath( nodeFor(srcColorSpace), nodeFor(dstColorSpace)); Q_ASSERT(path.length() > 0); KoColorConversionTransformation* transfo = createTransformationFromPath(path, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); Q_ASSERT(*transfo->srcColorSpace() == *srcColorSpace); Q_ASSERT(*transfo->dstColorSpace() == *dstColorSpace); Q_ASSERT(transfo); return transfo; } void KoColorConversionSystem::createColorConverters(const KoColorSpace* colorSpace, const QList< QPair >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const { // TODO This function currently only select the best conversion only based on the transformation // from colorSpace to one of the color spaces in the list, but not the other way around // it might be worth to look also the return path. const Node* csNode = nodeFor(colorSpace); PathQualityChecker pQC(csNode->referenceDepth, !csNode->isHdr, !csNode->isGray); // Look for a color conversion Path bestPath; typedef QPair KoID2KoID; foreach(const KoID2KoID & possibility, possibilities) { const KoColorSpaceFactory* csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(KoColorSpaceRegistry::instance()->colorSpaceId(possibility.first.id(), possibility.second.id())); if (csf) { Path path = findBestPath(csNode, nodeFor(csf->colorModelId().id(), csf->colorDepthId().id(), csf->defaultProfile())); Q_ASSERT(path.length() > 0); path.isGood = pQC.isGoodPath(path); if (bestPath.isEmpty()) { bestPath = path; } else if ((!bestPath.isGood && path.isGood) || pQC.lessWorseThan(path, bestPath)) { bestPath = path; } } } Q_ASSERT(!bestPath.isEmpty()); const KoColorSpace* endColorSpace = defaultColorSpaceForNode(bestPath.endNode()); fromCS = createTransformationFromPath(bestPath, colorSpace, endColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Path returnPath = findBestPath(bestPath.endNode(), csNode); Q_ASSERT(!returnPath.isEmpty()); toCS = createTransformationFromPath(returnPath, endColorSpace, colorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); Q_ASSERT(*toCS->dstColorSpace() == *fromCS->srcColorSpace()); Q_ASSERT(*fromCS->dstColorSpace() == *toCS->srcColorSpace()); } KoColorConversionTransformation* KoColorConversionSystem::createTransformationFromPath(const Path &path, const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT(srcColorSpace->colorModelId().id() == path.startNode()->modelId); Q_ASSERT(srcColorSpace->colorDepthId().id() == path.startNode()->depthId); Q_ASSERT(dstColorSpace->colorModelId().id() == path.endNode()->modelId); Q_ASSERT(dstColorSpace->colorDepthId().id() == path.endNode()->depthId); KoColorConversionTransformation* transfo; const QList< Path::node2factory > pathOfNode = path.compressedPath(); if (pathOfNode.size() == 2) { // Direct connection transfo = pathOfNode[1].second->createColorTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); } else { KoMultipleColorConversionTransformation* mccTransfo = new KoMultipleColorConversionTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); transfo = mccTransfo; // Get the first intermediary color space dbgPigmentCCS << pathOfNode[ 0 ].first->id() << " to " << pathOfNode[ 1 ].first->id(); const KoColorSpace* intermCS = defaultColorSpaceForNode(pathOfNode[1].first); mccTransfo->appendTransfo(pathOfNode[1].second->createColorTransformation(srcColorSpace, intermCS, renderingIntent, conversionFlags)); for (int i = 2; i < pathOfNode.size() - 1; i++) { dbgPigmentCCS << pathOfNode[ i - 1 ].first->id() << " to " << pathOfNode[ i ].first->id(); const KoColorSpace* intermCS2 = defaultColorSpaceForNode(pathOfNode[i].first); Q_ASSERT(intermCS2); mccTransfo->appendTransfo(pathOfNode[i].second->createColorTransformation(intermCS, intermCS2, renderingIntent, conversionFlags)); intermCS = intermCS2; } dbgPigmentCCS << pathOfNode[ pathOfNode.size() - 2 ].first->id() << " to " << pathOfNode[ pathOfNode.size() - 1 ].first->id(); mccTransfo->appendTransfo(pathOfNode.last().second->createColorTransformation(intermCS, dstColorSpace, renderingIntent, conversionFlags)); } return transfo; } KoColorConversionSystem::Vertex* KoColorConversionSystem::vertexBetween(KoColorConversionSystem::Node* srcNode, KoColorConversionSystem::Node* dstNode) { foreach(Vertex* oV, srcNode->outputVertexes) { if (oV->dstNode == dstNode) { return oV; } } return 0; } KoColorConversionSystem::Vertex* KoColorConversionSystem::createVertex(Node* srcNode, Node* dstNode) { Vertex* v = new Vertex(srcNode, dstNode); srcNode->outputVertexes.append(v); d->vertexes.append(v); return v; } // -- Graph visualization functions -- QString KoColorConversionSystem::vertexToDot(KoColorConversionSystem::Vertex* v, const QString &options) const { - return QString(" \"%1\" -> \"%2\" %3\n").arg(v->srcNode->id()).arg(v->dstNode->id()).arg(options); + return QString(" \"%1\" -> \"%2\" %3\n").arg(v->srcNode->id(), v->dstNode->id(), options); } QString KoColorConversionSystem::toDot() const { QString dot = "digraph CCS {\n"; foreach(Vertex* oV, d->vertexes) { dot += vertexToDot(oV, "default") ; } dot += "}\n"; return dot; } bool KoColorConversionSystem::existsPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { //dbgPigmentCCS << "srcModelId = " << srcModelId << " srcDepthId = " << srcDepthId << " srcProfileName = " << srcProfileName << " dstModelId = " << dstModelId << " dstDepthId = " << dstDepthId << " dstProfileName = " << dstProfileName; const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path path = findBestPath(srcNode, dstNode); bool exist = !path.isEmpty(); return exist; } bool KoColorConversionSystem::existsGoodPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const { const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName); const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName); if (srcNode == dstNode) return true; if (!srcNode) return false; if (!dstNode) return false; Path path = findBestPath(srcNode, dstNode); bool existAndGood = path.isGood; return existAndGood; } QString KoColorConversionSystem::bestPathToDot(const QString& srcKey, const QString& dstKey) const { const Node* srcNode = 0; const Node* dstNode = 0; foreach(Node* node, d->graph) { if (node->id() == srcKey) { srcNode = node; } if (node->id() == dstKey) { dstNode = node; } } Path p = findBestPath(srcNode, dstNode); Q_ASSERT(!p.isEmpty()); QString dot = "digraph CCS {\n" + QString(" \"%1\" [color=red]\n").arg(srcNode->id()) + QString(" \"%1\" [color=red]\n").arg(dstNode->id()); foreach(Vertex* oV, d->vertexes) { QString options; if (p.vertexes.contains(oV)) { options = "[color=red]"; } dot += vertexToDot(oV, options) ; } dot += "}\n"; return dot; } inline KoColorConversionSystem::Path KoColorConversionSystem::findBestPathImpl2(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr, bool ignoreColorCorrectness) const { PathQualityChecker pQC(qMin(srcNode->referenceDepth, dstNode->referenceDepth), ignoreHdr, ignoreColorCorrectness); Node2PathHash node2path; // current best path to reach a given node QList possiblePaths; // list of all paths // Generate the initial list of paths foreach(Vertex* v, srcNode->outputVertexes) { if (v->dstNode->isInitialized) { Path p; p.appendVertex(v); Node* endNode = v->dstNode; if (endNode == dstNode) { Q_ASSERT(pQC.isGoodPath(p)); // <- it's a direct link, it has to be a good path p.isGood = true; return p; } else { Q_ASSERT(!node2path.contains(endNode)); // That would be a total fuck up if there are two vertexes between two nodes node2path.insert(endNode, p); possiblePaths.append(p); } } } Path currentBestPath; // Continue while there are any possibilities remaining while (possiblePaths.size() > 0) { // Loop through all paths and explore one step further const QList currentPaths = possiblePaths; for (const Path &p : currentPaths) { const Node* endNode = p.endNode(); for (Vertex* v : endNode->outputVertexes) { if (v->dstNode->isInitialized && !p.contains(v->dstNode)) { Path newP = p; // Candidate newP.appendVertex(v); Node* newEndNode = v->dstNode; if (newEndNode == dstNode) { if (pQC.isGoodPath(newP)) { // Victory newP.isGood = true; return newP; } else if (pQC.lessWorseThan(newP, currentBestPath)) { Q_ASSERT(newP.startNode()->id() == currentBestPath.startNode()->id()); Q_ASSERT(newP.endNode()->id() == currentBestPath.endNode()->id()); // Can we do better than dumping memory values??? // warnPigment << pQC.lessWorseThan(newP, currentBestPath) << " " << newP << " " << currentBestPath; currentBestPath = newP; } } else { // This is an incomplete path. Check if there's a better way to get to its endpoint. Node2PathHash::Iterator it = node2path.find(newEndNode); if (it != node2path.end()) { Path &p2 = it.value(); if (pQC.lessWorseThan(newP, p2)) { p2 = newP; possiblePaths.append(newP); } } else { node2path.insert(newEndNode, newP); possiblePaths.append(newP); } } } } possiblePaths.removeAll(p); // Remove from list of remaining paths } } if (!currentBestPath.isEmpty()) { warnPigment << "No good path from " << srcNode->id() << " to " << dstNode->id() << " found : length = " << currentBestPath.length() << " cost = " << currentBestPath.cost << " referenceDepth = " << currentBestPath.referenceDepth << " respectColorCorrectness = " << currentBestPath.respectColorCorrectness << " isGood = " << currentBestPath.isGood ; return currentBestPath; } errorPigment << "No path from " << srcNode->id() << " to " << dstNode->id() << " found not "; return currentBestPath; } inline KoColorConversionSystem::Path KoColorConversionSystem::findBestPathImpl(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode, bool ignoreHdr) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); return findBestPathImpl2(srcNode, dstNode, ignoreHdr, (srcNode->isGray || dstNode->isGray)); } KoColorConversionSystem::Path KoColorConversionSystem::findBestPath(const KoColorConversionSystem::Node* srcNode, const KoColorConversionSystem::Node* dstNode) const { Q_ASSERT(srcNode); Q_ASSERT(dstNode); //dbgPigmentCCS << "Find best path between " << srcNode->id() << " and " << dstNode->id(); if (srcNode->isHdr && dstNode->isHdr) { return findBestPathImpl(srcNode, dstNode, false); } else { return findBestPathImpl(srcNode, dstNode, true); } } diff --git a/libs/pigment/KoColorDisplayRendererInterface.h b/libs/pigment/KoColorDisplayRendererInterface.h index de69d74f36d..c8557e48b81 100644 --- a/libs/pigment/KoColorDisplayRendererInterface.h +++ b/libs/pigment/KoColorDisplayRendererInterface.h @@ -1,109 +1,110 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #ifndef __KO_COLOR_DISPLAY_RENDERER_INTERFACE_H #define __KO_COLOR_DISPLAY_RENDERER_INTERFACE_H #include #include #include #include "KoColor.h" class KoChannelInfo; /** * A special interface class provided by pigment to let widgets render * a KoColor on screen using custom profiling provided by the user. * * If you want to provide your own rendering of the KoColor on screen, * reimplement this class and provide its instance to a supporting * widget. */ class PIGMENTCMS_EXPORT KoColorDisplayRendererInterface : public QObject { Q_OBJECT public: KoColorDisplayRendererInterface(); virtual ~KoColorDisplayRendererInterface(); /** * Convert the color \p c to a custom QColor that will be * displayed by the widget on screen. Please note, that the * reverse conversion may simply not exist. */ virtual QColor toQColor(const KoColor &c) const = 0; /** * This tries to approximate a rendered QColor into the KoColor * of the painting color space. Please note, that in most of the * cases the exact reverse transformation does not exist, so the * resulting color will be only a rough approximation. Never try * to do a round trip like that: * * // r will never be equal to c! * r = approximateFromRenderedQColor(toQColor(c)); */ virtual KoColor approximateFromRenderedQColor(const QColor &c) const = 0; virtual KoColor fromHsv(int h, int s, int v, int a = 255) const = 0; virtual void getHsv(const KoColor &srcColor, int *h, int *s, int *v, int *a = 0) const = 0; /** * \return the minimum value of a floating point channel that can * be seen on screen */ virtual qreal minVisibleFloatValue(const KoChannelInfo *chaninfo) const = 0; /** * \return the maximum value of a floating point channel that can * be seen on screen. In normal situation it is 1.0. When * the user changes exposure the value varies. */ virtual qreal maxVisibleFloatValue(const KoChannelInfo *chaninfo) const = 0; Q_SIGNALS: void displayConfigurationChanged(); private: Q_DISABLE_COPY(KoColorDisplayRendererInterface) }; /** * The default conversion class that just calls KoColor::toQColor() * conversion implementation which efectively renders the color into * sRGB color space. */ class PIGMENTCMS_EXPORT KoDumbColorDisplayRenderer : public KoColorDisplayRendererInterface { +Q_OBJECT public: QColor toQColor(const KoColor &c) const; KoColor approximateFromRenderedQColor(const QColor &c) const; KoColor fromHsv(int h, int s, int v, int a = 255) const; void getHsv(const KoColor &srcColor, int *h, int *s, int *v, int *a = 0) const; virtual qreal minVisibleFloatValue(const KoChannelInfo *chaninfo) const; virtual qreal maxVisibleFloatValue(const KoChannelInfo *chaninfo) const; static KoColorDisplayRendererInterface* instance(); }; #endif /* __KO_COLOR_DISPLAY_RENDERER_INTERFACE_H */ diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp index 762ed17c6b0..c34c458dcec 100644 --- a/libs/pigment/KoColorSpace.cpp +++ b/libs/pigment/KoColorSpace.cpp @@ -1,796 +1,796 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "KoColorSpace.h" #include "KoColorSpace_p.h" #include "KoChannelInfo.h" #include "DebugPigment.h" #include "KoCompositeOp.h" #include "KoColorTransformation.h" #include "KoColorTransformationFactory.h" #include "KoColorTransformationFactoryRegistry.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "KoCopyColorConversionTransformation.h" #include "KoFallBackColorTransformation.h" #include "KoUniqueNumberForIdServer.h" #include "KoMixColorsOp.h" #include "KoConvolutionOp.h" #include "KoCompositeOpRegistry.h" #include #include #include #include #include #include KoColorSpace::KoColorSpace() : d(new Private()) { } KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp) : d(new Private()) { d->id = id; d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id); d->name = name; d->mixColorsOp = mixColorsOp; d->convolutionOp = convolutionOp; d->transfoToRGBA16 = 0; d->transfoFromRGBA16 = 0; d->transfoToLABA16 = 0; d->transfoFromLABA16 = 0; d->gamutXYY = QPolygonF(); d->TRCXYY = QPolygonF(); d->colorants = QVector (0); d->lumaCoefficients = QVector (0); d->deletability = NotOwnedByRegistry; } KoColorSpace::~KoColorSpace() { Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete); qDeleteAll(d->compositeOps); foreach(KoChannelInfo * channel, d->channels) { delete channel; } if (d->deletability == NotOwnedByRegistry) { KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache(); if (cache) { cache->colorSpaceIsDestroyed(this); } } delete d->mixColorsOp; delete d->convolutionOp; delete d->transfoToRGBA16; delete d->transfoFromRGBA16; delete d->transfoToLABA16; delete d->transfoFromLABA16; delete d; } bool KoColorSpace::operator==(const KoColorSpace& rhs) const { const KoColorProfile* p1 = rhs.profile(); const KoColorProfile* p2 = profile(); return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2)); } QString KoColorSpace::id() const { return d->id; } QString KoColorSpace::name() const { return d->name; } //Color space info stuff. QPolygonF KoColorSpace::gamutXYY() const { if (d->gamutXYY.empty()) { //now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases. //first make a list of colors. qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. - max = this->channels()[0]->getUIMax(); + max = this->channels().at(0)->getUIMax(); } int samples = 5;//amount of samples in our color space. QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF32")->defaultProfile(); const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32", name); quint8 *data = new quint8[pixelSize()]; quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel. //QVector sampleCoordinates(pow(colorChannelCount(),samples)); //sampleCoordinates.fill(0.0); // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for(int x=0;xnormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY << QPointF(x,y); } else { for(int y=0;ynormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } else { channelValuesF[0]=(max/(samples-1))*(x); channelValuesF[1]=(max/(samples-1))*(y); channelValuesF[2]=(max/(samples-1))*(z); channelValuesF[3]=max; if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } } } } delete[] data; //if we ever implement a boundary-checking thing I'd add it here. return d->gamutXYY; } else { return d->gamutXYY; } } QPolygonF KoColorSpace::estimatedTRCXYY() const { if (d->TRCXYY.empty()){ qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. - max = this->channels()[0]->getUIMax(); + max = this->channels().at(0)->getUIMax(); } QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF16")->defaultProfile(); const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F16", name); quint8 *data = new quint8[pixelSize()]; quint8 data2[8]; // xyza is 8 bytes per pixel. // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for (quint32 i=0; i0; j--){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(5-j)); if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(5-j))); } } else { for (int j=0; j<5; j++){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(j)); fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j))); } } } delete[] data; return d->TRCXYY; } else { return d->TRCXYY; } } QVector KoColorSpace::colorants() const { if (d->colorants.size()>1){ return d->colorants; } else if (profile() && profile()->hasColorants()) { d->colorants.resize(3*colorChannelCount()); d->colorants = profile()->getColorantsxyY(); return d->colorants; } else { estimatedTRCXYY(); return d->colorants; } } QVector KoColorSpace::lumaCoefficients() const { if (d->lumaCoefficients.size()>1){ return d->lumaCoefficients; } else { d->lumaCoefficients.resize(3); if (colorModelId().id()!="RGBA") { d->lumaCoefficients.fill(0.33); } else { colorants(); if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) { d->lumaCoefficients[0]=0.2126; d->lumaCoefficients[1]=0.7152; d->lumaCoefficients[2]=0.0722; } else { d->lumaCoefficients[0]=d->colorants[2]; d->lumaCoefficients[1]=d->colorants[5]; d->lumaCoefficients[2]=d->colorants[8]; } } return d->lumaCoefficients; } } QList KoColorSpace::channels() const { return d->channels; } QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const { QBitArray ba(d->channels.size()); if (!color && !alpha) return ba; for (int i = 0; i < d->channels.size(); ++i) { KoChannelInfo * channel = d->channels.at(i); if ((color && channel->channelType() == KoChannelInfo::COLOR) || (alpha && channel->channelType() == KoChannelInfo::ALPHA)) ba.setBit(i, true); } return ba; } void KoColorSpace::addChannel(KoChannelInfo * ci) { d->channels.push_back(ci); } bool KoColorSpace::hasCompositeOp(const QString& id) const { return d->compositeOps.contains(id); } QList KoColorSpace::compositeOps() const { return d->compositeOps.values(); } KoMixColorsOp* KoColorSpace::mixColorsOp() const { return d->mixColorsOp; } KoConvolutionOp* KoColorSpace::convolutionOp() const { return d->convolutionOp; } const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const { const QHash::ConstIterator it = d->compositeOps.constFind(id); if (it != d->compositeOps.constEnd()) { return it.value(); } else { warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER; return d->compositeOps.value(COMPOSITE_OVER); } } void KoColorSpace::addCompositeOp(const KoCompositeOp * op) { if (op->colorSpace()->id() == id()) { d->compositeOps.insert(op->id(), const_cast(op)); } } const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const { if (!d->transfoToLABA16) { d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToLABA16; } const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const { if (!d->transfoFromLABA16) { d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromLABA16; } const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const { if (!d->transfoToRGBA16) { d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToRGBA16; } const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const { if (!d->transfoFromRGBA16) { d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromRGBA16; } void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toRgbA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromRgbA16Converter()->transform(src, dst, nPixels); } KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { return new KoCopyColorConversionTransformation(this); } else { return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags); } } bool KoColorSpace::convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { if (src != dst) { memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize()); } } else { KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags); cct.transformation()->transform(src, dst, numPixels); } return true; } void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1()); if(params.rows <= 0 || params.cols <= 0) return; if(!(*this == *srcSpace)) { if (preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(op->id())) { quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize(); QVector * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride); quint8* conversionDstData = conversionDstCache->data(); for(qint32 row=0; rowcompositeOp(op->id()); KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.dstRowStart = conversionDstData; paramInfo.dstRowStride = conversionDstBufferStride; otherOp->composite(paramInfo); for(qint32 row=0; rowconvertPixelsTo(conversionDstData + row * conversionDstBufferStride, params.dstRowStart + row * params.dstRowStride, this, params.cols, renderingIntent, conversionFlags); } } else { quint32 conversionBufferStride = params.cols * pixelSize(); QVector * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride); quint8* conversionData = conversionCache->data(); for(qint32 row=0; rowconvertPixelsTo(params.srcRowStart + row * params.srcRowStride, conversionData + row * conversionBufferStride, this, params.cols, renderingIntent, conversionFlags); } KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.srcRowStart = conversionData; paramInfo.srcRowStride = conversionBufferStride; op->composite(paramInfo); } } else { op->composite(params); } } QVector * KoColorSpace::threadLocalConversionCache(quint32 size) const { QVector * ba = 0; if (!d->conversionCache.hasLocalData()) { ba = new QVector(size, '0'); d->conversionCache.setLocalData(ba); } else { ba = d->conversionCache.localData(); if ((quint8)ba->size() < size) ba->resize(size); } return ba; } KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash & parameters) const { KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id); if (!factory) return 0; QPair model(colorModelId(), colorDepthId()); QList< QPair > models = factory->supportedModels(); if (models.isEmpty() || models.contains(model)) { return factory->createTransformation(this, parameters); } else { // Find the best solution // TODO use the color conversion cache KoColorConversionTransformation* csToFallBack = 0; KoColorConversionTransformation* fallBackToCs = 0; KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverters(this, models, csToFallBack, fallBackToCs); Q_ASSERT(csToFallBack); Q_ASSERT(fallBackToCs); KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters); return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo); } } void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{ int channelnumber = channelCount(); QVector channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); luma = qMin(1.0, luma + step); luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = qMin(1.0, luma + step); channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat += step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat -= step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue+step>1.0){ hue=(hue+step)- 1.0; } else { hue += step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue-step<0.0){ hue=1.0-(step-hue); } else { hue -= step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u += step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u -= step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v += step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v -= step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;irgb8(dstProfile); if (data) this->convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags); return img; } bool KoColorSpace::preferCompositionInSourceColorSpace() const { return false; } diff --git a/libs/pigment/KoColorSpaceAbstract.h b/libs/pigment/KoColorSpaceAbstract.h index 98352ca5043..ee920abfbe9 100644 --- a/libs/pigment/KoColorSpaceAbstract.h +++ b/libs/pigment/KoColorSpaceAbstract.h @@ -1,209 +1,209 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2007 Emanuele Tamponi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #ifndef KOCOLORSPACEABSTRACT_H #define KOCOLORSPACEABSTRACT_H #include #include #include #include #include #include #include "KoFallBackColorTransformation.h" #include "KoLabDarkenColorTransformation.h" #include "KoMixColorsOpImpl.h" #include "KoConvolutionOpImpl.h" #include "KoInvertColorTransformation.h" /** * This in an implementation of KoColorSpace which can be used as a base for colorspaces with as many * different channels of the same type. * * The template parameters must be a class which inherits KoColorSpaceTrait (or a class with the same signature). * * SOMETYPE is the type of the channel for instance (quint8, quint32...), * SOMENBOFCHANNELS is the number of channels including the alpha channel * SOMEALPHAPOS is the position of the alpha channel in the pixel (can be equal to -1 if no alpha channel). */ template class KoColorSpaceAbstract : public KoColorSpace { public: KoColorSpaceAbstract(const QString &id, const QString &name) : KoColorSpace(id, name, new KoMixColorsOpImpl< _CSTrait>(), new KoConvolutionOpImpl< _CSTrait>()) { } virtual quint32 colorChannelCount() const { if (_CSTrait::alpha_pos == -1) return _CSTrait::channels_nb; else return _CSTrait::channels_nb - 1; } virtual quint32 channelCount() const { return _CSTrait::channels_nb; } virtual quint32 pixelSize() const { return _CSTrait::pixelSize; } virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const { return _CSTrait::channelValueText(pixel, channelIndex); } virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const { return _CSTrait::normalisedChannelValueText(pixel, channelIndex); } virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const { return _CSTrait::normalisedChannelsValue(pixel, channels); } virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const { return _CSTrait::fromNormalisedChannelsValue(pixel, values); } virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const { typename _CSTrait::channels_type c = _CSTrait::nativeArray(srcPixel)[channelIndex]; return KoColorSpaceMaths::scaleToA(c); } virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const { _CSTrait::singleChannelPixel(dstPixel, srcPixel, channelIndex); } virtual quint8 opacityU8(const quint8 * U8_pixel) const { return _CSTrait::opacityU8(U8_pixel); } virtual qreal opacityF(const quint8 * U8_pixel) const { return _CSTrait::opacityF(U8_pixel); } virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const { _CSTrait::setOpacity(pixels, alpha, nPixels); } virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const { _CSTrait::setOpacity(pixels, alpha, nPixels); } virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const { _CSTrait::multiplyAlpha(pixels, alpha, nPixels); } virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const { _CSTrait::applyAlphaU8Mask(pixels, alpha, nPixels); } virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const { _CSTrait::applyInverseAlphaU8Mask(pixels, alpha, nPixels); } virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const { _CSTrait::applyAlphaNormedFloatMask(pixels, alpha, nPixels); } virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const { _CSTrait::applyInverseAlphaNormedFloatMask(pixels, alpha, nPixels); } virtual quint8 intensity8(const quint8 * src) const { QColor c; const_cast *>(this)->toQColor(src, &c); return static_cast((c.red() * 0.30 + c.green() * 0.59 + c.blue() * 0.11) + 0.5); } virtual KoColorTransformation* createInvertTransformation() const { return new KoInvertColorTransformation(this); } virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const { return new KoFallBackColorTransformation(this, KoColorSpaceRegistry::instance()->lab16(""), new KoLabDarkenColorTransformation(shade, compensate, compensation, KoColorSpaceRegistry::instance()->lab16(""))); } virtual bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace *dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { // check whether we have the same profile and color model, but only a different bit // depth; in that case we don't convert as such, but scale bool scaleOnly = false; // Note: getting the id() is really, really expensive, so only do that if // we are sure there is a difference between the colorspaces if (!(*this == *dstColorSpace)) { scaleOnly = dstColorSpace->colorModelId().id() == colorModelId().id() && dstColorSpace->colorDepthId().id() != colorDepthId().id() && dstColorSpace->profile()->name() == profile()->name(); } if (scaleOnly && dynamic_cast(dstColorSpace)) { typedef typename _CSTrait::channels_type channels_type; - switch(dstColorSpace->channels()[0]->channelValueType()) + switch(dstColorSpace->channels().at(0)->channelValueType()) { case KoChannelInfo::UINT8: scalePixels<_CSTrait::pixelSize, 1, channels_type, quint8>(src, dst, numPixels); return true; // case KoChannelInfo::INT8: // scalePixels<_CSTrait::pixelSize, 1, channels_type, qint8>(src, dst, numPixels); // return true; case KoChannelInfo::UINT16: scalePixels<_CSTrait::pixelSize, 2, channels_type, quint16>(src, dst, numPixels); return true; case KoChannelInfo::INT16: scalePixels<_CSTrait::pixelSize, 2, channels_type, qint16>(src, dst, numPixels); return true; case KoChannelInfo::UINT32: scalePixels<_CSTrait::pixelSize, 4, channels_type, quint32>(src, dst, numPixels); return true; default: break; } } return KoColorSpace::convertPixelsTo(src, dst, dstColorSpace, numPixels, renderingIntent, conversionFlags); } private: template void scalePixels(const quint8* src, quint8* dst, quint32 numPixels) const { qint32 dstPixelSize = dstChannelSize * _CSTrait::channels_nb; for(quint32 i=0; i(src + i * srcPixelSize); TDstChannel* dstPixel = reinterpret_cast(dst + i * dstPixelSize); for(quint32 c=0; c<_CSTrait::channels_nb; ++c) dstPixel[c] = Arithmetic::scale(srcPixel[c]); } } }; #endif // KOCOLORSPACEABSTRACT_H diff --git a/libs/pigment/KoCompositeOpRegistry.h b/libs/pigment/KoCompositeOpRegistry.h index 7d0e12f3916..00fae3713fe 100644 --- a/libs/pigment/KoCompositeOpRegistry.h +++ b/libs/pigment/KoCompositeOpRegistry.h @@ -1,170 +1,172 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #ifndef KOCOMPOSITEOPREGISTRY_H #define KOCOMPOSITEOPREGISTRY_H +// clazy:excludeall=non-pod-global-static + #include #include #include #include #include "pigment_export.h" class KoColorSpace; #include const QString COMPOSITE_OVER = "normal"; const QString COMPOSITE_ERASE = "erase"; const QString COMPOSITE_IN = "in"; const QString COMPOSITE_OUT = "out"; const QString COMPOSITE_ALPHA_DARKEN = "alphadarken"; const QString COMPOSITE_XOR = "xor"; const QString COMPOSITE_PLUS = "plus"; const QString COMPOSITE_MINUS = "minus"; const QString COMPOSITE_ADD = "add"; const QString COMPOSITE_SUBTRACT = "subtract"; const QString COMPOSITE_INVERSE_SUBTRACT = "inverse_subtract"; const QString COMPOSITE_DIFF = "diff"; const QString COMPOSITE_MULT = "multiply"; const QString COMPOSITE_DIVIDE = "divide"; const QString COMPOSITE_ARC_TANGENT = "arc_tangent"; const QString COMPOSITE_GEOMETRIC_MEAN = "geometric_mean"; const QString COMPOSITE_ADDITIVE_SUBTRACTIVE = "additive_subtractive"; const QString COMPOSITE_EQUIVALENCE = "equivalence"; const QString COMPOSITE_ALLANON = "allanon"; const QString COMPOSITE_PARALLEL = "parallel"; const QString COMPOSITE_GRAIN_MERGE = "grain_merge"; const QString COMPOSITE_GRAIN_EXTRACT = "grain_extract"; const QString COMPOSITE_EXCLUSION = "exclusion"; const QString COMPOSITE_HARD_MIX = "hard mix"; const QString COMPOSITE_OVERLAY = "overlay"; const QString COMPOSITE_BEHIND = "behind"; const QString COMPOSITE_GREATER = "greater"; const QString COMPOSITE_DARKEN = "darken"; const QString COMPOSITE_BURN = "burn";//this is also known as 'color burn'. const QString COMPOSITE_LINEAR_BURN = "linear_burn"; const QString COMPOSITE_GAMMA_DARK = "gamma_dark"; const QString COMPOSITE_LIGHTEN = "lighten"; const QString COMPOSITE_DODGE = "dodge"; const QString COMPOSITE_LINEAR_DODGE = "linear_dodge"; const QString COMPOSITE_SCREEN = "screen"; const QString COMPOSITE_HARD_LIGHT = "hard_light"; const QString COMPOSITE_SOFT_LIGHT_PHOTOSHOP = "soft_light"; const QString COMPOSITE_SOFT_LIGHT_SVG = "soft_light_svg"; const QString COMPOSITE_GAMMA_LIGHT = "gamma_light"; const QString COMPOSITE_VIVID_LIGHT = "vivid_light"; const QString COMPOSITE_LINEAR_LIGHT = "linear light"; const QString COMPOSITE_PIN_LIGHT = "pin_light"; const QString COMPOSITE_HUE = "hue"; const QString COMPOSITE_COLOR = "color"; const QString COMPOSITE_SATURATION = "saturation"; const QString COMPOSITE_INC_SATURATION = "inc_saturation"; const QString COMPOSITE_DEC_SATURATION = "dec_saturation"; const QString COMPOSITE_LUMINIZE = "luminize"; const QString COMPOSITE_INC_LUMINOSITY = "inc_luminosity"; const QString COMPOSITE_DEC_LUMINOSITY = "dec_luminosity"; const QString COMPOSITE_HUE_HSV = "hue_hsv"; const QString COMPOSITE_COLOR_HSV = "color_hsv"; const QString COMPOSITE_SATURATION_HSV = "saturation_hsv"; const QString COMPOSITE_INC_SATURATION_HSV = "inc_saturation_hsv"; const QString COMPOSITE_DEC_SATURATION_HSV = "dec_saturation_hsv"; const QString COMPOSITE_VALUE = "value"; const QString COMPOSITE_INC_VALUE = "inc_value"; const QString COMPOSITE_DEC_VALUE = "dec_value"; const QString COMPOSITE_HUE_HSL = "hue_hsl"; const QString COMPOSITE_COLOR_HSL = "color_hsl"; const QString COMPOSITE_SATURATION_HSL = "saturation_hsl"; const QString COMPOSITE_INC_SATURATION_HSL = "inc_saturation_hsl"; const QString COMPOSITE_DEC_SATURATION_HSL = "dec_saturation_hsl"; const QString COMPOSITE_LIGHTNESS = "lightness"; const QString COMPOSITE_INC_LIGHTNESS = "inc_lightness"; const QString COMPOSITE_DEC_LIGHTNESS = "dec_lightness"; const QString COMPOSITE_HUE_HSI = "hue_hsi"; const QString COMPOSITE_COLOR_HSI = "color_hsi"; const QString COMPOSITE_SATURATION_HSI = "saturation_hsi"; const QString COMPOSITE_INC_SATURATION_HSI = "inc_saturation_hsi"; const QString COMPOSITE_DEC_SATURATION_HSI = "dec_saturation_hsi"; const QString COMPOSITE_INTENSITY = "intensity"; const QString COMPOSITE_INC_INTENSITY = "inc_intensity"; const QString COMPOSITE_DEC_INTENSITY = "dec_intensity"; const QString COMPOSITE_COPY = "copy"; const QString COMPOSITE_COPY_RED = "copy_red"; const QString COMPOSITE_COPY_GREEN = "copy_green"; const QString COMPOSITE_COPY_BLUE = "copy_blue"; const QString COMPOSITE_TANGENT_NORMALMAP = "tangent_normalmap"; const QString COMPOSITE_COLORIZE = "colorize"; const QString COMPOSITE_BUMPMAP = "bumpmap"; const QString COMPOSITE_COMBINE_NORMAL = "combine_normal"; const QString COMPOSITE_CLEAR = "clear"; const QString COMPOSITE_DISSOLVE = "dissolve"; const QString COMPOSITE_DISPLACE = "displace"; const QString COMPOSITE_NO = "nocomposition"; const QString COMPOSITE_PASS_THROUGH = "pass through"; // XXX: not implemented anywhere yet const QString COMPOSITE_DARKER_COLOR = "darker color"; const QString COMPOSITE_LIGHTER_COLOR = "lighter color"; const QString COMPOSITE_UNDEF = "undefined"; class PIGMENTCMS_EXPORT KoCompositeOpRegistry { typedef QMultiMap KoIDMap; typedef QList KoIDList; public: KoCompositeOpRegistry(); static const KoCompositeOpRegistry& instance(); KoID getDefaultCompositeOp() const; KoID getKoID(const QString& compositeOpID) const; KoIDMap getCompositeOps() const; KoIDList getCategories() const; KoIDList getCompositeOps(const KoColorSpace* colorSpace) const; KoIDList getCompositeOps(const KoID& category, const KoColorSpace* colorSpace=0) const; bool colorSpaceHasCompositeOp(const KoColorSpace* colorSpace, const KoID& compositeOp) const; template KoIDList filterCompositeOps(TKoIdIterator begin, TKoIdIterator end, const KoColorSpace* colorSpace, bool removeInvaliOps=true) const { KoIDList list; for(; begin!=end; ++begin){ if( colorSpaceHasCompositeOp(colorSpace, *begin) == removeInvaliOps) list.push_back(*begin); } return list; } private: KoIDList m_categories; KoIDMap m_map; }; #endif // KOCOMPOSITEOPREGISTRY_H diff --git a/libs/pigment/colorspaces/KoAlphaColorSpace.cpp b/libs/pigment/colorspaces/KoAlphaColorSpace.cpp index 25e5a5d9603..e5dae7c06ec 100644 --- a/libs/pigment/colorspaces/KoAlphaColorSpace.cpp +++ b/libs/pigment/colorspaces/KoAlphaColorSpace.cpp @@ -1,369 +1,369 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoAlphaColorSpace.h" #include #include #include #include #include #include "KoChannelInfo.h" #include "KoID.h" #include "KoIntegerMaths.h" #include "KoCompositeOpOver.h" #include "KoCompositeOpErase.h" #include "KoCompositeOpCopy2.h" #include "KoCompositeOpAlphaDarken.h" #include namespace { const quint8 PIXEL_MASK = 0; class CompositeClear : public KoCompositeOp { public: CompositeClear(KoColorSpace * cs) : KoCompositeOp(cs, COMPOSITE_CLEAR, i18n("Clear"), KoCompositeOp::categoryMix()) { } public: using KoCompositeOp::composite; void composite(quint8 *dst, qint32 dststride, const quint8 *src, qint32 srcstride, const quint8 *maskRowStart, qint32 maskstride, qint32 rows, qint32 cols, quint8 opacity, const QBitArray & channelFlags) const { Q_UNUSED(src); Q_UNUSED(srcstride); Q_UNUSED(opacity); Q_UNUSED(channelFlags); quint8 *d; qint32 linesize; if (rows <= 0 || cols <= 0) return; if (maskRowStart == 0) { linesize = sizeof(quint8) * cols; d = dst; while (rows-- > 0) { memset(d, OPACITY_TRANSPARENT_U8, linesize); d += dststride; } } else { while (rows-- > 0) { const quint8 *mask = maskRowStart; d = dst; for (qint32 i = cols; i > 0; --i, ++d) { // If the mask tells us to completely not // blend this pixel, continue. if (mask != 0) { if (mask[0] == OPACITY_TRANSPARENT_U8) { ++mask; continue; } ++mask; } // linesize is uninitialized here, so it just crashes //memset(d, OPACITY_TRANSPARENT, linesize); } dst += dststride; src += srcstride; maskRowStart += maskstride; } } } }; class CompositeSubtract : public KoCompositeOp { public: CompositeSubtract(KoColorSpace * cs) : KoCompositeOp(cs, COMPOSITE_SUBTRACT, i18n("Subtract"), KoCompositeOp::categoryArithmetic()) { } public: using KoCompositeOp::composite; void composite(quint8 *dst, qint32 dststride, const quint8 *src, qint32 srcstride, const quint8 *maskRowStart, qint32 maskstride, qint32 rows, qint32 cols, quint8 opacity, const QBitArray & channelFlags) const { Q_UNUSED(opacity); Q_UNUSED(channelFlags); quint8 *d; const quint8 *s; qint32 i; if (rows <= 0 || cols <= 0) return; while (rows-- > 0) { const quint8 *mask = maskRowStart; d = dst; s = src; for (i = cols; i > 0; --i, ++d, ++s) { // If the mask tells us to completely not // blend this pixel, continue. if (mask != 0) { if (mask[0] == OPACITY_TRANSPARENT_U8) { ++mask; continue; } ++mask; } if (d[PIXEL_MASK] <= s[PIXEL_MASK]) { d[PIXEL_MASK] = OPACITY_TRANSPARENT_U8; } else { d[PIXEL_MASK] -= s[PIXEL_MASK]; } } dst += dststride; src += srcstride; if (maskRowStart) { maskRowStart += maskstride; } } } }; class CompositeMultiply : public KoCompositeOp { public: CompositeMultiply(KoColorSpace * cs) : KoCompositeOp(cs, COMPOSITE_MULT, i18n("Multiply"), KoCompositeOp::categoryArithmetic()) { } public: using KoCompositeOp::composite; void composite(quint8 *dst, qint32 dststride, const quint8 *src, qint32 srcstride, const quint8 *maskRowStart, qint32 maskstride, qint32 rows, qint32 cols, quint8 opacity, const QBitArray & channelFlags) const { Q_UNUSED(opacity); Q_UNUSED(channelFlags); quint8 *destination; const quint8 *source; qint32 i; if (rows <= 0 || cols <= 0) return; while (rows-- > 0) { const quint8 *mask = maskRowStart; destination = dst; source = src; for (i = cols; i > 0; --i, ++destination, ++source) { // If the mask tells us to completely not // blend this pixel, continue. if (mask != 0) { if (mask[0] == OPACITY_TRANSPARENT_U8) { ++mask; continue; } ++mask; } // here comes the math destination[PIXEL_MASK] = KoColorSpaceMaths::multiply(destination[PIXEL_MASK], source[PIXEL_MASK]); } dst += dststride; src += srcstride; if (maskRowStart) { maskRowStart += maskstride; } } } }; } KoAlphaColorSpace::KoAlphaColorSpace() : KoColorSpaceAbstract("ALPHA", i18n("Alpha mask")) { addChannel(new KoChannelInfo(i18n("Alpha"), 0, 0, KoChannelInfo::ALPHA, KoChannelInfo::UINT8)); m_compositeOps << new KoCompositeOpOver(this) << new CompositeClear(this) << new KoCompositeOpErase(this) << new KoCompositeOpCopy2(this) << new CompositeSubtract(this) << new CompositeMultiply(this) << new KoCompositeOpAlphaDarken(this); foreach(KoCompositeOp *op, m_compositeOps) { addCompositeOp(op); } m_profile = new KoDummyColorProfile; } KoAlphaColorSpace::~KoAlphaColorSpace() { qDeleteAll(m_compositeOps); delete m_profile; m_profile = 0; } void KoAlphaColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const { dst[PIXEL_MASK] = c.alpha(); } void KoAlphaColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const { c->setRgba(qRgba(255, 255, 255, src[PIXEL_MASK])); } quint8 KoAlphaColorSpace::difference(const quint8 *src1, const quint8 *src2) const { // Arithmetic operands smaller than int are converted to int automatically return qAbs(src2[PIXEL_MASK] - src1[PIXEL_MASK]); } quint8 KoAlphaColorSpace::differenceA(const quint8 *src1, const quint8 *src2) const { return difference(src1, src2); } QString KoAlphaColorSpace::channelValueText(const quint8 *pixel, quint32 channelIndex) const { Q_ASSERT(channelIndex < channelCount()); - quint32 channelPosition = channels()[channelIndex]->pos(); + quint32 channelPosition = channels().at(channelIndex)->pos(); return QString().setNum(pixel[channelPosition]); } QString KoAlphaColorSpace::normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const { Q_ASSERT(channelIndex < channelCount()); - quint32 channelPosition = channels()[channelIndex]->pos(); + quint32 channelPosition = channels().at(channelIndex)->pos(); return QString().setNum(static_cast(pixel[channelPosition]) / UINT8_MAX); } void KoAlphaColorSpace::convolveColors(quint8** colors, qreal * kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const { qreal totalAlpha = 0; while (nColors--) { qreal weight = *kernelValues; if (weight != 0) { totalAlpha += (*colors)[PIXEL_MASK] * weight; } ++colors; ++kernelValues; } if (channelFlags.isEmpty() || channelFlags.testBit(PIXEL_MASK)) dst[PIXEL_MASK] = CLAMP((totalAlpha / factor) + offset, 0, SCHAR_MAX); } QImage KoAlphaColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * /*dstProfile*/, KoColorConversionTransformation::Intent /*renderingIntent*/, KoColorConversionTransformation::ConversionFlags /*conversionFlags*/) const { QImage img(width, height, QImage::Format_Indexed8); QVector table; for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i)); img.setColorTable(table); quint8* data_img; for (int i = 0; i < height; ++i) { data_img=img.scanLine(i); for (int j = 0; j < width; ++j) data_img[j]=*(data++); } return img; } KoColorSpace* KoAlphaColorSpace::clone() const { return new KoAlphaColorSpace(); } bool KoAlphaColorSpace::preferCompositionInSourceColorSpace() const { return true; } diff --git a/libs/pigment/tests/CCSGraph.cpp b/libs/pigment/tests/CCSGraph.cpp index 8cbc9640d1e..949bc1a5550 100644 --- a/libs/pigment/tests/CCSGraph.cpp +++ b/libs/pigment/tests/CCSGraph.cpp @@ -1,109 +1,109 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorConversionSystem.h" #include #include #include int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); // Initialize the list of options parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graphs"), i18n("return the list of available graphs"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graph"), i18n("specify the type of graph (see --graphs to get the full list, the default is full)"), QLatin1String("type"), QLatin1String("full"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the source color space"), QLatin1String("key"), QLatin1String(""))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the destination color space"), QLatin1String("key"), QLatin1String(""))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("output"), i18n("specify the output (can be ps or dot, the default is ps)"), QLatin1String("type"), QLatin1String("ps"))); parser.addPositionalArgument(QLatin1String("outputfile"), i18n("name of the output file")); parser.process(app); // PORTING SCRIPT: move this to after any parser.addOption if (parser.isSet("graphs")) { // Don't change those lines to use dbgPigment derivatives, they need to be outputed // to stdout not stderr. std::cout << "full : show all the connection on the graph" << std::endl; std::cout << "bestpath : show the best path for a given transformation" << std::endl; exit(EXIT_SUCCESS); } QString graphType = parser.value("graph"); QString outputType = parser.value("output"); if (parser.positionalArguments().count() != 1) { errorPigment << "No output file name specified"; parser.showHelp(); exit(EXIT_FAILURE); } - QString outputFileName = parser.positionalArguments()[0]; + QString outputFileName = parser.positionalArguments().at(0); // Generate the graph QString dot; if (graphType == "full") { dot = KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot(); } else if (graphType == "bestpath") { QString srcKey = parser.value("src-key"); QString dstKey = parser.value("dst-key"); if (srcKey.isEmpty() || dstKey.isEmpty()) { errorPigment << "src-key and dst-key must be specified for the graph bestpath"; exit(EXIT_FAILURE); } else { dot = KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey); } } else { errorPigment << "Unknown graph type : " << graphType.toLatin1(); exit(EXIT_FAILURE); } if (outputType == "dot") { QFile file(outputFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) exit(EXIT_FAILURE); QTextStream out(&file); out << dot; } else if (outputType == "ps" || outputType == "svg") { QTemporaryFile file; if (!file.open()) { exit(EXIT_FAILURE); } QTextStream out(&file); out << dot; - QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType).arg(file.fileName()).arg(outputFileName); + QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType, file.fileName(), outputFileName); file.close(); if (QProcess::execute(cmd) != 0) { errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphiz.org)"; } } else { errorPigment << "Unknown output type : " << outputType; exit(EXIT_FAILURE); } } diff --git a/libs/pigment/tests/TestColorConversionSystem.cpp b/libs/pigment/tests/TestColorConversionSystem.cpp index 02ff3fd7c72..751e68b09ec 100644 --- a/libs/pigment/tests/TestColorConversionSystem.cpp +++ b/libs/pigment/tests/TestColorConversionSystem.cpp @@ -1,124 +1,126 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "TestColorConversionSystem.h" #include #include #include #include #include #include TestColorConversionSystem::TestColorConversionSystem() { countFail = 0; foreach(const KoID& modelId, KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces)) { foreach(const KoID& depthId, KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces)) { QList< const KoColorProfile * > profiles = KoColorSpaceRegistry::instance()->profilesFor( KoColorSpaceRegistry::instance()->colorSpaceId(modelId, depthId)); foreach(const KoColorProfile * profile, profiles) { listModels.append(ModelDepthProfile(modelId.id(), depthId.id(), profile->name())); } } } //listModels.append(ModelDepthProfile(AlphaColorModelID.id(), Integer8BitsColorDepthID.id(), "")); } void TestColorConversionSystem::testConnections_data() { QTest::addColumn("smodel"); QTest::addColumn("sdepth"); QTest::addColumn("sprofile"); QTest::addColumn("dmodel"); QTest::addColumn("ddepth"); QTest::addColumn("dprofile"); QTest::addColumn("result"); for (int i = 0; i < listModels.count(); ++i) { const ModelDepthProfile& srcCS = listModels[i]; - for (const ModelDepthProfile& dstCS : listModels) { - QByteArray name = QString("Path: %1/%2 to %3/%4").arg(srcCS.model).arg(srcCS.depth).arg(dstCS.model).arg(dstCS.depth).toLocal8Bit(); + for (int j = 0; j < listModels.size(); ++j) { + const ModelDepthProfile& dstCS = listModels.at(j); + QByteArray name = QString("Path: %1/%2 to %3/%4").arg(srcCS.model, srcCS.depth, dstCS.model, dstCS.depth).toLocal8Bit(); QTest::newRow(name) << srcCS.model << srcCS.depth << srcCS.profile << dstCS.model << dstCS.depth << dstCS.profile << true; } } } void TestColorConversionSystem::testConnections() { QFETCH(QString, smodel); QFETCH(QString, sdepth); QFETCH(QString, sprofile); QFETCH(QString, dmodel); QFETCH(QString, ddepth); QFETCH(QString, dprofile); QFETCH(bool, result); QCOMPARE(KoColorSpaceRegistry::instance()->colorConversionSystem()->existsPath(smodel, sdepth, sprofile, dmodel, ddepth, dprofile), result); } void TestColorConversionSystem::testGoodConnections_data() { QTest::addColumn("smodel"); QTest::addColumn("sdepth"); QTest::addColumn("sprofile"); QTest::addColumn("dmodel"); QTest::addColumn("ddepth"); QTest::addColumn("dprofile"); QTest::addColumn("result"); for (int i = 0; i < listModels.count(); ++i) { const ModelDepthProfile& srcCS = listModels[i]; - for (const ModelDepthProfile& dstCS : listModels) { - QByteArray name = QString("Path: %1/%2 to %3/%4").arg(srcCS.model).arg(srcCS.depth).arg(dstCS.model).arg(dstCS.depth).toLocal8Bit(); + for (int j = 0; j < listModels.size(); ++j) { + const ModelDepthProfile& dstCS = listModels.at(j); + QByteArray name = QString("Path: %1/%2 to %3/%4").arg(srcCS.model, srcCS.depth, dstCS.model, dstCS.depth).toLocal8Bit(); QTest::newRow(name) << srcCS.model << srcCS.depth << srcCS.profile << dstCS.model << dstCS.depth << dstCS.profile << true; } } } void TestColorConversionSystem::testGoodConnections() { QFETCH(QString, smodel); QFETCH(QString, sdepth); QFETCH(QString, sprofile); QFETCH(QString, dmodel); QFETCH(QString, ddepth); QFETCH(QString, dprofile); QFETCH(bool, result); if (!KoColorSpaceRegistry::instance()->colorConversionSystem()->existsGoodPath(smodel, sdepth, sprofile , dmodel, ddepth, dprofile)) { ++countFail; dbgPigment << "No good path between \"" << smodel << " " << sdepth << " " << sprofile << "\" \"" << dmodel << " " << ddepth << " " << dprofile << "\""; } } void TestColorConversionSystem::testFailedConnections() { int failed = 0; if (!KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0) && KoColorSpaceRegistry::instance()->colorSpace( "KS6", Float32BitsColorDepthID.id(), 0) ) { failed = 42; } QVERIFY2(countFail == failed, QString("%1 tests have fails (it should have been %2)").arg(countFail).arg(failed).toLatin1()); } QTEST_GUILESS_MAIN(TestColorConversionSystem) diff --git a/libs/pigment/tests/TestFallBackColorTransformation.cpp b/libs/pigment/tests/TestFallBackColorTransformation.cpp index 3972751f507..9f13de07669 100644 --- a/libs/pigment/tests/TestFallBackColorTransformation.cpp +++ b/libs/pigment/tests/TestFallBackColorTransformation.cpp @@ -1,57 +1,57 @@ #include "TestFallBackColorTransformation.h" #include "KoColorTransformation.h" #include #include #include struct KoDummyColorTransformation : public KoColorTransformation { KoDummyColorTransformation() { m_parameters << 1 << 2; } QList m_parameters; virtual void transform(const quint8 */*src*/, quint8 */*dst*/, qint32 /*nPixels*/) const { } virtual QList parameters() const { QList s; s << "test"; return s; } virtual int parameterId(const QString& name) const { if(name == "test") { return 1; } else { return 0; } } virtual void setParameter(int id, const QVariant& parameter) { m_parameters[id] = parameter; } }; void TestFallBackColorTransformation::parametersForward() { KoDummyColorTransformation* dummy = new KoDummyColorTransformation; KoFallBackColorTransformation* fallback = new KoFallBackColorTransformation(KoColorSpaceRegistry::instance()->rgb8(), KoColorSpaceRegistry::instance()->rgb16(), dummy); QCOMPARE(fallback->parameters().size(), 1); - QCOMPARE(fallback->parameters()[0], QString("test")); + QCOMPARE(fallback->parameters().at(0), QString("test")); QCOMPARE(fallback->parameterId("test"), 1); QCOMPARE(fallback->parameterId("other"), 0); fallback->setParameter(0, -1); fallback->setParameter(1, "value"); QCOMPARE(dummy->m_parameters[0], QVariant(-1)); QCOMPARE(dummy->m_parameters[1], QVariant("value")); delete fallback; } QTEST_GUILESS_MAIN(TestFallBackColorTransformation) diff --git a/libs/pigment/tests/TestKoColor.cpp b/libs/pigment/tests/TestKoColor.cpp index 4d09dd1a839..f0189f624eb 100644 --- a/libs/pigment/tests/TestKoColor.cpp +++ b/libs/pigment/tests/TestKoColor.cpp @@ -1,88 +1,88 @@ /* * Copyright (C) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "TestKoColor.h" #include #include #include "KoColorModelStandardIds.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "DebugPigment.h" bool nearEqualValue(int a, int b) { return qAbs(a - b) <= 1; } void TestKoColor::testForModel(QString model) { QColor qc(200, 125, 100); QList depthIDs = KoColorSpaceRegistry::instance()->colorDepthList(model, KoColorSpaceRegistry::AllColorSpaces); foreach(const KoID& depthId, depthIDs) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(model, depthId.id() , ""); if (cs) { KoColor kc(cs); kc.fromQColor(qc); QDomDocument doc; QDomElement elt = doc.createElement("color"); kc.toXML(doc, elt); doc.appendChild(elt); dbgPigment << doc.toString(); KoColor kcu = KoColor::fromXML(elt.firstChildElement(), depthId.id(), QHash()); QVERIFY2(*(kc.colorSpace()) == *(kcu.colorSpace()), QString("Not identical color space (colorModelId = %1 depthId = %2) != (colorModelId = %3 depthId = %4) ") - .arg(kc.colorSpace()->colorModelId().id()) - .arg(kc.colorSpace()->colorDepthId().id()) - .arg(kcu.colorSpace()->colorModelId().id()) - .arg(kcu.colorSpace()->colorDepthId().id()).toLatin1()); + .arg(kc.colorSpace()->colorModelId().id(), + kc.colorSpace()->colorDepthId().id(), + kcu.colorSpace()->colorModelId().id(), + kcu.colorSpace()->colorDepthId().id()).toLatin1()); QVERIFY(cs->difference(kcu.data(), kc.data()) <= 1); } } } void TestKoColor::testSerialization() { testForModel(RGBAColorModelID.id()); testForModel(XYZAColorModelID.id()); testForModel(LABAColorModelID.id()); testForModel(CMYKAColorModelID.id()); testForModel(GrayAColorModelID.id()); // we cannot test ycbcr since we cannot ship profiles //testForModel(YCbCrAColorModelID.id()); } void TestKoColor::testConversion() { QColor c = Qt::red; const KoColorSpace *csOrig = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace *csDst = KoColorSpaceRegistry::instance()->lab16(); KoColor kc(csOrig); kc.fromQColor(c); kc.convertTo(csDst); } QTEST_GUILESS_MAIN(TestKoColor) diff --git a/libs/plugin/KoPluginLoader.cpp b/libs/plugin/KoPluginLoader.cpp index 78d3515ec41..a964d473cff 100644 --- a/libs/plugin/KoPluginLoader.cpp +++ b/libs/plugin/KoPluginLoader.cpp @@ -1,208 +1,210 @@ /* This file is part of the KDE project * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2016 Friedrich W. H. Kossebau * * 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. */ #include "KoPluginLoader.h" #include // CALLIGRA_OLD_PLUGIN_METADATA #include #include #include #include #include #include #include #include #include #include const QLoggingCategory &PLUGIN_LOG() { static const QLoggingCategory category("calligra.lib.plugin"); return category; } #define debugPlugin qCDebug(PLUGIN_LOG) #define warnPlugin qCWarning(PLUGIN_LOG) class KoPluginLoaderImpl : public QObject { +Q_OBJECT public: QStringList loadedDirectories; }; Q_GLOBAL_STATIC(KoPluginLoaderImpl, pluginLoaderInstance) void KoPluginLoader::load(const QString & directory, const PluginsConfig &config, QObject* owner) { // Don't load the same plugins again if (pluginLoaderInstance->loadedDirectories.contains(directory)) { return; } pluginLoaderInstance->loadedDirectories << directory; QList offers = KoPluginLoader::pluginLoaders(directory); QList plugins; bool configChanged = false; QList blacklist; // what we will save out afterwards if (config.whiteList && config.blacklist && config.group) { debugPlugin << "Loading" << directory << "with checking the config"; KConfigGroup configGroup(KSharedConfig::openConfig(), config.group); QList whiteList = configGroup.readEntry(config.whiteList, config.defaults); QList knownList; // if there was no list of defaults; all plugins are loaded. const bool firstStart = !config.defaults.isEmpty() && !configGroup.hasKey(config.whiteList); knownList = configGroup.readEntry(config.blacklist, knownList); if (firstStart) { configChanged = true; } foreach(QPluginLoader *loader, offers) { QJsonObject json = loader->metaData().value("MetaData").toObject(); json = json.value("KPlugin").toObject(); const QString pluginName = json.value("Id").toString(); if (pluginName.isEmpty()) { warnPlugin << "Loading plugin" << loader->fileName() << "failed, has no X-KDE-PluginInfo-Name."; continue; } if (whiteList.contains(pluginName)) { plugins.append(loader); } else if (!firstStart && !knownList.contains(pluginName)) { // also load newly installed plugins. plugins.append(loader); configChanged = true; } else { blacklist << pluginName; } } } else { plugins = offers; } QMap serviceNames; foreach(QPluginLoader *loader, plugins) { if (serviceNames.contains(loader->fileName())) { // duplicate QJsonObject json2 = loader->metaData().value("MetaData").toObject(); QVariant pluginVersion2 = json2.value("X-Flake-PluginVersion").toVariant(); if (pluginVersion2.isNull()) { // just take the first one found... continue; } QPluginLoader *currentLoader = serviceNames.value(loader->fileName()); QJsonObject json = currentLoader->metaData().value("MetaData").toObject(); QVariant pluginVersion = json.value("X-Flake-PluginVersion").toVariant(); if (!(pluginVersion.isNull() || pluginVersion.toInt() < pluginVersion2.toInt())) { continue; // replace the old one with this one, since its newer. } } serviceNames.insert(loader->fileName(), loader); } QList whiteList; foreach(QPluginLoader *loader, serviceNames) { KPluginFactory *factory = qobject_cast(loader->instance()); QObject *plugin = factory->create(owner ? owner : pluginLoaderInstance, QVariantList()); if (plugin) { QJsonObject json = loader->metaData().value("MetaData").toObject(); json = json.value("KPlugin").toObject(); const QString pluginName = json.value("Id").toString(); whiteList << pluginName; debugPlugin << "Loaded plugin" << loader->fileName() << owner; if (!owner) { delete plugin; } } else { warnPlugin << "Loading plugin" << loader->fileName() << "failed, " << loader->errorString(); } } if (configChanged && config.whiteList && config.blacklist && config.group) { KConfigGroup configGroup(KSharedConfig::openConfig(), config.group); configGroup.writeEntry(config.whiteList, whiteList); configGroup.writeEntry(config.blacklist, blacklist); } qDeleteAll(offers); } QList KoPluginLoader::instantiatePluginFactories(const QString & directory) { QList pluginFactories; const QList offers = KoPluginLoader::pluginLoaders(directory); foreach(QPluginLoader *pluginLoader, offers) { QObject* pluginInstance = pluginLoader->instance(); if (!pluginInstance) { warnPlugin << "Loading plugin" << pluginLoader->fileName() << "failed, " << pluginLoader->errorString(); continue; } KPluginFactory *factory = qobject_cast(pluginInstance); if (factory == 0) { warnPlugin << "Expected a KPluginFactory, got a" << pluginInstance->metaObject()->className(); delete pluginInstance; continue; } pluginFactories.append(factory); } qDeleteAll(offers); return pluginFactories; } QList KoPluginLoader::pluginLoaders(const QString &directory, const QString &mimeType) { QListlist; KPluginLoader::forEachPlugin(directory, [&](const QString &pluginPath) { debugPlugin << "Trying to load" << pluginPath; QPluginLoader *loader = new QPluginLoader(pluginPath); QJsonObject metaData = loader->metaData().value("MetaData").toObject(); if (metaData.isEmpty()) { debugPlugin << pluginPath << "has no MetaData!"; return; } if (!mimeType.isEmpty()) { #ifdef CALLIGRA_OLD_PLUGIN_METADATA QStringList mimeTypes = metaData.value("MimeType").toString().split(';'); mimeTypes += metaData.value("X-KDE-ExtraNativeMimeTypes").toString().split(QLatin1Char(',')); #else QJsonObject pluginData = metaData.value("KPlugin").toObject(); QStringList mimeTypes = pluginData.value("MimeTypes").toVariant().toStringList(); mimeTypes += metaData.value("X-KDE-ExtraNativeMimeTypes").toVariant().toStringList(); #endif mimeTypes += metaData.value("X-KDE-NativeMimeType").toString(); if (! mimeTypes.contains(mimeType)) { return; } } list.append(loader); }); return list; } +#include "KoPluginLoader.moc" diff --git a/libs/store/CMakeLists.txt b/libs/store/CMakeLists.txt index 601b050f8bd..58b98878c53 100644 --- a/libs/store/CMakeLists.txt +++ b/libs/store/CMakeLists.txt @@ -1,55 +1,56 @@ add_subdirectory(tests) ########### libkostore ############### if( Qca-qt5_FOUND ) add_definitions( -DQCA2 ) endif() set(kostore_LIB_SRCS KoDirectoryStore.cpp KoEncryptedStore.cpp KoEncryptionChecker.cpp KoLZF.cpp KoStore.cpp + KoStoreDevice.cpp KoTarStore.cpp KoXmlNS.cpp KoXmlReader.cpp KoXmlWriter.cpp KoZipStore.cpp StoreDebug.cpp KoNetAccess.cpp # temporary while porting ) add_library(kostore SHARED ${kostore_LIB_SRCS}) generate_export_header(kostore BASE_NAME kostore) target_link_libraries(kostore PUBLIC Qt5::Xml Qt5::Core KF5::KIOCore PRIVATE Qt5::Gui KF5::Archive KF5::Wallet KF5::KIOWidgets KF5::I18n ) if( Qca-qt5_FOUND ) target_link_libraries(kostore PRIVATE qca-qt5) endif() set_target_properties(kostore PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) install(TARGETS kostore ${INSTALL_TARGETS_DEFAULT_ARGS} ) if (SHOULD_BUILD_DEVEL_HEADERS) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kostore_export.h KoStore.h DESTINATION ${INCLUDE_INSTALL_DIR}/calligra COMPONENT Devel) endif() diff --git a/libs/widgets/KoIconToolTip.h b/libs/store/KoStoreDevice.cpp similarity index 63% copy from libs/widgets/KoIconToolTip.h copy to libs/store/KoStoreDevice.cpp index ebbd83224b7..99511c5d0a8 100644 --- a/libs/widgets/KoIconToolTip.h +++ b/libs/store/KoStoreDevice.cpp @@ -1,40 +1,24 @@ /* This file is part of the KDE project - Copyright (c) 1999 Carsten Pfeiffer (pfeiffer@kde.org) - Copyright (c) 2002 Igor Jansen (rm@kde.org) + Copyright (C) 2000 David Faure 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. */ -#ifndef KOICONTOOLTIP_H -#define KOICONTOOLTIP_H +#include "KoStoreDevice.h" -#include "KoItemToolTip.h" - -class KoIconToolTip: public KoItemToolTip +KoStoreDevice::~KoStoreDevice() { - - public: - KoIconToolTip() {} - virtual ~KoIconToolTip() {} - - protected: - virtual QTextDocument *createDocument( const QModelIndex &index ); - - private: - typedef KoItemToolTip super; -}; - -#endif // KOICONTOOLTIP_H +} diff --git a/libs/store/KoStoreDevice.h b/libs/store/KoStoreDevice.h index e220afd2007..acdc854407b 100644 --- a/libs/store/KoStoreDevice.h +++ b/libs/store/KoStoreDevice.h @@ -1,85 +1,86 @@ /* This file is part of the KDE project Copyright (C) 2000 David Faure 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. */ #ifndef koStoreDevice_h #define koStoreDevice_h #include "KoStore.h" /** * This class implements a QIODevice around KoStore, so that * it can be used to create a QDomDocument from it, to be written or read * using QDataStream or to be written using QTextStream */ -class KoStoreDevice : public QIODevice +class KOSTORE_EXPORT KoStoreDevice : public QIODevice { +Q_OBJECT public: /// Note: KoStore::open() should be called before calling this. explicit KoStoreDevice(KoStore * store) : m_store(store) { // calligra-1.x behavior compat: a KoStoreDevice is automatically open setOpenMode(m_store->mode() == KoStore::Read ? QIODevice::ReadOnly : QIODevice::WriteOnly); } - ~KoStoreDevice() {} + ~KoStoreDevice(); virtual bool isSequential() const { return true; } virtual bool open(OpenMode m) { setOpenMode(m); if (m & QIODevice::ReadOnly) return (m_store->mode() == KoStore::Read); if (m & QIODevice::WriteOnly) return (m_store->mode() == KoStore::Write); return false; } virtual void close() {} qint64 size() const { if (m_store->mode() == KoStore::Read) return m_store->size(); else return 0xffffffff; } // See QIODevice virtual qint64 pos() const { return m_store->pos(); } virtual bool seek(qint64 pos) { return m_store->seek(pos); } virtual bool atEnd() const { return m_store->atEnd(); } protected: KoStore *m_store; virtual qint64 readData(char *data, qint64 maxlen) { return m_store->read(data, maxlen); } virtual qint64 writeData(const char *data, qint64 len) { return m_store->write(data, len); } }; #endif diff --git a/libs/text/BibliographyGenerator.cpp b/libs/text/BibliographyGenerator.cpp index fcc26653db6..3ab7f3bacc6 100644 --- a/libs/text/BibliographyGenerator.cpp +++ b/libs/text/BibliographyGenerator.cpp @@ -1,215 +1,215 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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. */ #include "BibliographyGenerator.h" #include "KoInlineCite.h" #include #include #include #include #include #include #include static QVector sortKeys; BibliographyGenerator::BibliographyGenerator(QTextDocument *bibDocument, const QTextBlock &block, KoBibliographyInfo *bibInfo) : QObject(bibDocument) , m_bibDocument(bibDocument) , m_bibInfo(bibInfo) , m_block(block) { Q_ASSERT(bibDocument); Q_ASSERT(bibInfo); m_bibInfo->setGenerator(this); bibDocument->setUndoRedoEnabled(false); generate(); } BibliographyGenerator::~BibliographyGenerator() { delete m_bibInfo; } static bool compare_on(int keyIndex, KoInlineCite *c1, KoInlineCite *c2) { if ( keyIndex == sortKeys.size() ) return false; else if (sortKeys[keyIndex].second == Qt::AscendingOrder) { if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return true; else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return false; } else if (sortKeys[keyIndex].second == Qt::DescendingOrder) { if (c1->dataField( sortKeys[keyIndex].first ) < c2->dataField( sortKeys[keyIndex].first )) return false; else if (c1->dataField( sortKeys[keyIndex].first ) > c2->dataField( sortKeys[keyIndex].first )) return true; } else return compare_on( keyIndex + 1, c1, c2 ); return false; } static bool lessThan(KoInlineCite *c1, KoInlineCite *c2) { return compare_on(0, c1, c2); } static QList sort(QList cites, const QVector &keys) { sortKeys = keys; sortKeys << QPair("identifier", Qt::AscendingOrder); qSort(cites.begin(), cites.end(), lessThan); return cites; } void BibliographyGenerator::generate() { if (!m_bibInfo) return; QTextCursor cursor = m_bibDocument->rootFrame()->lastCursorPosition(); cursor.setPosition(m_bibDocument->rootFrame()->firstPosition(), QTextCursor::KeepAnchor); cursor.beginEditBlock(); KoStyleManager *styleManager = KoTextDocument(m_block.document()).styleManager(); if (!m_bibInfo->m_indexTitleTemplate.text.isNull()) { KoParagraphStyle *titleStyle = styleManager->paragraphStyle(m_bibInfo->m_indexTitleTemplate.styleId); if (!titleStyle) { titleStyle = styleManager->defaultBibliographyTitleStyle(); m_bibInfo->m_indexTitleTemplate.styleName = titleStyle->name(); } QTextBlock titleTextBlock = cursor.block(); titleStyle->applyStyle(titleTextBlock); cursor.insertText(m_bibInfo->m_indexTitleTemplate.text); cursor.insertBlock(); } QTextCharFormat savedCharFormat = cursor.charFormat(); QList citeList; if ( KoTextDocument(m_block.document()).styleManager()->bibliographyConfiguration()->sortByPosition() ) { citeList = KoTextDocument(m_block.document()) .inlineTextObjectManager()->citationsSortedByPosition(false, m_block.document()->firstBlock()); } else { KoTextDocument *doc = new KoTextDocument(m_block.document()); citeList = sort(doc->inlineTextObjectManager()->citationsSortedByPosition(false, m_block.document()->firstBlock()), KoTextDocument(m_block.document()).styleManager()->bibliographyConfiguration()->sortKeys()); } foreach (KoInlineCite *cite, citeList) { KoParagraphStyle *bibTemplateStyle = 0; BibliographyEntryTemplate bibEntryTemplate; - if (m_bibInfo->m_entryTemplate.keys().contains(cite->bibliographyType())) { + if (m_bibInfo->m_entryTemplate.contains(cite->bibliographyType())) { bibEntryTemplate = m_bibInfo->m_entryTemplate[cite->bibliographyType()]; bibTemplateStyle = styleManager->paragraphStyle(bibEntryTemplate.styleId); if (bibTemplateStyle == 0) { bibTemplateStyle = styleManager->defaultBibliographyEntryStyle(bibEntryTemplate.bibliographyType); bibEntryTemplate.styleName = bibTemplateStyle->name(); } } else { continue; } cursor.insertBlock(QTextBlockFormat(),QTextCharFormat()); QTextBlock bibEntryTextBlock = cursor.block(); bibTemplateStyle->applyStyle(bibEntryTextBlock); bool spanEnabled = false; //true if data field is not empty foreach (IndexEntry * entry, bibEntryTemplate.indexEntries) { switch(entry->name) { case IndexEntry::BIBLIOGRAPHY: { IndexEntryBibliography *indexEntry = static_cast(entry); cursor.insertText(QString(((spanEnabled)?" ":"")).append(cite->dataField(indexEntry->dataField))); spanEnabled = !cite->dataField(indexEntry->dataField).isEmpty(); break; } case IndexEntry::SPAN: { if(spanEnabled) { IndexEntrySpan *span = static_cast(entry); cursor.insertText(span->text); } break; } case IndexEntry::TAB_STOP: { IndexEntryTabStop *tabEntry = static_cast(entry); cursor.insertText("\t"); QTextBlockFormat blockFormat = cursor.blockFormat(); QList tabList; if (tabEntry->m_position == "MAX") { tabEntry->tab.position = m_maxTabPosition; } else { tabEntry->tab.position = tabEntry->m_position.toDouble(); } tabList.append(QVariant::fromValue(tabEntry->tab)); blockFormat.setProperty(KoParagraphStyle::TabPositions, QVariant::fromValue >(tabList)); cursor.setBlockFormat(blockFormat); break; } default:{ break; } } }// foreach } cursor.setCharFormat(savedCharFormat); // restore the cursor char format } QMap BibliographyGenerator::defaultBibliographyEntryTemplates() { QMap entryTemplates; foreach (const QString &bibType, KoOdfBibliographyConfiguration::bibTypes) { BibliographyEntryTemplate bibEntryTemplate; //Now creating default IndexEntries for all BibliographyEntryTemplates IndexEntryBibliography *identifier = new IndexEntryBibliography(QString()); IndexEntryBibliography *author = new IndexEntryBibliography(QString()); IndexEntryBibliography *title = new IndexEntryBibliography(QString()); IndexEntryBibliography *year = new IndexEntryBibliography(QString()); IndexEntrySpan *firstSpan = new IndexEntrySpan(QString()); IndexEntrySpan *otherSpan = new IndexEntrySpan(QString()); identifier->dataField = "identifier"; author->dataField = "author"; title->dataField = "title"; year->dataField = "year"; firstSpan->text = ":"; otherSpan->text = ","; bibEntryTemplate.bibliographyType = bibType; bibEntryTemplate.indexEntries.append(static_cast(identifier)); bibEntryTemplate.indexEntries.append(static_cast(firstSpan)); bibEntryTemplate.indexEntries.append(static_cast(author)); bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); bibEntryTemplate.indexEntries.append(static_cast(title)); bibEntryTemplate.indexEntries.append(static_cast(otherSpan)); bibEntryTemplate.indexEntries.append(static_cast(year)); entryTemplates[bibType] = bibEntryTemplate; } return entryTemplates; } diff --git a/libs/text/InsertNamedVariableAction_p.h b/libs/text/InsertNamedVariableAction_p.h index 76d00b83c48..46ea075636d 100644 --- a/libs/text/InsertNamedVariableAction_p.h +++ b/libs/text/InsertNamedVariableAction_p.h @@ -1,42 +1,43 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * 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. */ #ifndef INSERTNAMEDVARIABLEACTION_H #define INSERTNAMEDVARIABLEACTION_H #include "InsertInlineObjectActionBase_p.h" class KoInlineTextObjectManager; /** * helper class */ class InsertNamedVariableAction : public InsertInlineObjectActionBase { +Q_OBJECT public: InsertNamedVariableAction(KoCanvasBase *canvas, const KoInlineTextObjectManager *manager, const QString &name); private: virtual KoInlineObject *createInlineObject(); const KoInlineTextObjectManager *m_manager; QString m_name; }; #endif diff --git a/libs/text/InsertTextLocator_p.h b/libs/text/InsertTextLocator_p.h index 9e01d6c9676..bbdab8f7518 100644 --- a/libs/text/InsertTextLocator_p.h +++ b/libs/text/InsertTextLocator_p.h @@ -1,37 +1,38 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * 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. */ #ifndef INSERTTEXTLOCATOR_H #define INSERTTEXTLOCATOR_H #include "InsertInlineObjectActionBase_p.h" /** * helper class */ class InsertTextLocator : public InsertInlineObjectActionBase { +Q_OBJECT public: explicit InsertTextLocator(KoCanvasBase *canvas); private: virtual KoInlineObject *createInlineObject(); }; #endif diff --git a/libs/text/InsertTextReferenceAction_p.h b/libs/text/InsertTextReferenceAction_p.h index e5a402ad804..b575ddeaaa4 100644 --- a/libs/text/InsertTextReferenceAction_p.h +++ b/libs/text/InsertTextReferenceAction_p.h @@ -1,41 +1,42 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * 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. */ #ifndef INSERTTEXTREFERENCEACTION_H #define INSERTTEXTREFERENCEACTION_H #include "InsertInlineObjectActionBase_p.h" class KoInlineTextObjectManager; /** * helper class */ class InsertTextReferenceAction : public InsertInlineObjectActionBase { +Q_OBJECT public: InsertTextReferenceAction(KoCanvasBase *canvas, const KoInlineTextObjectManager *manager); private: virtual KoInlineObject *createInlineObject(); const KoInlineTextObjectManager *m_manager; }; #endif diff --git a/libs/text/InsertVariableAction_p.h b/libs/text/InsertVariableAction_p.h index 69954c0fcd7..944c16ae6be 100644 --- a/libs/text/InsertVariableAction_p.h +++ b/libs/text/InsertVariableAction_p.h @@ -1,44 +1,45 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * * 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. */ #ifndef INSERTVARIABLEACTION_H #define INSERTVARIABLEACTION_H #include "InsertInlineObjectActionBase_p.h" class KoProperties; class KoInlineObjectFactoryBase; struct KoInlineObjectTemplate; /// \internal class InsertVariableAction : public InsertInlineObjectActionBase { +Q_OBJECT public: InsertVariableAction(KoCanvasBase *base, KoInlineObjectFactoryBase *factory, const KoInlineObjectTemplate &templ); private: virtual KoInlineObject *createInlineObject(); KoInlineObjectFactoryBase *const m_factory; const QString m_templateId; const KoProperties *const m_properties; QString m_templateName; }; #endif diff --git a/libs/text/KoBibliographyInfo.cpp b/libs/text/KoBibliographyInfo.cpp index 270fd932e2a..0caee628c64 100644 --- a/libs/text/KoBibliographyInfo.cpp +++ b/libs/text/KoBibliographyInfo.cpp @@ -1,159 +1,159 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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. */ #include "KoBibliographyInfo.h" #include #include #include #include #include #include int KoBibliographyInfo::styleNameToStyleId(KoTextSharedLoadingData *sharedLoadingData, const QString &styleName) { KoParagraphStyle * style = sharedLoadingData->paragraphStyle(styleName, true); if (style) { return style->styleId(); } return 0; } KoBibliographyInfo::KoBibliographyInfo() : m_generator(0) { } KoBibliographyInfo::~KoBibliographyInfo() { - foreach (const BibliographyEntryTemplate &entryTemplate, m_entryTemplate.values()) { + foreach (const BibliographyEntryTemplate &entryTemplate, m_entryTemplate) { qDeleteAll(entryTemplate.indexEntries); } delete m_generator; m_generator = 0; // just to be safe } void KoBibliographyInfo::loadOdf(KoTextSharedLoadingData *sharedLoadingData, const KoXmlElement& element) { Q_ASSERT(element.localName() == "bibliography-source" && element.namespaceURI() == KoXmlNS::text); KoXmlElement p; forEachElement(p, element) { if (p.namespaceURI() != KoXmlNS::text) { continue; } // first child if (p.localName() == "index-title-template") { m_indexTitleTemplate.styleName = p.attribute("style-name"); m_indexTitleTemplate.styleId = styleNameToStyleId(sharedLoadingData, m_indexTitleTemplate.styleName); m_indexTitleTemplate.text = p.text(); // second child } else if (p.localName() == "bibliography-entry-template") { BibliographyEntryTemplate bibEntryTemplate; bibEntryTemplate.styleName = p.attribute("style-name"); bibEntryTemplate.bibliographyType = p.attribute("bibliography-type"); bibEntryTemplate.styleId = styleNameToStyleId(sharedLoadingData, bibEntryTemplate.styleName ); KoXmlElement indexEntry; forEachElement(indexEntry, p) { if (indexEntry.namespaceURI() != KoXmlNS::text) { continue; } if (indexEntry.localName() == "index-entry-bibliography") { // use null String if the style name is not present, it means that we inherit it from the parent IndexEntryBibliography * entryBibliography = new IndexEntryBibliography( indexEntry.attribute("style-name", QString()) ); entryBibliography->dataField = indexEntry.attribute("bibliography-data-field", "article"); bibEntryTemplate.indexEntries.append(static_cast(entryBibliography)); } else if (indexEntry.localName() == "index-entry-span") { IndexEntrySpan * entrySpan = new IndexEntrySpan(indexEntry.attribute("style-name", QString())); entrySpan->text = indexEntry.text(); bibEntryTemplate.indexEntries.append(static_cast(entrySpan)); } else if (indexEntry.localName() == "index-entry-tab-stop") { IndexEntryTabStop * entryTabStop = new IndexEntryTabStop(indexEntry.attribute("style-name", QString())); QString type = indexEntry.attribute("type","right"); // left or right if (type == "left") { entryTabStop->tab.type = QTextOption::LeftTab; } else { entryTabStop->tab.type = QTextOption::RightTab; } entryTabStop->setPosition(indexEntry.attribute("position", QString())); entryTabStop->tab.leaderText = indexEntry.attribute("leader-char","."); bibEntryTemplate.indexEntries.append(static_cast(entryTabStop)); } } m_entryTemplate[bibEntryTemplate.bibliographyType] = bibEntryTemplate; // third child } }// forEachElement } void KoBibliographyInfo::saveOdf(KoXmlWriter * writer) const { writer->startElement("text:bibliography-source"); m_indexTitleTemplate.saveOdf(writer); - foreach (const BibliographyEntryTemplate &entry, m_entryTemplate.values()) { + foreach (const BibliographyEntryTemplate &entry, m_entryTemplate) { entry.saveOdf(writer); } writer->endElement(); } void KoBibliographyInfo::setGenerator(BibliographyGeneratorInterface *generator) { delete m_generator; m_generator = generator; } void KoBibliographyInfo::setEntryTemplates(QMap &entryTemplates) { m_entryTemplate = entryTemplates; } KoBibliographyInfo *KoBibliographyInfo::clone() { KoBibliographyInfo *newBibInfo = new KoBibliographyInfo(); newBibInfo->m_entryTemplate.clear(); newBibInfo->m_name = QString(m_name); newBibInfo->m_styleName = QString(m_name); newBibInfo->m_indexTitleTemplate = m_indexTitleTemplate; for (int i = 0; i < m_entryTemplate.size() ; i++) { newBibInfo->m_entryTemplate.insert(KoOdfBibliographyConfiguration::bibTypes.at(i), m_entryTemplate[KoOdfBibliographyConfiguration::bibTypes.at(i)]); } return newBibInfo; } BibliographyGeneratorInterface *KoBibliographyInfo::generator() const { return m_generator; } diff --git a/libs/text/KoFindStrategy.cpp b/libs/text/KoFindStrategy.cpp index 028f956db4d..a03e739860b 100644 --- a/libs/text/KoFindStrategy.cpp +++ b/libs/text/KoFindStrategy.cpp @@ -1,72 +1,74 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thorsten Zachmann * * 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. */ #include "KoFindStrategy.h" #include #include #include #include #include "FindDirection_p.h" class NonClosingFindDialog : public KFindDialog { +Q_OBJECT public: NonClosingFindDialog(QWidget *parent) : KFindDialog(parent) {} virtual void accept() {} }; KoFindStrategy::KoFindStrategy(QWidget *parent) : m_dialog(new NonClosingFindDialog(parent)) , m_matches(0) { m_dialog->setOptions(KFind::FromCursor); } KoFindStrategy::~KoFindStrategy() { if (m_dialog->parent() == 0) delete m_dialog; } KFindDialog *KoFindStrategy::dialog() const { return m_dialog; } void KoFindStrategy::reset() { m_matches = 0; } void KoFindStrategy::displayFinalDialog() { KMessageBox::information(m_dialog, m_matches ? i18np("Found 1 match", "Found %1 matches", m_matches) : i18n("Found no match")); reset(); } bool KoFindStrategy::foundMatch(QTextCursor &cursor, FindDirection *findDirection) { ++m_matches; findDirection->select(cursor); return false; } +#include "KoFindStrategy.moc" diff --git a/libs/text/KoInlineCite.cpp b/libs/text/KoInlineCite.cpp index e078eb66d9b..8575151d14c 100644 --- a/libs/text/KoInlineCite.cpp +++ b/libs/text/KoInlineCite.cpp @@ -1,753 +1,753 @@ /* This file is part of the KDE project * Copyright (C) 2011 Smit Patel * * 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. */ #include "KoInlineCite.h" #include "KoInlineTextObjectManager.h" #include #include #include #include #include #include #include "TextDebug.h" #include #include #include #include // Include Q_UNSUSED classes, for building on Windows #include class Q_DECL_HIDDEN KoInlineCite::Private { public: Private(KoInlineCite::Type t) : type(t) { } KoInlineCite::Type type; int posInDocument; QString label; QString bibliographyType; QString identifier; QString address; QString annote; QString author; QString booktitle; QString chapter; QString edition; QString editor; QString publicationType; QString institution; QString journal; QString month; QString note; QString number; QString organisation; QString pages; QString publisher; QString school; //university in UI and saved under text:school tag in XML format QString series; QString title; QString reportType; QString volume; QString year; QString url; QString isbn; QString issn; QString custom1; QString custom2; QString custom3; QString custom4; QString custom5; }; KoInlineCite::KoInlineCite(Type type) :KoInlineObject(true) ,d(new Private(type)) { } KoInlineCite::~KoInlineCite() { delete d; } KoInlineCite::Type KoInlineCite::type() const { return d->type; } void KoInlineCite::setType(Type t) { d->type = t; } QString KoInlineCite::dataField(const QString &fieldName) const { if ( fieldName == "address" ) { return d->address; } else if ( fieldName == "annote" ) { return d->annote; } else if ( fieldName == "author" ) { return d->author; } else if ( fieldName == "bibliography-type" ) { return d->bibliographyType; } else if ( fieldName == "booktitle" ) { return d->booktitle; } else if ( fieldName == "chapter" ) { return d->chapter; } else if ( fieldName == "custom1" ) { return d->custom1; } else if ( fieldName == "custom2" ) { return d->custom2; } else if ( fieldName == "custom3" ) { return d->custom3; } else if ( fieldName == "custom4" ) { return d->custom4; } else if ( fieldName == "custom5" ) { return d->custom5; } else if ( fieldName == "edition" ) { return d->edition; } else if ( fieldName == "editor" ) { return d->editor; } else if ( fieldName == "howpublished" ) { return d->publicationType; } else if ( fieldName == "identifier" ) { return d->identifier; } else if ( fieldName == "institution" ) { return d->institution; } else if ( fieldName == "isbn" ) { return d->isbn; } else if ( fieldName == "issn" ) { return d->issn; } else if ( fieldName == "journal" ) { return d->journal; } else if ( fieldName == "month" ) { return d->month; } else if ( fieldName == "note" ) { return d->note; } else if ( fieldName == "number" ) { return d->number; } else if ( fieldName == "organisations" ) { return d->organisation; } else if ( fieldName == "pages" ) { return d->pages; } else if ( fieldName == "publisher" ) { return d->publisher; } else if ( fieldName == "report-type" ) { return d->reportType; } else if ( fieldName == "school" ) { return d->school; } else if ( fieldName == "series" ) { return d->series; } else if ( fieldName == "title" ) { return d->title; } else if ( fieldName == "url" ) { return d->url; } else if ( fieldName == "volume" ) { return d->volume; } else if ( fieldName == "year" ) { return d->year; } else { return QString(); } } void KoInlineCite::setIdentifier(const QString &identifier) { d->identifier = identifier; } void KoInlineCite::setAddress(const QString &addr) { d->address = addr; } void KoInlineCite::setAnnotation(const QString &annotation) { d->annote = annotation; } void KoInlineCite::setAuthor(const QString &author) { d->author = author; } void KoInlineCite::setBibliographyType(const QString &bibliographyType) { d->bibliographyType = bibliographyType; } void KoInlineCite::setBookTitle(const QString &booktitle) { d->booktitle = booktitle; } void KoInlineCite::setChapter(const QString &chapter) { d->chapter = chapter; } void KoInlineCite::setCustom1(const QString &custom1) { d->custom1 = custom1; } void KoInlineCite::setCustom2(const QString &custom2) { d->custom2 = custom2; } void KoInlineCite::setCustom3(const QString &custom3) { d->custom3 = custom3; } void KoInlineCite::setCustom4(const QString &custom4) { d->custom4 = custom4; } void KoInlineCite::setCustom5(const QString &custom5) { d->custom5 = custom5; } void KoInlineCite::setEdition(const QString &edition) { d->edition = edition; } void KoInlineCite::setEditor(const QString &editor) { d->editor = editor; } void KoInlineCite::setInstitution(const QString &institution) { d->institution = institution; } void KoInlineCite::setISBN(const QString &isbn) { d->isbn = isbn; } void KoInlineCite::setISSN(const QString &issn) { d->issn = issn; } void KoInlineCite::setJournal(const QString &journal) { d->journal = journal; } void KoInlineCite::setLabel(const QString &label) { d->label = label; } void KoInlineCite::setMonth(const QString &month) { d->month = month; } void KoInlineCite::setNote(const QString ¬e) { d->note = note; } void KoInlineCite::setNumber(const QString &number) { d->number = number; } void KoInlineCite::setOrganisation(const QString &organisation) { d->organisation = organisation; } void KoInlineCite::setPages(const QString &pages) { d->pages = pages; } void KoInlineCite::setPublicationType(const QString &publicationType) { d->publicationType = publicationType; } void KoInlineCite::setPublisher(const QString &publisher) { d->publisher = publisher; } void KoInlineCite::setReportType(const QString &reportType) { d->reportType = reportType; } void KoInlineCite::setSchool(const QString &school) { d->school = school; } void KoInlineCite::setSeries(const QString &series) { d->series = series; } void KoInlineCite::setTitle(const QString &title) { d->title = title; } void KoInlineCite::setURL(const QString &url) { d->url = url; } void KoInlineCite::setVolume(const QString &volume) { d->volume = volume; } void KoInlineCite::setYear(const QString &year) { d->year = year; } QString KoInlineCite::identifier() const { return d->identifier; } QString KoInlineCite::address() const { return d->address; } QString KoInlineCite::annotation() const { return d->annote; } QString KoInlineCite::author() const { return d->author; } QString KoInlineCite::bibliographyType() const { return d->bibliographyType; } QString KoInlineCite::bookTitle() const { return d->booktitle; } QString KoInlineCite::chapter() const { return d->chapter; } QString KoInlineCite::custom1() const { return d->custom1; } QString KoInlineCite::custom2() const { return d->custom2; } QString KoInlineCite::custom3() const { return d->custom3; } QString KoInlineCite::custom4() const { return d->custom4; } QString KoInlineCite::custom5() const { return d->custom5; } QString KoInlineCite::edition() const { return d->edition; } QString KoInlineCite::editor() const { return d->editor; } QString KoInlineCite::institution() const { return d->institution; } QString KoInlineCite::isbn() const { return d->isbn; } QString KoInlineCite::issn() const { return d->issn; } QString KoInlineCite::journal() const { return d->journal; } QString KoInlineCite::month() const { return d->month; } QString KoInlineCite::note() const { return d->note; } QString KoInlineCite::number() const { return d->number; } QString KoInlineCite::organisations() const { return d->organisation; } QString KoInlineCite::pages() const { return d->pages; } QString KoInlineCite::publisher() const { return d->publisher; } QString KoInlineCite::publicationType() const { return d->publicationType; } QString KoInlineCite::reportType() const { return d->reportType; } QString KoInlineCite::school() const { return d->school; } QString KoInlineCite::series() const { return d->series; } QString KoInlineCite::title() const { return d->title; } QString KoInlineCite::volume() const { return d->volume; } QString KoInlineCite::year() const { return d->year; } QString KoInlineCite::url() const { return d->url; } int KoInlineCite::posInDocument() const { return d->posInDocument; } bool KoInlineCite::operator!= ( const KoInlineCite &cite ) const { return !(d->address == cite.address() && d->annote == cite.annotation() && d->author == cite.author() && d->bibliographyType == cite.bibliographyType() && d->booktitle == cite.bookTitle() && d->chapter == cite.chapter() && d->custom1 == cite.custom1() && d->custom2 == cite.custom2() && d->custom3 == cite.custom3() && d->custom4 == cite.custom4() && d->custom5 == cite.custom5() && d->edition == cite.edition() && d->editor == cite.editor() && d->identifier == cite.identifier() && d->institution == cite.institution() && d->isbn == cite.isbn() && d->issn == cite.issn() && d->journal == cite.journal() && d->month == cite.month() && d->note == cite.note() && d->number == cite.number() && d->organisation == cite.organisations() && d->pages == cite.pages() && d->publicationType == cite.publicationType() && d->publisher == cite.publisher() && d->reportType == cite.reportType() && d->school == cite.school() && d->series == cite.series() && d->title == cite.title() && d->url == cite.url() && d->volume == cite.volume() && d->year == cite.year()); } KoInlineCite &KoInlineCite::operator =(const KoInlineCite &cite) { d->address = cite.address(); d->annote = cite.annotation(); d->author = cite.author(); d->bibliographyType = cite.bibliographyType(); d->booktitle = cite.bookTitle(); d->chapter = cite.chapter(); d->custom1 = cite.custom1(); d->custom2 = cite.custom2(); d->custom3 = cite.custom3(); d->custom4 = cite.custom4(); d->custom5 = cite.custom5(); d->edition = cite.edition(); d->editor = cite.editor(); d->identifier = cite.identifier(); d->institution = cite.institution(); d->isbn = cite.isbn(); d->issn = cite.issn(); d->journal = cite.journal(); d->month = cite.month(); d->note = cite.note(); d->number = cite.number(); d->organisation = cite.organisations(); d->pages = cite.pages(); d->publicationType = cite.publicationType(); d->publisher = cite.publisher(); d->reportType = cite.reportType(); d->school = cite.school(); d->series = cite.series(); d->title = cite.title(); d->url = cite.url(); d->volume = cite.volume(); d->year = cite.year(); return *this; } void KoInlineCite::updatePosition(const QTextDocument *document, int posInDocument, const QTextCharFormat &format) { Q_UNUSED(document); Q_UNUSED(format); d->posInDocument = posInDocument; } void KoInlineCite::resize(const QTextDocument *document, QTextInlineObject &object, int posInDocument, const QTextCharFormat &format, QPaintDevice *pd) { Q_UNUSED(document); Q_UNUSED(posInDocument); if (d->identifier.isEmpty()) return; KoOdfBibliographyConfiguration *bibConfiguration = KoTextDocument(document).styleManager()->bibliographyConfiguration(); if (!bibConfiguration->numberedEntries()) { - d->label = QString("%1%2%3").arg(bibConfiguration->prefix()) - .arg(d->identifier) - .arg(bibConfiguration->suffix()); + d->label = QString("%1%2%3").arg(bibConfiguration->prefix(), + d->identifier, + bibConfiguration->suffix()); } else { - d->label = QString("%1%2%3").arg(bibConfiguration->prefix()) - .arg(QString::number(manager()->citationsSortedByPosition(true).indexOf(this) + 1)) - .arg(bibConfiguration->suffix()); + d->label = QString("%1%2%3").arg(bibConfiguration->prefix(), + QString::number(manager()->citationsSortedByPosition(true).indexOf(this) + 1), + bibConfiguration->suffix()); } Q_ASSERT(format.isCharFormat()); QFontMetricsF fm(format.font(), pd); object.setWidth(fm.width(d->label)); object.setAscent(fm.ascent()); object.setDescent(fm.descent()); } void KoInlineCite::paint(QPainter &painter, QPaintDevice *pd, const QTextDocument *document, const QRectF &rect, const QTextInlineObject &object, int posInDocument, const QTextCharFormat &format) { Q_UNUSED(document); Q_UNUSED(object); Q_UNUSED(posInDocument); if (d->identifier.isEmpty()) return; KoOdfBibliographyConfiguration *bibConfiguration = KoTextDocument(document).styleManager()->bibliographyConfiguration(); if (!bibConfiguration->numberedEntries()) { - d->label = QString("%1%2%3").arg(bibConfiguration->prefix()) - .arg(d->identifier) - .arg(bibConfiguration->suffix()); + d->label = QString("%1%2%3").arg(bibConfiguration->prefix(), + d->identifier, + bibConfiguration->suffix()); } else { - d->label = QString("%1%2%3").arg(bibConfiguration->prefix()) - .arg(QString::number(manager()->citationsSortedByPosition(true, document->firstBlock()).indexOf(this) + 1)) - .arg(bibConfiguration->suffix()); + d->label = QString("%1%2%3").arg(bibConfiguration->prefix(), + QString::number(manager()->citationsSortedByPosition(true, document->firstBlock()).indexOf(this) + 1), + bibConfiguration->suffix()); } QFont font(format.font(), pd); QTextLayout layout(d->label, font, pd); layout.setCacheEnabled(true); QList layouts; QTextLayout::FormatRange range; range.start = 0; range.length = d->label.length(); range.format = format; range.format.setVerticalAlignment(QTextCharFormat::AlignNormal); layouts.append(range); layout.setAdditionalFormats(layouts); QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute); option.setTextDirection(object.textDirection()); layout.setTextOption(option); layout.beginLayout(); layout.createLine(); layout.endLayout(); layout.draw(&painter, rect.topLeft()); } bool KoInlineCite::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(context); //KoTextLoader loader(context); if (element.namespaceURI() == KoXmlNS::text && element.localName() == "bibliography-mark") { d->identifier = element.attributeNS(KoXmlNS::text, "identifier"); d->bibliographyType = element.attributeNS(KoXmlNS::text, "bibliography-type"); d->address = element.attributeNS(KoXmlNS::text, "address"); d->annote = element.attributeNS(KoXmlNS::text, "annote"); d->author = element.attributeNS(KoXmlNS::text, "author"); d->booktitle = element.attributeNS(KoXmlNS::text, "booktitle"); d->chapter = element.attributeNS(KoXmlNS::text, "chapter"); d->edition = element.attributeNS(KoXmlNS::text, "edition"); d->editor = element.attributeNS(KoXmlNS::text, "editor"); d->publicationType = element.attributeNS(KoXmlNS::text, "howpublished"); d->institution = element.attributeNS(KoXmlNS::text, "institution"); d->journal = element.attributeNS(KoXmlNS::text, "journal"); d->month = element.attributeNS(KoXmlNS::text, "month"); d->note = element.attributeNS(KoXmlNS::text, "note"); d->number = element.attributeNS(KoXmlNS::text, "number"); d->organisation = element.attributeNS(KoXmlNS::text, "organisations"); d->pages = element.attributeNS(KoXmlNS::text, "pages"); d->publisher = element.attributeNS(KoXmlNS::text, "publisher"); d->school = element.attributeNS(KoXmlNS::text, "school"); d->series = element.attributeNS(KoXmlNS::text, "series"); d->title = element.attributeNS(KoXmlNS::text, "title"); d->reportType = element.attributeNS(KoXmlNS::text, "report-type"); d->volume = element.attributeNS(KoXmlNS::text, "volume"); d->year = element.attributeNS(KoXmlNS::text, "year"); d->url = element.attributeNS(KoXmlNS::text, "url"); d->isbn = element.attributeNS(KoXmlNS::text, "isbn"); d->issn = element.attributeNS(KoXmlNS::text, "issn"); d->custom1 = element.attributeNS(KoXmlNS::text, "custom1"); d->custom2 = element.attributeNS(KoXmlNS::text, "custom2"); d->custom3 = element.attributeNS(KoXmlNS::text, "custom3"); d->custom4 = element.attributeNS(KoXmlNS::text, "custom4"); d->custom5 = element.attributeNS(KoXmlNS::text, "custom5"); //Now checking for cloned citation (with same identifier) if (manager()->citations(true).keys().count(d->identifier) > 1) { this->setType(KoInlineCite::ClonedCitation); } } else { return false; } return true; } void KoInlineCite::saveOdf(KoShapeSavingContext &context) { KoXmlWriter *writer = &context.xmlWriter(); writer->startElement("text:bibliography-mark", false); if (!d->identifier.isEmpty()) writer->addAttribute("text:identifier", d->identifier); //can't be "" //to be changed later if (!d->bibliographyType.isEmpty()) writer->addAttribute("text:bibliography-type", d->bibliographyType); if (!d->address.isEmpty()) writer->addAttribute("text:address",d->identifier); if (!d->annote.isEmpty()) writer->addAttribute("text:annote",d->annote); if (!d->author.isEmpty()) writer->addAttribute("text:author",d->author); if (!d->booktitle.isEmpty()) writer->addAttribute("text:booktitle",d->booktitle); if (!d->chapter.isEmpty()) writer->addAttribute("text:chapter",d->chapter); if (!d->edition.isEmpty()) writer->addAttribute("text:edition",d->edition); if (!d->editor.isEmpty()) writer->addAttribute("text:editor",d->editor); if (!d->publicationType.isEmpty()) writer->addAttribute("text:howpublished",d->publicationType); if (!d->institution.isEmpty()) writer->addAttribute("text:institution",d->institution); if (!d->journal.isEmpty()) writer->addAttribute("text:journal",d->journal); if (!d->month.isEmpty()) writer->addAttribute("text:month",d->month); if (!d->note.isEmpty()) writer->addAttribute("text:note",d->note); if (!d->number.isEmpty()) writer->addAttribute("text:number",d->number); if (!d->pages.isEmpty()) writer->addAttribute("text:pages",d->pages); if (!d->publisher.isEmpty()) writer->addAttribute("text:publisher",d->publisher); if (!d->school.isEmpty()) writer->addAttribute("text:school",d->school); if (!d->series.isEmpty()) writer->addAttribute("text:series",d->series); if (!d->title.isEmpty()) writer->addAttribute("text:title",d->title); if (!d->reportType.isEmpty()) writer->addAttribute("text:report-type",d->reportType); if (!d->volume.isEmpty()) writer->addAttribute("text:volume",d->volume); if (!d->year.isEmpty()) writer->addAttribute("text:year",d->year); if (!d->url.isEmpty()) writer->addAttribute("text:url",d->url); if (!d->isbn.isEmpty()) writer->addAttribute("text:isbn",d->isbn); if (!d->issn.isEmpty()) writer->addAttribute("text:issn",d->issn); if (!d->custom1.isEmpty()) writer->addAttribute("text:custom1",d->custom1); if (!d->custom2.isEmpty()) writer->addAttribute("text:custom2",d->custom2); if (!d->custom3.isEmpty()) writer->addAttribute("text:custom3",d->custom3); if (!d->custom4.isEmpty()) writer->addAttribute("text:custom4",d->custom4); if (!d->custom5.isEmpty()) writer->addAttribute("text:custom5",d->custom5); writer->addTextNode(QString("[%1]").arg(d->identifier)); writer->endElement(); } diff --git a/libs/text/KoNamedVariable.h b/libs/text/KoNamedVariable.h index 43527a94288..9a6cd1a3273 100644 --- a/libs/text/KoNamedVariable.h +++ b/libs/text/KoNamedVariable.h @@ -1,59 +1,60 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * 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. */ #ifndef KONAMEDVARIABLE_H #define KONAMEDVARIABLE_H #include "KoVariable.h" #include "kotext_export.h" /** * This inlineObject shows the curent value of a variable as registered in the KoVariableManager. * The proper way to create a new class is to use KoVariableManager::createVariable() */ class KoNamedVariable : public KoVariable { +Q_OBJECT public: /// return the name of this named variable QString name() const { return m_name; } virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context); virtual void saveOdf(KoShapeSavingContext &context); protected: friend class KoVariableManager; /** * Constructor * @param key the property that represents the named variable. As defined internally in the KoVariableManager * @param name the name of the variable. */ KoNamedVariable(Property key, const QString &name); private: /// reimplemented method void propertyChanged(Property property, const QVariant &value); /// reimplemented method void setup(); const QString m_name; const Property m_key; }; #endif diff --git a/libs/text/KoSectionModel.cpp b/libs/text/KoSectionModel.cpp index 42e4d7372a1..49449125fb6 100644 --- a/libs/text/KoSectionModel.cpp +++ b/libs/text/KoSectionModel.cpp @@ -1,217 +1,217 @@ #include "KoSectionModel.h" #include #include #include KoSectionModel::KoSectionModel(QTextDocument *doc) : QAbstractItemModel() , m_doc(doc) { KoTextDocument(m_doc).setSectionModel(this); } KoSectionModel::~KoSectionModel() { foreach(KoSection *sec, m_registeredSections) { delete sec; // This will delete associated KoSectionEnd in KoSection destructor } } KoSection *KoSectionModel::createSection(const QTextCursor &cursor, KoSection *parent, const QString &name) { if (!isValidNewName(name)) { return 0; } KoSection *result = new KoSection(cursor, name, parent); // Lets find our number in parent's children by cursor position QVector children = (parent ? parent->children() : m_rootSections); int childrenId = children.size(); for (int i = 0; i < children.size(); i++) { if (cursor.position() < children[i]->bounds().first) { childrenId = i; break; } } // We need to place link from parent to children in childId place // Also need to find corresponding index and declare operations in terms of model insertToModel(result, childrenId); return result; } KoSection *KoSectionModel::createSection(const QTextCursor &cursor, KoSection *parent) { return createSection(cursor, parent, possibleNewName()); } KoSectionEnd *KoSectionModel::createSectionEnd(KoSection *section) { return new KoSectionEnd(section); } KoSection *KoSectionModel::sectionAtPosition(int pos) const { // TODO: Rewrite it by traversing Model as tree KoSection *result = 0; int level = -1; // Seeking the section with maximum level QHash::ConstIterator it = m_sectionNames.begin(); for (; it != m_sectionNames.end(); ++it) { QPair bounds = it.value()->bounds(); if (bounds.first > pos || bounds.second < pos) { continue; } if (it.value()->level() > level) { result = it.value(); level = it.value()->level(); } } return result; } bool KoSectionModel::isValidNewName(const QString &name) const { return (m_sectionNames.constFind(name) == m_sectionNames.constEnd()); } QString KoSectionModel::possibleNewName() { QString newName; int i = m_registeredSections.count(); do { i++; newName = i18nc("new numbered section name", "New section %1", i); } while (!isValidNewName(newName)); return newName; } bool KoSectionModel::setName(KoSection *section, const QString &name) { if (section->name() == name || isValidNewName(name)) { section->setName(name); //TODO: we don't have name in columns, but need something to notify views about change emit dataChanged(m_modelIndex[section], m_modelIndex[section]); return true; } return false; } void KoSectionModel::allowMovingEndBound() { QSet::ConstIterator it = m_registeredSections.constBegin(); for (; it != m_registeredSections.constEnd(); ++it) { (*it)->setKeepEndBound(false); } } int KoSectionModel::findRowOfChild(KoSection *section) const { QVector lookOn; if (!section->parent()) { lookOn = m_rootSections; } else { lookOn = section->parent()->children(); } int result = lookOn.indexOf(section); Q_ASSERT(result != -1); return result; } QModelIndex KoSectionModel::index(int row, int column, const QModelIndex &parentIdx) const { if (!hasIndex(row, column, parentIdx)) { return QModelIndex(); } if (!parentIdx.isValid()) { return createIndex(row, column, m_rootSections[row]); } KoSection *parent = static_cast(parentIdx.internalPointer()); - return createIndex(row, column, parent->children()[row]); + return createIndex(row, column, parent->children().at(row)); } QModelIndex KoSectionModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return QModelIndex(); } KoSection *section = static_cast(child.internalPointer()); KoSection *parent = section->parent(); if (parent) { return createIndex(findRowOfChild(parent), 0, parent); } return QModelIndex(); } int KoSectionModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_rootSections.size(); } return static_cast(parent.internalPointer())->children().size(); } int KoSectionModel::columnCount(const QModelIndex &/*parent*/) const { return 1; } QVariant KoSectionModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.column() == 0 && role == PointerRole) { QVariant v; v.setValue(static_cast(index.internalPointer())); return v; } return QVariant(); } void KoSectionModel::insertToModel(KoSection *section, int childIdx) { Q_ASSERT(isValidNewName(section->name())); KoSection *parent = section->parent(); if (parent) { // Inserting to some section beginInsertRows(m_modelIndex[parent], childIdx, childIdx); parent->insertChild(childIdx, section); endInsertRows(); m_modelIndex[section] = QPersistentModelIndex(index(childIdx, 0, m_modelIndex[parent])); } else { // It will be root section beginInsertRows(QModelIndex(), childIdx, childIdx); m_rootSections.insert(childIdx, section); endInsertRows(); m_modelIndex[section] = QPersistentModelIndex(index(childIdx, 0, QModelIndex())); } m_registeredSections.insert(section); m_sectionNames[section->name()] = section; } void KoSectionModel::deleteFromModel(KoSection *section) { KoSection *parent = section->parent(); int childIdx = findRowOfChild(section); if (parent) { // Deleting non root section beginRemoveRows(m_modelIndex[parent], childIdx, childIdx); parent->removeChild(childIdx); endRemoveRows(); } else { // Deleting root section beginRemoveRows(QModelIndex(), childIdx, childIdx); m_rootSections.remove(childIdx); endRemoveRows(); } m_modelIndex.remove(section); m_sectionNames.remove(section->name()); } diff --git a/libs/text/KoTextDebug.cpp b/libs/text/KoTextDebug.cpp index 1790b406527..f14b02965e6 100644 --- a/libs/text/KoTextDebug.cpp +++ b/libs/text/KoTextDebug.cpp @@ -1,999 +1,998 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 Elvis Stansvik * * 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. */ #include "KoTextDebug.h" #include #include #include #include #include #include #include #include #include #include "styles/KoParagraphStyle.h" #include "styles/KoCharacterStyle.h" #include "styles/KoListStyle.h" #include "styles/KoTableStyle.h" #include "styles/KoTableCellStyle.h" #include "styles/KoStyleManager.h" #include "KoTextDocument.h" #include "KoTextBlockData.h" #include #include #include #define PARAGRAPH_BORDER_DEBUG int KoTextDebug::depth = 0; const int KoTextDebug::INDENT = 2; const QTextDocument *KoTextDebug::document = 0; #define dumpIndent(T) { for (int i=0; i) static QString fontProperties(const QTextCharFormat &textFormat) { QMap properties = textFormat.properties(); QStringList fontProps; // add only font properties here foreach(int id, properties.keys()) { - QString key, value; switch (id) { case QTextFormat::FontFamily: fontProps.append(properties[id].toString()); break; case QTextFormat::FontPointSize: fontProps.append(QString("%1pt").arg(properties[id].toDouble())); break; case QTextFormat::FontSizeAdjustment: fontProps.append(QString("%1adj").arg(properties[id].toDouble())); break; case QTextFormat::FontWeight: fontProps.append(QString("weight %1").arg(properties[id].toInt())); break; case QTextFormat::FontItalic: fontProps.append(properties[id].toBool() ? "italic" : "non-italic"); break; case QTextFormat::FontPixelSize: fontProps.append(QString("%1px").arg(properties[id].toDouble())); break; case QTextFormat::FontFixedPitch: fontProps.append(properties[id].toBool() ? "fixedpitch" : "varpitch"); break; case QTextFormat::FontCapitalization: fontProps.append(QString("caps %1").arg(properties[id].toInt())); break; case KoCharacterStyle::FontCharset: fontProps.append(properties[id].toString()); break; case QTextFormat::FontStyleHint: fontProps.append(QString::number(properties[id].toInt())); break; case QTextFormat::FontKerning: fontProps.append(QString("kerning %1").arg(properties[id].toInt())); break; default: break; } } return fontProps.join(","); } void KoTextDebug::dumpDocument(const QTextDocument *doc, QTextStream &out) { Q_ASSERT(doc); document = doc; out << QString("").arg(doc->defaultFont().toString()); dumpFrame(document->rootFrame(), out); out << ""; document = 0; } QString KoTextDebug::textAttributes(const KoCharacterStyle &style) { QTextCharFormat format; style.applyStyle(format); return textAttributes(format); } QString KoTextDebug::inlineObjectAttributes(const QTextCharFormat &textFormat) { QString attrs; if (textFormat.objectType() == QTextFormat::UserObject + 1) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(document).inlineTextObjectManager(); KoInlineObject *inlineObject = inlineObjectManager->inlineTextObject(textFormat); if (KoInlineNote *note = dynamic_cast(inlineObject)) { attrs.append(QString(" id=\"%1\"").arg(note->id())); if (note->type() == KoInlineNote::Footnote) { attrs.append(" type=\"footnote\""); } else if (note->type() == KoInlineNote::Endnote) { attrs.append(" type=\"endnote\""); } attrs.append(QString(" label=\"%1\"").arg(note->label())); } else { attrs.append(" type=\"inlineobject\">"); } } return attrs; } QString KoTextDebug::textAttributes(const QTextCharFormat &textFormat) { QString attrs; QTextImageFormat imageFormat = textFormat.toImageFormat(); if (imageFormat.isValid()) { attrs.append(" type=\"image\">"); return attrs; } KoStyleManager *styleManager = document ? KoTextDocument(document).styleManager() : 0; if (styleManager && textFormat.hasProperty(KoCharacterStyle::StyleId)) { int id = textFormat.intProperty(KoCharacterStyle::StyleId); KoCharacterStyle *characterStyle = styleManager->characterStyle(id); attrs.append(" characterStyle=\"id:").append(QString::number(id)); if (characterStyle) attrs.append(" name:").append(characterStyle->name()); attrs.append("\""); } QMap properties = textFormat.properties(); attrs.append(" type=\"char\""); QString fontProps = fontProperties(textFormat); if (!fontProps.isEmpty()) attrs.append(QString(" font=\"%1\"").arg(fontProps)); if (textFormat.isAnchor()) { attrs.append(QString(" achorHref=\"%1\"").arg(textFormat.anchorHref())); attrs.append(QString(" achorName=\"%1\"").arg(textFormat.anchorName())); } foreach(int id, properties.keys()) { QString key, value; switch (id) { case QTextFormat::TextOutline: { key = "outline"; QPen pen = qvariant_cast(properties[id]); if (pen.style() == Qt::NoPen) value = "false"; else value = pen.color().name(); break; } case KoCharacterStyle::UnderlineStyle: key = "underlinestyle"; value = QString::number(properties[id].toInt()); break; case QTextFormat::TextUnderlineColor: key = "underlinecolor"; value = qvariant_cast(properties[id]).name(); break; case KoCharacterStyle::UnderlineType: key = "underlinetype"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::UnderlineMode: key = "underlinemode"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::UnderlineWeight: key = "underlineweight"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::UnderlineWidth: key = "underlinewidth"; value = QString::number(properties[id].toDouble()); break; case KoCharacterStyle::StrikeOutStyle: key = "strikeoutstyle"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::StrikeOutColor: key = "strikeoutcolor"; value = qvariant_cast(properties[id]).name(); break; case KoCharacterStyle::StrikeOutType: key = "strikeouttype"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::StrikeOutMode: key = "strikeoutmode"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::StrikeOutWeight: key = "strikeoutweight"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::StrikeOutWidth: key = "strikeoutwidth"; value = QString::number(properties[id].toDouble()); break; case QTextFormat::ForegroundBrush: key = "foreground"; value = qvariant_cast(properties[id]).color().name(); // beware! break; case QTextFormat::BackgroundBrush: key = "background"; value = qvariant_cast(properties[id]).color().name(); // beware! break; case QTextFormat::BlockAlignment: key = "align"; value = QString::number(properties[id].toInt()); break; case QTextFormat::TextIndent: key = "textindent"; value = QString::number(properties[id].toInt()); break; case QTextFormat::BlockIndent: key = "indent"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::Country: key = "country"; value = properties[id].toString(); break; case KoCharacterStyle::Language: key = "language"; value = properties[id].toString(); break; case KoCharacterStyle::HasHyphenation: key = "hypenation"; value = properties[id].toBool(); break; case KoCharacterStyle::StrikeOutText: key = "strikeout-text"; value = properties[id].toString(); break; case KoCharacterStyle::FontCharset: key = "font-charset"; value = properties[id].toString(); break; case KoCharacterStyle::TextRotationAngle: key = "rotation-angle"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::TextRotationScale: key = "text-rotation-scale"; value = properties[id].toInt() == KoCharacterStyle::Fixed ? "Fixed" : "LineHeight"; break; case KoCharacterStyle::TextScale: key = "text-scale"; value = QString::number(properties[id].toInt()); break; case KoCharacterStyle::InlineRdf: key = "inline-rdf"; value = QString::number(properties[id].toInt()); break; default: key = "unknown"+QString::number(id); value = QString::number(properties[id].toInt()); break; } if (!key.isEmpty()) attrs.append(" ").append(key).append("=\"").append(value).append("\""); } return attrs; } QString KoTextDebug::paraAttributes(const KoParagraphStyle &style) { QTextBlockFormat format; style.applyStyle(format); return paraAttributes(format); } QString KoTextDebug::paraAttributes(const QTextBlockFormat &blockFormat) { QString attrs; KoStyleManager *styleManager = document ? KoTextDocument(document).styleManager() : 0; if (styleManager && blockFormat.hasProperty(KoParagraphStyle::StyleId)) { int id = blockFormat.intProperty(KoParagraphStyle::StyleId); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(id); attrs.append(" paragraphStyle=\"id:").append(QString::number(id)); if (paragraphStyle) attrs.append(" name:").append(paragraphStyle->name()); attrs.append("\""); } QMap properties = blockFormat.properties(); foreach(int id, properties.keys()) { QString key, value; switch (id) { // the following are 'todo' case KoParagraphStyle::PercentLineHeight: case KoParagraphStyle::FixedLineHeight: case KoParagraphStyle::MinimumLineHeight: case KoParagraphStyle::LineSpacing: case KoParagraphStyle::LineSpacingFromFont: case KoParagraphStyle::AlignLastLine: case KoParagraphStyle::WidowThreshold: case KoParagraphStyle::OrphanThreshold: case KoParagraphStyle::DropCapsTextStyle: case KoParagraphStyle::FollowDocBaseline: case KoParagraphStyle::HasLeftBorder: case KoParagraphStyle::HasTopBorder: case KoParagraphStyle::HasRightBorder: case KoParagraphStyle::HasBottomBorder: case KoParagraphStyle::BorderLineWidth: case KoParagraphStyle::SecondBorderLineWidth: case KoParagraphStyle::DistanceToSecondBorder: case KoParagraphStyle::LeftPadding: case KoParagraphStyle::TopPadding: case KoParagraphStyle::RightPadding: case KoParagraphStyle::BottomPadding: case KoParagraphStyle::LeftBorderColor: case KoParagraphStyle::TopInnerBorderWidth: case KoParagraphStyle::TopBorderSpacing: case KoParagraphStyle::TopBorderStyle: case KoParagraphStyle::TopBorderColor: case KoParagraphStyle::RightInnerBorderWidth: case KoParagraphStyle::RightBorderSpacing: case KoParagraphStyle::RightBorderStyle: case KoParagraphStyle::RightBorderColor: case KoParagraphStyle::BottomInnerBorderWidth: case KoParagraphStyle::BottomBorderSpacing: case KoParagraphStyle::BottomBorderStyle: case KoParagraphStyle::BottomBorderColor: case KoParagraphStyle::ListStyleId: case KoParagraphStyle::ListStartValue: case KoParagraphStyle::RestartListNumbering: case KoParagraphStyle::TextProgressionDirection: case KoParagraphStyle::MasterPageName: case KoParagraphStyle::OutlineLevel: break; case KoParagraphStyle::AutoTextIndent: key = "autotextindent"; value = properties[id].toBool() ? "true" : "false" ; break; #ifdef PARAGRAPH_BORDER_DEBUG // because it tends to get annoyingly long :) case KoParagraphStyle::LeftBorderWidth: key = "border-width-left"; value = QString::number(properties[id].toDouble()) ; break; case KoParagraphStyle::TopBorderWidth: key = "border-width-top"; value = QString::number(properties[id].toDouble()) ; break; case KoParagraphStyle::RightBorderWidth: key = "border-width-right"; value = QString::number(properties[id].toDouble()) ; break; case KoParagraphStyle::BottomBorderWidth: key = "border-width-bottom"; value = QString::number(properties[id].toDouble()) ; break; case KoParagraphStyle::LeftBorderStyle: key = "border-style-left"; value = QString::number(properties[id].toDouble()) ; break; case KoParagraphStyle::LeftBorderSpacing: key = "inner-border-spacing-left"; value = QString::number(properties[id].toDouble()) ; break; case KoParagraphStyle::LeftInnerBorderWidth: key = "inner-border-width-left"; value = QString::number(properties[id].toDouble()) ; break; #endif case KoParagraphStyle::TabStopDistance: key = "tab-stop-distance"; value = QString::number(properties[id].toDouble()); break; case KoParagraphStyle::TabPositions: key = "tab-stops"; value.clear(); foreach(const QVariant & qvtab, qvariant_cast >(properties[id])) { KoText::Tab tab = qvtab.value(); value.append("{"); value.append(" pos:").append(QString::number(tab.position)); value.append(" type:").append(QString::number(tab.type)); if (! tab.delimiter.isNull()) value.append(" delim:").append(QString(tab.delimiter)); value.append(" leadertype:").append(QString::number(tab.leaderType)); value.append(" leaderstyle:").append(QString::number(tab.leaderStyle)); value.append(" leaderweight:").append(QString::number(tab.leaderWeight)); value.append(" leaderwidth:").append(QString().setNum(tab.leaderWidth)); value.append(" leadercolor:").append(tab.leaderColor.name()); if (! tab.leaderText.isEmpty()) value.append(" leadertext:").append(QString(tab.leaderText)); value.append("}, "); } break; case KoParagraphStyle::DropCaps: key = "drop-caps"; value = QString::number(properties[id].toBool()); break; case KoParagraphStyle::DropCapsLines: key = "drop-caps-lines"; value = QString::number(properties[id].toInt()); break; case KoParagraphStyle::DropCapsLength: key = "drop-caps-length"; value = QString::number(properties[id].toInt()); break; case KoParagraphStyle::DropCapsDistance: key = "drop-caps-distance"; value = QString::number(properties[id].toDouble()); break; case QTextFormat::BlockBottomMargin: value = QString::number(properties[id].toDouble()); if (value != "0") key = "block-bottom-margin"; break; case QTextFormat::BlockTopMargin: value = QString::number(properties[id].toDouble()); if (value != "0") key = "block-top-margin"; break; case QTextFormat::BlockLeftMargin: value = QString::number(properties[id].toDouble()); if (value != "0") key = "block-left-margin"; break; case QTextFormat::BlockRightMargin: value = QString::number(properties[id].toDouble()); if (value != "0") key = "block-right-margin"; break; case KoParagraphStyle::UnnumberedListItem: key = "unnumbered-list-item"; value = QString::number(properties[id].toBool()); break; case KoParagraphStyle::IsListHeader: key = "list-header"; value = '1'; break; case KoParagraphStyle::ListLevel: key = "list-level"; value = QString::number(properties[id].toInt()); break; default: break; } if (!key.isEmpty()) attrs.append(" ").append(key).append("=\"").append(value).append("\""); } return attrs; } QString KoTextDebug::listAttributes(const QTextListFormat &listFormat) { QString attrs; KoStyleManager *styleManager = document ? KoTextDocument(document).styleManager() : 0; if (styleManager && listFormat.hasProperty(KoListStyle::StyleId)) { int id = listFormat.intProperty(KoListStyle::StyleId); KoListStyle *listStyle = styleManager->listStyle(id); attrs.append(" listStyle=\"id:").append(QString::number(id)); if (listStyle) attrs.append(" name:").append(listStyle->name()); attrs.append("\""); } QMap properties = listFormat.properties(); foreach(int id, properties.keys()) { QString key, value; switch (id) { case QTextListFormat::ListStyle: key = "type"; value = QString::number(properties[id].toInt()); break; case QTextListFormat::ListIndent: key = "indent"; value = QString::number(properties[id].toDouble()); break; case KoListStyle::ListItemPrefix: key = "prefix"; value = properties[id].toString(); break; case KoListStyle::ListItemSuffix: key = "suffix"; value = properties[id].toString(); break; case KoListStyle::StartValue: key = "start-value"; value = QString::number(properties[id].toInt()); break; case KoListStyle::Level: key = "level"; value = QString::number(properties[id].toInt()); break; case KoListStyle::DisplayLevel: key = "display-level"; value = QString::number(properties[id].toInt()); break; case KoListStyle::Alignment: key = "alignment"; value = QString::number(properties[id].toInt()); break; case KoListStyle::RelativeBulletSize: key = "bullet-size"; value = QString::number(properties[id].toInt()); break; case KoListStyle::BulletCharacter: key = "bullet-char"; value = properties[id].toString(); break; case KoListStyle::LetterSynchronization: key = "letter-sync"; value = QString::number(properties[id].toInt()); break; case KoListStyle::StyleId: key = "styleid"; value = QString::number(properties[id].toInt()); break; case KoListStyle::MinimumWidth: key = "minimum-width"; value = QString::number(properties[id].toDouble()); break; case KoListStyle::ListId: key = "list-id"; value = QString::number(properties[id].toInt()); break; case KoListStyle::IsOutline: key = "is-outline"; value = properties[id].toBool(); break; case KoListStyle::Indent: key = "indent"; value = QString::number(properties[id].toInt()); break; case KoListStyle::MinimumDistance: key = "minimum-distance"; value = QString::number(properties[id].toDouble()); break; case KoListStyle::Width: key = "width"; value = QString::number(properties[id].toDouble()); break; case KoListStyle::Height: key = "height"; value = QString::number(properties[id].toDouble()); break; case KoListStyle::BulletImage: key = "bullet-image"; value = QString::number((quintptr)(properties[id].value())); break; case KoListStyle::Margin: key="margin-left"; value =QString::number(properties[id].toInt()); break; case KoListStyle::TextIndent: key="text-indent"; value =QString::number(properties[id].toInt()); break; case KoListStyle::AlignmentMode: key="label-alignment"; value=QString(properties[id].toBool()? "true":"false"); break; case KoListStyle::LabelFollowedBy: key="label-followed-by"; value =QString::number(properties[id].toInt()); break; case KoListStyle::TabStopPosition: key="tab-stop-position"; value =QString::number(properties[id].toInt()); break; default: break; } if (!key.isEmpty()) attrs.append(" ").append(key).append("=\"").append(value).append("\""); } return attrs; } QString KoTextDebug::tableAttributes(const KoTableStyle &tableStyle) { QTextTableFormat format; tableStyle.applyStyle(format); return tableAttributes(format); } QString KoTextDebug::tableAttributes(const QTextTableFormat &tableFormat) { QString attrs; KoStyleManager *styleManager = document ? KoTextDocument(document).styleManager() : 0; if (styleManager) { int id = tableFormat.intProperty(KoTableStyle::StyleId); KoTableStyle *tableStyle = styleManager->tableStyle(id); attrs.append(" tableStyle=\"id:").append(QString::number(id)); if (tableStyle) attrs.append(" name:").append(tableStyle->name()); attrs.append("\""); } QMap properties = tableFormat.properties(); foreach(int id, properties.keys()) { QString key, value; switch (id) { case QTextTableFormat::TableColumnWidthConstraints: case QTextFormat::BackgroundBrush: key = "background"; value = qvariant_cast(properties[id]).color().name(); // beware! break; case QTextFormat::BlockAlignment: key = "alignment"; switch (properties[id].toInt()) { case Qt::AlignLeft: value = "left"; break; case Qt::AlignRight: value = "right"; break; case Qt::AlignHCenter: value = "center"; break; case Qt::AlignJustify: value = "justify"; break; default: value.clear(); break; } break; case KoTableStyle::KeepWithNext: key = "keep-with-next"; value = properties[id].toBool() ? "true" : "false"; break; case KoTableStyle::BreakBefore: key = "break-before"; value = properties[id].toBool() ? "true" : "false"; break; case KoTableStyle::BreakAfter: key = "break-after"; value = properties[id].toBool() ? "true" : "false"; break; case KoTableStyle::MayBreakBetweenRows: key = "may-break-between-rows"; value = properties[id].toBool() ? "true" : "false"; break; case KoTableStyle::MasterPageName: key = "master-page-name"; value = properties[id].toString(); break; case QTextTableFormat::TableColumns: key = "columns"; value = QString::number(properties[id].toInt()); break; case QTextTableFormat::TableCellSpacing: key = "cell-spacing"; value = QString::number(properties[id].toDouble()); break; case QTextTableFormat::TableHeaderRowCount: key = "header-row-count"; value = QString::number(properties[id].toInt()); break; default: break; } if (!key.isEmpty()) attrs.append(" ").append(key).append("=\"").append(value).append("\""); } return attrs; } QString KoTextDebug::frameAttributes(const QTextFrameFormat &frameFormat) { QString attrs; QMap properties = frameFormat.properties(); foreach(int id, properties.keys()) { QString key, value; switch (id) { case QTextFrameFormat::FrameBorderBrush: break; case QTextFrameFormat::FrameBorderStyle: key = "border-style"; // determine border style. switch (properties[id].toInt()) { case QTextFrameFormat::BorderStyle_None: value = "None"; break; case QTextFrameFormat::BorderStyle_Dotted: value = "Dotted"; break; case QTextFrameFormat::BorderStyle_Dashed: value = "Dashed"; break; case QTextFrameFormat::BorderStyle_Solid: value = "Solid"; break; case QTextFrameFormat::BorderStyle_Double: value = "Double"; break; case QTextFrameFormat::BorderStyle_DotDash: value = "DotDash"; break; case QTextFrameFormat::BorderStyle_DotDotDash: value = "DotDotDash"; break; case QTextFrameFormat::BorderStyle_Groove: value = "Groove"; break; case QTextFrameFormat::BorderStyle_Ridge: value = "Ridge"; break; case QTextFrameFormat::BorderStyle_Inset: value = "Inset"; break; case QTextFrameFormat::BorderStyle_Outset: value = "Outset"; break; default: value = "Unknown"; break; } break; case QTextFrameFormat::FrameBorder: key = "border"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameMargin: key = "margin"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FramePadding: key = "padding"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameWidth: key = "width"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameHeight: key = "height"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameTopMargin: key = "top-margin"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameBottomMargin: key = "bottom-margin"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameLeftMargin: key = "left-margin"; value = QString::number(properties[id].toDouble()); break; case QTextFrameFormat::FrameRightMargin: key = "right-margin"; value = QString::number(properties[id].toDouble()); break; default: break; } if (!key.isEmpty()) attrs.append(" ").append(key).append("=\"").append(value).append("\""); } return attrs; } QString KoTextDebug::tableCellAttributes(const KoTableCellStyle &tableCellStyle) { QTextTableCellFormat format; tableCellStyle.applyStyle(format); return tableCellAttributes(format); } QString KoTextDebug::tableCellAttributes(const QTextTableCellFormat &tableCellFormat) { QString attrs; KoStyleManager *styleManager = document ? KoTextDocument(document).styleManager() : 0; if (styleManager) { int id = tableCellFormat.intProperty(KoTableCellStyle::StyleId); KoTableCellStyle *tableCellStyle = styleManager->tableCellStyle(id); attrs.append(" tableCellStyle=\"id:").append(QString::number(id)); if (tableCellStyle) attrs.append(" name:").append(tableCellStyle->name()); attrs.append("\""); } QMap properties = tableCellFormat.properties(); foreach(int id, properties.keys()) { QString key, value; switch (id) { case QTextTableCellFormat::TableCellRowSpan: key = "row-span"; value = QString::number(properties[id].toInt()); break; case QTextTableCellFormat::TableCellColumnSpan: key = "column-span"; value = QString::number(properties[id].toInt()); break; case QTextFormat::TableCellTopPadding: key = "top-padding"; value = QString::number(properties[id].toDouble()); break; case QTextFormat::TableCellBottomPadding: key = "bottom-padding"; value = QString::number(properties[id].toDouble()); break; case QTextFormat::TableCellLeftPadding: key = "left-padding"; value = QString::number(properties[id].toDouble()); break; case QTextFormat::TableCellRightPadding: key = "right-padding"; value = QString::number(properties[id].toDouble()); break; case KoTableCellStyle::MasterPageName: key = "master-page-name"; value = properties[id].toString(); break; default: break; } if (!key.isEmpty()) attrs.append(" ").append(key).append("=\"").append(value).append("\""); } return attrs; } void KoTextDebug::dumpFrame(const QTextFrame *frame, QTextStream &out) { depth += INDENT; dumpIndent(depth); out << "frameFormat()) << '>' << endl; QTextFrame::iterator iterator = frame->begin(); for (; !iterator.atEnd() && !iterator.atEnd(); ++iterator) { QTextFrame *childFrame = iterator.currentFrame(); QTextBlock textBlock = iterator.currentBlock(); if (childFrame) { QTextTable *table = qobject_cast(childFrame); if (table) { dumpTable(table, out); } else { dumpFrame(frame, out); } } else if (textBlock.isValid()) { dumpBlock(textBlock, out); } } dumpIndent(depth); out << "" << endl; depth -= INDENT; } void KoTextDebug::dumpBlock(const QTextBlock &block, QTextStream &out) { depth += INDENT; QString attrs; attrs.append(paraAttributes(block.blockFormat())); //attrs.append(" blockcharformat=\"").append(textAttributes(QTextCursor(block).blockCharFormat())).append('\"'); attrs.append(textAttributes(QTextCursor(block).blockCharFormat())); QTextList *list = block.textList(); if (list) { attrs.append(" list=\"item:").append(QString::number(list->itemNumber(block) + 1)).append('/') .append(QString::number(list->count())); attrs.append('"'); attrs.append(listAttributes(list->format())); } dumpIndent(depth); out << "' << endl; QTextBlock::Iterator iterator = block.begin(); for (; !iterator.atEnd() && !iterator.atEnd(); ++iterator) { QTextFragment fragment = iterator.fragment(); if (fragment.isValid()) { dumpFragment(fragment, out); } } dumpIndent(depth); out << "" << endl; depth -= INDENT; if (block.next().isValid()) out << ' '; } void KoTextDebug::dumpTable(const QTextTable *table, QTextStream &out) { depth += INDENT; QString attrs; attrs.append(tableAttributes(table->format())); attrs.append(frameAttributes(table->frameFormat())); // include frame attributes too. dumpIndent(depth); out << "' << endl; // loop through all the cells in the table and dump the cells. for (int row = 0; row < table->rows(); ++row) { for (int column = 0; column < table->columns(); ++column) { dumpTableCell(table->cellAt(row, column), out); } } dumpIndent(depth); out << "" << endl; depth -= INDENT; } void KoTextDebug::dumpTableCell(const QTextTableCell &cell, QTextStream &out) { depth += INDENT; QString attrs; attrs.append(textAttributes(cell.format())); attrs.append(tableCellAttributes(cell.format().toTableCellFormat())); dumpIndent(depth); out << "' << endl; // iterate through the cell content. QTextFrame::iterator cellIter = cell.begin(); while (!cellIter.atEnd()) { if (cellIter.currentFrame() != 0) { // content is a frame or table. dumpFrame(cellIter.currentFrame(), out); } else { // content is a block. dumpBlock(cellIter.currentBlock(), out); } ++cellIter; } dumpIndent(depth); out << "\n"; depth -= INDENT; } void KoTextDebug::dumpFragment(const QTextFragment &fragment, QTextStream &out) { depth += INDENT; QTextCharFormat charFormat = fragment.charFormat(); KoInlineObject *inlineObject = KoTextDocument(document).inlineTextObjectManager()->inlineTextObject(charFormat); if (inlineObject) { QString cf = inlineObjectAttributes(charFormat); dumpIndent(depth); out << "\n"; } else { QString cf = textAttributes(charFormat); dumpIndent(depth); out << "\n"; dumpIndent(depth + INDENT); out << '|' << fragment.text() << "|\n"; dumpIndent(depth); out << "\n"; } depth -= INDENT; } diff --git a/libs/text/KoTextEditor.cpp b/libs/text/KoTextEditor.cpp index 13b23fe3fa9..18a31ea009c 100644 --- a/libs/text/KoTextEditor.cpp +++ b/libs/text/KoTextEditor.cpp @@ -1,1666 +1,1666 @@ /* This file is part of the KDE project * Copyright (C) 2009-2012 Pierre Stirnweiss * Copyright (C) 2006-2010 Thomas Zander * Copyright (c) 2011 Boudewijn Rempt * Copyright (C) 2011-2015 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * Copyright (C) 2015 Soma Schliszka * * 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. */ #include "KoTextEditor.h" #include "KoTextEditor_p.h" #include "KoList.h" #include "KoBookmark.h" #include "KoAnnotation.h" #include "KoTextRangeManager.h" #include "KoInlineTextObjectManager.h" #include "KoInlineNote.h" #include "KoInlineCite.h" #include "BibliographyGenerator.h" #include #include #include #include #include #include "KoShapeAnchor.h" #include "KoTextDocument.h" #include "KoTextLocator.h" #include "KoTableOfContentsGeneratorInfo.h" #include "KoBibliographyInfo.h" #include "changetracker/KoChangeTracker.h" #include "changetracker/KoChangeTrackerElement.h" #include "styles/KoCharacterStyle.h" #include "styles/KoParagraphStyle.h" #include "styles/KoStyleManager.h" #include "styles/KoTableCellStyle.h" #include "styles/KoTableStyle.h" #include "KoTableColumnAndRowStyleManager.h" #include "commands/DeleteTableRowCommand.h" #include "commands/DeleteTableColumnCommand.h" #include "commands/InsertTableRowCommand.h" #include "commands/InsertTableColumnCommand.h" #include "commands/ResizeTableCommand.h" #include "commands/TextPasteCommand.h" #include "commands/ListItemNumberingCommand.h" #include "commands/ChangeListCommand.h" #include "commands/InsertInlineObjectCommand.h" #include "commands/DeleteCommand.h" #include "commands/DeleteAnchorsCommand.h" #include "commands/DeleteAnnotationsCommand.h" #include "commands/InsertNoteCommand.h" #include "commands/AddTextRangeCommand.h" #include "commands/AddAnnotationCommand.h" #include "commands/RenameSectionCommand.h" #include "commands/NewSectionCommand.h" #include "commands/SplitSectionsCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" #include "KoTextDebug.h" Q_DECLARE_METATYPE(QTextFrame*) /*Private*/ KoTextEditor::Private::Private(KoTextEditor *qq, QTextDocument *document) : q(qq) , document (document) , addNewCommand(true) , dummyMacroAdded(false) , customCommandCount(0) , editProtectionCached(false) { caret = QTextCursor(document); editorState = NoOp; } void KoTextEditor::Private::emitTextFormatChanged() { emit q->textFormatChanged(); } void KoTextEditor::Private::newLine(KUndo2Command *parent) { // Handle if this is the special block before a table bool hiddenTableHandling = caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable); if (hiddenTableHandling) { // Easy solution is to go back to the end of previous block and do the insertion from there. // However if there is no block before we have a problem. This may be the case if there is // a table before or we are at the beginning of a cell or a document. // So here is a better approach // 1) create block // 2) select the previous block so it get's deleted and replaced // 3) remove HiddenByTable from both new and previous block // 4) actually make new line replacing the block we just inserted // 5) set HiddenByTable on the block just before the table again caret.insertText("oops you should never see this"); caret.insertBlock(); caret.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); caret.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); QTextBlockFormat bf = caret.blockFormat(); bf.clearProperty(KoParagraphStyle::HiddenByTable); caret.setBlockFormat(bf); } if (caret.hasSelection()) { q->deleteChar(false, parent); } KoTextDocument textDocument(document); KoStyleManager *styleManager = textDocument.styleManager(); KoParagraphStyle *nextStyle = 0; KoParagraphStyle *currentStyle = 0; if (styleManager) { int id = caret.blockFormat().intProperty(KoParagraphStyle::StyleId); currentStyle = styleManager->paragraphStyle(id); if (currentStyle == 0) // not a style based parag. Lets make the next one correct. nextStyle = styleManager->defaultParagraphStyle(); else nextStyle = styleManager->paragraphStyle(currentStyle->nextStyle()); Q_ASSERT(nextStyle); if (currentStyle == nextStyle) nextStyle = 0; } QTextCharFormat format = caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } // Build the block format and subtract the properties that are not inherited QTextBlockFormat bf = caret.blockFormat(); bf.clearProperty(KoParagraphStyle::BreakBefore); bf.clearProperty(KoParagraphStyle::ListStartValue); bf.clearProperty(KoParagraphStyle::UnnumberedListItem); bf.clearProperty(KoParagraphStyle::IsListHeader); bf.clearProperty(KoParagraphStyle::MasterPageName); bf.clearProperty(KoParagraphStyle::OutlineLevel); bf.clearProperty(KoParagraphStyle::HiddenByTable); // We should stay in the same section so we can't start new one. bf.clearProperty(KoParagraphStyle::SectionStartings); // But we move all the current endings to the next paragraph. QTextBlockFormat origin = caret.blockFormat(); origin.clearProperty(KoParagraphStyle::SectionEndings); caret.setBlockFormat(origin); // Build the block char format which is just a copy QTextCharFormat bcf = caret.blockCharFormat(); // Actually insert the new paragraph char int startPosition = caret.position(); caret.insertBlock(bf, bcf); int endPosition = caret.position(); // Mark the CR as a tracked change QTextCursor changeCursor(document); changeCursor.beginEditBlock(); changeCursor.setPosition(startPosition); changeCursor.setPosition(endPosition, QTextCursor::KeepAnchor); changeCursor.endEditBlock(); q->registerTrackedChange(changeCursor, KoGenChange::InsertChange, kundo2_i18n("New Paragraph"), format, format, false); // possibly change the style if requested if (nextStyle) { QTextBlock block = caret.block(); if (currentStyle) currentStyle->unapplyStyle(block); nextStyle->applyStyle(block); format = block.charFormat(); } caret.setCharFormat(format); if (hiddenTableHandling) { // see code and comment above QTextBlockFormat bf = caret.blockFormat(); bf.setProperty(KoParagraphStyle::HiddenByTable, true); caret.setBlockFormat(bf); caret.movePosition(QTextCursor::PreviousCharacter); } } /*KoTextEditor*/ //TODO factor out the changeTracking charFormat setting from all individual slots to a public slot, which will be available for external commands (TextShape) //The BlockFormatVisitor and CharFormatVisitor are used when a property needs to be modified relative to its current value (which could be different over the selection). For example: increase indentation by 10pt. //The BlockFormatVisitor is also used for the change tracking of a blockFormat. The changeTracker stores the information about the changeId in the charFormat. The BlockFormatVisitor ensures that thd changeId is set on the whole block (even if only a part of the block is actually selected). //Should such mechanisms be later provided directly by Qt, we could dispose of these classes. KoTextEditor::KoTextEditor(QTextDocument *document) : QObject(document), d (new Private(this, document)) { connect (d->document, SIGNAL(undoCommandAdded()), this, SLOT(documentCommandAdded())); } KoTextEditor::~KoTextEditor() { delete d; } KoTextEditor *KoTextEditor::getTextEditorFromCanvas(KoCanvasBase *canvas) { KoSelection *selection = canvas->shapeManager()->selection(); if (selection) { foreach(KoShape *shape, selection->selectedShapes()) { if (KoTextShapeDataBase *textData = qobject_cast(shape->userData())) { KoTextDocument doc(textData->document()); return doc.textEditor(); } } } return 0; } QTextCursor* KoTextEditor::cursor() { return &(d->caret); } const QTextCursor KoTextEditor::constCursor() const { return QTextCursor(d->caret); } void KoTextEditor::registerTrackedChange(QTextCursor &selection, KoGenChange::Type changeType, const KUndo2MagicString &title, QTextFormat& format, QTextFormat& prevFormat, bool applyToWholeBlock) { KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (!changeTracker || !changeTracker->recordChanges()) { // clear the ChangeTrackerId from the passed in selection, without recursively registring // change tracking again ;) int start = qMin(selection.position(), selection.anchor()); int end = qMax(selection.position(), selection.anchor()); QTextBlock block = selection.block(); if (block.position() > start) block = block.document()->findBlock(start); while (block.isValid() && block.position() < end) { QTextBlock::iterator iter = block.begin(); while (!iter.atEnd()) { QTextFragment fragment = iter.fragment(); if (fragment.position() > end) { break; } if (fragment.position() + fragment.length() <= start) { ++iter; continue; } QTextCursor cursor(block); cursor.setPosition(fragment.position()); QTextCharFormat fm = fragment.charFormat(); if (fm.hasProperty(KoCharacterStyle::ChangeTrackerId)) { fm.clearProperty(KoCharacterStyle::ChangeTrackerId); int to = qMin(end, fragment.position() + fragment.length()); cursor.setPosition(to, QTextCursor::KeepAnchor); cursor.setCharFormat(fm); iter = block.begin(); } else { ++iter; } } block = block.next(); } } else { if (changeType != KoGenChange::DeleteChange) { //first check if there already is an identical change registered just before or just after the selection. If so, merge appropriatly. //TODO implement for format change. handle the prevFormat/newFormat check. QTextCursor checker = QTextCursor(selection); int idBefore = 0; int idAfter = 0; int changeId = 0; int selectionBegin = qMin(checker.anchor(), checker.position()); int selectionEnd = qMax(checker.anchor(), checker.position()); checker.setPosition(selectionBegin); if (!checker.atBlockStart()) { int changeId = checker.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt(); if (changeId && changeTracker->elementById(changeId)->getChangeType() == changeType) idBefore = changeId; } else { if (!checker.currentTable()) { int changeId = checker.blockFormat().intProperty(KoCharacterStyle::ChangeTrackerId); if (changeId && changeTracker->elementById(changeId)->getChangeType() == changeType) idBefore = changeId; } else { idBefore = checker.currentTable()->format().intProperty(KoCharacterStyle::ChangeTrackerId); if (!idBefore) { idBefore = checker.currentTable()->cellAt(checker).format().intProperty(KoCharacterStyle::ChangeTrackerId); } } } checker.setPosition(selectionEnd); if (!checker.atEnd()) { checker.movePosition(QTextCursor::NextCharacter); idAfter = changeTracker->mergeableId(changeType, title, checker.charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt()); } changeId = (idBefore)?idBefore:idAfter; switch (changeType) {//TODO: this whole thing actually needs to be done like a visitor. If the selection contains several change regions, the parenting needs to be individualised. case KoGenChange::InsertChange: if (!changeId) changeId = changeTracker->getInsertChangeId(title, 0); break; case KoGenChange::FormatChange: if (!changeId) changeId = changeTracker->getFormatChangeId(title, format, prevFormat, 0); break; case KoGenChange::DeleteChange: //this should never be the case break; default: ;// do nothing } if (applyToWholeBlock) { selection.movePosition(QTextCursor::StartOfBlock); selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); } QTextCharFormat f; f.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); selection.mergeCharFormat(f); QTextBlock startBlock = selection.document()->findBlock(selection.anchor()); QTextBlock endBlock = selection.document()->findBlock(selection.position()); while (startBlock.isValid() && startBlock != endBlock) { startBlock = startBlock.next(); QTextCursor cursor(startBlock); QTextBlockFormat blockFormat; blockFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); cursor.mergeBlockFormat(blockFormat); QTextCharFormat blockCharFormat = cursor.blockCharFormat(); if (blockCharFormat.hasProperty(KoCharacterStyle::ChangeTrackerId)) { blockCharFormat.clearProperty(KoCharacterStyle::ChangeTrackerId); cursor.setBlockCharFormat(blockCharFormat); } } } } } // To figure out if a the blocks of the selection are write protected we need to // traverse the entire document as sections build up the protectiveness recursively. void KoTextEditor::recursivelyVisitSelection(QTextFrame::iterator it, KoTextVisitor &visitor) const { do { if (visitor.abortVisiting()) return; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); if (table) { // There are 4 ways this table can be selected: // - "before to mid" // - "mid to after" // - "complex mid to mid" // - "simple mid to mid" // The 3 first are entire cells, the fourth is within a cell if (d->caret.selectionStart() <= table->lastPosition() && d->caret.selectionEnd() >= table->firstPosition()) { // We have a selection somewhere QTextTableCell cell1 = table->cellAt(d->caret.selectionStart()); QTextTableCell cell2 = table->cellAt(d->caret.selectionEnd()); if (cell1 != cell2 || !cell1.isValid() || !cell2.isValid()) { // And the selection is complex or entire table int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; if (!cell1.isValid() || !cell2.isValid()) { // entire table visitor.visitTable(table, KoTextVisitor::Entirely); selectionRow = selectionColumn = 0; selectionRowSpan = table->rows(); selectionColumnSpan = table->columns(); } else { visitor.visitTable(table, KoTextVisitor::Partly); d->caret.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); } for (int r = selectionRow; r < selectionRow + selectionRowSpan; r++) { for (int c = selectionColumn; c < selectionColumn + selectionColumnSpan; c++) { QTextTableCell cell = table->cellAt(r,c); if (!cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { visitor.visitTableCell(&cell, KoTextVisitor::Partly); recursivelyVisitSelection(cell.begin(), visitor); } else { visitor.nonVisit(); } if (visitor.abortVisiting()) return; } } } else { visitor.visitTable(table, KoTextVisitor::Partly); // And the selection is simple if (!cell1.format().boolProperty(KoTableCellStyle::CellIsProtected)) { visitor.visitTableCell(&cell1, KoTextVisitor::Entirely); recursivelyVisitSelection(cell1.begin(), visitor); } else { visitor.nonVisit(); } return; } } if (d->caret.selectionEnd() <= table->lastPosition()) { return; } } else if (subFrame) { recursivelyVisitSelection(subFrame->begin(), visitor); } else { // TODO build up the section stack if (d->caret.selectionStart() < block.position() + block.length() && d->caret.selectionEnd() >= block.position()) { // We have a selection somewhere if (true) { // TODO don't change if block is protected by section visitor.visitBlock(block, d->caret); } else { visitor.nonVisit(); } } // TODO tear down the section stack if (d->caret.selectionEnd() < block.position() + block.length()) { return; } } if (!it.atEnd()) { ++it; } } while (!it.atEnd()); } KoBookmark *KoTextEditor::addBookmark(const QString &name) {//TODO changeTracking KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Bookmark")); KoBookmark *bookmark = new KoBookmark(d->caret); bookmark->setName(name); bookmark->setManager(KoTextDocument(d->document).textRangeManager()); addCommand(new AddTextRangeCommand(bookmark, topCommand)); endEditBlock(); return bookmark; } KoTextRangeManager *KoTextEditor::textRangeManager() const { return KoTextDocument(d->document).textRangeManager(); } KoAnnotation *KoTextEditor::addAnnotation(KoShape *annotationShape) { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Annotation")); KoAnnotation *annotation = new KoAnnotation(d->caret); KoTextRangeManager *textRangeManager = KoTextDocument(d->document).textRangeManager(); annotation->setManager(textRangeManager); //FIXME: I need the name, a unique name, we can set selected text as annotation name or use createUniqueAnnotationName function // to do it for us. QString name = annotation->createUniqueAnnotationName(textRangeManager->annotationManager(), "", false); annotation->setName(name); annotation->setAnnotationShape(annotationShape); addCommand(new AddAnnotationCommand(annotation, topCommand)); endEditBlock(); return annotation; } KoInlineObject *KoTextEditor::insertIndexMarker() {//TODO changeTracking if (isEditProtected()) { return 0; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Index")); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); } QTextBlock block = d->caret.block(); if (d->caret.position() >= block.position() + block.length() - 1) return 0; // can't insert one at end of text - if (block.text()[ d->caret.position() - block.position()].isSpace()) + if (block.text().at( d->caret.position() - block.position()).isSpace()) return 0; // can't insert one on a whitespace as that does not indicate a word. KoTextLocator *tl = new KoTextLocator(); KoTextDocument(d->document).inlineTextObjectManager()->insertInlineObject(d->caret, tl); d->updateState(KoTextEditor::Private::NoOp); return tl; } void KoTextEditor::insertInlineObject(KoInlineObject *inliner, KUndo2Command *cmd) { if (isEditProtected()) { return; } KUndo2Command *topCommand = cmd; if (!cmd) { topCommand = beginEditBlock(kundo2_i18n("Insert Variable")); } if (d->caret.hasSelection()) { deleteChar(false, topCommand); } d->caret.beginEditBlock(); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); } QTextCharFormat format = d->caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } InsertInlineObjectCommand *insertInlineObjectCommand = new InsertInlineObjectCommand(inliner, d->document, topCommand); Q_UNUSED(insertInlineObjectCommand); d->caret.endEditBlock(); if (!cmd) { addCommand(topCommand); endEditBlock(); } emit cursorPositionChanged(); } void KoTextEditor::updateInlineObjectPosition(int start, int end) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(d->document).inlineTextObjectManager(); // and, of course, every inline object after the current position has the wrong position QTextCursor cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), start); while (!cursor.isNull() && (end > -1 && cursor.position() < end )) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *obj = inlineObjectManager->inlineTextObject(fmt); obj->updatePosition(d->document, cursor.position(), fmt); cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), cursor.position()); } } void KoTextEditor::removeAnchors(const QList &anchors, KUndo2Command *parent) { Q_ASSERT(parent); addCommand(new DeleteAnchorsCommand(anchors, d->document, parent)); } void KoTextEditor::removeAnnotations(const QList &annotations, KUndo2Command *parent) { Q_ASSERT(parent); addCommand(new DeleteAnnotationsCommand(annotations, d->document, parent)); } void KoTextEditor::insertFrameBreak() { if (isEditProtected()) { return; } QTextCursor curr(d->caret.block()); if (dynamic_cast (curr.currentFrame())) { return; } d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Insert Break")); QTextBlock block = d->caret.block(); if (d->caret.position() == block.position() && block.length() > 0) { // start of parag QTextBlockFormat bf = d->caret.blockFormat(); bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); d->caret.insertBlock(bf); if (block.textList()) block.textList()->remove(block); } else { QTextBlockFormat bf = d->caret.blockFormat(); if (!d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { newLine(); } bf = d->caret.blockFormat(); bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); d->caret.setBlockFormat(bf); } d->updateState(KoTextEditor::Private::NoOp); emit cursorPositionChanged(); } void KoTextEditor::paste(KoCanvasBase *canvas, const QMimeData *mimeData, bool pasteAsText) { if (isEditProtected()) { return; } KoShapeController *shapeController = KoTextDocument(d->document).shapeController(); addCommand(new TextPasteCommand(mimeData, d->document, shapeController, canvas, 0, pasteAsText)); } void KoTextEditor::deleteChar(bool previous, KUndo2Command *parent) { if (isEditProtected()) { return; } KoShapeController *shapeController = KoTextDocument(d->document).shapeController(); // Find out if we should track changes or not // KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); // bool trackChanges = false; // if (changeTracker && changeTracker->recordChanges()) { // trackChanges = true; // } if (previous) { if (!d->caret.hasSelection() && d->caret.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { movePosition(QTextCursor::PreviousCharacter); if (d->caret.block().length() <= 1) { movePosition(QTextCursor::NextCharacter); } else return; // it becomes just a cursor movement; } } else { if (!d->caret.hasSelection() && d->caret.block().length() > 1) { QTextCursor tmpCursor = d->caret; tmpCursor.movePosition(QTextCursor::NextCharacter); if (tmpCursor.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { movePosition(QTextCursor::NextCharacter); return; // it becomes just a cursor movement; } } } if (previous) { addCommand(new DeleteCommand(DeleteCommand::PreviousChar, d->document, shapeController, parent)); } else { addCommand(new DeleteCommand(DeleteCommand::NextChar, d->document, shapeController, parent)); } } void KoTextEditor::toggleListNumbering(bool numberingEnabled) { if (isEditProtected()) { return; } addCommand(new ListItemNumberingCommand(block(), numberingEnabled)); emit textFormatChanged(); } void KoTextEditor::setListProperties(const KoListLevelProperties &llp, ChangeListFlags flags, KUndo2Command *parent) { if (isEditProtected()) { return; } if (flags & AutoListStyle && d->caret.block().textList() == 0) { flags = MergeWithAdjacentList; } if (KoList *list = KoTextDocument(d->document).list(d->caret.block().textList())) { KoListStyle *listStyle = list->style(); if (KoStyleManager *styleManager = KoTextDocument(d->document).styleManager()) { QList paragraphStyles = styleManager->paragraphStyles(); foreach (KoParagraphStyle *paragraphStyle, paragraphStyles) { if (paragraphStyle->listStyle() == listStyle || (paragraphStyle->list() && paragraphStyle->list()->style() == listStyle)) { flags = NoFlags; break; } } } } addCommand(new ChangeListCommand(d->caret, llp, flags, parent)); emit textFormatChanged(); } int KoTextEditor::anchor() const { return d->caret.anchor(); } bool KoTextEditor::atBlockEnd() const { return d->caret.atBlockEnd(); } bool KoTextEditor::atBlockStart() const { return d->caret.atBlockStart(); } bool KoTextEditor::atEnd() const { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { //auxFrame really is the auxillary frame if (d->caret.position() == auxFrame->firstPosition() - 1) { return true; } return false; } return d->caret.atEnd(); } bool KoTextEditor::atStart() const { return d->caret.atStart(); } QTextBlock KoTextEditor::block() const { return d->caret.block(); } int KoTextEditor::blockNumber() const { return d->caret.blockNumber(); } void KoTextEditor::clearSelection() { d->caret.clearSelection(); } int KoTextEditor::columnNumber() const { return d->caret.columnNumber(); } void KoTextEditor::deleteChar() { if (isEditProtected()) { return; } if (!d->caret.hasSelection()) { if (d->caret.atEnd()) return; // We alson need to refuse delete if we are at final pos in table cell if (QTextTable *table = d->caret.currentTable()) { QTextTableCell cell = table->cellAt(d->caret.position()); if (d->caret.position() == cell.lastCursorPosition().position()) { return; } } // We also need to refuse delete if it will delete a note frame QTextCursor after(d->caret); after.movePosition(QTextCursor::NextCharacter); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame != afterFrame) { return; } } deleteChar(false); emit cursorPositionChanged(); } void KoTextEditor::deletePreviousChar() { if (isEditProtected()) { return; } if (!d->caret.hasSelection()) { if (d->caret.atStart()) return; // We also need to refuse delete if we are at first pos in table cell if (QTextTable *table = d->caret.currentTable()) { QTextTableCell cell = table->cellAt(d->caret.position()); if (d->caret.position() == cell.firstCursorPosition().position()) { return; } } // We also need to refuse delete if it will delete a note frame QTextCursor after(d->caret); after.movePosition(QTextCursor::PreviousCharacter); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame != afterFrame) { return; } } deleteChar(true); emit cursorPositionChanged(); } QTextDocument *KoTextEditor::document() const { return d->caret.document(); } bool KoTextEditor::hasComplexSelection() const { return d->caret.hasComplexSelection(); } bool KoTextEditor::hasSelection() const { return d->caret.hasSelection(); } class ProtectionCheckVisitor : public KoTextVisitor { public: ProtectionCheckVisitor(const KoTextEditor *editor) : KoTextVisitor(const_cast(editor)) { } // override super's implementation to not waste cpu cycles virtual void visitBlock(QTextBlock&, const QTextCursor &) { } virtual void nonVisit() { setAbortVisiting(true); } }; bool KoTextEditor::isEditProtected(bool useCached) const { ProtectionCheckVisitor visitor(this); if (useCached) { if (! d->editProtectionCached) { recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); d->editProtected = visitor.abortVisiting(); d->editProtectionCached = true; } return d->editProtected; } d->editProtectionCached = false; recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); return visitor.abortVisiting(); } void KoTextEditor::insertTable(int rows, int columns) { if (isEditProtected() || rows <= 0 || columns <= 0) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextTableFormat tableFormat; tableFormat.setWidth(QTextLength(QTextLength::PercentageLength, 100)); tableFormat.setProperty(KoTableStyle::CollapsingBorders, true); tableFormat.setMargin(5); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Table"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } tableFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } QTextBlock currentBlock = d->caret.block(); if (d->caret.position() != currentBlock.position()) { d->caret.insertBlock(); currentBlock = d->caret.block(); } QTextTable *table = d->caret.insertTable(rows, columns, tableFormat); // Get (and thus create) columnandrowstyle manager so it becomes part of undo // and not something that happens uncontrollably during layout KoTableColumnAndRowStyleManager::getManager(table); // 'Hide' the block before the table QTextBlockFormat blockFormat = currentBlock.blockFormat(); QTextCursor cursor(currentBlock); blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true); cursor.setBlockFormat(blockFormat); // Define the initial cell format QTextTableCellFormat format; KoTableCellStyle cellStyle; cellStyle.setEdge(KoBorder::TopBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::LeftBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::BottomBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::RightBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setPadding(5); cellStyle.applyStyle(format); // Apply formatting to all cells for (int row = 0; row < table->rows(); ++row) { for (int col = 0; col < table->columns(); ++col) { QTextTableCell cell = table->cellAt(row, col); cell.setFormat(format); } } if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } void KoTextEditor::insertTableRowAbove() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableRowCommand(this, table, false)); } } void KoTextEditor::insertTableRowBelow() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableRowCommand(this, table, true)); } } void KoTextEditor::insertTableColumnLeft() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableColumnCommand(this, table, false)); } } void KoTextEditor::insertTableColumnRight() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableColumnCommand(this, table, true)); } } void KoTextEditor::deleteTableColumn() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new DeleteTableColumnCommand(this, table)); } } void KoTextEditor::deleteTableRow() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new DeleteTableRowCommand(this, table)); } } void KoTextEditor::mergeTableCells() { if (isEditProtected()) { return; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Merge Cells")); QTextTable *table = d->caret.currentTable(); if (table) { table->mergeCells(d->caret); } d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::splitTableCells() { if (isEditProtected()) { return; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Split Cells")); QTextTable *table = d->caret.currentTable(); if (table) { QTextTableCell cell = table->cellAt(d->caret); table->splitCell(cell.row(), cell.column(), 1, 1); } d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::adjustTableColumnWidth(QTextTable *table, int column, qreal width, KUndo2Command *parentCommand) { ResizeTableCommand *cmd = new ResizeTableCommand(table, true, column, width, parentCommand); addCommand(cmd); } void KoTextEditor::adjustTableRowHeight(QTextTable *table, int column, qreal height, KUndo2Command *parentCommand) { ResizeTableCommand *cmd = new ResizeTableCommand(table, false, column, height, parentCommand); addCommand(cmd); } void KoTextEditor::adjustTableWidth(QTextTable *table, qreal dLeft, qreal dRight) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Adjust Table Width")); d->caret.beginEditBlock(); QTextTableFormat fmt = table->format(); if (dLeft) { fmt.setLeftMargin(fmt.leftMargin() + dLeft); } if (dRight) { fmt.setRightMargin(fmt.rightMargin() + dRight); } table->setFormat(fmt); d->caret.endEditBlock(); d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::setTableBorderData(QTextTable *table, int row, int column, KoBorder::BorderSide cellSide, const KoBorder::BorderData &data) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Change Border Formatting")); d->caret.beginEditBlock(); QTextTableCell cell = table->cellAt(row, column); QTextCharFormat fmt = cell.format(); KoBorder border = fmt.property(KoTableCellStyle::Borders).value(); border.setBorderData(cellSide, data); fmt.setProperty(KoTableCellStyle::Borders, QVariant::fromValue(border)); cell.setFormat(fmt); d->caret.endEditBlock(); d->updateState(KoTextEditor::Private::NoOp); } KoInlineNote *KoTextEditor::insertFootNote() { if (isEditProtected()) { return 0; } InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Footnote, d->document); addCommand(cmd); emit cursorPositionChanged(); return cmd->m_inlineNote; } KoInlineNote *KoTextEditor::insertEndNote() { if (isEditProtected()) { return 0; } InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Endnote, d->document); addCommand(cmd); emit cursorPositionChanged(); return cmd->m_inlineNote; } void KoTextEditor::insertTableOfContents(KoTableOfContentsGeneratorInfo *info) { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table Of Contents")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table Of Contents")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextBlockFormat tocFormat; KoTableOfContentsGeneratorInfo *newToCInfo = info->clone(); QTextDocument *tocDocument = new QTextDocument(); tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(newToCInfo) ); tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(tocDocument)); //make sure we set up the textrangemanager on the subdocument as well KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Table Of Contents"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } tocFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } d->caret.insertBlock(); d->caret.movePosition(QTextCursor::Left); d->caret.insertBlock(tocFormat); d->caret.movePosition(QTextCursor::Right); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } void KoTextEditor::setTableOfContentsConfig(KoTableOfContentsGeneratorInfo *info, const QTextBlock &block) { if (isEditProtected()) { return; } KoTableOfContentsGeneratorInfo *newToCInfo=info->clone(); d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Modify Table Of Contents")); QTextCursor cursor(block); QTextBlockFormat tocBlockFormat=block.blockFormat(); tocBlockFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(newToCInfo) ); cursor.setBlockFormat(tocBlockFormat); d->updateState(KoTextEditor::Private::NoOp); emit cursorPositionChanged(); const_cast(document())->markContentsDirty(document()->firstBlock().position(), 0); } void KoTextEditor::insertBibliography(KoBibliographyInfo *info) { bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Bibliography")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Bibliography")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextBlockFormat bibFormat; KoBibliographyInfo *newBibInfo = info->clone(); QTextDocument *bibDocument = new QTextDocument(); bibFormat.setProperty( KoParagraphStyle::BibliographyData, QVariant::fromValue(newBibInfo)); bibFormat.setProperty( KoParagraphStyle::GeneratedDocument, QVariant::fromValue(bibDocument)); //make sure we set up the textrangemanager on the subdocument as well KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Bibliography"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } bibFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } d->caret.insertBlock(); d->caret.movePosition(QTextCursor::Left); d->caret.insertBlock(bibFormat); d->caret.movePosition(QTextCursor::Right); new BibliographyGenerator(bibDocument, block(), newBibInfo); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } KoInlineCite *KoTextEditor::insertCitation() { bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Add Citation")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Citation")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation); KoInlineTextObjectManager *manager = KoTextDocument(d->document).inlineTextObjectManager(); manager->insertInlineObject(d->caret,cite); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } return cite; } void KoTextEditor::insertText(const QString &text, const QString &hRef) { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Typing")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Typing")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } //first we make sure that we clear the inlineObject charProperty, if we have no selection if (!hasSelection && d->caret.charFormat().hasProperty(KoCharacterStyle::InlineInstanceId)) d->clearCharFormatProperty(KoCharacterStyle::InlineInstanceId); int startPosition = d->caret.position(); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); startPosition = d->caret.position(); } QTextCharFormat format = d->caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } static QRegExp urlScanner("\\S+://\\S+"); if (!hRef.isEmpty()) { format.setAnchor(true); format.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); if ((urlScanner.indexIn(hRef)) == 0) {//web url format.setAnchorHref(hRef); } else { format.setAnchorHref("#"+hRef); } } d->caret.insertText(text, format); int endPosition = d->caret.position(); //Mark the inserted text d->caret.setPosition(startPosition); d->caret.setPosition(endPosition, QTextCursor::KeepAnchor); registerTrackedChange(d->caret, KoGenChange::InsertChange, kundo2_i18n("Typing"), format, format, false); d->caret.clearSelection(); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } if (!hRef.isEmpty()) { format.setAnchor(false); format.clearProperty(KoCharacterStyle::Anchor); format.clearProperty(KoCharacterStyle::AnchorType); d->caret.setCharFormat(format); } emit cursorPositionChanged(); } bool KoTextEditor::movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n) { d->editProtectionCached = false; // We need protection against moving in and out of note areas QTextCursor after(d->caret); bool b = after.movePosition (operation, mode, n); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame == afterFrame) { if (after.selectionEnd() == after.document()->characterCount() -1) { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (operation == QTextCursor::End) { d->caret.setPosition(auxFrame->firstPosition() - 1, mode); emit cursorPositionChanged(); return true; } return false; } } d->caret = after; emit cursorPositionChanged(); return b; } return false; } void KoTextEditor::newSection() { if (isEditProtected()) { return; } NewSectionCommand *cmd = new NewSectionCommand(d->document); addCommand(cmd); emit cursorPositionChanged(); } void KoTextEditor::splitSectionsStartings(int sectionIdToInsertBefore) { if (isEditProtected()) { return; } addCommand(new SplitSectionsCommand( d->document, SplitSectionsCommand::Startings, sectionIdToInsertBefore)); emit cursorPositionChanged(); } void KoTextEditor::splitSectionsEndings(int sectionIdToInsertAfter) { if (isEditProtected()) { return; } addCommand(new SplitSectionsCommand( d->document, SplitSectionsCommand::Endings, sectionIdToInsertAfter)); emit cursorPositionChanged(); } void KoTextEditor::renameSection(KoSection* section, const QString &newName) { if (isEditProtected()) { return; } addCommand(new RenameSectionCommand(section, newName, document())); } void KoTextEditor::newLine() { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("New Paragraph")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("New Paragraph")); deleteChar(false, topCommand); } d->caret.beginEditBlock(); d->newLine(0); d->caret.endEditBlock(); if (hasSelection) { endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } class WithinSelectionVisitor : public KoTextVisitor { public: WithinSelectionVisitor(KoTextEditor *editor, int position) : KoTextVisitor(editor) , m_position(position) , m_returnValue(false) { } virtual void visitBlock(QTextBlock &block, const QTextCursor &caret) { if (m_position >= qMax(block.position(), caret.selectionStart()) && m_position <= qMin(block.position() + block.length(), caret.selectionEnd())) { m_returnValue = true; setAbortVisiting(true); } } int m_position; //the position we are searching for bool m_returnValue; //if position is within the selection }; bool KoTextEditor::isWithinSelection(int position) const { // we know the visitor doesn't do anything with the texteditor so let's const cast // to have a more beautiful outer api WithinSelectionVisitor visitor(const_cast(this), position); recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); return visitor.m_returnValue; } int KoTextEditor::position() const { return d->caret.position(); } void KoTextEditor::select(QTextCursor::SelectionType selection) { //TODO add selection of previous/next char, and option about hasSelection d->caret.select(selection); } QString KoTextEditor::selectedText() const { return d->caret.selectedText(); } QTextDocumentFragment KoTextEditor::selection() const { return d->caret.selection(); } int KoTextEditor::selectionEnd() const { return d->caret.selectionEnd(); } int KoTextEditor::selectionStart() const { return d->caret.selectionStart(); } void KoTextEditor::setPosition(int pos, QTextCursor::MoveMode mode) { d->editProtectionCached = false; if (pos == d->caret.document()->characterCount() -1) { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { return; } } if (mode == QTextCursor::MoveAnchor) { d->caret.setPosition (pos, mode); emit cursorPositionChanged(); } // We need protection against moving in and out of note areas QTextCursor after(d->caret); after.setPosition (pos, mode); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame == afterFrame) { d->caret = after; emit cursorPositionChanged(); } } void KoTextEditor::setVisualNavigation(bool b) { d->caret.setVisualNavigation (b); } bool KoTextEditor::visualNavigation() const { return d->caret.visualNavigation(); } const QTextFrame *KoTextEditor::currentFrame () const { return d->caret.currentFrame(); } const QTextList *KoTextEditor::currentList () const { return d->caret.currentList(); } const QTextTable *KoTextEditor::currentTable () const { return d->caret.currentTable(); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoTextEditor.cpp" diff --git a/libs/text/KoTextEditor_p.h b/libs/text/KoTextEditor_p.h index 7e8de6e1eb4..a686e19517c 100644 --- a/libs/text/KoTextEditor_p.h +++ b/libs/text/KoTextEditor_p.h @@ -1,264 +1,264 @@ /* This file is part of the KDE project * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2009 Thomas Zander * Copyright (C) 2015 Soma Schliszka * * 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. */ #ifndef KOTEXTEDITOR_P_H #define KOTEXTEDITOR_P_H #include "KoTextEditor.h" #include "KoTextDocument.h" #include "styles/KoParagraphStyle.h" #include "styles/KoStyleManager.h" #include "changetracker/KoChangeTracker.h" #include #include #include #include #include #include #include class KUndo2Command; class Q_DECL_HIDDEN KoTextEditor::Private { public: enum State { NoOp, KeyPress, Delete, Format, Custom }; explicit Private(KoTextEditor *qq, QTextDocument *document); ~Private() {} void documentCommandAdded(); void updateState(State newState, const KUndo2MagicString &title = KUndo2MagicString()); void newLine(KUndo2Command *parent); void clearCharFormatProperty(int propertyId); void emitTextFormatChanged(); KoTextEditor *q; QTextCursor caret; QTextDocument *document; QStack commandStack; bool addNewCommand; bool dummyMacroAdded; int customCommandCount; KUndo2MagicString commandTitle; State editorState; bool editProtected; bool editProtectionCached; }; class KoTextVisitor { public: /// The ObjectVisitingMode enum marks how was the visited object selected. enum ObjectVisitingMode { Partly, /// The visited object (table, cell, ...) is just @b partly selected. (Eg. just one cell is selected in the visited table) Entirely, /// The visited object (table, cell, ...) is @b entirely selected. }; explicit KoTextVisitor(KoTextEditor *editor) : m_abortVisiting(false) , m_editor(editor) { } virtual ~KoTextVisitor() {} // called whenever a visit was prevented by editprotection virtual void nonVisit() {} virtual void visitFragmentSelection(QTextCursor &) { } /** * This method allows to perform custom operation when the visitor reaches a QTextTable * @param visitedTable pointer to the currenlty visited table object * @param visitingMode flag, marks if the table is just partly visited or entirely */ virtual void visitTable(QTextTable *visitedTable, ObjectVisitingMode visitingMode) { Q_UNUSED(visitedTable); Q_UNUSED(visitingMode); } /** * This method allows to perform custom operation when the visitor reaches a QTextTableCell * @param visitedTable pointer to the currenlty visited cell object * @param visitingMode flag, marks if the cell is just partly visited or entirely */ virtual void visitTableCell(QTextTableCell *visitedCell, ObjectVisitingMode visitingMode) { Q_UNUSED(visitedCell); Q_UNUSED(visitingMode); } // The default implementation calls visitFragmentSelection on each fragment.intersect.selection virtual void visitBlock(QTextBlock &block, const QTextCursor &caret) { for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextCursor fragmentSelection(caret); fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position())); fragmentSelection.setPosition(qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor); if (fragmentSelection.anchor() >= fragmentSelection.position()) { continue; } visitFragmentSelection(fragmentSelection); } } bool abortVisiting() { return m_abortVisiting;} void setAbortVisiting(bool abort) {m_abortVisiting = abort;} KoTextEditor * editor() const {return m_editor;} private: bool m_abortVisiting; KoTextEditor *m_editor; }; class BlockFormatVisitor { public: BlockFormatVisitor() {} virtual ~BlockFormatVisitor() {} virtual void visit(QTextBlock &block) const = 0; static void visitSelection(KoTextEditor *editor, const BlockFormatVisitor &visitor, const KUndo2MagicString &title = kundo2_i18n("Format"), bool resetProperties = false, bool registerChange = true) { int start = qMin(editor->position(), editor->anchor()); int end = qMax(editor->position(), editor->anchor()); QTextBlock block = editor->block(); if (block.position() > start) block = block.document()->findBlock(start); // now loop over all blocks that the selection contains and alter the text fragments where applicable. while (block.isValid() && block.position() <= end) { QTextBlockFormat prevFormat = block.blockFormat(); if (resetProperties) { if (KoTextDocument(editor->document()).styleManager()) { KoParagraphStyle *old = KoTextDocument(editor->document()).styleManager()->paragraphStyle(block.blockFormat().intProperty(KoParagraphStyle::StyleId)); if (old) old->unapplyStyle(block); } } visitor.visit(block); QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); if (registerChange) editor->registerTrackedChange(cursor, KoGenChange::FormatChange, title, format, prevFormat, true); block = block.next(); } } }; class CharFormatVisitor { public: CharFormatVisitor() {} virtual ~CharFormatVisitor() {} virtual void visit(QTextCharFormat &format) const = 0; static void visitSelection(KoTextEditor *editor, const CharFormatVisitor &visitor, const KUndo2MagicString &title = kundo2_i18n("Format"), bool registerChange = true) { int start = qMin(editor->position(), editor->anchor()); int end = qMax(editor->position(), editor->anchor()); if (start == end) { // just set a new one. QTextCharFormat format = editor->charFormat(); visitor.visit(format); if (registerChange && KoTextDocument(editor->document()).changeTracker() && KoTextDocument(editor->document()).changeTracker()->recordChanges()) { QTextCharFormat prevFormat(editor->charFormat()); int changeId = KoTextDocument(editor->document()).changeTracker()->getFormatChangeId(title, format, prevFormat, editor->charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt()); format.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } editor->cursor()->setCharFormat(format); return; } QTextBlock block = editor->block(); if (block.position() > start) block = block.document()->findBlock(start); QList cursors; - QList formats; + QVector formats; // now loop over all blocks that the selection contains and alter the text fragments where applicable. while (block.isValid() && block.position() < end) { QTextBlock::iterator iter = block.begin(); while (! iter.atEnd()) { QTextFragment fragment = iter.fragment(); if (fragment.position() > end) break; if (fragment.position() + fragment.length() <= start) { ++iter; continue; } QTextCursor cursor(block); cursor.setPosition(fragment.position() + 1); QTextCharFormat format = cursor.charFormat(); // this gets the format one char after the postion. visitor.visit(format); if (registerChange && KoTextDocument(editor->document()).changeTracker() && KoTextDocument(editor->document()).changeTracker()->recordChanges()) { QTextCharFormat prevFormat(cursor.charFormat()); int changeId = KoTextDocument(editor->document()).changeTracker()->getFormatChangeId(title, format, prevFormat, cursor.charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt()); format.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } cursor.setPosition(qMax(start, fragment.position())); int to = qMin(end, fragment.position() + fragment.length()); cursor.setPosition(to, QTextCursor::KeepAnchor); cursors.append(cursor); formats.append(format); QTextCharFormat prevFormat(cursor.charFormat()); if (registerChange) editor->registerTrackedChange(cursor,KoGenChange::FormatChange,title, format, prevFormat, false); //this will lead to every fragment having a different change untill the change merging in registerTrackedChange checks also for formatChange or not? ++iter; } block = block.next(); } - QList::Iterator iter = formats.begin(); + QVector::Iterator iter = formats.begin(); foreach(QTextCursor cursor, cursors) { cursor.setCharFormat(*iter); ++iter; } } }; #endif //KOTEXTEDITOR_P_H diff --git a/libs/text/KoTextReference.h b/libs/text/KoTextReference.h index 39ea4a4a2b5..fde387e3ba9 100644 --- a/libs/text/KoTextReference.h +++ b/libs/text/KoTextReference.h @@ -1,58 +1,59 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * 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. */ #ifndef KOTEXTREFERENCE_H #define KOTEXTREFERENCE_H #include "KoVariable.h" class KoTextLocator; /** * This variable displays information about a text reference. * A user can insert characters that are called locators. And are represented by a KoTextLocator * the user can then insert (several) KoTextReference variables that will update the textual description * of the locator whenever text is re-layouted. * This effectively means that the reference will print the page number (for example) of where the * locator is and keep it updated automatically. */ class KoTextReference : public KoVariable { +Q_OBJECT public: /** * Constructor; please don't use directly as the KoInlineTextObjectManager will supply an action * to create one. * @param indexId the index of the inline object that is the locator. See KoInlineObject::id() */ explicit KoTextReference(int indexId); ~KoTextReference(); virtual void variableMoved(const QTextDocument *document, int posInDocument); virtual void setup(); virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context); virtual void saveOdf(KoShapeSavingContext &context); private: KoTextLocator *locator(); int m_indexId; // TODO store a config of what we actually want to show. The hardcoded pagenumber is not enough. // we want 'section' / chapter name/number and maybe word. All in a nice formatted text. // see also the ODF spec. }; #endif diff --git a/libs/text/KoTextTableTemplate.h b/libs/text/KoTextTableTemplate.h index bab46972109..661e0283312 100644 --- a/libs/text/KoTextTableTemplate.h +++ b/libs/text/KoTextTableTemplate.h @@ -1,118 +1,119 @@ /* This file is part of the KDE project * Copyright (C) 2012 Gopalakrishna Bhat A * * 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. */ #ifndef KOTEXTTABLETEMPLATE_H #define KOTEXTTABLETEMPLATE_H #include "kotext_export.h" #include #include class KoXmlWriter; class KoShapeLoadingContext; class KoTextSharedSavingData; class KOTEXT_EXPORT KoTextTableTemplate : public QObject { +Q_OBJECT public: enum Property { StyleId = 0, BackGround, Body, EvenColumns, EvenRows, FirstColumn, FirstRow, LastColumn, LastRow, OddColumns, OddRows }; int background() const; void setBackground(int styleId); int body() const; void setBody(int styleId); int evenColumns() const; void setEvenColumns(int styleId); int evenRows() const; void setEvenRows(int styleId); int firstColumn() const; void setFirstColumn(int styleId); int firstRow() const; void setFirstRow(int styleId); int lastColumn() const; void setLastColumn(int styleId); int lastRow() const; void setLastRow(int styleId); int oddColumns() const; void setOddColumns(int styleId); int oddRows() const; void setOddRows(int styleId); /// Constructor explicit KoTextTableTemplate(QObject *parent = 0); /// Destructor ~KoTextTableTemplate(); /// return the name of the style. QString name() const; /// set a user-visible name on the style. void setName(const QString &name); /// each style has a unique ID (non persistent) given out by the styleManager int styleId() const; /// each style has a unique ID (non persistent) given out by the styleManager void setStyleId(int id); /** * Load the template style from the element * * @param context the odf loading context * @param element the element containing the */ void loadOdf(const KoXmlElement *element, KoShapeLoadingContext &context); /** * save the table-template element */ void saveOdf(KoXmlWriter *writer, KoTextSharedSavingData *savingData) const; private: class Private; Private * const d; }; #endif //KOTEXTTABLETEMPLATE_H diff --git a/libs/text/OdfTextTrackStyles.cpp b/libs/text/OdfTextTrackStyles.cpp index 2d79162c350..88135e78d1b 100644 --- a/libs/text/OdfTextTrackStyles.cpp +++ b/libs/text/OdfTextTrackStyles.cpp @@ -1,119 +1,119 @@ /* This file is part of the KDE project * Copyright (C) 2006, 2009 Thomas Zander * Copyright (C) 2012-2014 C. Boemann * * 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. */ #include "OdfTextTrackStyles.h" #include "KoTextDocument.h" #include "KoParagraphStyle.h" #include "KoCharacterStyle.h" #include #include "TextDebug.h" -QMap OdfTextTrackStyles::instances; +QHash OdfTextTrackStyles::instances; OdfTextTrackStyles *OdfTextTrackStyles::instance(KoStyleManager *manager) { if (! instances.contains(manager)) { instances[manager] = new OdfTextTrackStyles(manager); connect(manager,SIGNAL(destroyed(QObject*)),instances[manager], SLOT(styleManagerDied(QObject*))); } return instances[manager]; } void OdfTextTrackStyles::registerDocument(QTextDocument *qDoc) { if (! m_documents.contains(qDoc)) { m_documents.append(qDoc); connect(qDoc,SIGNAL(destroyed(QObject*)), this, SLOT(documentDied(QObject*))); } } void OdfTextTrackStyles::unregisterDocument(QTextDocument *qDoc) { if (m_documents.contains(qDoc)) { m_documents.removeOne(qDoc); } } OdfTextTrackStyles::OdfTextTrackStyles(KoStyleManager *manager) : QObject(manager) , m_styleManager(manager) , m_changeCommand(0) { connect(manager, SIGNAL(editHasBegun()), this, SLOT(beginEdit())); connect(manager, SIGNAL(editHasEnded()), this, SLOT(endEdit())); connect(manager, SIGNAL(styleHasChanged(int,const KoCharacterStyle*,const KoCharacterStyle*)), this, SLOT(recordStyleChange(int,const KoCharacterStyle*,const KoCharacterStyle*))); connect(manager, SIGNAL(styleHasChanged(int,const KoParagraphStyle*,const KoParagraphStyle*)), this, SLOT(recordStyleChange(int,const KoParagraphStyle*,const KoParagraphStyle*))); } OdfTextTrackStyles::~OdfTextTrackStyles() { } void OdfTextTrackStyles::beginEdit() { Q_ASSERT(m_changeCommand == 0); m_changeCommand = new ChangeStylesMacroCommand(m_documents, m_styleManager.data()); } void OdfTextTrackStyles::endEdit() { if (m_documents.length() > 0) { KUndo2Stack *undoStack= KoTextDocument(m_documents.first()).undoStack(); if (undoStack) { undoStack->push(m_changeCommand); } } else delete m_changeCommand; m_changeCommand = 0; } void OdfTextTrackStyles::recordStyleChange(int id, const KoParagraphStyle *origStyle, const KoParagraphStyle *newStyle) { m_changeCommand->changedStyle(id); if (origStyle != newStyle) { m_changeCommand->origStyle(origStyle->clone()); m_changeCommand->changedStyle(newStyle->clone()); } } void OdfTextTrackStyles::recordStyleChange(int id, const KoCharacterStyle *origStyle, const KoCharacterStyle *newStyle) { m_changeCommand->changedStyle(id); if (origStyle != newStyle) { m_changeCommand->origStyle(origStyle->clone()); m_changeCommand->changedStyle(newStyle->clone()); } } void OdfTextTrackStyles::styleManagerDied(QObject *manager) { OdfTextTrackStyles::instances.remove(manager); } void OdfTextTrackStyles::documentDied(QObject *document) { unregisterDocument(static_cast(document)); } diff --git a/libs/text/OdfTextTrackStyles.h b/libs/text/OdfTextTrackStyles.h index da76f75e2b2..052b5ee4e23 100644 --- a/libs/text/OdfTextTrackStyles.h +++ b/libs/text/OdfTextTrackStyles.h @@ -1,80 +1,80 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2012 C. Boemann * * 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. */ #ifndef CHANGEFOLLOWER_H #define CHANGEFOLLOWER_H #include "commands/ChangeStylesMacroCommand.h" #include #include #include #include #include /** * OdfTextTrackStyles is used to update a list of qtextdocument with * any changes made in the style manager. * * It also creates undo commands and adds them to the undo stack * * Style changes affect a lot of qtextdocuments and we store the changes and apply * the changes to every qtextdocument, so every KoTextDocument has to * register their QTextDocument to us. * * We use the QObject principle of children getting deleted when the * parent gets deleted. Thus we die when the KoStyleManager dies. */ class OdfTextTrackStyles : public QObject { Q_OBJECT public: static OdfTextTrackStyles *instance(KoStyleManager *manager); private: - static QMap instances; + static QHash instances; explicit OdfTextTrackStyles(KoStyleManager *manager); /// Destructor, called when the parent is deleted. ~OdfTextTrackStyles(); private Q_SLOTS: void beginEdit(); void endEdit(); void recordStyleChange(int id, const KoParagraphStyle *origStyle, const KoParagraphStyle *newStyle); void recordStyleChange(int id, const KoCharacterStyle *origStyle, const KoCharacterStyle *newStyle); void styleManagerDied(QObject *manager); void documentDied(QObject *manager); public: void registerDocument(QTextDocument *qDoc); void unregisterDocument(QTextDocument *qDoc); private: QList m_documents; QWeakPointer m_styleManager; ChangeStylesMacroCommand *m_changeCommand; }; #endif diff --git a/libs/text/changetracker/KoChangeTracker.cpp b/libs/text/changetracker/KoChangeTracker.cpp index e50f5d434cd..4613cc7b572 100644 --- a/libs/text/changetracker/KoChangeTracker.cpp +++ b/libs/text/changetracker/KoChangeTracker.cpp @@ -1,739 +1,739 @@ /* This file is part of the KDE project * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2011 Boudewijn Rempt * * 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. */ #include "KoChangeTracker.h" //Calligra includes #include "styles/KoCharacterStyle.h" #include "KoChangeTrackerElement.h" #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" // KF5 #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoChangeTracker::Private { public: Private() : changeId(1), recordChanges(false), displayChanges(false), insertionBgColor(101,255,137), deletionBgColor(255,185,185), formatChangeBgColor(195,195,255), changeSaveFormat(UNKNOWN) { } ~Private() { } QMultiHash children; QMultiHash duplicateIds; QHash parents; QHash changes; QHash loadedChanges; QHash changeInformation; QList saveChanges; QList acceptedRejectedChanges; int changeId; bool recordChanges; bool displayChanges; QColor insertionBgColor, deletionBgColor, formatChangeBgColor; QString changeAuthorName; KoChangeTracker::ChangeSaveFormat changeSaveFormat; }; KoChangeTracker::KoChangeTracker(QObject *parent) : QObject(parent), d(new Private()) { d->changeId = 1; } KoChangeTracker::~KoChangeTracker() { delete d; } void KoChangeTracker::setRecordChanges(bool enabled) { d->recordChanges = enabled; } bool KoChangeTracker::recordChanges() const { return d->recordChanges; } void KoChangeTracker::setDisplayChanges(bool enabled) { d->displayChanges = enabled; } bool KoChangeTracker::displayChanges() const { return d->displayChanges; } QString KoChangeTracker::authorName() const { return d->changeAuthorName; } void KoChangeTracker::setAuthorName(const QString &authorName) { d->changeAuthorName = authorName; } KoChangeTracker::ChangeSaveFormat KoChangeTracker::saveFormat() const { return d->changeSaveFormat; } void KoChangeTracker::setSaveFormat(ChangeSaveFormat saveFormat) { d->changeSaveFormat = saveFormat; } int KoChangeTracker::getFormatChangeId(const KUndo2MagicString &title, const QTextFormat &format, const QTextFormat &prevFormat, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::FormatChange); changeElement->setChangeFormat(format); changeElement->setPrevFormat(prevFormat); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getInsertChangeId(const KUndo2MagicString &title, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::InsertChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } int KoChangeTracker::getDeleteChangeId(const KUndo2MagicString &title, const QTextDocumentFragment &selection, int existingChangeId) { if ( existingChangeId ) { d->children.insert(existingChangeId, d->changeId); d->parents.insert(d->changeId, existingChangeId); } KoChangeTrackerElement *changeElement = new KoChangeTrackerElement(title, KoGenChange::DeleteChange); QLocale l; changeElement->setDate(l.toString(QDateTime::currentDateTime()).replace(QLocale().decimalPoint(), QString("."))); changeElement->setCreator(d->changeAuthorName); changeElement->setDeleteData(selection); changeElement->setEnabled(d->recordChanges); d->changes.insert(d->changeId, changeElement); return d->changeId++; } KoChangeTrackerElement* KoChangeTracker::elementById(int id) const { if (isDuplicateChangeId(id)) { id = originalChangeId(id); } return d->changes.value(id); } bool KoChangeTracker::removeById(int id, bool freeMemory) { if (freeMemory) { KoChangeTrackerElement *temp = d->changes.value(id); delete temp; } return d->changes.remove(id); } bool KoChangeTracker::containsInlineChanges(const QTextFormat &format) const { if (format.property(KoCharacterStyle::ChangeTrackerId).toInt()) return true; return false; } int KoChangeTracker::mergeableId(KoGenChange::Type type, const KUndo2MagicString &title, int existingId) const { if (!existingId || !d->changes.value(existingId)) return 0; if (d->changes.value(existingId)->getChangeType() == type && d->changes.value(existingId)->getChangeTitle() == title) { return existingId; } else { if (d->parents.contains(existingId)) { return mergeableId(type, title, d->parents.value(existingId)); } else { return 0; } } } int KoChangeTracker::split(int changeId) { KoChangeTrackerElement *element = new KoChangeTrackerElement(*d->changes.value(changeId)); d->changes.insert(d->changeId, element); return d->changeId++; } bool KoChangeTracker::isParent(int testedParentId, int testedChildId) const { if ((testedParentId == testedChildId) && !d->acceptedRejectedChanges.contains(testedParentId)) return true; else if (d->parents.contains(testedChildId)) return isParent(testedParentId, d->parents.value(testedChildId)); else return false; } void KoChangeTracker::setParent(int child, int parent) { if (!d->children.values(parent).contains(child)) { d->children.insert(parent, child); } if (!d->parents.contains(child)) { d->parents.insert(child, parent); } } int KoChangeTracker::parent(int changeId) const { if (!d->parents.contains(changeId)) return 0; if (d->acceptedRejectedChanges.contains(d->parents.value(changeId))) return parent(d->parents.value(changeId)); return d->parents.value(changeId); } int KoChangeTracker::createDuplicateChangeId(int existingChangeId) { int duplicateChangeId = d->changeId; d->changeId++; d->duplicateIds.insert(existingChangeId, duplicateChangeId); return duplicateChangeId; } bool KoChangeTracker::isDuplicateChangeId(int duplicateChangeId) const { return d->duplicateIds.values().contains(duplicateChangeId); } int KoChangeTracker::originalChangeId(int duplicateChangeId) const { int originalChangeId = 0; QMultiHash::const_iterator i = d->duplicateIds.constBegin(); while (i != d->duplicateIds.constEnd()) { if (duplicateChangeId == i.value()) { originalChangeId = i.key(); break; } ++i; } return originalChangeId; } void KoChangeTracker::acceptRejectChange(int changeId, bool set) { if (set) { if (!d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.append(changeId); } else { if (d->acceptedRejectedChanges.contains(changeId)) d->acceptedRejectedChanges.removeAll(changeId); } d->changes.value(changeId)->setAcceptedRejected(set); } bool KoChangeTracker::saveInlineChange(int changeId, KoGenChange &change) { if (!d->changes.contains(changeId)) return false; change.setType(d->changes.value(changeId)->getChangeType()); change.addChangeMetaData("dc-creator", d->changes.value(changeId)->getCreator()); change.addChangeMetaData("dc-date", d->changes.value(changeId)->getDate()); if (d->changes.value(changeId)->hasExtraMetaData()) change.addChildElement("changeMetaData", d->changes.value(changeId)->getExtraMetaData()); return true; } QMap KoChangeTracker::saveInlineChanges(QMap changeTransTable, KoGenChanges &genChanges) { foreach (int changeId, d->changes.keys()) { // return if the id we find in the changetranstable already has a length. if (changeTransTable.value(changeId).length()) { continue; } if ((elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) && (saveFormat() == KoChangeTracker::ODF_1_2)) { continue; } KoGenChange change; if (saveFormat() == KoChangeTracker::ODF_1_2) { change.setChangeFormat(KoGenChange::ODF_1_2); } else { change.setChangeFormat(KoGenChange::DELTAXML); } saveInlineChange(changeId, change); QString changeName = genChanges.insert(change); changeTransTable.insert(changeId, changeName); } return changeTransTable; } void KoChangeTracker::setFormatChangeInformation(int formatChangeId, KoFormatChangeInformation *formatInformation) { d->changeInformation.insert(formatChangeId, formatInformation); } KoFormatChangeInformation *KoChangeTracker::formatChangeInformation(int formatChangeId) const { return d->changeInformation.value(formatChangeId); } void KoChangeTracker::loadOdfChanges(const KoXmlElement& element) { if (element.namespaceURI() == KoXmlNS::text) { KoXmlElement tag; forEachElement(tag, element) { if (! tag.isNull()) { const QString localName = tag.localName(); if (localName == "changed-region") { KoChangeTrackerElement *changeElement = 0; KoXmlElement region; forEachElement(region, tag) { if (!region.isNull()) { if (region.localName() == "insertion") { changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::InsertChange); } else if (region.localName() == "format-change") { changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::FormatChange); } else if (region.localName() == "deletion") { changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::text,"id")),KoGenChange::DeleteChange); } KoXmlElement metadata = region.namedItemNS(KoXmlNS::office,"change-info").toElement(); if (!metadata.isNull()) { KoXmlElement date = metadata.namedItem("dc:date").toElement(); if (!date.isNull()) { changeElement->setDate(date.text()); } KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); if (!date.isNull()) { changeElement->setCreator(creator.text()); } //TODO load comments /* KoXmlElement extra = metadata.namedItem("dc-").toElement(); if (!date.isNull()) { debugText << "creator: " << creator.text(); changeElement->setCreator(creator.text()); }*/ } changeElement->setEnabled(d->recordChanges); d->changes.insert( d->changeId, changeElement); d->loadedChanges.insert(tag.attributeNS(KoXmlNS::text,"id"), d->changeId++); } } } } } } else { //This is the ODF 1.2 Change Format KoXmlElement tag; forEachElement(tag, element) { if (! tag.isNull()) { const QString localName = tag.localName(); if (localName == "change-transaction") { KoChangeTrackerElement *changeElement = 0; //Set the change element as an insertion element for now //Will be changed to the correct type when actual changes referencing this change-id are encountered changeElement = new KoChangeTrackerElement(kundo2_noi18n(tag.attributeNS(KoXmlNS::delta,"change-id")),KoGenChange::InsertChange); KoXmlElement metadata = tag.namedItemNS(KoXmlNS::delta,"change-info").toElement(); if (!metadata.isNull()) { KoXmlElement date = metadata.namedItem("dc:date").toElement(); if (!date.isNull()) { changeElement->setDate(date.text()); } KoXmlElement creator = metadata.namedItem("dc:creator").toElement(); if (!creator.isNull()) { changeElement->setCreator(creator.text()); } } changeElement->setEnabled(d->recordChanges); d->changes.insert( d->changeId, changeElement); d->loadedChanges.insert(tag.attributeNS(KoXmlNS::delta,"change-id"), d->changeId++); } } } } } int KoChangeTracker::getLoadedChangeId(const QString &odfId) const { return d->loadedChanges.value(odfId); } int KoChangeTracker::getDeletedChanges(QVector& deleteVector) const { int numAppendedItems = 0; - foreach (KoChangeTrackerElement *element, d->changes.values()) { + foreach (KoChangeTrackerElement *element, d->changes) { if(element->getChangeType() == KoGenChange::DeleteChange && !element->acceptedRejected()) { deleteVector << element; numAppendedItems++; } } return numAppendedItems; } QColor KoChangeTracker::getInsertionBgColor() const { return d->insertionBgColor; } QColor KoChangeTracker::getDeletionBgColor() const { return d->deletionBgColor; } QColor KoChangeTracker::getFormatChangeBgColor() const { return d->formatChangeBgColor; } void KoChangeTracker::setInsertionBgColor(const QColor& bgColor) { d->insertionBgColor = bgColor; } void KoChangeTracker::setDeletionBgColor(const QColor& bgColor) { d->deletionBgColor = bgColor; } void KoChangeTracker::setFormatChangeBgColor(const QColor& bgColor) { d->formatChangeBgColor = bgColor; } ////A convenience function to get a ListIdType from a format //static KoListStyle::ListIdType ListId(const QTextListFormat &format) //{ // KoListStyle::ListIdType listId; // if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) { // listId = format.property(KoListStyle::ListId).toUInt(); // } // else { // listId = format.property(KoListStyle::ListId).toULongLong(); // } // return listId; //} QTextDocumentFragment KoChangeTracker::generateDeleteFragment(const QTextCursor &cursor) { QTextCursor editCursor(cursor); QTextDocument *document = cursor.document(); QTextDocument deletedDocument; QTextDocument deleteCursor(&deletedDocument); KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); if (textObjectManager) { for (int i = cursor.anchor();i <= cursor.position(); i++) { if (document->characterAt(i) == QChar::ObjectReplacementCharacter) { editCursor.setPosition(i+1); } } } QTextBlock currentBlock = document->findBlock(cursor.anchor()); QTextBlock startBlock = currentBlock; QTextBlock endBlock = document->findBlock(cursor.position()).next(); currentBlock = document->findBlock(cursor.anchor()); startBlock = currentBlock; endBlock = document->findBlock(cursor.position()).next(); for (;currentBlock != endBlock; currentBlock = currentBlock.next()) { editCursor.setPosition(currentBlock.position()); if (editCursor.currentTable()) { QTextTableFormat tableFormat = editCursor.currentTable()->format(); editCursor.currentTable()->setFormat(tableFormat); } if (currentBlock != startBlock) { QTextBlockFormat blockFormat; editCursor.mergeBlockFormat(blockFormat); } } return cursor.selection(); } bool KoChangeTracker::checkListDeletion(const QTextList &list, const QTextCursor &cursor) { int startOfList = (list.item(0).position() - 1); int endOfList = list.item(list.count() -1).position() + list.item(list.count() -1).length() - 1; if ((cursor.anchor() <= startOfList) && (cursor.position() >= endOfList)) return true; else { /***************************************************************************************************/ /* Qt Quirk Work-Around */ /***************************************************************************************************/ if ((cursor.anchor() == (startOfList + 1)) && (cursor.position() > endOfList)) { return true; /***************************************************************************************************/ } else if((cursor.anchor() <= startOfList) && (list.count() == 1)) { return true; } else { return false; } } } void KoChangeTracker::insertDeleteFragment(QTextCursor &cursor) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { //This condition is for the work-around for a Qt behaviour //Even if a delete ends at the end of a table, the fragment will have an empty block after the table //If such a block is detected then, just ignore it if ((currentBlock.next() == tempDoc.end()) && (currentBlock.text().length() == 0) && (QTextCursor(currentBlock.previous()).currentTable())) { continue; } tempCursor.setPosition(currentBlock.position()); QTextList *textList = tempCursor.currentList(); int outlineLevel = currentBlock.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); KoList *currentList = KoTextDocument(cursor.document()).list(cursor.block()); int docOutlineLevel = cursor.block().blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); if (docOutlineLevel) { //Even though we got a list, it is actually a list for storing headings. So don't consider it currentList = nullptr; } QTextList *previousTextList = currentBlock.previous().isValid() ? QTextCursor(currentBlock.previous()).currentList():nullptr; if (textList && previousTextList && (textList != previousTextList) && (KoList::level(currentBlock) == KoList::level(currentBlock.previous()))) { //Even though we are already in a list, the QTextList* of the current block is differnt from that of the previous block //Also the levels of the list-items ( previous and current ) are the same. //This can happen only when two lists are merged together without any intermediate content. //So we need to create a new list. currentList = nullptr; } if (textList) { if (deletedListItem && currentBlock != tempDoc.begin()) { // Found a deleted list item in the fragment. So insert a new list-item int deletedListItemLevel = KoList::level(currentBlock); if (!(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } else { cursor.mergeBlockFormat(currentBlock.blockFormat()); } if(!currentList) { if (!outlineLevel) { //This happens when a part of a paragraph and a succeeding list-item are deleted together //So go to the next block and insert it in the list there. QTextCursor tmp(cursor); tmp.setPosition(tmp.block().next().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } else { // This is a heading. So find the KoList for heading and add the block there KoList *headingList = KoTextDocument(cursor.document()).headingList(); currentList = headingList; } } currentList->add(cursor.block(), deletedListItemLevel); } } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); QTextTable *insertedTable = cursor.insertTable(numRows, numColumns, deletedTable->format()); for (int i=0; icellAt(i,j).firstCursorPosition().position()); tempCursor.setPosition(deletedTable->cellAt(i,j).lastCursorPosition().position(), QTextCursor::KeepAnchor); insertedTable->cellAt(i,j).setFormat(deletedTable->cellAt(i,j).format().toTableCellFormat()); cursor.setPosition(insertedTable->cellAt(i,j).firstCursorPosition().position()); cursor.insertFragment(tempCursor.selection()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); //Move the cursor outside of table cursor.setPosition(cursor.position() + 1); continue; } else { // This block does not contain a list. So no special work here. if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) { cursor.insertBlock(currentBlock.blockFormat(), currentBlock.charFormat()); } if (QTextCursor(currentBlock.previous()).currentTable()) { cursor.mergeBlockFormat(currentBlock.blockFormat()); } } /********************************************************************************************************************/ /*This section of code is a work-around for a bug in the Qt. This work-around is safe. If and when the bug is fixed */ /*the if condition would never be true and the code would never get executed */ /********************************************************************************************************************/ if ((KoList::level(cursor.block()) != KoList::level(currentBlock)) && currentBlock.text().length()) { if (!currentList) { QTextCursor tmp(cursor); tmp.setPosition(tmp.block().previous().position()); currentList = KoTextDocument(tmp.document()).list(tmp.block()); } currentList->add(cursor.block(), KoList::level(currentBlock)); } /********************************************************************************************************************/ // Finally insert all the contents of the block into the main document. QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { cursor.insertText(currentFragment.text(), currentFragment.charFormat()); } } } } int KoChangeTracker::fragmentLength(const QTextDocumentFragment &fragment) { QTextDocument tempDoc; QTextCursor tempCursor(&tempDoc); tempCursor.insertFragment(fragment); int length = 0; bool deletedListItem = false; for (QTextBlock currentBlock = tempDoc.begin(); currentBlock != tempDoc.end(); currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); if (tempCursor.currentList()) { if (currentBlock != tempDoc.begin() && deletedListItem) length += 1; //For the Block separator } else if (tempCursor.currentTable()) { QTextTable *deletedTable = tempCursor.currentTable(); int numRows = deletedTable->rows(); int numColumns = deletedTable->columns(); for (int i=0; icellAt(i,j).lastCursorPosition().position() - deletedTable->cellAt(i,j).firstCursorPosition().position()); } } tempCursor.setPosition(deletedTable->cellAt(numRows-1,numColumns-1).lastCursorPosition().position()); currentBlock = tempCursor.block(); length += 1; continue; } else { if ((currentBlock != tempDoc.begin()) && !(QTextCursor(currentBlock.previous()).currentTable())) length += 1; //For the Block Separator } QTextBlock::iterator it; for (it = currentBlock.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) length += currentFragment.text().length(); } } return length; } diff --git a/libs/text/commands/DeleteTableColumnCommand.h b/libs/text/commands/DeleteTableColumnCommand.h index 79bdc2a1b31..3e5334eb1c7 100644 --- a/libs/text/commands/DeleteTableColumnCommand.h +++ b/libs/text/commands/DeleteTableColumnCommand.h @@ -1,50 +1,49 @@ /* This file is part of the KDE project * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 C. Boemann * * 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.*/ #ifndef DELETETABLECOLUMNCOMMAND_H #define DELETETABLECOLUMNCOMMAND_H #include #include #include class KoTextEditor; class QTextTable; class DeleteTableColumnCommand : public KUndo2Command { public: DeleteTableColumnCommand(KoTextEditor *te, QTextTable *t, KUndo2Command *parent = 0); virtual void undo(); virtual void redo(); private: bool m_first; KoTextEditor *m_textEditor; QTextTable *m_table; int m_selectionColumn; int m_selectionColumnSpan; - int m_changeId; QVector m_deletedStyles; }; #endif // DELETETABLEROWCOMMAND_H diff --git a/libs/text/opendocument/KoTextLoader.cpp b/libs/text/opendocument/KoTextLoader.cpp index 0fc147cc759..2d3da261670 100644 --- a/libs/text/opendocument/KoTextLoader.cpp +++ b/libs/text/opendocument/KoTextLoader.cpp @@ -1,1657 +1,1655 @@ /* This file is part of the KDE project * Copyright (C) 2001-2006 David Faure * Copyright (C) 2007,2009,2011 Thomas Zander * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2007,2011 Pierre Ducroquet * Copyright (C) 2007-2011 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009-2012 KO GmbH * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 KO GmbH * Copyright (C) 2011 Pavol Korinek * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011-2012 Gopalakrishna Bhat A * Copyright (C) 2012 Inge Wallin * Copyright (C) 2009-2012 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * * 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. */ #include "KoTextLoader.h" #include #include #include #include #include #include #include #include #include #include "KoList.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoTextDebug.h" #include "KoTextDocument.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include #include #include "KoTextInlineRdf.h" #include "KoTableOfContentsGeneratorInfo.h" #include "KoBibliographyInfo.h" #include "KoSection.h" #include "KoSectionEnd.h" #include "KoTextSoftPageBreak.h" #include "KoDocumentRdfBase.h" #include "KoElementReference.h" #include "KoTextTableTemplate.h" #include "styles/KoStyleManager.h" #include "styles/KoParagraphStyle.h" #include "styles/KoCharacterStyle.h" #include "styles/KoListStyle.h" #include "styles/KoListLevelProperties.h" #include "styles/KoTableStyle.h" #include "styles/KoTableColumnStyle.h" #include "styles/KoTableCellStyle.h" #include "styles/KoSectionStyle.h" #include #include #include #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // if defined then debugging is enabled // #define KOOPENDOCUMENTLOADER_DEBUG /// \internal d-pointer class. class Q_DECL_HIDDEN KoTextLoader::Private { public: KoShapeLoadingContext &context; KoTextSharedLoadingData *textSharedData; // store it here so that you don't need to get it all the time from // the KoOdfLoadingContext. bool stylesDotXml; QTextBlockFormat defaultBlockFormat; QTextCharFormat defaultCharFormat; int bodyProgressTotal; int bodyProgressValue; int nextProgressReportMs; QTime progressTime; QVector currentLists; KoListStyle *currentListStyle; int currentListLevel; // Two lists that follow the same style are considered as one for numbering purposes // This hash keeps all the lists that have the same style in one KoList. QHash lists; KoCharacterStyle *endCharStyle; // charstyle from empty span used at end of paragraph KoStyleManager *styleManager; KoShape *shape; int loadSpanLevel; int loadSpanInitialPos; QVector nameSpacesList; QList openingSections; QStack sectionStack; // Used to track the parent of current section QMap xmlIdToListMap; QVector m_previousList; QMap numberedParagraphListId; QStringList rdfIdList; /// level is between 1 and 10 void setCurrentList(KoList *currentList, int level); /// level is between 1 and 10 KoList *previousList(int level); explicit Private(KoShapeLoadingContext &context, KoShape *s) : context(context), textSharedData(0), // stylesDotXml says from where the office:automatic-styles are to be picked from: // the content.xml or the styles.xml (in a multidocument scenario). It does not // decide from where the office:styles are to be picked (always picked from styles.xml). // For our use here, stylesDotXml is always false (see ODF1.1 spec §2.1). stylesDotXml(context.odfLoadingContext().useStylesAutoStyles()), bodyProgressTotal(0), bodyProgressValue(0), nextProgressReportMs(0), currentLists(10), currentListStyle(0), currentListLevel(1), endCharStyle(0), styleManager(0), shape(s), loadSpanLevel(0), loadSpanInitialPos(0) , m_previousList(10) { progressTime.start(); } ~Private() { debugText << "Loading took" << (float)(progressTime.elapsed()) / 1000 << " seconds"; } KoList *list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList); }; KoList *KoTextLoader::Private::list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList) { //TODO: Remove mergeSimilarStyledList parameter by finding a way to put the numbered-paragraphs of same level // to a single QTextList while loading rather than maintaining a hash list if (mergeSimilarStyledList) { if (lists.contains(listStyle)) { return lists[listStyle]; } } KoList *newList = new KoList(document, listStyle); lists[listStyle] = newList; return newList; } void KoTextLoader::Private::setCurrentList(KoList *currentList, int level) { Q_ASSERT(level > 0 && level <= 10); currentLists[level - 1] = currentList; m_previousList[level - 1] = currentList; } KoList *KoTextLoader::Private::previousList(int level) { Q_ASSERT(level > 0 && level <= 10); if (m_previousList.size() < level) { return 0; } return m_previousList.at(level - 1); } inline static bool isspace(ushort ch) { // options are ordered by likelyhood return ch == ' ' || ch== '\n' || ch == '\r' || ch == '\t'; } QString KoTextLoader::normalizeWhitespace(const QString &in, bool leadingSpace) { QString textstring = in; ushort *text = (ushort*)textstring.data(); // this detaches from the string 'in' int r, w = 0; int len = textstring.length(); for (r = 0; r < len; ++r) { const ushort ch = text[r]; // check for space, tab, line feed, carriage return if (isspace(ch)) { // if we were lead by whitespace in some parent or previous sibling element, // we completely collapse this space if (r != 0 || !leadingSpace) text[w++] = ' '; // find the end of the whitespace run while (r < len && isspace(text[r])) ++r; // and then record the next non-whitespace character if (r < len) text[w++] = text[r]; } else { text[w++] = ch; } } // and now trim off the unused part of the string textstring.truncate(w); return textstring; } /////////////KoTextLoader KoTextLoader::KoTextLoader(KoShapeLoadingContext &context, KoShape *shape) : QObject() , d(new Private(context, shape)) { KoSharedLoadingData *sharedData = context.sharedData(KOTEXT_SHARED_LOADING_ID); if (sharedData) { d->textSharedData = dynamic_cast(sharedData); } //debugText << "sharedData" << sharedData << "textSharedData" << d->textSharedData; if (!d->textSharedData) { d->textSharedData = new KoTextSharedLoadingData(); KoDocumentResourceManager *rm = context.documentResourceManager(); KoStyleManager *styleManager = rm->resource(KoText::StyleManager).value(); d->textSharedData->loadOdfStyles(context, styleManager); if (!sharedData) { context.addSharedData(KOTEXT_SHARED_LOADING_ID, d->textSharedData); } else { warnText << "A different type of sharedData was found under the" << KOTEXT_SHARED_LOADING_ID; Q_ASSERT(false); } } if (context.documentRdf()) { d->rdfIdList = qobject_cast(context.documentRdf())->idrefList(); } } KoTextLoader::~KoTextLoader() { delete d; } void KoTextLoader::loadBody(const KoXmlElement &bodyElem, QTextCursor &cursor, LoadBodyMode mode) { const QTextDocument *document = cursor.block().document(); static int rootCallChecker = 0; if (rootCallChecker == 0) { if (document->resource(KoTextDocument::FrameCharFormat, KoTextDocument::FrameCharFormatUrl).isValid()) { d->defaultBlockFormat = KoTextDocument(document).frameBlockFormat(); d->defaultCharFormat = KoTextDocument(document).frameCharFormat(); } else { // This is the first call of loadBody on the document. // Store the default block and char formats // Will be used whenever a new block is inserted d->defaultCharFormat = cursor.charFormat(); KoTextDocument(document).setFrameCharFormat(cursor.blockCharFormat()); d->defaultBlockFormat = cursor.blockFormat(); KoTextDocument(document).setFrameBlockFormat(cursor.blockFormat()); } } rootCallChecker++; cursor.beginEditBlock(); // If we are pasting text, we should handle sections correctly // we are saving which sections end in current block // and put their ends after the inserted text. QList oldSectionEndings; if (mode == PasteMode) { QTextBlockFormat fmt = cursor.blockFormat(); oldSectionEndings = KoSectionUtils::sectionEndings(fmt); fmt.clearProperty(KoParagraphStyle::SectionEndings); cursor.setBlockFormat(fmt); } if (!d->openingSections.isEmpty()) { QTextBlockFormat format = cursor.block().blockFormat(); d->openingSections << KoSectionUtils::sectionStartings(format); // if we had some already we need to append the new ones KoSectionUtils::setSectionStartings(format, d->openingSections); cursor.setBlockFormat(format); d->openingSections.clear(); } KoOdfLineNumberingConfiguration *lineNumberingConfiguration = new KoOdfLineNumberingConfiguration(d->context.odfLoadingContext() .stylesReader() .lineNumberingConfiguration()); KoTextDocument(document).setLineNumberingConfiguration(lineNumberingConfiguration); KoOdfBibliographyConfiguration *bibConfiguration = new KoOdfBibliographyConfiguration(d->context.odfLoadingContext() .stylesReader() .globalBibliographyConfiguration()); KoTextDocument(document).styleManager()->setBibliographyConfiguration(bibConfiguration); d->styleManager = KoTextDocument(document).styleManager(); #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif bool usedParagraph = false; // set to true if we found a tag that used the paragraph, indicating that the next round needs to start a new one. if (bodyElem.namespaceURI() == KoXmlNS::table && bodyElem.localName() == "table") { loadTable(bodyElem, cursor); } else { startBody(KoXml::childNodesCount(bodyElem)); KoXmlElement tag; for (KoXmlNode _node = bodyElem.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { if (!(tag = _node.toElement()).isNull()) { const QString localName = tag.localName(); if (tag.namespaceURI() == KoXmlNS::text) { if ((usedParagraph) && (tag.localName() != "table")) cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat); usedParagraph = true; if (localName == "p") { // text paragraph loadParagraph(tag, cursor); } else if (localName == "h") { // heading loadHeading(tag, cursor); } else if (localName == "unordered-list" || localName == "ordered-list" // OOo-1.1 || localName == "list" || localName == "numbered-paragraph") { // OASIS loadList(tag, cursor); } else if (localName == "section") { loadSection(tag, cursor); } else if (localName == "table-of-content") { loadTableOfContents(tag, cursor); } else if (localName == "bibliography") { loadBibliography(tag, cursor); } else { KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(tag, d->context); if (obj) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { KoVariableManager *varManager = textObjectManager->variableManager(); if (varManager) { textObjectManager->insertInlineObject(cursor, obj); } } } else { usedParagraph = false; warnText << "unhandled text:" << localName; } } } else if (tag.namespaceURI() == KoXmlNS::draw || tag.namespaceURI() == KoXmlNS::dr3d) { loadShape(tag, cursor); } else if (tag.namespaceURI() == KoXmlNS::table) { if (localName == "table") { loadTable(tag, cursor); usedParagraph = false; } else { warnText << "KoTextLoader::loadBody unhandled table::" << localName; } } processBody(); } } endBody(); } rootCallChecker--; // Here we put old endings after text insertion. if (mode == PasteMode) { QTextBlockFormat fmt = cursor.blockFormat(); oldSectionEndings = KoSectionUtils::sectionEndings(fmt); KoSectionUtils::setSectionEndings(fmt, oldSectionEndings); cursor.setBlockFormat(fmt); } cursor.endEditBlock(); KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); Q_UNUSED(textRangeManager); //debugText << "text ranges::"; //foreach(KoTextRange *range, textRangeManager->textRanges()) { //debugText << range->id(); //} if (!rootCallChecker) { // Allow to move end bounds of sections with inserting text KoTextDocument(cursor.block().document()).sectionModel()->allowMovingEndBound(); } } void KoTextLoader::loadParagraph(const KoXmlElement &element, QTextCursor &cursor) { // TODO use the default style name a default value? const QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml); Q_ASSERT(d->styleManager); if (!paragraphStyle) { // Either the paragraph has no style or the style-name could not be found. // Fix up the paragraphStyle to be our default paragraph style in either case. if (!styleName.isEmpty()) warnText << "paragraph style " << styleName << "not found - using default style"; paragraphStyle = d->styleManager->defaultParagraphStyle(); } QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (paragraphStyle && (cursor.position() == cursor.block().position())) { QTextBlock block = cursor.block(); // Apply list style when loading a list but we don't have a list style paragraphStyle->applyStyle(block, d->currentLists[d->currentListLevel - 1] && !d->currentListStyle); // Clear the outline level property. If a default-outline-level was set, it should not // be applied when loading a document, only on user action. block.blockFormat().clearProperty(KoParagraphStyle::OutlineLevel); } // Some paragraph have id's defined which we need to store so that we can eg // attach text animations to this specific paragraph later on KoElementReference id; id.loadOdf(element); if (id.isValid() && d->shape) { QTextBlock block = cursor.block(); KoTextBlockData data(block); // this sets the user data, so don't remove d->context.addShapeSubItemId(d->shape, QVariant::fromValue(block.userData()), id.toString()); } // attach Rdf to cursor.block() // remember inline Rdf metadata -- if the xml-id is actually // about rdf. if (element.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { QTextBlock block = cursor.block(); KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)block.document(), block); if (inlineRdf->loadOdf(element)) { KoTextInlineRdf::attach(inlineRdf, cursor); } else { delete inlineRdf; inlineRdf = 0; } } #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()) << d->currentLists[d->currentListLevel - 1] << d->currentListStyle; #endif bool stripLeadingSpace = true; loadSpan(element, cursor, &stripLeadingSpace); QTextBlock block = cursor.block(); QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (d->endCharStyle) { QTextBlockFormat blockFormat = block.blockFormat(); blockFormat.setProperty(KoParagraphStyle::EndCharStyle, QVariant::fromValue< QSharedPointer >(QSharedPointer(d->endCharStyle->clone()))); cursor.setBlockFormat(blockFormat); } } d->endCharStyle = 0; cursor.setCharFormat(cf); // restore the cursor char format } void KoTextLoader::loadHeading(const KoXmlElement &element, QTextCursor &cursor) { Q_ASSERT(d->styleManager); int level = qMax(-1, element.attributeNS(KoXmlNS::text, "outline-level", "-1").toInt()); // This will fallback to the default-outline-level applied by KoParagraphStyle QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); QTextBlock block = cursor.block(); // Set the paragraph-style on the block KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml); if (!paragraphStyle) { paragraphStyle = d->styleManager->defaultParagraphStyle(); } if (paragraphStyle) { // Apply list style when loading a list but we don't have a list style paragraphStyle->applyStyle(block, (d->currentListLevel > 1) && d->currentLists[d->currentListLevel - 2] && !d->currentListStyle); } QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format bool stripLeadingSpace = true; loadSpan(element, cursor, &stripLeadingSpace); cursor.setCharFormat(cf); // restore the cursor char format if ((block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) && (level == -1)) { level = block.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); } else { if (level == -1) level = 1; QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::OutlineLevel, level); cursor.mergeBlockFormat(blockFormat); } if (element.hasAttributeNS(KoXmlNS::text, "is-list-header")) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::IsListHeader, element.attributeNS(KoXmlNS::text, "is-list-header") == "true"); cursor.mergeBlockFormat(blockFormat); } //we are defining our default behaviour here //Case 1: If text:outline-style is specified then we use the outline style to determine the numbering style //Case 2: If text:outline-style is not specified then if the element is inside a then it is numbered // otherwise it is not KoListStyle *outlineStyle = d->styleManager->outlineStyle(); if (!outlineStyle) { outlineStyle = d->styleManager->defaultOutlineStyle()->clone(); d->styleManager->setOutlineStyle(outlineStyle); } //if outline style is not specified and this is not inside a list then we do not number it if (outlineStyle->styleId() == d->styleManager->defaultOutlineStyle()->styleId()) { if (d->currentListLevel <= 1) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::UnnumberedListItem, true); cursor.mergeBlockFormat(blockFormat); } else { //inside a list then take the numbering from the list style int level = d->currentListLevel - 1; KoListLevelProperties llp; if (!d->currentListStyle->hasLevelProperties(level)) { // Look if one of the lower levels are defined to we can copy over that level. for(int i = level - 1; i >= 0; --i) { if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) { llp = d->currentLists[level - 1]->style()->levelProperties(i); break; } } } else { llp = d->currentListStyle->levelProperties(level); } llp.setLevel(level); outlineStyle->setLevelProperties(llp); } } KoList *list = KoTextDocument(block.document()).headingList(); if (!list) { list = d->list(block.document(), outlineStyle, false); KoTextDocument(block.document()).setHeadingList(list); } list->setStyle(outlineStyle); list->add(block, level); // attach Rdf to cursor.block() // remember inline Rdf metadata KoElementReference id; id.loadOdf(element); if (element.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { QTextBlock block = cursor.block(); KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)block.document(), block); if (inlineRdf->loadOdf(element)) { KoTextInlineRdf::attach(inlineRdf, cursor); } else { delete inlineRdf; inlineRdf = 0; } } #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif } void KoTextLoader::loadList(const KoXmlElement &element, QTextCursor &cursor) { const bool numberedParagraph = element.localName() == "numbered-paragraph"; QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); KoListStyle *listStyle = d->textSharedData->listStyle(styleName, d->stylesDotXml); KoList *continuedList = 0; int level; if (d->currentLists[d->currentListLevel - 1] || d->currentListLevel == 1) { d->currentLists[d->currentListLevel - 1] = 0; } else { d->currentLists[d->currentListLevel - 1] = d->currentLists[d->currentListLevel - 2]; } if (element.hasAttributeNS(KoXmlNS::text, "continue-list")) { if (d->xmlIdToListMap.contains(element.attributeNS(KoXmlNS::text, "continue-list", QString()))) { continuedList = d->xmlIdToListMap.value(element.attributeNS(KoXmlNS::text, "continue-list", QString())); } } else { //the ODF spec says that continue-numbering is considered only if continue-list is not specified if (element.hasAttributeNS(KoXmlNS::text, "continue-numbering")) { const QString continueNumbering = element.attributeNS(KoXmlNS::text, "continue-numbering", QString()); if (continueNumbering == "true") { //since ODF spec says "and the numbering style of the preceding list is the same as the current list" KoList *prevList = d->previousList(d->currentListLevel); if (prevList && listStyle && prevList->style()->hasLevelProperties(d->currentListLevel) && listStyle->hasLevelProperties(d->currentListLevel) && (prevList->style()->levelProperties(d->currentListLevel).labelType() == listStyle->levelProperties(d->currentListLevel).labelType())) { continuedList = prevList; } } } } // TODO: get level from the style, if it has a style:list-level attribute (new in ODF-1.2) if (numberedParagraph) { if (element.hasAttributeNS(KoXmlNS::text, "list-id")) { QString listId = element.attributeNS(KoXmlNS::text, "list-id"); if (d->numberedParagraphListId.contains(listId)) { d->currentLists.fill(d->numberedParagraphListId.value(listId)); } else { KoList *currentList = d->list(cursor.block().document(), listStyle, false); d->currentLists.fill(currentList); d->numberedParagraphListId.insert(listId, currentList); } } else { d->currentLists.fill(d->list(cursor.block().document(), listStyle, true)); } level = element.attributeNS(KoXmlNS::text, "level", "1").toInt(); d->currentListStyle = listStyle; } else { if (!listStyle) listStyle = d->currentListStyle; level = d->currentListLevel++; KoList *currentList = d->currentLists[d->currentListLevel - 2]; if (!currentList) { currentList = d->list(cursor.block().document(), listStyle, false); currentList->setListContinuedFrom(continuedList); d->currentLists[d->currentListLevel - 2] = currentList; } d->currentListStyle = listStyle; } if (element.hasAttributeNS(KoXmlNS::xml, "id")) { d->xmlIdToListMap.insert(element.attributeNS(KoXmlNS::xml, "id", QString()), d->currentLists[d->currentListLevel - 2]); } if (level < 0 || level > 10) { // should not happen but if it does then we should not crash/assert warnText << "Out of bounds list-level=" << level; level = qBound(0, level, 10); } if (! numberedParagraph) { d->setCurrentList(d->currentLists[d->currentListLevel - 2], level); } #ifdef KOOPENDOCUMENTLOADER_DEBUG if (d->currentListStyle) debugText << "styleName =" << styleName << "listStyle =" << d->currentListStyle->name() << "level =" << level << "hasLevelProperties =" << d->currentListStyle->hasLevelProperties(level) //<<" style="< childElementsList; - QString generatedXmlString; KoXmlDocument doc; QXmlStreamReader reader; for (KoXmlNode _node = element.firstChild(); !_node.isNull(); _node = _node.nextSibling()) { if (!(e = _node.toElement()).isNull()) { childElementsList.append(e); } } // Iterate over list items and add them to the textlist bool firstTime = true; foreach (e, childElementsList) { if (e.localName() != "removed-content") { if (!firstTime && !numberedParagraph) cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat); firstTime = false; loadListItem(e, cursor, level); } } if (numberedParagraph || --d->currentListLevel == 1) { d->currentListStyle = 0; d->currentLists.fill(0); } } void KoTextLoader::loadListItem(const KoXmlElement &e, QTextCursor &cursor, int level) { bool numberedParagraph = e.parentNode().toElement().localName() == "numbered-paragraph"; if (e.isNull() || e.namespaceURI() != KoXmlNS::text) return; const bool listHeader = e.tagName() == "list-header"; if (!numberedParagraph && e.tagName() != "list-item" && !listHeader) return; QTextBlock current = cursor.block(); QTextBlockFormat blockFormat; if (numberedParagraph) { if (e.localName() == "p") { loadParagraph(e, cursor); } else if (e.localName() == "h") { loadHeading(e, cursor); } blockFormat.setProperty(KoParagraphStyle::ListLevel, level); } else { loadBody(e, cursor); } if (!cursor.blockFormat().boolProperty(KoParagraphStyle::ForceDisablingList)) { if (!current.textList()) { if (!d->currentLists[level - 1]->style()->hasLevelProperties(level)) { KoListLevelProperties llp; // Look if one of the lower levels are defined to we can copy over that level. for(int i = level - 1; i >= 0; --i) { if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) { llp = d->currentLists[level - 1]->style()->levelProperties(i); break; } } llp.setLevel(level); // TODO make the 10 configurable llp.setIndent(level * 10.0); d->currentLists[level - 1]->style()->setLevelProperties(llp); } d->currentLists[level - 1]->add(current, level); } if (listHeader) blockFormat.setProperty(KoParagraphStyle::IsListHeader, true); if (e.hasAttributeNS(KoXmlNS::text, "start-value")) { int startValue = e.attributeNS(KoXmlNS::text, "start-value", QString()).toInt(); blockFormat.setProperty(KoParagraphStyle::ListStartValue, startValue); } // mark intermediate paragraphs as unnumbered items QTextCursor c(current); c.mergeBlockFormat(blockFormat); while (c.block() != cursor.block()) { c.movePosition(QTextCursor::NextBlock); if (c.block().textList()) // a sublist break; blockFormat = c.blockFormat(); blockFormat.setProperty(listHeader ? KoParagraphStyle::IsListHeader : KoParagraphStyle::UnnumberedListItem, true); c.setBlockFormat(blockFormat); d->currentLists[level - 1]->add(c.block(), level); } } debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); } void KoTextLoader::loadSection(const KoXmlElement §ionElem, QTextCursor &cursor) { KoSection *parent = d->sectionStack.empty() ? 0 : d->sectionStack.top(); KoSection *section = d->context.sectionModel()->createSection(cursor, parent); if (!section->loadOdf(sectionElem, d->textSharedData, d->stylesDotXml)) { delete section; warnText << "Could not load section"; return; } d->sectionStack << section; d->openingSections << section; loadBody(sectionElem, cursor); // Close the section on the last block of text we have loaded just now. QTextBlockFormat format = cursor.block().blockFormat(); KoSectionUtils::setSectionEndings(format, KoSectionUtils::sectionEndings(format) << d->context.sectionModel()->createSectionEnd(section)); d->sectionStack.pop(); cursor.setBlockFormat(format); section->setKeepEndBound(true); // This bound should stop moving with new text } void KoTextLoader::loadNote(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { QString className = noteElem.attributeNS(KoXmlNS::text, "note-class"); KoInlineNote *note = 0; int position = cursor.position(); // need to store this as the following might move is if (className == "footnote") { note = new KoInlineNote(KoInlineNote::Footnote); note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame()); } else { note = new KoInlineNote(KoInlineNote::Endnote); note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame()); } if (note->loadOdf(noteElem, d->context)) { cursor.setPosition(position); // restore the position before inserting the note textObjectManager->insertInlineObject(cursor, note); } else { cursor.setPosition(position); // restore the position delete note; } } } void KoTextLoader::loadCite(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { //Now creating citation with default type KoInlineCite::Citation. KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation); // the manager is needed during loading so set it now cite->setManager(textObjectManager); if (cite->loadOdf(noteElem, d->context)) { textObjectManager->insertInlineObject(cursor, cite); } else { delete cite; } } } void KoTextLoader::loadText(const QString &fulltext, QTextCursor &cursor, bool *stripLeadingSpace, bool isLastNode) { QString text = normalizeWhitespace(fulltext, *stripLeadingSpace); #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << " text=" << text << text.length() << *stripLeadingSpace; #endif if (!text.isEmpty()) { // if present text ends with a space, // we can remove the leading space in the next text *stripLeadingSpace = text[text.length() - 1].isSpace(); cursor.insertText(text); if (d->loadSpanLevel == 1 && isLastNode && cursor.position() > d->loadSpanInitialPos) { QTextCursor tempCursor(cursor); tempCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1); // select last char loaded if (tempCursor.selectedText() == " " && *stripLeadingSpace) { // if it's a collapsed blankspace tempCursor.removeSelectedText(); // remove it } } } } void KoTextLoader::loadSpan(const KoXmlElement &element, QTextCursor &cursor, bool *stripLeadingSpace) { #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif Q_ASSERT(stripLeadingSpace); if (d->loadSpanLevel++ == 0) d->loadSpanInitialPos = cursor.position(); for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { KoXmlElement ts = node.toElement(); const QString localName(ts.localName()); const bool isTextNS = ts.namespaceURI() == KoXmlNS::text; const bool isDrawNS = ts.namespaceURI() == KoXmlNS::draw; const bool isDr3dNS = ts.namespaceURI() == KoXmlNS::dr3d; const bool isOfficeNS = ts.namespaceURI() == KoXmlNS::office; //if (isOfficeNS) debugText << "office:"<endCharStyle = 0; } if (node.isText()) { bool isLastNode = node.nextSibling().isNull(); loadText(node.toText().data(), cursor, stripLeadingSpace, isLastNode); } else if (isTextNS && localName == "span") { // text:span #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << " localName=" << localName; #endif QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString()); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml); if (characterStyle) { characterStyle->applyStyle(&cursor); if (ts.firstChild().isNull()) { // empty span so let's save the characterStyle for possible use at end of par d->endCharStyle = characterStyle; } } else if (!styleName.isEmpty()) { warnText << "character style " << styleName << " not found"; } loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } else if (isTextNS && localName == "s") { // text:s int howmany = 1; if (ts.hasAttributeNS(KoXmlNS::text, "c")) { howmany = ts.attributeNS(KoXmlNS::text, "c", QString()).toInt(); } cursor.insertText(QString().fill(32, howmany)); *stripLeadingSpace = false; } else if ( (isTextNS && localName == "note")) { // text:note loadNote(ts, cursor); } else if (isTextNS && localName == "bibliography-mark") { // text:bibliography-mark loadCite(ts,cursor); } else if (isTextNS && localName == "tab") { // text:tab cursor.insertText("\t"); *stripLeadingSpace = false; } else if (isTextNS && localName == "a") { // text:a QString target = ts.attributeNS(KoXmlNS::xlink, "href"); QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString()); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (!styleName.isEmpty()) { KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml); if (characterStyle) { characterStyle->applyStyle(&cursor); } else { warnText << "character style " << styleName << " not found"; } } QTextCharFormat newCharFormat = cursor.charFormat(); newCharFormat.setAnchor(true); newCharFormat.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); newCharFormat.setAnchorHref(target); cursor.setCharFormat(newCharFormat); loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } else if (isTextNS && localName == "line-break") { // text:line-break #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << " Node localName=" << localName; #endif cursor.insertText(QChar(0x2028)); *stripLeadingSpace = false; } else if (isTextNS && localName == "soft-page-break") { // text:soft-page-break KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { textObjectManager->insertInlineObject(cursor, new KoTextSoftPageBreak()); } } else if (isTextNS && localName == "meta") { #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "loading a text:meta"; #endif KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { const QTextDocument *document = cursor.block().document(); KoTextMeta* startmark = new KoTextMeta(document); textObjectManager->insertInlineObject(cursor, startmark); // Add inline Rdf here. KoElementReference id; id.loadOdf(ts); if (ts.hasAttributeNS(KoXmlNS::xhtml, "property") || (id.isValid() && d->rdfIdList.contains(id.toString()))) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)document, startmark); if (inlineRdf->loadOdf(ts)) { startmark->setInlineRdf(inlineRdf); } else { delete inlineRdf; inlineRdf = 0; } } loadSpan(ts, cursor, stripLeadingSpace); // recurse KoTextMeta* endmark = new KoTextMeta(document); textObjectManager->insertInlineObject(cursor, endmark); startmark->setEndBookmark(endmark); } } // text:bookmark, text:bookmark-start and text:bookmark-end else if (isTextNS && (localName == "bookmark" || localName == "bookmark-start" || localName == "bookmark-end")) { KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); if (localName == "bookmark-end") { KoBookmark *bookmark = textRangeManager->bookmarkManager()->bookmark(KoBookmark::createUniqueBookmarkName(textRangeManager->bookmarkManager(), ts.attribute("name"), true)); if (bookmark) { bookmark->setRangeEnd(cursor.position()); } } else { KoBookmark *bookmark = new KoBookmark(cursor); bookmark->setManager(textRangeManager); if (textRangeManager && bookmark->loadOdf(ts, d->context)) { textRangeManager->insert(bookmark); } else { warnText << "Could not load bookmark"; delete bookmark; } } } else if (isTextNS && localName == "bookmark-ref") { QString bookmarkName = ts.attribute("ref-name"); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (!bookmarkName.isEmpty()) { QTextCharFormat linkCf(cf); // and copy it to alter it linkCf.setAnchor(true); linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Bookmark); QStringList anchorName; anchorName << bookmarkName; linkCf.setAnchorHref('#'+ bookmarkName); cursor.setCharFormat(linkCf); } // TODO add support for loading text:reference-format loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } // text:annotation and text:annotation-end else if (isOfficeNS && (localName == "annotation" || localName == "annotation-end")) { debugText << "------> annotation found" << localName; KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); if (localName == "annotation-end") { // If the tag is annotation-end, there should already be a KoAnnotation // with the same name as this one. If so, just find it and set the end // of the range to the current position. KoAnnotation *annotation = textRangeManager->annotationManager()->annotation(KoAnnotation::createUniqueAnnotationName(textRangeManager->annotationManager(), ts.attribute("name"), true)); if (annotation) { annotation->setRangeEnd(cursor.position()); } } else { // if the tag is "annotation" then create a KoAnnotation // and an annotation shape and call loadOdf() in them. KoAnnotation *annotation = new KoAnnotation(cursor); annotation->setManager(textRangeManager); if (textRangeManager && annotation->loadOdf(ts, d->context)) { textRangeManager->insert(annotation); KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(ts, d->context); // Don't show it before it's being laid out. // // Also this hides it in all applications but // those that have an annotation layout manager // (currently: words, author). shape->setVisible(false); d->textSharedData->shapeInserted(shape, element, d->context); annotation->setAnnotationShape(shape); } else { warnText << "Could not load annotation"; delete annotation; } } } else if (isTextNS && localName == "number") { // text:number /* ODF Spec, §4.1.1, Formatted Heading Numbering If a heading has a numbering applied, the text of the formatted number can be included in a element. This text can be used by applications that do not support numbering of headings, but it will be ignored by applications that support numbering. */ } else if (isTextNS && localName == "dde-connection") { // TODO: load actual connection (or at least preserve it) // For now: just load the text for (KoXmlNode n = ts.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isText()) { loadText(n.toText().data(), cursor, stripLeadingSpace, false); } } } else if ((isDrawNS) && localName == "a") { // draw:a loadShapeWithHyperLink(ts, cursor); } else if (isDrawNS || isDr3dNS) { loadShape(ts, cursor); } else { KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(ts, d->context); KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (obj && textObjectManager) { KoVariableManager *varManager = textObjectManager->variableManager(); if (varManager) { textObjectManager->insertInlineObject(cursor, obj); // we need to update whitespace stripping here so we don't remove to many whitespaces. // this is simplified as it assumes the first child it the text item but that should be the case // most of the time with variables so it should be fine. KoXmlNode child = ts.firstChild(); if (child.isText()) { QString text = normalizeWhitespace(child.toText().data(), *stripLeadingSpace); if (!text.isEmpty()) { // if present text ends with a space, // we can remove the leading space in the next text *stripLeadingSpace = text[text.length() - 1].isSpace(); } } } } else { #if 0 //1.6: bool handled = false; // Check if it's a variable KoVariable *var = context.variableCollection().loadOasisField(textDocument(), ts, context); if (var) { textData = "#"; // field placeholder customItem = var; handled = true; } if (!handled) { handled = textDocument()->loadSpanTag(ts, context, this, pos, textData, customItem); if (!handled) { warnText << "Ignoring tag " << ts.tagName(); context.styleStack().restore(); continue; } } #else #ifdef KOOPENDOCUMENTLOADER_DEBUG debugText << "Node '" << localName << "' unhandled"; #endif } #endif } } --d->loadSpanLevel; } void KoTextLoader::loadTable(const KoXmlElement &tableElem, QTextCursor &cursor) { QTextTableFormat tableFormat; QString tableStyleName = tableElem.attributeNS(KoXmlNS::table, "style-name", ""); if (!tableStyleName.isEmpty()) { KoTableStyle *tblStyle = d->textSharedData->tableStyle(tableStyleName, d->stylesDotXml); if (tblStyle) tblStyle->applyStyle(tableFormat); } QString tableTemplateName = tableElem.attributeNS(KoXmlNS::table, "template-name", ""); if (! tableTemplateName.isEmpty()) { if(KoTextTableTemplate *tableTemplate = d->styleManager->tableTemplate(tableTemplateName)) { tableFormat.setProperty(KoTableStyle::TableTemplate, tableTemplate->styleId()); } } if (tableElem.attributeNS(KoXmlNS::table, "use-banding-columns-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseBandingColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-banding-rows-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseBandingRowStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-first-column-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseFirstColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-first-row-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseFirstRowStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-last-column-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseLastColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-last-row-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseLastRowStyles, true); } // Let's try to figure out when to hide the current block QTextBlock currentBlock = cursor.block(); QTextTable *outerTable = cursor.currentTable(); bool hide = cursor.position() == 0; if (outerTable) { QTextTableCell cell = outerTable->cellAt(cursor.position()); if (cursor.position() == cell.firstCursorPosition().position()) { hide = true; } } if (!hide) { // Let's insert an extra block so that will be the one we hide instead cursor.insertBlock(cursor.blockFormat(), cursor.blockCharFormat()); currentBlock = cursor.block(); } if (tableElem.attributeNS(KoXmlNS::table, "protected", "false") == "true") { tableFormat.setProperty(KoTableStyle::TableIsProtected, true); } QTextTable *tbl = cursor.insertTable(1, 1, tableFormat); // 'Hide' the block before the table (possibly the extra block we just inserted) QTextBlockFormat blockFormat; //blockFormat.setFont(currentBlock.blockFormat().font()); QTextCursor tmpCursor(currentBlock); blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true); QVariant masterStyle = tableFormat.property(KoTableStyle::MasterPageName); if (!masterStyle.isNull()) { // if table has a master page style property, copy it to block before table, because // this block is a hidden block so let's make it belong to the same masterpage // so we don't end up with a page break at the wrong place blockFormat.setProperty(KoParagraphStyle::MasterPageName, masterStyle); } tmpCursor.setBlockFormat(blockFormat); KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int rows = 0; int columns = 0; QVector spanStore; //temporary list to store spans until the entire table have been created KoXmlElement tblTag; int headingRowCounter = 0; - QList rowTags; forEachElement(tblTag, tableElem) { if (! tblTag.isNull()) { const QString tblLocalName = tblTag.localName(); if (tblTag.namespaceURI() == KoXmlNS::table) { if (tblLocalName == "table-column") { loadTableColumn(tblTag, tbl, columns); } else if (tblLocalName == "table-columns") { KoXmlElement e; forEachElement(e, tblTag) { if (e.localName() == "table-column") { loadTableColumn(e, tbl, columns); } } } else if (tblLocalName == "table-row") { loadTableRow(tblTag, tbl, spanStore, cursor, rows); } else if (tblLocalName == "table-rows") { KoXmlElement subTag; forEachElement(subTag, tblTag) { if (!subTag.isNull()) { if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) { loadTableRow(subTag, tbl, spanStore, cursor, rows); } } } } else if (tblLocalName == "table-header-rows") { KoXmlElement subTag; forEachElement(subTag, tblTag) { if (!subTag.isNull()) { if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) { headingRowCounter++; loadTableRow(subTag, tbl, spanStore, cursor, rows); } } } } } } } if (headingRowCounter > 0) { QTextTableFormat fmt = tbl->format(); fmt.setProperty(KoTableStyle::NumberHeadingRows, headingRowCounter); tbl->setFormat(fmt); } // Finally create spans foreach(const QRect &span, spanStore) { tbl->mergeCells(span.y(), span.x(), span.height(), span.width()); // for some reason Qt takes row, column } cursor = tbl->lastCursorPosition(); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); } void KoTextLoader::loadTableColumn(const KoXmlElement &tblTag, QTextTable *tbl, int &columns) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int rows = tbl->rows(); int repeatColumn = tblTag.attributeNS(KoXmlNS::table, "number-columns-repeated", "1").toInt(); QString columnStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", ""); if (!columnStyleName.isEmpty()) { KoTableColumnStyle *columnStyle = d->textSharedData->tableColumnStyle(columnStyleName, d->stylesDotXml); if (columnStyle) { for (int c = columns; c < columns + repeatColumn; c++) { tcarManager.setColumnStyle(c, *columnStyle); } } } QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", ""); if (!defaultCellStyleName.isEmpty()) { KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml); for (int c = columns; c < columns + repeatColumn; c++) { tcarManager.setDefaultColumnCellStyle(c, cellStyle); } } columns = columns + repeatColumn; if (rows > 0) tbl->resize(rows, columns); else tbl->resize(1, columns); } void KoTextLoader::loadTableRow(const KoXmlElement &tblTag, QTextTable *tbl, QVector &spanStore, QTextCursor &cursor, int &rows) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int columns = tbl->columns(); QString rowStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", ""); if (!rowStyleName.isEmpty()) { KoTableRowStyle *rowStyle = d->textSharedData->tableRowStyle(rowStyleName, d->stylesDotXml); if (rowStyle) { tcarManager.setRowStyle(rows, *rowStyle); } } QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", ""); if (!defaultCellStyleName.isEmpty()) { KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml); tcarManager.setDefaultRowCellStyle(rows, cellStyle); } rows++; if (columns > 0) tbl->resize(rows, columns); else tbl->resize(rows, 1); // Added a row int currentCell = 0; KoXmlElement rowTag; forEachElement(rowTag, tblTag) { if (!rowTag.isNull()) { const QString rowLocalName = rowTag.localName(); if (rowTag.namespaceURI() == KoXmlNS::table) { if (rowLocalName == "table-cell") { loadTableCell(rowTag, tbl, spanStore, cursor, currentCell); currentCell++; } else if (rowLocalName == "covered-table-cell") { currentCell++; } } } } } void KoTextLoader::loadTableCell(const KoXmlElement &rowTag, QTextTable *tbl, QVector &spanStore, QTextCursor &cursor, int ¤tCell) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); const int currentRow = tbl->rows() - 1; QTextTableCell cell = tbl->cellAt(currentRow, currentCell); // store spans until entire table have been loaded int rowsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-rows-spanned", "1").toInt(); int columnsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-columns-spanned", "1").toInt(); spanStore.append(QRect(currentCell, currentRow, columnsSpanned, rowsSpanned)); if (cell.isValid()) { QString cellStyleName = rowTag.attributeNS(KoXmlNS::table, "style-name", ""); KoTableCellStyle *cellStyle = 0; if (!cellStyleName.isEmpty()) { cellStyle = d->textSharedData->tableCellStyle(cellStyleName, d->stylesDotXml); } else if (tcarManager.defaultRowCellStyle(currentRow)) { cellStyle = tcarManager.defaultRowCellStyle(currentRow); } else if (tcarManager.defaultColumnCellStyle(currentCell)) { cellStyle = tcarManager.defaultColumnCellStyle(currentCell); } if (cellStyle) cellStyle->applyStyle(cell); QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); if (rowTag.attributeNS(KoXmlNS::table, "protected", "false") == "true") { cellFormat.setProperty(KoTableCellStyle::CellIsProtected, true); } cell.setFormat(cellFormat); // handle inline Rdf // rowTag is the current table cell. KoElementReference id; id.loadOdf(rowTag); if (rowTag.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)cursor.block().document(),cell); if (inlineRdf->loadOdf(rowTag)) { QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); cellFormat.setProperty(KoTableCellStyle::InlineRdf,QVariant::fromValue(inlineRdf)); cell.setFormat(cellFormat); } else { delete inlineRdf; inlineRdf = 0; } } cursor = cell.firstCursorPosition(); loadBody(rowTag, cursor); } } void KoTextLoader::loadShapeWithHyperLink(const KoXmlElement &element, QTextCursor& cursor) { // get the hyperlink QString hyperLink = element.attributeNS(KoXmlNS::xlink, "href"); KoShape *shape = 0; //load the shape for hyperlink KoXmlNode node = element.firstChild(); if (!node.isNull()) { KoXmlElement ts = node.toElement(); shape = loadShape(ts, cursor); if (shape) { shape->setHyperLink(hyperLink); } } } KoShape *KoTextLoader::loadShape(const KoXmlElement &element, QTextCursor &cursor) { KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(element, d->context); if (!shape) { debugText << "shape '" << element.localName() << "' unhandled"; return 0; } KoShapeAnchor *anchor = new KoShapeAnchor(shape); anchor->loadOdf(element, d->context); shape->setAnchor(anchor); d->textSharedData->shapeInserted(shape, element, d->context); // page anchored shapes are handled differently if (anchor->anchorType() == KoShapeAnchor::AnchorPage) { // nothing else to do } else if (anchor->anchorType() == KoShapeAnchor::AnchorAsCharacter) { KoAnchorInlineObject *anchorObject = new KoAnchorInlineObject(anchor); KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { textObjectManager->insertInlineObject(cursor, anchorObject); } } else { // KoShapeAnchor::AnchorToCharacter or KoShapeAnchor::AnchorParagraph KoAnchorTextRange *anchorRange = new KoAnchorTextRange(anchor, cursor); KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); anchorRange->setManager(textRangeManager); textRangeManager->insert(anchorRange); } return shape; } void KoTextLoader::loadTableOfContents(const KoXmlElement &element, QTextCursor &cursor) { // make sure that the tag is table-of-content Q_ASSERT(element.tagName() == "table-of-content"); QTextBlockFormat tocFormat; // for "meta-information" about the TOC we use this class KoTableOfContentsGeneratorInfo *info = new KoTableOfContentsGeneratorInfo(); // to store the contents we use an extrafor "meta-information" about the TOC we use this class QTextDocument *tocDocument = new QTextDocument(); KoTextDocument(tocDocument).setStyleManager(d->styleManager); KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager); info->m_name = element.attribute("name"); info->m_styleName = element.attribute("style-name"); KoXmlElement e; forEachElement(e, element) { if (e.isNull() || e.namespaceURI() != KoXmlNS::text) { continue; } if (e.localName() == "table-of-content-source" && e.namespaceURI() == KoXmlNS::text) { info->loadOdf(d->textSharedData, e); // uncomment to see what has been loaded //info.tableOfContentData()->dump(); tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(info) ); tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(tocDocument) ); cursor.insertBlock(tocFormat); // We'll just try to find displayable elements and add them as paragraphs } else if (e.localName() == "index-body") { QTextCursor cursorFrame = tocDocument->rootFrame()->lastCursorPosition(); bool firstTime = true; KoXmlElement p; forEachElement(p, e) { // All elem will be "p" instead of the title, which is particular if (p.isNull() || p.namespaceURI() != KoXmlNS::text) continue; if (!firstTime) { // use empty formats to not inherit from the prev parag QTextBlockFormat bf; QTextCharFormat cf; cursorFrame.insertBlock(bf, cf); } firstTime = false; QTextBlock current = cursorFrame.block(); QTextBlockFormat blockFormat; if (p.localName() == "p") { loadParagraph(p, cursorFrame); } else if (p.localName() == "index-title") { loadBody(p, cursorFrame); } QTextCursor c(current); c.mergeBlockFormat(blockFormat); } }// index-body } } void KoTextLoader::loadBibliography(const KoXmlElement &element, QTextCursor &cursor) { // make sure that the tag is bibliography Q_ASSERT(element.tagName() == "bibliography"); QTextBlockFormat bibFormat; // for "meta-information" about the bibliography we use this class KoBibliographyInfo *info = new KoBibliographyInfo(); QTextDocument *bibDocument = new QTextDocument(); KoTextDocument(bibDocument).setStyleManager(d->styleManager); KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager); info->m_name = element.attribute("name"); info->m_styleName = element.attribute("style-name"); KoXmlElement e; forEachElement(e, element) { if (e.isNull() || e.namespaceURI() != KoXmlNS::text) { continue; } if (e.localName() == "bibliography-source" && e.namespaceURI() == KoXmlNS::text) { info->loadOdf(d->textSharedData, e); bibFormat.setProperty(KoParagraphStyle::BibliographyData, QVariant::fromValue(info)); bibFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(bibDocument)); cursor.insertBlock(bibFormat); // We'll just try to find displayable elements and add them as paragraphs } else if (e.localName() == "index-body") { QTextCursor cursorFrame = bibDocument->rootFrame()->lastCursorPosition(); bool firstTime = true; KoXmlElement p; forEachElement(p, e) { // All elem will be "p" instead of the title, which is particular if (p.isNull() || p.namespaceURI() != KoXmlNS::text) continue; if (!firstTime) { // use empty formats to not inherit from the prev parag QTextBlockFormat bf; QTextCharFormat cf; cursorFrame.insertBlock(bf, cf); } firstTime = false; QTextBlock current = cursorFrame.block(); QTextBlockFormat blockFormat; if (p.localName() == "p") { loadParagraph(p, cursorFrame); } else if (p.localName() == "index-title") { loadBody(p, cursorFrame); } QTextCursor c(current); c.mergeBlockFormat(blockFormat); } }// index-body } } void KoTextLoader::startBody(int total) { d->bodyProgressTotal += total; } void KoTextLoader::processBody() { d->bodyProgressValue++; if (d->progressTime.elapsed() >= d->nextProgressReportMs) { // update based on elapsed time, don't saturate the queue d->nextProgressReportMs = d->progressTime.elapsed() + 333; // report 3 times per second Q_ASSERT(d->bodyProgressTotal > 0); const int percent = d->bodyProgressValue * 100 / d->bodyProgressTotal; emit sigProgress(percent); } } void KoTextLoader::endBody() { } diff --git a/libs/text/opendocument/KoTextWriter_p.cpp b/libs/text/opendocument/KoTextWriter_p.cpp index 6e7a840562d..0b06ad8d875 100644 --- a/libs/text/opendocument/KoTextWriter_p.cpp +++ b/libs/text/opendocument/KoTextWriter_p.cpp @@ -1,1142 +1,1142 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "KoTextWriter_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" #include // A convenience function to get a listId from a list-format static KoListStyle::ListIdType ListId(const QTextListFormat &format) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = format.property(KoListStyle::ListId).toUInt(); else listId = format.property(KoListStyle::ListId).toULongLong(); return listId; } typedef QPair Attribute; KoTextWriter::Private::Private(KoShapeSavingContext &context) : rdfData(0) , sharedData(0) , styleManager(0) , document(0) , writer(0) , context(context) { currentPairedInlineObjectsStack = new QStack(); writer = &context.xmlWriter(); } void KoTextWriter::Private::writeBlocks(QTextDocument *document, int from, int to, QHash &listStyles, QTextTable *currentTable, QTextList *currentList) { pairedInlineObjectsStackStack.push(currentPairedInlineObjectsStack); currentPairedInlineObjectsStack = new QStack(); QTextBlock block = document->findBlock(from); // Here we are going to detect all sections that // are positioned entirely inside selection. // They will stay untouched, and others will be omitted. // So we are using stack to detect them, by going through // the selection and finding open/close pairs. QSet entireWithinSectionNames; QStack sectionNamesStack; QTextCursor cur(document); cur.setPosition(from); while (to == -1 || cur.position() <= to) { if (cur.block().position() >= from) { // Begin of the block is inside selection. foreach (const KoSection *sec, KoSectionUtils::sectionStartings(cur.blockFormat())) { sectionNamesStack.push_back(sec->name()); } } if (to == -1 || cur.block().position() + cur.block().length() - 1 <= to) { // End of the block is inside selection. foreach (const KoSectionEnd *sec, KoSectionUtils::sectionEndings(cur.blockFormat())) { if (!sectionNamesStack.empty() && sectionNamesStack.top() == sec->name()) { sectionNamesStack.pop(); entireWithinSectionNames.insert(sec->name()); } } } if (!KoSectionUtils::getNextBlock(cur)) { break; } } while (block.isValid() && ((to == -1) || (block.position() <= to))) { QTextCursor cursor(block); int frameType = cursor.currentFrame()->format().intProperty(KoText::SubFrameType); if (frameType == KoText::AuxillaryFrameType) { break; // we've reached the "end" (end/footnotes saved by themselves) // note how NoteFrameType passes through here so the notes can // call writeBlocks to save their contents. } QTextBlockFormat format = block.blockFormat(); foreach (const KoSection *section, KoSectionUtils::sectionStartings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(section->name())) { section->saveOdf(context); } } if (cursor.currentTable() && cursor.currentTable() != currentTable) { // Call the code to save the table.... saveTable(cursor.currentTable(), listStyles, from, to); // We skip to the end of the table. block = cursor.currentTable()->lastCursorPosition().block(); block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::HiddenByTable)) { block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::TableOfContentsData)) { saveTableOfContents(document, listStyles, block); block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::BibliographyData)) { saveBibliography(document, listStyles, block); block = block.next(); continue; } if (cursor.currentList() && cursor.currentList() != currentList) { int previousBlockNumber = block.blockNumber(); block = saveList(block, listStyles, 1, currentTable); int blockNumberToProcess = block.blockNumber(); if (blockNumberToProcess != previousBlockNumber) continue; } saveParagraph(block, from, to); foreach (const KoSectionEnd *sectionEnd, KoSectionUtils::sectionEndings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(sectionEnd->name())) { sectionEnd->saveOdf(context); } } block = block.next(); } // while Q_ASSERT(!pairedInlineObjectsStackStack.isEmpty()); delete currentPairedInlineObjectsStack; currentPairedInlineObjectsStack = pairedInlineObjectsStackStack.pop(); } QHash KoTextWriter::Private::saveListStyles(QTextBlock block, int to) { QHash generatedLists; QHash listStyles; for (;block.isValid() && ((to == -1) || (block.position() < to)); block = block.next()) { QTextList *textList = block.textList(); if (!textList) continue; KoListStyle::ListIdType listId = ListId(textList->format()); if (KoList *list = KoTextDocument(document).list(listId)) { if (generatedLists.contains(list)) { if (!listStyles.contains(textList)) listStyles.insert(textList, generatedLists.value(list)); continue; } KoListStyle *listStyle = list->style(); if (listStyle && listStyle->isOulineStyle()) { continue; } bool automatic = listStyle->styleId() == 0; KoGenStyle style(automatic ? KoGenStyle::ListAutoStyle : KoGenStyle::ListStyle); if (automatic && context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); listStyle->saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle->name(), listStyle->isNumberingStyle() ? KoGenStyles::AllowDuplicates : KoGenStyles::DontAddNumberToName); listStyles[textList] = generatedName; generatedLists.insert(list, generatedName); } else { if (listStyles.contains(textList)) continue; KoListLevelProperties llp = KoListLevelProperties::fromTextList(textList); KoGenStyle style(KoGenStyle::ListAutoStyle); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoListStyle listStyle; listStyle.setLevelProperties(llp); if (listStyle.isOulineStyle()) { continue; } listStyle.saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle.name()); listStyles[textList] = generatedName; } } return listStyles; } //---------------------------- PRIVATE ----------------------------------------------------------- void KoTextWriter::Private::openTagRegion(ElementType elementType, TagInformation& tagInformation) { //debugText << "tag:" << tagInformation.name() << openedTagStack.size(); if (tagInformation.name()) { writer->startElement(tagInformation.name(), elementType != ParagraphOrHeader); foreach (const Attribute &attribute, tagInformation.attributes()) { writer->addAttribute(attribute.first.toLocal8Bit(), attribute.second); } } openedTagStack.push(tagInformation.name()); //debugText << "stack" << openedTagStack.size(); } void KoTextWriter::Private::closeTagRegion() { // the tag needs to be closed even if there is no change tracking //debugText << "stack" << openedTagStack.size(); const char *tagName = openedTagStack.pop(); //debugText << "tag:" << tagName << openedTagStack.size(); if (tagName) { writer->endElement(); // close the tag } } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlock &block) { return KoTextWriter::saveParagraphStyle(block, styleManager, context); } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &charFormat) { return KoTextWriter::saveParagraphStyle(blockFormat, charFormat, styleManager, context); } QString KoTextWriter::Private::saveCharacterStyle(const QTextCharFormat &charFormat, const QTextCharFormat &blockCharFormat) { KoCharacterStyle *defaultCharStyle = styleManager->defaultCharacterStyle(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(charFormat.intProperty(KoCharacterStyle::StyleId)); if (!originalCharStyle) originalCharStyle = defaultCharStyle; QString generatedName; QString displayName = originalCharStyle->name(); QString internalName = QString(QUrl::toPercentEncoding(displayName, "", " ")).replace('%', '_'); KoCharacterStyle *autoStyle = originalCharStyle->autoStyle(charFormat, blockCharFormat); if (autoStyle->isEmpty()) { // This is the real, unmodified character style. if (originalCharStyle != defaultCharStyle) { KoGenStyle style(KoGenStyle::TextStyle, "text"); originalCharStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TextAutoStyle, "text", originalCharStyle != defaultCharStyle ? internalName : "" /*parent*/); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); autoStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, "T"); } delete autoStyle; return generatedName; } QString KoTextWriter::Private::saveTableStyle(const QTextTable& table) { KoTableStyle *originalTableStyle = styleManager->tableStyle(table.format().intProperty(KoTableStyle::StyleId)); QString generatedName; QString internalName; if (originalTableStyle) { internalName = QString(QUrl::toPercentEncoding(originalTableStyle->name(), "", " ")).replace('%', '_'); } KoTableStyle tableStyle(table.format()); if ((originalTableStyle) && (*originalTableStyle == tableStyle)) { // This is the real unmodified table style KoGenStyle style(KoGenStyle::TableStyle, "table"); originalTableStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TableAutoStyle, "table", internalName); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); if (originalTableStyle) tableStyle.removeDuplicates(*originalTableStyle); if (!tableStyle.isEmpty()) { tableStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, "Table"); } } return generatedName; } QString KoTextWriter::Private::saveTableColumnStyle(const KoTableColumnStyle& tableColumnStyle, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableColumnStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableRowStyle(const KoTableRowStyle& tableRowStyle, int rowNumber, const QString& tableStyleName) { QString generatedName = tableStyleName + '.' + QString::number(rowNumber + 1); KoGenStyle style(KoGenStyle::TableRowAutoStyle, "table-row"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableRowStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableCellStyle(const QTextTableCellFormat& cellFormat, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableCellAutoStyle, "table-cell"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoTableCellStyle cellStyle(cellFormat); cellStyle.saveOdf(style, context); generatedName = context.mainStyles().insert(style, generatedName); return generatedName; } void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* rdf, TagInformation* tagInfos) { QBuffer rdfXmlData; KoXmlWriter rdfXmlWriter(&rdfXmlData); rdfXmlWriter.startDocument("rdf"); rdfXmlWriter.startElement("rdf"); rdf->saveOdf(context, &rdfXmlWriter); rdfXmlWriter.endElement(); rdfXmlWriter.endDocument(); KoXmlDocument xmlReader; xmlReader.setContent(rdfXmlData.data(), true); KoXmlElement mainElement = xmlReader.documentElement(); foreach (const Attribute &attributeNameNS, mainElement.attributeFullNames()) { - QString attributeName = QString("%1:%2").arg(KoXmlNS::nsURI2NS(attributeNameNS.first)) - .arg(attributeNameNS.second); + QString attributeName = QString("%1:%2").arg(KoXmlNS::nsURI2NS(attributeNameNS.first), + attributeNameNS.second); if (attributeName.startsWith(':')) attributeName.prepend("xml"); tagInfos->addAttribute(attributeName, mainElement.attribute(attributeNameNS.second)); } } /* Note on saving textranges: Start and end tags of textranges can appear on cursor positions in a text block. in front of the first text element, between the elements, or behind the last. A textblock is composed of no, one or many text fragments. If there is no fragment at all, the only possible cursor position is 0 (relative to the begin of the block). Example: ([] marks a block, {} a fragment) Three blocks, first with text fragments {AB} {C}, second empty, last with {DEF}. Possible positions are: [|{A|B}|{C}|] [|] [|{D|E|F}|] Start tags are ideally written in front of the content they are tagging, not behind the previous content. That way tags which are at the very begin of the complete document do not need special handling. End tags are ideally written directly behind the content, and not in front of the next content. That way end tags which are at the end of the complete document do not need special handling. Next there is the case of start tags which are at the final position of a text block: the content they belong to includes the block end/border, so they need to be written at the place of the last position. Then there is the case of end tags at the first position of a text block: the content they belong to includes the block start/border, so they need to be written at the place of the first position. Example: (< marks a start tag, > marks an end tag) [|>{}|{}|<] [|><] [|>{||}|<] */ void KoTextWriter::Private::saveParagraph(const QTextBlock &block, int from, int to) { QTextCursor cursor(block); QTextBlockFormat blockFormat = block.blockFormat(); const int outlineLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); TagInformation blockTagInformation; if (outlineLevel > 0) { blockTagInformation.setTagName("text:h"); blockTagInformation.addAttribute("text:outline-level", outlineLevel); if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader) || blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)) { blockTagInformation.addAttribute("text:is-list-header", "true"); } } else { blockTagInformation.setTagName("text:p"); } openTagRegion(KoTextWriter::Private::ParagraphOrHeader, blockTagInformation); QString styleName = saveParagraphStyle(block); if (!styleName.isEmpty()) writer->addAttribute("text:style-name", styleName); KoElementReference xmlid; xmlid.invalidate(); QTextBlock currentBlock = block; KoTextBlockData blockData(currentBlock); if (blockData.saveXmlID()) { xmlid = context.xmlid(&blockData); xmlid.saveOdf(writer, KoElementReference::TextId); } // Write the fragments and their formats QTextCharFormat blockCharFormat = cursor.blockCharFormat(); QTextCharFormat previousCharFormat; QTextBlock::iterator it; if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(blockCharFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId() << "active xml id" << xmlid.toString(); inlineRdf->saveOdf(context, writer, xmlid); } const KoTextRangeManager *textRangeManager = KoTextDocument(block.document()).textRangeManager(); if (textRangeManager) { // write tags for ranges which end at the first position of the block const QHash endingTextRangesAtStart = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position(), globalFrom, globalTo); foreach (const KoTextRange *range, endingTextRangesAtStart) { range->saveOdf(context, block.position(), KoTextRange::EndTag); } } QString previousFragmentLink; // stores the end position of the last fragment, is position of the block without any fragment at all int lastEndPosition = block.position(); for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); const int fragmentStart = currentFragment.position(); const int fragmentEnd = fragmentStart + currentFragment.length(); if (to != -1 && fragmentStart >= to) break; if (currentFragment.isValid()) { QTextCharFormat charFormat = currentFragment.charFormat(); if ((!previousFragmentLink.isEmpty()) && (charFormat.anchorHref() != previousFragmentLink || !charFormat.isAnchor())) { // Close the current text:a closeTagRegion(); previousFragmentLink.clear(); } if (charFormat.isAnchor() && charFormat.anchorHref() != previousFragmentLink) { // Open a text:a previousFragmentLink = charFormat.anchorHref(); TagInformation linkTagInformation; if (charFormat.intProperty(KoCharacterStyle::AnchorType) == KoCharacterStyle::Bookmark) { linkTagInformation.setTagName("text:bookmark-ref"); QString href = previousFragmentLink.right(previousFragmentLink.size()-1); linkTagInformation.addAttribute("text:ref-name", href); //linkTagInformation.addAttribute("text:ref-format", add the style of the ref here); } else { linkTagInformation.setTagName("text:a"); linkTagInformation.addAttribute("xlink:type", "simple"); linkTagInformation.addAttribute("xlink:href", charFormat.anchorHref()); } if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(charFormat)) { // Write xml:id here for Rdf debugText << "have inline rdf xmlid:" << inlineRdf->xmlId(); saveInlineRdf(inlineRdf, &linkTagInformation); } openTagRegion(KoTextWriter::Private::Span, linkTagInformation); } KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); KoInlineObject *inlineObject = textObjectManager ? textObjectManager->inlineTextObject(charFormat) : 0; // If we are in an inline object if (currentFragment.length() == 1 && inlineObject - && currentFragment.text()[0].unicode() == QChar::ObjectReplacementCharacter) { + && currentFragment.text().at(0).unicode() == QChar::ObjectReplacementCharacter) { bool saveInlineObject = true; if (KoTextMeta* z = dynamic_cast(inlineObject)) { if (z->position() < from) { // // This starts before the selection, default // to not saving it with special cases to allow saving // saveInlineObject = false; if (z->type() == KoTextMeta::StartBookmark) { if (z->endBookmark()->position() > from) { // // They have selected something starting after the // opening but before the // saveInlineObject = true; } } } } // get all text ranges which start before this inline object // or end directly after it (+1 to last position for that) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), currentFragment.position(), currentFragment.position()+1, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // get all text ranges which start before this const QList textRangesBefore = textRanges.values(currentFragment.position()); // write tags for ranges which start before this content or at positioned at it foreach (const KoTextRange *range, textRangesBefore) { range->saveOdf(context, currentFragment.position(), KoTextRange::StartTag); } bool saveSpan = dynamic_cast(inlineObject) != 0; if (saveSpan) { QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); } else { saveSpan = false; } } if (saveInlineObject) { inlineObject->saveOdf(context); } if (saveSpan) { writer->endElement(); } // write tags for ranges which end after this inline object const QList textRangesAfter = textRanges.values(currentFragment.position()+1); foreach (const KoTextRange *range, textRangesAfter) { range->saveOdf(context, currentFragment.position()+1, KoTextRange::EndTag); } // // Track the end marker for matched pairs so we produce valid // ODF // if (KoTextMeta* z = dynamic_cast(inlineObject)) { debugText << "found kometa, type:" << z->type(); if (z->type() == KoTextMeta::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoTextMeta::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }/* else if (KoBookmark* z = dynamic_cast(inlineObject)) { if (z->type() == KoBookmark::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoBookmark::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }*/ } else { // Normal block, easier to handle QString styleName = saveCharacterStyle(charFormat, blockCharFormat); TagInformation fragmentTagInformation; if (!styleName.isEmpty() /*&& !identical*/) { fragmentTagInformation.setTagName("text:span"); fragmentTagInformation.addAttribute("text:style-name", styleName); } openTagRegion(KoTextWriter::Private::Span, fragmentTagInformation); QString text = currentFragment.text(); int spanFrom = fragmentStart >= from ? fragmentStart : from; int spanTo = to == -1 ? fragmentEnd : (fragmentEnd > to ? to : fragmentEnd); // get all text ranges which change within this span // or end directly after it (+1 to last position to include those) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), spanFrom, spanTo, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // avoid mid, if possible if (spanFrom != fragmentStart || spanTo != fragmentEnd || !textRanges.isEmpty()) { if (textRanges.isEmpty()) { writer->addTextSpan(text.mid(spanFrom - fragmentStart, spanTo - spanFrom)); } else { // split the fragment into subspans at the points of range starts/ends QList subSpanTos = textRanges.uniqueKeys(); qSort(subSpanTos); // ensure last subSpanTo to be at the end if (subSpanTos.last() != spanTo) { subSpanTos.append(spanTo); } // spanFrom should not need to be included if (subSpanTos.first() == spanFrom) { subSpanTos.removeOne(spanFrom); } int subSpanFrom = spanFrom; // for all subspans foreach (int subSpanTo, subSpanTos) { // write tags for text ranges which start before this subspan or are positioned at it const QList textRangesStartingBefore = textRanges.values(subSpanFrom); foreach (const KoTextRange *range, textRangesStartingBefore) { range->saveOdf(context, subSpanFrom, KoTextRange::StartTag); } // write subspan content writer->addTextSpan(text.mid(subSpanFrom - fragmentStart, subSpanTo - subSpanFrom)); // write tags for text ranges which end behind this subspan const QList textRangesEndingBehind = textRanges.values(subSpanTo); foreach (const KoTextRange *range, textRangesEndingBehind) { range->saveOdf(context, subSpanTo, KoTextRange::EndTag); } subSpanFrom = subSpanTo; } } } else { writer->addTextSpan(text); } closeTagRegion(); } // if (inlineObject) previousCharFormat = charFormat; lastEndPosition = fragmentEnd; } } if (!previousFragmentLink.isEmpty()) { writer->endElement(); } if (it.atEnd() && textRangeManager && ((to == -1) || (lastEndPosition <= to))) { // write tags for ranges which start at the last position of the block, // i.e. at the position after the last (text) fragment const QHash startingTextRangesAtEnd = textRangeManager->textRangesChangingWithin(block.document(), lastEndPosition, lastEndPosition, globalFrom, globalTo); foreach (const KoTextRange *range, startingTextRangesAtEnd) { range->saveOdf(context, lastEndPosition, KoTextRange::StartTag); } } QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { QTextCharFormat charFormat; endCharStyle->applyStyle(charFormat); QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); writer->endElement(); } } } } if (to !=-1 && to < block.position() + block.length()) { foreach (KoInlineObject* inlineObject, *currentPairedInlineObjectsStack) { inlineObject->saveOdf(context); } } closeTagRegion(); } void KoTextWriter::Private::saveTable(QTextTable *table, QHash &listStyles, int from, int to) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(table); int numberHeadingRows = table->format().property(KoTableStyle::NumberHeadingRows).toInt(); TagInformation tableTagInformation; QString tableStyleName = saveTableStyle(*table); tableTagInformation.setTagName("table:table"); tableTagInformation.addAttribute("table:style-name", tableStyleName); if (table->format().boolProperty(KoTableStyle::TableIsProtected)) { tableTagInformation.addAttribute("table:protected", "true"); } if (table->format().hasProperty(KoTableStyle::TableTemplate)) { tableTagInformation.addAttribute("table:template-name", sharedData->styleName(table->format().intProperty(KoTableStyle::TableTemplate))); } if (table->format().boolProperty(KoTableStyle::UseBandingColumnStyles)) { tableTagInformation.addAttribute("table:use-banding-columns-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseBandingRowStyles)) { tableTagInformation.addAttribute("table:use-banding-rows-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstColumnStyles)) { tableTagInformation.addAttribute("table:use-first-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstRowStyles)) { tableTagInformation.addAttribute("table:use-first-row-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastColumnStyles)) { tableTagInformation.addAttribute("table:use-last-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastRowStyles)) { tableTagInformation.addAttribute("table:use-last-row-styles", "true"); } int firstColumn = 0; int lastColumn = table->columns() -1; int firstRow = 0; int lastRow = table->rows() -1; if (to != -1 && from >= table->firstPosition() && to <= table->lastPosition()) { firstColumn = table->cellAt(from).column(); firstRow = table->cellAt(from).row(); lastColumn = table->cellAt(to).column(); lastRow = table->cellAt(to).row(); if (firstColumn == lastColumn && firstRow == lastRow && from >= table->firstPosition()) { // we only selected something inside a single cell so don't save a table writeBlocks(table->document(), from, to, listStyles, table); return; } } openTagRegion(KoTextWriter::Private::Table, tableTagInformation); for (int c = firstColumn ; c <= lastColumn; c++) { KoTableColumnStyle columnStyle = tcarManager.columnStyle(c); int repetition = 0; for (; repetition <= (lastColumn - c) ; repetition++) { if (columnStyle != tcarManager.columnStyle(c + repetition + 1)) break; } TagInformation tableColumnInformation; tableColumnInformation.setTagName("table:table-column"); QString columnStyleName = saveTableColumnStyle(columnStyle, c, tableStyleName); tableColumnInformation.addAttribute("table:style-name", columnStyleName); if (repetition > 0) tableColumnInformation.addAttribute("table:number-columns-repeated", repetition + 1); openTagRegion(KoTextWriter::Private::TableColumn, tableColumnInformation); closeTagRegion(); c += repetition; } if (numberHeadingRows) writer->startElement("table:table-header-rows"); // TODO make work for copying part of table that has header rows - copy header rows additionally or not ? for (int r = firstRow; r <= lastRow; r++) { TagInformation tableRowInformation; tableRowInformation.setTagName("table:table-row"); KoTableRowStyle rowStyle = tcarManager.rowStyle(r); if (!rowStyle.isEmpty()) { QString rowStyleName = saveTableRowStyle(rowStyle, r, tableStyleName); tableRowInformation.addAttribute("table:style-name", rowStyleName); } openTagRegion(KoTextWriter::Private::TableRow, tableRowInformation); for (int c = firstColumn; c <= lastColumn; c++) { QTextTableCell cell = table->cellAt(r, c); TagInformation tableCellInformation; if ((cell.row() == r) && (cell.column() == c)) { tableCellInformation.setTagName("table:table-cell"); if (cell.rowSpan() > 1) tableCellInformation.addAttribute("table:number-rows-spanned", cell.rowSpan()); if (cell.columnSpan() > 1) tableCellInformation.addAttribute("table:number-columns-spanned", cell.columnSpan()); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } // Save the Rdf for the table cell QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); QVariant v = cellFormat.property(KoTableCellStyle::InlineRdf); if (KoTextInlineRdf* inlineRdf = v.value()) { inlineRdf->saveOdf(context, writer); } QString cellStyleName = saveTableCellStyle(cellFormat, c, tableStyleName); tableCellInformation.addAttribute("table:style-name", cellStyleName); openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); writeBlocks(table->document(), cell.firstPosition(), cell.lastPosition(), listStyles, table); } else { tableCellInformation.setTagName("table:covered-table-cell"); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); } closeTagRegion(); } closeTagRegion(); if (r + 1 == numberHeadingRows) { writer->endElement(); // table:table-header-rows writer->startElement("table:table-rows"); } } if (numberHeadingRows) writer->endElement(); // table:table-rows closeTagRegion(); } void KoTextWriter::Private::saveTableOfContents(QTextDocument *document, QHash &listStyles, QTextBlock toc) { Q_UNUSED(document); writer->startElement("text:table-of-content"); KoTableOfContentsGeneratorInfo *info = toc.blockFormat().property(KoParagraphStyle::TableOfContentsData).value(); QTextDocument *tocDocument = toc.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = tocDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writer->addAttribute("text:name", QString("%1_Head").arg(info->m_name)); writeBlocks(tocDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(tocDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:table-of-content } void KoTextWriter::Private::saveBibliography(QTextDocument *document, QHash &listStyles, QTextBlock bib) { Q_UNUSED(document); writer->startElement("text:bibliography"); KoBibliographyInfo *info = bib.blockFormat().property(KoParagraphStyle::BibliographyData).value(); QTextDocument *bibDocument = bib.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = bibDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writeBlocks(bibDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(bibDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:bibliography } QTextBlock& KoTextWriter::Private::saveList(QTextBlock &block, QHash &listStyles, int level, QTextTable *currentTable) { QTextList *textList, *topLevelTextList; topLevelTextList = textList = block.textList(); int headingLevel = 0, numberedParagraphLevel = 0; QTextBlockFormat blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); KoTextDocument textDocument(block.document()); KoList *list = textDocument.list(block); int topListLevel = KoList::level(block); bool listStarted = false; if (!headingLevel && !numberedParagraphLevel) { listStarted = true; TagInformation listTagInformation; listTagInformation.setTagName("text:list"); listTagInformation.addAttribute("text:style-name", listStyles[textList]); if (list && listXmlIds.contains(list->listContinuedFrom())) { listTagInformation.addAttribute("text:continue-list", listXmlIds.value(list->listContinuedFrom())); } QString listXmlId = QString("list-%1").arg(createXmlId()); listTagInformation.addAttribute("xml:id", listXmlId); if (! listXmlIds.contains(list)) { listXmlIds.insert(list, listXmlId); } openTagRegion(KoTextWriter::Private::List, listTagInformation); } if (!headingLevel) { do { if (numberedParagraphLevel) { TagInformation paraTagInformation; paraTagInformation.setTagName("text:numbered-paragraph"); paraTagInformation.addAttribute("text:level", numberedParagraphLevel); paraTagInformation.addAttribute("text:style-name", listStyles.value(textList)); QString listId = numberedParagraphListIds.value(list, QString("list-%1").arg(createXmlId())); numberedParagraphListIds.insert(list, listId); paraTagInformation.addAttribute("text:list-id", listId); openTagRegion(KoTextWriter::Private::NumberedParagraph, paraTagInformation); writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); closeTagRegion(); } else { const bool listHeader = blockFormat.boolProperty(KoParagraphStyle::IsListHeader)|| blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem); TagInformation listItemTagInformation; listItemTagInformation.setTagName(listHeader ? "text:list-header" : "text:list-item"); if (block.blockFormat().hasProperty(KoParagraphStyle::ListStartValue)) { int startValue = block.blockFormat().intProperty(KoParagraphStyle::ListStartValue); listItemTagInformation.addAttribute("text:start-value", startValue); } if (textList == topLevelTextList) { openTagRegion(KoTextWriter::Private::ListItem, listItemTagInformation); } else { // This is a sub-list. So check for a list-change openTagRegion(KoTextWriter::Private::List, listItemTagInformation); } if (KoListStyle::isNumberingStyle(textList->format().style())) { KoTextBlockData blockData(block); writer->startElement("text:number", false); writer->addTextSpan(blockData.counterText()); writer->endElement(); } if (topListLevel == level && textList == topLevelTextList) { writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); // we are generating a text:list-item. Look forward and generate unnumbered list items. while (true) { QTextBlock nextBlock = block.next(); if (!nextBlock.textList() || !nextBlock.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) break; block = nextBlock; saveParagraph(block, block.position(), block.position() + block.length() - 1); } } else { //This is a sub-list while (KoList::level(block) >= (level + 1) && !(headingLevel || numberedParagraphLevel)) { block = saveList(block, listStyles, level + 1, currentTable); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); } //saveList will return a block one-past the last block of the list. //Since we are doing a block.next() below, we need to go one back. block = block.previous(); } closeTagRegion(); } block = block.next(); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); textList = block.textList(); } while ((textDocument.list(block) == list) && (KoList::level(block) >= topListLevel)); } if (listStarted) { closeTagRegion(); } return block; } void KoTextWriter::Private::addNameSpaceDefinitions(QString &generatedXmlString) { //Generate the name-space definitions so that it can be parsed. Like what is office:text, office:delta etc QString nameSpaceDefinitions; QTextStream nameSpacesStream(&nameSpaceDefinitions); nameSpacesStream << ""; generatedXmlString.prepend(nameSpaceDefinitions); generatedXmlString.append(""); } void KoTextWriter::Private::writeAttributes(QTextStream &outputXmlStream, KoXmlElement &element) { QList > attributes = element.attributeFullNames(); foreach (const Attribute &attributeNamePair, attributes) { if (attributeNamePair.first == KoXmlNS::text) { outputXmlStream << " text:" << attributeNamePair.second << "="; outputXmlStream << "\"" << element.attributeNS(KoXmlNS::text, attributeNamePair.second) << "\""; } else { //To Be Added when needed } } } void KoTextWriter::Private::writeNode(QTextStream &outputXmlStream, KoXmlNode &node, bool writeOnlyChildren) { if (node.isText()) { outputXmlStream << node.toText().data(); } else if (node.isElement()) { KoXmlElement element = node.toElement(); if ((element.localName() == "removed-content") && !element.childNodesCount()) { return; } if (!writeOnlyChildren) { outputXmlStream << "<" << element.prefix() << ":" << element.localName(); writeAttributes(outputXmlStream,element); outputXmlStream << ">"; } for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { writeNode(outputXmlStream, node); } if (!writeOnlyChildren) { outputXmlStream << ""; } } } QString KoTextWriter::Private::createXmlId() { QString uuid = QUuid::createUuid().toString(); uuid.remove('{'); uuid.remove('}'); return uuid; } diff --git a/libs/text/styles/KoCharacterStyle.cpp b/libs/text/styles/KoCharacterStyle.cpp index 9f1cf17fc16..f62cfd29b5a 100644 --- a/libs/text/styles/KoCharacterStyle.cpp +++ b/libs/text/styles/KoCharacterStyle.cpp @@ -1,2271 +1,2271 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Stuart Dickson * * 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. */ #include "KoCharacterStyle.h" #include "Styles_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoTextDocument.h" #ifdef SHOULD_BUILD_FONT_CONVERSION #include #include #include #include #include FT_FREETYPE_H #include FT_GLYPH_H #include FT_TYPES_H #include FT_OUTLINE_H #include FT_RENDER_H #include FT_TRUETYPE_TABLES_H #include FT_SFNT_NAMES_H #endif #include "TextDebug.h" #include "KoTextDebug.h" #ifdef SHOULD_BUILD_FONT_CONVERSION QMap textScaleMap; #endif //SHOULD_BUILD_FONT_CONVERSION class Q_DECL_HIDDEN KoCharacterStyle::Private { public: Private(); ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } qreal propertyDouble(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyDouble(key); else if (defaultStyle) return defaultStyle->d->propertyDouble(key); return 0.0; } return variant.toDouble(); } int propertyInt(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyInt(key); else if (defaultStyle) return defaultStyle->d->propertyInt(key); return 0; } return variant.toInt(); } QString propertyString(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyString(key); else if (defaultStyle) return defaultStyle->d->propertyString(key); return QString(); } return qvariant_cast(variant); } bool propertyBoolean(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyBoolean(key); else if (defaultStyle) return defaultStyle->d->propertyBoolean(key); return false; } return variant.toBool(); } QColor propertyColor(int key) const { QVariant variant = stylesPrivate.value(key); if (variant.isNull()) { if (parentStyle) return parentStyle->d->propertyColor(key); else if (defaultStyle) return defaultStyle->d->propertyColor(key); return QColor(); } return variant.value(); } // problem with fonts in linux and windows is that true type fonts have more than one metric // they have normal metric placed in font header table // microsoft metric placed in os2 table // apple metric placed in os2 table // ms-word is probably using CreateFontIndirect and GetOutlineTextMetric function to calculate line height // and this functions are using windows gdi environment which is using microsoft font metric placed in os2 table // qt on linux is using normal font metric // this two metrics are different and change from font to font // this font stretch is needed if we want to have exact line height as in ms-word and oo // // font_size * font_stretch = windows_font_height qreal calculateFontYStretch(const QString &fontFamily); StylePrivate hardCodedDefaultStyle; QString name; StylePrivate stylesPrivate; KoCharacterStyle *parentStyle; KoCharacterStyle *defaultStyle; bool m_inUse; }; KoCharacterStyle::Private::Private() : parentStyle(0), defaultStyle(0), m_inUse(false) { //set the minimal default properties hardCodedDefaultStyle.add(QTextFormat::FontFamily, QString("Sans Serif")); hardCodedDefaultStyle.add(QTextFormat::FontPointSize, 12.0); hardCodedDefaultStyle.add(QTextFormat::ForegroundBrush, QBrush(Qt::black)); hardCodedDefaultStyle.add(KoCharacterStyle::FontYStretch, 1); hardCodedDefaultStyle.add(QTextFormat::FontHintingPreference, QFont::PreferNoHinting); } void KoCharacterStyle::ensureMinimalProperties(QTextCharFormat &format) const { if (d->defaultStyle) { QMap props = d->defaultStyle->d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { // in case there is already a foreground color don't apply the use window font color as then the forground color // should be used. if (it.key() == KoCharacterStyle::UseWindowFontColor && format.hasProperty(QTextFormat::ForegroundBrush)) { ++it; continue; } // in case there is already a use window font color don't apply the forground brush as this overwrite the foreground color if (it.key() == QTextFormat::ForegroundBrush && format.hasProperty(KoCharacterStyle::UseWindowFontColor)) { ++it; continue; } if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } QMap props = d->hardCodedDefaultStyle.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { if (it.key() == QTextFormat::ForegroundBrush && format.hasProperty(KoCharacterStyle::UseWindowFontColor)) { ++it; continue; } format.setProperty(it.key(), it.value()); } ++it; } } qreal KoCharacterStyle::Private::calculateFontYStretch(const QString &fontFamily) { qreal stretch = 1; #ifdef SHOULD_BUILD_FONT_CONVERSION if (textScaleMap.contains(fontFamily)) { return textScaleMap.value(fontFamily); } FcResult result = FcResultMatch; FT_Library library; FT_Face face; int id = 0; int error = 0; QByteArray fontName = fontFamily.toLatin1(); //TODO http://freedesktop.org/software/fontconfig/fontconfig-devel/x19.html // we should specify slant and weight too FcPattern *font = FcPatternBuild (0, FC_FAMILY, FcTypeString,fontName.data(), FC_SIZE, FcTypeDouble, (qreal)11, nullptr); if (font == 0) { return 1; } // find font FcPattern *matched = 0; matched = FcFontMatch (0, font, &result); if (matched == 0) { FcPatternDestroy (font); return 1; } // get font family name char * str = 0; result = FcPatternGetString (matched, FC_FAMILY, 0,(FcChar8**) &str); if (result != FcResultMatch || str == 0) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // check if right font was found QByteArray foundFontFamily = QByteArray::fromRawData(str, strlen(str)); if (foundFontFamily != fontName) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get path to font str = 0; result = FcPatternGetString (matched, FC_FILE, 0,(FcChar8**) &str); if (result != FcResultMatch) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get index of font inside the font file result = FcPatternGetInteger (matched, FC_INDEX, 0, &id); if (result != FcResultMatch) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // initialize freetype error = FT_Init_FreeType( &library ); if (error) { FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get font metric error = FT_New_Face (library,(char *) str, id, &face); if (error) { FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get font metric os2 table TT_OS2 *os2; os2 = (TT_OS2 *) FT_Get_Sfnt_Table (face, ft_sfnt_os2); if(os2 == 0) { FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // get font metric header table TT_Header *header; header = (TT_Header *) FT_Get_Sfnt_Table (face, ft_sfnt_head); if(header == 0) { FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // check if the data is valid if (header->Units_Per_EM == 0 || (os2->usWinAscent + os2->usWinDescent) == 0) { FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); return 1; } // compute font height stretch // font_size * font_stretch = windows_font_height qreal height = os2->usWinAscent + os2->usWinDescent; height = height * (2048 / header->Units_Per_EM); stretch = (1.215 * height)/2500; stretch = (1.15 * height)/2500; // seems a better guess but probably not right FT_Done_Face(face); FT_Done_FreeType(library); FcPatternDestroy (font); FcPatternDestroy (matched); textScaleMap.insert(fontFamily, stretch); #else Q_UNUSED(fontFamily); #endif //SHOULD_BUILD_FONT_CONVERSION return stretch; } KoCharacterStyle::KoCharacterStyle(QObject *parent) : QObject(parent), d(new Private()) { } KoCharacterStyle::KoCharacterStyle(const QTextCharFormat &format, QObject *parent) : QObject(parent), d(new Private()) { copyProperties(format); } KoCharacterStyle::Type KoCharacterStyle::styleType() const { return KoCharacterStyle::CharacterStyle; } void KoCharacterStyle::copyProperties(const KoCharacterStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change d->parentStyle = style->d->parentStyle; d->defaultStyle = style->d->defaultStyle; } void KoCharacterStyle::copyProperties(const QTextCharFormat &format) { d->stylesPrivate = format.properties(); } KoCharacterStyle *KoCharacterStyle::clone(QObject *parent) const { KoCharacterStyle *newStyle = new KoCharacterStyle(parent); newStyle->copyProperties(this); return newStyle; } KoCharacterStyle::~KoCharacterStyle() { delete d; } void KoCharacterStyle::setDefaultStyle(KoCharacterStyle *defaultStyle) { d->defaultStyle = defaultStyle; } void KoCharacterStyle::setParentStyle(KoCharacterStyle *parent) { d->parentStyle = parent; } KoCharacterStyle *KoCharacterStyle::parentStyle() const { return d->parentStyle; } QPen KoCharacterStyle::textOutline() const { QVariant variant = value(QTextFormat::TextOutline); if (variant.isNull()) { return QPen(Qt::NoPen); } return qvariant_cast(variant); } QBrush KoCharacterStyle::background() const { QVariant variant = value(QTextFormat::BackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoCharacterStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoCharacterStyle::foreground() const { QVariant variant = value(QTextFormat::ForegroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoCharacterStyle::clearForeground() { d->stylesPrivate.remove(QTextCharFormat::ForegroundBrush); } void KoCharacterStyle::applyStyle(QTextCharFormat &format, bool emitSignal) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } bool fontSizeSet = false; // if this style has already set size don't apply the relatives const QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.begin(); QList clearProperty; while (it != props.end()) { if (!it.value().isNull()) { if (it.key() == KoCharacterStyle::PercentageFontSize && !fontSizeSet) { qreal size = it.value().toDouble() / 100.0; if (format.hasProperty(QTextFormat::FontPointSize)) { size *= format.doubleProperty(QTextFormat::FontPointSize); } else { size *= 12.0; } format.setProperty(QTextFormat::FontPointSize, size); } else if (it.key() == KoCharacterStyle::AdditionalFontSize && !fontSizeSet) { qreal size = it.value().toDouble() / 100.0; if (format.hasProperty(QTextFormat::FontPointSize)) { size += format.doubleProperty(QTextFormat::FontPointSize); } else { size += 12.0; } format.setProperty(QTextFormat::FontPointSize, size); } else if (it.key() == QTextFormat::FontFamily) { if (!props.contains(QTextFormat::FontStyleHint)) { clearProperty.append(QTextFormat::FontStyleHint); } if (!props.contains(QTextFormat::FontFixedPitch)) { clearProperty.append(QTextFormat::FontFixedPitch); } if (!props.contains(KoCharacterStyle::FontCharset)) { clearProperty.append(KoCharacterStyle::FontCharset); } format.setProperty(it.key(), it.value()); } else { format.setProperty(it.key(), it.value()); } if (it.key() == QTextFormat::FontPointSize) { fontSizeSet = true; } if (it.key() == QTextFormat::ForegroundBrush) { clearProperty.append(KoCharacterStyle::UseWindowFontColor); } else if (it.key() == KoCharacterStyle::UseWindowFontColor) { clearProperty.append(QTextFormat::ForegroundBrush); } } ++it; } foreach (int property, clearProperty) { debugText << "clearProperty" << property; format.clearProperty(property); } if (emitSignal) { emit styleApplied(this); d->m_inUse = true; } } KoCharacterStyle *KoCharacterStyle::autoStyle(const QTextCharFormat &format, QTextCharFormat blockCharFormat) const { KoCharacterStyle *autoStyle = new KoCharacterStyle(format); applyStyle(blockCharFormat, false); ensureMinimalProperties(blockCharFormat); autoStyle->removeDuplicates(blockCharFormat); autoStyle->setParentStyle(const_cast(this)); // remove StyleId if it is there as it is not a property of the style itself and will not be written out // so it should not be part of the autostyle. As otherwise it can happen that the StyleId is the only // property left and then we write out an empty style which is unneeded. // we also need to remove the properties of links as they are saved differently autoStyle->d->stylesPrivate.remove(StyleId); autoStyle->d->stylesPrivate.remove(QTextFormat::IsAnchor); autoStyle->d->stylesPrivate.remove(QTextFormat::AnchorHref); autoStyle->d->stylesPrivate.remove(QTextFormat::AnchorName); return autoStyle; } struct FragmentData { FragmentData(const QTextCharFormat &format, int position, int length) : format(format) , position(position) , length(length) {} FragmentData() {} QTextCharFormat format; int position; int length; }; Q_DECLARE_TYPEINFO(FragmentData, Q_MOVABLE_TYPE); void KoCharacterStyle::applyStyle(QTextBlock &block) const { QTextCursor cursor(block); QTextCharFormat cf = block.charFormat(); if (!cf.isTableCellFormat()) { cf = KoTextDocument(block.document()).frameCharFormat(); } applyStyle(cf); ensureMinimalProperties(cf); cursor.setBlockCharFormat(cf); // be sure that we keep the InlineInstanceId, anchor information and ChangeTrackerId when applying a style QVector fragments; for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextFragment currentFragment = it.fragment(); if (currentFragment.isValid()) { QTextCharFormat format(cf); QVariant v = currentFragment.charFormat().property(InlineInstanceId); if (!v.isNull()) { format.setProperty(InlineInstanceId, v); } v = currentFragment.charFormat().property(ChangeTrackerId); if (!v.isNull()) { format.setProperty(ChangeTrackerId, v); } if (currentFragment.charFormat().isAnchor()) { format.setAnchor(true); format.setAnchorHref(currentFragment.charFormat().anchorHref()); } fragments.append(FragmentData(format, currentFragment.position(), currentFragment.length())); } } foreach (const FragmentData &fragment, fragments) { cursor.setPosition(fragment.position); cursor.setPosition(fragment.position + fragment.length, QTextCursor::KeepAnchor); cursor.setCharFormat(fragment.format); } } void KoCharacterStyle::applyStyle(QTextCursor *selection) const { // FIXME below should be done for each frament in the selection QTextCharFormat cf = selection->charFormat(); applyStyle(cf); ensureMinimalProperties(cf); selection->setCharFormat(cf); } void KoCharacterStyle::unapplyStyle(QTextCharFormat &format) const { if (d->parentStyle) d->parentStyle->unapplyStyle(format); QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && it.value() == format.property(it.key())) { format.clearProperty(it.key()); } ++it; } props = d->hardCodedDefaultStyle.properties(); it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } bool KoCharacterStyle::isApplied() const { return d->m_inUse; } void KoCharacterStyle::unapplyStyle(QTextBlock &block) const { QTextCursor cursor(block); QTextCharFormat cf = cursor.blockCharFormat(); unapplyStyle(cf); cursor.setBlockCharFormat(cf); if (block.length() == 1) // only the linefeed return; QTextBlock::iterator iter = block.end(); do { --iter; QTextFragment fragment = iter.fragment(); cursor.setPosition(fragment.position() + 1); cf = cursor.charFormat(); unapplyStyle(cf); cursor.setPosition(fragment.position()); cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); cursor.setCharFormat(cf); } while (iter != block.begin()); } // OASIS 14.2.29 static void parseOdfLineWidth(const QString &width, KoCharacterStyle::LineWeight &lineWeight, qreal &lineWidth) { lineWidth = 0; lineWeight = KoCharacterStyle::AutoLineWeight; if (width.isEmpty() || width == "auto") lineWeight = KoCharacterStyle::AutoLineWeight; else if (width == "normal") lineWeight = KoCharacterStyle::NormalLineWeight; else if (width == "bold") lineWeight = KoCharacterStyle::BoldLineWeight; else if (width == "thin") lineWeight = KoCharacterStyle::ThinLineWeight; else if (width == "dash") lineWeight = KoCharacterStyle::DashLineWeight; else if (width == "medium") lineWeight = KoCharacterStyle::MediumLineWeight; else if (width == "thick") lineWeight = KoCharacterStyle::ThickLineWeight; else if (width.endsWith('%')) { lineWeight = KoCharacterStyle::PercentLineWeight; - lineWidth = width.mid(0, width.length() - 1).toDouble(); + lineWidth = width.midRef(0, width.length() - 1).toDouble(); } else if (width[width.length()-1].isNumber()) { lineWeight = KoCharacterStyle::LengthLineWeight; lineWidth = width.toDouble(); } else { lineWeight = KoCharacterStyle::LengthLineWeight; lineWidth = KoUnit::parseValue(width); } } // OASIS 14.2.29 static void importOdfLine(const QString &type, const QString &style, KoCharacterStyle::LineStyle &lineStyle, KoCharacterStyle::LineType &lineType) { lineStyle = KoCharacterStyle::NoLineStyle; lineType = KoCharacterStyle::NoLineType; QString fixedType = type; QString fixedStyle = style; if (fixedStyle == "none") fixedType.clear(); else if (fixedType.isEmpty() && !fixedStyle.isEmpty()) fixedType = "single"; else if (!fixedType.isEmpty() && fixedType != "none" && fixedStyle.isEmpty()) { // don't set a style when the type is none fixedStyle = "solid"; } if (fixedType == "single") lineType = KoCharacterStyle::SingleLine; else if (fixedType == "double") lineType = KoCharacterStyle::DoubleLine; if (fixedStyle == "solid") lineStyle = KoCharacterStyle::SolidLine; else if (fixedStyle == "dotted") lineStyle = KoCharacterStyle::DottedLine; else if (fixedStyle == "dash") lineStyle = KoCharacterStyle::DashLine; else if (fixedStyle == "long-dash") lineStyle = KoCharacterStyle::LongDashLine; else if (fixedStyle == "dot-dash") lineStyle = KoCharacterStyle::DotDashLine; else if (fixedStyle == "dot-dot-dash") lineStyle = KoCharacterStyle::DotDotDashLine; else if (fixedStyle == "wave") lineStyle = KoCharacterStyle::WaveLine; } static QString exportOdfLineType(KoCharacterStyle::LineType lineType) { switch (lineType) { case KoCharacterStyle::NoLineType: return "none"; case KoCharacterStyle::SingleLine: return "single"; case KoCharacterStyle::DoubleLine: return "double"; default: return ""; } } static QString exportOdfLineStyle(KoCharacterStyle::LineStyle lineStyle) { switch (lineStyle) { case KoCharacterStyle::NoLineStyle: return "none"; case KoCharacterStyle::SolidLine: return "solid"; case KoCharacterStyle::DottedLine: return "dotted"; case KoCharacterStyle::DashLine: return "dash"; case KoCharacterStyle::LongDashLine: return "long-dash"; case KoCharacterStyle::DotDashLine: return "dot-dash"; case KoCharacterStyle::DotDotDashLine: return "dot-dot-dash"; case KoCharacterStyle::WaveLine: return "wave"; default: return ""; } } static QString exportOdfLineMode(KoCharacterStyle::LineMode lineMode) { switch (lineMode) { case KoCharacterStyle::ContinuousLineMode: return "continuous"; case KoCharacterStyle::SkipWhiteSpaceLineMode: return "skip-white-space"; default: return ""; } } static QString exportOdfLineWidth(KoCharacterStyle::LineWeight lineWeight, qreal lineWidth) { switch (lineWeight) { case KoCharacterStyle::AutoLineWeight: return "auto"; case KoCharacterStyle::NormalLineWeight: return "normal"; case KoCharacterStyle::BoldLineWeight: return "bold"; case KoCharacterStyle::ThinLineWeight: return "thin"; case KoCharacterStyle::DashLineWeight: return "dash"; case KoCharacterStyle::MediumLineWeight: return "medium"; case KoCharacterStyle::ThickLineWeight: return "thick"; case KoCharacterStyle::PercentLineWeight: return QString("%1%").arg(lineWidth); case KoCharacterStyle::LengthLineWeight: return QString("%1pt").arg(lineWidth); default: return QString(); } } static QString exportOdfFontStyleHint(QFont::StyleHint hint) { switch (hint) { case QFont::Serif: return "roman"; case QFont::SansSerif: return "swiss"; case QFont::TypeWriter: return "modern"; case QFont::Decorative: return "decorative"; case QFont::System: return "system"; /*case QFont::Script */ default: return ""; } } void KoCharacterStyle::setFontFamily(const QString &family) { d->setProperty(QTextFormat::FontFamily, family); setFontYStretch(d->calculateFontYStretch(family)); } QString KoCharacterStyle::fontFamily() const { return d->propertyString(QTextFormat::FontFamily); } void KoCharacterStyle::setFontPointSize(qreal size) { d->setProperty(QTextFormat::FontPointSize, size); } void KoCharacterStyle::clearFontPointSize() { d->stylesPrivate.remove(QTextFormat::FontPointSize); } qreal KoCharacterStyle::fontPointSize() const { return d->propertyDouble(QTextFormat::FontPointSize); } void KoCharacterStyle::setFontWeight(int weight) { d->setProperty(QTextFormat::FontWeight, weight); } int KoCharacterStyle::fontWeight() const { return d->propertyInt(QTextFormat::FontWeight); } void KoCharacterStyle::setFontItalic(bool italic) { d->setProperty(QTextFormat::FontItalic, italic); } bool KoCharacterStyle::fontItalic() const { return d->propertyBoolean(QTextFormat::FontItalic); } ///TODO Review legacy fontOverline functions and testing (consider removal) /* void KoCharacterStyle::setFontOverline(bool overline) { d->setProperty(QTextFormat::FontOverline, overline); } bool KoCharacterStyle::fontOverline() const { return d->propertyBoolean(QTextFormat::FontOverline); } */ void KoCharacterStyle::setFontFixedPitch(bool fixedPitch) { d->setProperty(QTextFormat::FontFixedPitch, fixedPitch); } bool KoCharacterStyle::fontFixedPitch() const { return d->propertyBoolean(QTextFormat::FontFixedPitch); } void KoCharacterStyle::setFontStyleHint(QFont::StyleHint styleHint) { d->setProperty(QTextFormat::FontStyleHint, styleHint); } QFont::StyleHint KoCharacterStyle::fontStyleHint() const { return static_cast(d->propertyInt(QTextFormat::FontStyleHint)); } void KoCharacterStyle::setFontKerning(bool enable) { d->setProperty(QTextFormat::FontKerning, enable); } bool KoCharacterStyle::fontKerning() const { return d->propertyBoolean(QTextFormat::FontKerning); } void KoCharacterStyle::setVerticalAlignment(QTextCharFormat::VerticalAlignment alignment) { d->setProperty(QTextFormat::TextVerticalAlignment, alignment); } QTextCharFormat::VerticalAlignment KoCharacterStyle::verticalAlignment() const { return static_cast(d->propertyInt(QTextFormat::TextVerticalAlignment)); } void KoCharacterStyle::setTextOutline(const QPen &pen) { d->setProperty(QTextFormat::TextOutline, pen); } void KoCharacterStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoCharacterStyle::setForeground(const QBrush &brush) { d->setProperty(QTextFormat::ForegroundBrush, brush); } void KoCharacterStyle::setFontAutoColor(bool use) { d->setProperty(KoCharacterStyle::UseWindowFontColor, use); } QString KoCharacterStyle::name() const { return d->name; } void KoCharacterStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoCharacterStyle::styleId() const { return d->propertyInt(StyleId); } void KoCharacterStyle::setStyleId(int id) { d->setProperty(StyleId, id); } QFont KoCharacterStyle::font() const { QFont font; if (d->stylesPrivate.contains(QTextFormat::FontFamily)) font.setFamily(fontFamily()); if (d->stylesPrivate.contains(QTextFormat::FontPointSize)) font.setPointSizeF(fontPointSize()); if (d->stylesPrivate.contains(QTextFormat::FontWeight)) font.setWeight(fontWeight()); if (d->stylesPrivate.contains(QTextFormat::FontItalic)) font.setItalic(fontItalic()); return font; } void KoCharacterStyle::setHasHyphenation(bool on) { d->setProperty(HasHyphenation, on); } bool KoCharacterStyle::hasHyphenation() const { return d->propertyBoolean(HasHyphenation); } void KoCharacterStyle::setHyphenationPushCharCount(int count) { if (count > 0) d->setProperty(HyphenationPushCharCount, count); else d->stylesPrivate.remove(HyphenationPushCharCount); } int KoCharacterStyle::hyphenationPushCharCount() const { if (hasProperty(HyphenationPushCharCount)) return d->propertyInt(HyphenationPushCharCount); return 0; } void KoCharacterStyle::setHyphenationRemainCharCount(int count) { if (count > 0) d->setProperty(HyphenationRemainCharCount, count); else d->stylesPrivate.remove(HyphenationRemainCharCount); } int KoCharacterStyle::hyphenationRemainCharCount() const { if (hasProperty(HyphenationRemainCharCount)) return d->propertyInt(HyphenationRemainCharCount); return 0; } void KoCharacterStyle::setStrikeOutStyle(KoCharacterStyle::LineStyle strikeOut) { d->setProperty(StrikeOutStyle, strikeOut); } KoCharacterStyle::LineStyle KoCharacterStyle::strikeOutStyle() const { return (KoCharacterStyle::LineStyle) d->propertyInt(StrikeOutStyle); } void KoCharacterStyle::setStrikeOutType(LineType lineType) { d->setProperty(StrikeOutType, lineType); } KoCharacterStyle::LineType KoCharacterStyle::strikeOutType() const { return (KoCharacterStyle::LineType) d->propertyInt(StrikeOutType); } void KoCharacterStyle::setStrikeOutColor(const QColor &color) { d->setProperty(StrikeOutColor, color); } QColor KoCharacterStyle::strikeOutColor() const { return d->propertyColor(StrikeOutColor); } void KoCharacterStyle::setStrikeOutWidth(LineWeight weight, qreal width) { d->setProperty(KoCharacterStyle::StrikeOutWeight, weight); d->setProperty(KoCharacterStyle::StrikeOutWidth, width); } void KoCharacterStyle::strikeOutWidth(LineWeight &weight, qreal &width) const { weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::StrikeOutWeight); width = d->propertyDouble(KoCharacterStyle::StrikeOutWidth); } void KoCharacterStyle::setStrikeOutMode(LineMode lineMode) { d->setProperty(StrikeOutMode, lineMode); } void KoCharacterStyle::setStrikeOutText(const QString &text) { d->setProperty(StrikeOutText, text); } QString KoCharacterStyle::strikeOutText() const { return d->propertyString(StrikeOutText); } KoCharacterStyle::LineMode KoCharacterStyle::strikeOutMode() const { return (KoCharacterStyle::LineMode) d->propertyInt(StrikeOutMode); } void KoCharacterStyle::setOverlineStyle(KoCharacterStyle::LineStyle overline) { d->setProperty(OverlineStyle, overline); } KoCharacterStyle::LineStyle KoCharacterStyle::overlineStyle() const { return (KoCharacterStyle::LineStyle) d->propertyInt(OverlineStyle); } void KoCharacterStyle::setOverlineType(LineType lineType) { d->setProperty(OverlineType, lineType); } KoCharacterStyle::LineType KoCharacterStyle::overlineType() const { return (KoCharacterStyle::LineType) d->propertyInt(OverlineType); } void KoCharacterStyle::setOverlineColor(const QColor &color) { d->setProperty(KoCharacterStyle::OverlineColor, color); } QColor KoCharacterStyle::overlineColor() const { return d->propertyColor(KoCharacterStyle::OverlineColor); } void KoCharacterStyle::setOverlineWidth(LineWeight weight, qreal width) { d->setProperty(KoCharacterStyle::OverlineWeight, weight); d->setProperty(KoCharacterStyle::OverlineWidth, width); } void KoCharacterStyle::overlineWidth(LineWeight &weight, qreal &width) const { weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::OverlineWeight); width = d->propertyDouble(KoCharacterStyle::OverlineWidth); } void KoCharacterStyle::setOverlineMode(LineMode mode) { d->setProperty(KoCharacterStyle::OverlineMode, mode); } KoCharacterStyle::LineMode KoCharacterStyle::overlineMode() const { return static_cast(d->propertyInt(KoCharacterStyle::OverlineMode)); } void KoCharacterStyle::setUnderlineStyle(KoCharacterStyle::LineStyle underline) { d->setProperty(UnderlineStyle, underline); } KoCharacterStyle::LineStyle KoCharacterStyle::underlineStyle() const { return (KoCharacterStyle::LineStyle) d->propertyInt(UnderlineStyle); } void KoCharacterStyle::setUnderlineType(LineType lineType) { d->setProperty(UnderlineType, lineType); } KoCharacterStyle::LineType KoCharacterStyle::underlineType() const { return (KoCharacterStyle::LineType) d->propertyInt(UnderlineType); } void KoCharacterStyle::setUnderlineColor(const QColor &color) { d->setProperty(QTextFormat::TextUnderlineColor, color); } QColor KoCharacterStyle::underlineColor() const { return d->propertyColor(QTextFormat::TextUnderlineColor); } void KoCharacterStyle::setUnderlineWidth(LineWeight weight, qreal width) { d->setProperty(KoCharacterStyle::UnderlineWeight, weight); d->setProperty(KoCharacterStyle::UnderlineWidth, width); } void KoCharacterStyle::underlineWidth(LineWeight &weight, qreal &width) const { weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::UnderlineWeight); width = d->propertyDouble(KoCharacterStyle::UnderlineWidth); } void KoCharacterStyle::setUnderlineMode(LineMode mode) { d->setProperty(KoCharacterStyle::UnderlineMode, mode); } KoCharacterStyle::LineMode KoCharacterStyle::underlineMode() const { return static_cast(d->propertyInt(KoCharacterStyle::UnderlineMode)); } void KoCharacterStyle::setFontLetterSpacing(qreal spacing) { d->setProperty(KoCharacterStyle::FontLetterSpacing, spacing); } qreal KoCharacterStyle::fontLetterSpacing() const { return d->propertyDouble(KoCharacterStyle::FontLetterSpacing); } void KoCharacterStyle::setFontWordSpacing(qreal spacing) { d->setProperty(QTextCharFormat::FontWordSpacing, spacing); } qreal KoCharacterStyle::fontWordSpacing() const { return d->propertyDouble(QTextCharFormat::FontWordSpacing); } void KoCharacterStyle::setFontCapitalization(QFont::Capitalization capitalization) { d->setProperty(QTextFormat::FontCapitalization, capitalization); } QFont::Capitalization KoCharacterStyle::fontCapitalization() const { return (QFont::Capitalization) d->propertyInt(QTextFormat::FontCapitalization); } void KoCharacterStyle::setFontYStretch(qreal stretch) { d->setProperty(KoCharacterStyle::FontYStretch, stretch); } qreal KoCharacterStyle::fontYStretch() const { return d->propertyDouble(KoCharacterStyle::FontYStretch); } void KoCharacterStyle::setCountry(const QString &country) { if (country.isEmpty()) d->stylesPrivate.remove(KoCharacterStyle::Country); else d->setProperty(KoCharacterStyle::Country, country); } void KoCharacterStyle::setLanguage(const QString &language) { if (language.isEmpty()) d->stylesPrivate.remove(KoCharacterStyle::Language); else d->setProperty(KoCharacterStyle::Language, language); } QString KoCharacterStyle::country() const { return value(KoCharacterStyle::Country).toString(); } QString KoCharacterStyle::language() const { return d->propertyString(KoCharacterStyle::Language); } bool KoCharacterStyle::blinking() const { return d->propertyBoolean(Blink); } void KoCharacterStyle::setBlinking(bool blink) { d->setProperty(KoCharacterStyle::Blink, blink); } bool KoCharacterStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } static QString rotationScaleToString(KoCharacterStyle::RotationScale rotationScale) { QString scale = "line-height"; if (rotationScale == KoCharacterStyle::Fixed) { scale = "fixed"; } return scale; } static KoCharacterStyle::RotationScale stringToRotationScale(const QString &scale) { KoCharacterStyle::RotationScale rotationScale = KoCharacterStyle::LineHeight; if (scale == "fixed") { rotationScale = KoCharacterStyle::Fixed; } return rotationScale; } void KoCharacterStyle::setTextRotationAngle(qreal angle) { d->setProperty(TextRotationAngle, angle); } qreal KoCharacterStyle::textRotationAngle() const { return d->propertyDouble(TextRotationAngle); } void KoCharacterStyle::setTextRotationScale(RotationScale scale) { d->setProperty(TextRotationScale, rotationScaleToString(scale)); } KoCharacterStyle::RotationScale KoCharacterStyle::textRotationScale() const { return stringToRotationScale(d->propertyString(TextRotationScale)); } void KoCharacterStyle::setTextScale(int scale) { d->setProperty(TextScale, scale); } int KoCharacterStyle::textScale() const { return d->propertyInt(TextScale); } void KoCharacterStyle::setTextShadow(const KoShadowStyle& shadow) { d->setProperty(TextShadow, qVariantFromValue(shadow)); } KoShadowStyle KoCharacterStyle::textShadow() const { if (hasProperty(TextShadow)) { QVariant shadow = value(TextShadow); if (shadow.canConvert()) return shadow.value(); } return KoShadowStyle(); } void KoCharacterStyle::setTextCombine(KoCharacterStyle::TextCombineType type) { d->setProperty(TextCombine, type); } KoCharacterStyle::TextCombineType KoCharacterStyle::textCombine() const { if (hasProperty(TextCombine)) { return (KoCharacterStyle::TextCombineType) d->propertyInt(TextCombine); } return NoTextCombine; } QChar KoCharacterStyle::textCombineEndChar() const { if (hasProperty(TextCombineEndChar)) { QString val = d->propertyString(TextCombineEndChar); if (val.length() > 0) return val.at(0); } return QChar(); } void KoCharacterStyle::setTextCombineEndChar(const QChar& character) { d->setProperty(TextCombineEndChar, character); } QChar KoCharacterStyle::textCombineStartChar() const { if (hasProperty(TextCombineStartChar)) { QString val = d->propertyString(TextCombineStartChar); if (val.length() > 0) return val.at(0); } return QChar(); } void KoCharacterStyle::setTextCombineStartChar(const QChar& character) { d->setProperty(TextCombineStartChar, character); } void KoCharacterStyle::setFontRelief(KoCharacterStyle::ReliefType relief) { d->setProperty(FontRelief, relief); } KoCharacterStyle::ReliefType KoCharacterStyle::fontRelief() const { if (hasProperty(FontRelief)) return (KoCharacterStyle::ReliefType) d->propertyInt(FontRelief); return KoCharacterStyle::NoRelief; } KoCharacterStyle::EmphasisPosition KoCharacterStyle::textEmphasizePosition() const { if (hasProperty(TextEmphasizePosition)) return (KoCharacterStyle::EmphasisPosition) d->propertyInt(TextEmphasizePosition); return KoCharacterStyle::EmphasisAbove; } void KoCharacterStyle::setTextEmphasizePosition(KoCharacterStyle::EmphasisPosition position) { d->setProperty(TextEmphasizePosition, position); } KoCharacterStyle::EmphasisStyle KoCharacterStyle::textEmphasizeStyle() const { if (hasProperty(TextEmphasizeStyle)) return (KoCharacterStyle::EmphasisStyle) d->propertyInt(TextEmphasizeStyle); return KoCharacterStyle::NoEmphasis; } void KoCharacterStyle::setTextEmphasizeStyle(KoCharacterStyle::EmphasisStyle emphasis) { d->setProperty(TextEmphasizeStyle, emphasis); } void KoCharacterStyle::setPercentageFontSize(qreal percent) { d->setProperty(KoCharacterStyle::PercentageFontSize, percent); } qreal KoCharacterStyle::percentageFontSize() const { return d->propertyDouble(KoCharacterStyle::PercentageFontSize); } void KoCharacterStyle::setAdditionalFontSize(qreal percent) { d->setProperty(KoCharacterStyle::AdditionalFontSize, percent); } qreal KoCharacterStyle::additionalFontSize() const { return d->propertyDouble(KoCharacterStyle::AdditionalFontSize); } void KoCharacterStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext, bool loadParents) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString())); if (!name.isEmpty()) { d->name = name; } else { d->name = element->attributeNS(KoXmlNS::style, "name", QString()); } QString family = element->attributeNS(KoXmlNS::style, "family", "text"); context.styleStack().save(); if (loadParents) { context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent } else { context.styleStack().push(*element); } context.styleStack().setTypeProperties("text"); // load the style:text-properties loadOdfProperties(scontext); context.styleStack().restore(); } void KoCharacterStyle::loadOdfProperties(KoShapeLoadingContext &scontext) { KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack(); d->stylesPrivate = StylePrivate(); // The fo:color attribute specifies the foreground color of text. const QString color(styleStack.property(KoXmlNS::fo, "color")); if (!color.isEmpty()) { QColor c(color); if (c.isValid()) { // 3.10.3 setForeground(QBrush(c)); } } QString fontName(styleStack.property(KoXmlNS::fo, "font-family")); if (!fontName.isEmpty()) { // Specify whether a font has a fixed or variable width. // These attributes are ignored if there is no corresponding fo:font-family attribute attached to the same formatting properties element. const QString fontPitch(styleStack.property(KoXmlNS::style, "font-pitch")); if (!fontPitch.isEmpty()) { setFontFixedPitch(fontPitch == "fixed"); } const QString genericFamily(styleStack.property(KoXmlNS::style, "font-family-generic")); if (!genericFamily.isEmpty()) { if (genericFamily == "roman") setFontStyleHint(QFont::Serif); else if (genericFamily == "swiss") setFontStyleHint(QFont::SansSerif); else if (genericFamily == "modern") setFontStyleHint(QFont::TypeWriter); else if (genericFamily == "decorative") setFontStyleHint(QFont::Decorative); else if (genericFamily == "system") setFontStyleHint(QFont::System); else if (genericFamily == "script") { ; // TODO: no hint available in Qt yet, we should at least store it as a property internally! } } const QString fontCharset(styleStack.property(KoXmlNS::style, "font-charset")); if (!fontCharset.isEmpty()) { // this property is not required by Qt, since Qt auto selects the right font based on the text // The only charset of interest to us is x-symbol - this should disable spell checking d->setProperty(KoCharacterStyle::FontCharset, fontCharset); } } const QString fontFamily(styleStack.property(KoXmlNS::style, "font-family")); if (!fontFamily.isEmpty()) fontName = fontFamily; if (styleStack.hasProperty(KoXmlNS::style, "font-name")) { // This font name is a reference to a font face declaration. KoOdfStylesReader &stylesReader = scontext.odfLoadingContext().stylesReader(); const KoXmlElement *fontFace = stylesReader.findStyle(styleStack.property(KoXmlNS::style, "font-name")); if (fontFace != 0) { fontName = fontFace->attributeNS(KoXmlNS::svg, "font-family", ""); KoXmlElement fontFaceElem; forEachElement(fontFaceElem, (*fontFace)) { if (fontFaceElem.tagName() == "font-face-src") { KoXmlElement fontUriElem; forEachElement(fontUriElem, fontFaceElem) { if (fontUriElem.tagName() == "font-face-uri") { QString filename = fontUriElem.attributeNS(KoXmlNS::xlink, "href"); KoStore *store = scontext.odfLoadingContext().store(); if (store->open(filename)) { KoStoreDevice device(store); QByteArray data = device.readAll(); if (device.open(QIODevice::ReadOnly)) { QFontDatabase::addApplicationFontFromData(data); } } } } } } } } if (!fontName.isEmpty()) { // Hmm, the remove "'" could break it's in the middle of the fontname... fontName = fontName.remove('\''); // 'Thorndale' is not known outside OpenOffice so we substitute it // with 'Times New Roman' that looks nearly the same. if (fontName == "Thorndale") fontName = "Times New Roman"; // 'StarSymbol' is written by OpenOffice but they actually mean // 'OpenSymbol'. if (fontName == "StarSymbol") fontName = "OpenSymbol"; fontName.remove(QRegExp("\\sCE$")); // Arial CE -> Arial setFontFamily(fontName); } // Specify the size of a font. The value of these attribute is either an absolute length or a percentage if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) { const QString fontSize(styleStack.property(KoXmlNS::fo, "font-size")); if (!fontSize.isEmpty()) { if (fontSize.endsWith('%')) { - setPercentageFontSize(fontSize.left(fontSize.length() - 1).toDouble()); + setPercentageFontSize(fontSize.leftRef(fontSize.length() - 1).toDouble()); } else { setFontPointSize(KoUnit::parseValue(fontSize)); } } } else { const QString fontSizeRel(styleStack.property(KoXmlNS::style, "font-size-rel")); if (!fontSizeRel.isEmpty()) { setAdditionalFontSize(KoUnit::parseValue(fontSizeRel)); } } // Specify the weight of a font. The permitted values are normal, bold, and numeric values 100-900, in steps of 100. Unsupported numerical values are rounded off to the next supported value. const QString fontWeight(styleStack.property(KoXmlNS::fo, "font-weight")); if (!fontWeight.isEmpty()) { // 3.10.24 int boldness; if (fontWeight == "normal") boldness = 50; else if (fontWeight == "bold") boldness = 75; else // XSL/CSS has 100,200,300...900. Not the same scale as Qt! // See http://www.w3.org/TR/2001/REC-xsl-20011015/slice7.html#font-weight boldness = fontWeight.toInt() / 10; setFontWeight(boldness); } // Specify whether to use normal or italic font face. const QString fontStyle(styleStack.property(KoXmlNS::fo, "font-style" )); if (!fontStyle.isEmpty()) { // 3.10.19 if (fontStyle == "italic" || fontStyle == "oblique") { // no difference in kotext setFontItalic(true); } else { setFontItalic(false); } } //TODO #if 0 d->m_bWordByWord = styleStack.property(KoXmlNS::style, "text-underline-mode") == "skip-white-space"; // TODO style:text-line-through-mode /* // OO compat code, to move to OO import filter d->m_bWordByWord = (styleStack.hasProperty( KoXmlNS::fo, "score-spaces")) // 3.10.25 && (styleStack.property( KoXmlNS::fo, "score-spaces") == "false"); if( styleStack.hasProperty( KoXmlNS::style, "text-crossing-out" )) { // 3.10.6 QString strikeOutType = styleStack.property( KoXmlNS::style, "text-crossing-out" ); if( strikeOutType =="double-line") m_strikeOutType = S_DOUBLE; else if( strikeOutType =="single-line") m_strikeOutType = S_SIMPLE; else if( strikeOutType =="thick-line") m_strikeOutType = S_SIMPLE_BOLD; // not supported by Words: "slash" and "X" // not supported by OO: stylelines (solid, dash, dot, dashdot, dashdotdot) } */ #endif // overline modes const QString textOverlineMode(styleStack.property( KoXmlNS::style, "text-overline-mode")); if (!textOverlineMode.isEmpty()) { if (textOverlineMode == "skip-white-space") { setOverlineMode(SkipWhiteSpaceLineMode); } else if (textOverlineMode == "continuous") { setOverlineMode(ContinuousLineMode); } } // Specifies whether text is overlined, and if so, whether a single or qreal line will be used for overlining. const QString textOverlineType(styleStack.property(KoXmlNS::style, "text-overline-type")); const QString textOverlineStyle(styleStack.property(KoXmlNS::style, "text-overline-style")); if (!textOverlineType.isEmpty() || !textOverlineStyle.isEmpty()) { // OASIS 14.4.28 LineStyle overlineStyle; LineType overlineType; importOdfLine(textOverlineType, textOverlineStyle, overlineStyle, overlineType); setOverlineStyle(overlineStyle); setOverlineType(overlineType); } const QString textOverlineWidth(styleStack.property(KoXmlNS::style, "text-overline-width")); if (!textOverlineWidth.isEmpty()) { qreal overlineWidth; LineWeight overlineWeight; parseOdfLineWidth(textOverlineWidth, overlineWeight, overlineWidth); setOverlineWidth(overlineWeight, overlineWidth); } // Specifies the color that is used to overline text. The value of this attribute is either font-color or a color. If the value is font-color, the current text color is used for overlining. QString overLineColor = styleStack.property(KoXmlNS::style, "text-overline-color"); // OO 3.10.23, OASIS 14.4.31 if (!overLineColor.isEmpty() && overLineColor != "font-color") { setOverlineColor(QColor(overLineColor)); } else if (overLineColor == "font-color") { setOverlineColor(QColor()); } // underline modes const QString textUndelineMode(styleStack.property( KoXmlNS::style, "text-underline-mode")); if (!textUndelineMode.isEmpty()) { if (textUndelineMode == "skip-white-space") { setUnderlineMode(SkipWhiteSpaceLineMode); } else if (textUndelineMode == "continuous") { setUnderlineMode(ContinuousLineMode); } } // Specifies whether text is underlined, and if so, whether a single or qreal line will be used for underlining. const QString textUnderlineType(styleStack.property(KoXmlNS::style, "text-underline-type")); const QString textUnderlineStyle(styleStack.property(KoXmlNS::style, "text-underline-style")); if (!textUnderlineType.isEmpty() || !textUnderlineStyle.isEmpty()) { // OASIS 14.4.28 LineStyle underlineStyle; LineType underlineType; importOdfLine(textUnderlineType, textUnderlineStyle, underlineStyle, underlineType); setUnderlineStyle(underlineStyle); setUnderlineType(underlineType); } const QString textUnderlineWidth(styleStack.property(KoXmlNS::style, "text-underline-width")); if (!textUnderlineWidth.isEmpty()) { qreal underlineWidth; LineWeight underlineWeight; parseOdfLineWidth(textUnderlineWidth, underlineWeight, underlineWidth); setUnderlineWidth(underlineWeight, underlineWidth); } // Specifies the color that is used to underline text. The value of this attribute is either font-color or a color. If the value is font-color, the current text color is used for underlining. QString underLineColor = styleStack.property(KoXmlNS::style, "text-underline-color"); // OO 3.10.23, OASIS 14.4.31 if (!underLineColor.isEmpty() && underLineColor != "font-color") { setUnderlineColor(QColor(underLineColor)); } else if (underLineColor == "font-color") { setUnderlineColor(QColor()); } const QString textLineThroughType(styleStack.property(KoXmlNS::style, "text-line-through-type")); const QString textLineThroughStyle(styleStack.property(KoXmlNS::style, "text-line-through-style")); if (!textLineThroughType.isEmpty() || !textLineThroughStyle.isEmpty()) { // OASIS 14.4.7 KoCharacterStyle::LineStyle throughStyle; LineType throughType; importOdfLine(textLineThroughType,textLineThroughStyle, throughStyle, throughType); setStrikeOutStyle(throughStyle); setStrikeOutType(throughType); const QString textLineThroughText(styleStack.property(KoXmlNS::style, "text-line-through-text")); if (!textLineThroughText.isEmpty()) { setStrikeOutText(textLineThroughText); } } const QString textLineThroughWidth(styleStack.property(KoXmlNS::style, "text-line-through-width")); if (!textLineThroughWidth.isEmpty()) { qreal throughWidth; LineWeight throughWeight; parseOdfLineWidth(textLineThroughWidth, throughWeight, throughWidth); setStrikeOutWidth(throughWeight, throughWidth); } const QString lineThroughColor(styleStack.property(KoXmlNS::style, "text-line-through-color")); // OO 3.10.23, OASIS 14.4.31 if (!lineThroughColor.isEmpty() && lineThroughColor != "font-color") { setStrikeOutColor(QColor(lineThroughColor)); } const QString lineThroughMode(styleStack.property(KoXmlNS::style, "text-line-through-mode")); if (lineThroughMode == "continuous") { setStrikeOutMode(ContinuousLineMode); } else if (lineThroughMode == "skip-white-space") { setStrikeOutMode(SkipWhiteSpaceLineMode); } const QString textPosition(styleStack.property(KoXmlNS::style, "text-position")); if (!textPosition.isEmpty()) { // OO 3.10.7 if (textPosition.startsWith(QLatin1String("super"))) setVerticalAlignment(QTextCharFormat::AlignSuperScript); else if (textPosition.startsWith(QLatin1String("sub"))) setVerticalAlignment(QTextCharFormat::AlignSubScript); else { QRegExp re("(-?[\\d.]+)%.*"); if (re.exactMatch(textPosition)) { - int percent = re.capturedTexts()[1].toInt(); + int percent = re.capturedTexts().at(1).toInt(); if (percent > 0) setVerticalAlignment(QTextCharFormat::AlignSuperScript); else if (percent < 0) setVerticalAlignment(QTextCharFormat::AlignSubScript); else // set explicit to overwrite inherited text-position's setVerticalAlignment(QTextCharFormat::AlignNormal); } } } // The fo:font-variant attribute provides the option to display text as small capitalized letters. const QString textVariant(styleStack.property(KoXmlNS::fo, "font-variant")); if (!textVariant.isEmpty()) { if (textVariant == "small-caps") setFontCapitalization(QFont::SmallCaps); else if (textVariant == "normal") setFontCapitalization(QFont::MixedCase); } // The fo:text-transform attribute specifies text transformations to uppercase, lowercase, and capitalization. else { const QString textTransform(styleStack.property(KoXmlNS::fo, "text-transform")); if (!textTransform.isEmpty()) { if (textTransform == "uppercase") setFontCapitalization(QFont::AllUppercase); else if (textTransform == "lowercase") setFontCapitalization(QFont::AllLowercase); else if (textTransform == "capitalize") setFontCapitalization(QFont::Capitalize); else if (textTransform == "none") setFontCapitalization(QFont::MixedCase); } } const QString foLanguage(styleStack.property(KoXmlNS::fo, "language")); if (!foLanguage.isEmpty()) { setLanguage(foLanguage); } const QString foCountry(styleStack.property(KoXmlNS::fo, "country")); if (!foCountry.isEmpty()) { setCountry(foCountry); } // The fo:background-color attribute specifies the background color of a paragraph. const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color")); if (!bgcolor.isEmpty()) { QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } // The style:use-window-font-color attribute specifies whether or not the window foreground color should be as used as the foreground color for a light background color and white for a dark background color. const QString useWindowFont(styleStack.property(KoXmlNS::style, "use-window-font-color")); if (!useWindowFont.isEmpty()) { setFontAutoColor(useWindowFont == "true"); } const QString letterKerning(styleStack.property( KoXmlNS::style, "letter-kerning")); if (!letterKerning.isEmpty()) { setFontKerning(letterKerning == "true"); } const QString letterSpacing(styleStack.property(KoXmlNS::fo, "letter-spacing")); if ((!letterSpacing.isEmpty()) && (letterSpacing != "normal")) { qreal space = KoUnit::parseValue(letterSpacing); setFontLetterSpacing(space); } const QString textOutline(styleStack.property(KoXmlNS::style, "text-outline")); if (!textOutline.isEmpty()) { if (textOutline == "true") { setTextOutline(QPen((foreground().style() != Qt::NoBrush)?foreground():QBrush(Qt::black) , 0)); setForeground(Qt::transparent); } else { setTextOutline(QPen(Qt::NoPen)); } } const QString textRotationAngle(styleStack.property(KoXmlNS::style, "text-rotation-angle")); if (!textRotationAngle.isEmpty()) { setTextRotationAngle(KoUnit::parseAngle(textRotationAngle)); } const QString textRotationScale(styleStack.property(KoXmlNS::style, "text-rotation-scale")); if (!textRotationScale.isEmpty()) { setTextRotationScale(stringToRotationScale(textRotationScale)); } const QString textScale(styleStack.property(KoXmlNS::style, "text-scale")); if (!textScale.isEmpty()) { const int scale = (textScale.endsWith('%') ? textScale.left(textScale.length()-1) : textScale).toInt(); setTextScale(scale); } const QString textShadow(styleStack.property(KoXmlNS::fo, "text-shadow")); if (!textShadow.isEmpty()) { KoShadowStyle shadow; if (shadow.loadOdf(textShadow)) setTextShadow(shadow); } const QString textCombine(styleStack.property(KoXmlNS::style, "text-combine")); if (!textCombine.isEmpty()) { if (textCombine == "letters") setTextCombine(TextCombineLetters); else if (textCombine == "lines") setTextCombine(TextCombineLines); else if (textCombine == "none") setTextCombine(NoTextCombine); } const QString textCombineEndChar(styleStack.property(KoXmlNS::style, "text-combine-end-char")); if (!textCombineEndChar.isEmpty()) { setTextCombineEndChar(textCombineEndChar.at(0)); } const QString textCombineStartChar(styleStack.property(KoXmlNS::style, "text-combine-start-char")); if (!textCombineStartChar.isEmpty()) { setTextCombineStartChar(textCombineStartChar.at(0)); } const QString fontRelief(styleStack.property(KoXmlNS::style, "font-relief")); if (!fontRelief.isEmpty()) { if (fontRelief == "none") setFontRelief(KoCharacterStyle::NoRelief); else if (fontRelief == "embossed") setFontRelief(KoCharacterStyle::Embossed); else if (fontRelief == "engraved") setFontRelief(KoCharacterStyle::Engraved); } const QString fontEmphasize(styleStack.property(KoXmlNS::style, "text-emphasize")); if (!fontEmphasize.isEmpty()) { QString style, position; QStringList parts = fontEmphasize.split(' '); style = parts[0]; if (parts.length() > 1) position = parts[1]; if (style == "none") { setTextEmphasizeStyle(NoEmphasis); } else if (style == "accent") { setTextEmphasizeStyle(AccentEmphasis); } else if (style == "circle") { setTextEmphasizeStyle(CircleEmphasis); } else if (style == "disc") { setTextEmphasizeStyle(DiscEmphasis); } else if (style == "dot") { setTextEmphasizeStyle(DotEmphasis); } if (position == "below") { setTextEmphasizePosition(EmphasisBelow); } else if (position == "above") { setTextEmphasizePosition(EmphasisAbove); } } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenate")) setHasHyphenation(styleStack.property(KoXmlNS::fo, "hyphenate") == "true"); if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-remain-char-count")) { bool ok = false; int count = styleStack.property(KoXmlNS::fo, "hyphenation-remain-char-count").toInt(&ok); if (ok) setHyphenationRemainCharCount(count); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-push-char-count")) { bool ok = false; int count = styleStack.property(KoXmlNS::fo, "hyphenation-push-char-count").toInt(&ok); if (ok) setHyphenationPushCharCount(count); } if (styleStack.hasProperty(KoXmlNS::style, "text-blinking")) { setBlinking(styleStack.property(KoXmlNS::style, "text-blinking") == "true"); } //TODO #if 0 /* Missing properties: style:font-style-name, 3.10.11 - can be ignored, says DV, the other ways to specify a font are more precise fo:letter-spacing, 3.10.16 - not implemented in kotext style:text-relief, 3.10.20 - not implemented in kotext style:text-blinking, 3.10.27 - not implemented in kotext IIRC style:text-combine, 3.10.29/30 - not implemented, see http://www.w3.org/TR/WD-i18n-format/ style:text-emphasis, 3.10.31 - not implemented in kotext style:text-scale, 3.10.33 - not implemented in kotext style:text-rotation-angle, 3.10.34 - not implemented in kotext (kpr rotates whole objects) style:text-rotation-scale, 3.10.35 - not implemented in kotext (kpr rotates whole objects) style:punctuation-wrap, 3.10.36 - not implemented in kotext */ d->m_underLineWidth = 1.0; generateKey(); addRef(); #endif } bool KoCharacterStyle::operator==(const KoCharacterStyle &other) const { return compareCharacterProperties(other); } bool KoCharacterStyle::operator!=(const KoCharacterStyle &other) const { return !compareCharacterProperties(other); } bool KoCharacterStyle::compareCharacterProperties(const KoCharacterStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } void KoCharacterStyle::removeDuplicates(const KoCharacterStyle &other) { // In case the current style doesn't have the flag UseWindowFontColor set but the other has it set and they use the same color // remove duplicates will remove the color. However to make it work correctly we need to store the color with the style so it // will be loaded again. We don't store a use-window-font-color="false" as that is not compatible to the way OO/LO does work. // So save the color and restore it after the remove duplicates QBrush brush; if (other.d->propertyBoolean(KoCharacterStyle::UseWindowFontColor) && !d->propertyBoolean(KoCharacterStyle::UseWindowFontColor)) { brush = foreground(); } // this properties should need to be kept if there is a font family defined as these are only evaluated if there is also a font family int keepProperties[] = { QTextFormat::FontStyleHint, QTextFormat::FontFixedPitch, KoCharacterStyle::FontCharset }; QMap keep; for (unsigned int i = 0; i < sizeof(keepProperties)/sizeof(*keepProperties); ++i) { if (hasProperty(keepProperties[i])) { keep.insert(keepProperties[i], value(keepProperties[i])); } } this->d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); if (brush.style() != Qt::NoBrush) { setForeground(brush); } // in case the char style has any of the following properties it also needs to have the fontFamily as otherwise // these values will be ignored when loading according to the odf spec if (!hasProperty(QTextFormat::FontFamily)) { if (hasProperty(QTextFormat::FontStyleHint) || hasProperty(QTextFormat::FontFixedPitch) || hasProperty(KoCharacterStyle::FontCharset)) { QString fontFamily = other.fontFamily(); if (!fontFamily.isEmpty()) { setFontFamily(fontFamily); } } } else { for (QMap::const_iterator it(keep.constBegin()); it != keep.constEnd(); ++it) { this->d->stylesPrivate.add(it.key(), it.value()); } } } void KoCharacterStyle::removeDuplicates(const QTextCharFormat &otherFormat) { KoCharacterStyle other(otherFormat); removeDuplicates(other); } void KoCharacterStyle::remove(int key) { d->stylesPrivate.remove(key); } bool KoCharacterStyle::isEmpty() const { return d->stylesPrivate.isEmpty(); } void KoCharacterStyle::saveOdf(KoGenStyle &style) const { if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList keys = d->stylesPrivate.keys(); foreach(int key, keys) { if (key == QTextFormat::FontWeight) { bool ok = false; int boldness = d->stylesPrivate.value(key).toInt(&ok); if (ok) { if (boldness == QFont::Normal) { style.addProperty("fo:font-weight", "normal", KoGenStyle::TextType); } else if (boldness == QFont::Bold) { style.addProperty("fo:font-weight", "bold", KoGenStyle::TextType); } else { // Remember : Qt and CSS/XSL doesn't have the same scale. Its 100-900 instead of Qts 0-100 style.addProperty("fo:font-weight", qBound(10, boldness, 90) * 10, KoGenStyle::TextType); } } } else if (key == QTextFormat::FontItalic) { if (d->stylesPrivate.value(key).toBool()) { style.addProperty("fo:font-style", "italic", KoGenStyle::TextType); } else { style.addProperty("fo:font-style", "normal", KoGenStyle::TextType); } } else if (key == QTextFormat::FontFamily) { QString fontFamily = d->stylesPrivate.value(key).toString(); style.addProperty("fo:font-family", fontFamily, KoGenStyle::TextType); } else if (key == QTextFormat::FontFixedPitch) { bool fixedPitch = d->stylesPrivate.value(key).toBool(); style.addProperty("style:font-pitch", fixedPitch ? "fixed" : "variable", KoGenStyle::TextType); // if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } else if (key == QTextFormat::FontStyleHint) { bool ok = false; int styleHint = d->stylesPrivate.value(key).toInt(&ok); if (ok) { QString generic = exportOdfFontStyleHint((QFont::StyleHint) styleHint); if (!generic.isEmpty()) { style.addProperty("style:font-family-generic", generic, KoGenStyle::TextType); } // if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } } else if (key == QTextFormat::FontKerning) { style.addProperty("style:letter-kerning", fontKerning() ? "true" : "false", KoGenStyle::TextType); } else if (key == QTextFormat::FontCapitalization) { switch (fontCapitalization()) { case QFont::SmallCaps: style.addProperty("fo:font-variant", "small-caps", KoGenStyle::TextType); break; case QFont::MixedCase: style.addProperty("fo:font-variant", "normal", KoGenStyle::TextType); style.addProperty("fo:text-transform", "none", KoGenStyle::TextType); break; case QFont::AllUppercase: style.addProperty("fo:text-transform", "uppercase", KoGenStyle::TextType); break; case QFont::AllLowercase: style.addProperty("fo:text-transform", "lowercase", KoGenStyle::TextType); break; case QFont::Capitalize: style.addProperty("fo:text-transform", "capitalize", KoGenStyle::TextType); break; } } else if (key == OverlineStyle) { bool ok = false; int styleId = d->stylesPrivate.value(key).toInt(&ok); if (ok) { style.addProperty("style:text-overline-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType); } } else if (key == OverlineType) { bool ok = false; int type = d->stylesPrivate.value(key).toInt(&ok); if (ok) { style.addProperty("style:text-overline-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType); } } else if (key == OverlineColor) { QColor color = d->stylesPrivate.value(key).value(); if (color.isValid()) style.addProperty("style:text-overline-color", color.name(), KoGenStyle::TextType); else style.addProperty("style:text-overline-color", "font-color", KoGenStyle::TextType); } else if (key == OverlineMode) { bool ok = false; int mode = d->stylesPrivate.value(key).toInt(&ok); if (ok) { style.addProperty("style:text-overline-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType); } } else if (key == OverlineWidth) { KoCharacterStyle::LineWeight weight; qreal width; overlineWidth(weight, width); style.addProperty("style:text-overline-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType); } else if (key == UnderlineStyle) { bool ok = false; int styleId = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-underline-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType); } else if (key == UnderlineType) { bool ok = false; int type = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-underline-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType); } else if (key == QTextFormat::TextUnderlineColor) { QColor color = d->stylesPrivate.value(key).value(); if (color.isValid()) style.addProperty("style:text-underline-color", color.name(), KoGenStyle::TextType); else style.addProperty("style:text-underline-color", "font-color", KoGenStyle::TextType); } else if (key == UnderlineMode) { bool ok = false; int mode = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-underline-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType); } else if (key == UnderlineWidth) { KoCharacterStyle::LineWeight weight; qreal width; underlineWidth(weight, width); style.addProperty("style:text-underline-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType); } else if (key == StrikeOutStyle) { bool ok = false; int styleId = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-line-through-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType); } else if (key == StrikeOutType) { bool ok = false; int type = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-line-through-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType); } else if (key == StrikeOutText) { style.addProperty("style:text-line-through-text", d->stylesPrivate.value(key).toString(), KoGenStyle::TextType); } else if (key == StrikeOutColor) { QColor color = d->stylesPrivate.value(key).value(); if (color.isValid()) style.addProperty("style:text-line-through-color", color.name(), KoGenStyle::TextType); } else if (key == StrikeOutMode) { bool ok = false; int mode = d->stylesPrivate.value(key).toInt(&ok); if (ok) style.addProperty("style:text-line-through-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType); } else if (key == StrikeOutWidth) { KoCharacterStyle::LineWeight weight; qreal width; strikeOutWidth(weight, width); style.addProperty("style:text-line-through-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType); } else if (key == QTextFormat::BackgroundBrush) { QBrush brush = d->stylesPrivate.value(key).value(); if (brush.style() == Qt::NoBrush) style.addProperty("fo:background-color", "transparent", KoGenStyle::TextType); else style.addProperty("fo:background-color", brush.color().name(), KoGenStyle::TextType); } else if (key == QTextFormat::ForegroundBrush) { QBrush brush = d->stylesPrivate.value(key).value(); if (brush.style() != Qt::NoBrush) { style.addProperty("fo:color", brush.color().name(), KoGenStyle::TextType); } } else if (key == KoCharacterStyle::UseWindowFontColor) { bool use = d->stylesPrivate.value(key).toBool(); style.addProperty("style:use-window-font-color", use ? "true" : "false", KoGenStyle::TextType); } else if (key == QTextFormat::TextVerticalAlignment) { if (verticalAlignment() == QTextCharFormat::AlignSuperScript) style.addProperty("style:text-position", "super", KoGenStyle::TextType); else if (verticalAlignment() == QTextCharFormat::AlignSubScript) style.addProperty("style:text-position", "sub", KoGenStyle::TextType); else if (d->stylesPrivate.contains(QTextFormat::TextVerticalAlignment)) // no superscript or subscript style.addProperty("style:text-position", "0% 100%", KoGenStyle::TextType); } else if (key == QTextFormat::FontPointSize) { // when there is percentageFontSize!=100% property ignore the fontSize property and store the percentage property if ( (!hasProperty(KoCharacterStyle::PercentageFontSize)) || (percentageFontSize()==100)) style.addPropertyPt("fo:font-size", fontPointSize(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::PercentageFontSize) { if(percentageFontSize()!=100) { style.addProperty("fo:font-size", QString::number(percentageFontSize()) + '%', KoGenStyle::TextType); } } else if (key == KoCharacterStyle::Country) { style.addProperty("fo:country", d->stylesPrivate.value(KoCharacterStyle::Country).toString(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::Language) { style.addProperty("fo:language", d->stylesPrivate.value(KoCharacterStyle::Language).toString(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::FontLetterSpacing) { qreal space = fontLetterSpacing(); style.addPropertyPt("fo:letter-spacing", space, KoGenStyle::TextType); } else if (key == QTextFormat::TextOutline) { QPen outline = textOutline(); style.addProperty("style:text-outline", outline.style() == Qt::NoPen ? "false" : "true", KoGenStyle::TextType); } else if (key == KoCharacterStyle::FontCharset) { style.addProperty("style:font-charset", d->stylesPrivate.value(KoCharacterStyle::FontCharset).toString(), KoGenStyle::TextType); // if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextRotationAngle) { style.addProperty("style:text-rotation-angle", QString::number(textRotationAngle()), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextRotationScale) { RotationScale scale = textRotationScale(); style.addProperty("style:text-rotation-scale", rotationScaleToString(scale), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextScale) { int scale = textScale(); style.addProperty("style:text-scale", QString::number(scale) + '%', KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextShadow) { KoShadowStyle shadow = textShadow(); style.addProperty("fo:text-shadow", shadow.saveOdf(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextCombine) { KoCharacterStyle::TextCombineType textCombineType = textCombine(); switch (textCombineType) { case KoCharacterStyle::NoTextCombine: style.addProperty("style:text-combine", "none", KoGenStyle::TextType); break; case KoCharacterStyle::TextCombineLetters: style.addProperty("style:text-combine", "letters", KoGenStyle::TextType); break; case KoCharacterStyle::TextCombineLines: style.addProperty("style:text-combine", "lines", KoGenStyle::TextType); break; } } else if (key == KoCharacterStyle::TextCombineEndChar) { style.addProperty("style:text-combine-end-char", textCombineEndChar(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::TextCombineStartChar) { style.addProperty("style:text-combine-start-char", textCombineStartChar(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::FontRelief) { KoCharacterStyle::ReliefType relief = fontRelief(); switch (relief) { case KoCharacterStyle::NoRelief: style.addProperty("style:font-relief", "none", KoGenStyle::TextType); break; case KoCharacterStyle::Embossed: style.addProperty("style:font-relief", "embossed", KoGenStyle::TextType); break; case KoCharacterStyle::Engraved: style.addProperty("style:font-relief", "engraved", KoGenStyle::TextType); break; } } else if (key == KoCharacterStyle::TextEmphasizeStyle) { KoCharacterStyle::EmphasisStyle emphasisStyle = textEmphasizeStyle(); KoCharacterStyle::EmphasisPosition position = textEmphasizePosition(); QString odfEmphasis; switch (emphasisStyle) { case KoCharacterStyle::NoEmphasis: odfEmphasis = "none"; break; case KoCharacterStyle::AccentEmphasis: odfEmphasis = "accent"; break; case KoCharacterStyle::CircleEmphasis: odfEmphasis = "circle"; break; case KoCharacterStyle::DiscEmphasis: odfEmphasis = "disc"; break; case KoCharacterStyle::DotEmphasis: odfEmphasis = "dot"; break; } if (hasProperty(KoCharacterStyle::TextEmphasizePosition)) { if (position == KoCharacterStyle::EmphasisAbove) odfEmphasis += " above"; else odfEmphasis += " below"; } style.addProperty("style:text-emphasize", odfEmphasis, KoGenStyle::TextType); } else if (key == KoCharacterStyle::HasHyphenation) { if (hasHyphenation()) style.addProperty("fo:hyphenate", "true", KoGenStyle::TextType); else style.addProperty("fo:hyphenate", "false", KoGenStyle::TextType); } else if (key == KoCharacterStyle::HyphenationPushCharCount) { style.addProperty("fo:hyphenation-push-char-count", hyphenationPushCharCount(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::HyphenationRemainCharCount) { style.addProperty("fo:hyphenation-remain-char-count", hyphenationRemainCharCount(), KoGenStyle::TextType); } else if (key == KoCharacterStyle::Blink) { style.addProperty("style:text-blinking", blinking(), KoGenStyle::TextType); } } //TODO: font name and family } QVariant KoCharacterStyle::value(int key) const { QVariant variant = d->stylesPrivate.value(key); if (variant.isNull()) { if (d->parentStyle) variant = d->parentStyle->value(key); else if (d->defaultStyle) variant = d->defaultStyle->value(key); } return variant; } void KoCharacterStyle::removeHardCodedDefaults() { d->hardCodedDefaultStyle.clearAll(); } diff --git a/libs/text/styles/KoParagraphStyle.cpp b/libs/text/styles/KoParagraphStyle.cpp index 2597a8916c7..e81af9eef9d 100644 --- a/libs/text/styles/KoParagraphStyle.cpp +++ b/libs/text/styles/KoParagraphStyle.cpp @@ -1,2369 +1,2369 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2007,2008 Sebastian Sauer * Copyright (C) 2007-2011 Pierre Ducroquet * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Gopalakrishna Bhat A * * 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. */ #include "KoParagraphStyle.h" #include "KoList.h" #include "KoListStyle.h" #include "KoTextBlockData.h" #include "KoStyleManager.h" #include "KoListLevelProperties.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include "Styles_p.h" #include "KoTextDocument.h" #include "TextDebug.h" #include #include #include #include #include #include #include #include #include #include #include static int compareTabs(KoText::Tab &tab1, KoText::Tab &tab2) { return tab1.position < tab2.position; } class Q_DECL_HIDDEN KoParagraphStyle::Private { public: Private() : parentStyle(0), defaultStyle(0), list(0), m_inUse(false) {} ~Private() { } void setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } void ensureDefaults(QTextBlockFormat &format) { if (defaultStyle) { QMap props = defaultStyle->d->stylesPrivate.properties(); QMap::const_iterator it = props.constBegin(); while (it != props.constEnd()) { if (!it.value().isNull() && !format.hasProperty(it.key())) { format.setProperty(it.key(), it.value()); } ++it; } } } QString name; KoParagraphStyle *parentStyle; KoParagraphStyle *defaultStyle; KoList *list; StylePrivate stylesPrivate; bool m_inUse; }; KoParagraphStyle::KoParagraphStyle(QObject *parent) : KoCharacterStyle(parent), d(new Private()) { } KoParagraphStyle::KoParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &blockCharFormat, QObject *parent) : KoCharacterStyle(blockCharFormat, parent), d(new Private()) { d->stylesPrivate = blockFormat.properties(); } KoParagraphStyle *KoParagraphStyle::fromBlock(const QTextBlock &block, QObject *parent) { QTextBlockFormat blockFormat = block.blockFormat(); QTextCursor cursor(block); KoParagraphStyle *answer = new KoParagraphStyle(blockFormat, cursor.blockCharFormat(), parent); int listStyleId = blockFormat.intProperty(ListStyleId); KoStyleManager *sm = KoTextDocument(block.document()).styleManager(); if (KoListStyle *listStyle = sm->listStyle(listStyleId)) { answer->setListStyle(listStyle->clone(answer)); } else if (block.textList()) { KoListLevelProperties llp = KoListLevelProperties::fromTextList(block.textList()); KoListStyle *listStyle = new KoListStyle(answer); listStyle->setLevelProperties(llp); answer->setListStyle(listStyle); } return answer; } KoParagraphStyle::~KoParagraphStyle() { delete d; } KoCharacterStyle::Type KoParagraphStyle::styleType() const { return KoCharacterStyle::ParagraphStyle; } void KoParagraphStyle::setDefaultStyle(KoParagraphStyle *defaultStyle) { d->defaultStyle = defaultStyle; KoCharacterStyle::setDefaultStyle(defaultStyle); } void KoParagraphStyle::setParentStyle(KoParagraphStyle *parent) { d->parentStyle = parent; KoCharacterStyle::setParentStyle(parent); } void KoParagraphStyle::setProperty(int key, const QVariant &value) { if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoParagraphStyle::remove(int key) { d->stylesPrivate.remove(key); } QVariant KoParagraphStyle::value(int key) const { QVariant var = d->stylesPrivate.value(key); if (var.isNull()) { if (d->parentStyle) return d->parentStyle->value(key); else if (d->defaultStyle) return d->defaultStyle->value(key); } return var; } bool KoParagraphStyle::hasProperty(int key) const { return d->stylesPrivate.contains(key); } qreal KoParagraphStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QTextLength KoParagraphStyle::propertyLength(int key) const { QVariant variant = value(key); if (variant.isNull()) return QTextLength(QTextLength::FixedLength, 0.0); if (!variant.canConvert()) { // Fake support, for compatibility sake if (variant.canConvert()) { return QTextLength(QTextLength::FixedLength, variant.toReal()); } warnText << "This should never happen : requested property can't be converted to QTextLength"; return QTextLength(QTextLength::FixedLength, 0.0); } return variant.value(); } int KoParagraphStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoParagraphStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoParagraphStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoParagraphStyle::applyStyle(QTextBlockFormat &format) const { if (d->parentStyle) { d->parentStyle->applyStyle(format); } const QMap props = d->stylesPrivate.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { if (it.key() == QTextBlockFormat::BlockLeftMargin) { format.setLeftMargin(leftMargin()); } else if (it.key() == QTextBlockFormat::BlockRightMargin) { format.setRightMargin(rightMargin()); } else if (it.key() == QTextBlockFormat::TextIndent) { format.setTextIndent(textIndent()); } else { format.setProperty(it.key(), it.value()); } ++it; } if ((hasProperty(DefaultOutlineLevel)) && (!format.hasProperty(OutlineLevel))) { format.setProperty(OutlineLevel, defaultOutlineLevel()); } emit styleApplied(this); d->m_inUse = true; } void KoParagraphStyle::applyStyle(QTextBlock &block, bool applyListStyle) const { QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); applyStyle(format); d->ensureDefaults(format); cursor.setBlockFormat(format); KoCharacterStyle::applyStyle(block); if (applyListStyle) { applyParagraphListStyle(block, format); } } bool KoParagraphStyle::isApplied() const { return d->m_inUse; } void KoParagraphStyle::applyParagraphListStyle(QTextBlock &block, const QTextBlockFormat &blockFormat) const { //gopalakbhat: We need to differentiate between normal styles and styles with outline(part of heading) //Styles part of outline: We ignore the listStyle()( even if this is a valid in ODF; even LibreOffice does the same) // since we can specify all info required in text:outline-style //Normal styles: we use the listStyle() if (blockFormat.hasProperty(OutlineLevel)) { if (! d->list) { if (! KoTextDocument(block.document()).headingList()) { if (KoTextDocument(block.document()).styleManager() && KoTextDocument(block.document()).styleManager()->outlineStyle()) { d->list = new KoList(block.document(), KoTextDocument(block.document()).styleManager()->outlineStyle()); KoTextDocument(block.document()).setHeadingList(d->list); } } else { d->list = KoTextDocument(block.document()).headingList(); } } if (d->list) { d->list->applyStyle(block, KoTextDocument(block.document()).styleManager()->outlineStyle(), blockFormat.intProperty(OutlineLevel)); } } else { if (listStyle()) { if (!d->list) { d->list = new KoList(block.document(), listStyle()); } //FIXME: Gopalakrishna Bhat A: This condition should never happen. // i.e. d->list->style() should always be in sync with the listStyle() if (d->list->style() != listStyle()) { d->list->setStyle(listStyle()); } d->list->add(block, listLevel()); } else { if (block.textList()) block.textList()->remove(block); KoTextBlockData data(block); data.setCounterWidth(-1); } } } void KoParagraphStyle::unapplyStyle(QTextBlock &block) const { if (d->parentStyle) d->parentStyle->unapplyStyle(block); QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); if (keys[i] == QTextBlockFormat::BlockLeftMargin) { if (leftMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::BlockRightMargin) { if (rightMargin() == format.property(keys[i])) format.clearProperty(keys[i]); } else if (keys[i] == QTextBlockFormat::TextIndent) { if (textIndent() == format.property(keys[i])) format.clearProperty(keys[i]); } else { if (variant == format.property(keys[i])) format.clearProperty(keys[i]); } } format.clearProperty(KoParagraphStyle::OutlineLevel); cursor.setBlockFormat(format); KoCharacterStyle::unapplyStyle(block); if (listStyle() && block.textList()) { // TODO check its the same one? KoList::remove(block); } if (d->list && block.textList()) { // TODO check its the same one? KoList::remove(block); } } void KoParagraphStyle::setLineHeightPercent(qreal lineHeight) { setProperty(PercentLineHeight, lineHeight); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightPercent() const { return propertyInt(PercentLineHeight); } void KoParagraphStyle::setLineHeightAbsolute(qreal height) { setProperty(FixedLineHeight, height); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); remove(NormalLineHeight); } qreal KoParagraphStyle::lineHeightAbsolute() const { return propertyDouble(FixedLineHeight); } void KoParagraphStyle::setMinimumLineHeight(const QTextLength &height) { setProperty(FixedLineHeight, 0.0); setProperty(PercentLineHeight, 0); setProperty(MinimumLineHeight, height); remove(NormalLineHeight); } qreal KoParagraphStyle::minimumLineHeight() const { if (parentStyle()) return propertyLength(MinimumLineHeight).value(parentStyle()->minimumLineHeight()); else return propertyLength(MinimumLineHeight).value(0); } void KoParagraphStyle::setLineSpacing(qreal spacing) { setProperty(LineSpacing, spacing); remove(NormalLineHeight); } qreal KoParagraphStyle::lineSpacing() const { return propertyDouble(LineSpacing); } void KoParagraphStyle::setLineSpacingFromFont(bool on) { setProperty(LineSpacingFromFont, on); remove(NormalLineHeight); } bool KoParagraphStyle::lineSpacingFromFont() const { return propertyBoolean(LineSpacingFromFont); } void KoParagraphStyle::setNormalLineHeight() { setProperty(NormalLineHeight, true); setProperty(PercentLineHeight, 0); setProperty(FixedLineHeight, 0.0); setProperty(MinimumLineHeight, QTextLength(QTextLength::FixedLength, 0.0)); setProperty(LineSpacing, 0.0); } bool KoParagraphStyle::hasNormalLineHeight() const { return propertyBoolean(NormalLineHeight); } void KoParagraphStyle::setAlignLastLine(Qt::Alignment alignment) { setProperty(AlignLastLine, (int) alignment); } Qt::Alignment KoParagraphStyle::alignLastLine() const { if (hasProperty(AlignLastLine)) return static_cast(propertyInt(AlignLastLine)); // Hum, that doesn't sound right ! return alignment(); } void KoParagraphStyle::setWidowThreshold(int lines) { setProperty(WidowThreshold, lines); } int KoParagraphStyle::widowThreshold() const { return propertyInt(WidowThreshold); } void KoParagraphStyle::setOrphanThreshold(int lines) { setProperty(OrphanThreshold, lines); } int KoParagraphStyle::orphanThreshold() const { return propertyInt(OrphanThreshold); } void KoParagraphStyle::setDropCaps(bool on) { setProperty(DropCaps, on); } bool KoParagraphStyle::dropCaps() const { return propertyBoolean(DropCaps); } void KoParagraphStyle::setDropCapsLength(int characters) { setProperty(DropCapsLength, characters); } int KoParagraphStyle::dropCapsLength() const { return propertyInt(DropCapsLength); } void KoParagraphStyle::setDropCapsLines(int lines) { setProperty(DropCapsLines, lines); } int KoParagraphStyle::dropCapsLines() const { return propertyInt(DropCapsLines); } void KoParagraphStyle::setDropCapsDistance(qreal distance) { setProperty(DropCapsDistance, distance); } qreal KoParagraphStyle::dropCapsDistance() const { return propertyDouble(DropCapsDistance); } void KoParagraphStyle::setDropCapsTextStyleId(int id) { setProperty(KoParagraphStyle::DropCapsTextStyle, id); } int KoParagraphStyle::dropCapsTextStyleId() const { return propertyInt(KoParagraphStyle::DropCapsTextStyle); } void KoParagraphStyle::setFollowDocBaseline(bool on) { setProperty(FollowDocBaseline, on); } bool KoParagraphStyle::followDocBaseline() const { return propertyBoolean(FollowDocBaseline); } void KoParagraphStyle::setBreakBefore(KoText::KoTextBreakProperty value) { setProperty(BreakBefore, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakBefore() const { return static_cast(propertyInt(BreakBefore)); } void KoParagraphStyle::setBreakAfter(KoText::KoTextBreakProperty value) { setProperty(BreakAfter, value); } KoText::KoTextBreakProperty KoParagraphStyle::breakAfter() const { return static_cast(propertyInt(BreakAfter)); } void KoParagraphStyle::setLeftPadding(qreal padding) { setProperty(LeftPadding, padding); } qreal KoParagraphStyle::leftPadding() const { return propertyDouble(LeftPadding); } void KoParagraphStyle::setTopPadding(qreal padding) { setProperty(TopPadding, padding); } qreal KoParagraphStyle::topPadding() const { return propertyDouble(TopPadding); } void KoParagraphStyle::setRightPadding(qreal padding) { setProperty(RightPadding, padding); } qreal KoParagraphStyle::rightPadding() const { return propertyDouble(RightPadding); } void KoParagraphStyle::setBottomPadding(qreal padding) { setProperty(BottomPadding, padding); } qreal KoParagraphStyle::bottomPadding() const { return propertyDouble(BottomPadding); } void KoParagraphStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } void KoParagraphStyle::setLeftBorderWidth(qreal width) { setProperty(LeftBorderWidth, width); } qreal KoParagraphStyle::leftBorderWidth() const { return propertyDouble(LeftBorderWidth); } void KoParagraphStyle::setLeftInnerBorderWidth(qreal width) { setProperty(LeftInnerBorderWidth, width); } qreal KoParagraphStyle::leftInnerBorderWidth() const { return propertyDouble(LeftInnerBorderWidth); } void KoParagraphStyle::setLeftBorderSpacing(qreal width) { setProperty(LeftBorderSpacing, width); } qreal KoParagraphStyle::leftBorderSpacing() const { return propertyDouble(LeftBorderSpacing); } void KoParagraphStyle::setLeftBorderStyle(KoBorder::BorderStyle style) { setProperty(LeftBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::leftBorderStyle() const { return static_cast(propertyInt(LeftBorderStyle)); } void KoParagraphStyle::setLeftBorderColor(const QColor &color) { setProperty(LeftBorderColor, color); } QColor KoParagraphStyle::leftBorderColor() const { return propertyColor(LeftBorderColor); } void KoParagraphStyle::setTopBorderWidth(qreal width) { setProperty(TopBorderWidth, width); } qreal KoParagraphStyle::topBorderWidth() const { return propertyDouble(TopBorderWidth); } void KoParagraphStyle::setTopInnerBorderWidth(qreal width) { setProperty(TopInnerBorderWidth, width); } qreal KoParagraphStyle::topInnerBorderWidth() const { return propertyDouble(TopInnerBorderWidth); } void KoParagraphStyle::setTopBorderSpacing(qreal width) { setProperty(TopBorderSpacing, width); } qreal KoParagraphStyle::topBorderSpacing() const { return propertyDouble(TopBorderSpacing); } void KoParagraphStyle::setTopBorderStyle(KoBorder::BorderStyle style) { setProperty(TopBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::topBorderStyle() const { return static_cast(propertyInt(TopBorderStyle)); } void KoParagraphStyle::setTopBorderColor(const QColor &color) { setProperty(TopBorderColor, color); } QColor KoParagraphStyle::topBorderColor() const { return propertyColor(TopBorderColor); } void KoParagraphStyle::setRightBorderWidth(qreal width) { setProperty(RightBorderWidth, width); } qreal KoParagraphStyle::rightBorderWidth() const { return propertyDouble(RightBorderWidth); } void KoParagraphStyle::setRightInnerBorderWidth(qreal width) { setProperty(RightInnerBorderWidth, width); } qreal KoParagraphStyle::rightInnerBorderWidth() const { return propertyDouble(RightInnerBorderWidth); } void KoParagraphStyle::setRightBorderSpacing(qreal width) { setProperty(RightBorderSpacing, width); } qreal KoParagraphStyle::rightBorderSpacing() const { return propertyDouble(RightBorderSpacing); } void KoParagraphStyle::setRightBorderStyle(KoBorder::BorderStyle style) { setProperty(RightBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::rightBorderStyle() const { return static_cast(propertyInt(RightBorderStyle)); } void KoParagraphStyle::setRightBorderColor(const QColor &color) { setProperty(RightBorderColor, color); } QColor KoParagraphStyle::rightBorderColor() const { return propertyColor(RightBorderColor); } void KoParagraphStyle::setBottomBorderWidth(qreal width) { setProperty(BottomBorderWidth, width); } qreal KoParagraphStyle::bottomBorderWidth() const { return propertyDouble(BottomBorderWidth); } void KoParagraphStyle::setBottomInnerBorderWidth(qreal width) { setProperty(BottomInnerBorderWidth, width); } qreal KoParagraphStyle::bottomInnerBorderWidth() const { return propertyDouble(BottomInnerBorderWidth); } void KoParagraphStyle::setBottomBorderSpacing(qreal width) { setProperty(BottomBorderSpacing, width); } qreal KoParagraphStyle::bottomBorderSpacing() const { return propertyDouble(BottomBorderSpacing); } void KoParagraphStyle::setBottomBorderStyle(KoBorder::BorderStyle style) { setProperty(BottomBorderStyle, style); } KoBorder::BorderStyle KoParagraphStyle::bottomBorderStyle() const { return static_cast(propertyInt(BottomBorderStyle)); } void KoParagraphStyle::setBottomBorderColor(const QColor &color) { setProperty(BottomBorderColor, color); } QColor KoParagraphStyle::bottomBorderColor() const { return propertyColor(BottomBorderColor); } void KoParagraphStyle::setTopMargin(QTextLength topMargin) { setProperty(QTextFormat::BlockTopMargin, topMargin); } qreal KoParagraphStyle::topMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockTopMargin).value(parentStyle()->topMargin()); else return propertyLength(QTextFormat::BlockTopMargin).value(0); } void KoParagraphStyle::setBottomMargin(QTextLength margin) { setProperty(QTextFormat::BlockBottomMargin, margin); } qreal KoParagraphStyle::bottomMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockBottomMargin).value(parentStyle()->bottomMargin()); else return propertyLength(QTextFormat::BlockBottomMargin).value(0); } void KoParagraphStyle::setLeftMargin(QTextLength margin) { setProperty(QTextFormat::BlockLeftMargin, margin); } qreal KoParagraphStyle::leftMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockLeftMargin).value(parentStyle()->leftMargin()); else return propertyLength(QTextFormat::BlockLeftMargin).value(0); } void KoParagraphStyle::setRightMargin(QTextLength margin) { setProperty(QTextFormat::BlockRightMargin, margin); } qreal KoParagraphStyle::rightMargin() const { if (parentStyle()) return propertyLength(QTextFormat::BlockRightMargin).value(parentStyle()->rightMargin()); else return propertyLength(QTextFormat::BlockRightMargin).value(0); } void KoParagraphStyle::setMargin(QTextLength margin) { setTopMargin(margin); setBottomMargin(margin); setLeftMargin(margin); setRightMargin(margin); } void KoParagraphStyle::setAlignment(Qt::Alignment alignment) { setProperty(QTextFormat::BlockAlignment, (int) alignment); } Qt::Alignment KoParagraphStyle::alignment() const { return static_cast(propertyInt(QTextFormat::BlockAlignment)); } void KoParagraphStyle::setTextIndent(QTextLength margin) { setProperty(QTextFormat::TextIndent, margin); } qreal KoParagraphStyle::textIndent() const { if (parentStyle()) return propertyLength(QTextFormat::TextIndent).value(parentStyle()->textIndent()); else return propertyLength(QTextFormat::TextIndent).value(0); } void KoParagraphStyle::setAutoTextIndent(bool on) { setProperty(KoParagraphStyle::AutoTextIndent, on); } bool KoParagraphStyle::autoTextIndent() const { return propertyBoolean(KoParagraphStyle::AutoTextIndent); } void KoParagraphStyle::setNonBreakableLines(bool on) { setProperty(QTextFormat::BlockNonBreakableLines, on); } bool KoParagraphStyle::nonBreakableLines() const { return propertyBoolean(QTextFormat::BlockNonBreakableLines); } void KoParagraphStyle::setKeepWithNext(bool value) { setProperty(KeepWithNext, value); } bool KoParagraphStyle::keepWithNext() const { if (hasProperty(KeepWithNext)) return propertyBoolean(KeepWithNext); return false; } bool KoParagraphStyle::punctuationWrap() const { if (hasProperty(PunctuationWrap)) return propertyBoolean(PunctuationWrap); return false; } void KoParagraphStyle::setPunctuationWrap(bool value) { setProperty(PunctuationWrap, value); } KoParagraphStyle *KoParagraphStyle::parentStyle() const { return d->parentStyle; } void KoParagraphStyle::setNextStyle(int next) { setProperty(NextStyle, next); } int KoParagraphStyle::nextStyle() const { return propertyInt(NextStyle); } QString KoParagraphStyle::name() const { return d->name; } void KoParagraphStyle::setName(const QString &name) { if (name == d->name) return; d->name = name; KoCharacterStyle::setName(name); emit nameChanged(name); } int KoParagraphStyle::styleId() const { // duplicate some code to avoid getting the parents style id QVariant variant = d->stylesPrivate.value(StyleId); if (variant.isNull()) return 0; return variant.toInt(); } void KoParagraphStyle::setStyleId(int id) { setProperty(StyleId, id); if (nextStyle() == 0) setNextStyle(id); KoCharacterStyle::setStyleId(id); } QString KoParagraphStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoParagraphStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoParagraphStyle::setListStartValue(int value) { setProperty(ListStartValue, value); } int KoParagraphStyle::listStartValue() const { return propertyInt(ListStartValue); } void KoParagraphStyle::setRestartListNumbering(bool on) { setProperty(RestartListNumbering, on); } bool KoParagraphStyle::restartListNumbering() { return propertyBoolean(RestartListNumbering); } void KoParagraphStyle::setListLevel(int value) { setProperty(ListLevel, value); } int KoParagraphStyle::listLevel() const { return propertyInt(ListLevel); } void KoParagraphStyle::setOutlineLevel(int outline) { setProperty(OutlineLevel, outline); } int KoParagraphStyle::outlineLevel() const { return propertyInt(OutlineLevel); } void KoParagraphStyle::setDefaultOutlineLevel(int outline) { setProperty(DefaultOutlineLevel, outline); } int KoParagraphStyle::defaultOutlineLevel() const { return propertyInt(DefaultOutlineLevel); } bool KoParagraphStyle::lineNumbering() const { return propertyBoolean(LineNumbering); } void KoParagraphStyle::setLineNumbering(bool lineNumbering) { setProperty(LineNumbering, lineNumbering); } int KoParagraphStyle::lineNumberStartValue() const { return propertyInt(LineNumberStartValue); } void KoParagraphStyle::setLineNumberStartValue(int lineNumberStartValue) { setProperty(LineNumberStartValue, lineNumberStartValue); } void KoParagraphStyle::setIsListHeader(bool on) { setProperty(IsListHeader, on); } bool KoParagraphStyle::isListHeader() const { return propertyBoolean(IsListHeader); } KoListStyle *KoParagraphStyle::listStyle() const { QVariant variant = value(ParagraphListStyleId); if (variant.isNull()) return 0; return variant.value(); } void KoParagraphStyle::setListStyle(KoListStyle *style) { if (listStyle() == style) return; if (listStyle() && listStyle()->parent() == this) delete listStyle(); QVariant variant; KoListStyle *cloneStyle = 0; if (style) { cloneStyle = style->clone(); variant.setValue(cloneStyle); setProperty(ParagraphListStyleId, variant); } else { d->stylesPrivate.remove(ParagraphListStyleId); } } KoText::Direction KoParagraphStyle::textProgressionDirection() const { return static_cast(propertyInt(TextProgressionDirection)); } void KoParagraphStyle::setTextProgressionDirection(KoText::Direction dir) { setProperty(TextProgressionDirection, dir); } bool KoParagraphStyle::keepHyphenation() const { if (hasProperty(KeepHyphenation)) return propertyBoolean(KeepHyphenation); return false; } void KoParagraphStyle::setKeepHyphenation(bool value) { setProperty(KeepHyphenation, value); } int KoParagraphStyle::hyphenationLadderCount() const { if (hasProperty(HyphenationLadderCount)) return propertyInt(HyphenationLadderCount); return 0; } void KoParagraphStyle::setHyphenationLadderCount(int value) { setProperty(HyphenationLadderCount, value); } void KoParagraphStyle::setBackground(const QBrush &brush) { d->setProperty(QTextFormat::BackgroundBrush, brush); } void KoParagraphStyle::clearBackground() { d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush); } QBrush KoParagraphStyle::background() const { QVariant variant = d->stylesPrivate.value(QTextFormat::BackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } qreal KoParagraphStyle::backgroundTransparency() const { if (hasProperty(BackgroundTransparency)) return propertyDouble(BackgroundTransparency); return 0.0; } void KoParagraphStyle::setBackgroundTransparency(qreal transparency) { setProperty(BackgroundTransparency, transparency); } void KoParagraphStyle::setSnapToLayoutGrid(bool value) { setProperty(SnapToLayoutGrid, value); } bool KoParagraphStyle::snapToLayoutGrid() const { if (hasProperty(SnapToLayoutGrid)) return propertyBoolean(SnapToLayoutGrid); return false; } bool KoParagraphStyle::joinBorder() const { if (hasProperty(JoinBorder)) return propertyBoolean(JoinBorder); return true; //default is true } void KoParagraphStyle::setJoinBorder(bool value) { setProperty(JoinBorder, value); } int KoParagraphStyle::pageNumber() const { return propertyInt(PageNumber); } void KoParagraphStyle::setPageNumber(int pageNumber) { if (pageNumber >= 0) setProperty(PageNumber, pageNumber); } bool KoParagraphStyle::automaticWritingMode() const { if (hasProperty(AutomaticWritingMode)) return propertyBoolean(AutomaticWritingMode); return true; } void KoParagraphStyle::setAutomaticWritingMode(bool value) { setProperty(AutomaticWritingMode, value); } void KoParagraphStyle::setVerticalAlignment(KoParagraphStyle::VerticalAlign value) { setProperty(VerticalAlignment, value); } KoParagraphStyle::VerticalAlign KoParagraphStyle::verticalAlignment() const { if (hasProperty(VerticalAlignment)) return (VerticalAlign) propertyInt(VerticalAlignment); return VAlignAuto; } void KoParagraphStyle::setShadow(const KoShadowStyle &shadow) { d->setProperty(Shadow, QVariant::fromValue(shadow)); } KoShadowStyle KoParagraphStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoParagraphStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext, bool loadParents) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString())); if (!name.isEmpty()) { setName(name); } else { setName(element->attributeNS(KoXmlNS::style, "name", QString())); } QString family = element->attributeNS(KoXmlNS::style, "family", "paragraph"); context.styleStack().save(); if (loadParents) { context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent } else { context.styleStack().push(*element); } context.styleStack().setTypeProperties("text"); // load the style:text-properties KoCharacterStyle::loadOdfProperties(scontext); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } if (element->hasAttributeNS(KoXmlNS::style, "default-outline-level")) { bool ok = false; int level = element->attributeNS(KoXmlNS::style, "default-outline-level").toInt(&ok); if (ok) setDefaultOutlineLevel(level); } context.styleStack().setTypeProperties("paragraph"); // load all style attributes from "style:paragraph-properties" loadOdfProperties(scontext); // load the KoParagraphStyle from the stylestack context.styleStack().restore(); } struct ParagraphBorderData { enum Values {Style = 1, Color = 2, Width = 4}; ParagraphBorderData() : values(0) {} ParagraphBorderData(const ParagraphBorderData &other) : values(other.values), style(other.style), color(other.color), width(other.width) {} // flag defining which data is set int values; KoBorder::BorderStyle style; QColor color; qreal width; ///< in pt }; /// Parses the @p dataString as value defined by CSS2 §7.29.3 "border" /// Adds parsed data to the data as set for @p defaultParagraphBorderData. /// Returns the enriched border data on success, the original @p defaultParagraphBorderData on a parsing error static ParagraphBorderData parseParagraphBorderData(const QString &dataString, const ParagraphBorderData &defaultParagraphBorderData) { const QStringList bv = dataString.split(QLatin1Char(' '), QString::SkipEmptyParts); // too many items? ignore complete value if (bv.count() > 3) { return defaultParagraphBorderData; } ParagraphBorderData borderData = defaultParagraphBorderData; int parsedValues = 0; ///< used to track what is read from the given string foreach(const QString &v, bv) { // try style if (! (parsedValues & ParagraphBorderData::Style)) { bool success = false; KoBorder::BorderStyle style = KoBorder::odfBorderStyle(v, &success); // workaround for not yet supported "hidden" if (! success && (v == QLatin1String("hidden"))) { // map to "none" for now TODO: KoBorder needs to support "hidden" style = KoBorder::BorderNone; success = true; } if (success) { borderData.style = style; borderData.values |= ParagraphBorderData::Style; parsedValues |= ParagraphBorderData::Style; continue; } } // try color if (! (parsedValues & ParagraphBorderData::Color)) { const QColor color(v); if (color.isValid()) { borderData.color = color; borderData.values |= ParagraphBorderData::Color; parsedValues |= ParagraphBorderData::Color; continue; } } // try width if (! (parsedValues & ParagraphBorderData::Width)) { const qreal width = KoUnit::parseValue(v); if (width >= 0.0) { borderData.width = width; borderData.values |= ParagraphBorderData::Width; parsedValues |= ParagraphBorderData::Width; continue; } } // still here? found a value which cannot be parsed return defaultParagraphBorderData; } return borderData; } void KoParagraphStyle::loadOdfProperties(KoShapeLoadingContext &scontext) { KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack(); // in 1.6 this was defined at KoParagLayout::loadOasisParagLayout(KoParagLayout&, KoOasisContext&) const QString writingMode(styleStack.property(KoXmlNS::style, "writing-mode")); if (!writingMode.isEmpty()) { setTextProgressionDirection(KoText::directionFromString(writingMode)); } // Alignment const QString textAlign(styleStack.property(KoXmlNS::fo, "text-align")); if (!textAlign.isEmpty()) { setAlignment(KoText::alignmentFromString(textAlign)); } // Spacing (padding) const QString padding(styleStack.property(KoXmlNS::fo, "padding")); if (!padding.isEmpty()) { setPadding(KoUnit::parseValue(padding)); } const QString paddingLeft(styleStack.property(KoXmlNS::fo, "padding-left" )); if (!paddingLeft.isEmpty()) { setLeftPadding(KoUnit::parseValue(paddingLeft)); } const QString paddingRight(styleStack.property(KoXmlNS::fo, "padding-right" )); if (!paddingRight.isEmpty()) { setRightPadding(KoUnit::parseValue(paddingRight)); } const QString paddingTop(styleStack.property(KoXmlNS::fo, "padding-top" )); if (!paddingTop.isEmpty()) { setTopPadding(KoUnit::parseValue(paddingTop)); } const QString paddingBottom(styleStack.property(KoXmlNS::fo, "padding-bottom" )); if (!paddingBottom.isEmpty()) { setBottomPadding(KoUnit::parseValue(paddingBottom)); } // Indentation (margin) const QString margin(styleStack.property(KoXmlNS::fo, "margin")); if (!margin.isEmpty()) { setMargin(KoText::parseLength(margin)); } const QString marginLeft(styleStack.property(KoXmlNS::fo, "margin-left" )); if (!marginLeft.isEmpty()) { setLeftMargin(KoText::parseLength(marginLeft)); } const QString marginRight(styleStack.property(KoXmlNS::fo, "margin-right" )); if (!marginRight.isEmpty()) { setRightMargin(KoText::parseLength(marginRight)); } const QString marginTop(styleStack.property(KoXmlNS::fo, "margin-top")); if (!marginTop.isEmpty()) { setTopMargin(KoText::parseLength(marginTop)); } const QString marginBottom(styleStack.property(KoXmlNS::fo, "margin-bottom")); if (!marginBottom.isEmpty()) { setBottomMargin(KoText::parseLength(marginBottom)); } // Automatic Text indent // OOo is not assuming this. Commenting this line thus allow more OpenDocuments to be supported, including a // testcase from the ODF test suite. See §15.5.18 in the spec. //if ( hasMarginLeft || hasMarginRight ) { // style:auto-text-indent takes precedence const QString autoTextIndent(styleStack.property(KoXmlNS::style, "auto-text-indent")); if (!autoTextIndent.isEmpty()) { setAutoTextIndent(autoTextIndent == "true"); } if (autoTextIndent != "true" || autoTextIndent.isEmpty()) { const QString textIndent(styleStack.property(KoXmlNS::fo, "text-indent")); if (!textIndent.isEmpty()) { setTextIndent(KoText::parseLength(textIndent)); } } //} // Line spacing QString lineHeight(styleStack.property(KoXmlNS::fo, "line-height")); if (!lineHeight.isEmpty()) { if (lineHeight != "normal") { if (lineHeight.indexOf('%') > -1) { bool ok; const qreal percent = lineHeight.remove('%').toDouble(&ok); if (ok) { setLineHeightPercent(percent); } } else { // fixed value is between 0.0201in and 3.9402in const qreal value = KoUnit::parseValue(lineHeight, -1.0); if (value >= 0.0) { setLineHeightAbsolute(value); } } } else { setNormalLineHeight(); } } else { const QString lineSpacing(styleStack.property(KoXmlNS::style, "line-spacing")); if (!lineSpacing.isEmpty()) { // 3.11.3 setLineSpacing(KoUnit::parseValue(lineSpacing)); } } // 15.5.30 - 31 if (styleStack.hasProperty(KoXmlNS::text, "number-lines")) { setLineNumbering(styleStack.property(KoXmlNS::text, "number-lines", "false") == "true"); } if (styleStack.hasProperty(KoXmlNS::text, "line-number")) { bool ok; int startValue = styleStack.property(KoXmlNS::text, "line-number").toInt(&ok); if (ok) { setLineNumberStartValue(startValue); } } const QString lineHeightAtLeast(styleStack.property(KoXmlNS::style, "line-height-at-least")); if (!lineHeightAtLeast.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { // 3.11.2 setMinimumLineHeight(KoText::parseLength(lineHeightAtLeast)); } // Line-height-at-least is mutually exclusive with absolute line-height const QString fontIndependentLineSpacing(styleStack.property(KoXmlNS::style, "font-independent-line-spacing")); if (!fontIndependentLineSpacing.isEmpty() && !propertyBoolean(NormalLineHeight) && lineHeightAbsolute() == 0) { setLineSpacingFromFont(fontIndependentLineSpacing == "true"); } // Tabulators const QString tabStopDistance(styleStack.property(KoXmlNS::style, "tab-stop-distance")); if (!tabStopDistance.isEmpty()) { qreal stopDistance = KoUnit::parseValue(tabStopDistance); if (stopDistance >= 0) setTabStopDistance(stopDistance); } KoXmlElement tabStops(styleStack.childNode(KoXmlNS::style, "tab-stops")); if (!tabStops.isNull()) { // 3.11.10 QVector tabList; KoXmlElement tabStop; forEachElement(tabStop, tabStops) { if(tabStop.localName() != "tab-stop") continue; // Tab position KoText::Tab tab; tab.position = KoUnit::parseValue(tabStop.attributeNS(KoXmlNS::style, "position", QString())); //debugText << "tab position " << tab.position; // Tab stop positions in the XML are relative to the left-margin // Equivalently, relative to the left end of our textshape // Tab type (left/right/center/char) const QString type = tabStop.attributeNS(KoXmlNS::style, "type", QString()); if (type == "center") tab.type = QTextOption::CenterTab; else if (type == "right") tab.type = QTextOption::RightTab; else if (type == "char") { tab.type = QTextOption::DelimiterTab; tab.delimiter = QChar('.'); } else //if ( type == "left" ) tab.type = QTextOption::LeftTab; // Tab delimiter char if (tab.type == QTextOption::DelimiterTab) { QString delimiterChar = tabStop.attributeNS(KoXmlNS::style, "char", QString()); // single character if (!delimiterChar.isEmpty()) { tab.delimiter = delimiterChar[0]; } else { // this is invalid. fallback to left-tabbing. tab.type = QTextOption::LeftTab; } } QString leaderType = tabStop.attributeNS(KoXmlNS::style, "leader-type", QString()); if (leaderType.isEmpty() || leaderType == "none") { tab.leaderType = KoCharacterStyle::NoLineType; } else { if (leaderType == "single") tab.leaderType = KoCharacterStyle::SingleLine; else if (leaderType == "double") tab.leaderType = KoCharacterStyle::DoubleLine; // change default leaderStyle tab.leaderStyle = KoCharacterStyle::SolidLine; } QString leaderStyle = tabStop.attributeNS(KoXmlNS::style, "leader-style", QString()); if (leaderStyle == "none") tab.leaderStyle = KoCharacterStyle::NoLineStyle; else if (leaderStyle == "solid") tab.leaderStyle = KoCharacterStyle::SolidLine; else if (leaderStyle == "dotted") tab.leaderStyle = KoCharacterStyle::DottedLine; else if (leaderStyle == "dash") tab.leaderStyle = KoCharacterStyle::DashLine; else if (leaderStyle == "long-dash") tab.leaderStyle = KoCharacterStyle::LongDashLine; else if (leaderStyle == "dot-dash") tab.leaderStyle = KoCharacterStyle::DotDashLine; else if (leaderStyle == "dot-dot-dash") tab.leaderStyle = KoCharacterStyle::DotDotDashLine; else if (leaderStyle == "wave") tab.leaderStyle = KoCharacterStyle::WaveLine; if (tab.leaderType == KoCharacterStyle::NoLineType && tab.leaderStyle != KoCharacterStyle::NoLineStyle) { if (leaderType == "none") // if leaderType was explicitly specified as none, but style was not none, // make leaderType override (ODF1.1 §15.5.11) tab.leaderStyle = KoCharacterStyle::NoLineStyle; else // if leaderType was implicitly assumed none, but style was not none, // make leaderStyle override tab.leaderType = KoCharacterStyle::SingleLine; } QString leaderColor = tabStop.attributeNS(KoXmlNS::style, "leader-color", QString()); if (leaderColor != "font-color") tab.leaderColor = QColor(leaderColor); // if invalid color (the default), will use text color QString width = tabStop.attributeNS(KoXmlNS::style, "leader-width", QString()); if (width.isEmpty() || width == "auto") tab.leaderWeight = KoCharacterStyle::AutoLineWeight; else if (width == "normal") tab.leaderWeight = KoCharacterStyle::NormalLineWeight; else if (width == "bold") tab.leaderWeight = KoCharacterStyle::BoldLineWeight; else if (width == "thin") tab.leaderWeight = KoCharacterStyle::ThinLineWeight; else if (width == "dash") tab.leaderWeight = KoCharacterStyle::DashLineWeight; else if (width == "medium") tab.leaderWeight = KoCharacterStyle::MediumLineWeight; else if (width == "thick") tab.leaderWeight = KoCharacterStyle::ThickLineWeight; else if (width.endsWith('%')) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; - tab.leaderWidth = width.mid(0, width.length() - 1).toDouble(); + tab.leaderWidth = width.midRef(0, width.length() - 1).toDouble(); } else if (width[width.length()-1].isNumber()) { tab.leaderWeight = KoCharacterStyle::PercentLineWeight; tab.leaderWidth = 100 * width.toDouble(); } else { tab.leaderWeight = KoCharacterStyle::LengthLineWeight; tab.leaderWidth = KoUnit::parseValue(width); } tab.leaderText = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); #if 0 else { // Fallback: convert leaderChar's unicode value QString leaderChar = tabStop.attributeNS(KoXmlNS::style, "leader-text", QString()); if (!leaderChar.isEmpty()) { QChar ch = leaderChar[0]; switch (ch.latin1()) { case '.': tab.filling = TF_DOTS; break; case '-': case '_': // TODO in Words: differentiate --- and ___ tab.filling = TF_LINE; break; default: // Words doesn't have support for "any char" as filling. break; } } } #endif tabList.append(tab); } //for setTabPositions(tabList); } #if 0 layout.joinBorder = !(styleStack.property(KoXmlNS::style, "join-border") == "false"); #endif // Borders // The border attribute is actually three attributes in one string, all optional // and with no given order. Also there is a hierachy, first the common for all // sides and then overwrites per side, while in the code only the sides are stored. // So first the common data border is fetched, then this is overwritten per // side and the result stored. const QString border(styleStack.property(KoXmlNS::fo, "border")); const ParagraphBorderData borderData = parseParagraphBorderData(border, ParagraphBorderData()); const QString borderLeft(styleStack.property(KoXmlNS::fo, "border-left")); const ParagraphBorderData leftParagraphBorderData = parseParagraphBorderData(borderLeft, borderData); if (leftParagraphBorderData.values & ParagraphBorderData::Width) { setLeftBorderWidth(leftParagraphBorderData.width); } if (leftParagraphBorderData.values & ParagraphBorderData::Style) { setLeftBorderStyle(leftParagraphBorderData.style); } if (leftParagraphBorderData.values & ParagraphBorderData::Color) { setLeftBorderColor(leftParagraphBorderData.color); } const QString borderTop(styleStack.property(KoXmlNS::fo, "border-top")); const ParagraphBorderData topParagraphBorderData = parseParagraphBorderData(borderTop, borderData); if (topParagraphBorderData.values & ParagraphBorderData::Width) { setTopBorderWidth(topParagraphBorderData.width); } if (topParagraphBorderData.values & ParagraphBorderData::Style) { setTopBorderStyle(topParagraphBorderData.style); } if (topParagraphBorderData.values & ParagraphBorderData::Color) { setTopBorderColor(topParagraphBorderData.color); } const QString borderRight(styleStack.property(KoXmlNS::fo, "border-right")); const ParagraphBorderData rightParagraphBorderData = parseParagraphBorderData(borderRight, borderData); if (rightParagraphBorderData.values & ParagraphBorderData::Width) { setRightBorderWidth(rightParagraphBorderData.width); } if (rightParagraphBorderData.values & ParagraphBorderData::Style) { setRightBorderStyle(rightParagraphBorderData.style); } if (rightParagraphBorderData.values & ParagraphBorderData::Color) { setRightBorderColor(rightParagraphBorderData.color); } const QString borderBottom(styleStack.property(KoXmlNS::fo, "border-bottom")); const ParagraphBorderData bottomParagraphBorderData = parseParagraphBorderData(borderBottom, borderData); if (bottomParagraphBorderData.values & ParagraphBorderData::Width) { setBottomBorderWidth(bottomParagraphBorderData.width); } if (bottomParagraphBorderData.values & ParagraphBorderData::Style) { setBottomBorderStyle(bottomParagraphBorderData.style); } if (bottomParagraphBorderData.values & ParagraphBorderData::Color) { setBottomBorderColor(bottomParagraphBorderData.color); } const QString borderLineWidthLeft(styleStack.property(KoXmlNS::style, "border-line-width", "left")); if (!borderLineWidthLeft.isEmpty()) { QStringList blw = borderLineWidthLeft.split(' ', QString::SkipEmptyParts); setLeftInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setLeftBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setLeftBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthTop(styleStack.property(KoXmlNS::style, "border-line-width", "top")); if (!borderLineWidthTop.isEmpty()) { QStringList blw = borderLineWidthTop.split(' ', QString::SkipEmptyParts); setTopInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setTopBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setTopBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthRight(styleStack.property(KoXmlNS::style, "border-line-width", "right")); if (!borderLineWidthRight.isEmpty()) { QStringList blw = borderLineWidthRight.split(' ', QString::SkipEmptyParts); setRightInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setRightBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setRightBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } const QString borderLineWidthBottom(styleStack.property(KoXmlNS::style, "border-line-width", "bottom")); if (!borderLineWidthBottom.isEmpty()) { QStringList blw = borderLineWidthBottom.split(' ', QString::SkipEmptyParts); setBottomInnerBorderWidth(KoUnit::parseValue(blw.value(0), 0.1)); setBottomBorderSpacing(KoUnit::parseValue(blw.value(1), 1.0)); setBottomBorderWidth(KoUnit::parseValue(blw.value(2), 0.1)); } // drop caps KoXmlElement dropCap(styleStack.childNode(KoXmlNS::style, "drop-cap")); if (!dropCap.isNull()) { setDropCaps(true); const QString length = dropCap.attributeNS(KoXmlNS::style, "length", QString("1")); if (length.toLower() == "word") { setDropCapsLength(0); // 0 indicates drop caps of the whole first word } else { int l = length.toInt(); if (l > 0) // somefiles may use this to turn dropcaps off setDropCapsLength(length.toInt()); else setDropCaps(false); } const QString lines = dropCap.attributeNS(KoXmlNS::style, "lines", QString("1")); setDropCapsLines(lines.toInt()); const qreal distance = KoUnit::parseValue(dropCap.attributeNS(KoXmlNS::style, "distance", QString())); setDropCapsDistance(distance); const QString dropstyle = dropCap.attributeNS(KoXmlNS::style, "style-name"); if (! dropstyle.isEmpty()) { KoSharedLoadingData *sharedData = scontext.sharedData(KOTEXT_SHARED_LOADING_ID); KoTextSharedLoadingData *textSharedData = 0; textSharedData = dynamic_cast(sharedData); if (textSharedData) { KoCharacterStyle *cs = textSharedData->characterStyle(dropstyle, true); if (cs) setDropCapsTextStyleId(cs->styleId()); } } } // The fo:break-before and fo:break-after attributes insert a page or column break before or after a paragraph. const QString breakBefore(styleStack.property(KoXmlNS::fo, "break-before")); if (!breakBefore.isEmpty()) { setBreakBefore(KoText::textBreakFromString(breakBefore)); } const QString breakAfter(styleStack.property(KoXmlNS::fo, "break-after")); if (!breakAfter.isEmpty()) { setBreakAfter(KoText::textBreakFromString(breakAfter)); } const QString keepTogether(styleStack.property(KoXmlNS::fo, "keep-together")); if (!keepTogether.isEmpty()) { setNonBreakableLines(keepTogether == "always"); } const QString rawPageNumber(styleStack.property(KoXmlNS::style, "page-number")); if (!rawPageNumber.isEmpty()) { if (rawPageNumber == "auto") { setPageNumber(0); } else { bool ok; int number = rawPageNumber.toInt(&ok); if (ok) setPageNumber(number); } } // The fo:background-color attribute specifies the background color of a paragraph. const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color")); if (!bgcolor.isEmpty()) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") brush.setStyle(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format } setBackground(brush); } if (styleStack.hasProperty(KoXmlNS::style, "background-transparency")) { QString transparency = styleStack.property(KoXmlNS::style, "background-transparency"); bool ok = false; qreal transparencyValue = transparency.remove('%').toDouble(&ok); if (ok) { setBackgroundTransparency(transparencyValue/100); } } if (styleStack.hasProperty(KoXmlNS::style, "snap-to-layout-grid")) { setSnapToLayoutGrid(styleStack.property(KoXmlNS::style, "snap-to-layout-grid") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "register-true")) { setRegisterTrue(styleStack.property(KoXmlNS::style, "register-true") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "join-border")) { setJoinBorder(styleStack.property(KoXmlNS::style, "join-border") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "line-break")) { setStrictLineBreak(styleStack.property(KoXmlNS::style, "line-break") == "strict"); } // Support for an old non-standard OpenOffice attribute that we still find in too many documents... if (styleStack.hasProperty(KoXmlNS::text, "enable-numbering")) { setProperty(ForceDisablingList, styleStack.property(KoXmlNS::text, "enable-numbering") == "false"); } if (styleStack.hasProperty(KoXmlNS::fo, "orphans")) { bool ok = false; int orphans = styleStack.property(KoXmlNS::fo, "orphans").toInt(&ok); if (ok) setOrphanThreshold(orphans); } if (styleStack.hasProperty(KoXmlNS::fo, "widows")) { bool ok = false; int widows = styleStack.property(KoXmlNS::fo, "widows").toInt(&ok); if (ok) setWidowThreshold(widows); } if (styleStack.hasProperty(KoXmlNS::style, "justify-single-word")) { setJustifySingleWord(styleStack.property(KoXmlNS::style, "justify-single-word") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "writing-mode-automatic")) { setAutomaticWritingMode(styleStack.property(KoXmlNS::style, "writing-mode-automatic") == "true"); } if (styleStack.hasProperty(KoXmlNS::fo, "text-align-last")) { setAlignLastLine(KoText::alignmentFromString(styleStack.property(KoXmlNS::fo, "text-align-last"))); } if (styleStack.hasProperty(KoXmlNS::fo, "keep-with-next")) { setKeepWithNext(styleStack.property(KoXmlNS::fo, "keep-with-next") == "always"); } if (styleStack.hasProperty(KoXmlNS::style, "text-autospace")) { const QString autoSpace = styleStack.property(KoXmlNS::style, "text-autospace"); if (autoSpace == "none") setTextAutoSpace(NoAutoSpace); else if (autoSpace == "ideograph-alpha") setTextAutoSpace(IdeographAlpha); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-keep")) { setKeepHyphenation(styleStack.property(KoXmlNS::fo, "hyphenation-keep") == "page"); } if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-ladder-count")) { QString ladderCount = styleStack.property(KoXmlNS::fo, "hyphenation-ladder-count"); if (ladderCount == "no-limit") setHyphenationLadderCount(0); else { bool ok; int value = ladderCount.toInt(&ok); if ((ok) && (value > 0)) setHyphenationLadderCount(value); } } if (styleStack.hasProperty(KoXmlNS::style, "punctuation-wrap")) { setPunctuationWrap(styleStack.property(KoXmlNS::style, "punctuation-wrap") == "simple"); } if (styleStack.hasProperty(KoXmlNS::style, "vertical-align")) { const QString valign = styleStack.property(KoXmlNS::style, "vertical-align"); if (valign == "auto") setVerticalAlignment(VAlignAuto); else if (valign == "baseline") setVerticalAlignment(VAlignBaseline); else if (valign == "bottom") setVerticalAlignment(VAlignBottom); else if (valign == "middle") setVerticalAlignment(VAlignMiddle); else if (valign == "top") setVerticalAlignment(VAlignTop); } if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) setShadow(shadow); } //following properties KoParagraphStyle provides us are not handled now; // LineSpacingFromFont, // FollowDocBaseline, } void KoParagraphStyle::setTabPositions(const QVector &tabs) { QVector newTabs = tabs; qSort(newTabs.begin(), newTabs.end(), compareTabs); QList list; foreach(const KoText::Tab &tab, tabs) { QVariant v; v.setValue(tab); list.append(v); } setProperty(TabPositions, list); } QVector KoParagraphStyle::tabPositions() const { QVector answer; QVariant variant = value(TabPositions); if (variant.isNull()) { return answer; } // TODO: why is this stored as QList and not as QVector variant? const QList tabPositions = qvariant_cast >(variant); answer.reserve(tabPositions.size()); foreach(const QVariant &tab, tabPositions) { answer.append(tab.value()); } return answer; } void KoParagraphStyle::setTabStopDistance(qreal value) { setProperty(TabStopDistance, value); } qreal KoParagraphStyle::tabStopDistance() const { return propertyDouble(TabStopDistance); } bool KoParagraphStyle::registerTrue() const { if (hasProperty(RegisterTrue)) return propertyBoolean(RegisterTrue); return false; } void KoParagraphStyle::setRegisterTrue(bool value) { setProperty(RegisterTrue, value); } bool KoParagraphStyle::strictLineBreak() const { if (hasProperty(StrictLineBreak)) return propertyBoolean(StrictLineBreak); return false; } void KoParagraphStyle::setStrictLineBreak(bool value) { setProperty(StrictLineBreak, value); } bool KoParagraphStyle::justifySingleWord() const { if (hasProperty(JustifySingleWord)) return propertyBoolean(JustifySingleWord); return false; } void KoParagraphStyle::setJustifySingleWord(bool value) { setProperty(JustifySingleWord, value); } void KoParagraphStyle::setTextAutoSpace(KoParagraphStyle::AutoSpace value) { setProperty(TextAutoSpace, value); } KoParagraphStyle::AutoSpace KoParagraphStyle::textAutoSpace() const { if (hasProperty(TextAutoSpace)) return static_cast(propertyInt(TextAutoSpace)); return NoAutoSpace; } void KoParagraphStyle::copyProperties(const KoParagraphStyle *style) { d->stylesPrivate = style->d->stylesPrivate; setName(style->name()); // make sure we emit property change KoCharacterStyle::copyProperties(style); d->parentStyle = style->d->parentStyle; d->defaultStyle = style->d->defaultStyle; } KoParagraphStyle *KoParagraphStyle::clone(QObject *parent) const { KoParagraphStyle *newStyle = new KoParagraphStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoParagraphStyle::compareParagraphProperties(const KoParagraphStyle &other) const { return other.d->stylesPrivate == d->stylesPrivate; } bool KoParagraphStyle::operator==(const KoParagraphStyle &other) const { if (!compareParagraphProperties(other)) return false; if (!compareCharacterProperties(other)) return false; return true; } void KoParagraphStyle::removeDuplicates(const KoParagraphStyle &other) { d->stylesPrivate.removeDuplicates(other.d->stylesPrivate); KoCharacterStyle::removeDuplicates(other); } void KoParagraphStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) const { bool writtenLineSpacing = false; KoCharacterStyle::saveOdf(style); if (listStyle()) { KoGenStyle liststyle(KoGenStyle::ListStyle); listStyle()->saveOdf(liststyle, context); QString name(QString(QUrl::toPercentEncoding(listStyle()->name(), "", " ")).replace('%', '_')); if (name.isEmpty()) name = 'L'; style.addAttribute("style:list-style-name", context.mainStyles().insert(liststyle, name, KoGenStyles::DontAddNumberToName)); } // only custom style have a displayname. automatic styles don't have a name set. if (!d->name.isEmpty() && !style.isDefaultStyle()) { style.addAttribute("style:display-name", d->name); } QList keys = d->stylesPrivate.keys(); if (keys.contains(KoParagraphStyle::LeftPadding) && keys.contains(KoParagraphStyle::RightPadding) && keys.contains(KoParagraphStyle::TopPadding) && keys.contains(KoParagraphStyle::BottomPadding)) { if ((leftPadding() == rightPadding()) && (topPadding() == bottomPadding()) && (rightPadding() == topPadding())) { style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::ParagraphType); keys.removeOne(KoParagraphStyle::LeftPadding); keys.removeOne(KoParagraphStyle::RightPadding); keys.removeOne(KoParagraphStyle::TopPadding); keys.removeOne(KoParagraphStyle::BottomPadding); } } if (keys.contains(QTextFormat::BlockLeftMargin) && keys.contains(QTextFormat::BlockRightMargin) && keys.contains(QTextFormat::BlockBottomMargin) && keys.contains(QTextFormat::BlockTopMargin)) { if ((leftMargin() == rightMargin()) && (topMargin() == bottomMargin()) && (rightMargin() == topMargin())) { style.addPropertyLength("fo:margin", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); keys.removeOne(QTextFormat::BlockLeftMargin); keys.removeOne(QTextFormat::BlockRightMargin); keys.removeOne(QTextFormat::BlockTopMargin); keys.removeOne(QTextFormat::BlockBottomMargin); } } foreach (int key, keys) { if (key == QTextFormat::BlockAlignment) { int alignValue = 0; bool ok = false; alignValue = d->stylesPrivate.value(key).toInt(&ok); if (ok) { Qt::Alignment alignment = (Qt::Alignment) alignValue; QString align = KoText::alignmentToString(alignment); if (!align.isEmpty()) style.addProperty("fo:text-align", align, KoGenStyle::ParagraphType); } } else if (key == KoParagraphStyle::AlignLastLine) { QString align = KoText::alignmentToString(alignLastLine()); if (!align.isEmpty()) style.addProperty("fo:text-align-last", align, KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextProgressionDirection) { style.addProperty("style:writing-mode", KoText::directionToString(textProgressionDirection()), KoGenStyle::ParagraphType); } else if (key == LineNumbering) { style.addProperty("text:number-lines", lineNumbering()); } else if (key == PageNumber) { if (pageNumber() == 0) style.addProperty("style:page-number", "auto", KoGenStyle::ParagraphType); else style.addProperty("style:page-number", pageNumber(), KoGenStyle::ParagraphType); } else if (key == LineNumberStartValue) { style.addProperty("text:line-number", lineNumberStartValue()); } else if (key == BreakAfter) { style.addProperty("fo:break-after", KoText::textBreakToString(breakAfter()), KoGenStyle::ParagraphType); } else if (key == BreakBefore) { style.addProperty("fo:break-before", KoText::textBreakToString(breakBefore()), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockNonBreakableLines) { if (nonBreakableLines()) { style.addProperty("fo:keep-together", "always", KoGenStyle::ParagraphType); } else { style.addProperty("fo:keep-together", "auto", KoGenStyle::ParagraphType); } } else if (key == QTextFormat::BackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::ParagraphType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BackgroundTransparency) { style.addProperty("style:background-transparency", QString("%1%").arg(backgroundTransparency() * 100), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::SnapToLayoutGrid) { style.addProperty("style:snap-to-layout-grid", snapToLayoutGrid(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::JustifySingleWord) { style.addProperty("style:justify-single-word", justifySingleWord(), KoGenStyle::ParagraphType); } else if (key == RegisterTrue) { style.addProperty("style:register-true", registerTrue(), KoGenStyle::ParagraphType); } else if (key == StrictLineBreak) { if (strictLineBreak()) style.addProperty("style:line-break", "strict", KoGenStyle::ParagraphType); else style.addProperty("style:line-break", "normal", KoGenStyle::ParagraphType); } else if (key == JoinBorder) { style.addProperty("style:join-border", joinBorder(), KoGenStyle::ParagraphType); } else if (key == OrphanThreshold) { style.addProperty("fo:orphans", orphanThreshold(), KoGenStyle::ParagraphType); } else if (key == WidowThreshold) { style.addProperty("fo:widows", widowThreshold(), KoGenStyle::ParagraphType); // Padding } else if (key == KoParagraphStyle::LeftPadding) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::RightPadding) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TopPadding) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::BottomPadding) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::ParagraphType); // Margin } else if (key == QTextFormat::BlockLeftMargin) { style.addPropertyLength("fo:margin-left", propertyLength(QTextFormat::BlockLeftMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockRightMargin) { style.addPropertyLength("fo:margin-right", propertyLength(QTextFormat::BlockRightMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockTopMargin) { style.addPropertyLength("fo:margin-top", propertyLength(QTextFormat::BlockTopMargin), KoGenStyle::ParagraphType); } else if (key == QTextFormat::BlockBottomMargin) { style.addPropertyLength("fo:margin-bottom", propertyLength(QTextFormat::BlockBottomMargin), KoGenStyle::ParagraphType); // Line spacing } else if ( key == KoParagraphStyle::MinimumLineHeight || key == KoParagraphStyle::LineSpacing || key == KoParagraphStyle::PercentLineHeight || key == KoParagraphStyle::FixedLineHeight || key == KoParagraphStyle::LineSpacingFromFont) { if (key == KoParagraphStyle::MinimumLineHeight && propertyLength(MinimumLineHeight).rawValue() != 0) { style.addPropertyLength("style:line-height-at-least", propertyLength(MinimumLineHeight), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacing && lineSpacing() != 0) { style.addPropertyPt("style:line-spacing", lineSpacing(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::PercentLineHeight && lineHeightPercent() != 0) { style.addProperty("fo:line-height", QString("%1%").arg(lineHeightPercent()), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::FixedLineHeight && lineHeightAbsolute() != 0) { style.addPropertyPt("fo:line-height", lineHeightAbsolute(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } else if (key == KoParagraphStyle::LineSpacingFromFont && lineHeightAbsolute() == 0) { style.addProperty("style:font-independent-line-spacing", lineSpacingFromFont(), KoGenStyle::ParagraphType); writtenLineSpacing = true; } // } else if (key == QTextFormat::TextIndent) { style.addPropertyLength("fo:text-indent", propertyLength(QTextFormat::TextIndent), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::AutoTextIndent) { style.addProperty("style:auto-text-indent", autoTextIndent(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TabStopDistance) { style.addPropertyPt("style:tab-stop-distance", tabStopDistance(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::MasterPageName) { style.addAttribute("style:master-page-name", masterPageName()); } else if (key == KoParagraphStyle::DefaultOutlineLevel) { style.addAttribute("style:default-outline-level", defaultOutlineLevel()); } else if (key == KoParagraphStyle::AutomaticWritingMode) { style.addProperty("style:writing-mode-automatic", automaticWritingMode(), KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::TextAutoSpace) { if (textAutoSpace() == NoAutoSpace) style.addProperty("style:text-autospace", "none", KoGenStyle::ParagraphType); else if (textAutoSpace() == IdeographAlpha) style.addProperty("style:text-autospace", "ideograph-alpha", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepWithNext) { if (keepWithNext()) style.addProperty("fo:keep-with-next", "always", KoGenStyle::ParagraphType); else style.addProperty("fo:keep-with-next", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::KeepHyphenation) { if (keepHyphenation()) style.addProperty("fo:hyphenation-keep", "page", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-keep", "auto", KoGenStyle::ParagraphType); } else if (key == KoParagraphStyle::HyphenationLadderCount) { int value = hyphenationLadderCount(); if (value == 0) style.addProperty("fo:hyphenation-ladder-count", "no-limit", KoGenStyle::ParagraphType); else style.addProperty("fo:hyphenation-ladder-count", value, KoGenStyle::ParagraphType); } else if (key == PunctuationWrap) { if (punctuationWrap()) style.addProperty("style:punctuation-wrap", "simple", KoGenStyle::ParagraphType); else style.addProperty("style:punctuation-wrap", "hanging", KoGenStyle::ParagraphType); } else if (key == VerticalAlignment) { VerticalAlign valign = verticalAlignment(); if (valign == VAlignAuto) style.addProperty("style:vertical-align", "auto", KoGenStyle::ParagraphType); else if (valign == VAlignBaseline) style.addProperty("style:vertical-align", "baseline", KoGenStyle::ParagraphType); else if (valign == VAlignBottom) style.addProperty("style:vertical-align", "bottom", KoGenStyle::ParagraphType); else if (valign == VAlignMiddle) style.addProperty("style:vertical-align", "middle", KoGenStyle::ParagraphType); else if (valign == VAlignTop) style.addProperty("style:vertical-align", "top", KoGenStyle::ParagraphType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (!writtenLineSpacing && propertyBoolean(NormalLineHeight)) style.addProperty("fo:line-height", QString("normal"), KoGenStyle::ParagraphType); // save border stuff QString leftBorder = QString("%1pt %2 %3").arg(QString::number(leftBorderWidth()), KoBorder::odfBorderStyleString(leftBorderStyle()), leftBorderColor().name()); QString rightBorder = QString("%1pt %2 %3").arg(QString::number(rightBorderWidth()), KoBorder::odfBorderStyleString(rightBorderStyle()), rightBorderColor().name()); QString topBorder = QString("%1pt %2 %3").arg(QString::number(topBorderWidth()), KoBorder::odfBorderStyleString(topBorderStyle()), topBorderColor().name()); QString bottomBorder = QString("%1pt %2 %3").arg(QString::number(bottomBorderWidth()), KoBorder::odfBorderStyleString(bottomBorderStyle()), bottomBorderColor().name()); if (leftBorder == rightBorder && leftBorder == topBorder && leftBorder == bottomBorder) { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border", leftBorder, KoGenStyle::ParagraphType); } else { if (leftBorderWidth() > 0 && leftBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-left", leftBorder, KoGenStyle::ParagraphType); if (rightBorderWidth() > 0 && rightBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-right", rightBorder, KoGenStyle::ParagraphType); if (topBorderWidth() > 0 && topBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-top", topBorder, KoGenStyle::ParagraphType); if (bottomBorderWidth() > 0 && bottomBorderStyle() != KoBorder::BorderNone) style.addProperty("fo:border-bottom", bottomBorder, KoGenStyle::ParagraphType); } QString leftBorderLineWidth, rightBorderLineWidth, topBorderLineWidth, bottomBorderLineWidth; if (leftBorderStyle() == KoBorder::BorderDouble) leftBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(leftInnerBorderWidth()), QString::number(leftBorderSpacing()), QString::number(leftBorderWidth())); if (rightBorderStyle() == KoBorder::BorderDouble) rightBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(rightInnerBorderWidth()), QString::number(rightBorderSpacing()), QString::number(rightBorderWidth())); if (topBorderStyle() == KoBorder::BorderDouble) topBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(topInnerBorderWidth()), QString::number(topBorderSpacing()), QString::number(topBorderWidth())); if (bottomBorderStyle() == KoBorder::BorderDouble) bottomBorderLineWidth = QString("%1pt %2pt %3pt").arg(QString::number(bottomInnerBorderWidth()), QString::number(bottomBorderSpacing()), QString::number(bottomBorderWidth())); if (leftBorderLineWidth == rightBorderLineWidth && leftBorderLineWidth == topBorderLineWidth && leftBorderLineWidth == bottomBorderLineWidth && !leftBorderLineWidth.isEmpty()) { style.addProperty("style:border-line-width", leftBorderLineWidth, KoGenStyle::ParagraphType); } else { if (!leftBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-left", leftBorderLineWidth, KoGenStyle::ParagraphType); if (!rightBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-right", rightBorderLineWidth, KoGenStyle::ParagraphType); if (!topBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-top", topBorderLineWidth, KoGenStyle::ParagraphType); if (!bottomBorderLineWidth.isEmpty()) style.addProperty("style:border-line-width-bottom", bottomBorderLineWidth, KoGenStyle::ParagraphType); } const int indentation = 4; // indentation for children of office:styles/style:style/style:paragraph-properties // drop-caps if (dropCaps()) { QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:drop-cap"); elementWriter.addAttribute("style:lines", QString::number(dropCapsLines())); elementWriter.addAttribute("style:length", dropCapsLength() == 0 ? "word" : QString::number(dropCapsLength())); if (dropCapsDistance()) elementWriter.addAttributePt("style:distance", dropCapsDistance()); elementWriter.endElement(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:drop-cap", elementContents, KoGenStyle::ParagraphType); } if (tabPositions().count() > 0) { QMap tabTypeMap, leaderTypeMap, leaderStyleMap, leaderWeightMap; tabTypeMap[QTextOption::LeftTab] = "left"; tabTypeMap[QTextOption::RightTab] = "right"; tabTypeMap[QTextOption::CenterTab] = "center"; tabTypeMap[QTextOption::DelimiterTab] = "char"; leaderTypeMap[KoCharacterStyle::NoLineType] = "none"; leaderTypeMap[KoCharacterStyle::SingleLine] = "single"; leaderTypeMap[KoCharacterStyle::DoubleLine] = "double"; leaderStyleMap[KoCharacterStyle::NoLineStyle] = "none"; leaderStyleMap[KoCharacterStyle::SolidLine] = "solid"; leaderStyleMap[KoCharacterStyle::DottedLine] = "dotted"; leaderStyleMap[KoCharacterStyle::DashLine] = "dash"; leaderStyleMap[KoCharacterStyle::LongDashLine] = "long-dash"; leaderStyleMap[KoCharacterStyle::DotDashLine] = "dot-dash"; leaderStyleMap[KoCharacterStyle::DotDotDashLine] = "dot-dot-dash"; leaderStyleMap[KoCharacterStyle::WaveLine] = "wave"; leaderWeightMap[KoCharacterStyle::AutoLineWeight] = "auto"; leaderWeightMap[KoCharacterStyle::NormalLineWeight] = "normal"; leaderWeightMap[KoCharacterStyle::BoldLineWeight] = "bold"; leaderWeightMap[KoCharacterStyle::ThinLineWeight] = "thin"; leaderWeightMap[KoCharacterStyle::DashLineWeight] = "dash"; leaderWeightMap[KoCharacterStyle::MediumLineWeight] = "medium"; leaderWeightMap[KoCharacterStyle::ThickLineWeight] = "thick"; QBuffer buf; buf.open(QIODevice::WriteOnly); KoXmlWriter elementWriter(&buf, indentation); elementWriter.startElement("style:tab-stops"); foreach(const KoText::Tab &tab, tabPositions()) { elementWriter.startElement("style:tab-stop"); elementWriter.addAttributePt("style:position", tab.position); if (!tabTypeMap[tab.type].isEmpty()) elementWriter.addAttribute("style:type", tabTypeMap[tab.type]); if (tab.type == QTextOption::DelimiterTab && !tab.delimiter.isNull()) elementWriter.addAttribute("style:char", tab.delimiter); if (!leaderTypeMap[tab.leaderType].isEmpty()) elementWriter.addAttribute("style:leader-type", leaderTypeMap[tab.leaderType]); if (!leaderStyleMap[tab.leaderStyle].isEmpty()) elementWriter.addAttribute("style:leader-style", leaderStyleMap[tab.leaderStyle]); if (!leaderWeightMap[tab.leaderWeight].isEmpty()) elementWriter.addAttribute("style:leader-width", leaderWeightMap[tab.leaderWeight]); else if (tab.leaderWeight == KoCharacterStyle::PercentLineWeight) elementWriter.addAttribute("style:leader-width", QString("%1%").arg(QString::number(tab.leaderWidth))); else if (tab.leaderWeight == KoCharacterStyle::LengthLineWeight) elementWriter.addAttributePt("style:leader-width", tab.leaderWidth); if (tab.leaderColor.isValid()) elementWriter.addAttribute("style:leader-color", tab.leaderColor.name()); else elementWriter.addAttribute("style:leader-color", "font-color"); if (!tab.leaderText.isEmpty()) elementWriter.addAttribute("style:leader-text", tab.leaderText); elementWriter.endElement(); } elementWriter.endElement(); buf.close(); QString elementContents = QString::fromUtf8(buf.buffer(), buf.buffer().size()); style.addChildElement("style:tab-stops", elementContents, KoGenStyle::ParagraphType); } } bool KoParagraphStyle::hasDefaults() const { int size=d->stylesPrivate.properties().size(); if ((size == 0) || (size==1 && d->stylesPrivate.properties().contains(StyleId))) { return true; } return false; } KoList *KoParagraphStyle::list() const { return d->list; } diff --git a/libs/text/styles/tests/TestStyles.cpp b/libs/text/styles/tests/TestStyles.cpp index 37e89d18607..3e4e4e243da 100644 --- a/libs/text/styles/tests/TestStyles.cpp +++ b/libs/text/styles/tests/TestStyles.cpp @@ -1,400 +1,400 @@ /* This file is part of the Calligra project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2011 Stuart Dickson * * 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. */ #include "TestStyles.h" #include #include "TextDebug.h" #include #include #include #include #include #include #include void TestStyles::testStyleInheritance() { KoParagraphStyle style1; style1.setTopMargin(QTextLength(QTextLength::FixedLength, 10.0)); QCOMPARE(style1.topMargin(), 10.0); KoParagraphStyle style2; style2.setParentStyle(&style1); QCOMPARE(style2.topMargin(), 10.0); style2.setTopMargin(QTextLength(QTextLength::FixedLength, 20.0)); QCOMPARE(style2.topMargin(), 20.0); QCOMPARE(style1.topMargin(), 10.0); style1.setTopMargin(QTextLength(QTextLength::FixedLength, 15.0)); QCOMPARE(style2.topMargin(), 20.0); QCOMPARE(style1.topMargin(), 15.0); style2.setTopMargin(QTextLength(QTextLength::FixedLength, 15.0)); // the same, resetting the difference. QCOMPARE(style2.topMargin(), 15.0); QCOMPARE(style1.topMargin(), 15.0); style1.setTopMargin(QTextLength(QTextLength::FixedLength, 12.0)); // parent, so both are affected QCOMPARE(style2.topMargin(), 12.0); QCOMPARE(style1.topMargin(), 12.0); } void TestStyles::testChangeParent() { KoParagraphStyle style1; style1.setTopMargin(QTextLength(QTextLength::FixedLength, 10.0)); KoParagraphStyle style2; style2.setTopMargin(QTextLength(QTextLength::FixedLength, 20.0)); style2.setParentStyle(&style1); QCOMPARE(style1.topMargin(), 10.0); QCOMPARE(style2.topMargin(), 20.0); KoParagraphStyle style3; style3.setParentStyle(&style1); QCOMPARE(style1.topMargin(), 10.0); QCOMPARE(style3.topMargin(), 10.0); // test that separating will leave the child with exactly the same dataset // as it had before the inheritance style3.setParentStyle(0); QCOMPARE(style1.topMargin(), 10.0); QCOMPARE(style3.topMargin(), 0.0); // we hadn't explicitly set the margin on style3 // test adding it to another will not destroy any data style3.setParentStyle(&style1); QCOMPARE(style1.topMargin(), 10.0); // from style1 QCOMPARE(style2.topMargin(), 20.0); // from style2 QCOMPARE(style3.topMargin(), 10.0); // inherited from style1 // Check that style3 now starts following the parent since it does not have // the property set style3.setParentStyle(&style2); QCOMPARE(style3.topMargin(), 20.0); // inherited from style2 } void TestStyles::testTabsStorage() { KoParagraphStyle paragStyle; QVector tabs; paragStyle.setTabPositions(tabs); QCOMPARE(paragStyle.tabPositions().count(), 0); KoText::Tab tab; tabs.append(tab); KoText::Tab tab2; tab2.position = 10; tabs.append(tab2); paragStyle.setTabPositions(tabs); QCOMPARE(paragStyle.tabPositions().count(), 2); - QCOMPARE(paragStyle.tabPositions()[0], tab); - QCOMPARE(paragStyle.tabPositions()[1], tab2); + QCOMPARE(paragStyle.tabPositions().at(0), tab); + QCOMPARE(paragStyle.tabPositions().at(1), tab2); } void TestStyles::testApplyParagraphStyle() { KoParagraphStyle style; style.setStyleId(1001); QTextBlockFormat format; QCOMPARE(format.properties().count(), 0); style.applyStyle(format); QCOMPARE(format.properties().count(), 2); // the styleId and nextStyleId QCOMPARE(format.property(KoParagraphStyle::StyleId).toInt(), 1001); QCOMPARE(format.property(KoParagraphStyle::NextStyle).toInt(), 1001); style.setName("name"); style.setAlignment(Qt::AlignRight); style.applyStyle(format); QCOMPARE(format.properties().count(), 3); QCOMPARE(format.alignment(), Qt::AlignRight); } void TestStyles::testApplyParagraphStyleWithParent() { KoParagraphStyle style1; style1.setStyleId(1002); KoParagraphStyle style2; style2.setStyleId(1003); KoParagraphStyle style3; style3.setStyleId(1004); style3.setParentStyle(&style2); style2.setParentStyle(&style1); style1.setAlignment(Qt::AlignRight); QCOMPARE(style1.alignment(), Qt::AlignRight); QCOMPARE(style2.alignment(), Qt::AlignRight); QCOMPARE(style3.alignment(), Qt::AlignRight); style2.setAlignment(Qt::AlignCenter); QCOMPARE(style1.alignment(), Qt::AlignRight); QCOMPARE(style2.alignment(), Qt::AlignCenter); QCOMPARE(style3.alignment(), Qt::AlignCenter); style3.setAlignment(Qt::AlignLeft | Qt::AlignAbsolute); QCOMPARE(style1.alignment(), Qt::AlignRight); QCOMPARE(style2.alignment(), Qt::AlignCenter); QCOMPARE(style3.alignment(), Qt::AlignLeft | Qt::AlignAbsolute); style3.setLineSpacing(23.45); style3.setLineHeightPercent(150); style3.setLineHeightAbsolute(8.0); QCOMPARE(style3.lineHeightPercent(), 0.0); QCOMPARE(style3.lineHeightAbsolute(), 8.0); QCOMPARE(style3.lineSpacing(), 23.45); QVERIFY(!style3.hasNormalLineHeight()); style3.setNormalLineHeight(); QCOMPARE(style3.lineHeightPercent(), 0.0); QCOMPARE(style3.lineHeightAbsolute(), 0.0); QCOMPARE(style3.lineSpacing(), 0.0); QVERIFY(style3.hasNormalLineHeight()); style3.setLineHeightPercent(150); style3.setLineSpacing(56.78); QCOMPARE(style3.lineHeightPercent(), 150.0); QCOMPARE(style3.lineHeightAbsolute(), 0.0); QCOMPARE(style3.lineSpacing(), 56.78); QVERIFY(!style3.hasNormalLineHeight()); QTextLength length0(QTextLength::FixedLength, 0.0); QTextLength length1(QTextLength::FixedLength, 10.0); QTextLength length2(QTextLength::FixedLength, 20.0); style1.setLeftMargin(length1); QCOMPARE(style1.leftMargin(), 10.0); QCOMPARE(style2.leftMargin(), 10.0); QCOMPARE(style3.leftMargin(), 10.0); style2.setRightMargin(length2); QCOMPARE(style1.rightMargin(), 0.0); QCOMPARE(style2.rightMargin(), 20.0); QCOMPARE(style3.rightMargin(), 20.0); // now actually apply it. QTextBlockFormat rawFormat; style1.applyStyle(rawFormat); KoParagraphStyle format(rawFormat, rawFormat.toCharFormat()); QCOMPARE(rawFormat.properties().count(), 4); QCOMPARE(format.alignment(), Qt::AlignRight); QCOMPARE(rawFormat.property(KoParagraphStyle::StyleId).toInt(), 1002); //since we have not specified any NextStyle it should be the same as the current style QCOMPARE(rawFormat.property(KoParagraphStyle::StyleId).toInt(), rawFormat.property(KoParagraphStyle::NextStyle).toInt()); QCOMPARE(format.leftMargin(), 10.0); QCOMPARE(format.rightMargin(), 0.0); style2.applyStyle(rawFormat); KoParagraphStyle format2(rawFormat, rawFormat.toCharFormat()); QCOMPARE(rawFormat.properties().count(), 5); QCOMPARE(format2.alignment(), Qt::AlignCenter); QCOMPARE(rawFormat.property(KoParagraphStyle::StyleId).toInt(), 1003); //since we have not specified any NextStyle it should be the same as the current style QCOMPARE(rawFormat.property(KoParagraphStyle::StyleId).toInt(), rawFormat.property(KoParagraphStyle::NextStyle).toInt()); QCOMPARE(format2.leftMargin(), 10.0); QCOMPARE(format2.rightMargin(), 20.0); style3.applyStyle(rawFormat); KoParagraphStyle format3(rawFormat, rawFormat.toCharFormat()); QCOMPARE(rawFormat.properties().count(), 9); QCOMPARE(rawFormat.property(KoParagraphStyle::LineSpacing).toReal(), 56.78); QCOMPARE(format3.alignment(), Qt::AlignLeft | Qt::AlignAbsolute); QCOMPARE(rawFormat.property(KoParagraphStyle::StyleId).toInt(), 1004); //since we have not specified any NextStyle it should be the same as the current style QCOMPARE(rawFormat.property(KoParagraphStyle::StyleId).toInt(), rawFormat.property(KoParagraphStyle::NextStyle).toInt()); QCOMPARE(format3.leftMargin(), 10.0); QCOMPARE(format3.rightMargin(), 20.0); } void TestStyles::testCopyParagraphStyle() { QTextLength length1(QTextLength::FixedLength, 10.0); QTextLength length2(QTextLength::FixedLength, 20.0); QTextLength length3(QTextLength::FixedLength, 30.0); KoParagraphStyle style1; KoParagraphStyle style2; style2.setParentStyle(&style1); style1.setLeftMargin(length1); style1.setRightMargin(length3); style2.setRightMargin(length2); KoParagraphStyle newStyle; newStyle.copyProperties(&style2); QCOMPARE(newStyle.leftMargin(), 10.0); QCOMPARE(newStyle.rightMargin(), 20.0); } void TestStyles::testUnapplyStyle() { // Used to test OverlineColor style QColor testOverlineColor(255, 128, 64); KoCharacterStyle::LineWeight testOverlineWeight = KoCharacterStyle::ThickLineWeight; qreal testOverlineWidth = 1.5; // in this test we should avoid testing any of the hardcodedDefaultProperties; see KoCharacterStyle for details! KoParagraphStyle headers; headers.setOverlineColor(testOverlineColor); headers.setOverlineMode(KoCharacterStyle::ContinuousLineMode); headers.setOverlineStyle(KoCharacterStyle::DottedLine); headers.setOverlineType(KoCharacterStyle::DoubleLine); headers.setOverlineWidth(testOverlineWeight, testOverlineWidth); headers.setFontWeight(QFont::Bold); headers.setAlignment(Qt::AlignCenter); KoParagraphStyle head1; head1.setParentStyle(&headers); head1.setLeftMargin(QTextLength(QTextLength::FixedLength, 40)); QTextDocument doc; doc.setPlainText("abc"); QTextBlock block = doc.begin(); head1.applyStyle(block); QTextCursor cursor(block); QTextBlockFormat bf = cursor.blockFormat(); KoParagraphStyle bfStyle (bf, cursor.charFormat()); QCOMPARE(bf.alignment(), Qt::AlignCenter); QCOMPARE(bfStyle.leftMargin(), 40.); QTextCharFormat cf = cursor.charFormat(); QCOMPARE(cf.colorProperty(KoCharacterStyle::OverlineColor), testOverlineColor); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineMode), (int) KoCharacterStyle::ContinuousLineMode); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineStyle), (int) KoCharacterStyle::DottedLine); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineType), (int) KoCharacterStyle::DoubleLine); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineWeight), (int) testOverlineWeight); QCOMPARE(cf.doubleProperty(KoCharacterStyle::OverlineWidth), testOverlineWidth); head1.unapplyStyle(block); bf = cursor.blockFormat(); QCOMPARE(bf.hasProperty(QTextFormat::BlockAlignment), false); QCOMPARE(bf.hasProperty(QTextFormat::BlockLeftMargin), false); cf = cursor.charFormat(); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineColor), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineMode), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineStyle), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineType), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWeight), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWidth), false); doc.clear(); block = doc.begin(); head1.applyStyle(block); bf = cursor.blockFormat(); KoParagraphStyle bfStyle2 (bf, cursor.charFormat()); QCOMPARE(bf.alignment(), Qt::AlignCenter); QCOMPARE(bfStyle2.leftMargin(), 40.); cf = cursor.charFormat(); //QCOMPARE(cf.fontOverline(), true); QCOMPARE(cf.colorProperty(KoCharacterStyle::OverlineColor), testOverlineColor); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineMode), (int) KoCharacterStyle::ContinuousLineMode); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineStyle), (int) KoCharacterStyle::DottedLine); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineType), (int) KoCharacterStyle::DoubleLine); QCOMPARE(cf.intProperty(KoCharacterStyle::OverlineWeight), (int) testOverlineWeight); QCOMPARE(cf.doubleProperty(KoCharacterStyle::OverlineWidth), testOverlineWidth); head1.unapplyStyle(block); bf = cursor.blockFormat(); QCOMPARE(bf.hasProperty(QTextFormat::BlockAlignment), false); QCOMPARE(bf.hasProperty(QTextFormat::BlockLeftMargin), false); cf = cursor.charFormat(); //QCOMPARE(cf.hasProperty(QTextFormat::FontOverline), false); doc.setHtml("bla blaitalicenzo"); block = doc.begin(); head1.applyStyle(block); bf = cursor.blockFormat(); KoParagraphStyle bfStyle3(bf, cursor.charFormat()); QCOMPARE(bf.alignment(), Qt::AlignCenter); QCOMPARE(bfStyle3.leftMargin(), 40.); cf = cursor.charFormat(); //QCOMPARE(cf.fontOverline(), true); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineColor), true); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineMode), true); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineStyle), true); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineType), true); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWeight), true); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWidth), true); cursor.setPosition(7); cursor.setPosition(12, QTextCursor::KeepAnchor); QTextCharFormat italic; italic.setFontItalic(true); cursor.mergeCharFormat(italic); cursor.setPosition(8); cf = cursor.charFormat(); QCOMPARE(cf.fontItalic(), true); cursor.setPosition(0); head1.unapplyStyle(block); cursor.setPosition(0); bf = cursor.blockFormat(); QCOMPARE(bf.hasProperty(QTextFormat::BlockAlignment), false); QCOMPARE(bf.hasProperty(QTextFormat::BlockLeftMargin), false); cf = cursor.charFormat(); //QCOMPARE(cf.hasProperty(QTextFormat::FontOverline), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineColor), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineMode), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineStyle), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineType), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWeight), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWidth), false); cursor.setPosition(8); cf = cursor.charFormat(); //QCOMPARE(cf.hasProperty(QTextFormat::FontOverline), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineColor), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineMode), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineStyle), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineType), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWeight), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWidth), false); QCOMPARE(cf.fontItalic(), true); cursor.setPosition(12); cf = cursor.charFormat(); //QCOMPARE(cf.hasProperty(QTextFormat::FontOverline), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineColor), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineMode), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineStyle), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineType), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWeight), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWidth), false); QCOMPARE(cf.fontItalic(), true); cursor.setPosition(13); cf = cursor.charFormat(); //QCOMPARE(cf.hasProperty(QTextFormat::FontOverline), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineColor), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineMode), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineStyle), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineType), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWeight), false); QCOMPARE(cf.hasProperty(KoCharacterStyle::OverlineWidth), false); QCOMPARE(cf.hasProperty(QTextFormat::FontWeight), false); QCOMPARE(cf.hasProperty(QTextFormat::FontItalic), false); } QTEST_MAIN(TestStyles) diff --git a/libs/text/tests/TestKoInlineTextObjectManager.h b/libs/text/tests/TestKoInlineTextObjectManager.h index 6561f6ea2bd..9bad68ff332 100644 --- a/libs/text/tests/TestKoInlineTextObjectManager.h +++ b/libs/text/tests/TestKoInlineTextObjectManager.h @@ -1,102 +1,103 @@ /* This file is part of the KDE project * * Copyright (c) 2011 Boudewijn Rempt * * 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. */ #ifndef TEST_KO_INLINE_TEXT_OBJECT_MANAGER_H #define TEST_KO_INLINE_TEXT_OBJECT_MANAGER_H #include #include #include #include #include #include #include #include #include class DummyInlineObject : public KoInlineObject { +Q_OBJECT public: DummyInlineObject(bool propertyListener) : KoInlineObject(propertyListener) , m_position(-1) { } virtual ~DummyInlineObject() {} virtual void saveOdf(KoShapeSavingContext &/*context*/) { // dummy impl } virtual bool loadOdf(const KoXmlElement&, KoShapeLoadingContext&) { // dummy impl return false; } virtual void updatePosition(const QTextDocument *document, int posInDocument, const QTextCharFormat &/*format*/) { Q_ASSERT(posInDocument <= document->toPlainText().size()); Q_UNUSED(document); m_position = posInDocument; } virtual void resize(const QTextDocument */*document*/, QTextInlineObject &/*object*/, int /*posInDocument*/, const QTextCharFormat &/*format*/, QPaintDevice */*pd*/) { // dummy impl } virtual void paint(QPainter &/*painter*/, QPaintDevice */*pd*/, const QTextDocument */*document*/, const QRectF &/*rect*/, const QTextInlineObject &/*object*/, int /*posInDocument*/, const QTextCharFormat &/*format*/) { // dummy impl } virtual void propertyChanged(Property /*property*/, const QVariant &value) { m_property = value; } QVariant m_property; int m_position; }; class TestKoInlineTextObjectManager : public QObject { Q_OBJECT private Q_SLOTS: void testCreation(); void testInsertInlineObject(); void testRetrieveInlineObject(); void testRemoveInlineObject(); void testListenToProperties(); }; #endif // TEST_KO_INLINE_TEXT_OBJECT_MANAGER_H diff --git a/libs/text/tests/TestKoTextEditor.cpp b/libs/text/tests/TestKoTextEditor.cpp index 0c6456eca1f..33e6f878b74 100644 --- a/libs/text/tests/TestKoTextEditor.cpp +++ b/libs/text/tests/TestKoTextEditor.cpp @@ -1,546 +1,544 @@ /* This file is part of the KDE project * * Copyright (c) 2011 Boudewijn Rempt * Copyright (c) 2014-2015 Denis Kuplyakov * * 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. */ #include "TestKoTextEditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" /** * Convenient class to create a document and assign * stuff like KoTextRangeManager, etc. automatically. */ class TestDocument : public KoShapeBasedDocumentBase { public: TestDocument() { m_document = new QTextDocument(); KoTextDocument textDoc(m_document); KoTextEditor *editor = new KoTextEditor(m_document); KUndo2Stack *undoStack = new KUndo2Stack(); textDoc.setUndoStack(undoStack); textDoc.setInlineTextObjectManager(&m_inlineObjectManager); textDoc.setTextRangeManager(&m_rangeManager); textDoc.setStyleManager(new KoStyleManager(0)); textDoc.setTextEditor(editor); } virtual ~TestDocument() { delete m_document; } virtual void addShape(KoShape *shape) { m_shapes << shape; } virtual void removeShape(KoShape *shape) { m_shapes.removeAll(shape); } KoTextEditor *textEditor() { return KoTextDocument(m_document).textEditor(); } KoSectionModel *sectionModel() { return KoTextDocument(m_document).sectionModel(); } QList m_shapes; QTextDocument *m_document; KoInlineTextObjectManager m_inlineObjectManager; KoTextRangeManager m_rangeManager; KoDocumentRdfBase m_rdfBase; }; const QString lorem( "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor" "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla" "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia" "deserunt mollit anim id est laborum.\n" ); /* FIXME: all the meaning part of this test was commented by boud in 2011 void TestKoTextEditor::testInsertInlineObject() { QObject parent; // create a document QTextDocument doc; KoInlineTextObjectManager inlineObjectManager(&parent); KoTextDocument textDoc(&doc); textDoc.setInlineTextObjectManager(&inlineObjectManager); KoTextEditor editor(&doc); textDoc.setTextEditor(&editor); // Hmm, what kind of inline object should we test. variables maybe? // enter some lorem ipsum editor.insertText(lorem); KoBookmark *startmark = new KoBookmark(editor.document()); startmark->setName("single!"); editor.insertInlineObject(startmark); Q_ASSERT(startmark->id() == 1); Q_ASSERT(startmark->name() == "single!"); Q_ASSERT(startmark->position() == 444); QTextCursor cursor = doc.find(QString(QChar::ObjectReplacementCharacter), 0); Q_ASSERT(cursor.position() == 444); KoInlineObject *obj = inlineObjectManager.inlineTextObject(cursor.charFormat()); Q_ASSERT(obj == startmark); } */ void TestKoTextEditor::testRemoveSelectedText() { TestDocument doc; KoTextEditor *editor = doc.textEditor(); KoTextRangeManager *rangeManager = &doc.m_rangeManager; // enter some lorem ipsum editor->insertText(lorem); QTextCursor cur(doc.m_document); cur.setPosition(editor->position()); KoBookmark *bookmark = new KoBookmark(cur); bookmark->setName("start!"); bookmark->setPositionOnlyMode(false); // we want it to be several chars long rangeManager->insert(bookmark); editor->insertText(lorem); bookmark->setRangeEnd(editor->position()); QCOMPARE(bookmark->rangeStart(), lorem.length()); QCOMPARE(bookmark->rangeEnd(), lorem.length() * 2); Q_ASSERT(rangeManager->textRanges().length() == 1); // select all text editor->setPosition(0, QTextCursor::MoveAnchor); editor->movePosition(QTextCursor::End, QTextCursor::KeepAnchor); Q_ASSERT(editor->hasSelection()); // remove the text + the bookmark from the document editor->deleteChar(); // check whether the bookmark has gone. Q_ASSERT(rangeManager->textRanges().length() == 0); } void TestKoTextEditor::pushSectionStart(int num, KoSection *sec, KoTextEditor *editor) { editor->insertText(QString("[ %1").arg(num)); QTextBlockFormat fmt = editor->blockFormat(); KoSectionUtils::setSectionStartings(fmt, QList() << sec); editor->setBlockFormat(fmt); editor->insertText("\n"); fmt.clearProperty(KoParagraphStyle::SectionEndings); fmt.clearProperty(KoParagraphStyle::SectionStartings); editor->setBlockFormat(fmt); } void TestKoTextEditor::pushSectionEnd(int num, KoSectionEnd *secEnd, KoTextEditor *editor) { editor->insertText(QString("%1 ]").arg(num)); QTextBlockFormat fmt = editor->blockFormat(); KoSectionUtils::setSectionEndings(fmt, QList() << secEnd); editor->setBlockFormat(fmt); secEnd->correspondingSection()->setKeepEndBound(true); editor->insertText("\n"); fmt.clearProperty(KoParagraphStyle::SectionEndings); fmt.clearProperty(KoParagraphStyle::SectionStartings); editor->setBlockFormat(fmt); } void TestKoTextEditor::formSectionTestDocument(TestDocument *doc) { // Here we are going to create next document with nested sections: // ** offset ** block num // [ 0P 0 0 // [ 1P 4 1 // [ 2P 8 2 // 2 ]P 12 3 // 1 ]P 16 4 // [ 3P 20 5 // 3 ]P 24 6 // [ 4P 28 7 // 4 ]P 32 8 // 0 ]P 36 9 // (**empty_block**) 10 // // Sections will receive names "0", "1", etc. // [ and ] is actual text, not a sign! KoTextEditor *editor = doc->textEditor(); const int TOTAL_SECTIONS = 5; KoSection *sec[TOTAL_SECTIONS]; KoSectionEnd *secEnd[TOTAL_SECTIONS]; sec[0] = doc->sectionModel()->createSection(editor->constCursor(), 0, QString::number(0)); pushSectionStart(0, sec[0], editor); sec[1] = doc->sectionModel()->createSection(editor->constCursor(), sec[0], QString::number(1)); pushSectionStart(1, sec[1], editor); sec[2] = doc->sectionModel()->createSection(editor->constCursor(), sec[1], QString::number(2)); pushSectionStart(2, sec[2], editor); secEnd[2] = doc->sectionModel()->createSectionEnd(sec[2]); pushSectionEnd(2, secEnd[2], editor); secEnd[1] = doc->sectionModel()->createSectionEnd(sec[1]); pushSectionEnd(1, secEnd[1], editor); sec[3] = doc->sectionModel()->createSection(editor->constCursor(), sec[0], QString::number(3)); pushSectionStart(3, sec[3], editor); secEnd[3] = doc->sectionModel()->createSectionEnd(sec[3]); pushSectionEnd(3, secEnd[3], editor); sec[4] = doc->sectionModel()->createSection(editor->constCursor(), sec[0], QString::number(4)); pushSectionStart(4, sec[4], editor); secEnd[4] = doc->sectionModel()->createSectionEnd(sec[4]); pushSectionEnd(4, secEnd[4], editor); secEnd[0] = doc->sectionModel()->createSectionEnd(sec[0]); pushSectionEnd(0, secEnd[0], editor); doc->sectionModel()->allowMovingEndBound(); } bool TestKoTextEditor::checkStartings(const QVector &needStartings, KoTextEditor *editor) { QList lst = KoSectionUtils::sectionStartings(editor->blockFormat()); if (lst.size() != needStartings.size()) { debugText << QString("Startings list size is wrong." " Found %1, Expected %2.").arg(lst.size()).arg(needStartings.size()); return false; } for (int i = 0; i < needStartings.size(); i++) { if (lst[i]->name() != needStartings[i]) { debugText << QString("Found unexpected section starting." " Expected %1 section.").arg(needStartings[i]); return false; } } return true; } bool TestKoTextEditor::checkEndings(const QVector &needEndings, KoTextEditor *editor) { QList lst = KoSectionUtils::sectionEndings(editor->blockFormat()); if (lst.size() != needEndings.size()) { debugText << QString("Endings list size is wrong." " Found %1, expected %2.").arg(lst.size()).arg(needEndings.size()); return false; } for (int i = 0; i < needEndings.size(); i++) { if (lst[i]->correspondingSection()->name() != needEndings[i]) { debugText << QString("Found unexpected section ending." " Expected %1 section.").arg(needEndings[i]); return false; } } return true; } void TestKoTextEditor::checkSectionFormattingLevel( TestDocument *doc, int neededBlockCount, const QVector< QVector > &needStartings, const QVector< QVector > &needEndings) { // Assuming here that we can check names of the sections // instead of actual pointers. This seems to be true for now. QCOMPARE(needStartings.size(), neededBlockCount); QCOMPARE(needEndings.size(), neededBlockCount); KoTextEditor *editor = doc->textEditor(); editor->movePosition(QTextCursor::Start); QCOMPARE(doc->m_document->blockCount(), neededBlockCount); for (int i = 0; i < doc->m_document->blockCount(); i++) { if (!checkStartings(needStartings[i], editor) || !checkEndings(needEndings[i], editor)) { QFAIL("Wrong section information."); } editor->movePosition(QTextCursor::NextBlock); } } void TestKoTextEditor::checkSectionModelLevelRecursive(QModelIndex index, TestKoTextEditor::SectionHandle *handle) { QCOMPARE(index.data(KoSectionModel::PointerRole).value(), handle->sec); QCOMPARE(index.model()->rowCount(index), handle->children.size()); QModelIndex parent = index.parent(); QCOMPARE(parent.data(KoSectionModel::PointerRole).value(), handle->parent); for (int i = 0; i < handle->children.size(); i++) { checkSectionModelLevelRecursive(index.child(i, 0), handle->children[i]); } } void TestKoTextEditor::checkSectionModelLevel(TestDocument *doc) { // Assuming here that Formatting level is OK // Below I will rebuild sections structure from scratch // and compare it then with a KoSectionModel QVector allSections, rootSections; QStack sectionStack; QTextBlock curBlock = doc->m_document->firstBlock(); // This kind of cycle should visit all blocks // including ones in tables and frames. while (curBlock.isValid()) { QList secStartings = KoSectionUtils::sectionStartings(curBlock.blockFormat()); QList secEndings = KoSectionUtils::sectionEndings(curBlock.blockFormat()); foreach(KoSection *sec, secStartings) { SectionHandle *handle = new SectionHandle(sec); if (sectionStack.empty()) { rootSections.push_back(handle); handle->parent = 0; } else { sectionStack.top()->children.push_back(handle); handle->parent = sectionStack.top()->sec; } allSections.push_back(handle); sectionStack.push(handle); } - foreach(KoSectionEnd *secEnd, secEndings) { - sectionStack.pop(); - } + sectionStack.resize(sectionStack.size() - secEndings.size()); curBlock = curBlock.next(); } // Now lets compare builded tree with KoSectionModel KoSectionModel *model = doc->sectionModel(); QCOMPARE(model->rowCount(), rootSections.size()); for (int i = 0; i < rootSections.size(); i++) { checkSectionModelLevelRecursive(model->index(i, 0), rootSections[i]); } foreach (SectionHandle *handle, allSections) { delete handle; } } void TestKoTextEditor::dumpSectionFormattingLevel(TestDocument *doc) { QString result; result += QString("QTest::newRow(\"%1\") << %2\n").arg(QTest::currentDataTag()).arg(doc->m_document->blockCount()); result += " << (QVector< QVector >()\n"; QTextBlock curBlock = doc->m_document->firstBlock(); // This kind of cycle should visit all blocks // including ones in tables and frames. while (curBlock.isValid()) { result += " << (QVector()"; QList l = KoSectionUtils::sectionStartings(curBlock.blockFormat()); foreach (KoSection *s, l) { result += QString(" << \"%1\"").arg(s->name()); } result += ")"; curBlock = curBlock.next(); if (curBlock.isValid()) { result += "\n"; } else { result += ")\n"; } } result += " << (QVector< QVector >()\n"; curBlock = doc->m_document->firstBlock(); while (curBlock.isValid()) { result += " << (QVector()"; QList l = KoSectionUtils::sectionEndings(curBlock.blockFormat()); foreach (KoSectionEnd *e, l) { result += QString(" << \"%1\"").arg(e->correspondingSection()->name()); } result += ")"; curBlock = curBlock.next(); if (curBlock.isValid()) { result += "\n"; } else { result += ")\n"; } } result += ";"; QFile out(QString("dump_%1.txt").arg(QTest::currentDataTag())); if (out.open(QIODevice::ReadWrite)) { QTextStream(&out) << result; } out.close(); } void TestKoTextEditor::checkSectionTestDocument(TestDocument *doc) { int neededBlockCount = 11; QVector< QVector > needStartings = QVector< QVector >() << (QVector() << "0") << (QVector() << "1") << (QVector() << "2") << (QVector()) << (QVector()) << (QVector() << "3") << (QVector()) << (QVector() << "4") << (QVector()) << (QVector()) << (QVector()); QVector< QVector > needEndings = QVector< QVector >() << (QVector()) << (QVector()) << (QVector()) << (QVector() << "2") << (QVector() << "1") << (QVector()) << (QVector() << "3") << (QVector()) << (QVector() << "4") << (QVector() << "0") << (QVector()); checkSectionFormattingLevel(doc, neededBlockCount, needStartings, needEndings); checkSectionModelLevel(doc); } void TestKoTextEditor::testBasicSectionCreation() { TestDocument doc; formSectionTestDocument(&doc); checkSectionTestDocument(&doc); } #include "TestInsertSectionHandling_data.cpp" void TestKoTextEditor::testInsertSectionHandling() { TestDocument doc; formSectionTestDocument(&doc); KoTextEditor *editor = doc.textEditor(); QFETCH(int, insertPosition); editor->setPosition(insertPosition); editor->newSection(); QFETCH(int, neededBlockCount); QFETCH(QVector< QVector >, needStartings); QFETCH(QVector< QVector >, needEndings); checkSectionFormattingLevel(&doc, neededBlockCount, needStartings, needEndings); checkSectionModelLevel(&doc); // undo changes and check a source document KoTextDocument(doc.m_document).undoStack()->undo(); checkSectionTestDocument(&doc); } #include "TestDeleteSectionHandling_data.cpp" // This test tests delete handling only on Formatting Level // See KoSectionModel void TestKoTextEditor::testDeleteSectionHandling() { testBasicSectionCreation(); // create a document TestDocument doc; formSectionTestDocument(&doc); KoTextEditor *editor = doc.textEditor(); QFETCH(int, selectionStart); QFETCH(int, selectionEnd); QFETCH(int, neededBlockCount); QFETCH(QVector< QVector >, needStartings); QFETCH(QVector< QVector >, needEndings); // placing selection editor->setPosition(selectionStart); editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionEnd - selectionStart); // doing deletion editor->deleteChar(); checkSectionFormattingLevel(&doc, neededBlockCount, needStartings, needEndings); checkSectionModelLevel(&doc); // undo changes and check a source document KoTextDocument(doc.m_document).undoStack()->undo(); checkSectionTestDocument(&doc); } QTEST_MAIN(TestKoTextEditor) diff --git a/libs/textlayout/KoTextDocumentLayout.cpp b/libs/textlayout/KoTextDocumentLayout.cpp index 06122d2aa5d..c031d0cb9a0 100644 --- a/libs/textlayout/KoTextDocumentLayout.cpp +++ b/libs/textlayout/KoTextDocumentLayout.cpp @@ -1,1028 +1,1028 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2009-2010 Thomas Zander * Copyright (C) 2010 Johannes Simon * Copyright (C) 2011-2013 KO GmbH * Copyright (C) 2011-2013 C.Boemann * * 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. */ #include "KoTextDocumentLayout.h" #include "styles/KoStyleManager.h" #include "KoTextBlockData.h" #include "KoInlineTextObjectManager.h" #include "KoTextLayoutRootArea.h" #include "KoTextLayoutRootAreaProvider.h" #include "KoTextLayoutObstruction.h" #include "FrameIterator.h" #include "InlineAnchorStrategy.h" #include "FloatingAnchorStrategy.h" #include "AnchorStrategy.h" #include "IndexGeneratorManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int qt_defaultDpiY(); KoInlineObjectExtent::KoInlineObjectExtent(qreal ascent, qreal descent) : m_ascent(ascent), m_descent(descent) { } class Q_DECL_HIDDEN KoTextDocumentLayout::Private { public: Private(KoTextDocumentLayout *) : styleManager(0) , changeTracker(0) , inlineTextObjectManager(0) , textRangeManager(0) , provider(0) , layoutPosition(0) , anchoringRootArea(0) , anchoringIndex(0) , anAnchorIsPlaced(false) , anchoringSoftBreak(INT_MAX) , allowPositionInlineObject(true) , continuationObstruction(0) , referencedLayout(0) , annotationLayoutManager(0) , defaultTabSizing(0) , y(0) , isLayouting(false) , layoutScheduled(false) , continuousLayout(true) , layoutBlocked(false) , changesBlocked(false) , restartLayout(false) , wordprocessingMode(false) , showInlineObjectVisualization(false) { } KoStyleManager *styleManager; KoChangeTracker *changeTracker; KoInlineTextObjectManager *inlineTextObjectManager; KoTextRangeManager *textRangeManager; KoTextLayoutRootAreaProvider *provider; KoPostscriptPaintDevice *paintDevice; QList rootAreaList; FrameIterator *layoutPosition; QHash inlineObjectExtents; // maps text-position to whole-line-height of an inline object int inlineObjectOffset; QList textAnchors; // list of all inserted inline objects QList foundAnchors; // anchors found in an iteration run KoTextLayoutRootArea *anchoringRootArea; int anchoringIndex; // index of last not positioned inline object inside textAnchors bool anAnchorIsPlaced; int anchoringSoftBreak; QRectF anchoringParagraphRect; QRectF anchoringParagraphContentRect; QRectF anchoringLayoutEnvironmentRect; bool allowPositionInlineObject; QHash anchoredObstructions; // all obstructions created because KoShapeAnchor from m_textAnchors is in text QList freeObstructions; // obstructions affecting the current rootArea, and not anchored to text KoTextLayoutObstruction *continuationObstruction; KoTextDocumentLayout *referencedLayout; KoAnnotationLayoutManager *annotationLayoutManager; QHash rootAreaForInlineObject; qreal defaultTabSizing; qreal y; bool isLayouting; bool layoutScheduled; bool continuousLayout; bool layoutBlocked; bool changesBlocked; bool restartLayout; bool wordprocessingMode; bool showInlineObjectVisualization; }; // ------------------- KoTextDocumentLayout -------------------- KoTextDocumentLayout::KoTextDocumentLayout(QTextDocument *doc, KoTextLayoutRootAreaProvider *provider) : QAbstractTextDocumentLayout(doc), d(new Private(this)) { d->paintDevice = new KoPostscriptPaintDevice(); d->provider = provider; setPaintDevice(d->paintDevice); d->styleManager = KoTextDocument(document()).styleManager(); d->changeTracker = KoTextDocument(document()).changeTracker(); d->inlineTextObjectManager = KoTextDocument(document()).inlineTextObjectManager(); d->textRangeManager = KoTextDocument(document()).textRangeManager(); setTabSpacing(MM_TO_POINT(23)); // use same default as open office d->layoutPosition = new FrameIterator(doc->rootFrame()); } KoTextDocumentLayout::~KoTextDocumentLayout() { delete d->paintDevice; delete d->layoutPosition; qDeleteAll(d->freeObstructions); qDeleteAll(d->anchoredObstructions); qDeleteAll(d->textAnchors); delete d; } KoTextLayoutRootAreaProvider *KoTextDocumentLayout::provider() const { return d->provider; } void KoTextDocumentLayout::setWordprocessingMode() { d->wordprocessingMode = true; } bool KoTextDocumentLayout::wordprocessingMode() const { return d->wordprocessingMode; } bool KoTextDocumentLayout::relativeTabs(const QTextBlock &block) const { return KoTextDocument(document()).relativeTabs() && KoTextDocument(block.document()).relativeTabs(); } KoInlineTextObjectManager *KoTextDocumentLayout::inlineTextObjectManager() const { return d->inlineTextObjectManager; } void KoTextDocumentLayout::setInlineTextObjectManager(KoInlineTextObjectManager *manager) { d->inlineTextObjectManager = manager; } KoTextRangeManager *KoTextDocumentLayout::textRangeManager() const { return d->textRangeManager; } void KoTextDocumentLayout::setTextRangeManager(KoTextRangeManager *manager) { d->textRangeManager = manager; } KoChangeTracker *KoTextDocumentLayout::changeTracker() const { return d->changeTracker; } void KoTextDocumentLayout::setChangeTracker(KoChangeTracker *tracker) { d->changeTracker = tracker; } KoStyleManager *KoTextDocumentLayout::styleManager() const { return d->styleManager; } void KoTextDocumentLayout::setStyleManager(KoStyleManager *manager) { d->styleManager = manager; } QRectF KoTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const { QTextLayout *layout = block.layout(); return layout->boundingRect(); } QSizeF KoTextDocumentLayout::documentSize() const { return QSizeF(); } QRectF KoTextDocumentLayout::selectionBoundingBox(QTextCursor &cursor) const { QRectF retval; foreach(const KoTextLayoutRootArea *rootArea, d->rootAreaList) { if (!rootArea->isDirty()) { QRectF areaBB = rootArea->selectionBoundingBox(cursor); if (areaBB.isValid()) { retval |= areaBB; } } } return retval; } void KoTextDocumentLayout::draw(QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context) { // WARNING Text shapes ask their root area directly to paint. // It saves a lot of extra traversal, that is quite costly for big // documents Q_UNUSED(painter); Q_UNUSED(context); } int KoTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const { Q_UNUSED(point); Q_UNUSED(accuracy); Q_ASSERT(false); //we should no longer call this method. // There is no need and is just slower than needed // call rootArea->hitTest() directly // root area is available through KoTextShapeData return -1; } int KoTextDocumentLayout::pageCount() const { return 1; } void KoTextDocumentLayout::setTabSpacing(qreal spacing) { d->defaultTabSizing = spacing; } qreal KoTextDocumentLayout::defaultTabSpacing() const { return d->defaultTabSizing; } // this method is called on every char inserted or deleted, on format changes, setting/moving of variables or objects. void KoTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded) { if (d->changesBlocked) { return; } int from = position; const int to = from + charsAdded; while (from < to) { // find blocks that have been added QTextBlock block = document()->findBlock(from); if (! block.isValid()) break; if (from == block.position() && block.textList()) { KoTextBlockData data(block); data.setCounterWidth(-1); } from = block.position() + block.length(); } // Mark the to the position corresponding root-areas as dirty. If there is no root-area for the position then we // don't need to mark anything dirty but still need to go on to force a scheduled relayout. if (!d->rootAreaList.isEmpty()) { KoTextLayoutRootArea *fromArea; if (position) { fromArea = rootAreaForPosition(position-1); } else { fromArea = d->rootAreaList.at(0); } int startIndex = fromArea ? qMax(0, d->rootAreaList.indexOf(fromArea)) : 0; int endIndex = startIndex; if (charsRemoved != 0 || charsAdded != 0) { // If any characters got removed or added make sure to also catch other root-areas that may be // affected by this change. Note that adding, removing or formatting text will always charsRemoved>0 // and charsAdded>0 cause they are changing a range of characters. One case where both is zero is if // the content of a variable changed (see KoVariable::setValue which calls publicDocumentChanged). In // those cases we only need to relayout the root-area dirty where the variable is on. KoTextLayoutRootArea *toArea = fromArea ? rootAreaForPosition(position + qMax(charsRemoved, charsAdded) + 1) : 0; if (toArea) { if (toArea != fromArea) { endIndex = qMax(startIndex, d->rootAreaList.indexOf(toArea)); } else { endIndex = startIndex; } } else { endIndex = d->rootAreaList.count() - 1; } // The previous and following root-area of that range are selected too cause they can also be affect by // changes done to the range of root-areas. if (startIndex >= 1) --startIndex; if (endIndex + 1 < d->rootAreaList.count()) ++endIndex; } // Mark all selected root-areas as dirty so they are relayouted. for(int i = startIndex; i <= endIndex; ++i) { if (d->rootAreaList.size() > i && d->rootAreaList[i]) d->rootAreaList[i]->setDirty(); } } // Once done we emit the layoutIsDirty signal. The consumer (e.g. the TextShape) will then layout dirty // root-areas and if needed following ones which got dirty cause content moved to them. Also this will // created new root-areas using KoTextLayoutRootAreaProvider::provide if needed. emitLayoutIsDirty(); } KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPosition(int position) const { QTextBlock block = document()->findBlock(position); if (!block.isValid()) return 0; QTextLine line = block.layout()->lineForTextPosition(position - block.position()); if (!line.isValid()) return 0; foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) { QRectF rect = rootArea->boundingRect(); // should already be normalized() if (rect.width() <= 0.0 && rect.height() <= 0.0) // ignore the rootArea if it has a size of QSizeF(0,0) continue; QPointF pos = line.position(); qreal x = pos.x(); qreal y = pos.y(); //0.125 needed since Qt Scribe works with fixed point if (x + 0.125 >= rect.x() && x<= rect.right() && y + line.height() + 0.125 >= rect.y() && y <= rect.bottom()) { return rootArea; } } return 0; } KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPoint(const QPointF &point) const { foreach(KoTextLayoutRootArea *rootArea, d->rootAreaList) { if (!rootArea->isDirty()) { if (rootArea->boundingRect().contains(point)) { return rootArea; } } } return 0; } void KoTextDocumentLayout::showInlineObjectVisualization(bool show) { d->showInlineObjectVisualization = show; } void KoTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int position, const QTextFormat &format) { Q_ASSERT(format.isCharFormat()); if (d->inlineTextObjectManager == 0) return; QTextCharFormat cf = format.toCharFormat(); if (d->showInlineObjectVisualization) { QColor color = cf.foreground().color(); // initial idea was to use Qt::gray (#A0A0A4) // for non-black text on non-white background it was derived to use // the text color with a transparency of 0x5F, so white-gray (0xFF-0xA0) color.setAlpha(0x5F); cf.setBackground(QBrush(color)); } KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); if (obj) obj->paint(*painter, paintDevice(), document(), rect, object, position, cf); } QList KoTextDocumentLayout::textAnchors() const { return d->textAnchors; } void KoTextDocumentLayout::registerAnchoredObstruction(KoTextLayoutObstruction *obstruction) { d->anchoredObstructions.insert(obstruction->shape(), obstruction); } qreal KoTextDocumentLayout::maxYOfAnchoredObstructions(int firstCursorPosition, int lastCursorPosition) const { qreal y = 0.0; int index = 0; while (index < d->anchoringIndex) { Q_ASSERT(index < d->textAnchors.count()); KoShapeAnchor *anchor = d->textAnchors[index]; if (anchor->flowWithText()) { if (anchor->textLocation()->position() >= firstCursorPosition && anchor->textLocation()->position() <= lastCursorPosition) { y = qMax(y, anchor->shape()->boundingRect().bottom() - anchor->shape()->parent()->boundingRect().y()); } } ++index; } return y; } int KoTextDocumentLayout::anchoringSoftBreak() const { return d->anchoringSoftBreak; } void KoTextDocumentLayout::positionAnchoredObstructions() { if (!d->anchoringRootArea) return; KoTextPage *page = d->anchoringRootArea->page(); if (!page) return; if (d->anAnchorIsPlaced) return; // The specs define 3 different anchor modes using the // draw:wrap-influence-on-position. We only implement the // once-successive and decided against supporting the other // two modes cause; // 1. The first mode, once-concurrently, is only for backward-compatibility // with pre OpenOffice.org 1.1. No other application supports that. It // should never have been added to the specs. // 2. The iterative mode is undocumented and it's absolute unclear how to // implement it in a way that we would earn 100% the same results OO.org // produces. In fact by looking at the OO.org source-code there seem to // be lot of extra-conditions, assumptions and OO.org related things going // on to handle that mode. We tried to support that mode once and it did // hit us bad, our source-code become way more worse, layouting slower and // the result was still different from OO.org. So, we decided it's not // worth it. // 3. The explanation provided at http://lists.oasis-open.org/archives/office/200409/msg00018.html // why the specs support those 3 anchor modes is, well, poor. It just doesn't // make sense. The specs should be fixed. // 4. The only support mode, the once-successive, is the one (only) support by // MSOffice. It's clear, logical, easy and needs to be supported by all // major office-suites that like to be compatible with MSOffice and OO.org. if (d->anchoringIndex < d->textAnchors.count()) { KoShapeAnchor *textAnchor = d->textAnchors[d->anchoringIndex]; AnchorStrategy *strategy = static_cast(textAnchor->placementStrategy()); strategy->setPageRect(page->rect()); strategy->setPageContentRect(page->contentRect()); strategy->setPageNumber(page->pageNumber()); if (strategy->moveSubject()) { ++d->anchoringIndex; d->anAnchorIsPlaced = true; } } } void KoTextDocumentLayout::setAnchoringParagraphRect(const QRectF ¶graphRect) { d->anchoringParagraphRect = paragraphRect; } void KoTextDocumentLayout::setAnchoringParagraphContentRect(const QRectF ¶graphContentRect) { d->anchoringParagraphContentRect = paragraphContentRect; } void KoTextDocumentLayout::setAnchoringLayoutEnvironmentRect(const QRectF &layoutEnvironmentRect) { d->anchoringLayoutEnvironmentRect = layoutEnvironmentRect; } void KoTextDocumentLayout::allowPositionInlineObject(bool allow) { d->allowPositionInlineObject = allow; } // This method is called by qt every time QTextLine.setWidth()/setNumColumns() is called void KoTextDocumentLayout::positionInlineObject(QTextInlineObject item, int position, const QTextFormat &format) { // Note: "item" used to be what was positioned. We don't actually use qtextinlineobjects anymore // for our inline objects, but get the id from the format. Q_UNUSED(item); //We are called before layout so that we can position objects Q_ASSERT(format.isCharFormat()); if (d->inlineTextObjectManager == 0) return; if (!d->allowPositionInlineObject) return; QTextCharFormat cf = format.toCharFormat(); KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); // We need some special treatment for anchors as they need to position their object during // layout and not this early KoAnchorInlineObject *anchorObject = dynamic_cast(obj); if (anchorObject && d->anchoringRootArea->associatedShape()) { // The type can only be KoShapeAnchor::AnchorAsCharacter since it's inline KoShapeAnchor *anchor = anchorObject->anchor(); d->foundAnchors.append(anchor); // if there is no anchor strategy set then create one if (!anchor->placementStrategy()) { anchor->setPlacementStrategy(new InlineAnchorStrategy(anchorObject, d->anchoringRootArea)); d->textAnchors.append(anchor); anchorObject->updatePosition(document(), position, cf); // by extension calls updateContainerModel } static_cast(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect); static_cast(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect); static_cast(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect); } else if (obj) { obj->updatePosition(document(), position, cf); } } // This method is called by KoTextLauoutArea every time it encounters a KoAnchorTextRange void KoTextDocumentLayout::positionAnchorTextRanges(int pos, int length, const QTextDocument *effectiveDocument) { if (!d->allowPositionInlineObject) return; if (!textRangeManager()) { return; } QHash ranges = textRangeManager()->textRangesChangingWithin(effectiveDocument, pos, pos+length, pos, pos+length); - foreach(KoTextRange *range, ranges.values()) { + foreach(KoTextRange *range, ranges) { KoAnchorTextRange *anchorRange = dynamic_cast(range); if (anchorRange) { // We need some special treatment for anchors as they need to position their object during // layout and not this early KoShapeAnchor *anchor = anchorRange->anchor(); d->foundAnchors.append(anchor); // At the beginAnchorCollecting the strategy is cleared, so this if will be entered // every time we layout a page (though not every time for the inner repeats due to anchors) if (!anchor->placementStrategy()) { int index = d->textAnchors.count(); anchor->setPlacementStrategy(new FloatingAnchorStrategy(anchorRange, d->anchoringRootArea)); // The purpose of following code-block is to be sure that our paragraph-anchors are // properly sorted by their z-index so the FloatingAnchorStrategy::checkStacking // logic stack in the proper order. Bug 274512 has a testdoc for this attached. if (index > 0 && anchor->anchorType() == KoShapeAnchor::AnchorParagraph && (anchor->horizontalRel() == KoShapeAnchor::HParagraph || anchor->horizontalRel() == KoShapeAnchor::HParagraphContent) && (anchor->horizontalPos() == KoShapeAnchor::HLeft || anchor->horizontalPos() == KoShapeAnchor::HRight)) { QTextBlock anchorBlock = document()->findBlock(anchorRange->position()); for(int i = index - 1; i >= 0; --i) { KoShapeAnchor *a = d->textAnchors[i]; if (a->anchorType() != anchor->anchorType()) break; if (a->horizontalPos() != anchor->horizontalPos()) break; if (document()->findBlock(a->textLocation()->position()) != anchorBlock) break; if (a->shape()->zIndex() < anchor->shape()->zIndex()) break; --index; } } d->textAnchors.insert(index, anchor); anchorRange->updateContainerModel(); } static_cast(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect); static_cast(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect); static_cast(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect); } KoAnnotation *annotation = dynamic_cast(range); if (annotation) { int position = range->rangeStart(); QTextBlock block = range->document()->findBlock(position); QTextLine line = block.layout()->lineForTextPosition(position - block.position()); QPointF refPos(line.cursorToX(position - block.position()), line.y()); KoShape *refShape = d->anchoringRootArea->associatedShape(); //KoTextShapeData *refTextShapeData; //refPos += QPointF(refTextShapeData->leftPadding(), -refTextShapeData->documentOffset() + refTextShapeData->topPadding()); refPos += QPointF(0, -d->anchoringRootArea->top()); refPos = refShape->absoluteTransformation(0).map(refPos); //FIXME we need a more precise position than anchorParagraph Rect emit foundAnnotation(annotation->annotationShape(), refPos); } } } void KoTextDocumentLayout::beginAnchorCollecting(KoTextLayoutRootArea *rootArea) { for(int i = 0; itextAnchors.size(); i++ ) { d->textAnchors[i]->setPlacementStrategy(0); } qDeleteAll(d->anchoredObstructions); d->anchoredObstructions.clear(); d->textAnchors.clear(); d->anchoringIndex = 0; d->anAnchorIsPlaced = false; d->anchoringRootArea = rootArea; d->allowPositionInlineObject = true; d->anchoringSoftBreak = INT_MAX; } void KoTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int position, const QTextFormat &format) { // Note: This method is called by qt during layout AND during paint Q_ASSERT(format.isCharFormat()); if (d->inlineTextObjectManager == 0) return; QTextCharFormat cf = format.toCharFormat(); KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf); if (!obj) { return; } if (d->isLayouting) { d->rootAreaForInlineObject[obj] = d->anchoringRootArea; } KoTextLayoutRootArea *rootArea = d->rootAreaForInlineObject.value(obj); if (rootArea == 0 || rootArea->associatedShape() == 0) return; QTextDocument *doc = document(); QVariant v; v.setValue(rootArea->page()); doc->addResource(KoTextDocument::LayoutTextPage, KoTextDocument::LayoutTextPageUrl, v); obj->resize(doc, item, position, cf, paintDevice()); registerInlineObject(item); } void KoTextDocumentLayout::emitLayoutIsDirty() { emit layoutIsDirty(); } void KoTextDocumentLayout::layout() { if (d->layoutBlocked) { return; } if (IndexGeneratorManager::instance(document())->generate()) { return; } Q_ASSERT(!d->isLayouting); d->isLayouting = true; bool finished; do { // Try to layout as long as d->restartLayout==true. This can happen for example if // a schedule layout call interrupts the layouting and asks for a new layout run. finished = doLayout(); } while (d->restartLayout); Q_ASSERT(d->isLayouting); d->isLayouting = false; if (finished) { // We are only finished with layouting if continuousLayout()==true. emit finishedLayout(); } } RootAreaConstraint constraintsForPosition(QTextFrame::iterator it, bool previousIsValid) { RootAreaConstraint constraints; constraints.visiblePageNumber = -1; constraints.newPageForced = false; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); if (block.isValid()) { constraints.masterPageName = block.blockFormat().stringProperty(KoParagraphStyle::MasterPageName); if (block.blockFormat().hasProperty(KoParagraphStyle::PageNumber)) { constraints.visiblePageNumber = block.blockFormat().intProperty(KoParagraphStyle::PageNumber); } constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore) == KoText::PageBreak; } if (table) { constraints.masterPageName = table->frameFormat().stringProperty(KoTableStyle::MasterPageName); if (table->frameFormat().hasProperty(KoTableStyle::PageNumber)) { constraints.visiblePageNumber = table->frameFormat().intProperty(KoTableStyle::PageNumber); } constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakBefore) == KoText::PageBreak; } if (!constraints.masterPageName.isEmpty()) { constraints.newPageForced = true; } if (previousIsValid && !constraints.newPageForced) { it--; block = it.currentBlock(); table = qobject_cast(it.currentFrame()); if (block.isValid()) { constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter) == KoText::PageBreak; } if (table) { constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakAfter) == KoText::PageBreak; } } return constraints; } bool KoTextDocumentLayout::doLayout() { delete d->layoutPosition; d->layoutPosition = new FrameIterator(document()->rootFrame()); d->y = 0; d->layoutScheduled = false; d->restartLayout = false; FrameIterator *transferedFootNoteCursor = 0; KoInlineNote *transferedContinuedNote = 0; int footNoteAutoCount = 0; KoTextLayoutRootArea *rootArea = 0; d->rootAreaList.clear(); int currentAreaNumber = 0; do { if (d->restartLayout) { return false; // Abort layouting to restart from the beginning. } // Build our request for our rootArea provider RootAreaConstraint constraints = constraintsForPosition(d->layoutPosition->it, currentAreaNumber > 0); // Request a new root-area. If NULL is returned then layouting is finished. bool newRootArea = false; rootArea = d->provider->provide(this, constraints, currentAreaNumber, &newRootArea); if (!rootArea) { // Out of space ? Nothing more to do break; } d->rootAreaList.append(rootArea); bool shouldLayout = false; if (rootArea->top() != d->y) { shouldLayout = true; } else if (rootArea->isDirty()) { shouldLayout = true; } else if (!rootArea->isStartingAt(d->layoutPosition)) { shouldLayout = true; } else if (newRootArea) { shouldLayout = true; } if (shouldLayout) { QRectF rect = d->provider->suggestRect(rootArea); d->freeObstructions = d->provider->relevantObstructions(rootArea); rootArea->setReferenceRect(rect.left(), rect.right(), d->y + rect.top(), d->y + rect.bottom()); beginAnchorCollecting(rootArea); // Layout all that can fit into that root area bool finished; FrameIterator *tmpPosition = 0; do { rootArea->setFootNoteCountInDoc(footNoteAutoCount); rootArea->setFootNoteFromPrevious(transferedFootNoteCursor, transferedContinuedNote); d->foundAnchors.clear(); delete tmpPosition; tmpPosition = new FrameIterator(d->layoutPosition); finished = rootArea->layoutRoot(tmpPosition); if (d->anAnchorIsPlaced) { d->anAnchorIsPlaced = false; } else { ++d->anchoringIndex; } } while (d->anchoringIndex < d->textAnchors.count()); foreach (KoShapeAnchor *anchor, d->textAnchors) { if (!d->foundAnchors.contains(anchor)) { d->anchoredObstructions.remove(anchor->shape()); d->anchoringSoftBreak = qMin(d->anchoringSoftBreak, anchor->textLocation()->position()); } } if (d->textAnchors.count() > 0) { delete tmpPosition; tmpPosition = new FrameIterator(d->layoutPosition); finished = rootArea->layoutRoot(tmpPosition); } delete d->layoutPosition; d->layoutPosition = tmpPosition; d->provider->doPostLayout(rootArea, newRootArea); updateProgress(d->layoutPosition->it); if (finished && !rootArea->footNoteCursorToNext()) { d->provider->releaseAllAfter(rootArea); // We must also delete them from our own list too int newsize = d->rootAreaList.indexOf(rootArea) + 1; while (d->rootAreaList.size() > newsize) { d->rootAreaList.removeLast(); } return true; // Finished layouting } if (d->layoutPosition->it == document()->rootFrame()->end()) { return true; // Finished layouting } if (!continuousLayout()) { return false; // Let's take a break. We are not finished layouting yet. } } else { // Drop following rootAreas delete d->layoutPosition; d->layoutPosition = new FrameIterator(rootArea->nextStartOfArea()); if (d->layoutPosition->it == document()->rootFrame()->end() && !rootArea->footNoteCursorToNext()) { d->provider->releaseAllAfter(rootArea); // We must also delete them from our own list too int newsize = d->rootAreaList.indexOf(rootArea) + 1; while (d->rootAreaList.size() > newsize) { d->rootAreaList.removeLast(); } return true; // Finished layouting } } transferedFootNoteCursor = rootArea->footNoteCursorToNext(); transferedContinuedNote = rootArea->continuedNoteToNext(); footNoteAutoCount += rootArea->footNoteAutoCount(); d->y = rootArea->bottom() + qreal(50); // (post)Layout method(s) just set this // 50 just to separate pages currentAreaNumber++; } while (transferedFootNoteCursor || d->layoutPosition->it != document()->rootFrame()->end()); return true; // Finished layouting } void KoTextDocumentLayout::scheduleLayout() { // Compress multiple scheduleLayout calls into one executeScheduledLayout. if (d->layoutScheduled) { return; } d->layoutScheduled = true; QTimer::singleShot(0, this, SLOT(executeScheduledLayout())); } void KoTextDocumentLayout::executeScheduledLayout() { // Only do the actual layout if it wasn't done meanwhile by someone else. if (!d->layoutScheduled) { return; } d->layoutScheduled = false; if (d->isLayouting) { // Since we are already layouting ask for a restart to be sure to also include // root-areas that got dirty and are before the currently processed root-area. d->restartLayout = true; } else { layout(); } } bool KoTextDocumentLayout::continuousLayout() const { return d->continuousLayout; } void KoTextDocumentLayout::setContinuousLayout(bool continuous) { d->continuousLayout = continuous; } void KoTextDocumentLayout::setBlockLayout(bool block) { d->layoutBlocked = block; } bool KoTextDocumentLayout::layoutBlocked() const { return d->layoutBlocked; } void KoTextDocumentLayout::setBlockChanges(bool block) { d->changesBlocked = block; } bool KoTextDocumentLayout::changesBlocked() const { return d->changesBlocked; } KoTextDocumentLayout* KoTextDocumentLayout::referencedLayout() const { return d->referencedLayout; } void KoTextDocumentLayout::setReferencedLayout(KoTextDocumentLayout *layout) { d->referencedLayout = layout; } QRectF KoTextDocumentLayout::frameBoundingRect(QTextFrame*) const { return QRectF(); } void KoTextDocumentLayout::clearInlineObjectRegistry(const QTextBlock &block) { d->inlineObjectExtents.clear(); d->inlineObjectOffset = block.position(); } void KoTextDocumentLayout::registerInlineObject(const QTextInlineObject &inlineObject) { KoInlineObjectExtent pos(inlineObject.ascent(),inlineObject.descent()); d->inlineObjectExtents.insert(d->inlineObjectOffset + inlineObject.textPosition(), pos); } KoInlineObjectExtent KoTextDocumentLayout::inlineObjectExtent(const QTextFragment &fragment) { if (d->inlineObjectExtents.contains(fragment.position())) return d->inlineObjectExtents[fragment.position()]; return KoInlineObjectExtent(); } void KoTextDocumentLayout::setContinuationObstruction(KoTextLayoutObstruction *continuationObstruction) { if (d->continuationObstruction) { delete d->continuationObstruction; } d->continuationObstruction = continuationObstruction; } QList KoTextDocumentLayout::currentObstructions() { if (d->continuationObstruction) { // () is needed so we append to a local list and not anchoredObstructions return (d->freeObstructions + d->anchoredObstructions.values()) << d->continuationObstruction; } else { return d->freeObstructions + d->anchoredObstructions.values(); } } QList KoTextDocumentLayout::rootAreas() const { return d->rootAreaList; } void KoTextDocumentLayout::removeRootArea(KoTextLayoutRootArea *rootArea) { int indexOf = rootArea ? qMax(0, d->rootAreaList.indexOf(rootArea)) : 0; for(int i = d->rootAreaList.count() - 1; i >= indexOf; --i) d->rootAreaList.removeAt(i); } QList KoTextDocumentLayout::shapes() const { QList listOfShapes; foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) { if (rootArea->associatedShape()) listOfShapes.append(rootArea->associatedShape()); } return listOfShapes; } void KoTextDocumentLayout::updateProgress(const QTextFrame::iterator &it) { QTextBlock block = it.currentBlock(); if (block.isValid()) { int percent = block.position() / qreal(document()->rootFrame()->lastPosition()) * 100.0; emit layoutProgressChanged(percent); } else if (it.currentFrame()) { int percent = it.currentFrame()->firstPosition() / qreal(document()->rootFrame()->lastPosition()) * 100.0; emit layoutProgressChanged(percent); } } diff --git a/libs/textlayout/KoTextLayoutArea.cpp b/libs/textlayout/KoTextLayoutArea.cpp index 91d9117cea6..5af10ec9799 100644 --- a/libs/textlayout/KoTextLayoutArea.cpp +++ b/libs/textlayout/KoTextLayoutArea.cpp @@ -1,2183 +1,2188 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008,2011 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2007-2008 Pierre Ducroquet * Copyright (C) 2009-2011 KO GmbH * Copyright (C) 2009-2011 C. Boemann * Copyright (C) 2010 Nandita Suri * Copyright (C) 2010 Ajay Pundhir * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Gopalakrishna Bhat A * Copyright (C) 2011 Stuart Dickson * * 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. */ #include "KoTextLayoutArea.h" #include "KoTextLayoutArea_p.h" #include "TableIterator.h" #include "ListItemsHelper.h" #include "RunAroundHelper.h" #include "KoTextDocumentLayout.h" #include "FrameIterator.h" #include "KoPointedAt.h" #include "KoCharAreaInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int qt_defaultDpiY(); Q_DECLARE_METATYPE(QTextDocument *) #define DropCapsAdditionalFormattingId 25602902 #define PresenterFontStretch 1.2 KoTextLayoutArea::KoTextLayoutArea(KoTextLayoutArea *p, KoTextDocumentLayout *documentLayout) : d (new Private) { d->parent = p; d->documentLayout = documentLayout; } KoTextLayoutArea::~KoTextLayoutArea() { qDeleteAll(d->tableAreas); qDeleteAll(d->footNoteAreas); qDeleteAll(d->preregisteredFootNoteAreas); delete d->startOfArea; delete d->endOfArea; delete d; } KoPointedAt KoTextLayoutArea::hitTest(const QPointF &p, Qt::HitTestAccuracy accuracy) const { QPointF point = p - QPointF(0, d->verticalAlignOffset); if (d->startOfArea == 0) // We have not been layouted yet return KoPointedAt(); KoPointedAt pointedAt; bool basicallyFound = false; QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we contain is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } int tableAreaIndex = 0; int tocIndex = 0; int footNoteIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } if (point.y() > d->tableAreas[tableAreaIndex]->top() && point.y() < d->tableAreas[tableAreaIndex]->bottom()) { return d->tableAreas[tableAreaIndex]->hitTest(point, accuracy); } ++tableAreaIndex; continue; } else if (subFrame) { if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (point.y() > d->endNotesArea->top() && point.y() < d->endNotesArea->bottom()) { pointedAt = d->endNotesArea->hitTest(point, accuracy); return pointedAt; } } break; } else { if (!block.isValid()) continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { // check if p is over table of content if (point.y() > d->generatedDocAreas[tocIndex]->top() && point.y() < d->generatedDocAreas[tocIndex]->bottom()) { pointedAt = d->generatedDocAreas[tocIndex]->hitTest(point, accuracy); pointedAt.position = block.position(); return pointedAt; } ++tocIndex; continue; } if (basicallyFound) // a subsequent table or lines have now had their chance return pointedAt; QTextLayout *layout = block.layout(); QTextFrame::iterator next = it; ++next; if (next != stop && next.currentFrame() == 0 && point.y() > layout->boundingRect().bottom()) { // just skip this block. continue; } for (int i = 0; i < layout->lineCount(); i++) { QTextLine line = layout->lineAt(i); if (block == d->startOfArea->it.currentBlock() && line.textStart() < d->startOfArea->lineTextStart) { continue; // this line is part of a previous layoutArea } if (point.y() > line.y() + line.height()) { pointedAt.position = block.position() + line.textStart() + line.textLength(); if (block == d->endOfArea->it.currentBlock() && line.textStart() + line.textLength() >= d->endOfArea->lineTextStart) { pointedAt.position = block.position() + line.xToCursor(point.x()); break; // this and following lines are part of a next layoutArea } continue; } if (accuracy == Qt::ExactHit && point.y() < line.y()) { // between lines return KoPointedAt(); } const QRectF lineRect = line.naturalTextRect(); if (accuracy == Qt::ExactHit && // left or right of line (point.x() < lineRect.left() || point.x() > lineRect.right())) { return KoPointedAt(); } if (point.x() > lineRect.x() + lineRect.width() && layout->textOption().textDirection() == Qt::RightToLeft) { // totally right of RTL text means the position is the start of the text. //TODO how about the other side? pointedAt.position = block.position() + line.textStart(); return pointedAt; } if (basicallyFound && point.y() < lineRect.y()) { // This was not same baseline so basicallyFound was correct return pointedAt; } if (point.x() > lineRect.x() + lineRect.width()) { // right of line basicallyFound = true; pointedAt.position = block.position() + line.textStart() + line.textLength(); continue; // don't break as next line may be on same baseline } pointedAt.position = block.position() + line.xToCursor(point.x()); QTextCursor tmpCursor(block); tmpCursor.setPosition(block.position() + line.xToCursor(point.x(), QTextLine::CursorOnCharacter) + 1); pointedAt.fillInLinks(tmpCursor, d->documentLayout->inlineTextObjectManager(), d->documentLayout->textRangeManager()); return pointedAt; } } //and finally test the footnotes point -= QPointF(0, bottom() - d->footNotesHeight); while (footNoteIndex < d->footNoteAreas.length()) { // check if p is over foot notes area if (point.y() > 0 && point.y() < d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()) { pointedAt = d->footNoteAreas[footNoteIndex]->hitTest(point, accuracy); return pointedAt; } point -= QPointF(0, d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top()); ++footNoteIndex; } return pointedAt; } QVector KoTextLayoutArea::generateCharAreaInfos() const { QVector result; if (d->startOfArea == 0 || d->endOfArea == 0) { // We have not been completely layouted yet debugTextLayout << "called when not completely layouted yet"; return result; } QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; int tableAreaIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextTable *table = qobject_cast(it.currentFrame()); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } result.append(d->tableAreas[tableAreaIndex]->generateCharAreaInfos()); ++tableAreaIndex; continue; } QTextFrame *subFrame = it.currentFrame(); if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { result.append(d->endNotesArea->generateCharAreaInfos()); } continue; } QTextBlock block = it.currentBlock(); if (!block.isValid()) { continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { result.append(d->generatedDocAreas[tocIndex]->generateCharAreaInfos()); ++tocIndex; continue; } // TODO: also include header/paragraph numbering/bullet points QTextLayout *layout = block.layout(); for (int i = 0; i < layout->lineCount(); ++i) { QTextLine line = layout->lineAt(i); if (block == d->startOfArea->it.currentBlock() && line.textStart() < d->startOfArea->lineTextStart) { continue; // this line is part of a previous layoutArea } if (block == d->endOfArea->it.currentBlock() && line.textStart() + line.textLength() >= d->endOfArea->lineTextStart) { break; // this and following lines are part of a next layoutArea } qreal xLeading; qreal xTrailing; for (int j = line.textStart(); j < line.textStart() + line.textLength(); ++j) { // TODO: support RTL xLeading = line.cursorToX(j, QTextLine::Leading); xTrailing = line.cursorToX(j, QTextLine::Trailing); QRectF rect(xLeading, line.y(), xTrailing-xLeading, line.height()); // TODO: at least height needs more work - result.append(KoCharAreaInfo(rect, block.text()[j])); + result.append(KoCharAreaInfo(rect, block.text().at(j))); } // TODO: perhaps only at end of paragraph (last qtextline) add linebreak, for in-paragraph linebreak // use real whitespace(s) found in original text (or see if forced linebreak) QRectF rect(xTrailing, line.y(), 1, line.height()); // TODO: better dummy width needed, with reasoning result.append(KoCharAreaInfo(rect, QLatin1Char('\n'))); } } qreal footNoteYOffset = bottom() - d->footNotesHeight; foreach(KoTextLayoutNoteArea *footerArea, d->footNoteAreas) { QVector footNoteCharAreaInfos = footerArea->generateCharAreaInfos(); QMutableVectorIterator it(footNoteCharAreaInfos); while (it.hasNext()) { KoCharAreaInfo &info = it.next(); info.rect.translate(0, footNoteYOffset); } result.append(footNoteCharAreaInfos); footNoteYOffset += footerArea->bottom() - footerArea->top(); } return result; } QRectF KoTextLayoutArea::selectionBoundingBox(QTextCursor &cursor) const { QRectF retval(-5E6, top(), 105E6, 0); if (d->startOfArea == 0) // We have not been layouted yet return QRectF(); if (d->endOfArea == 0) // no end area yet return QRectF(); QTextFrame::iterator it = d->startOfArea->it; QTextFrame::iterator stop = d->endOfArea->it; if (!stop.atEnd()) { if(!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) { // Last thing we show is a frame (table) or first part of a paragraph split in two // The stop point should be the object after that // However if stop is already atEnd we shouldn't increment further ++stop; } } QTextFrame *subFrame; int footNoteIndex = 0; qreal offset = bottom() - d->footNotesHeight; while (footNoteIndex < d->footNoteAreas.length()) { subFrame = d->footNoteFrames[footNoteIndex]; if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->footNoteAreas[footNoteIndex]->selectionBoundingBox(cursor).translated(0, offset) ; } offset += d->footNoteAreas[footNoteIndex]->bottom() - d->footNoteAreas[footNoteIndex]->top(); ++footNoteIndex; } int tableAreaIndex = 0; int tocIndex = 0; for (; it != stop && !it.atEnd(); ++it) { QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); if (table) { if (tableAreaIndex >= d->tableAreas.size()) { continue; } if (cursor.selectionEnd() < table->firstPosition()) { return retval.translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() > table->lastPosition()) { ++tableAreaIndex; continue; } if (cursor.selectionStart() >= table->firstPosition() && cursor.selectionEnd() <= table->lastPosition()) { return d->tableAreas[tableAreaIndex]->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() >= table->firstPosition()) { retval = d->tableAreas[tableAreaIndex]->boundingRect(); } else { retval |= d->tableAreas[tableAreaIndex]->boundingRect(); } ++tableAreaIndex; continue; } else if (subFrame) { if (it.currentFrame()->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (cursor.selectionEnd() < subFrame->firstPosition()) { return retval.translated(0, d->verticalAlignOffset); } if (cursor.selectionStart() > subFrame->lastPosition()) { break; } if (cursor.selectionStart() >= subFrame->firstPosition() && cursor.selectionEnd() <= subFrame->lastPosition()) { return d->endNotesArea->selectionBoundingBox(cursor).translated(0, d->verticalAlignOffset); } break; } } else { if (!block.isValid()) continue; } if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { if (cursor.selectionStart() <= block.position() && cursor.selectionEnd() >= block.position()) { retval |= d->generatedDocAreas[tocIndex]->boundingRect(); } ++tocIndex; continue; } if(cursor.selectionEnd() < block.position()) { return retval.translated(0, d->verticalAlignOffset); } if(cursor.selectionStart() >= block.position() && cursor.selectionStart() < block.position() + block.length()) { QTextLine line = block.layout()->lineForTextPosition(cursor.selectionStart() - block.position()); if (line.isValid()) { retval.setTop(line.y()); retval.setBottom(line.y()); } } if(cursor.selectionEnd() >= block.position() && cursor.selectionEnd() < block.position() + block.length()) { QTextLine line = block.layout()->lineForTextPosition(cursor.selectionEnd() - block.position()); if (line.isValid()) { retval.setBottom(line.y() + line.height()); if (line.ascent()==0) { // Block is empty from any visible content and has as such no height // but in that case the block font defines line height retval.setBottom(line.y() + 24); } if (cursor.selectionStart() == cursor.selectionEnd()) { // We only have a caret so let's set the rect a bit more narrow retval.setX(line.cursorToX(cursor.position() - block.position())); retval.setWidth(1); } } } // if the full paragraph is selected to add it to the rect. This makes sure we get a rect for the case // where the end of the selection lies is a different area. if (cursor.selectionEnd() >= block.position() + block.length() && cursor.selectionStart() <= block.position()) { QTextLine line = block.layout()->lineForTextPosition(block.length()-1); if (line.isValid()) { retval.setBottom(line.y() + line.height()); if (line.ascent()==0) { // Block is empty from any visible content and has as such no height // but in that case the block font defines line height retval.setBottom(line.y() + 24); } } } } return retval.translated(0, d->verticalAlignOffset); } bool KoTextLayoutArea::isStartingAt(FrameIterator *cursor) const { if (d->startOfArea) { return *d->startOfArea == *cursor; } return false; } QTextFrame::iterator KoTextLayoutArea::startTextFrameIterator() const { return d->startOfArea->it; } QTextFrame::iterator KoTextLayoutArea::endTextFrameIterator() const { return d->endOfArea->it; } void KoTextLayoutArea::backtrackKeepWithNext(FrameIterator *cursor) { QTextFrame::iterator it = cursor->it; while (!(it == d->startOfArea->it)) { --it; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); bool keepWithNext = false; if (table) { keepWithNext = table->format().boolProperty(KoTableStyle::KeepWithNext); //setBottom(tableArea->bottom() + d->footNotesHeight); } else if (subFrame) { Q_ASSERT(false); // there should never be an aux frame before normal layouted stuff } else if (block.isValid()) { keepWithNext = block.blockFormat().boolProperty(KoParagraphStyle::KeepWithNext); //setBottom(d->blockRects.last()->bottom() + d->footNotesHeight); } if (!keepWithNext) { cursor->it = ++it; break; } } } bool KoTextLayoutArea::layout(FrameIterator *cursor) { qDeleteAll(d->tableAreas); d->tableAreas.clear(); qDeleteAll(d->footNoteAreas); d->footNoteAreas.clear(); qDeleteAll(d->preregisteredFootNoteAreas); d->preregisteredFootNoteAreas.clear(); d->footNoteFrames.clear(); d->preregisteredFootNoteFrames.clear(); qDeleteAll(d->generatedDocAreas); d->generatedDocAreas.clear(); d->blockRects.clear(); delete d->endNotesArea; d->endNotesArea=0; if (d->copyEndOfArea && !d->copyEndOfArea->isValid()) { delete d->copyEndOfArea; d->copyEndOfArea = 0; } if (d->endOfArea && d->endOfArea->isValid()) { delete d->copyEndOfArea; d->copyEndOfArea = new FrameIterator(d->endOfArea); } delete d->startOfArea; delete d->endOfArea; d->dropCapsWidth = 0; d->dropCapsDistance = 0; d->startOfArea = new FrameIterator(cursor); d->endOfArea = 0; d->y = top(); d->neededWidth = 0; setBottom(top()); d->bottomSpacing = 0; d->footNoteAutoCount = 0; d->footNotesHeight = 0; d->preregisteredFootNotesHeight = 0; d->prevBorder = 0; d->prevBorderPadding = 0; if (d->footNoteCursorFromPrevious) { KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(d->continuedNoteFromPrevious, this, d->documentLayout); d->footNoteFrames.append(d->continuedNoteFromPrevious->textFrame()); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom()); footNoteArea->setAsContinuedArea(true); footNoteArea->layout(d->footNoteCursorFromPrevious); d->footNotesHeight += footNoteArea->bottom() - footNoteArea->top(); d->footNoteAreas.append(footNoteArea); } while (!cursor->it.atEnd()) { QTextBlock block = cursor->it.currentBlock(); QTextTable *table = qobject_cast(cursor->it.currentFrame()); QTextFrame *subFrame = cursor->it.currentFrame(); if (table) { QString masterPageName = table->frameFormat().property(KoTableStyle::MasterPageName).toString(); bool masterPageNameChanged = !masterPageName.isEmpty(); if (masterPageNameChanged) { cursor->masterPageName = masterPageName; } if (!virginPage()) { int breaktype = table->frameFormat().intProperty(KoTableStyle::BreakBefore); if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) || (acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } return false; } } // Let's create KoTextLayoutTableArea and let that handle the table KoTextLayoutTableArea *tableArea = new KoTextLayoutTableArea(table, this, d->documentLayout); d->tableAreas.append(tableArea); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } tableArea->setVirginPage(virginPage()); tableArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); if (tableArea->layoutTable(cursor->tableIterator(table)) == false) { d->endOfArea = new FrameIterator(cursor); d->y = tableArea->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(tableArea->boundingRect().left()); expandBoundingRight(tableArea->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(tableArea->boundingRect().left()); expandBoundingRight(tableArea->boundingRect().right()); d->bottomSpacing = 0; d->y = tableArea->bottom(); delete cursor->currentTableIterator; cursor->currentTableIterator = 0; } else if (subFrame) { if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { Q_ASSERT(d->endNotesArea == 0); d->endNotesArea = new KoTextLayoutEndNotesArea(this, d->documentLayout); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->endNotesArea->setVirginPage(virginPage()); d->endNotesArea->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); if (d->endNotesArea->layout(cursor->subFrameIterator(subFrame)) == false) { d->endOfArea = new FrameIterator(cursor); d->y = d->endNotesArea->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->endNotesArea->boundingRect().left()); expandBoundingRight(d->endNotesArea->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->endNotesArea->boundingRect().left()); expandBoundingRight(d->endNotesArea->boundingRect().right()); d->bottomSpacing = 0; d->y = d->endNotesArea->bottom(); delete cursor->currentSubFrameIterator; cursor->currentSubFrameIterator = 0; // we have layouted till the end of the document except for a blank block // which we should ignore ++(cursor->it); ++(cursor->it); break; } } else if (block.isValid()) { if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) { QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument); QTextDocument *generatedDocument = data.value(); // Let's create KoTextLayoutArea and let it handle the generated document KoTextLayoutArea *area = new KoTextLayoutArea(this, documentLayout()); d->generatedDocAreas.append(area); d->y += d->bottomSpacing; if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } area->setVirginPage(virginPage()); area->setAcceptsPageBreak(acceptsPageBreak()); area->setAcceptsColumnBreak(acceptsColumnBreak()); area->setReferenceRect(left(), right(), d->y, maximumAllowedBottom()); QTextLayout *blayout = block.layout(); blayout->beginLayout(); QTextLine line = blayout->createLine(); line.setNumColumns(0); line.setPosition(QPointF(left(), d->y)); blayout->endLayout(); if (area->layout(cursor->subFrameIterator(generatedDocument->rootFrame())) == false) { cursor->lineTextStart = 1; // fake we are not done d->endOfArea = new FrameIterator(cursor); d->y = area->bottom(); setBottom(d->y + d->footNotesHeight); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(area->boundingRect().left()); expandBoundingRight(area->boundingRect().right()); return false; } setVirginPage(false); // Expand bounding rect so if we have content outside we show it expandBoundingLeft(area->boundingRect().left()); expandBoundingRight(area->boundingRect().right()); d->bottomSpacing = 0; d->y = area->bottom(); delete cursor->currentSubFrameIterator; cursor->lineTextStart = -1; // fake we are done cursor->currentSubFrameIterator = 0; } else { // FIXME this doesn't work for cells inside tables. We probably should make it more // generic to handle such cases too. QString masterPageName = block.blockFormat().property(KoParagraphStyle::MasterPageName).toString(); bool masterPageNameChanged = !masterPageName.isEmpty(); if (masterPageNameChanged) { cursor->masterPageName = masterPageName; } if (!virginPage()) { int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore); if ((acceptsPageBreak() && (masterPageNameChanged || (breaktype == KoText::PageBreak))) ||(acceptsColumnBreak() && (breaktype == KoText::ColumnBreak))) { d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } return false; } } if (layoutBlock(cursor) == false) { if (cursor->lineTextStart == -1) { //Nothing was added so lets backtrack keep-with-next backtrackKeepWithNext(cursor); } d->endOfArea = new FrameIterator(cursor); setBottom(d->y + d->footNotesHeight); d->blockRects.last().setBottom(d->y); return false; } d->extraTextIndent = 0; int breaktype = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter); if ((acceptsPageBreak() && (breaktype & KoText::PageBreak)) || (acceptsColumnBreak() && (breaktype & KoText::ColumnBreak))) { Q_ASSERT(!cursor->it.atEnd()); QTextFrame::iterator nextIt = cursor->it; ++nextIt; bool wasIncremented = !nextIt.currentFrame(); if (wasIncremented) cursor->it = nextIt; d->endOfArea = new FrameIterator(cursor); if (!wasIncremented) ++(cursor->it); setBottom(d->y + d->footNotesHeight); d->blockRects.last().setBottom(d->y); return false; } } } bool atEnd = cursor->it.atEnd(); if (!atEnd) { ++(cursor->it); } } d->endOfArea = new FrameIterator(cursor); d->y = qMin(maximumAllowedBottom(), d->y + d->bottomSpacing); setBottom(d->y + d->footNotesHeight); if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } if (d->maximumAllowedWidth>0) { d->right += d->neededWidth - d->width; d->maximumAllowedWidth = 0; setVirginPage(true); KoTextLayoutArea::layout(new FrameIterator(d->startOfArea)); } return true; // we have layouted till the end of the frame } QTextLine KoTextLayoutArea::Private::restartLayout(QTextBlock &block, int lineTextStartOfLastKeep) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); QPointF stashedCounterPosition = blockData.counterPosition(); QVector stashedLines; QTextLine line; for(int i = 0; i < layout->lineCount(); i++) { QTextLine l = layout->lineAt(i); if (l.textStart() >= lineTextStartOfLastKeep) { break; } LineKeeper lk; lk.lineWidth = l.width(); lk.columns = l.textLength(); lk.position = l.position(); stashedLines.append(lk); } layout->clearLayout(); layout->beginLayout(); line = layout->createLine(); return recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); } void KoTextLayoutArea::Private::stashRemainingLayout(QTextBlock &block, int lineTextStartOfFirstKeep, QVector &stashedLines, QPointF &stashedCounterPosition) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); stashedCounterPosition = blockData.counterPosition(); QTextLine line; for(int i = 0; i < layout->lineCount(); i++) { QTextLine l = layout->lineAt(i); if (l.textStart() < lineTextStartOfFirstKeep) { continue; } LineKeeper lk; lk.lineWidth = l.width(); lk.columns = l.textLength(); lk.position = l.position(); stashedLines.append(lk); } } QTextLine KoTextLayoutArea::Private::recreatePartialLayout(QTextBlock &block, const QVector &stashedLines, QPointF &stashedCounterPosition, QTextLine &line) { QTextLayout *layout = block.layout(); KoTextBlockData blockData(block); documentLayout->allowPositionInlineObject(false); if (layout->lineCount() == 1) { blockData.setCounterPosition(stashedCounterPosition); } foreach(const LineKeeper &lk, stashedLines) { line.setLineWidth(lk.lineWidth); if (lk.columns != line.textLength()) { // As setNumColumns might break differently we only use it if setLineWidth doesn't give // the same textLength as we had before line.setNumColumns(lk.columns, lk.lineWidth); } line.setPosition(lk.position); line = layout->createLine(); if (!line.isValid()) break; } documentLayout->allowPositionInlineObject(true); return line; } static bool compareTab(const QTextOption::Tab &tab1, const QTextOption::Tab &tab2) { return tab1.position < tab2.position; } // layoutBlock() method is structured like this: // // 1) Setup various helper values // a) related to or influenced by lists // b) related to or influenced by dropcaps // c) related to or influenced by margins // d) related to or influenced by tabs // e) related to or influenced by borders // f) related to or influenced by list counters // 2)layout each line (possibly restarting where we stopped earlier) // a) fit line into sub lines with as needed for text runaround // b) break if we encounter softbreak // c) make sure we keep above maximumAllowedBottom // d) calls addLine() // e) update dropcaps related variables bool KoTextLayoutArea::layoutBlock(FrameIterator *cursor) { QTextBlock block(cursor->it.currentBlock()); KoTextBlockData blockData(block); KoParagraphStyle pStyle(block.blockFormat(), block.charFormat()); - + qInfo()<<"layoutBlock:"<copyEndOfArea && d->copyEndOfArea->it.currentBlock() == block); KoText::Direction dir = pStyle.textProgressionDirection(); if (dir == KoText::InheritDirection) dir = parentTextDirection(); if (dir == KoText::AutoDirection) d->isRtl = block.text().isRightToLeft(); else d->isRtl = dir == KoText::RightLeftTopBottom; // initialize list item stuff for this parag. QTextList *textList = block.textList(); QTextListFormat listFormat; QTextCharFormat labelFormat; if (textList) { listFormat = textList->format(); if (block.text().size() == 0 || d->documentLayout->wordprocessingMode()) { labelFormat = block.charFormat(); } else { labelFormat = block.begin().fragment().charFormat(); } if (d->documentLayout->styleManager()) { const int id = listFormat.intProperty(KoListStyle::CharacterStyleId); KoCharacterStyle *cs = d->documentLayout->styleManager()->characterStyle(id); if (cs) { cs->applyStyle(labelFormat); cs->ensureMinimalProperties(labelFormat); } } // fetch the text-properties of the label if (listFormat.hasProperty(KoListStyle::CharacterProperties)) { QVariant v = listFormat.property(KoListStyle::CharacterProperties); QSharedPointer textPropertiesCharStyle = v.value< QSharedPointer >(); if (!textPropertiesCharStyle.isNull()) { textPropertiesCharStyle->applyStyle(labelFormat); textPropertiesCharStyle->ensureMinimalProperties(labelFormat); } } // Calculate the correct font point size taking into account the current // block format and the relative font size percent if the size is not absolute if (listFormat.hasProperty(KoListStyle::RelativeBulletSize)) { qreal percent = listFormat.property(KoListStyle::RelativeBulletSize).toDouble(); labelFormat.setFontPointSize((percent*labelFormat.fontPointSize())/100.00); } QFont font(labelFormat.font(), d->documentLayout->paintDevice()); if (!blockData.hasCounterData()) { ListItemsHelper lih(textList, font); lih.recalculateBlock(block); } blockData.setLabelFormat(labelFormat); } else { // make sure it is empty blockData.clearCounter(); } QTextLayout *layout = block.layout(); QTextOption option = layout->textOption(); option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); option.setAlignment(QStyle::visualAlignment(d->isRtl ? Qt::RightToLeft : Qt::LeftToRight, pStyle.alignment())); if (d->isRtl) { option.setTextDirection(Qt::RightToLeft); // For right-to-left we need to make sure that trailing spaces are included into the QTextLine naturalTextWidth // and naturalTextRect calculation so they are proper handled in the RunAroundHelper. For left-to-right we do // not like to include trailing spaces in the calculations cause else justified text would not look proper // justified. Seems for right-to-left we have to accept that justified text will not look proper justified then. // only set it for justified text as otherwise we will cut of text at the beginning of the line if (pStyle.alignment() == Qt::AlignJustify) { option.setFlags(QTextOption::IncludeTrailingSpaces); } } else { option.setFlags(0); option.setTextDirection(Qt::LeftToRight); } option.setUseDesignMetrics(true); //========== // Drop caps //========== d->dropCapsNChars = 0; if (cursor->lineTextStart == -1) { // first remove any drop-caps related formatting that's already there in the layout. // we'll do it all afresh now. QList formatRanges = layout->additionalFormats(); for (QList< QTextLayout::FormatRange >::Iterator iter = formatRanges.begin(); iter != formatRanges.end(); ) { if (iter->format.boolProperty(DropCapsAdditionalFormattingId)) { iter = formatRanges.erase(iter); } else { ++iter; } } if (formatRanges.count() != layout->additionalFormats().count()) layout->setAdditionalFormats(formatRanges); bool dropCaps = pStyle.dropCaps(); int dropCapsLength = pStyle.dropCapsLength(); int dropCapsLines = pStyle.dropCapsLines(); if (dropCaps && dropCapsLines > 1 && block.length() > 1) { QString blockText = block.text(); d->dropCapsDistance = pStyle.dropCapsDistance(); if (dropCapsLength == 0) { // means whole word is to be dropped int firstNonSpace = blockText.indexOf(QRegExp("[^ ]")); dropCapsLength = blockText.indexOf(QRegExp("\\W"), firstNonSpace); } else { // LibreOffice skips softbreaks but not spaces. We will do the same QTextCursor c1(block); c1.setPosition(block.position()); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoTextSoftPageBreak *softPageBreak = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (softPageBreak) { dropCapsLength++; } } dropCapsLength = qMin(dropCapsLength, blockText.length() - 1); if (dropCapsLength > 0) { // increase the size of the dropped chars QTextCursor blockStart(block); QTextLayout::FormatRange dropCapsFormatRange; dropCapsFormatRange.format = blockStart.charFormat(); // find out lineHeight for this block. QTextBlock::iterator it = block.begin(); QTextFragment lineRepresentative = it.fragment(); qreal lineHeight = pStyle.lineHeightAbsolute(); qreal dropCapsHeight = 0; if (lineHeight == 0) { lineHeight = lineRepresentative.charFormat().fontPointSize(); qreal linespacing = pStyle.lineSpacing(); if (linespacing == 0) { // unset qreal percent = pStyle.lineHeightPercent(); if (percent != 0) linespacing = lineHeight * ((percent - 100) / 100.0); else if (linespacing == 0) linespacing = lineHeight * 0.2; // default } dropCapsHeight = linespacing * (dropCapsLines-1); } const qreal minimum = pStyle.minimumLineHeight(); if (minimum > 0.0) { lineHeight = qMax(lineHeight, minimum); } dropCapsHeight += lineHeight * dropCapsLines; int dropCapsStyleId = pStyle.dropCapsTextStyleId(); KoCharacterStyle *dropCapsCharStyle = 0; if (dropCapsStyleId > 0 && d->documentLayout->styleManager()) { dropCapsCharStyle = d->documentLayout->styleManager()->characterStyle(dropCapsStyleId); dropCapsCharStyle->applyStyle(dropCapsFormatRange.format); } QFont f(dropCapsFormatRange.format.font(), d->documentLayout->paintDevice()); QString dropCapsText(block.text().left(dropCapsLength)); f.setPointSizeF(dropCapsHeight); for (int i=0; i < 5; ++i) { QTextLayout tmplayout(dropCapsText, f); tmplayout.setTextOption(option); tmplayout.beginLayout(); QTextLine tmpline = tmplayout.createLine(); tmplayout.endLayout(); d->dropCapsWidth = tmpline.naturalTextWidth(); QFontMetricsF fm(f, documentLayout()->paintDevice()); QRectF rect = fm.tightBoundingRect(dropCapsText); const qreal diff = dropCapsHeight - rect.height(); dropCapsPositionAdjust = rect.top() + fm.ascent(); if (qAbs(diff) < 0.5) // good enough break; const qreal adjustment = diff * (f.pointSizeF() / rect.height()); // warnTextLayout << "adjusting with" << adjustment; f.setPointSizeF(f.pointSizeF() + adjustment); } dropCapsFormatRange.format.setFontPointSize(f.pointSizeF()); dropCapsFormatRange.format.setProperty(DropCapsAdditionalFormattingId, (QVariant) true); dropCapsFormatRange.start = 0; dropCapsFormatRange.length = dropCapsLength; formatRanges.append(dropCapsFormatRange); layout->setAdditionalFormats(formatRanges); d->dropCapsNChars = dropCapsLength; dropCapsAffectsNMoreLines = (d->dropCapsNChars > 0) ? dropCapsLines : 0; } } } //======== // Margins //======== qreal startMargin = block.blockFormat().leftMargin(); qreal endMargin = block.blockFormat().rightMargin(); if (d->isRtl) { qSwap(startMargin, endMargin); } d->indent = textIndent(block, textList, pStyle) + d->extraTextIndent; qreal labelBoxWidth = 0; qreal labelBoxIndent = 0; if (textList) { if (listFormat.boolProperty(KoListStyle::AlignmentMode)) { // according to odf 1.2 17.20 list margin should be used when paragraph margin is // not specified by the auto style (additionally LO/OO uses 0 as condition so we do too) int id = pStyle.styleId(); bool set = false; if (id && d->documentLayout->styleManager()) { KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); if (originalParagraphStyle->leftMargin() != startMargin) { set = (startMargin != 0); } } else { set = (startMargin != 0); } if (! set) { startMargin = listFormat.doubleProperty(KoListStyle::Margin); } labelBoxWidth = blockData.counterWidth(); Qt::Alignment align = static_cast(listFormat.intProperty(KoListStyle::Alignment)); if (align == 0) { align = Qt::AlignLeft; } if (align & Qt::AlignLeft) { d->indent += labelBoxWidth; } else if (align & Qt::AlignHCenter) { d->indent += labelBoxWidth/2; } labelBoxIndent = d->indent - labelBoxWidth; } else { labelBoxWidth = blockData.counterSpacing() + blockData.counterWidth(); } } d->width = right() - left(); d->width -= startMargin + endMargin; d->x = left() + (d->isRtl ? 0.0 : startMargin); d->documentLayout->clearInlineObjectRegistry(block); //======== // Tabs //======== const QVector tabs = pStyle.tabPositions(); // Handle tabs relative to startMargin qreal tabOffset = -d->indent; if (!d->documentLayout->relativeTabs(block)) { tabOffset -= startMargin; } // Make a list of tabs that Qt can use QList qTabs; // Note: Converting to Qt tabs is needed as long as we use Qt for layout, but we // loose the possibility to do leader chars. foreach (const KoText::Tab &kTab, tabs) { qreal value = kTab.position; if (value == MaximumTabPos) { // MaximumTabPos is used in index generators // note: we subtract right margin as this is where the tab should be // note: we subtract indent so tab is not relative to it // note: we subtract left margin so tab is not relative to it // if rtl the above left/right reasons swap but formula stays the same // -tabOfset is just to cancel that we add it next // -2 is to avoid wrap at right edge to the next line value = right() - left() - startMargin - endMargin - d->indent - tabOffset - 2; } // conversion here is required because Qt thinks in device units and we don't value *= qt_defaultDpiY() / 72.0; value += tabOffset * qt_defaultDpiY() / 72.0; QTextOption::Tab tab; tab.position = value; tab.type = kTab.type; tab.delimiter = kTab.delimiter; qTabs.append(tab); } qreal presentationListTabValue(0.0); // for use in presentationListTabWorkaround // For some lists we need to add a special list tab according to odf 1.2 19.830 if (textList && listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab) { qreal listTab = 0; if (listFormat.hasProperty(KoListStyle::TabStopPosition)) { listTab = listFormat.doubleProperty(KoListStyle::TabStopPosition); if (!d->documentLayout->relativeTabs(block)) { // How list tab is defined if fixed tabs: // listTab //|>-------------------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin listTab -= startMargin; } else { // How list tab is defined if relative tabs: // It's relative to startMargin - list.startMargin // listTab // |>-------------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>--------------------| // startMargin | // |>-------------| // list.margin listTab -= listFormat.doubleProperty(KoListStyle::Margin); } } // How list tab is defined now: // listTab // |>-----| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin presentationListTabValue = listTab; listTab -= d->indent; // And now listTab is like this: // x() // | listTab // |>---------------| // d->indent // |---------<| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin // conversion here is required because Qt thinks in device units and we don't listTab *= qt_defaultDpiY() / 72.0; QTextOption::Tab tab; tab.position = listTab; tab.type = d->isRtl ? QTextOption::RightTab : QTextOption::LeftTab; qTabs.append(tab); } // We need to sort as the MaximumTabPos may be converted to a value that really // should be in the middle, and listtab needs to be sorted in too qSort(qTabs.begin(), qTabs.end(), compareTab); // Regular interval tabs. Since Qt doesn't handle regular interval tabs offset // by a fixed number we need to create the regular tabs ourselves. qreal tabStopDistance = pStyle.tabStopDistance() * qt_defaultDpiY() / 72.0; if (tabStopDistance <= 0) { tabStopDistance = d->documentLayout->defaultTabSpacing() * qt_defaultDpiY() / 72.0; } qreal regularSpacedTabPos = -d->indent * qt_defaultDpiY() / 72.0 -0.1; // first possible position if (!qTabs.isEmpty()) { regularSpacedTabPos = qTabs.last().position; } regularSpacedTabPos -= tabOffset * qt_defaultDpiY() / 72.0; if (regularSpacedTabPos < 0) { regularSpacedTabPos = -int(-regularSpacedTabPos / tabStopDistance) * tabStopDistance; } else { regularSpacedTabPos = (int(regularSpacedTabPos / tabStopDistance) + 1) * tabStopDistance; } regularSpacedTabPos += tabOffset * qt_defaultDpiY() / 72.0; while (regularSpacedTabPos < MaximumTabPos) { QTextOption::Tab tab; tab.position = regularSpacedTabPos; qTabs.append(tab); regularSpacedTabPos += tabStopDistance; } option.setTabs(qTabs); // conversion here is required because Qt thinks in device units and we don't option.setTabStop(tabStopDistance * qt_defaultDpiY() / 72.); layout->setTextOption(option); // ============== // Possibly store the old layout of lines in case we end up splitting the paragraph at the same position // ============== QVector stashedLines; QPointF stashedCounterPosition; if (lastOfPreviousRun) { // we have been layouted before, and the block ended on the following page so better // stash the layout for later d->stashRemainingLayout(block, d->copyEndOfArea->lineTextStart, stashedLines, stashedCounterPosition); } // ============== // Setup line and possibly restart paragraph continuing from previous other area // ============== QTextLine line; if (cursor->lineTextStart == -1) { layout->beginLayout(); line = layout->createLine(); cursor->fragmentIterator = block.begin(); } else { line = d->restartLayout(block, cursor->lineTextStart); d->indent = d->extraTextIndent; } if (block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // Unnumbered list items act like "following lines" in a numbered block d->indent = 0; } // ============== // List label/counter positioning // ============== if (textList && block.layout()->lineCount() == 1 && ! block.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // If first line in a list then set the counterposition. Following lines in the same // list-item have nothing to do with the counter. if (listFormat.boolProperty(KoListStyle::AlignmentMode) == false) { qreal minLabelWidth = listFormat.doubleProperty(KoListStyle::MinimumWidth); if (!d->isRtl) { d->x += listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; } d->width -= listFormat.doubleProperty(KoListStyle::Indent) + minLabelWidth; d->indent += labelBoxWidth - minLabelWidth; blockData.setCounterPosition(QPointF(d->x + d->indent - labelBoxWidth, d->y)); } else if (labelBoxWidth > 0.0 || blockData.counterText().length() > 0) { // Alignmentmode and there is a label (double check needed to account for both // picture bullets and non width chars) blockData.setCounterPosition(QPointF(d->x + labelBoxIndent, d->y)); if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::ListTab && !presentationListTabWorkaround(textIndent(block, textList, pStyle), labelBoxWidth, presentationListTabValue)) { foreach(QTextOption::Tab tab, qTabs) { qreal position = tab.position * 72. / qt_defaultDpiY(); if (position > 0.0) { d->indent += position; break; } } //And finally it's like this: // x() // d->indent // |>-----| // LABEL TEXT STARTS HERE AND GOES ON // TO THE NEXT LINE //|>------------------| // startMargin } else if (listFormat.intProperty(KoListStyle::LabelFollowedBy) == KoListStyle::Space) { QFontMetrics fm(labelFormat.font(), d->documentLayout->paintDevice()); d->indent += fm.width(' '); } // default needs to be no space so presentationListTabWorkaround above makes us go here } } // Whenever we relayout the markup layout becomes invalid blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, false); blockData.setMarkupsLayoutValidity(KoTextBlockData::Grammar, false); // ============== // Now once we know the physical context we can work on the borders of the paragraph // ============== if (block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->y += d->bottomSpacing; d->bottomSpacing = 0; d->blockRects.append(QRectF(d->x, d->y, d->width, 10.0)); } else { handleBordersAndSpacing(blockData, &block); } // Expand bounding rect so if we have content outside we show it expandBoundingLeft(d->blockRects.last().x()); expandBoundingRight(d->blockRects.last().right()); // ============== // Create the lines of this paragraph // ============== RunAroundHelper runAroundHelper; runAroundHelper.setObstructions(documentLayout()->currentObstructions()); qreal maxLineHeight = 0; qreal y_justBelowDropCaps = 0; bool anyLineAdded = false; int numBaselineShifts = 0; while (line.isValid()) { runAroundHelper.setLine(this, line); runAroundHelper.setObstructions(documentLayout()->currentObstructions()); QRectF anchoringRect = d->blockRects.last(); anchoringRect.setTop(d->anchoringParagraphContentTop); documentLayout()->setAnchoringParagraphContentRect(anchoringRect); anchoringRect.setLeft(left()); anchoringRect.setWidth(right() - left()); anchoringRect.setTop(d->anchoringParagraphTop); documentLayout()->setAnchoringParagraphRect(anchoringRect); documentLayout()->setAnchoringLayoutEnvironmentRect(layoutEnvironmentRect()); runAroundHelper.fit( /* resetHorizontalPosition */ false, /* rightToLeft */ d->isRtl, QPointF(x(), d->y)); documentLayout()->positionAnchorTextRanges(block.position()+line.textStart(), line.textLength(), block.document()); qreal bottomOfText = line.y() + line.height(); bool softBreak = false; bool moreInMiddle = d->y > maximumAllowedBottom() - 150; if (acceptsPageBreak() && !pStyle.nonBreakableLines() && moreInMiddle) { int softBreakPos = -1; QString text = block.text(); int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); while (pos >= 0 && pos <= line.textStart() + line.textLength()) { QTextCursor c1(block); c1.setPosition(block.position() + pos); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoTextSoftPageBreak *softPageBreak = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (softPageBreak) { softBreakPos = pos; break; } pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); } if (softBreakPos >= 0 && softBreakPos < line.textStart() + line.textLength()) { line.setNumColumns(softBreakPos - line.textStart() + 1, line.width()); softBreak = true; // if the softBreakPos is at the start of the line stop here so // we don't add a line here. That fixes the problem that e.g. the counter is before // the page break and the text is after the page break if (!virginPage() && softBreakPos == 0) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); + qInfo()<<1<<"layoutBlock: softbreak at start of line"; return false; } } } if (documentLayout()->anchoringSoftBreak() <= block.position() + line.textStart() + line.textLength()) { //don't add an anchor that has been moved away line.setNumColumns(documentLayout()->anchoringSoftBreak() - block.position() - line.textStart(), line.width()); softBreak = true; // if the softBreakPos is at the start of the block stop here so // we don't add a line here. That fixes the problem that e.g. the counter is before // the page break and the text is after the page break if (!virginPage() && documentLayout()->anchoringSoftBreak() == block.position()) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); + qInfo()<<2<<"layoutBlock: softbreak at start of line"; return false; } } findFootNotes(block, line, bottomOfText); if (bottomOfText > maximumAllowedBottom()) { // We can not fit line within our allowed space // in case we resume layout on next page the line is reused later // but if not then we need to make sure the line becomes invisible // we use d->maximalAllowedBottom because we want to be below // footnotes too. if (!virginPage() && pStyle.nonBreakableLines()) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); cursor->lineTextStart = -1; d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); + qInfo()<<1<<"layoutBlock: line does not fit in allowed space"; return false; //to indicate block was not done! } if (!virginPage() && pStyle.orphanThreshold() != 0 && pStyle.orphanThreshold() > numBaselineShifts) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); cursor->lineTextStart = -1; d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); + qInfo()<<2<<"layoutBlock: line does not fit in allowed space"; return false; //to indicate block was not done! } if (!virginPage() || anyLineAdded) { line.setPosition(QPointF(x(), d->maximalAllowedBottom)); d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); clearPreregisteredFootNotes(); + qInfo()<<3<<"layoutBlock: line does not fit in allowed space"; return false; //to indicate block was not done! } } confirmFootNotes(); anyLineAdded = true; maxLineHeight = qMax(maxLineHeight, addLine(line, cursor, blockData)); d->neededWidth = qMax(d->neededWidth, line.naturalTextWidth() + d->indent); if (!runAroundHelper.stayOnBaseline() && !(block.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable) && block.length() <= 1)) { d->y += maxLineHeight; maxLineHeight = 0; d->indent = 0; d->extraTextIndent = 0; ++numBaselineShifts; } // drop caps if (d->dropCapsNChars > 0) { // we just laid out the dropped chars y_justBelowDropCaps = d->y; // save the y position just below the dropped characters d->y = line.y(); // keep the same y for the next line line.setPosition(line.position() - QPointF(0, dropCapsPositionAdjust)); d->dropCapsNChars -= line.textLength(); } else if (dropCapsAffectsNMoreLines > 0) { // we just laid out a drop-cap-affected line dropCapsAffectsNMoreLines--; if (dropCapsAffectsNMoreLines == 0) { // no more drop-cap-affected lines if (d->y < y_justBelowDropCaps) d->y = y_justBelowDropCaps; // make sure d->y is below the dropped characters y_justBelowDropCaps = 0; d->dropCapsWidth = 0; d->dropCapsDistance = 0; } } documentLayout()->positionAnchoredObstructions(); // line fitted so try and do the next one line = layout->createLine(); if (!line.isValid()) { break; // no more line means our job is done } cursor->lineTextStart = line.textStart(); if (softBreak) { d->recreatePartialLayout(block, stashedLines, stashedCounterPosition, line); layout->endLayout(); return false; // page-break means we need to start again on the next page } } d->bottomSpacing = pStyle.bottomMargin(); layout->endLayout(); setVirginPage(false); cursor->lineTextStart = -1; //set lineTextStart to -1 and returning true indicate new block block.setLineCount(layout->lineCount()); return true; } bool KoTextLayoutArea::presentationListTabWorkaround(qreal indent, qreal labelBoxWidth, qreal presentationListTabValue) { if (!d->documentLayout->wordprocessingMode() && indent < 0.0) { // Impress / Powerpoint expects the label to be before the text if (indent + labelBoxWidth >= presentationListTabValue) { // but here is an unforseen overlap with normal text return true; } } return false; } qreal KoTextLayoutArea::textIndent(const QTextBlock &block, QTextList *textList, const KoParagraphStyle &pStyle) const { if (pStyle.autoTextIndent()) { // if auto-text-indent is set, // return an indent approximately 3-characters wide as per current font QTextCursor blockCursor(block); qreal guessGlyphWidth = QFontMetricsF(blockCursor.charFormat().font()).width('x'); return guessGlyphWidth * 3; } qreal blockTextIndent = block.blockFormat().textIndent(); if (textList && textList->format().boolProperty(KoListStyle::AlignmentMode)) { // according to odf 1.2 17.20 list text indent should be used when paragraph text indent is // not specified (additionally LO/OO uses 0 as condition so we do too) int id = pStyle.styleId(); bool set = false; if (id && d->documentLayout->styleManager()) { KoParagraphStyle *originalParagraphStyle = d->documentLayout->styleManager()->paragraphStyle(id); if (originalParagraphStyle->textIndent() != blockTextIndent) { set = (blockTextIndent != 0); } } else { set = (blockTextIndent != 0); } if (! set) { return textList->format().doubleProperty(KoListStyle::TextIndent); } } return blockTextIndent; } void KoTextLayoutArea::setExtraTextIndent(qreal extraTextIndent) { d->extraTextIndent = extraTextIndent; } qreal KoTextLayoutArea::x() const { if (d->isRtl) { return d->x; } else { if (d->dropCapsNChars > 0 || d->dropCapsWidth == 0) return d->x + d->indent ; else return d->x + d->indent + d->dropCapsWidth + d->dropCapsDistance; } } qreal KoTextLayoutArea::width() const { if (d->dropCapsNChars > 0) { return d->dropCapsWidth; } qreal width = d->width; if (d->maximumAllowedWidth > 0) { // lets use that instead but remember all the indent stuff we have calculated width = d->width - (d->right - d->left) + d->maximumAllowedWidth; } return width - d->indent - d->dropCapsWidth - d->dropCapsDistance; } void KoTextLayoutArea::setAcceptsPageBreak(bool accept) { d->acceptsPageBreak = accept; } bool KoTextLayoutArea::acceptsPageBreak() const { return d->acceptsPageBreak; } void KoTextLayoutArea::setAcceptsColumnBreak(bool accept) { d->acceptsColumnBreak = accept; } bool KoTextLayoutArea::acceptsColumnBreak() const { return d->acceptsColumnBreak; } void KoTextLayoutArea::setVirginPage(bool virgin) { d->virginPage = virgin; } bool KoTextLayoutArea::virginPage() const { return d->virginPage; } void KoTextLayoutArea::setVerticalAlignOffset(qreal offset) { d->boundingRect.setTop(d->top + qMin(qreal(0.0), offset)); d->boundingRect.setBottom(d->bottom + qMax(qreal(0.0), offset)); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); d->verticalAlignOffset = offset; } qreal KoTextLayoutArea::verticalAlignOffset() const { return d->verticalAlignOffset; } qreal KoTextLayoutArea::addLine(QTextLine &line, FrameIterator *cursor, KoTextBlockData &blockData) { QTextBlock block = cursor->it.currentBlock(); QTextBlockFormat format = block.blockFormat(); KoParagraphStyle style(format, block.charFormat()); if (block.textList() && block.layout()->lineCount() == 1) { Qt::Alignment alignment = format.alignment(); if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) { if (alignment & Qt::AlignLeft) { alignment = Qt::AlignRight; } else if (alignment & Qt::AlignRight) { alignment = Qt::AlignLeft; } } alignment &= Qt::AlignRight | Qt::AlignLeft | Qt::AlignHCenter; // First line, lets check where the line ended up and adjust the positioning of the counter. qreal newX; if (alignment & Qt::AlignHCenter) { const qreal padding = (line.width() - line.naturalTextWidth()) / 2; newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); } else if (alignment & Qt::AlignRight) { const qreal padding = line.width() - line.naturalTextWidth(); newX = blockData.counterPosition().x() + (d->isRtl ? -padding : padding); } else { newX = blockData.counterPosition().x(); } if (d->isRtl) { newX = line.x() + line.naturalTextWidth() + line.x() + d->indent - newX; } blockData.setCounterPosition(QPointF(newX, blockData.counterPosition().y())); } qreal height = 0; qreal breakHeight = 0.0; qreal ascent = 0.0; qreal descent = 0.0; const bool useFontProperties = format.boolProperty(KoParagraphStyle::LineSpacingFromFont); if (cursor->fragmentIterator.atEnd()) {// no text in parag. qreal fontStretch = 1; QTextCharFormat charFormat = block.charFormat(); if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { endCharStyle->applyStyle(charFormat); endCharStyle->ensureMinimalProperties(charFormat); } } if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (block.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = charFormat.property(KoCharacterStyle::FontYStretch).toDouble(); } height = charFormat.fontPointSize() * fontStretch; } else { qreal fontStretch = 1; QTextFragment fragment = cursor->fragmentIterator.fragment(); if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); } // read max font height height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); ascent = qMax(ascent, pos.m_ascent); descent = qMax(descent, pos.m_descent); bool lineBreak = false; int lastCharPos = block.position() + line.textStart() + line.textLength() - 1; int blockLastCharWithoutPreedit = line.textStart() + line.textLength() - 1; if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() && block.layout()->preeditAreaPosition() <= lastCharPos) { blockLastCharWithoutPreedit -= block.layout()->preeditAreaText().length(); } if (block.text().at(blockLastCharWithoutPreedit) == QChar(0x2028)) { // Was a line with line-break if (line.textLength() != 1) { //unless empty line we should ignore the format of it --lastCharPos; } lineBreak = true; } while (!(fragment.contains(lastCharPos))) { cursor->fragmentIterator++; if (cursor->fragmentIterator.atEnd()) { break; } fragment = cursor->fragmentIterator.fragment(); if (!d->documentLayout->changeTracker() || !d->documentLayout->changeTracker()->displayChanges() || !d->documentLayout->changeTracker()->containsInlineChanges(fragment.charFormat()) || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()) || !d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled() || (d->documentLayout->changeTracker()->elementById(fragment.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() != KoGenChange::DeleteChange) || d->documentLayout->changeTracker()->displayChanges()) { qreal fontStretch = 1; if (useFontProperties) { //stretch line height to powerpoint size fontStretch = PresenterFontStretch; } else if (fragment.charFormat().hasProperty(KoCharacterStyle::FontYStretch)) { // stretch line height to ms-word size fontStretch = fragment.charFormat().property(KoCharacterStyle::FontYStretch).toDouble(); } // read max font height height = qMax(height, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); ascent = qMax(ascent, pos.m_ascent); descent = qMax(descent, pos.m_descent); } } if (lineBreak) { // Was a line with line-break - the format of the line-break should not be // considered for the next line either. So we may have to advance the fragmentIterator. while (!cursor->fragmentIterator.atEnd() && lastCharPos > fragment.position() + fragment.length()-1) { cursor->fragmentIterator++; fragment = cursor->fragmentIterator.fragment(); } qreal breakAscent = ascent; qreal breakDescent = descent; breakHeight = height; int firstPos = block.position() + line.textStart() + line.textLength(); // Was a line with line-break - the format of the line-break should not be // considered for the next line either. So we may have to advance the fragmentIterator. while (!cursor->fragmentIterator.atEnd() && firstPos > fragment.position() + fragment.length()-1) { cursor->fragmentIterator++; if (!cursor->fragmentIterator.atEnd()) { fragment = cursor->fragmentIterator.fragment(); // read max font height breakHeight = qMax(breakHeight, fragment.charFormat().fontPointSize() * fontStretch); KoInlineObjectExtent pos = d->documentLayout->inlineObjectExtent(fragment); breakAscent = qMax(breakAscent, pos.m_ascent); breakDescent = qMax(breakDescent, pos.m_descent); } } breakHeight = qMax(breakHeight, breakAscent + breakDescent); } } height = qMax(height, ascent + descent); if (height < 0.01) { height = 12; // default size for uninitialized styles. } // Calculate adjustment to the height due to line height calculated by qt which shouldn't be // there in reality. We will just move the line qreal lineAdjust = 0.0; if (breakHeight > height) { lineAdjust = height - breakHeight; } // Adjust the line-height according to a probably defined fixed line height, // a proportional (percent) line-height and/or the line-spacing. Together // with the line-height we maybe also need to adjust the position of the // line. This is for example needed if the line needs to shrink in height // so the line-text stays on the baseline. If the line grows in height then // we don't need to do anything. if (d->dropCapsNChars <= 0) { // linespacing rules doesn't apply to drop caps qreal fixedLineHeight = format.doubleProperty(KoParagraphStyle::FixedLineHeight); if (fixedLineHeight != 0.0) { qreal prevHeight = height; height = fixedLineHeight; lineAdjust += height - prevHeight; } else { qreal lineSpacing = format.doubleProperty(KoParagraphStyle::LineSpacing); if (lineSpacing == 0.0) { // unset qreal percent = format.doubleProperty(KoParagraphStyle::PercentLineHeight); if (percent != 0) { height *= percent / 100.0; } else { height *= 1.2; // default } } height += lineSpacing; } qreal minimum = style.minimumLineHeight(); if (minimum > 0.0) { height = qMax(height, minimum); } } else { // for drop caps we just work with a basic linespacing for the dropped characters height *= 1.2; } //rounding problems due to Qt-scribe internally using ints. //also used when line was moved down because of intersections with other shapes if (qAbs(d->y - line.y()) >= 0.126) { d->y = line.y(); } if (lineAdjust) { // Adjust the position of the line itself. line.setPosition(QPointF(line.x(), line.y() + lineAdjust)); // Adjust the position of the block-rect for this line which is used later // to proper clip the line while drawing. If we would not adjust it here // then we could end with text-lines being partly cutoff. if (lineAdjust < 0.0) { d->blockRects.last().moveTop(d->blockRects.last().top() + lineAdjust); } if (block.textList() && block.layout()->lineCount() == 1) { // If this is the first line in a list (aka the first line after the list- // item) then we also need to adjust the counter to match to the line again. blockData.setCounterPosition(QPointF(blockData.counterPosition().x(), blockData.counterPosition().y() + lineAdjust)); } } return height; } void KoTextLayoutArea::setLayoutEnvironmentResctictions(bool isLayoutEnvironment, bool actsHorizontally) { d->isLayoutEnvironment = isLayoutEnvironment; d->actsHorizontally = actsHorizontally; } QRectF KoTextLayoutArea::layoutEnvironmentRect() const { QRectF rect(-5e10, -5e10, 10e10, 10e20); // large values that never really restrict anything if (d->parent) { rect = d->parent->layoutEnvironmentRect(); } if (d->isLayoutEnvironment) { if (d->actsHorizontally) { rect.setLeft(left()); rect.setRight(right()); } rect.setTop(top()); rect.setBottom(maximumAllowedBottom()); } return rect; } QRectF KoTextLayoutArea::boundingRect() const { return d->boundingRect; } qreal KoTextLayoutArea::maximumAllowedBottom() const { return d->maximalAllowedBottom - d->footNotesHeight - d->preregisteredFootNotesHeight; } FrameIterator *KoTextLayoutArea::footNoteCursorToNext() const { return d->footNoteCursorToNext; } KoInlineNote *KoTextLayoutArea::continuedNoteToNext() const { return d->continuedNoteToNext; } int KoTextLayoutArea::footNoteAutoCount() const { return d->footNoteAutoCount; } void KoTextLayoutArea::setFootNoteCountInDoc(int count) { d->footNoteCountInDoc = count; } void KoTextLayoutArea::setFootNoteFromPrevious(FrameIterator *footNoteCursor, KoInlineNote *note) { d->footNoteCursorFromPrevious = footNoteCursor; d->continuedNoteFromPrevious = note; } void KoTextLayoutArea::setNoWrap(qreal maximumAllowedWidth) { d->maximumAllowedWidth = maximumAllowedWidth; } KoText::Direction KoTextLayoutArea::parentTextDirection() const { Q_ASSERT(d->parent); //Root areas should overload this method return d->parent->parentTextDirection(); } KoTextLayoutArea *KoTextLayoutArea::parent() const { return d->parent; } KoTextDocumentLayout *KoTextLayoutArea::documentLayout() const { return d->documentLayout; } void KoTextLayoutArea::setReferenceRect(qreal left, qreal right, qreal top, qreal maximumAllowedBottom) { d->left = left; d->right = right; d->top = top; d->boundingRect = QRectF(left, top, right - left, 0.0); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom() && d->boundingRect.left() <= d->boundingRect.right(), __FUNCTION__, "Bounding-rect is not normalized"); d->maximalAllowedBottom = maximumAllowedBottom; } QRectF KoTextLayoutArea::referenceRect() const { return QRectF(d->left, d->top, d->right - d->left, d->bottom - d->top); } qreal KoTextLayoutArea::left() const { return d->left; } qreal KoTextLayoutArea::right() const { return d->right; } qreal KoTextLayoutArea::top() const { return d->top; } qreal KoTextLayoutArea::bottom() const { return d->bottom; } void KoTextLayoutArea::setBottom(qreal bottom) { d->boundingRect.setBottom(bottom + qMax(qreal(0.0), d->verticalAlignOffset)); Q_ASSERT_X(d->boundingRect.top() <= d->boundingRect.bottom(), __FUNCTION__, "Bounding-rect is not normalized"); d->bottom = bottom; } void KoTextLayoutArea::findFootNotes(const QTextBlock &block, const QTextLine &line, qreal bottomOfText) { if (d->documentLayout->inlineTextObjectManager() == 0) { return; } QString text = block.text(); int pos = text.indexOf(QChar::ObjectReplacementCharacter, line.textStart()); while (pos >= 0 && pos <= line.textStart() + line.textLength()) { QTextCursor c1(block); c1.setPosition(block.position() + pos); c1.setPosition(c1.position() + 1, QTextCursor::KeepAnchor); KoInlineNote *note = dynamic_cast(d->documentLayout->inlineTextObjectManager()->inlineTextObject(c1)); if (note && note->type() == KoInlineNote::Footnote) { preregisterFootNote(note, bottomOfText); } pos = text.indexOf(QChar::ObjectReplacementCharacter, pos + 1); } } qreal KoTextLayoutArea::preregisterFootNote(KoInlineNote *note, qreal bottomOfText) { if (d->parent == 0) { // TODO to support footnotes at end of document this is // where we need to add some extra condition if (note->autoNumbering()) { KoOdfNotesConfiguration *notesConfig = d->documentLayout->styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtDocument) { note->setAutoNumber(d->footNoteCountInDoc + (d->footNoteAutoCount++)); } else if (notesConfig->numberingScheme() == KoOdfNotesConfiguration::BeginAtPage) { note->setAutoNumber(d->footNoteAutoCount++); } } if (maximumAllowedBottom() - bottomOfText > 0) { QTextFrame *subFrame = note->textFrame(); d->footNoteCursorToNext = new FrameIterator(subFrame); KoTextLayoutNoteArea *footNoteArea = new KoTextLayoutNoteArea(note, this, d->documentLayout); d->preregisteredFootNoteFrames.append(subFrame); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); bool contNotNeeded = footNoteArea->layout(d->footNoteCursorToNext); if (contNotNeeded) { delete d->footNoteCursorToNext; d->footNoteCursorToNext = 0; d->continuedNoteToNext = 0; } else { d->continuedNoteToNext = note; //layout again now it has set up a continuationObstruction delete d->footNoteCursorToNext; d->footNoteCursorToNext = new FrameIterator(subFrame); footNoteArea->setReferenceRect(left(), right(), 0, maximumAllowedBottom() - bottomOfText); footNoteArea->layout(d->footNoteCursorToNext); documentLayout()->setContinuationObstruction(0); // remove it again } d->preregisteredFootNotesHeight += footNoteArea->bottom() - footNoteArea->top(); d->preregisteredFootNoteAreas.append(footNoteArea); return footNoteArea->bottom() - footNoteArea->top(); } return 0.0; } qreal h = d->parent->preregisterFootNote(note, bottomOfText); d->preregisteredFootNotesHeight += h; return h; } void KoTextLayoutArea::confirmFootNotes() { d->footNotesHeight += d->preregisteredFootNotesHeight; d->footNoteAreas.append(d->preregisteredFootNoteAreas); d->footNoteFrames.append(d->preregisteredFootNoteFrames); d->preregisteredFootNotesHeight = 0; d->preregisteredFootNoteAreas.clear(); d->preregisteredFootNoteFrames.clear(); if (d->parent) { d->parent->confirmFootNotes(); } } void KoTextLayoutArea::expandBoundingLeft(qreal x) { d->boundingRect.setLeft(qMin(x, d->boundingRect.x())); } void KoTextLayoutArea::expandBoundingRight(qreal x) { d->boundingRect.setRight(qMax(x, d->boundingRect.right())); } void KoTextLayoutArea::clearPreregisteredFootNotes() { d->preregisteredFootNotesHeight = 0; d->preregisteredFootNoteAreas.clear(); d->preregisteredFootNoteFrames.clear(); if (d->parent) { d->parent->clearPreregisteredFootNotes(); } } void KoTextLayoutArea::handleBordersAndSpacing(KoTextBlockData &blockData, QTextBlock *block) { QTextBlockFormat format = block->blockFormat(); KoParagraphStyle formatStyle(format, block->charFormat()); // The AddParaTableSpacingAtStart config-item is used to be able to optionally prevent that // defined fo:margin-top are applied to the first paragraph. If true then the fo:margin-top // is applied to all except the first paragraph. If false fo:margin-top is applied to all // paragraphs. bool paraTableSpacingAtStart = KoTextDocument(d->documentLayout->document()).paraTableSpacingAtStart(); bool paddingExpandsBorders = false;//KoTextDocument(d->documentLayout->document()).paddingExpandsBorders(); qreal topMargin = 0; if (paraTableSpacingAtStart || block->previous().isValid()) { topMargin = formatStyle.topMargin(); } qreal spacing = qMax(d->bottomSpacing, topMargin); qreal dx = 0.0; qreal x = d->x; qreal width = d->width; if (d->indent < 0) { x += d->indent; width -= d->indent; } if (blockData.hasCounterData() && blockData.counterPosition().x() < x) { width += x - blockData.counterPosition().x(); x = blockData.counterPosition().x(); } KoTextBlockBorderData border(QRectF(x, d->y, width, 1)); border.setEdge(border.Left, format, KoParagraphStyle::LeftBorderStyle, KoParagraphStyle::LeftBorderWidth, KoParagraphStyle::LeftBorderColor, KoParagraphStyle::LeftBorderSpacing, KoParagraphStyle::LeftInnerBorderWidth); border.setEdge(border.Right, format, KoParagraphStyle::RightBorderStyle, KoParagraphStyle::RightBorderWidth, KoParagraphStyle::RightBorderColor, KoParagraphStyle::RightBorderSpacing, KoParagraphStyle::RightInnerBorderWidth); border.setEdge(border.Top, format, KoParagraphStyle::TopBorderStyle, KoParagraphStyle::TopBorderWidth, KoParagraphStyle::TopBorderColor, KoParagraphStyle::TopBorderSpacing, KoParagraphStyle::TopInnerBorderWidth); border.setEdge(border.Bottom, format, KoParagraphStyle::BottomBorderStyle, KoParagraphStyle::BottomBorderWidth, KoParagraphStyle::BottomBorderColor, KoParagraphStyle::BottomBorderSpacing, KoParagraphStyle::BottomInnerBorderWidth); border.setMergeWithNext(formatStyle.joinBorder()); if (border.hasBorders()) { // check if we can merge with the previous parags border. if (d->prevBorder && d->prevBorder->equals(border)) { blockData.setBorder(d->prevBorder); // Merged mean we don't have inserts inbetween the blocks d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->anchoringParagraphTop); } d->anchoringParagraphTop = d->y; d->y += spacing; d->blockRects.append(QRectF(x, d->anchoringParagraphTop, width, 1.0)); } else { // can't merge; then these are our new borders. KoTextBlockBorderData *newBorder = new KoTextBlockBorderData(border); blockData.setBorder(newBorder); if (d->prevBorder) { d->y += d->prevBorderPadding; d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); } if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } d->y += spacing; if (paddingExpandsBorders) { d->blockRects.append(QRectF(x - format.doubleProperty(KoParagraphStyle::LeftPadding), d->y, width + format.doubleProperty(KoParagraphStyle::LeftPadding) + format.doubleProperty(KoParagraphStyle::RightPadding), 1.0)); } else { d->blockRects.append(QRectF(x, d->y, width, 1.0)); } d->y += newBorder->inset(KoTextBlockBorderData::Top); d->y += format.doubleProperty(KoParagraphStyle::TopPadding); } // finally, horizontal components of the borders dx = border.inset(KoTextBlockBorderData::Left); d->x += dx; d->width -= border.inset(KoTextBlockBorderData::Left); d->width -= border.inset(KoTextBlockBorderData::Right); } else { // this parag has no border. if (d->prevBorder) { d->y += d->prevBorderPadding; d->y += d->prevBorder->inset(KoTextBlockBorderData::Bottom); } blockData.setBorder(0); // remove an old one, if there was one. if (!d->blockRects.isEmpty()) { d->blockRects.last().setBottom(d->y); } d->anchoringParagraphTop = d->y; if (d->bottomSpacing + topMargin) { d->anchoringParagraphTop += spacing * d->bottomSpacing / (d->bottomSpacing + topMargin); } d->y += spacing; d->blockRects.append(QRectF(x, d->y, width, 1.0)); } if (!paddingExpandsBorders) { // add padding inside the border dx += format.doubleProperty(KoParagraphStyle::LeftPadding); d->x += format.doubleProperty(KoParagraphStyle::LeftPadding); d->width -= format.doubleProperty(KoParagraphStyle::LeftPadding); d->width -= format.doubleProperty(KoParagraphStyle::RightPadding); } if (block->layout()->lineCount() == 1 && blockData.hasCounterData()) { blockData.setCounterPosition(QPointF(blockData.counterPosition().x() + dx, d->y)); } d->prevBorder = blockData.border(); d->prevBorderPadding = format.doubleProperty(KoParagraphStyle::BottomPadding); d->anchoringParagraphContentTop = d->y; } diff --git a/libs/textlayout/KoTextShapeContainerModel.cpp b/libs/textlayout/KoTextShapeContainerModel.cpp index 56a8eaf54ad..b2db08595fb 100644 --- a/libs/textlayout/KoTextShapeContainerModel.cpp +++ b/libs/textlayout/KoTextShapeContainerModel.cpp @@ -1,244 +1,243 @@ /* This file is part of the KDE project * Copyright (C) 2007,2009,2010 Thomas Zander * Copyright (C) 2010 C. Boemann * * 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. */ #include "KoTextShapeContainerModel.h" #include "KoAnchorInlineObject.h" #include "KoTextShapeData.h" #include "KoShapeContainer.h" #include #include #include #include #include struct Relation { Relation(KoShape *shape = 0) : child(shape), anchor(0), nested(false), inheritsTransform(false) { } KoShape *child; KoShapeAnchor *anchor; uint nested : 1; uint inheritsTransform :1; }; class Q_DECL_HIDDEN KoTextShapeContainerModel::Private { public: QHash children; QList shapeRemovedAnchors; }; KoTextShapeContainerModel::KoTextShapeContainerModel() : d(new Private()) { } KoTextShapeContainerModel::~KoTextShapeContainerModel() { delete d; } void KoTextShapeContainerModel::add(KoShape *child) { if (d->children.contains(child)) return; Relation relation(child); d->children.insert(child, relation); KoShapeAnchor *toBeAddedAnchor = 0; foreach (KoShapeAnchor *anchor, d->shapeRemovedAnchors) { if (child == anchor->shape()) { toBeAddedAnchor = anchor; break; } } if (toBeAddedAnchor) { addAnchor(toBeAddedAnchor); d->shapeRemovedAnchors.removeAll(toBeAddedAnchor); } } void KoTextShapeContainerModel::remove(KoShape *child) { Relation relation = d->children.value(child); d->children.remove(child); if (relation.anchor) { relation.anchor->placementStrategy()->detachFromModel(); d->shapeRemovedAnchors.append(relation.anchor); } } void KoTextShapeContainerModel::setClipped(const KoShape *child, bool clipping) { Q_ASSERT(d->children.contains(child)); d->children[child].nested = clipping; } bool KoTextShapeContainerModel::isClipped(const KoShape *child) const { Q_ASSERT(d->children.contains(child)); return d->children[child].nested; } void KoTextShapeContainerModel::setInheritsTransform(const KoShape *shape, bool inherit) { Q_ASSERT(d->children.contains(shape)); d->children[shape].inheritsTransform = inherit; } bool KoTextShapeContainerModel::inheritsTransform(const KoShape *shape) const { Q_ASSERT(d->children.contains(shape)); return d->children[shape].inheritsTransform; } int KoTextShapeContainerModel::count() const { return d->children.count(); } QList KoTextShapeContainerModel::shapes() const { QList answer; answer.reserve(d->children.count()); foreach (const Relation &relation, d->children) { answer << relation.child; } return answer; } void KoTextShapeContainerModel::containerChanged(KoShapeContainer *container, KoShape::ChangeType type) { Q_UNUSED(container); Q_UNUSED(type); } void KoTextShapeContainerModel::childChanged(KoShape *child, KoShape::ChangeType type) { if (((type == KoShape::RotationChanged || type == KoShape::ScaleChanged || type == KoShape::ShearChanged || type == KoShape::ClipPathChanged || type == KoShape::PositionChanged || type == KoShape::SizeChanged) && child->textRunAroundSide() != KoShape::RunThrough) || type == KoShape::TextRunAroundChanged) { relayoutInlineObject(child); } KoShapeContainerModel::childChanged( child, type ); } void KoTextShapeContainerModel::addAnchor(KoShapeAnchor *anchor) { Q_ASSERT(anchor); Q_ASSERT(anchor->shape()); Q_ASSERT(d->children.contains(anchor->shape())); d->children[anchor->shape()].anchor = anchor; } void KoTextShapeContainerModel::removeAnchor(KoShapeAnchor *anchor) { if (d->children.contains(anchor->shape())) { d->children[anchor->shape()].anchor = 0; d->shapeRemovedAnchors.removeAll(anchor); } } void KoTextShapeContainerModel::proposeMove(KoShape *child, QPointF &move) { if (!d->children.contains(child)) return; Relation relation = d->children.value(child); if (relation.anchor == 0) return; QPointF newPosition = child->position() + move/* + relation.anchor->offset()*/; - const QRectF parentShapeRect(QPointF(0, 0), child->parent()->size()); //warnTextLayout <<"proposeMove:" /*<< move <<" |"*/ << newPosition <<" |" << parentShapeRect; QTextLayout *layout = 0; int anchorPosInParag = -1; if (relation.anchor->anchorType() == KoShapeAnchor::AnchorAsCharacter) { int posInDocument = relation.anchor->textLocation()->position(); const QTextDocument *document = relation.anchor->textLocation()->document(); QTextBlock block = document->findBlock(posInDocument); layout = block.layout(); anchorPosInParag = posInDocument - block.position(); if (layout) { QTextLine tl = layout->lineForTextPosition(anchorPosInParag); Q_ASSERT(tl.isValid()); relation.anchor->setOffset(QPointF(newPosition.x() - tl.cursorToX(anchorPosInParag) + tl.x(), 0)); relayoutInlineObject(child); } // the rest of the code uses the shape baseline, at this time the bottom. So adjust newPosition.setY(newPosition.y() + child->size().height()); if (layout == 0) { QTextBlock block = document->findBlock(posInDocument); layout = block.layout(); anchorPosInParag = posInDocument - block.position(); } if (layout->lineCount() > 0) { KoTextShapeData *data = qobject_cast(child->parent()->userData()); Q_ASSERT(data); QTextLine tl = layout->lineForTextPosition(anchorPosInParag); Q_ASSERT(tl.isValid()); qreal y = tl.y() - data->documentOffset() - newPosition.y() + child->size().height(); relation.anchor->setOffset(QPointF(relation.anchor->offset().x(), -y)); relayoutInlineObject(child); } } else { //TODO pavolk: handle position type change: absolute to realtive, etc .. child->setPosition(newPosition); relation.anchor->setOffset(relation.anchor->offset() + move); relayoutInlineObject(child); } move.setX(0); // let the text layout move it. move.setY(0); } bool KoTextShapeContainerModel::isChildLocked(const KoShape *child) const { return child->isGeometryProtected(); } void KoTextShapeContainerModel::relayoutInlineObject(KoShape *child) { if (child == 0) { return; } KoTextShapeData *data = qobject_cast(child->parent()->userData()); Q_ASSERT(data); data->setDirty(); } diff --git a/libs/textlayout/RunAroundHelper.cpp b/libs/textlayout/RunAroundHelper.cpp index fc098b49340..bc941145fe6 100644 --- a/libs/textlayout/RunAroundHelper.cpp +++ b/libs/textlayout/RunAroundHelper.cpp @@ -1,312 +1,315 @@ /* This file is part of the KDE project * Copyright (C) 2006-2007, 2010 Thomas Zander * Copyright (C) 2010-2011 KO Gmbh * * 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. */ #include "RunAroundHelper.h" #include "KoTextLayoutObstruction.h" #include "KoTextLayoutArea.h" +#include + const qreal RIDICULOUSLY_LARGE_NEGATIVE_INDENT = -5E6; #define MIN_WIDTH 0.01f RunAroundHelper::RunAroundHelper() { m_lineRect = QRectF(); m_updateValidObstructions = false; m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } void RunAroundHelper::setLine(KoTextLayoutArea *area, const QTextLine &l) { m_area = area; line = l; } void RunAroundHelper::setObstructions(const QList &obstructions) { m_obstructions = obstructions; } bool RunAroundHelper::stayOnBaseline() const { return m_stayOnBaseline; } void RunAroundHelper::updateObstruction(KoTextLayoutObstruction *obstruction) { QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect); if (obstructionLineRect.isValid()) { m_updateValidObstructions = true; } } bool RunAroundHelper::fit(const bool resetHorizontalPosition, bool isRightToLeft, const QPointF &position) { Q_ASSERT(line.isValid()); if (resetHorizontalPosition) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } const qreal maxLineWidth = m_area->width(); // Make sure at least some text is fitted if the basic width (page, table cell, column) // is too small if (maxLineWidth <= 0.) { // we need to make sure that something like "line.setLineWidth(0.0);" is called here to prevent // the QTextLine from being removed again and leading at a later point to crashes. It seems // following if-condition including the setNumColumns call was added to do exactly that. But // it's not clear for what the if-condition was added. In any case that condition is wrong or // incompleted cause things can still crash with m_state->layout->text().length() == 0 (see the // document attached to bug 244411). //if (m_state->layout->lineCount() > 1 || m_state->layout->text().length() > 0) line.setNumColumns(1); line.setPosition(position); return false; } // Too little width because of wrapping is handled in the remainder of this method line.setLineWidth(maxLineWidth); + qInfo()<<"line:"< m_textWidth) { // This can happen if spaces are added at the end of a line. Those spaces will not result in a // line-break. On left-to-right everything is fine and the spaces at the end are just not visible // but on right-to-left we need to adust the position cause spaces at the end are displayed at // the beginning and we need to make sure that doesn't result in us cutting of text at the right side. qreal diff = line.naturalTextWidth() - m_textWidth; lineRectPart.setX(lineRectPart.x() - diff); } line.setLineWidth(m_textWidth); line.setPosition(QPointF(lineRectPart.x(), lineRectPart.y())); checkEndOfLine(lineRectPart, maxNaturalTextWidth); return true; } void RunAroundHelper::validateObstructions() { m_validObstructions.clear(); foreach (KoTextLayoutObstruction *obstruction, m_obstructions) { validateObstruction(obstruction); } } void RunAroundHelper::validateObstruction(KoTextLayoutObstruction *obstruction) { QRectF obstructionLineRect = obstruction->cropToLine(m_lineRect); if (obstructionLineRect.isValid()) { m_validObstructions.append(obstruction); } } void RunAroundHelper::createLineParts() { m_lineParts.clear(); if (m_validObstructions.isEmpty()) { // Add whole line rect m_lineParts.append(m_lineRect); } else { QVector lineParts; QRectF rightLineRect = m_lineRect; bool lastRightRectValid = false; qSort(m_validObstructions.begin(), m_validObstructions.end(), KoTextLayoutObstruction::compareRectLeft); // Divide rect to parts, part can be invalid when obstructions are not disjunct. foreach (KoTextLayoutObstruction *validObstruction, m_validObstructions) { QRectF leftLineRect = validObstruction->getLeftLinePart(rightLineRect); lineParts.append(leftLineRect); QRectF lineRect = validObstruction->getRightLinePart(rightLineRect); if (lineRect.isValid()) { rightLineRect = lineRect; lastRightRectValid = true; } else { lastRightRectValid = false; } } if (lastRightRectValid) { lineParts.append(rightLineRect); } else { lineParts.append(QRect()); } Q_ASSERT(m_validObstructions.size() + 1 == lineParts.size()); // Select invalid parts because of wrap. for (int i = 0; i < m_validObstructions.size(); i++) { KoTextLayoutObstruction *obstruction = m_validObstructions.at(i); if (obstruction->noTextAround()) { lineParts.replace(i, QRectF()); lineParts.replace(i + 1, QRect()); } else if (obstruction->textOnLeft()) { lineParts.replace(i + 1, QRect()); } else if (obstruction->textOnRight()) { lineParts.replace(i, QRectF()); } else if (obstruction->textOnEnoughSides()) { QRectF leftRect = obstruction->getLeftLinePart(m_lineRect); QRectF rightRect = obstruction->getRightLinePart(m_lineRect); if (leftRect.width() < obstruction->runAroundThreshold()) { lineParts.replace(i, QRectF()); } if (rightRect.width() < obstruction->runAroundThreshold()) { lineParts.replace(i + 1, QRectF()); } } else if (obstruction->textOnBiggerSide()) { QRectF leftRect = obstruction->getLeftLinePart(m_lineRect); QRectF rightRect = obstruction->getRightLinePart(m_lineRect); if (leftRect.width() < rightRect.width()) { lineParts.replace(i, QRectF()); } else { lineParts.replace(i + 1, QRectF()); } } } // Filter invalid parts. foreach (const QRectF &rect, lineParts) { if (rect.isValid()) { m_lineParts.append(rect); } } } } QRectF RunAroundHelper::minimizeHeightToLeastNeeded(const QRectF &lineRect) { Q_ASSERT(line.isValid()); QRectF lineRectBase = lineRect; // Get width of one char or shape (as-char). m_textWidth = line.cursorToX(line.textStart() + 1) - line.cursorToX(line.textStart()); // Make sure width is not wider than the area allows. if (m_textWidth > m_area->width()) { m_textWidth = m_area->width(); } line.setLineWidth(m_textWidth); // Base linerect height on the width calculated above. lineRectBase.setHeight(line.height()); return lineRectBase; } void RunAroundHelper::updateLineParts(const QRectF &lineRect) { if (m_lineRect != lineRect || m_updateValidObstructions) { m_lineRect = lineRect; m_updateValidObstructions = false; validateObstructions(); createLineParts(); } } QRectF RunAroundHelper::getLineRectPart() { QRectF retVal; foreach (const QRectF &lineRectPart, m_lineParts) { if (m_horizontalPosition <= lineRectPart.left() && m_textWidth <= lineRectPart.width()) { retVal = lineRectPart; break; } } return retVal; } void RunAroundHelper::setMaxTextWidth(const QRectF &minLineRectPart, const qreal leftIndent, const qreal maxNaturalTextWidth) { Q_ASSERT(line.isValid()); qreal width = m_textWidth; qreal maxWidth = minLineRectPart.width() - leftIndent; qreal height; qreal maxHeight = minLineRectPart.height(); qreal widthDiff = maxWidth - width; widthDiff /= 2; while (width <= maxWidth && width <= maxNaturalTextWidth && widthDiff > MIN_WIDTH) { qreal linewidth = width + widthDiff; line.setLineWidth(linewidth); height = line.height(); if (height <= maxHeight) { width = linewidth; m_textWidth = width; } widthDiff /= 2; } } QRectF RunAroundHelper::getLineRect(const QRectF &lineRect, const qreal maxNaturalTextWidth) { Q_ASSERT(line.isValid()); const qreal leftIndent = lineRect.left(); QRectF minLineRect = minimizeHeightToLeastNeeded(lineRect); updateLineParts(minLineRect); // Get appropriate line rect part, to fit line, // using horizontal position, minimal height and width of line. QRectF lineRectPart = getLineRectPart(); if (lineRectPart.isValid()) { qreal x = lineRectPart.x(); qreal width = lineRectPart.width(); // Limit moved the left edge, keep the indent. if (leftIndent < x) { x += leftIndent; width -= leftIndent; } line.setLineWidth(width); // Check if line rect is big enough to fit line. // Otherwise find shorter width, what means also shorter height of line. // Condition is reverted. if (line.height() > lineRectPart.height()) { setMaxTextWidth(lineRectPart, leftIndent, maxNaturalTextWidth); } else { m_textWidth = width; } } return lineRectPart; } void RunAroundHelper::checkEndOfLine(const QRectF &lineRectPart, const qreal maxNaturalTextWidth) { if (lineRectPart == m_lineParts.last() || maxNaturalTextWidth <= lineRectPart.width()) { m_horizontalPosition = RIDICULOUSLY_LARGE_NEGATIVE_INDENT; m_stayOnBaseline = false; } else { m_horizontalPosition = lineRectPart.right(); m_stayOnBaseline = true; } } diff --git a/libs/textlayout/tests/TestBlockLayout.cpp b/libs/textlayout/tests/TestBlockLayout.cpp index 7c4afe44be9..e51e581492a 100644 --- a/libs/textlayout/tests/TestBlockLayout.cpp +++ b/libs/textlayout/tests/TestBlockLayout.cpp @@ -1,1076 +1,1078 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2011 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestBlockLayout.h" #include "MockRootAreaProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define FRAME_SPACING 10.0 void TestBlockLayout::initTestCase() { m_doc = 0; m_layout = 0; m_loremIpsum = QString("Lorem ipsum dolor sit amet, XgXgectetuer adiXiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi."); } void TestBlockLayout::setupTest(const QString &initText) { m_doc = new QTextDocument; Q_ASSERT(m_doc); MockRootAreaProvider *provider = new MockRootAreaProvider(); Q_ASSERT(provider); KoTextDocument(m_doc).setInlineTextObjectManager(new KoInlineTextObjectManager); m_doc->setDefaultFont(QFont("Sans Serif", 12, QFont::Normal, false)); //do it manually since we do not load the appDefaultStyle m_styleManager = new KoStyleManager(0); KoTextDocument(m_doc).setStyleManager(m_styleManager); m_layout = new KoTextDocumentLayout(m_doc, provider); Q_ASSERT(m_layout); m_doc->setDocumentLayout(m_layout); //m_area = provider->provide(m_layout); m_block = m_doc->begin(); if (initText.length() > 0) { QTextCursor cursor(m_doc); cursor.insertText(initText); KoParagraphStyle style; style.setFontPointSize(12.0); style.setStyleId(101); // needed to do manually since we don't use the stylemanager QTextBlock b2 = m_doc->begin(); while (b2.isValid()) { style.applyStyle(b2); b2 = b2.next(); } } } void TestBlockLayout::testLineBreaking() { setupTest(m_loremIpsum); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); //QCOMPARE(blockLayout->lineCount(), 16); QCOMPARE(blockLayout->lineForTextPosition(1).width(), 200.0); } void TestBlockLayout::testBasicLineSpacing() { /// Tests incrementing Y pos based on the font size setupTest(m_loremIpsum); QTextCursor cursor(m_doc); cursor.setPosition(0); cursor.setPosition(m_loremIpsum.length() - 1, QTextCursor::KeepAnchor); QTextCharFormat charFormat = cursor.charFormat(); charFormat.setFontPointSize(12); cursor.mergeCharFormat(charFormat); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); const qreal fontHeight12 = 12; qreal lineSpacing12 = fontHeight12 * 1.2; // 120% is the normal lineSpacing. const qreal fontHeight18 = 18; qreal lineSpacing18 = fontHeight18 * 1.2; // 120% is the normal lineSpacing. // QCOMPARE(blockLayout->lineCount(), 15); QTextLine line; for (int i = 0; i < 15; i++) { line = blockLayout->lineAt(i); QVERIFY(line.isValid()); // The reason for this weird check is that the values are stored internally // as 26.6 fixed point integers. The entire internal text layout is // actually done using fixed point arithmetic. This is due to embedded // considerations, and offers general performance benefits across all // platforms. //qDebug() << i << qAbs(line.y() - i * lineSpacing12); QVERIFY(qAbs(line.y() - (i * lineSpacing12 + 100.0)) < ROUNDING); } // make first word smaller, should have zero effect on lineSpacing. cursor.setPosition(0); cursor.setPosition(11, QTextCursor::KeepAnchor); charFormat.setFontPointSize(10); cursor.mergeCharFormat(charFormat); m_layout->layout(); for (int i = 0; i < 15; i++) { line = blockLayout->lineAt(i); QVERIFY(line.isValid()); //qDebug() << i << qAbs(line.y() - i * lineSpacing12); QVERIFY(qAbs(line.y() - (i * lineSpacing12 + 100.0)) < ROUNDING); } // make first word on second line word bigger, should move that line down a little. int pos = blockLayout->lineAt(1).textStart(); cursor.setPosition(pos); cursor.setPosition(pos + 12, QTextCursor::KeepAnchor); charFormat.setFontPointSize(18); cursor.mergeCharFormat(charFormat); m_layout->layout(); line = blockLayout->lineAt(0); QCOMPARE(line.y(), 0.0 + 100.0); line = blockLayout->lineAt(1); QVERIFY(qAbs(line.y() - (lineSpacing12 + 100.0)) < ROUNDING); for (int i = 2; i < 15; i++) { line = blockLayout->lineAt(i); //qDebug() << "i: " << i << " gives: " << line.y() << (lineSpacing12 + lineSpacing18 + (i - 2) * lineSpacing12); QVERIFY(qAbs(line.y() - (lineSpacing12 + lineSpacing18 + (i - 2) * lineSpacing12 + 100.0)) < ROUNDING); } } void TestBlockLayout::testBasicLineSpacing2() { setupTest(m_loremIpsum); QTextCursor cursor(m_doc); cursor.insertText("foo\n\n"); // insert empty parag; m_layout->layout(); QTextBlock block = m_doc->begin().next(); QTextLayout *blockLayout = block.layout(); QVERIFY(block.isValid()); blockLayout = block.layout(); QCOMPARE(blockLayout->lineCount(), 1); block = block.next(); QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (28.8 + 100.0)) < ROUNDING); } void TestBlockLayout::testFixedLineSpacing() { setupTest(QString("Line1")+QChar(0x2028)+"Line2"+QChar(0x2028)+"Line3"); - QTextCursor cursor(m_doc); KoParagraphStyle style; style.setFontPointSize(12.0); style.setLineHeightAbsolute(28.0); QTextBlock block = m_doc->begin(); style.applyStyle(block); QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::FixedLineHeight), 28.0); m_layout->layout(); QTextLayout *blockLayout = block.layout(); // lines with fontsize less than the fixed height are bottom aligned, resulting in // positive y for first line QCOMPARE(blockLayout->lineAt(0).y(), 28.0-12.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 28.0 + 28.0-12.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 56.0 + 28.0-12.0 + 100.0); style.setLineHeightAbsolute(8.0); style.applyStyle(block); QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::FixedLineHeight), 8.0); m_layout->layout(); blockLayout = block.layout(); // lines with fontsize more than the fixed height are bottom aligned, resulting in //negative y for first line QCOMPARE(blockLayout->lineAt(0).y(), 8.0-12.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 8.0-12.0 + 8.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 8.0-12.0 + 8.0 + 8.0 + 100.0); } void TestBlockLayout::testPercentageLineSpacing() { setupTest(QString("Line1")+QChar(0x2028)+"Line2"+QChar(0x2028)+"Line3"); - QTextCursor cursor(m_doc); KoParagraphStyle style; style.setFontPointSize(12.0); style.setLineHeightPercent(150); QTextBlock block = m_doc->begin(); style.applyStyle(block); QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::PercentLineHeight), 150.0); m_layout->layout(); QTextLayout *blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 0.0 + 18.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 0.0 + 18.0 + 18.0 + 100.0); style.setLineHeightPercent(50); style.applyStyle(block); QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::PercentLineHeight), 50.0); m_layout->layout(); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 0.0 + 6.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 0.0 + 6.0 + 6.0 + 100.0); } void TestBlockLayout::testAdvancedLineSpacing() { setupTest("Line1\nLine2\nLine3\nLine4\nLine5\nLine6\nLine7"); - QTextCursor cursor(m_doc); KoParagraphStyle style; style.setFontPointSize(12.0); style.setLineHeightPercent(80); QTextBlock block = m_doc->begin(); style.applyStyle(block); // check if styles do their work ;) QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::PercentLineHeight), 80.0); block = block.next(); QVERIFY(block.isValid()); //line2 style.setLineHeightAbsolute(28.0); // removes the percentage style.applyStyle(block); QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::PercentLineHeight), 0.0); QCOMPARE(block.blockFormat().doubleProperty(KoParagraphStyle::FixedLineHeight), 28.0); block = block.next(); QVERIFY(block.isValid()); // line3 style.setMinimumLineHeight(QTextLength(QTextLength::FixedLength, 40.0)); style.setLineHeightPercent(120); style.applyStyle(block); block = block.next(); QVERIFY(block.isValid()); // line4 style.remove(KoParagraphStyle::FixedLineHeight); style.setMinimumLineHeight(QTextLength(QTextLength::FixedLength, 5.0)); style.applyStyle(block); block = block.next(); QVERIFY(block.isValid()); // line5 style.setMinimumLineHeight(QTextLength(QTextLength::FixedLength, 0.0)); style.setLineSpacing(8.0); style.remove(KoParagraphStyle::PercentLineHeight); style.applyStyle(block); block = block.next(); QVERIFY(block.isValid()); // line6 style.setLineSpacingFromFont(true); style.setLineHeightPercent(100); style.remove(KoParagraphStyle::LineSpacing); style.applyStyle(block); block = m_block; // line1 m_layout->layout(); QTextLayout *blockLayout = block.layout(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.0 + 100.0)) < ROUNDING); block = block.next(); // line2 with fixed we are bottom aligned so offset by 28.0-12.0 QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0-12.0 + 100.0)) < ROUNDING); block = block.next(); // line3 QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0 + 100.0)) < ROUNDING); block = block.next(); // line4 QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); // percentage overrides minimum so percentage value is the right to test against //QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0 + 40.0 + 100.0)) < ROUNDING); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0 + 1.2*12 + 100.0)) < ROUNDING); block = block.next(); // line5 QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); // minimum of 5 is irelevant and percentage of 1.2 was still there QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0 + 1.2*12 + 1.2*12 + 100.0)) < ROUNDING); block = block.next(); // line6 QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0 + 1.2*12 + 1.2*12 + 12+8 + 100.0)) < ROUNDING); block = block.next(); // line 7 QVERIFY(block.isValid()); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (0.8*12 + 28.0 + 1.2*12 + 1.2*12 + 12+8 + 1.2*12 + 100.0)) < ROUNDING); } void TestBlockLayout::testEmptyLineHeights() { // 1) a blank line is affected by the line break after // 1b) a line with contents is not affected by the linebreak // 2) a final line if blank can have it's height specified by a special textstyle // If the special style is empty the par style is used for the line setupTest(QString("")+QChar(0x2028)+QChar(0x2028)+"\nNextBlock"); QTextCursor cursor(m_doc); QTextCharFormat bigCharFormat; bigCharFormat.setFontPointSize(20.0); QTextCharFormat smallCharFormat; smallCharFormat.setFontPointSize(8.0); KoParagraphStyle style; style.setFontPointSize(12.0); style.setLineHeightPercent(100); QTextBlock block = m_doc->begin(); style.applyStyle(block); // apply formats cursor.setPosition(0); cursor.setPosition(1, QTextCursor::KeepAnchor); cursor.mergeCharFormat(bigCharFormat); cursor.setPosition(1); cursor.setPosition(2, QTextCursor::KeepAnchor); cursor.mergeCharFormat(smallCharFormat); m_layout->layout(); QTextLayout *blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 20.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 20.0 + 8.0 + 100.0); block = block.next(); QVERIFY(block.isValid()); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 20.0 + 8.0 + 12.0 + 100.0); // Now do the test again but with last line having bigger font block = m_doc->begin(); QTextBlockFormat blockFormat = block.blockFormat(); KoCharacterStyle charStyle; charStyle.setFontPointSize(20.0); blockFormat.setProperty(KoParagraphStyle::EndCharStyle, QVariant::fromValue< QSharedPointer >(QSharedPointer(&charStyle))); cursor.setBlockFormat(blockFormat); m_layout->layout(); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 20.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 20.0 + 8.0 + 100.0); block = block.next(); QVERIFY(block.isValid()); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 20.0 + 8.0 + 20.0 + 100.0); // Now do the test again but with last line having a small font block = m_doc->begin(); KoCharacterStyle charStyle2; charStyle2.setFontPointSize(6.0); blockFormat.setProperty(KoParagraphStyle::EndCharStyle, QVariant::fromValue< QSharedPointer >(QSharedPointer(&charStyle2))); cursor.setBlockFormat(blockFormat); m_layout->layout(); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).y(), 20.0 + 100.0); QCOMPARE(blockLayout->lineAt(2).y(), 20.0 + 8.0 + 100.0); block = block.next(); QVERIFY(block.isValid()); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).y(), 20.0 + 8.0 + 6.0 + 100.0); } // Test that spacing between blocks are the max of bottomMargin and topMargin // of the top and bottom block respectively // If the block doesn't connect to another block (top and bottom of pages or // table cells, oif blocks are intersperced with say a table. Then it's // just the plain margin // For completeness sake we test with 3 blocks just to make sure it works void TestBlockLayout::testBlockSpacing() { setupTest(m_loremIpsum); QTextCursor cursor(m_doc); QTextCursor cursor1(m_doc); // create second parag cursor.setPosition(m_loremIpsum.length()); cursor.insertText("\n"); cursor.insertText(m_loremIpsum); // create third parag cursor.insertText("\n"); cursor.insertText(m_loremIpsum); m_layout->layout(); QTextBlock block2 = m_doc->begin().next(); QTextBlock block3 = m_doc->begin().next().next(); QTextCursor cursor2(block2); QTextCursor cursor3(block3); // and test spacing between blocks QTextBlockFormat bf1 = cursor1.blockFormat(); QTextLayout *block1Layout = m_block.layout(); QTextBlockFormat bf2 = cursor2.blockFormat(); QTextLayout *block2Layout = block2.layout(); QTextBlockFormat bf3 = cursor3.blockFormat(); QTextLayout *block3Layout = block3.layout(); int lastLineNum = block1Layout->lineCount() - 1; const qreal lineSpacing = 12.0 * 1.2; KoTextDocument(m_doc).setParaTableSpacingAtStart(false); bool paraTableSpacingAtStart = KoTextDocument(m_doc).paraTableSpacingAtStart(); qreal spaces[3] = {0.0, 3.0, 6.0}; for (int t1 = 0; t1 < 3; ++t1) { for (int t2 = 0; t2 < 3; ++t2) { for (int t3 = 0; t3 < 3; ++t3) { for (int b1 = 0; b1 < 3; ++b1) { bf1.setTopMargin(spaces[t1]); bf1.setBottomMargin(spaces[b1]); cursor1.setBlockFormat(bf1); for (int b2 = 0; b2 < 3; ++b2) { bf2.setTopMargin(spaces[t2]); bf2.setBottomMargin(spaces[b2]); cursor2.setBlockFormat(bf2); for (int b3 = 0; b3 < 3; ++b3) { bf3.setTopMargin(spaces[t3]); bf3.setBottomMargin(spaces[b3]); cursor3.setBlockFormat(bf3); m_layout->layout(); // Now lets do the actual testing //Above first block is just plain if (paraTableSpacingAtStart) { QVERIFY(qAbs(block1Layout->lineAt(0).y() - spaces[t1]) < ROUNDING); } else { QVERIFY(qAbs(block1Layout->lineAt(0).y() - (0.0 + 100.0)) < ROUNDING); } // Between 1st and 2nd block is max of spaces QVERIFY(qAbs((block2Layout->lineAt(0).y() - block1Layout->lineAt(lastLineNum).y() - lineSpacing) - qMax(spaces[b1], spaces[t2])) < ROUNDING); // Between 2nd and 3rd block is max of spaces QVERIFY(qAbs((block3Layout->lineAt(0).y() - block2Layout->lineAt(lastLineNum).y() - lineSpacing) - qMax(spaces[b2], spaces[t3])) < ROUNDING); //Below 3rd block is just plain //QVERIFY(qAbs(bottom()-block3Layout->lineAt(lastLineNum).y() - lineSpacing - spaces[t1]) < ROUNDING); } } } } } } KoTextDocument(m_doc).setParaTableSpacingAtStart(true); paraTableSpacingAtStart = KoTextDocument(m_doc).paraTableSpacingAtStart(); for (int t1 = 0; t1 < 3; ++t1) { for (int t2 = 0; t2 < 3; ++t2) { for (int t3 = 0; t3 < 3; ++t3) { for (int b1 = 0; b1 < 3; ++b1) { bf1.setTopMargin(spaces[t1]); bf1.setBottomMargin(spaces[b1]); cursor1.setBlockFormat(bf1); for (int b2 = 0; b2 < 3; ++b2) { bf2.setTopMargin(spaces[t2]); bf2.setBottomMargin(spaces[b2]); cursor2.setBlockFormat(bf2); for (int b3 = 0; b3 < 3; ++b3) { bf3.setTopMargin(spaces[t3]); bf3.setBottomMargin(spaces[b3]); cursor3.setBlockFormat(bf3); m_layout->layout(); // Now lets do the actual testing //Above first block is just plain if (paraTableSpacingAtStart) { QVERIFY(qAbs(block1Layout->lineAt(0).y() - (spaces[t1] + 100.0)) < ROUNDING); } else { QVERIFY(qAbs(block1Layout->lineAt(0).y() - (0.0 + 100.0)) < ROUNDING); } // Between 1st and 2nd block is max of spaces QVERIFY(qAbs((block2Layout->lineAt(0).y() - block1Layout->lineAt(lastLineNum).y() - lineSpacing) - qMax(spaces[b1], spaces[t2])) < ROUNDING); // Between 2nd and 3rd block is max of spaces QVERIFY(qAbs((block3Layout->lineAt(0).y() - block2Layout->lineAt(lastLineNum).y() - lineSpacing) - qMax(spaces[b2], spaces[t3])) < ROUNDING); //Below 3rd block is just plain //QVERIFY(qAbs(bottom()-block3Layout->lineAt(lastLineNum).y() - lineSpacing - spaces[t1]) < ROUNDING); } } } } } } } void TestBlockLayout::testLeftRightMargins() { setupTest(m_loremIpsum); QTextCursor cursor(m_doc); QTextBlockFormat bf = cursor.blockFormat(); bf.setLeftMargin(10.0); cursor.setBlockFormat(bf); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 10.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 190.0); bf.setRightMargin(15.0); cursor.setBlockFormat(bf); m_layout->layout(); QCOMPARE(blockLayout->lineAt(0).x(), 10.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 175.0); bf.setLeftMargin(0.0); cursor.setBlockFormat(bf); m_layout->layout(); QCOMPARE(blockLayout->lineAt(0).x(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 185.0); // still uses the right margin of 15 // create second parag cursor.setPosition(m_loremIpsum.length()); cursor.insertText("\n"); bf.setTopMargin(12); cursor.setBlockFormat(bf); cursor.insertText(m_loremIpsum); m_layout->layout(); QCOMPARE(blockLayout->lineAt(0).x(), 0.0 + 100.0); // parag 1 QCOMPARE(blockLayout->lineAt(0).width(), 185.0); // and test parag 2 QTextBlock block2 = m_doc->begin().next(); QTextLayout *block2Layout = block2.layout(); QCOMPARE(block2Layout->lineAt(0).x(), 0.0 + 100.0); QCOMPARE(block2Layout->lineAt(0).width(), 185.0); } void TestBlockLayout::testTextIndent() { setupTest(m_loremIpsum); QTextCursor cursor(m_doc); QTextBlockFormat bf = cursor.blockFormat(); bf.setTextIndent(20); cursor.setBlockFormat(bf); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 20.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 180.0); QCOMPARE(blockLayout->lineAt(1).x(), 0.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).width(), 200.0); // Add som left margin to check for no correlation bf.setLeftMargin(15.0); cursor.setBlockFormat(bf); m_layout->layout(); QCOMPARE(blockLayout->lineAt(0).x(), 35.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 165.0); QCOMPARE(blockLayout->lineAt(1).x(), 15.0 + 100.0); QCOMPARE(blockLayout->lineAt(1).width(), 185.0); // create second parag and see it works too cursor.setPosition(m_loremIpsum.length()); cursor.insertText("\n"); bf.setTopMargin(12); cursor.setBlockFormat(bf); cursor.insertText(m_loremIpsum); m_layout->layout(); QTextBlock block2 = m_doc->begin().next(); QTextLayout *block2Layout = block2.layout(); QCOMPARE(block2Layout->lineAt(0).x(), 35.0 + 100.0); QCOMPARE(block2Layout->lineAt(0).width(), 165.0); QCOMPARE(block2Layout->lineAt(1).x(), 15.0 + 100.0); QCOMPARE(block2Layout->lineAt(1).width(), 185.0); } void TestBlockLayout::testTabs_data() { static const struct TestCaseData { bool relativeTabs; qreal leftMargin; qreal textIndent; qreal rightMargin; qreal expected; // expected value of pos=2 of each line } testcaseDataList[] = { { true, 0, 0, 0, 50}, { true, 0, 0, 5, 50}, { true, 0, 10, 0, 50}, { true, 0, 10, 5, 50}, { true, 0, -10, 0, 0}, { true, 0, -10, 5, 0}, { true, 20, 0, 0, 70}, { true, 20, 0, 5, 70}, { true, 20, 10, 0, 70}, { true, 20, 10, 5, 70}, { true, 20, -10, 0, 20}, { true, 20, -10, 5, 20}, { true, -20, 0, 0+20, 30}, //+20 to avoid extra tab fitting in { true, -20, 0, 5+20, 30}, //+20 to avoid extra tab fitting in { true, -20, 10, 0+20, 30}, //+20 to avoid extra tab fitting in { true, -20, 10, 5+20, 30}, //+20 to avoid extra tab fitting in { true, -20, -10, 0+20, -20}, //+20 to avoid extra tab fitting in { true, -20, -10, 5+20, -20}, //+20 to avoid extra tab fitting in { false, 0, 0, 0, 50}, { false, 0, 0, 5, 50}, { false, 0, 10, 0, 50}, { false, 0, 10, 5, 50}, { false, 0, -10, 0, 0}, { false, 0, -10, 5, 0}, { false, 20, 0, 0, 50}, { false, 20, 0, 5, 50}, { false, 20, 10, 0, 50}, { false, 20, 10, 5, 50}, { false, 20, -10, 0, 50}, { false, 20, -10, 5, 50}, { false, -20, 0, 0+70, 0}, //+70 to avoid extra tab fitting in { false, -20, 0, 5+70, 0}, //+70 to avoid extra tab fitting in { false, -20, 10, 0+70, 0}, //+70 to avoid extra tab fitting in { false, -20, 10, 5+70, 0}, //+70 to avoid extra tab fitting in { false, -20, -10, 0+70, 0}, //+70 to avoid extra tab fitting in { false, -20, -10, 5+70, 0}, //+70 to avoid extra tab fitting in }; static const int testcasesCount = sizeof(testcaseDataList)/sizeof(testcaseDataList[0]); QTest::addColumn("relativeTabs"); QTest::addColumn("leftMargin"); QTest::addColumn("textIndent"); QTest::addColumn("rightMargin"); QTest::addColumn("expected"); for (int i = 0; i < testcasesCount; ++i) { const TestCaseData &testcaseData = testcaseDataList[i]; QTest::newRow(QString::number(i).toLatin1()) << testcaseData.relativeTabs << testcaseData.leftMargin << testcaseData.textIndent << testcaseData.rightMargin << testcaseData.expected; } } void TestBlockLayout::testTabs() { QFETCH(bool, relativeTabs); QFETCH(qreal, leftMargin); QFETCH(qreal, textIndent); QFETCH(qreal, rightMargin); QFETCH(qreal, expected); // expected value of pos=2 of each line setupTest("x\tx\tx\tx\tx\tx\tx\tx\tx\tx\tx\tx\tx\tx\tx\te"); QTextCursor cursor(m_doc); QTextBlockFormat bf = cursor.blockFormat(); cursor.setBlockFormat(bf); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); const qreal tabSpacing = 50.0; // in pt m_layout->setTabSpacing(tabSpacing); KoTextDocument(m_doc).setRelativeTabs(relativeTabs); bf.setLeftMargin(leftMargin); bf.setTextIndent(textIndent); bf.setRightMargin(rightMargin); cursor.setBlockFormat(bf); m_layout->layout(); for (int pos=0; pos<4; pos++) { if (pos==0) QCOMPARE(blockLayout->lineAt(0).cursorToX(pos*2), leftMargin + textIndent); else { warnTextLayout << blockLayout->lineAt(0).cursorToX(pos*2) << expected+(pos-1)*tabSpacing; QVERIFY(qAbs(blockLayout->lineAt(0).cursorToX(pos*2) - (expected+(pos-1)*tabSpacing)) < 1.0); } } if (textIndent == 0.0) { // excluding known fails for (int pos=0; pos<4; pos++) { // pos==0 is known to fail see https://bugs.kde.org/show_bug.cgi?id=239819 if (pos!=0) QVERIFY(qAbs(blockLayout->lineAt(1).cursorToX(pos*2+8)- (expected+(pos-1)*tabSpacing)) < 1.0); } for (int pos=0; pos<4; pos++) { // pos==0 is known to fail see https://bugs.kde.org/show_bug.cgi?id=239819 if (pos!=0) QVERIFY(qAbs(blockLayout->lineAt(2).cursorToX(pos*2+16)- (expected+(pos-1)*tabSpacing)) < 1.0); } } } void TestBlockLayout::testBasicTextAlignments() { setupTest("Left\nCenter\nRight"); QTextCursor cursor(m_doc); QTextBlockFormat format = cursor.blockFormat(); format.setAlignment(Qt::AlignLeft); cursor.setBlockFormat(format); cursor.setPosition(6); format.setAlignment(Qt::AlignHCenter); cursor.setBlockFormat(format); cursor.setPosition(13); format.setAlignment(Qt::AlignRight); cursor.setBlockFormat(format); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 100.0); QTextBlock block = m_doc->begin().next(); QVERIFY(block.isValid()); blockLayout = block.layout(); QRectF rect = blockLayout->lineAt(0).naturalTextRect(); QVERIFY(rect.x() > 60); QCOMPARE(rect.x() + rect.width() + (200 - rect.right()), 200.0); block = block.next(); QVERIFY(block.isValid()); blockLayout = block.layout(); rect = blockLayout->lineAt(0).naturalTextRect(); QVERIFY(rect.x() > 150); QVERIFY(rect.right() >= 200.0 + 100.0); } void TestBlockLayout::testTextAlignments() { // TODO justified & justified, last line setupTest("Left\nRight\nﺵﻻﺆﻴﺜﺒ\nﺵﻻﺆﻴﺜﺒ\nLast Line."); KoParagraphStyle start; start.setFontPointSize(12.0); start.setAlignment(Qt::AlignLeading); KoParagraphStyle end; end.setFontPointSize(12.0); end.setAlignment(Qt::AlignTrailing); KoParagraphStyle startRTL; startRTL.setFontPointSize(12.0); startRTL.setAlignment(Qt::AlignLeading); startRTL.setTextProgressionDirection(KoText::RightLeftTopBottom); KoParagraphStyle endRTL; endRTL.setAlignment(Qt::AlignTrailing); endRTL.setTextProgressionDirection(KoText::RightLeftTopBottom); endRTL.setFontPointSize(12.0); QTextBlock block = m_doc->begin(); start.applyStyle(block); block = block.next(); end.applyStyle(block); block = block.next(); startRTL.applyStyle(block); block = block.next(); endRTL.applyStyle(block); block = block.next(); endRTL.applyStyle(block); m_layout->layout(); QTextLayout *blockLayout = m_block.layout(); // line 'Left' QRectF rect = blockLayout->lineAt(0).naturalTextRect(); QCOMPARE(rect.x(), 100.0); // line 'Right' block = m_doc->begin().next(); rect = block.layout()->lineAt(0).naturalTextRect(); QVERIFY(rect.right() - 200 <= (1 + 100.0)); QVERIFY(rect.left() > 100.0); // line with align Leading and RTL progression block = block.next(); rect = block.layout()->lineAt(0).naturalTextRect(); QVERIFY(rect.right() - 200 <= (1 + 100.0)); QVERIFY(rect.left() > 100.0); // expect right alignment // line with align tailing and RTL progression block = block.next(); rect = block.layout()->lineAt(0).naturalTextRect(); QCOMPARE(rect.x(), 100.0); // expect left alignment // non RTL _text_ but RTL progression as well as align trailing block = block.next(); rect = block.layout()->lineAt(0).naturalTextRect(); QCOMPARE(rect.x(), 100.0); // expect left alignment // TODO can we check if the dot is the left most painted char? } void TestBlockLayout::testParagraphBorders() { setupTest("Paragraph with Borders\nAnother parag\n"); QTextCursor cursor(m_doc->begin()); QTextBlockFormat bf = cursor.blockFormat(); bf.setProperty(KoParagraphStyle::LeftBorderStyle, KoBorder::BorderSolid); bf.setProperty(KoParagraphStyle::TopBorderStyle, KoBorder::BorderSolid); bf.setProperty(KoParagraphStyle::BottomBorderStyle, KoBorder::BorderSolid); bf.setProperty(KoParagraphStyle::RightBorderStyle, KoBorder::BorderSolid); bf.setProperty(KoParagraphStyle::LeftBorderWidth, 8.0); bf.setProperty(KoParagraphStyle::TopBorderWidth, 9.0); bf.setProperty(KoParagraphStyle::BottomBorderWidth, 10.0); bf.setProperty(KoParagraphStyle::RightBorderWidth, 11.0); cursor.setBlockFormat(bf); m_layout->layout(); QTextBlock block = m_doc->begin(); QTextLayout *blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 8.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).y(), 9.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0); block = block.next(); blockLayout = block.layout(); //warnTextLayout << "blockLayout->lineAt(0).y() "<lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10.0 + 100.0)) < ROUNDING); // 14.4 is 12 pt font + 20% linespacing // borders + padding create the total inset. bf.setProperty(KoParagraphStyle::LeftPadding, 5.0); bf.setProperty(KoParagraphStyle::RightPadding, 5.0); bf.setProperty(KoParagraphStyle::TopPadding, 5.0); bf.setProperty(KoParagraphStyle::BottomPadding, 5.0); cursor.setBlockFormat(bf); m_layout->layout(); block = m_doc->begin(); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 13.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).y(), 14.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0 - 5.0 * 2); block = block.next(); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y() << (9.0 + 14.4 + 10 + 5.0 * 2); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10 + 5.0 * 2 + 100.0)) < ROUNDING); // borders are positioned outside the padding, lets check that to be the case. block = m_doc->begin(); KoTextBlockData data(block); KoTextBlockBorderData *border = data.border(); QVERIFY(border); QCOMPARE(border->hasBorders(), true); /* QRectF borderOutline = border->rect(); QCOMPARE(borderOutline.top(), 0.); QCOMPARE(borderOutline.left(), 0.); QCOMPARE(borderOutline.right(), 200.); */ // qreal borders. Specify an additional width for each side. bf.setProperty(KoParagraphStyle::LeftBorderStyle, KoBorder::BorderDouble); bf.setProperty(KoParagraphStyle::TopBorderStyle, KoBorder::BorderDouble); bf.setProperty(KoParagraphStyle::BottomBorderStyle, KoBorder::BorderDouble); bf.setProperty(KoParagraphStyle::RightBorderStyle, KoBorder::BorderDouble); bf.setProperty(KoParagraphStyle::LeftInnerBorderWidth, 2.0); bf.setProperty(KoParagraphStyle::RightInnerBorderWidth, 2.0); bf.setProperty(KoParagraphStyle::BottomInnerBorderWidth, 2.0); bf.setProperty(KoParagraphStyle::TopInnerBorderWidth, 2.0); cursor.setBlockFormat(bf); m_layout->layout(); block = m_doc->begin(); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 15.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).y(), 16.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0 - (5.0 + 2.0) * 2); block = block.next(); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10 + (5.0 + 2.0) * 2 + 100.0)) < ROUNDING); // and last, make the 2 qreal border have a blank space in the middle. bf.setProperty(KoParagraphStyle::LeftBorderSpacing, 3.0); bf.setProperty(KoParagraphStyle::RightBorderSpacing, 3.0); bf.setProperty(KoParagraphStyle::BottomBorderSpacing, 3.0); bf.setProperty(KoParagraphStyle::TopBorderSpacing, 3.0); cursor.setBlockFormat(bf); m_layout->layout(); block = m_doc->begin(); blockLayout = block.layout(); QCOMPARE(blockLayout->lineAt(0).x(), 18.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).y(), 19.0 + 100.0); QCOMPARE(blockLayout->lineAt(0).width(), 200.0 - 8.0 - 11.0 - (5.0 + 2.0 + 3.0) * 2); block = block.next(); blockLayout = block.layout(); //qDebug() << blockLayout->lineAt(0).y(); QVERIFY(qAbs(blockLayout->lineAt(0).y() - (9.0 + 14.4 + 10 + (5.0 + 2.0 + 3.0) * 2 + 100.0)) < ROUNDING); } void TestBlockLayout::testParagraphMargins() { setupTest("Emtpy\nParagraph\nAnother parag\n"); KoParagraphStyle style; style.setFontPointSize(12.0); m_styleManager->add(&style); style.setTopMargin(QTextLength(QTextLength::FixedLength, 10)); KoListStyle listStyle; KoListLevelProperties llp = listStyle.levelProperties(1); llp.setLabelType(KoListStyle::NumberLabelType); llp.setNumberFormat(KoOdfNumberDefinition::Numeric); listStyle.setLevelProperties(llp); style.setListStyle(&listStyle); style.setLeftBorderWidth(3); QTextBlock block = m_doc->begin().next(); style.applyStyle(block); block = block.next(); style.applyStyle(block); m_layout->layout(); block = m_doc->begin().next(); KoTextBlockData data(block); KoTextBlockBorderData *border = data.border(); QVERIFY(border); QCOMPARE(data.counterPosition(), QPointF(3 + 100.0, 24.4 + 100.0)); block = block.next(); KoTextBlockData data2(block); QCOMPARE(data2.counterPosition(), QPointF(3 + 100.0, 48.8 + 100.0)); style.setBottomMargin(QTextLength(QTextLength::FixedLength, 5)); //bottom spacing // manually reapply and relayout to force immediate reaction. block = m_doc->begin().next(); style.applyStyle(block); block = block.next(); style.applyStyle(block); m_layout->layout(); block = m_doc->begin().next(); border = data2.border(); QVERIFY(border); KoTextBlockData data3(block); QCOMPARE(data3.counterPosition(), QPointF(3 + 100.0, 24.4 + 100.0)); block = block.next(); KoTextBlockData data4(block); QCOMPARE(data4.counterPosition(), QPointF(3 + 100.0, 48.8 + 100.0)); // same y as before as we take max spacing } void TestBlockLayout::testEmptyParag() { setupTest("Foo\n\nBar\n"); m_layout->layout(); QTextBlock block = m_doc->begin(); QTextLayout *lay = block.layout(); QVERIFY(lay); QCOMPARE(lay->lineCount(), 1); const qreal y = lay->lineAt(0).position().y(); block = block.next(); lay = block.layout(); QVERIFY(lay); QCOMPARE(lay->lineCount(), 1); QVERIFY(lay->lineAt(0).position().y() > y); QVERIFY(qAbs(lay->lineAt(0).position().y() - (14.4 + 100.0)) < ROUNDING); } void TestBlockLayout::testDropCaps() { setupTest(QString("Lorem ipsum dolor sit amet, XgXgectetuer adiXiscing elit, sed diam\nsome more text")); // some not too long text so the dropcap will be bigger than the block KoParagraphStyle style; style.setFontPointSize(12.0); style.setDropCaps(false); style.setDropCapsLength(1); style.setDropCapsLines(4); style.setDropCapsDistance(9.0); QTextBlock block = m_doc->begin(); QTextBlock secondblock = block.next(); style.applyStyle(block); + qInfo()<<"Font:"<layout(); // dummy version, caps is still false. QTextLayout *blockLayout =block.layout(); QVERIFY(blockLayout->lineCount() > 2); QTextLine line = blockLayout->lineAt(0); QVERIFY(line.textLength() > 3); + qInfo()<<"Dropcaps on"; style.setDropCaps(true); style.applyStyle(block); m_layout->layout(); // test that the first text line is the dropcaps and the positions are right. QVERIFY(blockLayout->lineCount() > 2); line = blockLayout->lineAt(0); QCOMPARE(line.textLength(), 1); QCOMPARE(line.position().x(), 100.0); QVERIFY(line.position().y() <= 100.0); // can't get a tight-boundingrect here. line = blockLayout->lineAt(1); QVERIFY(line.textLength() > 2); qreal heightNormalLine = line.height(); qreal linexpos = line.position().x(); QCOMPARE(line.position().y(), 100.0); // aligned top //qDebug()< 149.0); // can't get a tight-boundingrect here. QVERIFY(line.position().x() < 154.0); // can't get a tight-boundingrect here. // Now test that a following block is moved inward by the same about since // it should still be influenced by the dropcap blockLayout = secondblock.layout(); QVERIFY(blockLayout->lineCount() == 1); line = blockLayout->lineAt(0); QVERIFY(line.textLength() > 3); QCOMPARE(line.position().x(), linexpos); QVERIFY(line.position().x() > 149.0); // can't get a tight-boundingrect here. QVERIFY(line.position().x() < 154.0); // can't get a tight-boundingrect here. + qInfo()<<"Dropcaps off"; style.setDropCaps(false); // remove it style.applyStyle(block); m_layout->layout(); blockLayout = block.layout(); // test that the first text line is no longer dropcaps QVERIFY(blockLayout->lineCount() > 2); line = blockLayout->lineAt(0); QVERIFY(line.textLength() > 1); QCOMPARE(line.height(), heightNormalLine); } QTEST_MAIN(TestBlockLayout) diff --git a/libs/textlayout/tests/TestBlockLayout.h b/libs/textlayout/tests/TestBlockLayout.h index 5475d568718..79505e1f8f5 100644 --- a/libs/textlayout/tests/TestBlockLayout.h +++ b/libs/textlayout/tests/TestBlockLayout.h @@ -1,100 +1,101 @@ /* * This file is part of Calligra tests * * Copyright (C) 2006-2010 Thomas Zander * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTBLOCKLAYOUT_H #define TESTBLOCKLAYOUT_H #include #include #include class KoStyleManager; class QTextDocument; #define ROUNDING 0.126 class TestBlockLayout : public QObject { Q_OBJECT public: TestBlockLayout() {} /// FIXME: fix these broken tests /// Test tabs. void testTabs_data(); void testTabs(); private Q_SLOTS: void initTestCase(); + void testDropCaps(); + /// make sure empty paragraphs are initialized properly void testEmptyParag(); /// Test breaking lines based on the width of the reference rect. void testLineBreaking(); /// Tests incrementing Y pos based on the font size void testBasicLineSpacing(); /// Tests incrementing Y pos based on the font size void testBasicLineSpacing2(); /// Tests fixed linespacing. void testFixedLineSpacing(); /// Tests percentage linespacing. void testPercentageLineSpacing(); /// Tests advanced linespacing options provided in our style. void testAdvancedLineSpacing(); /// Tests that empty lines are given the correct height like in LibreOffice void testEmptyLineHeights(); /// Test distance above and below paragraphs. void testBlockSpacing(); /// Test left and right margins of paragraphs. void testLeftRightMargins(); /// Test first line indent of paragraphs. void testTextIndent(); void testBasicTextAlignments(); void testTextAlignments(); // relativeBulletSize //etc //void testParagOffset(); void testParagraphBorders(); void testParagraphMargins(); - void testDropCaps(); private: void setupTest(const QString &initText = QString()); private: QTextDocument *m_doc; KoTextDocumentLayout *m_layout; QTextBlock m_block; QString m_loremIpsum; KoStyleManager *m_styleManager; KoTextLayoutRootArea *m_area; }; #endif diff --git a/libs/widgets/CMakeLists.txt b/libs/widgets/CMakeLists.txt index d7a0e7ef513..e7734477f2b 100644 --- a/libs/widgets/CMakeLists.txt +++ b/libs/widgets/CMakeLists.txt @@ -1,188 +1,189 @@ add_subdirectory( tests ) add_subdirectory( pics ) include_directories(${KOTEXT_INCLUDES} ${KOODF_INCLUDES} ${PIGMENT_INCLUDES}) include_directories(${CMAKE_SOURCE_DIR}/libs/widgetutils) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if (LIBATTICA_FOUND) include_directories(${LIBATTICA_INCLUDE_DIR}) endif () set(kowidgets_LIB_SRCS KoGradientEditWidget.cpp KoResourcePaths.cpp KoVBox.cpp KoDialog.cpp KoGlobal.cpp KoZoomWidget.cpp KoTagToolButton.cpp KoTagChooserWidget.cpp KoTagFilterWidget.cpp KoResourceTaggingManager.cpp KoResourceItemChooserContextMenu.cpp KoAspectButton.cpp KoCsvImportDialog.cpp KoPageLayoutDialog.cpp KoPageLayoutWidget.cpp KoPagePreviewWidget.cpp KoPositionSelector.cpp KoSliderCombo.cpp KoColorPopupButton.cpp KoConfigAuthorPage.cpp KoUnitDoubleSpinBox.cpp KoZoomAction.cpp KoZoomController.cpp KoZoomInput.cpp KoZoomHandler.cpp KoZoomMode.cpp KoDpi.cpp KoGlobal.cpp KoColorPatch.cpp KoColorPopupAction.cpp KoColorSetWidget.cpp KoColorSlider.cpp KoDualColorButton.cpp KoEditColorSetDialog.cpp KoTriangleColorSelector.cpp KoResourcePopupAction.cpp KoStrokeConfigWidget.cpp KoFillConfigWidget.cpp KoShadowConfigWidget.cpp KoIconToolTip.cpp KoResourceItemChooser.cpp KoResourceItemChooserSync.cpp KoResourceSelector.cpp KoResourceModel.cpp KoResourceItemDelegate.cpp KoResourceItemView.cpp KoResourceTagStore.cpp KoRuler.cpp KoRulerController.cpp KoItemToolTip.cpp KoCheckerBoardPainter.cpp KoResourceServerAdapter.cpp KoResourceServerProvider.cpp KoLineStyleSelector.cpp KoLineStyleItemDelegate.cpp KoLineStyleModel.cpp KoMarkerModel.cpp KoMarkerItemDelegate.cpp KoMarkerSelector.cpp KoDockWidgetTitleBar.cpp KoDockWidgetTitleBarButton.cpp KoViewItemContextBar.cpp KoContextBarButton.cpp KoResourceFiltering.cpp KoResourceModelBase.cpp KoToolBoxButton.cpp KoToolBox.cpp KoToolBoxDocker.cpp KoToolBoxFactory.cpp + KoToolBoxLayout_p.cpp KoToolDocker.cpp KoModeBox.cpp KoModeBoxDocker.cpp KoModeBoxFactory.cpp KoDocumentInfoDlg.cpp KoDocumentInfoPropsPage.cpp KoGlobal.cpp KoTableView.cpp WidgetsDebug.cpp ) ki18n_wrap_ui( kowidgets_LIB_SRCS KoConfigAuthorPage.ui KoCsvImportDialog.ui koDocumentInfoAboutWidget.ui koDocumentInfoAuthorWidget.ui KoEditColorSet.ui KoPageLayoutWidget.ui KoShadowConfigWidget.ui ) add_library(kowidgets SHARED ${kowidgets_LIB_SRCS}) generate_export_header(kowidgets BASE_NAME kowidgets) target_link_libraries(kowidgets PUBLIC kotext pigmentcms kowidgetutils KF5::KIOWidgets PRIVATE KF5::GuiAddons KF5::WidgetsAddons KF5::ConfigCore KF5::Codecs KF5::Completion KF5::IconThemes ) if(GHNS) target_link_libraries(kowidgets PRIVATE KF5::NewStuff) endif () if(X11_FOUND) target_link_libraries(kowidgets PRIVATE Qt5::X11Extras ${X11_LIBRARIES}) endif() set_target_properties(kowidgets PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) install(TARGETS kowidgets ${INSTALL_TARGETS_DEFAULT_ARGS}) if (SHOULD_BUILD_DEVEL_HEADERS) install( FILES KoGlobal.h KoResourceItemChooserContextMenu.h KoGenericRegistryModel.h KoPageLayoutDialog.h KoPageLayoutWidget.h KoPagePreviewWidget.h KoPositionSelector.h ${CMAKE_CURRENT_BINARY_DIR}/kowidgets_export.h KoZoomAction.h KoZoomController.h KoZoomInput.h KoDpi.h KoZoomHandler.h KoZoomMode.h KoGlobal.h KoColorPatch.h KoStrokeConfigWidget.h KoFillConfigWidget.h KoShadowConfigWidget.h KoColorPopupAction.h KoColorSetWidget.h KoColorSlider.h KoDualColorButton.h KoEditColorSetDialog.h KoTriangleColorSelector.h KoResourceItemChooser.h KoResourceSelector.h KoResourceServer.h KoResourceServerAdapter.h KoResourceServerObserver.h KoResourceServerProvider.h KoResourceTagStore.h KoLineStyleSelector.h KoDockWidgetTitleBar.h KoDockWidgetTitleBarButton.h KoResourceModelBase.h KoGlobal.h DESTINATION ${INCLUDE_INSTALL_DIR}/calligra COMPONENT Devel) endif() diff --git a/libs/widgets/KoCsvImportDialog.cpp b/libs/widgets/KoCsvImportDialog.cpp index 132fa2c5e5f..0ffdcf62a70 100644 --- a/libs/widgets/KoCsvImportDialog.cpp +++ b/libs/widgets/KoCsvImportDialog.cpp @@ -1,784 +1,786 @@ /* This file is part of the KDE project Copyright (C) 1999 David Faure Copyright (C) 2004 Nicolas GOUTTE 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. */ #include "KoCsvImportDialog.h" // Qt #include #include #include #include // KF5 #include #include #include #include #include #include #include "ui_KoCsvImportDialog.h" class KoCsvImportWidget : public QWidget, public Ui::KoCsvImportWidget { +Q_OBJECT public: explicit KoCsvImportWidget(QWidget* parent) : QWidget(parent) { setupUi(this); } }; class Q_DECL_HIDDEN KoCsvImportDialog::Private { public: KoCsvImportDialog* q; KoCsvImportWidget* dialog; bool rowsAdjusted; bool columnsAdjusted; int startRow; int startCol; int endRow; int endCol; QChar textQuote; QString delimiter; QString commentSymbol; bool ignoreDuplicates; QByteArray data; QTextCodec* codec; QStringList formatList; ///< List of the column formats explicit Private(KoCsvImportDialog* qq) : q(qq) {} void loadSettings(); void saveSettings(); void fillTable(); void setText(int row, int col, const QString& text); void adjustRows(int iRows); void adjustCols(int iCols); bool checkUpdateRange(); QTextCodec* updateCodec() const; }; KoCsvImportDialog::KoCsvImportDialog(QWidget* parent) : KoDialog(parent) , d(new Private(this)) { d->dialog = new KoCsvImportWidget(this); d->rowsAdjusted = false; d->columnsAdjusted = false; d->startRow = 0; d->startCol = 0; d->endRow = -1; d->endCol = -1; d->textQuote = QChar('"'); d->delimiter = QString(','); d->commentSymbol = QString('#'); d->ignoreDuplicates = false; d->codec = QTextCodec::codecForName("UTF-8"); setButtons( KoDialog::Ok|KoDialog::Cancel ); setCaption( i18n( "Import Data" ) ); QStringList encodings; encodings << i18nc( "Descriptive encoding name", "Recommended ( %1 )" ,"UTF-8" ); encodings << i18nc( "Descriptive encoding name", "Locale ( %1 )" ,QString(QTextCodec::codecForLocale()->name() )); encodings += KCharsets::charsets()->descriptiveEncodingNames(); // Add a few non-standard encodings, which might be useful for text files const QString description(i18nc("Descriptive encoding name","Other ( %1 )")); encodings << description.arg("Apple Roman"); // Apple encodings << description.arg("IBM 850") << description.arg("IBM 866"); // MS DOS encodings << description.arg("CP 1258"); // Windows d->dialog->comboBoxEncoding->insertItems( 0, encodings ); setDataTypes(Generic|Text|Date|None); // XXX: Qt3->Q4 //d->dialog->m_sheet->setReadOnly( true ); d->loadSettings(); //resize(sizeHint()); resize( 600, 400 ); // Try to show as much as possible of the table view setMainWidget(d->dialog); d->dialog->m_sheet->setSelectionMode( QAbstractItemView::MultiSelection ); QButtonGroup* buttonGroup = new QButtonGroup( this ); buttonGroup->addButton(d->dialog->m_radioComma, 0); buttonGroup->addButton(d->dialog->m_radioSemicolon, 1); buttonGroup->addButton(d->dialog->m_radioSpace, 2); buttonGroup->addButton(d->dialog->m_radioTab, 3); buttonGroup->addButton(d->dialog->m_radioOther, 4); connect(d->dialog->m_formatComboBox, SIGNAL(activated(QString)), this, SLOT(formatChanged(QString))); connect(buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(delimiterClicked(int))); connect(d->dialog->m_delimiterEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed())); connect(d->dialog->m_delimiterEdit, SIGNAL(textChanged(QString)), this, SLOT(genericDelimiterChanged(QString))); connect(d->dialog->m_comboQuote, SIGNAL(activated(QString)), this, SLOT(textquoteSelected(QString))); connect(d->dialog->m_sheet, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(currentCellChanged(int,int))); connect(d->dialog->m_ignoreDuplicates, SIGNAL(stateChanged(int)), this, SLOT(ignoreDuplicatesChanged(int))); connect(d->dialog->m_updateButton, SIGNAL(clicked()), this, SLOT(updateClicked())); connect(d->dialog->comboBoxEncoding, SIGNAL(textChanged(QString)), this, SLOT(encodingChanged(QString))); } KoCsvImportDialog::~KoCsvImportDialog() { d->saveSettings(); delete d; } // ---------------------------------------------------------------- // public methods void KoCsvImportDialog::setData( const QByteArray& data ) { d->data = data; d->fillTable(); } bool KoCsvImportDialog::firstRowContainHeaders() const { return d->dialog->m_firstRowHeader->isChecked(); } bool KoCsvImportDialog::firstColContainHeaders() const { return d->dialog->m_firstColHeader->isChecked(); } int KoCsvImportDialog::rows() const { int rows = d->dialog->m_sheet->rowCount(); if ( d->endRow >= 0 ) rows = d->endRow - d->startRow + 1; return rows; } int KoCsvImportDialog::cols() const { int cols = d->dialog->m_sheet->columnCount(); if ( d->endCol >= 0 ) cols = d->endCol - d->startCol + 1; return cols; } QString KoCsvImportDialog::text(int row, int col) const { // Check for overflow. if ( row >= rows() || col >= cols()) return QString(); QTableWidgetItem* item = d->dialog->m_sheet->item( row - d->startRow, col - d->startCol ); if ( !item ) return QString(); return item->text(); } void KoCsvImportDialog::setDataTypes(DataTypes dataTypes) { d->formatList.clear(); if (dataTypes & Generic) d->formatList << i18n("Generic"); if (dataTypes & Text) d->formatList << i18n("Text"); if (dataTypes & Date) d->formatList << i18n("Date"); if (dataTypes & Currency) d->formatList << i18n("Currency"); if (dataTypes & None) d->formatList << i18n("None"); d->dialog->m_formatComboBox->insertItems(0, d->formatList); } void KoCsvImportDialog::setDataWidgetEnabled(bool enable) { d->dialog->m_tabWidget->setTabEnabled(0, enable); } QString KoCsvImportDialog::decimalSymbol() const { return d->dialog->m_decimalSymbol->text(); } void KoCsvImportDialog::setDecimalSymbol(const QString& symbol) { d->dialog->m_decimalSymbol->setText(symbol); } QString KoCsvImportDialog::thousandsSeparator() const { return d->dialog->m_thousandsSeparator->text(); } void KoCsvImportDialog::setThousandsSeparator(const QString& separator) { d->dialog->m_thousandsSeparator->setText(separator); } QString KoCsvImportDialog::delimiter() const { return d->delimiter; } void KoCsvImportDialog::setDelimiter(const QString& delimit) { d->delimiter = delimit; if (delimit == ",") d->dialog->m_radioComma->setChecked(true); else if (delimit == "\t") d->dialog->m_radioTab->setChecked(true); else if (delimit == " ") d->dialog->m_radioSpace->setChecked(true); else if (delimit == ";") d->dialog->m_radioSemicolon->setChecked(true); else { d->dialog->m_radioOther->setChecked(true); d->dialog->m_delimiterEdit->setText(delimit); } } // ---------------------------------------------------------------- void KoCsvImportDialog::Private::loadSettings() { KConfigGroup configGroup = KSharedConfig::openConfig()->group("CSVDialog Settings"); - textQuote = configGroup.readEntry("textQuote", "\"")[0]; + textQuote = configGroup.readEntry("textQuote", "\"").at(0); delimiter = configGroup.readEntry("delimiter", ","); ignoreDuplicates = configGroup.readEntry("ignoreDups", false); const QString codecText = configGroup.readEntry("codec", ""); // update widgets if (!codecText.isEmpty()) { dialog->comboBoxEncoding->setCurrentIndex(dialog->comboBoxEncoding->findText(codecText)); codec = updateCodec(); } q->setDelimiter(delimiter); dialog->m_ignoreDuplicates->setChecked(ignoreDuplicates); dialog->m_comboQuote->setCurrentIndex(textQuote == '\'' ? 1 : textQuote == '"' ? 0 : 2); } void KoCsvImportDialog::Private::saveSettings() { KConfigGroup configGroup = KSharedConfig::openConfig()->group("CSVDialog Settings"); configGroup.writeEntry("textQuote", QString(textQuote)); configGroup.writeEntry("delimiter", delimiter); configGroup.writeEntry("ignoreDups", ignoreDuplicates); configGroup.writeEntry("codec", dialog->comboBoxEncoding->currentText()); configGroup.sync(); } void KoCsvImportDialog::Private::fillTable() { int row, column; bool lastCharDelimiter = false; enum { Start, InQuotedField, MaybeQuotedFieldEnd, QuotedFieldEnd, MaybeInNormalField, InNormalField } state = Start; QChar x; QString field; QApplication::setOverrideCursor(Qt::WaitCursor); dialog->m_sheet->setRowCount(0); dialog->m_sheet->setColumnCount(0); int maxColumn = 1; row = column = 1; QTextStream inputStream(data, QIODevice::ReadOnly); debugWidgets <<"Encoding:" << codec->name(); inputStream.setCodec( codec ); int delimiterIndex = 0; const int delimiterLength = delimiter.size(); bool lastCharWasCr = false; // Last character was a Carriage Return while (!inputStream.atEnd()) { inputStream >> x; // read one char // ### TODO: we should perhaps skip all other control characters if ( x == '\r' ) { // We have a Carriage Return, assume that its role is the one of a LineFeed lastCharWasCr = true; x = '\n'; // Replace by Line Feed } else if ( x == '\n' && lastCharWasCr ) { // The end of line was already handled by the Carriage Return, so do nothing for this character lastCharWasCr = false; continue; } else if ( x == QChar( 0xc ) ) { // We have a FormFeed, skip it lastCharWasCr = false; continue; } else { lastCharWasCr = false; } if ( column > maxColumn ) maxColumn = column; switch (state) { case Start : if (x == textQuote) { state = InQuotedField; } else if (delimiterIndex < delimiterLength && x == delimiter.at(delimiterIndex)) { field += x; delimiterIndex++; if (field.right(delimiterIndex) == delimiter) { if ((ignoreDuplicates == false) || (lastCharDelimiter == false)) column += delimiterLength; lastCharDelimiter = true; field.clear(); delimiterIndex = 0; state = Start; } else if (delimiterIndex >= delimiterLength) delimiterIndex = 0; } else if (x == '\n') { ++row; column = 1; if ( row > ( endRow - startRow ) && endRow >= 0 ) break; } else { field += x; state = MaybeInNormalField; } break; case InQuotedField : if (x == textQuote) { state = MaybeQuotedFieldEnd; } else if (x == '\n') { setText(row - startRow, column - startCol, field); field.clear(); ++row; column = 1; if ( row > ( endRow - startRow ) && endRow >= 0 ) break; state = Start; } else { field += x; } break; case MaybeQuotedFieldEnd : if (x == textQuote) { field += x; state = InQuotedField; } else if (x == '\n') { setText(row - startRow, column - startCol, field); field.clear(); ++row; column = 1; if ( row > ( endRow - startRow ) && endRow >= 0 ) break; state = Start; } else if (delimiterIndex < delimiterLength && x == delimiter.at(delimiterIndex)) { field += x; delimiterIndex++; if (field.right(delimiterIndex) == delimiter) { setText(row - startRow, column - startCol, field.left(field.count()-delimiterIndex)); field.clear(); if ((ignoreDuplicates == false) || (lastCharDelimiter == false)) column += delimiterLength; lastCharDelimiter = true; field.clear(); delimiterIndex = 0; } else if (delimiterIndex >= delimiterLength) delimiterIndex = 0; state = Start; } else { state = QuotedFieldEnd; } break; case QuotedFieldEnd : if (x == '\n') { setText(row - startRow, column - startCol, field); field.clear(); ++row; column = 1; if ( row > ( endRow - startRow ) && endRow >= 0 ) break; state = Start; } else if (delimiterIndex < delimiterLength && x == delimiter.at(delimiterIndex)) { field += x; delimiterIndex++; if (field.right(delimiterIndex) == delimiter) { setText(row - startRow, column - startCol, field.left(field.count()-delimiterIndex)); field.clear(); if ((ignoreDuplicates == false) || (lastCharDelimiter == false)) column += delimiterLength; lastCharDelimiter = true; field.clear(); delimiterIndex = 0; } else if (delimiterIndex >= delimiterLength) delimiterIndex = 0; state = Start; } else { state = QuotedFieldEnd; } break; case MaybeInNormalField : if (x == textQuote) { field.clear(); state = InQuotedField; break; } state = InNormalField; case InNormalField : if (x == '\n') { setText(row - startRow, column - startCol, field); field.clear(); ++row; column = 1; if ( row > ( endRow - startRow ) && endRow >= 0 ) break; state = Start; } else if (delimiterIndex < delimiterLength && x == delimiter.at(delimiterIndex)) { field += x; delimiterIndex++; if (field.right(delimiterIndex) == delimiter) { setText(row - startRow, column - startCol, field.left(field.count()-delimiterIndex)); field.clear(); if ((ignoreDuplicates == false) || (lastCharDelimiter == false)) column += delimiterLength; lastCharDelimiter = true; field.clear(); delimiterIndex = 0; } else if (delimiterIndex >= delimiterLength) delimiterIndex = 0; state = Start; } else { field += x; } } if (delimiter.isEmpty() || x != delimiter.at(0)) lastCharDelimiter = false; } if ( !field.isEmpty() ) { // the last line of the file had not any line end setText(row - startRow, column - startCol, field); ++row; field.clear(); } if (row) row--; // row is higher by 1, so reduce it columnsAdjusted = true; adjustRows( row - startRow ); adjustCols( maxColumn - startCol ); for (column = 0; column < dialog->m_sheet->columnCount(); ++column) { const QTableWidgetItem* headerItem = dialog->m_sheet->horizontalHeaderItem(column); if (!headerItem || !formatList.contains(headerItem->text())) { dialog->m_sheet->setHorizontalHeaderItem(column, new QTableWidgetItem(i18n("Generic"))); } } dialog->m_rowStart->setMinimum(1); dialog->m_colStart->setMinimum(1); dialog->m_rowStart->setMaximum(row); dialog->m_colStart->setMaximum(maxColumn); dialog->m_rowEnd->setMinimum(1); dialog->m_colEnd->setMinimum(1); dialog->m_rowEnd->setMaximum(row); dialog->m_colEnd->setMaximum(maxColumn); dialog->m_rowEnd->setValue(endRow == -1 ? row : endRow); dialog->m_colEnd->setValue(endCol == -1 ? maxColumn : endCol); QApplication::restoreOverrideCursor(); } KoCsvImportDialog::DataType KoCsvImportDialog::dataType(int col) const { const QString header = d->dialog->m_sheet->model()->headerData(col, Qt::Horizontal).toString(); if (header == i18n("Generic")) return Generic; else if (header == i18n("Text")) return Text; else if (header == i18n("Date")) return Date; else if (header == i18n("Currency")) return Currency; else if (header == i18n("None")) return None; return Generic; } void KoCsvImportDialog::Private::setText(int row, int col, const QString& text) { if (row < 1 || col < 1) // skipped by the user return; if ((row > (endRow - startRow) && endRow > 0) || (col > (endCol - startCol) && endCol > 0)) return; if (dialog->m_sheet->rowCount() < row) { dialog->m_sheet->setRowCount(row + 5000); /* We add 5000 at a time to limit recalculations */ rowsAdjusted = true; } if (dialog->m_sheet->columnCount() < col) { dialog->m_sheet->setColumnCount(col); columnsAdjusted = true; } QTableWidgetItem* item = dialog->m_sheet->item(row - 1, col - 1); if (!item) { item = new QTableWidgetItem(); dialog->m_sheet->setItem(row - 1, col - 1, item); } item->setText(text); } /* * Called after the first fillTable() when number of rows are unknown. */ void KoCsvImportDialog::Private::adjustRows(int iRows) { if (rowsAdjusted) { dialog->m_sheet->setRowCount(iRows); rowsAdjusted = false; } } void KoCsvImportDialog::Private::adjustCols(int iCols) { if (columnsAdjusted) { dialog->m_sheet->setColumnCount(iCols); columnsAdjusted = false; if (endCol == -1) { if (iCols > (endCol - startCol)) iCols = endCol - startCol; dialog->m_sheet->setColumnCount(iCols); } } } void KoCsvImportDialog::returnPressed() { if (d->dialog->m_radioOther->isChecked()) return; d->delimiter = d->dialog->m_delimiterEdit->text(); d->fillTable(); } void KoCsvImportDialog::genericDelimiterChanged( const QString & ) { d->dialog->m_radioOther->setChecked ( true ); delimiterClicked(d->dialog->m_radioOther->group()->id(d->dialog->m_radioOther)); // other } void KoCsvImportDialog::formatChanged( const QString& newValue ) { QList selectionRanges = d->dialog->m_sheet->selectedRanges(); foreach (const QTableWidgetSelectionRange &selectionRange, selectionRanges) { for (int j = selectionRange.leftColumn(); j <= selectionRange.rightColumn(); ++j) { d->dialog->m_sheet->horizontalHeaderItem(j)->setText(newValue); } } } void KoCsvImportDialog::delimiterClicked(int id) { const QButtonGroup* group = d->dialog->m_radioComma->group(); if (id == group->id(d->dialog->m_radioComma) ) d->delimiter = ','; else if (id == group->id(d->dialog->m_radioOther)) d->delimiter = d->dialog->m_delimiterEdit->text(); else if (id == group->id(d->dialog->m_radioTab)) d->delimiter = '\t'; else if (id == group->id(d->dialog->m_radioSpace)) d->delimiter = ' '; else if (id == group->id(d->dialog->m_radioSemicolon)) d->delimiter = ';'; debugWidgets << "Delimiter" << d->delimiter << "selected."; d->fillTable(); } void KoCsvImportDialog::textquoteSelected(const QString& mark) { if (mark == i18n("None")) d->textQuote = 0; else d->textQuote = mark[0]; d->fillTable(); } void KoCsvImportDialog::updateClicked() { if ( !d->checkUpdateRange() ) return; d->startRow = d->dialog->m_rowStart->value() - 1; d->endRow = d->dialog->m_rowEnd->value(); d->startCol = d->dialog->m_colStart->value() - 1; d->endCol = d->dialog->m_colEnd->value(); d->fillTable(); } bool KoCsvImportDialog::Private::checkUpdateRange() { if ((dialog->m_rowStart->value() > dialog->m_rowEnd->value()) || (dialog->m_colStart->value() > dialog->m_colEnd->value())) { KMessageBox::error(0, i18n("Please check the ranges you specified. The start value must be lower than the end value.")); return false; } return true; } void KoCsvImportDialog::currentCellChanged(int, int col) { const QString header = d->dialog->m_sheet->model()->headerData(col, Qt::Horizontal).toString(); const int index = d->dialog->m_formatComboBox->findText(header); d->dialog->m_formatComboBox->setCurrentIndex(index > -1 ? index : 0); } void KoCsvImportDialog::ignoreDuplicatesChanged(int) { if (d->dialog->m_ignoreDuplicates->isChecked()) d->ignoreDuplicates = true; else d->ignoreDuplicates = false; d->fillTable(); } QTextCodec* KoCsvImportDialog::Private::updateCodec() const { const QString strCodec( KCharsets::charsets()->encodingForName( dialog->comboBoxEncoding->currentText() ) ); debugWidgets <<"Encoding:" << strCodec; bool ok = false; QTextCodec* codec = QTextCodec::codecForName( strCodec.toUtf8() ); // If QTextCodec has not found a valid encoding, so try with KCharsets. if ( codec ) { ok = true; } else { codec = KCharsets::charsets()->codecForName( strCodec, ok ); } // Still nothing? if ( !codec || !ok ) { // Default: UTF-8 warnWidgets << "Cannot find encoding:" << strCodec; // ### TODO: what parent to use? KMessageBox::error( 0, i18n("Cannot find encoding: %1", strCodec ) ); return 0; } return codec; } void KoCsvImportDialog::encodingChanged(const QString &) { QTextCodec* codec = d->updateCodec(); if ( codec ) { d->codec = codec; d->fillTable(); } } +#include "KoCsvImportDialog.moc" diff --git a/libs/widgets/KoDocumentInfoDlg.cpp b/libs/widgets/KoDocumentInfoDlg.cpp index f6f7b71cdac..51059c7cadd 100644 --- a/libs/widgets/KoDocumentInfoDlg.cpp +++ b/libs/widgets/KoDocumentInfoDlg.cpp @@ -1,470 +1,472 @@ /* This file is part of the KDE project Copyright (c) 2000 Simon Hausmann 2006 Martin Pfeiffer 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. */ #include "KoDocumentInfoDlg.h" #include "ui_koDocumentInfoAboutWidget.h" #include "ui_koDocumentInfoAuthorWidget.h" #include "KoDocumentInfo.h" #include "KoDocumentBase.h" #include "KoGlobal.h" #include #include "KoPageWidgetItem.h" #include #include #include #include #include #include #include #include #include #include #include #include // see KoIcon.h #define koSmallIcon(name) (SmallIcon(QStringLiteral(name))) class KoPageWidgetItemAdapter : public KPageWidgetItem { +Q_OBJECT public: KoPageWidgetItemAdapter(KoPageWidgetItem *item) : KPageWidgetItem(item->widget(), item->name()) , m_item(item) { setHeader(item->name()); setIcon(QIcon::fromTheme(item->iconName())); } ~KoPageWidgetItemAdapter() { delete m_item; } bool shouldDialogCloseBeVetoed() { return m_item->shouldDialogCloseBeVetoed(); } void apply() { m_item->apply(); } private: KoPageWidgetItem * const m_item; }; class KoDocumentInfoDlg::KoDocumentInfoDlgPrivate { public: KoDocumentInfoDlgPrivate() : toggleEncryption(false), applyToggleEncryption(false), documentSaved(false) {} ~KoDocumentInfoDlgPrivate() {} KoDocumentInfo* info; QList pages; Ui::KoDocumentInfoAboutWidget* aboutUi; Ui::KoDocumentInfoAuthorWidget* authorUi; bool toggleEncryption; bool applyToggleEncryption; bool documentSaved; }; KoDocumentInfoDlg::KoDocumentInfoDlg(QWidget* parent, KoDocumentInfo* docInfo) : KPageDialog(parent) , d(new KoDocumentInfoDlgPrivate) { d->info = docInfo; setWindowTitle(i18n("Document Information")); // setInitialSize(QSize(500, 500)); setFaceType(KPageDialog::List); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); button(QDialogButtonBox::Ok)->setDefault(true); d->aboutUi = new Ui::KoDocumentInfoAboutWidget(); QWidget *infodlg = new QWidget(); d->aboutUi->setupUi(infodlg); if (!KoEncryptionChecker::isEncryptionSupported()) { d->aboutUi->lblEncryptedDesc->setVisible(false); d->aboutUi->lblEncrypted->setVisible(false); d->aboutUi->pbEncrypt->setVisible(false); d->aboutUi->lblEncryptedPic->setVisible(false); } d->aboutUi->cbLanguage->addItems(KoGlobal::listOfLanguages()); d->aboutUi->cbLanguage->setCurrentIndex(-1); KPageWidgetItem *page = new KPageWidgetItem(infodlg, i18n("General")); page->setHeader(i18n("General")); // Ugly hack, the mimetype should be a parameter, instead KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent()); if (doc) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(doc->mimeType()); if (mime.isValid()) { page->setIcon(QIcon::fromTheme(mime.iconName())); } } else { // hide all entries not used in pages for KoDocumentInfoPropsPage d->aboutUi->filePathInfoLabel->setVisible(false); d->aboutUi->filePathLabel->setVisible(false); d->aboutUi->filePathSeparatorLine->setVisible(false); d->aboutUi->lblTypeDesc->setVisible(false); d->aboutUi->lblType->setVisible(false); } addPage(page); d->pages.append(page); initAboutTab(); d->authorUi = new Ui::KoDocumentInfoAuthorWidget(); QWidget *authordlg = new QWidget(); d->authorUi->setupUi(authordlg); page = new KPageWidgetItem(authordlg, i18n("Author")); page->setHeader(i18n("Last saved by")); page->setIcon(koIcon("user-identity")); addPage(page); d->pages.append(page); initAuthorTab(); } KoDocumentInfoDlg::~KoDocumentInfoDlg() { delete d->authorUi; delete d->aboutUi; delete d; } void KoDocumentInfoDlg::accept() { // check if any pages veto the close foreach(KPageWidgetItem* item, d->pages) { KoPageWidgetItemAdapter *page = dynamic_cast(item); if (page) { if (page->shouldDialogCloseBeVetoed()) { return; } } } // all fine, go and apply saveAboutData(); foreach(KPageWidgetItem* item, d->pages) { KoPageWidgetItemAdapter *page = dynamic_cast(item); if (page) { page->apply(); } } KPageDialog::accept(); } bool KoDocumentInfoDlg::isDocumentSaved() { return d->documentSaved; } void KoDocumentInfoDlg::initAboutTab() { KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent()); if (doc) { d->aboutUi->filePathLabel->setText(doc->localFilePath()); } d->aboutUi->leTitle->setText(d->info->aboutInfo("title")); d->aboutUi->leSubject->setText(d->info->aboutInfo("subject")); QString language = KoGlobal::languageFromTag(d->info->aboutInfo("language")); d->aboutUi->cbLanguage->setCurrentIndex(d->aboutUi->cbLanguage->findText(language)); d->aboutUi->leKeywords->setToolTip(i18n("Use ';' (Example: Office;KDE;Calligra)")); if (!d->info->aboutInfo("keyword").isEmpty()) d->aboutUi->leKeywords->setText(d->info->aboutInfo("keyword")); d->aboutUi->meComments->setPlainText(d->info->aboutInfo("description")); if (doc && !doc->mimeType().isEmpty()) { QMimeDatabase db; QMimeType docmime = db.mimeTypeForName(doc->mimeType()); if (docmime.isValid()) d->aboutUi->lblType->setText(docmime.comment()); } if (!d->info->aboutInfo("creation-date").isEmpty()) { QDateTime t = QDateTime::fromString(d->info->aboutInfo("creation-date"), Qt::ISODate); QString s = QLocale().toString(t); d->aboutUi->lblCreated->setText(s + ", " + d->info->aboutInfo("initial-creator")); } if (!d->info->aboutInfo("date").isEmpty()) { QDateTime t = QDateTime::fromString(d->info->aboutInfo("date"), Qt::ISODate); QString s = QLocale().toString(t); d->aboutUi->lblModified->setText(s + ", " + d->info->authorInfo("creator")); } d->aboutUi->lblRevision->setText(d->info->aboutInfo("editing-cycles")); if (doc && (doc->supportedSpecialFormats() & KoDocumentBase::SaveEncrypted)) { if (doc->specialOutputFlag() == KoDocumentBase::SaveEncrypted) { if (d->toggleEncryption) { d->aboutUi->lblEncrypted->setText(i18n("This document will be decrypted")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked")); d->aboutUi->pbEncrypt->setText(i18n("Do not decrypt")); } else { d->aboutUi->lblEncrypted->setText(i18n("This document is encrypted")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked")); d->aboutUi->pbEncrypt->setText(i18n("D&ecrypt")); } } else { if (d->toggleEncryption) { d->aboutUi->lblEncrypted->setText(i18n("This document will be encrypted.")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked")); d->aboutUi->pbEncrypt->setText(i18n("Do not encrypt")); } else { d->aboutUi->lblEncrypted->setText(i18n("This document is not encrypted")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked")); d->aboutUi->pbEncrypt->setText(i18n("&Encrypt")); } } } else { d->aboutUi->lblEncrypted->setText(i18n("This document does not support encryption")); d->aboutUi->pbEncrypt->setEnabled( false ); } connect(d->aboutUi->pbReset, SIGNAL(clicked()), this, SLOT(slotResetMetaData())); connect(d->aboutUi->pbEncrypt, SIGNAL(clicked()), this, SLOT(slotToggleEncryption())); } void KoDocumentInfoDlg::initAuthorTab() { d->authorUi->fullName->setText(d->info->authorInfo("creator")); d->authorUi->initials->setText(d->info->authorInfo("initial")); d->authorUi->title->setText(d->info->authorInfo("author-title")); d->authorUi->company->setText(d->info->authorInfo("company")); d->authorUi->email->setText(d->info->authorInfo("email")); d->authorUi->phoneWork->setText(d->info->authorInfo("telephone-work")); d->authorUi->phoneHome->setText(d->info->authorInfo("telephone")); d->authorUi->fax->setText(d->info->authorInfo("fax")); d->authorUi->country->setText(d->info->authorInfo("country")); d->authorUi->postal->setText(d->info->authorInfo("postal-code")); d->authorUi->city->setText(d->info->authorInfo("city")); d->authorUi->street->setText(d->info->authorInfo("street")); d->authorUi->position->setText(d->info->authorInfo("position")); } void KoDocumentInfoDlg::saveAboutData() { d->info->setAboutInfo("keyword", d->aboutUi->leKeywords->text()); d->info->setAboutInfo("title", d->aboutUi->leTitle->text()); d->info->setAboutInfo("subject", d->aboutUi->leSubject->text()); d->info->setAboutInfo("description", d->aboutUi->meComments->toPlainText()); d->info->setAboutInfo("language", KoGlobal::tagOfLanguage(d->aboutUi->cbLanguage->currentText())); d->applyToggleEncryption = d->toggleEncryption; } void KoDocumentInfoDlg::hideEvent( QHideEvent *event ) { Q_UNUSED(event); // Saving encryption implies saving the document, this is done after closing the dialog // TODO: shouldn't this be skipped if cancel is pressed? saveEncryption(); } void KoDocumentInfoDlg::slotResetMetaData() { d->info->resetMetaData(); if (!d->info->aboutInfo("creation-date").isEmpty()) { QDateTime t = QDateTime::fromString(d->info->aboutInfo("creation-date"), Qt::ISODate); QString s = QLocale().toString(t); d->aboutUi->lblCreated->setText(s + ", " + d->info->aboutInfo("initial-creator")); } if (!d->info->aboutInfo("date").isEmpty()) { QDateTime t = QDateTime::fromString(d->info->aboutInfo("date"), Qt::ISODate); QString s = QLocale().toString(t); d->aboutUi->lblModified->setText(s + ", " + d->info->authorInfo("creator")); } d->aboutUi->lblRevision->setText(d->info->aboutInfo("editing-cycles")); } void KoDocumentInfoDlg::slotToggleEncryption() { KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent()); if (!doc) return; d->toggleEncryption = !d->toggleEncryption; if (doc->specialOutputFlag() == KoDocumentBase::SaveEncrypted) { if (d->toggleEncryption) { d->aboutUi->lblEncrypted->setText(i18n("This document will be decrypted")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked")); d->aboutUi->pbEncrypt->setText(i18n("Do not decrypt")); } else { d->aboutUi->lblEncrypted->setText(i18n("This document is encrypted")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked")); d->aboutUi->pbEncrypt->setText(i18n("D&ecrypt")); } } else { if (d->toggleEncryption) { d->aboutUi->lblEncrypted->setText(i18n("This document will be encrypted.")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-locked")); d->aboutUi->pbEncrypt->setText(i18n("Do not encrypt")); } else { d->aboutUi->lblEncrypted->setText(i18n("This document is not encrypted")); d->aboutUi->lblEncryptedPic->setPixmap(koSmallIcon("object-unlocked")); d->aboutUi->pbEncrypt->setText(i18n("&Encrypt")); } } } void KoDocumentInfoDlg::saveEncryption() { if (!d->applyToggleEncryption) return; KoDocumentBase* doc = dynamic_cast< KoDocumentBase* >(d->info->parent()); if (!doc) return; KMainWindow* mainWindow = dynamic_cast< KMainWindow* >(parent()); if (doc->specialOutputFlag() == KoDocumentBase::SaveEncrypted) { // Decrypt if (KMessageBox::warningContinueCancel( this, i18n("Decrypting the document will remove the password protection from it." "

Do you still want to decrypt the file?"), i18n("Confirm Decrypt"), KGuiItem(i18n("Decrypt")), KStandardGuiItem::cancel(), "DecryptConfirmation" ) != KMessageBox::Continue) { return; } bool modified = doc->isModified(); doc->setOutputMimeType(doc->outputMimeType(), doc->specialOutputFlag() & ~KoDocumentBase::SaveEncrypted); if (!mainWindow) { KMessageBox::information( this, i18n("Your document could not be saved automatically." "

To complete the decryption, please save the document."), i18n("Save Document"), "DecryptSaveMessage"); return; } if (modified && KMessageBox::questionYesNo( this, i18n("The document has been changed since it was opened. To complete the decryption the document needs to be saved." "

Do you want to save the document now?"), i18n("Save Document"), KStandardGuiItem::save(), KStandardGuiItem::dontSave(), "DecryptSaveConfirmation" ) != KMessageBox::Yes) { return; } } else { // Encrypt bool modified = doc->isModified(); if (!doc->url().isEmpty() && !(doc->mimeType().startsWith("application/vnd.oasis.opendocument.") && doc->specialOutputFlag() == 0)) { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(doc->mimeType()); QString comment = mime.isValid() ? mime.comment() : i18n("%1 (unknown file type)", QString::fromLatin1(doc->mimeType())); if (KMessageBox::warningContinueCancel( this, i18n("The document is currently saved as %1. The document needs to be changed to OASIS OpenDocument to be encrypted." "

Do you want to change the file to OASIS OpenDocument?", QString("%1").arg(comment)), i18n("Change Filetype"), KGuiItem(i18n("Change")), KStandardGuiItem::cancel(), "EncryptChangeFiletypeConfirmation" ) != KMessageBox::Continue) { return; } doc->resetURL(); } doc->setMimeType(doc->nativeOasisMimeType()); doc->setOutputMimeType(doc->nativeOasisMimeType(), KoDocumentBase::SaveEncrypted); if (!mainWindow) { KMessageBox::information( this, i18n("Your document could not be saved automatically." "

To complete the encryption, please save the document."), i18n("Save Document"), "EncryptSaveMessage"); return; } if (modified && KMessageBox::questionYesNo( this, i18n("The document has been changed since it was opened. To complete the encryption the document needs to be saved." "

Do you want to save the document now?"), i18n("Save Document"), KStandardGuiItem::save(), KStandardGuiItem::dontSave(), "EncryptSaveConfirmation" ) != KMessageBox::Yes) { return; } } // Why do the dirty work ourselves? emit saveRequested(); d->toggleEncryption = false; d->applyToggleEncryption = false; // Detects when the user cancelled saving d->documentSaved = !doc->url().isEmpty(); } QList KoDocumentInfoDlg::pages() const { return d->pages; } void KoDocumentInfoDlg::setReadOnly(bool ro) { d->aboutUi->meComments->setReadOnly(ro); Q_FOREACH(KPageWidgetItem* page, d->pages) { Q_FOREACH(QLineEdit* le, page->widget()->findChildren()) { le->setReadOnly(ro); } Q_FOREACH(QPushButton* le, page->widget()->findChildren()) { le->setDisabled(ro); } } } void KoDocumentInfoDlg::addPageItem(KoPageWidgetItem *item) { KPageWidgetItem * page = new KoPageWidgetItemAdapter(item); addPage(page); d->pages.append(page); } +#include "KoDocumentInfoDlg.moc" diff --git a/libs/widgets/KoIconToolTip.h b/libs/widgets/KoIconToolTip.h index ebbd83224b7..5093a7ffbd6 100644 --- a/libs/widgets/KoIconToolTip.h +++ b/libs/widgets/KoIconToolTip.h @@ -1,40 +1,40 @@ /* This file is part of the KDE project Copyright (c) 1999 Carsten Pfeiffer (pfeiffer@kde.org) Copyright (c) 2002 Igor Jansen (rm@kde.org) 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. */ #ifndef KOICONTOOLTIP_H #define KOICONTOOLTIP_H #include "KoItemToolTip.h" class KoIconToolTip: public KoItemToolTip { - +Q_OBJECT public: KoIconToolTip() {} virtual ~KoIconToolTip() {} protected: virtual QTextDocument *createDocument( const QModelIndex &index ); private: typedef KoItemToolTip super; }; #endif // KOICONTOOLTIP_H diff --git a/libs/widgets/KoLineStyleItemDelegate_p.h b/libs/widgets/KoLineStyleItemDelegate_p.h index 65683fcdc4d..04ecdb9901e 100644 --- a/libs/widgets/KoLineStyleItemDelegate_p.h +++ b/libs/widgets/KoLineStyleItemDelegate_p.h @@ -1,34 +1,35 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * 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. */ #ifndef KOLINESTYLEITEMDELEGATE_H #define KOLINESTYLEITEMDELEGATE_H #include /// The line style item delegate for rendering the styles class KoLineStyleItemDelegate : public QAbstractItemDelegate { +Q_OBJECT public: explicit KoLineStyleItemDelegate(QObject *parent = 0); ~KoLineStyleItemDelegate() {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const; }; #endif diff --git a/libs/widgets/KoLineStyleModel_p.h b/libs/widgets/KoLineStyleModel_p.h index 5920c9118f7..5fc44c181a7 100644 --- a/libs/widgets/KoLineStyleModel_p.h +++ b/libs/widgets/KoLineStyleModel_p.h @@ -1,45 +1,46 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * 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. */ #ifndef KOLINESTYLEMODEL_H #define KOLINESTYLEMODEL_H #include #include /// The line style model managing the style data class KoLineStyleModel : public QAbstractListModel { +Q_OBJECT public: explicit KoLineStyleModel(QObject *parent = 0); virtual ~KoLineStyleModel() {} int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; /// adds the given style to the model bool addCustomStyle(const QVector &style); /// selects the given style int setLineStyle(Qt::PenStyle style, const QVector &dashes); private: QList > m_styles; ///< the added styles QVector m_tempStyle; ///< a temporary added style bool m_hasTempStyle; ///< state of the temporary style }; #endif diff --git a/libs/widgets/KoMarkerItemDelegate.h b/libs/widgets/KoMarkerItemDelegate.h index e8b847f5ef9..12ecc172fcf 100644 --- a/libs/widgets/KoMarkerItemDelegate.h +++ b/libs/widgets/KoMarkerItemDelegate.h @@ -1,40 +1,41 @@ /* This file is part of the KDE project * Copyright (C) 2011 Thorsten Zachmann * * 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. */ #ifndef KOMARKERITEMDELEGATE_H #define KOMARKERITEMDELEGATE_H // Calligra #include // Qt #include class KoMarkerItemDelegate : public QAbstractItemDelegate { +Q_OBJECT public: explicit KoMarkerItemDelegate(KoMarkerData::MarkerPosition position, QObject *parent = 0); virtual ~KoMarkerItemDelegate(); virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &index) const; private: KoMarkerData::MarkerPosition m_position; }; #endif /* KOMARKERITEMDELEGATE_H */ diff --git a/libs/widgets/KoMarkerModel.h b/libs/widgets/KoMarkerModel.h index 7f7402b0436..581473eda84 100644 --- a/libs/widgets/KoMarkerModel.h +++ b/libs/widgets/KoMarkerModel.h @@ -1,46 +1,47 @@ /* This file is part of the KDE project * Copyright (C) 2011 Thorsten Zachmann * * 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. */ #ifndef KOMARKERMODEL_H #define KOMARKERMODEL_H #include #include class KoMarker; class KoMarkerModel : public QAbstractListModel { +Q_OBJECT public: KoMarkerModel(const QList markers, KoMarkerData::MarkerPosition position, QObject *parent = 0); virtual ~KoMarkerModel(); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; int markerIndex(KoMarker *marker) const; QVariant marker(int index, int role = Qt::UserRole) const; KoMarkerData::MarkerPosition position() const; private: QList m_markers; KoMarkerData::MarkerPosition m_markerPosition; }; #endif /* KOMARKERMODEL_H */ diff --git a/libs/widgets/KoPositionSelector.cpp b/libs/widgets/KoPositionSelector.cpp index ce5123f806c..cdf7f83d4e4 100644 --- a/libs/widgets/KoPositionSelector.cpp +++ b/libs/widgets/KoPositionSelector.cpp @@ -1,233 +1,235 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * * 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. */ #include "KoPositionSelector.h" #include #include #include #include #include #include #define GAP 0 class Q_DECL_HIDDEN KoPositionSelector::Private { public: Private() : position(KoFlake::TopLeftCorner) { topLeft = createButton(KoFlake::TopLeftCorner); topLeft->setChecked(true); topRight = createButton(KoFlake::TopRightCorner); center = createButton(KoFlake::CenteredPosition); bottomRight = createButton(KoFlake::BottomRightCorner); bottomLeft = createButton(KoFlake::BottomLeftCorner); } QRadioButton *createButton(int id) { QRadioButton *b = new QRadioButton(); buttonGroup.addButton(b, id); return b; } QRadioButton *topLeft, *topRight, *center, *bottomRight, *bottomLeft; QButtonGroup buttonGroup; KoFlake::Position position; }; class RadioLayout : public QLayout { +Q_OBJECT public: RadioLayout(QWidget *parent) : QLayout(parent) { } ~RadioLayout() { foreach( const Item & item, items ) delete item.child; items.clear(); } void setGeometry (const QRect &geom) { QSize prefSize = calcSizes(); qreal columnWidth, rowHeight; if (geom.width() <= minimum.width()) columnWidth = geom.width() / (qreal) maxCol; else columnWidth = prefSize.width() + GAP; if (geom.height() <= minimum.height()) rowHeight = geom.height() / (qreal) maxRow; else rowHeight = prefSize.height() + GAP; // padding inside row and column so that radio button is centered QPoint padding( qRound(0.5 * (columnWidth - prefSize.width())), qRound(0.5 * (rowHeight - prefSize.height()))); // offset so that all the radio button are centered within the widget qreal offsetX = 0.5 * (geom.width()- static_cast(maxCol) * columnWidth); qreal offsetY = 0.5 * (geom.height() - static_cast(maxRow) * rowHeight); QPoint offset( qRound(offsetX), qRound(offsetY)); foreach(const Item & item, items) { QPoint point( qRound(item.column * columnWidth), qRound(item.row * rowHeight) ); QRect rect(point + offset + padding + geom.topLeft(), prefSize); item.child->setGeometry(rect); } } QSize calcSizes() { QSize prefSize; maxRow = 0; maxCol = 0; foreach(const Item & item, items) { if(prefSize.isEmpty()) { QAbstractButton *but = dynamic_cast (item.child->widget()); Q_ASSERT(but); QStyleOptionButton opt; opt.initFrom(but); prefSize = QSize(but->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, &opt, but), but->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight, &opt, but)); } maxRow = qMax(maxRow, item.row); maxCol = qMax(maxCol, item.column); } maxCol++; maxRow++; // due to being zero-based. preferred = QSize(maxCol * prefSize.width() + (maxCol-1) * GAP, maxRow * prefSize.height() + (maxRow-1) * GAP); minimum = QSize(maxCol * prefSize.width(), maxRow * prefSize.height()); return prefSize; } QLayoutItem *itemAt (int index) const { if( index < count() ) return items.at(index).child; else return 0; } QLayoutItem *takeAt (int index) { Q_ASSERT(index < count()); Item item = items.takeAt(index); return item.child; } int count () const { return items.count(); } void addItem(QLayoutItem *) { Q_ASSERT(0); } QSize sizeHint() const { if(preferred.isEmpty()) const_cast (this)->calcSizes(); return preferred; } QSize minimumSize() const { if(minimum.isEmpty()) const_cast (this)->calcSizes(); return minimum; } void addWidget(QRadioButton *widget, int row, int column) { addChildWidget(widget); Item newItem; newItem.child = new QWidgetItem(widget); newItem.row = row; newItem.column = column; items.append(newItem); } private: struct Item { QLayoutItem *child; int column; int row; }; QList items; QSize preferred, minimum; int maxCol, maxRow; }; KoPositionSelector::KoPositionSelector(QWidget *parent) : QWidget(parent), d(new Private()) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); RadioLayout *lay = new RadioLayout(this); lay->addWidget(d->topLeft, 0, 0); lay->addWidget(d->topRight, 0, 2); lay->addWidget(d->center, 1, 1); lay->addWidget(d->bottomRight, 2, 2); lay->addWidget(d->bottomLeft, 2, 0); setLayout(lay); connect(&d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(positionChanged(int))); } KoPositionSelector::~KoPositionSelector() { delete d; } KoFlake::Position KoPositionSelector::position() const { return d->position; } void KoPositionSelector::setPosition(KoFlake::Position position) { d->position = position; switch(d->position) { case KoFlake::TopLeftCorner: d->topLeft->setChecked(true); break; case KoFlake::TopRightCorner: d->topRight->setChecked(true); break; case KoFlake::CenteredPosition: d->center->setChecked(true); break; case KoFlake::BottomLeftCorner: d->bottomLeft->setChecked(true); break; case KoFlake::BottomRightCorner: d->bottomRight->setChecked(true); break; } } void KoPositionSelector::positionChanged(int position) { d->position = static_cast (position); emit positionSelected(d->position); } void KoPositionSelector::paintEvent (QPaintEvent *) { QPainter painter( this ); QPen pen(Qt::black); int width; if (d->topLeft->width() %2 == 0) width = 2; else width = 3; pen.setWidth(width); painter.setPen(pen); painter.drawRect( QRect( d->topLeft->geometry().center(), d->bottomRight->geometry().center() ) ); painter.end(); } +#include "KoPositionSelector.moc" diff --git a/libs/widgets/KoResourceItemDelegate.h b/libs/widgets/KoResourceItemDelegate.h index af8a1e5ad30..0dcea630e48 100644 --- a/libs/widgets/KoResourceItemDelegate.h +++ b/libs/widgets/KoResourceItemDelegate.h @@ -1,40 +1,41 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * 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. */ #ifndef KORESOURCEITEMDELEGATE_H #define KORESOURCEITEMDELEGATE_H #include #include "KoCheckerBoardPainter.h" /// The resource item delegate for rendering the resource preview class KoResourceItemDelegate : public QAbstractItemDelegate { +Q_OBJECT public: explicit KoResourceItemDelegate(QObject *parent = 0); virtual ~KoResourceItemDelegate() {} /// reimplemented virtual void paint( QPainter *, const QStyleOptionViewItem &, const QModelIndex & ) const; /// reimplemented QSize sizeHint ( const QStyleOptionViewItem &, const QModelIndex & ) const; private: KoCheckerBoardPainter m_checkerPainter; }; #endif // KORESOURCEITEMDELEGATE_H diff --git a/libs/widgets/KoResourcePopupAction.cpp b/libs/widgets/KoResourcePopupAction.cpp index 24323a918da..df2ff1e6e96 100644 --- a/libs/widgets/KoResourcePopupAction.cpp +++ b/libs/widgets/KoResourcePopupAction.cpp @@ -1,201 +1,201 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2012 Jean-Nicolas Artaud * * 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. */ #include "KoResourcePopupAction.h" #include "KoResourceServerAdapter.h" #include "KoResourceItemView.h" #include "KoResourceModel.h" #include "KoResourceItemDelegate.h" #include "KoResource.h" #include "KoCheckerBoardPainter.h" #include "KoShapeBackground.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KoResourcePopupAction::Private { public: Private() : resourceList(0), background(0), checkerPainter(4) {} QMenu *menu; KoResourceItemView *resourceList; QSharedPointer background; KoCheckerBoardPainter checkerPainter; }; KoResourcePopupAction::KoResourcePopupAction(QSharedPointerresourceAdapter, QObject *parent) : QAction(parent) , d(new Private()) { Q_ASSERT(resourceAdapter); d->menu = new QMenu(); QWidget *widget = new QWidget(); QWidgetAction *wdgAction = new QWidgetAction(this); d->resourceList = new KoResourceItemView(widget); d->resourceList->setModel(new KoResourceModel(resourceAdapter, widget)); d->resourceList->setItemDelegate(new KoResourceItemDelegate(widget)); KoResourceModel * resourceModel = qobject_cast(d->resourceList->model()); if (resourceModel) { resourceModel->setColumnCount(1); } KoResource *resource = 0; if (resourceAdapter->resources().count() > 0) { resource = resourceAdapter->resources().at(0); } KoAbstractGradient *gradient = dynamic_cast(resource); KoPattern *pattern = dynamic_cast(resource); if (gradient) { QGradient *qg = gradient->toQGradient(); qg->setCoordinateMode(QGradient::ObjectBoundingMode); d->background = QSharedPointer(new KoGradientBackground(qg)); } else if (pattern) { KoImageCollection *collection = new KoImageCollection(); d->background = QSharedPointer(new KoPatternBackground(collection)); static_cast(d->background.data())->setPattern(pattern->pattern()); } QHBoxLayout *layout = new QHBoxLayout(widget); layout->addWidget(d->resourceList); widget->setLayout(layout); wdgAction->setDefaultWidget(widget); d->menu->addAction(wdgAction); setMenu(d->menu); new QHBoxLayout(d->menu); d->menu->layout()->addWidget(widget); d->menu->layout()->setMargin(0); connect(d->resourceList, SIGNAL(clicked(QModelIndex)), this, SLOT(indexChanged(QModelIndex))); updateIcon(); } KoResourcePopupAction::~KoResourcePopupAction() { /* Removing the actions here make them be deleted together with their default widget. * This happens only if the actions are QWidgetAction, and we know they are since * the only ones added are in KoResourcePopupAction constructor. */ int i = 0; while(d->menu->actions().size() > 0) { - d->menu->removeAction(d->menu->actions()[i]); + d->menu->removeAction(d->menu->actions().at(i)); ++i; } delete d->menu; delete d; } QSharedPointer KoResourcePopupAction::currentBackground() const { return d->background; } void KoResourcePopupAction::setCurrentBackground(QSharedPointer background) { d->background = background; updateIcon(); } void KoResourcePopupAction::indexChanged(const QModelIndex &modelIndex) { if (! modelIndex.isValid()) { return; } d->menu->hide(); KoResource *resource = static_cast(modelIndex.internalPointer()); if(resource) { KoAbstractGradient *gradient = dynamic_cast(resource); KoPattern *pattern = dynamic_cast(resource); if (gradient) { QGradient *qg = gradient->toQGradient(); qg->setCoordinateMode(QGradient::ObjectBoundingMode); d->background = QSharedPointer(new KoGradientBackground(qg)); } else if (pattern) { KoImageCollection *collection = new KoImageCollection(); d->background = QSharedPointer(new KoPatternBackground(collection)); qSharedPointerDynamicCast(d->background)->setPattern(pattern->pattern()); } emit resourceSelected(d->background); updateIcon(); } } void KoResourcePopupAction::updateIcon() { QSize iconSize; QToolButton *toolButton = dynamic_cast(parentWidget()); if (toolButton) { iconSize = QSize(toolButton->iconSize()); } else { iconSize = QSize(16, 16); } // This must be a QImage, as drawing to a QPixmap outside the // UI thread will cause sporadic crashes. QImage pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied); pm.fill(Qt::transparent); QPainter p(&pm); QSharedPointer gradientBackground = qSharedPointerDynamicCast(d->background); QSharedPointer patternBackground = qSharedPointerDynamicCast(d->background); if (gradientBackground) { QRect innerRect(0, 0, iconSize.width(), iconSize.height()); QLinearGradient paintGradient; paintGradient.setStops(gradientBackground->gradient()->stops()); paintGradient.setStart(innerRect.topLeft()); paintGradient.setFinalStop(innerRect.topRight()); d->checkerPainter.paint(p, innerRect); p.fillRect(innerRect, QBrush(paintGradient)); } else if (patternBackground) { d->checkerPainter.paint(p, QRect(QPoint(),iconSize)); p.fillRect(0, 0, iconSize.width(), iconSize.height(), patternBackground->pattern()); } p.end(); setIcon(QIcon(QPixmap::fromImage(pm))); } diff --git a/libs/widgets/KoResourceServerProvider.cpp b/libs/widgets/KoResourceServerProvider.cpp index 1e49a873364..4f8d32a8307 100644 --- a/libs/widgets/KoResourceServerProvider.cpp +++ b/libs/widgets/KoResourceServerProvider.cpp @@ -1,246 +1,246 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include "KoSegmentGradient.h" #include "KoStopGradient.h" #include "KoColorSpaceRegistry.h" #include "KoResourcePaths.h" #include using namespace std; class GradientResourceServer : public KoResourceServer { public: GradientResourceServer(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) , m_foregroundToTransparent(0) , m_foregroundToBackground(0) { insertSpecialGradients(); } void insertSpecialGradients() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); QList stops; KoStopGradient* gradient = new KoStopGradient(""); gradient->setType(QGradient::LinearGradient); gradient->setName("Foreground to Transparent"); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(QColor(0, 0, 0, 0), cs)); gradient->setStops(stops); gradient->setValid(true); addResource(gradient, false, true); m_foregroundToTransparent = gradient; gradient = new KoStopGradient(""); gradient->setType(QGradient::LinearGradient); gradient->setName("Foreground to Background"); stops.clear(); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(Qt::white, cs)); gradient->setStops(stops); gradient->setValid(true); addResource(gradient, false, true); m_foregroundToBackground = gradient; } private: friend class KoResourceBundle; virtual KoAbstractGradient* createResource( const QString & filename ) { QString fileExtension; int index = filename.lastIndexOf('.'); if (index != -1) fileExtension = filename.mid(index).toLower(); KoAbstractGradient* grad = 0; if(fileExtension == ".svg" || fileExtension == ".kgr") grad = new KoStopGradient(filename); else if(fileExtension == ".ggr" ) grad = new KoSegmentGradient(filename); return grad; } virtual QList< KoAbstractGradient* > sortedResources() { QList< KoAbstractGradient* > resources = KoResourceServer::sortedResources(); QList< KoAbstractGradient* > sorted; if (m_foregroundToTransparent && resources.contains(m_foregroundToTransparent)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToTransparent))); } if (m_foregroundToBackground && resources.contains(m_foregroundToBackground)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToBackground))); } return sorted + resources; } KoAbstractGradient* m_foregroundToTransparent; KoAbstractGradient* m_foregroundToBackground; }; KoResourceLoaderThread::KoResourceLoaderThread(KoResourceServerBase * server) : QThread() , m_server(server) { m_fileNames = m_server->fileNames(); QStringList fileNames = m_server->blackListedFiles(); if (!fileNames.isEmpty()) { foreach (const QString &s, fileNames) { if (m_fileNames.contains(s)) { m_fileNames.removeAll(s); } } } connect(qApp, SIGNAL(aboutToQuit()), SLOT(barrier())); } KoResourceLoaderThread::~KoResourceLoaderThread() { } void KoResourceLoaderThread::run() { m_server->loadResources(m_fileNames); } void KoResourceLoaderThread::barrier() { if(isRunning()) { wait(); } } struct Q_DECL_HIDDEN KoResourceServerProvider::Private { KoResourceServer* patternServer; KoResourceServer* gradientServer; KoResourceServer* paletteServer; KoResourceLoaderThread *paletteThread; KoResourceLoaderThread *gradientThread; KoResourceLoaderThread *patternThread; }; KoResourceServerProvider::KoResourceServerProvider() : d(new Private) { KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); KoResourcePaths::addResourceType("ko_gradients", "data", "karbon/gradients/"); KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); KoResourcePaths::addResourceType("ko_palettes", "data", "calligra/palettes/"); KoResourcePaths::addResourceType("ko_palettes", "data", "karbon/palettes/"); KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); d->patternServer = new KoResourceServerSimpleConstruction("ko_patterns", "*.pat:*.jpg:*.gif:*.png:*.tif:*.xpm:*.bmp" ); - if (!QFileInfo(d->patternServer->saveLocation()).exists()) { + if (!QFileInfo::exists(d->patternServer->saveLocation())) { QDir().mkpath(d->patternServer->saveLocation()); } d->patternThread = new KoResourceLoaderThread(d->patternServer); d->patternThread->start(); if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { d->patternThread->wait(); } d->gradientServer = new GradientResourceServer("ko_gradients", "*.kgr:*.svg:*.ggr"); - if (!QFileInfo(d->gradientServer->saveLocation()).exists()) { + if (!QFileInfo::exists(d->gradientServer->saveLocation())) { QDir().mkpath(d->gradientServer->saveLocation()); } d->gradientThread = new KoResourceLoaderThread(d->gradientServer); d->gradientThread->start(); if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { d->gradientThread->wait(); } d->paletteServer = new KoResourceServerSimpleConstruction("ko_palettes", "*.gpl:*.pal:*.act:*.aco:*.css:*.colors"); - if (!QFileInfo(d->paletteServer->saveLocation()).exists()) { + if (!QFileInfo::exists(d->paletteServer->saveLocation())) { QDir().mkpath(d->paletteServer->saveLocation()); } d->paletteThread = new KoResourceLoaderThread(d->paletteServer); d->paletteThread->start(); if (qApp->applicationName().contains(QLatin1String("test"), Qt::CaseInsensitive)) { d->paletteThread->wait(); } } KoResourceServerProvider::~KoResourceServerProvider() { delete d->patternThread; delete d->gradientThread; delete d->paletteThread; delete d->patternServer; delete d->gradientServer; delete d->paletteServer; delete d; } Q_GLOBAL_STATIC(KoResourceServerProvider, s_instance); KoResourceServerProvider* KoResourceServerProvider::instance() { return s_instance; } KoResourceServer* KoResourceServerProvider::patternServer(bool block) { if (block) d->patternThread->barrier(); return d->patternServer; } KoResourceServer* KoResourceServerProvider::gradientServer(bool block) { if (block) d->gradientThread->barrier(); return d->gradientServer; } KoResourceServer* KoResourceServerProvider::paletteServer(bool block) { if (block) d->paletteThread->barrier(); return d->paletteServer; } diff --git a/libs/widgets/KoRuler_p.h b/libs/widgets/KoRuler_p.h index bf0c2ebe623..730c6f91fd1 100644 --- a/libs/widgets/KoRuler_p.h +++ b/libs/widgets/KoRuler_p.h @@ -1,208 +1,209 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * * 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. */ #ifndef KORULER_P_H #define KORULER_P_H #include class RulerTabChooser : public QWidget { +Q_OBJECT public: RulerTabChooser(QWidget *parent) : QWidget(parent), m_type(QTextOption::LeftTab), m_showTabs(false) {} virtual ~RulerTabChooser() {} inline QTextOption::TabType type() {return m_type;} void setShowTabs(bool showTabs) { if (m_showTabs == showTabs) return; m_showTabs = showTabs; update(); } void mousePressEvent(QMouseEvent *); void paintEvent(QPaintEvent *); private: QTextOption::TabType m_type; bool m_showTabs :1; }; class PaintingStrategy { public: /// constructor PaintingStrategy() {} /// destructor virtual ~PaintingStrategy() {} /** * Draw the background of the ruler. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** * Draw the indicators for text-tabs. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual void drawTabs(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** * Draw the indicators for the measurements which typically are drawn every [unit]. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. * @param rectangle */ virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle) = 0; /** * Draw the indicators for the indents of a text paragraph * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual void drawIndents(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** *returns the size suggestion for a ruler with this strategy. */ virtual QSize sizeHint() = 0; }; class HorizontalPaintingStrategy : public PaintingStrategy { public: HorizontalPaintingStrategy() : lengthInPixel(1) {} virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter); virtual void drawTabs(const KoRulerPrivate *ruler, QPainter &painter); virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle); virtual void drawIndents(const KoRulerPrivate *ruler, QPainter &painter); virtual QSize sizeHint(); private: qreal lengthInPixel; }; class VerticalPaintingStrategy : public PaintingStrategy { public: VerticalPaintingStrategy() : lengthInPixel(1) {} virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter); virtual void drawTabs(const KoRulerPrivate *, QPainter &) {} virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle); virtual void drawIndents(const KoRulerPrivate *, QPainter &) { } virtual QSize sizeHint(); private: qreal lengthInPixel; }; class HorizontalDistancesPaintingStrategy : public HorizontalPaintingStrategy { public: HorizontalDistancesPaintingStrategy() {} virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle); private: void drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end); }; class KoRulerPrivate { public: KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation orientation); ~KoRulerPrivate(); void emitTabChanged(); KoUnit unit; const Qt::Orientation orientation; const KoViewConverter * const viewConverter; int offset; qreal rulerLength; qreal activeRangeStart; qreal activeRangeEnd; qreal activeOverrideRangeStart; qreal activeOverrideRangeEnd; int mouseCoordinate; int showMousePosition; bool showSelectionBorders; qreal firstSelectionBorder; qreal secondSelectionBorder; bool showIndents; qreal firstLineIndent; qreal paragraphIndent; qreal endIndent; bool showTabs; bool relativeTabs; bool tabMoved; // set to true on first move of a selected tab QList tabs; int originalIndex; //index of selected tab before we started dragging it. int currentIndex; //index of selected tab or selected HotSpot - only valid when selected indicates tab or hotspot KoRuler::Tab deletedTab; qreal tabDistance; struct HotSpotData { qreal position; int id; }; QList hotspots; bool rightToLeft; enum Selection { None, Tab, FirstLineIndent, ParagraphIndent, EndIndent, HotSpot }; Selection selected; int selectOffset; QList popupActions; RulerTabChooser *tabChooser; // Cached painting strategies PaintingStrategy * normalPaintingStrategy; PaintingStrategy * distancesPaintingStrategy; // Current painting strategy PaintingStrategy * paintingStrategy; KoRuler *ruler; qreal numberStepForUnit() const; /// @return The rounding of value to the nearest multiple of stepValue qreal doSnapping(const qreal value) const; Selection selectionAtPosition(const QPoint & pos, int *selectOffset = 0); int hotSpotIndex(const QPoint & pos); qreal effectiveActiveRangeStart() const; qreal effectiveActiveRangeEnd() const; friend class VerticalPaintingStrategy; friend class HorizontalPaintingStrategy; }; #endif diff --git a/libs/widgets/KoSliderCombo.cpp b/libs/widgets/KoSliderCombo.cpp index f3d78735b30..5529bd65521 100644 --- a/libs/widgets/KoSliderCombo.cpp +++ b/libs/widgets/KoSliderCombo.cpp @@ -1,291 +1,307 @@ /* This file is part of the KDE project Copyright (c) 2007 C. Boemann 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. */ #include "KoSliderCombo.h" #include "KoSliderCombo_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KoSliderCombo::KoSliderCombo(QWidget *parent) : QComboBox(parent) ,d(new KoSliderComboPrivate()) { d->thePublic = this; d->minimum = 0.0; d->maximum = 100.0; d->decimals = 2; d->container = new KoSliderComboContainer(this); d->container->setAttribute(Qt::WA_WindowPropagation); QStyleOptionComboBox opt; opt.initFrom(this); // d->container->setFrameStyle(style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, this)); d->slider = new QSlider(Qt::Horizontal); d->slider->setMinimum(0); d->slider->setMaximum(256); d->slider->setPageStep(10); d->slider->setValue(0); // When set to true, causes flicker on Qt 4.6. Any reason to keep it? d->firstShowOfSlider = false; //true; QHBoxLayout * l = new QHBoxLayout(); l->setMargin(2); l->setSpacing(2); l->addWidget(d->slider); d->container->setLayout(l); d->container->resize(200, 30); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setEditable(true); setEditText(QLocale().toString(0.0, d->decimals)); connect(d->slider, SIGNAL(valueChanged(int)), SLOT(sliderValueChanged(int))); connect(d->slider, SIGNAL(sliderReleased()), SLOT(sliderReleased())); connect(lineEdit(), SIGNAL(editingFinished()), SLOT(lineEditFinished())); } KoSliderCombo::~KoSliderCombo() { delete d; } QSize KoSliderCombo::sizeHint() const { return minimumSizeHint(); } QSize KoSliderCombo::minimumSizeHint() const { QSize sh; const QFontMetrics &fm = fontMetrics(); sh.setWidth(5 * fm.width(QLatin1Char('8'))); sh.setHeight(qMax(fm.lineSpacing(), 14) + 2); // add style and strut values QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; sh = style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, this); return sh.expandedTo(QApplication::globalStrut()); } void KoSliderCombo::KoSliderComboPrivate::showPopup() { if(firstShowOfSlider) { container->show(); //show container a bit early so the slider can be layout'ed firstShowOfSlider = false; } QStyleOptionSlider opt; opt.initFrom(slider); opt.maximum=256; opt.sliderPosition = opt.sliderValue = slider->value(); int hdlPos = thePublic->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle).center().x(); QStyleOptionComboBox optThis; optThis.initFrom(thePublic); optThis.subControls = QStyle::SC_All; optThis.editable = true; int arrowPos = thePublic->style()->subControlRect(QStyle::CC_ComboBox, &optThis, QStyle::SC_ComboBoxArrow).center().x(); QSize popSize = container->size(); QRect popupRect(thePublic->mapToGlobal(QPoint(arrowPos - hdlPos - slider->x(), thePublic->size().height())), popSize); // Make sure the popup is not drawn outside the screen area QRect screenRect = QApplication::desktop()->availableGeometry(thePublic); if (popupRect.right() > screenRect.right()) popupRect.translate(screenRect.right() - popupRect.right(), 0); if (popupRect.left() < screenRect.left()) popupRect.translate(screenRect.left() - popupRect.left(), 0); if (popupRect.bottom() > screenRect.bottom()) popupRect.translate(0, -(thePublic->height() + container->height())); container->setGeometry(popupRect); container->raise(); container->show(); slider->setFocus(); } void KoSliderCombo::KoSliderComboPrivate::hidePopup() { container->hide(); } void KoSliderCombo::hideEvent(QHideEvent *) { d->hidePopup(); } void KoSliderCombo::changeEvent(QEvent *e) { switch (e->type()) { case QEvent::EnabledChange: if (!isEnabled()) d->hidePopup(); break; case QEvent::PaletteChange: d->container->setPalette(palette()); break; default: break; } QComboBox::changeEvent(e); } void KoSliderCombo::paintEvent(QPaintEvent *) { QStylePainter gc(this); gc.setPen(palette().color(QPalette::Text)); QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; gc.drawComplexControl(QStyle::CC_ComboBox, opt); gc.drawControl(QStyle::CE_ComboBoxLabel, opt); } void KoSliderCombo::mousePressEvent(QMouseEvent *e) { QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->pos(), this); if (sc == QStyle::SC_ComboBoxArrow && !d->container->isVisible()) { d->showPopup(); } else QComboBox::mousePressEvent(e); } void KoSliderCombo::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5); else if (e->key() == Qt::Key_Down) setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5); else QComboBox::keyPressEvent(e); } void KoSliderCombo::wheelEvent(QWheelEvent *e) { if (e->delta() > 0) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5); else setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5); } void KoSliderCombo::KoSliderComboPrivate::lineEditFinished() { qreal value = QLocale().toDouble(thePublic->currentText()); slider->blockSignals(true); slider->setValue(int((value - minimum) * 256 / (maximum - minimum) + 0.5)); slider->blockSignals(false); emit thePublic->valueChanged(value, true); } void KoSliderCombo::KoSliderComboPrivate::sliderValueChanged(int slidervalue) { thePublic->setEditText(QLocale().toString(minimum + (maximum - minimum)*slidervalue/256, decimals)); qreal value = QLocale().toDouble(thePublic->currentText()); emit thePublic->valueChanged(value, false); } void KoSliderCombo::KoSliderComboPrivate::sliderReleased() { qreal value = QLocale().toDouble(thePublic->currentText()); emit thePublic->valueChanged(value, true); } qreal KoSliderCombo::maximum() const { return d->maximum; } qreal KoSliderCombo::minimum() const { return d->minimum; } qreal KoSliderCombo::decimals() const { return d->decimals; } qreal KoSliderCombo::value() const { return QLocale().toDouble(currentText()); } void KoSliderCombo::setDecimals(int dec) { d->decimals = dec; if (dec == 0) lineEdit()->setValidator(new QIntValidator(this)); else lineEdit()->setValidator(new QDoubleValidator(this)); } void KoSliderCombo::setMinimum(qreal min) { d->minimum = min; } void KoSliderCombo::setMaximum(qreal max) { d->maximum = max; } void KoSliderCombo::setValue(qreal value) { if(value < d->minimum) value = d->minimum; if(value > d->maximum) value = d->maximum; setEditText(QLocale().toString(value, d->decimals)); d->slider->blockSignals(true); d->slider->setValue(int((value - d->minimum) * 256 / (d->maximum - d->minimum) + 0.5)); d->slider->blockSignals(false); emit valueChanged(value, true); } +KoSliderComboContainer::~KoSliderComboContainer() {} + +void KoSliderComboContainer::mousePressEvent(QMouseEvent *e) +{ + QStyleOptionComboBox opt; + opt.init(m_parent); + opt.subControls = QStyle::SC_All; + opt.activeSubControls = QStyle::SC_ComboBoxArrow; + QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, + m_parent->mapFromGlobal(e->globalPos()), + m_parent); + if (sc == QStyle::SC_ComboBoxArrow) + setAttribute(Qt::WA_NoMouseReplay); + QMenu::mousePressEvent(e); +} + //have to include this because of Q_PRIVATE_SLOT #include diff --git a/libs/widgets/KoSliderCombo_p.h b/libs/widgets/KoSliderCombo_p.h index 93e010cb46f..1d98cbdbe9c 100644 --- a/libs/widgets/KoSliderCombo_p.h +++ b/libs/widgets/KoSliderCombo_p.h @@ -1,89 +1,76 @@ /* This file is part of the KDE project Copyright (c) 2007 C. Boemann 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. */ #ifndef KoSliderCombo_p_h #define KoSliderCombo_p_h #include "KoSliderCombo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoSliderComboContainer : public QMenu { +Q_OBJECT public: KoSliderComboContainer(KoSliderCombo *parent) : QMenu(parent ), m_parent(parent) {} - + ~KoSliderComboContainer(); protected: virtual void mousePressEvent(QMouseEvent *e); private: KoSliderCombo *m_parent; }; -void KoSliderComboContainer::mousePressEvent(QMouseEvent *e) -{ - QStyleOptionComboBox opt; - opt.init(m_parent); - opt.subControls = QStyle::SC_All; - opt.activeSubControls = QStyle::SC_ComboBoxArrow; - QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, - m_parent->mapFromGlobal(e->globalPos()), - m_parent); - if (sc == QStyle::SC_ComboBoxArrow) - setAttribute(Qt::WA_NoMouseReplay); - QMenu::mousePressEvent(e); -} - class Q_DECL_HIDDEN KoSliderCombo::KoSliderComboPrivate { public: KoSliderCombo *thePublic; QValidator *m_validator; QTimer m_timer; KoSliderComboContainer *container; QSlider *slider; QStyle::StateFlag arrowState; qreal minimum; qreal maximum; int decimals; bool firstShowOfSlider; void showPopup(); void hidePopup(); void sliderValueChanged(int value); void sliderReleased(); void lineEditFinished(); }; #endif diff --git a/libs/widgets/KoStrokeConfigWidget.cpp b/libs/widgets/KoStrokeConfigWidget.cpp index 6393f5f68aa..8ba20acd247 100644 --- a/libs/widgets/KoStrokeConfigWidget.cpp +++ b/libs/widgets/KoStrokeConfigWidget.cpp @@ -1,549 +1,551 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2002 Tomislav Lukman * Copyright (C) 2002-2003 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2007 Thomas Zander * Copyright (C) 2005-2006, 2011 Inge Wallin * Copyright (C) 2005-2008 Jan Hambrecht * Copyright (C) 2006 C. Boemann * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006 Laurent Montel * Copyright (C) 2007,2011 Thorsten Zachmann * Copyright (C) 2011 Jean-Nicolas Artaud * * 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 "KoStrokeConfigWidget.h" // Qt #include #include #include #include #include #include #include // KF5 #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class CapNJoinMenu : public QMenu { +Q_OBJECT public: CapNJoinMenu(QWidget *parent = 0); virtual QSize sizeHint() const; KoUnitDoubleSpinBox *miterLimit; QButtonGroup *capGroup; QButtonGroup *joinGroup; }; CapNJoinMenu::CapNJoinMenu(QWidget *parent) : QMenu(parent) { QGridLayout *mainLayout = new QGridLayout(); mainLayout->setMargin(2); // The cap group capGroup = new QButtonGroup(this); capGroup->setExclusive(true); QToolButton *button = 0; button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-butt")); button->setCheckable(true); button->setToolTip(i18n("Butt cap")); capGroup->addButton(button, Qt::FlatCap); mainLayout->addWidget(button, 2, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-round")); button->setCheckable(true); button->setToolTip(i18n("Round cap")); capGroup->addButton(button, Qt::RoundCap); mainLayout->addWidget(button, 2, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-square")); button->setCheckable(true); button->setToolTip(i18n("Square cap")); capGroup->addButton(button, Qt::SquareCap); mainLayout->addWidget(button, 2, 2, Qt::AlignLeft); // The join group joinGroup = new QButtonGroup(this); joinGroup->setExclusive(true); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-miter")); button->setCheckable(true); button->setToolTip(i18n("Miter join")); joinGroup->addButton(button, Qt::MiterJoin); mainLayout->addWidget(button, 3, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-round")); button->setCheckable(true); button->setToolTip(i18n("Round join")); joinGroup->addButton(button, Qt::RoundJoin); mainLayout->addWidget(button, 3, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-bevel")); button->setCheckable(true); button->setToolTip(i18n("Bevel join")); joinGroup->addButton(button, Qt::BevelJoin); mainLayout->addWidget(button, 3, 2, Qt::AlignLeft); // Miter limit // set min/max/step and value in points, then set actual unit miterLimit = new KoUnitDoubleSpinBox(this); miterLimit->setMinMaxStep(0.0, 1000.0, 0.5); miterLimit->setDecimals(2); miterLimit->setUnit(KoUnit(KoUnit::Point)); miterLimit->setToolTip(i18n("Miter limit")); mainLayout->addWidget(miterLimit, 4, 0, 1, 3); mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); setLayout(mainLayout); } QSize CapNJoinMenu::sizeHint() const { return layout()->sizeHint(); } class Q_DECL_HIDDEN KoStrokeConfigWidget::Private { public: Private() : canvas(0), active(true) { } KoLineStyleSelector *lineStyle; KoUnitDoubleSpinBox *lineWidth; KoMarkerSelector *startMarkerSelector; KoMarkerSelector *endMarkerSelector; CapNJoinMenu *capNJoinMenu; QToolButton *colorButton; KoColorPopupAction *colorAction; QWidget *spacer; KoCanvasBase *canvas; bool active; }; KoStrokeConfigWidget::KoStrokeConfigWidget(QWidget * parent) : QWidget(parent) , d(new Private()) { setObjectName("Stroke widget"); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setMargin(0); QHBoxLayout *firstLineLayout = new QHBoxLayout(); // Start marker QList markers; d->startMarkerSelector = new KoMarkerSelector(KoMarkerData::MarkerStart, this); d->startMarkerSelector->updateMarkers(markers); d->startMarkerSelector->setMaximumWidth(50); firstLineLayout->addWidget(d->startMarkerSelector); // Line style d->lineStyle = new KoLineStyleSelector(this); d->lineStyle->setMinimumWidth(70); firstLineLayout->addWidget(d->lineStyle); // End marker d->endMarkerSelector = new KoMarkerSelector(KoMarkerData::MarkerEnd, this); d->endMarkerSelector->updateMarkers(markers); d->endMarkerSelector->setMaximumWidth(50); firstLineLayout->addWidget(d->endMarkerSelector); QHBoxLayout *secondLineLayout = new QHBoxLayout(); // Line width QLabel *l = new QLabel(this); l->setText(i18n("Thickness:")); l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); secondLineLayout->addWidget(l); // set min/max/step and value in points, then set actual unit d->lineWidth = new KoUnitDoubleSpinBox(this); d->lineWidth->setMinMaxStep(0.0, 1000.0, 0.5); d->lineWidth->setDecimals(2); d->lineWidth->setUnit(KoUnit(KoUnit::Point)); d->lineWidth->setToolTip(i18n("Set line width of actual selection")); secondLineLayout->addWidget(d->lineWidth); QToolButton *capNJoinButton = new QToolButton(this); capNJoinButton->setMinimumHeight(25); d->capNJoinMenu = new CapNJoinMenu(this); capNJoinButton->setMenu(d->capNJoinMenu); capNJoinButton->setText("..."); capNJoinButton->setPopupMode(QToolButton::InstantPopup); secondLineLayout->addWidget(capNJoinButton); d->colorButton = new QToolButton(this); secondLineLayout->addWidget(d->colorButton); d->colorAction = new KoColorPopupAction(this); d->colorAction->setIcon(koIcon("format-stroke-color")); d->colorAction->setToolTip(i18n("Change the color of the line/border")); d->colorButton->setDefaultAction(d->colorAction); mainLayout->addLayout(firstLineLayout); mainLayout->addLayout(secondLineLayout); // Spacer d->spacer = new QWidget(); d->spacer->setObjectName("SpecialSpacer"); mainLayout->addWidget(d->spacer); // set sensitive defaults d->lineStyle->setLineStyle(Qt::SolidLine); d->lineWidth->changeValue(1); d->colorAction->setCurrentColor(Qt::black); // Make the signals visible on the outside of this widget. connect(d->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyChanges())); connect(d->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges())); connect(d->colorAction, SIGNAL(colorChanged(KoColor)), this, SLOT(applyChanges())); connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyChanges())); connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyChanges())); connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges())); connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(startMarkerChanged())); connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(endMarkerChanged())); } KoStrokeConfigWidget::~KoStrokeConfigWidget() { delete d; } // ---------------------------------------------------------------- // getters and setters Qt::PenStyle KoStrokeConfigWidget::lineStyle() const { return d->lineStyle->lineStyle(); } QVector KoStrokeConfigWidget::lineDashes() const { return d->lineStyle->lineDashes(); } qreal KoStrokeConfigWidget::lineWidth() const { return d->lineWidth->value(); } QColor KoStrokeConfigWidget::color() const { return d->colorAction->currentColor(); } qreal KoStrokeConfigWidget::miterLimit() const { return d->capNJoinMenu->miterLimit->value(); } KoMarker *KoStrokeConfigWidget::startMarker() const { return d->startMarkerSelector->marker(); } KoMarker *KoStrokeConfigWidget::endMarker() const { return d->endMarkerSelector->marker(); } Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const { return static_cast(d->capNJoinMenu->capGroup->checkedId()); } Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const { return static_cast(d->capNJoinMenu->joinGroup->checkedId()); } KoShapeStroke* KoStrokeConfigWidget::createShapeStroke() const { KoShapeStroke *stroke = new KoShapeStroke(); stroke->setColor(color()); stroke->setLineWidth(lineWidth()); stroke->setCapStyle(capStyle()); stroke->setJoinStyle(joinStyle()); stroke->setMiterLimit(miterLimit()); stroke->setLineStyle(lineStyle(), lineDashes()); return stroke; } // ---------------------------------------------------------------- // Other public functions void KoStrokeConfigWidget::updateControls(KoShapeStrokeModel *stroke, KoMarker *startMarker, KoMarker *endMarker) { blockChildSignals(true); const KoShapeStroke *lineStroke = dynamic_cast(stroke); if (lineStroke) { d->lineWidth->changeValue(lineStroke->lineWidth()); QAbstractButton *button = d->capNJoinMenu->capGroup->button(lineStroke->capStyle()); if (button) { button->setChecked(true); } button = d->capNJoinMenu->joinGroup->button(lineStroke->joinStyle()); if (button) { button->setChecked(true); } d->capNJoinMenu->miterLimit->changeValue(lineStroke->miterLimit()); d->capNJoinMenu->miterLimit->setEnabled(lineStroke->joinStyle() == Qt::MiterJoin); d->lineStyle->setLineStyle(lineStroke->lineStyle(), lineStroke->lineDashes()); d->colorAction->setCurrentColor(lineStroke->color()); } else { d->lineWidth->changeValue(0.0); d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true); d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true); d->capNJoinMenu->miterLimit->changeValue(0.0); d->capNJoinMenu->miterLimit->setEnabled(true); d->lineStyle->setLineStyle(Qt::NoPen, QVector()); } d->startMarkerSelector->setMarker(startMarker); d->endMarkerSelector->setMarker(endMarker); blockChildSignals(false); } void KoStrokeConfigWidget::setUnit(const KoUnit &unit) { blockChildSignals(true); KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController(); KoSelection *selection = canvasController->canvas()->shapeManager()->selection(); KoShape * shape = selection->firstSelectedShape(); /** * KoStrokeShape knows nothing about the transformations applied * to the shape, which doesn't prevent the shape to apply them and * display the stroke differently. So just take that into account * and show the user correct values using the multiplier in KoUnit. */ KoUnit newUnit(unit); if (shape) { newUnit.adjustByPixelTransform(shape->absoluteTransformation(0)); } d->lineWidth->setUnit(newUnit); d->capNJoinMenu->miterLimit->setUnit(newUnit); blockChildSignals(false); } void KoStrokeConfigWidget::updateMarkers(const QList &markers) { d->startMarkerSelector->updateMarkers(markers); d->endMarkerSelector->updateMarkers(markers); } void KoStrokeConfigWidget::blockChildSignals(bool block) { d->colorAction->blockSignals(block); d->lineWidth->blockSignals(block); d->capNJoinMenu->capGroup->blockSignals(block); d->capNJoinMenu->joinGroup->blockSignals(block); d->capNJoinMenu->miterLimit->blockSignals(block); d->lineStyle->blockSignals(block); d->startMarkerSelector->blockSignals(block); d->endMarkerSelector->blockSignals(block); } void KoStrokeConfigWidget::setActive(bool active) { d->active = active; } //------------------------ void KoStrokeConfigWidget::applyChanges() { KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController(); KoSelection *selection = canvasController->canvas()->shapeManager()->selection(); //FIXME d->canvas->resourceManager()->setActiveStroke( d->stroke ); if (!selection || !selection->count()) { return; } KoShapeStroke *newStroke = new KoShapeStroke(); KoShapeStroke *oldStroke = dynamic_cast( selection->firstSelectedShape()->stroke() ); if (oldStroke) { newStroke->setLineBrush(oldStroke->lineBrush()); } newStroke->setColor(color()); newStroke->setLineWidth(lineWidth()); newStroke->setCapStyle(static_cast(d->capNJoinMenu->capGroup->checkedId())); newStroke->setJoinStyle(static_cast(d->capNJoinMenu->joinGroup->checkedId())); newStroke->setMiterLimit(miterLimit()); newStroke->setLineStyle(lineStyle(), lineDashes()); if (d->active) { KoShapeStrokeCommand *cmd = new KoShapeStrokeCommand(selection->selectedShapes(), newStroke); canvasController->canvas()->addCommand(cmd); } } void KoStrokeConfigWidget::applyMarkerChanges(KoMarkerData::MarkerPosition position) { KoMarker *marker = 0; if (position == KoMarkerData::MarkerStart) { marker = startMarker(); } else if (position == KoMarkerData::MarkerEnd) { marker = endMarker(); } KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController(); KoSelection *selection = canvasController->canvas()->shapeManager()->selection(); if (! selection || !selection->count()) { return; } const QList shapeList = selection->selectedShapes(); QList pathShapeList; for (QList::ConstIterator itShape = shapeList.begin(); itShape != shapeList.end(); ++itShape) { KoPathShape* pathShape = dynamic_cast(*itShape); if (pathShape) { pathShapeList << pathShape; } } if (pathShapeList.size()) { KoPathShapeMarkerCommand* cmdMarker = new KoPathShapeMarkerCommand(pathShapeList, marker, position); canvasController->canvas()->addCommand(cmdMarker); } } void KoStrokeConfigWidget::startMarkerChanged() { applyMarkerChanges(KoMarkerData::MarkerStart); } void KoStrokeConfigWidget::endMarkerChanged() { applyMarkerChanges(KoMarkerData::MarkerEnd); } // ---------------------------------------------------------------- void KoStrokeConfigWidget::selectionChanged() { // see a comment in setUnit() setUnit(d->canvas->unit()); KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController(); KoSelection *selection = canvasController->canvas()->shapeManager()->selection(); KoShape * shape = selection->firstSelectedShape(); if (shape && shape->stroke()) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { updateControls(shape->stroke(), pathShape->marker(KoMarkerData::MarkerStart), pathShape->marker(KoMarkerData::MarkerEnd)); } else { updateControls(shape->stroke(), 0 ,0); } } } void KoStrokeConfigWidget::setCanvas( KoCanvasBase *canvas ) { if (canvas) { connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged())); connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged())); connect(canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(canvasResourceChanged(int,QVariant))); setUnit(canvas->unit()); KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager(); if (resourceManager) { KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value(); if (collection) { updateMarkers(collection->markers()); } } } d->canvas = canvas; } void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value) { switch (key) { case KoCanvasResourceManager::Unit: setUnit(value.value()); break; } } +#include "KoStrokeConfigWidget.moc" diff --git a/libs/widgets/KoToolBox.cpp b/libs/widgets/KoToolBox.cpp index fd257562390..bab33c0ac48 100644 --- a/libs/widgets/KoToolBox.cpp +++ b/libs/widgets/KoToolBox.cpp @@ -1,358 +1,358 @@ /* * Copyright (c) 2005-2009 Thomas Zander * Copyright (c) 2009 Peter Simonsson * Copyright (c) 2010 Cyrille Berger * * 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. */ #include "KoToolBox_p.h" #include "KoToolBoxLayout_p.h" #include "KoToolBoxButton_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUTTON_MARGIN 10 static int buttonSize(int screen) { QRect rc = qApp->desktop()->screenGeometry(screen); if (rc.width() <= 1024) { return 12; } else if (rc.width() <= 1377) { return 14; } else if (rc.width() <= 1920 ) { return 16; } else { return 22; } } class KoToolBox::Private { public: Private() : layout(0) , buttonGroup(0) , floating(false) , contextSize(0) { } void addSection(Section *section, const QString &name); QList buttons; QMap sections; KoToolBoxLayout *layout; QButtonGroup *buttonGroup; QHash visibilityCodes; bool floating; QMap contextIconSizes; QMenu* contextSize; }; void KoToolBox::Private::addSection(Section *section, const QString &name) { section->setName(name); layout->addSection(section); sections.insert(name, section); } KoToolBox::KoToolBox() : d(new Private) { d->layout = new KoToolBoxLayout(this); // add defaults d->addSection(new Section(this), "main"); d->addSection(new Section(this), "dynamic"); d->buttonGroup = new QButtonGroup(this); setLayout(d->layout); foreach(KoToolAction *toolAction, KoToolManager::instance()->toolActionList()) { addButton(toolAction); } // Update visibility of buttons setButtonsVisible(QList()); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), this, SLOT(setActiveTool(KoCanvasController*,int))); connect(KoToolManager::instance(), SIGNAL(currentLayerChanged(const KoCanvasController*,const KoShapeLayer*)), this, SLOT(setCurrentLayer(const KoCanvasController*,const KoShapeLayer*))); connect(KoToolManager::instance(), SIGNAL(toolCodesSelected(QList)), this, SLOT(setButtonsVisible(QList))); connect(KoToolManager::instance(), SIGNAL(addedTool(KoToolAction*,KoCanvasController*)), this, SLOT(toolAdded(KoToolAction*,KoCanvasController*))); QTimer::singleShot(0, this, SLOT(adjustToFit())); } KoToolBox::~KoToolBox() { delete d; } void KoToolBox::addButton(KoToolAction *toolAction) { KoToolBoxButton *button = new KoToolBoxButton(toolAction, this); d->buttons << button; int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this)); KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); int iconSize = cfg.readEntry("iconSize", toolbuttonSize); button->setIconSize(QSize(iconSize, iconSize)); - foreach (Section *section, d->sections.values()) { + foreach (Section *section, d->sections) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } QString sectionToBeAddedTo; const QString section = toolAction->section(); if (section.contains(qApp->applicationName())) { sectionToBeAddedTo = "main"; } else if (section.contains("main")) { sectionToBeAddedTo = "main"; } else if (section.contains("dynamic")) { sectionToBeAddedTo = "dynamic"; } else { sectionToBeAddedTo = section; } Section *sectionWidget = d->sections.value(sectionToBeAddedTo); if (sectionWidget == 0) { sectionWidget = new Section(this); d->addSection(sectionWidget, sectionToBeAddedTo); } sectionWidget->addButton(button, toolAction->priority()); d->buttonGroup->addButton(button, toolAction->buttonGroupId()); d->visibilityCodes.insert(button, toolAction->visibilityCode()); } void KoToolBox::setActiveTool(KoCanvasController *canvas, int id) { Q_UNUSED(canvas); QAbstractButton *button = d->buttonGroup->button(id); if (button) { button->setChecked(true); (qobject_cast(button))->setHighlightColor(); } else { warnWidgets << "KoToolBox::setActiveTool(" << id << "): no such button found"; } } void KoToolBox::setButtonsVisible(const QList &codes) { foreach(QToolButton *button, d->visibilityCodes.keys()) { QString code = d->visibilityCodes.value(button); if (code.startsWith(QLatin1String("flake/"))) { continue; } if (code.endsWith( QLatin1String( "/always"))) { button->setVisible(true); button->setEnabled( true ); } else if (code.isEmpty()) { button->setVisible(true); button->setEnabled( codes.count() != 0 ); } else { button->setVisible( codes.contains(code) ); } } layout()->invalidate(); update(); } void KoToolBox::setCurrentLayer(const KoCanvasController *canvas, const KoShapeLayer *layer) { Q_UNUSED(canvas); const bool enabled = layer == 0 || (layer->isEditable() && layer->isVisible()); foreach (QToolButton *button, d->visibilityCodes.keys()) { if (d->visibilityCodes[button].endsWith( QLatin1String( "/always") ) ) { continue; } button->setEnabled(enabled); } } void KoToolBox::paintEvent(QPaintEvent *) { QPainter painter(this); const QList sections = d->sections.values(); QList::const_iterator iterator = sections.begin(); int halfSpacing = layout()->spacing(); if (halfSpacing > 0) { halfSpacing /= 2; } while(iterator != sections.end()) { Section *section = *iterator; QStyleOption styleoption; styleoption.palette = palette(); if (section->separators() & Section::SeparatorTop) { int y = section->y() - halfSpacing; styleoption.state = QStyle::State_None; styleoption.rect = QRect(section->x(), y-1, section->width(), 2); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter); } if (section->separators() & Section::SeparatorLeft) { int x = section->x() - halfSpacing; styleoption.state = QStyle::State_Horizontal; styleoption.rect = QRect(x-1, section->y(), 2, section->height()); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter); } ++iterator; } painter.end(); } void KoToolBox::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (!d->floating) { setMinimumSize(layout()->minimumSize()); // This enforces the minimum size on the widget } } void KoToolBox::setOrientation(Qt::Orientation orientation) { d->layout->setOrientation(orientation); QTimer::singleShot(0, this, SLOT(update())); foreach(Section* section, d->sections) { section->setOrientation(orientation); } } void KoToolBox::setFloating(bool v) { setMinimumSize(QSize(1,1)); d->floating = v; } void KoToolBox::toolAdded(KoToolAction *toolAction, KoCanvasController *canvas) { Q_UNUSED(canvas); addButton(toolAction); setButtonsVisible(QList()); } void KoToolBox::adjustToFit() { int newWidth = width() - (width() % layout()->minimumSize().width()); if (newWidth != width() && newWidth >= layout()->minimumSize().width()) { setMaximumWidth(newWidth); QTimer::singleShot(0, this, SLOT(resizeUnlock())); } } void KoToolBox::resizeUnlock() { setMaximumWidth(QWIDGETSIZE_MAX); } void KoToolBox::slotContextIconSize() { QAction* action = qobject_cast(sender()); if (action && d->contextIconSizes.contains(action)) { const int iconSize = d->contextIconSizes.value(action); KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); cfg.writeEntry("iconSize", iconSize); foreach(QToolButton *button, d->buttons) { button->setIconSize(QSize(iconSize, iconSize)); } - foreach(Section *section, d->sections.values()) { + foreach(Section *section, d->sections) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } } adjustToFit(); } void KoToolBox::contextMenuEvent(QContextMenuEvent *event) { int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this)); if (!d->contextSize) { d->contextSize = new QMenu(i18n("Icon Size"), this); d->contextIconSizes.insert(d->contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), this, SLOT(slotContextIconSize())), toolbuttonSize); QList sizes; sizes << 12 << 14 << 16 << 22 << 32 << 48 << 64; //<< 96 << 128 << 192 << 256; foreach(int i, sizes) { d->contextIconSizes.insert(d->contextSize->addAction(i18n("%1x%2", i, i), this, SLOT(slotContextIconSize())), i); } QActionGroup *sizeGroup = new QActionGroup(d->contextSize); foreach (QAction *action, d->contextSize->actions()) { action->setActionGroup(sizeGroup); action->setCheckable(true); } } KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); toolbuttonSize = cfg.readEntry("iconSize", toolbuttonSize); QMapIterator< QAction*, int > it = d->contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == toolbuttonSize) { it.key()->setChecked(true); break; } } d->contextSize->exec(event->globalPos()); } diff --git a/libs/text/InsertTextLocator_p.h b/libs/widgets/KoToolBoxLayout_p.cpp similarity index 65% copy from libs/text/InsertTextLocator_p.h copy to libs/widgets/KoToolBoxLayout_p.cpp index 9e01d6c9676..9bd4953790b 100644 --- a/libs/text/InsertTextLocator_p.h +++ b/libs/widgets/KoToolBoxLayout_p.cpp @@ -1,37 +1,27 @@ -/* This file is part of the KDE project - * Copyright (C) 2007 Thomas Zander +/* + * Copyright (c) 2005-2009 Thomas Zander + * Copyright (c) 2009 Peter Simonsson + * Copyright (c) 2010 Cyrille Berger * * 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. */ +#include "KoToolBoxLayout_p.h" -#ifndef INSERTTEXTLOCATOR_H -#define INSERTTEXTLOCATOR_H - -#include "InsertInlineObjectActionBase_p.h" - -/** - * helper class - */ -class InsertTextLocator : public InsertInlineObjectActionBase +KoToolBoxLayout::~KoToolBoxLayout() { -public: - explicit InsertTextLocator(KoCanvasBase *canvas); - -private: - virtual KoInlineObject *createInlineObject(); -}; - -#endif + qDeleteAll( m_sections ); + m_sections.clear(); +} diff --git a/libs/widgets/KoToolBoxLayout_p.h b/libs/widgets/KoToolBoxLayout_p.h index 536870f9c31..af6d8edea41 100644 --- a/libs/widgets/KoToolBoxLayout_p.h +++ b/libs/widgets/KoToolBoxLayout_p.h @@ -1,357 +1,355 @@ /* * Copyright (c) 2005-2009 Thomas Zander * Copyright (c) 2009 Peter Simonsson * Copyright (c) 2010 Cyrille Berger * * 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. */ #ifndef _KO_TOOLBOX_LAYOUT_H_ #define _KO_TOOLBOX_LAYOUT_H_ #include #include #include #include #include #include #include class SectionLayout : public QLayout { +Q_OBJECT public: explicit SectionLayout(QWidget *parent) : QLayout(parent), m_orientation(Qt::Vertical) { } ~SectionLayout() { qDeleteAll( m_items ); m_items.clear(); } void addButton(QAbstractButton *button, int priority) { addChildWidget(button); m_priorities.insert(button, priority); int index = 1; foreach(QWidgetItem *item, m_items) { if (m_priorities.value(static_cast(item->widget())) > priority) break; index++; } m_items.insert(index-1, new QWidgetItem(button)); } QSize sizeHint() const { Q_ASSERT(0); return QSize(); } void addItem(QLayoutItem*) { Q_ASSERT(0); } QLayoutItem* itemAt(int i) const { if (m_items.count() <= i) return 0; return m_items.at(i); } QLayoutItem* takeAt(int i) { return m_items.takeAt(i); } int count() const { return m_items.count(); } void setGeometry (const QRect &rect) { int x = 0; int y = 0; const QSize &size = buttonSize(); if (m_orientation == Qt::Vertical) { foreach (QWidgetItem* w, m_items) { if (w->isEmpty()) continue; w->widget()->setGeometry(QRect(x, y, size.width(), size.height())); x += size.width(); if (x + size.width() > rect.width()) { x = 0; y += size.height(); } } } else { foreach (QWidgetItem* w, m_items) { if (w->isEmpty()) continue; w->widget()->setGeometry(QRect(x, y, size.width(), size.height())); y += size.height(); if (y + size.height() > rect.height()) { x += size.width(); y = 0; } } } } void setButtonSize(const QSize size) { m_buttonSize = size; } const QSize &buttonSize() const { return m_buttonSize; } void setOrientation (Qt::Orientation orientation) { m_orientation = orientation; } private: QSize m_buttonSize; QMap m_priorities; QList m_items; Qt::Orientation m_orientation; }; class Section : public QWidget { +Q_OBJECT public: enum SeparatorFlag { SeparatorTop = 0x0001,/* SeparatorBottom = 0x0002, SeparatorRight = 0x0004,*/ SeparatorLeft = 0x0008 }; Q_DECLARE_FLAGS(Separators, SeparatorFlag) explicit Section(QWidget *parent = 0) : QWidget(parent), m_layout(new SectionLayout(this)) { setLayout(m_layout); } void addButton(QAbstractButton *button, int priority) { m_layout->addButton(button, priority); } void setName(const QString &name) { m_name = name; } QString name() const { return m_name; } void setButtonSize(const QSize& size) { m_layout->setButtonSize(size); } QSize iconSize() const { return m_layout->buttonSize(); } int visibleButtonCount() const { int count = 0; for(int i = m_layout->count()-1; i >= 0; --i) { if (! static_cast (m_layout->itemAt(i))->isEmpty()) ++count; } return count; } void setSeparator(Separators separators) { m_separators = separators; } Separators separators() const { return m_separators; } void setOrientation (Qt::Orientation orientation) { m_layout->setOrientation(orientation); } private: SectionLayout *m_layout; QString m_name; Separators m_separators; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Section::Separators) class KoToolBoxLayout : public QLayout { +Q_OBJECT public: explicit KoToolBoxLayout(QWidget *parent) : QLayout(parent), m_orientation(Qt::Vertical), m_currentHeight(0) { setSpacing(6); } - - ~KoToolBoxLayout() - { - qDeleteAll( m_sections ); - m_sections.clear(); - } + ~KoToolBoxLayout(); QSize sizeHint() const { if (m_sections.isEmpty()) return QSize(); QSize oneIcon = static_cast (m_sections[0]->widget())->iconSize(); return oneIcon; } QSize minimumSize() const { QSize s = sizeHint(); if (m_orientation == Qt::Vertical) { s.setHeight(m_currentHeight); } else { s.setWidth(m_currentHeight); } return s; } void addSection(Section *section) { addChildWidget(section); QList::iterator iterator = m_sections.begin(); int defaults = 2; // skip the first two as they are the 'main' and 'dynamic' sections. while (iterator != m_sections.end()) { if (--defaults < 0 && static_cast ((*iterator)->widget())->name() > section->name()) break; ++iterator; } m_sections.insert(iterator, new QWidgetItem(section)); } void addItem(QLayoutItem*) { Q_ASSERT(0); // don't let anything else be added. (code depends on this!) } QLayoutItem* itemAt(int i) const { if (m_sections.count() >= i) return 0; return m_sections.at(i); } QLayoutItem* takeAt(int i) { return m_sections.takeAt(i); } int count() const { return m_sections.count(); } void setGeometry (const QRect &rect) { // nothing to do? if (m_sections.isEmpty()) { m_currentHeight = 0; return; } // the names of the variables assume a vertical orientation, // but all calculations are done based on the real orientation const QSize iconSize = static_cast (m_sections.first()->widget())->iconSize(); const int maxWidth = (m_orientation == Qt::Vertical) ? rect.width() : rect.height(); // using min 1 as width to e.g. protect against div by 0 below const int iconWidth = qMax(1, (m_orientation == Qt::Vertical) ? iconSize.width() : iconSize.height()); const int iconHeight = qMax(1, (m_orientation == Qt::Vertical) ? iconSize.height() : iconSize.width()); const int maxColumns = qMax(1, (maxWidth / iconWidth)); int x = 0; int y = 0; bool firstSection = true; foreach (QWidgetItem *wi, m_sections) { Section *section = static_cast (wi->widget()); // Since sections can overlap (if a section occupies two rows, and there // is space on the second row for all of the next section, the next section // will be placed overlapping with the previous section), it's important that // later sections will be higher in the widget z-order than previous // sections, so raise it. section->raise(); const int buttonCount = section->visibleButtonCount(); if (buttonCount == 0) { // move out of view, not perfect TODO: better solution section->setGeometry(1000, 1000, 0, 0); continue; } // rows needed for the buttons (calculation gets the ceiling value of the plain div) const int neededRowCount = ((buttonCount-1) / maxColumns) + 1; const int availableButtonCount = (maxWidth - x + 1) / iconWidth; if (firstSection) { firstSection = false; } else if (buttonCount > availableButtonCount) { // start on a new row, set separator x = 0; y += iconHeight + spacing(); const Section::Separators separator = (m_orientation == Qt::Vertical) ? Section::SeparatorTop : Section::SeparatorLeft; section->setSeparator( separator ); } else { // append to last row, set separators (on first row only to the left side) const bool isFirstRow = (y == 0); const Section::Separators separators = isFirstRow ? ((m_orientation == Qt::Vertical) ? Section::SeparatorLeft : Section::SeparatorTop) : (Section::SeparatorTop | Section::SeparatorLeft); section->setSeparator( separators ); } const int usedColumns = qMin(buttonCount, maxColumns); if (m_orientation == Qt::Vertical) { section->setGeometry(x, y, usedColumns * iconWidth, neededRowCount * iconHeight); } else { section->setGeometry(y, x, neededRowCount * iconHeight, usedColumns * iconWidth); } // advance by the icons in the last row const int lastRowColumnCount = buttonCount - ((neededRowCount-1) * maxColumns); x += (lastRowColumnCount * iconWidth) + spacing(); // advance by all but the last used row y += (neededRowCount - 1) * iconHeight; } // cache total height (or width), adding the iconHeight for the current row m_currentHeight = y + iconHeight; } void setOrientation (Qt::Orientation orientation) { m_orientation = orientation; invalidate(); } private: QList m_sections; Qt::Orientation m_orientation; mutable int m_currentHeight; }; #endif diff --git a/libs/widgets/KoTriangleColorSelector.cpp b/libs/widgets/KoTriangleColorSelector.cpp index 92bfeea83bd..85908f7c98c 100644 --- a/libs/widgets/KoTriangleColorSelector.cpp +++ b/libs/widgets/KoTriangleColorSelector.cpp @@ -1,438 +1,438 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoTriangleColorSelector.h" #include #include #include #include #include #include #include #include enum CurrentHandle { NoHandle, HueHandle, ValueSaturationHandle }; struct Q_DECL_HIDDEN KoTriangleColorSelector::Private { Private(KoTriangleColorSelector *_q, const KoColorDisplayRendererInterface *_displayRenderer) : q(_q), displayRenderer(_displayRenderer), hue(0), saturation(0), value(0), updateAllowed(true), invalidTriangle(true), lastX(-1), lastY(-1) { } KoTriangleColorSelector *q; const KoColorDisplayRendererInterface *displayRenderer; QPixmap wheelPixmap; QPixmap trianglePixmap; int hue; int saturation; int value; int sizeColorSelector; qreal centerColorSelector; qreal wheelWidthProportion; qreal wheelWidth; qreal wheelNormExt; qreal wheelNormInt; qreal wheelInnerRadius; qreal triangleRadius; qreal triangleLength; qreal triangleHeight; qreal triangleBottom; qreal triangleTop; qreal normExt; qreal normInt; bool updateAllowed; CurrentHandle handle; qreal triangleHandleSize; bool invalidTriangle; int lastX, lastY; QTimer updateTimer; void init(); }; void KoTriangleColorSelector::Private::init() { q->setMinimumHeight( 100 ); q->setMinimumWidth( 100 ); q->setMouseTracking( true ); q->updateTriangleCircleParameters(); updateTimer.setInterval(1); updateTimer.setSingleShot(true); q->connect(&updateTimer, SIGNAL(timeout()), q, SLOT(update())); } KoTriangleColorSelector::KoTriangleColorSelector(QWidget* parent) : QWidget(parent), d(new Private(this, KoDumbColorDisplayRenderer::instance())) { d->init(); } KoTriangleColorSelector::KoTriangleColorSelector(const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent) : QWidget(parent), d(new Private(this, displayRenderer)) { d->init(); connect(displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(configurationChanged())); } KoTriangleColorSelector::~KoTriangleColorSelector() { delete d; } void KoTriangleColorSelector::updateTriangleCircleParameters() { d->sizeColorSelector = qMin(width(), height()); d->centerColorSelector = 0.5 * d->sizeColorSelector; d->wheelWidthProportion = 0.25; d->wheelWidth = d->centerColorSelector * d->wheelWidthProportion; d->wheelNormExt = qAbs( d->centerColorSelector ); d->wheelNormInt = qAbs( d->centerColorSelector * (1.0 - d->wheelWidthProportion)); d->wheelInnerRadius = d->centerColorSelector * (1.0 - d->wheelWidthProportion); d->triangleRadius = d->wheelInnerRadius * 0.9; d->triangleLength = 3.0 / sqrt(3.0) * d->triangleRadius; d->triangleHeight = d->triangleLength * sqrt(3.0) * 0.5; d->triangleTop = 0.5 * d->sizeColorSelector - d->triangleRadius; d->triangleBottom = d->triangleHeight + d->triangleTop; d->triangleHandleSize = 10.0; } void KoTriangleColorSelector::paintEvent( QPaintEvent * event ) { if( d->invalidTriangle ) { generateTriangle(); } Q_UNUSED(event); QPainter p(this); p.setRenderHint(QPainter::SmoothPixmapTransform); p.setRenderHint(QPainter::Antialiasing); QPointF pos(d->centerColorSelector, d->centerColorSelector); p.translate(QPointF( 0.5*width(), 0.5*height() ) ); // Draw the wheel p.drawPixmap( -pos, d->wheelPixmap ); // Draw the triangle p.save(); p.rotate( hue() + 150 ); p.drawPixmap( -pos , d->trianglePixmap ); // Draw selectors p.restore(); // Draw value,saturation selector // Compute coordinates { qreal vs_selector_ypos_ = value() / 255.0; qreal ls_ = (vs_selector_ypos_) * d->triangleLength; // length of the saturation on the triangle qreal vs_selector_xpos_ = ls_ * (saturation() / 255.0 - 0.5); // Draw it p.save(); p.setPen( QPen( Qt::white, 1.0) ); QColor currentColor = d->displayRenderer->toQColor(color()); p.setBrush(currentColor); p.rotate( hue() + 150 ); p.drawEllipse( QRectF( -d->triangleHandleSize*0.5 + vs_selector_xpos_, -d->triangleHandleSize*0.5 - (d->centerColorSelector - d->triangleTop) + vs_selector_ypos_ * d->triangleHeight, d->triangleHandleSize , d->triangleHandleSize )); } p.restore(); // Draw Hue selector p.save(); p.setPen( QPen( Qt::white, 1.0) ); p.rotate( hue() - 90 ); qreal hueSelectorWidth_ = 0.8; qreal hueSelectorOffset_ = 0.5 *( 1.0 - hueSelectorWidth_) * d->wheelWidth; qreal hueSelectorSize_ = 0.8 * d->wheelWidth; p.drawRect( QRectF( -1.5, -d->centerColorSelector + hueSelectorOffset_, 3.0, hueSelectorSize_ )); p.restore(); p.end(); } // make sure to always use get/set functions when managing HSV properties( don't call directly like d->hue) // these settings get updated A LOT when the color picker is being used. You might get unexpected results int KoTriangleColorSelector::hue() const { return d->hue; } void KoTriangleColorSelector::setHue(int h) { // setColor() will give you -1 when saturation is 0 // ignore setting hue in this instance. otherwise it will mess up the hue ring if (h == -1) return; h = qBound(0, h, 359); d->hue = h; tellColorChanged(); d->invalidTriangle = true; d->updateTimer.start(); } int KoTriangleColorSelector::value() const { return d->value; } void KoTriangleColorSelector::setValue(int v) { v = qBound(0, v, 255); d->value = v; tellColorChanged(); d->invalidTriangle = true; d->updateTimer.start(); } int KoTriangleColorSelector::saturation() const { return d->saturation; } void KoTriangleColorSelector::setSaturation(int s) { s = qBound(0, s, 255); d->saturation = s; tellColorChanged(); d->invalidTriangle = true; d->updateTimer.start(); } void KoTriangleColorSelector::setHSV(int h, int s, int v) { d->invalidTriangle = (hue() != h); setHue(h); setValue(v); setSaturation(s); } KoColor KoTriangleColorSelector::color() const { return d->displayRenderer->fromHsv(hue(), saturation(), value()); } void KoTriangleColorSelector::setColor(const KoColor & _color) { if ( color() == _color) return; //displayrenderer->getHsv is what sets the foreground color in the application if(d->updateAllowed) { int hueRef = hue(); int saturationRef = saturation(); int valueRef = value(); d->displayRenderer->getHsv(_color, &hueRef, &saturationRef, &valueRef); setHSV(hueRef, saturationRef, valueRef); d->invalidTriangle = true; d->updateTimer.start(); } } void KoTriangleColorSelector::resizeEvent( QResizeEvent * event ) { QWidget::resizeEvent( event ); updateTriangleCircleParameters(); generateWheel(); d->invalidTriangle = true; } inline qreal pow2(qreal v) { return v*v; } void KoTriangleColorSelector::tellColorChanged() { d->updateAllowed = false; - emit(colorChanged(color())); + emit colorChanged(color()); d->updateAllowed = true; } void KoTriangleColorSelector::generateTriangle() { QImage image(d->sizeColorSelector, d->sizeColorSelector, QImage::Format_ARGB32); // Length of triangle int hue_ = hue(); for(int y = 0; y < d->sizeColorSelector; ++y) { qreal ynormalize = ( d->triangleTop - y ) / ( d->triangleTop - d->triangleBottom ); qreal v = 255 * ynormalize; qreal ls_ = (ynormalize) * d->triangleLength; qreal startx_ = d->centerColorSelector - 0.5 * ls_; uint* data = reinterpret_cast(image.scanLine(y)); for(int x = 0; x < d->sizeColorSelector; ++x, ++data) { qreal s = 255 * (x - startx_) / ls_; if(v < -1.0 || v > 256.0 || s < -1.0 || s > 256.0 ) { *data = qRgba(0,0,0,0); } else { qreal va = 1.0, sa = 1.0; if( v < 0.0) { va = 1.0 + v; v = 0; } else if( v > 255.0 ) { va = 256.0 - v; v = 255; } if( s < 0.0) { sa = 1.0 + s; s = 0; } else if( s > 255.0 ) { sa = 256.0 - s; s = 255; } qreal coeff = va * sa; KoColor color = d->displayRenderer->fromHsv(hue_, s, v, int(coeff * 255.0)); QColor qcolor = d->displayRenderer->toQColor(color); *data = qcolor.rgba(); } } } d->trianglePixmap = QPixmap::fromImage(image); d->invalidTriangle = false; } void KoTriangleColorSelector::generateWheel() { QImage image(d->sizeColorSelector, d->sizeColorSelector, QImage::Format_ARGB32); for(int y = 0; y < d->sizeColorSelector; y++) { qreal yc = y - d->centerColorSelector; qreal y2 = pow2( yc ); for(int x = 0; x < d->sizeColorSelector; x++) { qreal xc = x - d->centerColorSelector; qreal norm = sqrt(pow2( xc ) + y2); if( norm <= d->wheelNormExt + 1.0 && norm >= d->wheelNormInt - 1.0 ) { qreal acoef = 1.0; if(norm > d->wheelNormExt ) acoef = (1.0 + d->wheelNormExt - norm); else if(norm < d->wheelNormInt ) acoef = (1.0 - d->wheelNormInt + norm); qreal angle = atan2(yc, xc); int h = (int)((180 * angle / M_PI) + 180) % 360; KoColor color = d->displayRenderer->fromHsv(h, 255, 255, int(acoef * 255.0)); QColor qcolor = d->displayRenderer->toQColor(color); image.setPixel(x,y, qcolor.rgba()); } else { image.setPixel(x,y, qRgba(0,0,0,0)); } } } d->wheelPixmap = QPixmap::fromImage(image); } void KoTriangleColorSelector::mouseReleaseEvent( QMouseEvent * event ) { if(event->button() == Qt::LeftButton) { selectColorAt( event->x(), event->y()); d->handle = NoHandle; } else { QWidget::mouseReleaseEvent( event ); } } void KoTriangleColorSelector::mousePressEvent( QMouseEvent * event ) { if(event->button() == Qt::LeftButton) { d->handle = NoHandle; selectColorAt( event->x(), event->y()); } else { QWidget::mousePressEvent( event ); } } void KoTriangleColorSelector::mouseMoveEvent( QMouseEvent * event ) { if(event->buttons() & Qt::LeftButton) { selectColorAt( event->x(), event->y(), false ); } else { QWidget::mouseMoveEvent( event); } } void KoTriangleColorSelector::selectColorAt(int _x, int _y, bool checkInWheel) { Q_UNUSED( checkInWheel ); if (d->lastX == _x && d->lastY == _y) { return; } d->lastX = _x; d->lastY = _y; qreal x = _x - 0.5*width(); qreal y = _y - 0.5*height(); // Check if the click is inside the wheel qreal norm = sqrt( x * x + y * y); if ( ( (norm < d->wheelNormExt) && (norm > d->wheelNormInt) && d->handle == NoHandle ) || d->handle == HueHandle ) { d->handle = HueHandle; setHue( (int)(atan2(y, x) * 180 / M_PI ) + 180); d->updateTimer.start(); } else { // Compute the s and v value, if they are in range, use them qreal rotation = -(hue() + 150) * M_PI / 180; qreal cr = cos(rotation); qreal sr = sin(rotation); qreal x1 = x * cr - y * sr; // <- now x1 gives the saturation qreal y1 = x * sr + y * cr; // <- now y1 gives the value y1 += d->wheelNormExt; qreal ynormalize = (d->triangleTop - y1 ) / ( d->triangleTop - d->triangleBottom ); if( (ynormalize >= 0.0 && ynormalize <= 1.0 ) || d->handle == ValueSaturationHandle) { d->handle = ValueSaturationHandle; qreal ls_ = (ynormalize) * d->triangleLength; // length of the saturation on the triangle qreal sat = ( x1 / ls_ + 0.5) ; if((sat >= 0.0 && sat <= 1.0) || d->handle == ValueSaturationHandle) { setHSV( hue(), sat * 255, ynormalize * 255); } } d->updateTimer.start(); } } void KoTriangleColorSelector::configurationChanged() { generateWheel(); d->invalidTriangle = true; update(); } diff --git a/libs/widgets/tests/KoProgressUpdater_test.cpp b/libs/widgets/tests/KoProgressUpdater_test.cpp index c30f109b7d9..f522bbffa28 100644 --- a/libs/widgets/tests/KoProgressUpdater_test.cpp +++ b/libs/widgets/tests/KoProgressUpdater_test.cpp @@ -1,288 +1,289 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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. */ #include "KoProgressUpdater_test.h" #include "KoProgressUpdater.h" #include "KoUpdater.h" #include #include #include // KoProgressUpdater signal timer has interval of 250 ms, see PROGRESSUPDATER_GUITIMERINTERVAL // Also is the timer of type Qt::CoarseTimer, which "tr[ies] to keep accuracy within 5% of the desired interval", // so wait a little longer #define WAIT_FOR_PROGRESSUPDATER_UI_UPDATES 275 class TestWeaverJob : public ThreadWeaver::Job { public: TestWeaverJob( QPointer updater, int steps = 10 ) : ThreadWeaver::Job() , m_updater(updater) , m_steps(steps) { } void run(ThreadWeaver::JobPointer, ThreadWeaver::Thread *) { for (int i = 1; i < m_steps + 1; ++i) { for (int j = 1; j < 10000; ++j){} m_updater->setProgress((100 / m_steps) * i); if ( m_updater->interrupted() ) { m_updater->setProgress(100); return; } } m_updater->setProgress(100); } protected: QPointer m_updater; int m_steps; }; class TestJob : public QThread { - +Q_OBJECT public: TestJob( QPointer updater, int steps = 10 ) : QThread() , m_updater( updater ) , m_steps( steps ) { } virtual void run() { for (int i = 1; i < m_steps + 1; ++i) { sleep(1); m_updater->setProgress((100 / m_steps) * i); if ( m_updater->interrupted() ) { m_updater->setProgress(100); return; } } m_updater->setProgress(100); } private: QPointer m_updater; int m_steps; }; class TestProgressBar : public KoProgressProxy { public: int min; int max; int value; QString formatString; TestProgressBar() : min(0) , max(0) , value(0) { } int maximum() const { return max; } void setValue( int v ) { value = v; } void setRange( int minimum, int maximum ) { min = minimum; max = maximum; } void setFormat( const QString & format ) { formatString = format; } }; void KoProgressUpdaterTest::testCreation() { TestProgressBar bar; KoProgressUpdater pu(&bar); QPointer updater = pu.startSubtask(); QCOMPARE(bar.min, 0); QCOMPARE(bar.max, 0); QCOMPARE(bar.value, 0); QVERIFY(bar.formatString.isNull()); pu.start(); QCOMPARE(bar.min, 0); QCOMPARE(bar.max, 99); QCOMPARE(bar.value, 0); } void KoProgressUpdaterTest::testSimpleProgress() { TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(); QPointer updater = pu.startSubtask(); updater->setProgress(50); QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. updater->setProgress(100); QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. QCOMPARE(bar.value, 100); } void KoProgressUpdaterTest::testSimpleThreadedProgress() { TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(); QPointer u = pu.startSubtask(); TestJob t(u); t.start(); while (!t.isFinished()) { QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. } QCOMPARE(bar.value, 100); } void KoProgressUpdaterTest::testSubUpdaters() { TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(); QPointer u1 = pu.startSubtask(4); QPointer u2 = pu.startSubtask(6); u1->setProgress(100); QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. QCOMPARE(bar.value, 40); u2->setProgress(100); QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. QCOMPARE(bar.value, 100); } void KoProgressUpdaterTest::testThreadedSubUpdaters() { TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(); QPointer u1 = pu.startSubtask(4); QPointer u2= pu.startSubtask(6); TestJob t1(u1, 4); TestJob t2(u2, 6); t1.start(); t2.start(); while ( t1.isRunning() || t2.isRunning() ) { QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. } QCOMPARE(bar.value, 100); } void KoProgressUpdaterTest::testRecursiveProgress() { TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(); QPointer u1 = pu.startSubtask(); KoProgressUpdater pu2(u1); pu2.start(); QPointer u2 = pu2.startSubtask(); u2->setProgress(50); u2->setProgress(100); while(bar.value < 100) { QCoreApplication::processEvents(); QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); } QCOMPARE(bar.value, 100); } void KoProgressUpdaterTest::testThreadedRecursiveProgress() { TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(); QPointer u1 = pu.startSubtask(); KoProgressUpdater pu2(u1); pu2.start(); QPointer u2 = pu2.startSubtask(); TestJob t1(u2); t1.start(); while (t1.isRunning() ) { QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); // allow the action to do its job. QCoreApplication::processEvents(); // allow the actions 'gui' stuff to run. } while(bar.value < 100) { QCoreApplication::processEvents(); QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); } QCOMPARE(bar.value, 100); } void KoProgressUpdaterTest::testFromWeaver() { jobsdone = 0; TestProgressBar bar; KoProgressUpdater pu(&bar); pu.start(10); ThreadWeaver::Queue::instance()->setMaximumNumberOfThreads(4); for (int i = 0; i < 10; ++i) { QPointer up = pu.startSubtask(); ThreadWeaver::QObjectDecorator * job = new ThreadWeaver::QObjectDecorator(new TestWeaverJob(up, 10)); connect( job, SIGNAL(done(ThreadWeaver::JobPointer)), SLOT(jobDone(ThreadWeaver::JobPointer)) ); ThreadWeaver::Queue::instance()->enqueue(ThreadWeaver::make_job_raw(job)); } while (!ThreadWeaver::Queue::instance()->isIdle()) { QTest::qSleep(WAIT_FOR_PROGRESSUPDATER_UI_UPDATES); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } ThreadWeaver::Queue::instance()->finish(); QCOMPARE(jobsdone, 10); } void KoProgressUpdaterTest::jobDone(ThreadWeaver::JobPointer job) { Q_UNUSED(job); ++jobsdone; } QTEST_GUILESS_MAIN(KoProgressUpdaterTest) +#include "KoProgressUpdater_test.moc" diff --git a/libs/widgets/tests/KoResourceTaggingTest.cpp b/libs/widgets/tests/KoResourceTaggingTest.cpp index 00212bce64f..abac8748ea6 100644 --- a/libs/widgets/tests/KoResourceTaggingTest.cpp +++ b/libs/widgets/tests/KoResourceTaggingTest.cpp @@ -1,154 +1,154 @@ /* * Copyright (c) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoResourceTaggingTest.h" #include #include #include #include #include #include "KoResource.h" #include "KoResourceServerProvider.h" void KoResourceTaggingTest::initTestCase() { // use global png files as dummy pattern dir for now const QFileInfo file(QFINDTESTDATA("../../../pics/hicolor/16-actions-borderpainter.png")); Q_ASSERT(file.exists()); KoResourcePaths::addResourceDir("ko_patterns", file.absolutePath()); } void KoResourceTaggingTest::testInitialization() { KoResourceTagStore tagStore(KoResourceServerProvider::instance()->patternServer()); QVERIFY(tagStore.tagNamesList().isEmpty()); QVERIFY(tagStore.assignedTagsList(0).isEmpty()); QVERIFY(tagStore.searchTag("bla").isEmpty()); } void KoResourceTaggingTest::testTagging() { KoResourceServer* patServer = KoResourceServerProvider::instance()->patternServer(); KoResourceTagStore tagStore(patServer); - KoResource *res = patServer->resources().first(); + KoResource *res = patServer->resources().constFirst(); QVERIFY(res); QVERIFY(patServer->resourceByFilename(res->shortFilename()) == res); tagStore.addTag(res, "test1"); QVERIFY(tagStore.tagNamesList().size() == 1); QStringList resources = tagStore.searchTag("test1"); QVERIFY(resources.size() == 1); KoResource *res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.addTag(res, "test2"); QVERIFY(tagStore.tagNamesList().size() == 2); resources = tagStore.searchTag("test1"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.addTag(res, "test2"); QVERIFY(tagStore.tagNamesList().size() == 2); resources = tagStore.searchTag("test2"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); resources = tagStore.searchTag("test1,test2"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.delTag(res, "test1"); QVERIFY(tagStore.tagNamesList().size() == 2); resources = tagStore.searchTag("test1"); QVERIFY(resources.size() == 0); resources = tagStore.searchTag("test2"); QVERIFY(resources.size() == 1); res2 = patServer->resourceByFilename(resources.first()); QVERIFY(res == res2); tagStore.delTag("test1"); QVERIFY(tagStore.tagNamesList().size() == 1); } void KoResourceTaggingTest::testReadWriteXML() { KoResourceServer* patServer = KoResourceServerProvider::instance()->patternServer(); KoResourceTagStore tagStore(patServer); QList patterns = patServer->resources(); Q_ASSERT(patterns.size() > 5); tagStore.addTag(patterns[0], "test0"); tagStore.addTag(patterns[1], "test1"); tagStore.addTag(patterns[2], "test2"); tagStore.addTag(patterns[2], "test2"); tagStore.addTag(patterns[2], "test1"); tagStore.addTag(patterns[3], "test3"); tagStore.addTag(patterns[4], "test4"); tagStore.addTag(patterns[5], "test5"); tagStore.addTag(patterns[5], "test5.1"); tagStore.addTag(patterns[5], "test5.2"); tagStore.addTag(0, "dummytest"); QVERIFY(tagStore.tagNamesList().size() == 9); tagStore.writeXMLFile(QString(FILES_OUTPUT_DIR) + "/" + "kis_pattern_tags.xml"); KoResourceTagStore tagStore2(patServer); tagStore2.readXMLFile(QString(FILES_OUTPUT_DIR) + "/" + "kis_pattern_tags.xml"); QVERIFY(tagStore2.tagNamesList().size() == 9); QStringList resources = tagStore2.searchTag("test0"); QVERIFY(resources.size() == 1); QVERIFY(patServer->resourceByFilename(resources[0]) == patterns[0]); resources = tagStore2.searchTag("test1"); QVERIFY(resources.size() == 2); resources = tagStore2.searchTag("test2"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test3"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test4"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test5"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test5.1"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("test5.2"); QVERIFY(resources.size() == 1); resources = tagStore2.searchTag("dummytest"); QVERIFY(resources.size() == 0); } QTEST_MAIN(KoResourceTaggingTest) diff --git a/libs/widgets/tests/zoomhandler_test.cpp b/libs/widgets/tests/zoomhandler_test.cpp index e4dc0c2e6d7..7bdcf48910c 100644 --- a/libs/widgets/tests/zoomhandler_test.cpp +++ b/libs/widgets/tests/zoomhandler_test.cpp @@ -1,123 +1,115 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "zoomhandler_test.h" #include #include #include #include "KoZoomHandler.h" #include "KoDpi.h" #include "KoUnit.h" -// Same as qfuzzycompare, but less precise because KoZoomHandler is a -// bit messy itself. -static inline bool fuzzyCompare(qreal p1, qreal p2) -{ - return qAbs(p1 - p2) < 0.0000001; -} - - void zoomhandler_test::testConstruction() { KoZoomHandler * zoomHandler = new KoZoomHandler(); QCOMPARE( zoomHandler->zoomFactorX(), 1. ); QCOMPARE( zoomHandler->zoomFactorY(), 1. ); QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->resolutionX() ), ( int )KoDpi::dpiX() ); QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->resolutionY() ), ( int )KoDpi::dpiY() ); QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->zoomedResolutionX() ), ( int )KoDpi::dpiX() ); QCOMPARE( ( int )INCH_TO_POINT( zoomHandler->zoomedResolutionY() ), ( int )KoDpi::dpiY() ); QCOMPARE( zoomHandler->zoomMode(), KoZoomMode::ZOOM_CONSTANT ); QCOMPARE( zoomHandler->zoomInPercent(), 100 ); delete zoomHandler; } void zoomhandler_test::testApi() { KoZoomHandler zoomHandler; qreal x, y; zoomHandler.setResolution( 128, 129 ); QCOMPARE( zoomHandler.resolutionX(), 128. ); QCOMPARE( zoomHandler.resolutionY(), 129. ); zoomHandler.setZoomedResolution( 50, 60 ); QCOMPARE( zoomHandler.zoomedResolutionX(), 50.); QCOMPARE( zoomHandler.zoomedResolutionY(), 60.); zoomHandler.setZoom( 0.2 ); // is 20% QCOMPARE( zoomHandler.zoomInPercent(), 20); QCOMPARE( zoomHandler.resolutionX(), 128. ); QCOMPARE( zoomHandler.resolutionY(), 129. ); QCOMPARE( zoomHandler.zoomedResolutionX(), 25.6 ); QCOMPARE( zoomHandler.zoomedResolutionY(), 25.8 ); zoomHandler.zoom( &x, &y ); QVERIFY( x == 25.6 && y == 25.8 ); zoomHandler.setZoom( 1. ); zoomHandler.setZoom( 0.2 ); QCOMPARE( zoomHandler.zoomInPercent(), 20 ); QCOMPARE( zoomHandler.resolutionX(), 128. ); QCOMPARE( zoomHandler.resolutionY(), 129. ); QCOMPARE( zoomHandler.zoomedResolutionX(), 25.6 ); QCOMPARE( zoomHandler.zoomedResolutionY(), 25.8 ); zoomHandler.zoom( &x, &y ); QVERIFY( x == 25.6 && y == 25.8 ); zoomHandler.setZoomMode( KoZoomMode::ZOOM_CONSTANT ); QCOMPARE( zoomHandler.zoomMode(), KoZoomMode::ZOOM_CONSTANT ); zoomHandler.setZoomMode( KoZoomMode::ZOOM_WIDTH ); QCOMPARE( zoomHandler.zoomMode(), KoZoomMode::ZOOM_WIDTH ); zoomHandler.setZoomMode( KoZoomMode::ZOOM_PAGE ); QCOMPARE( zoomHandler.zoomMode(), KoZoomMode::ZOOM_PAGE ); } void zoomhandler_test::testViewToDocument() { KoZoomHandler zoomHandler; zoomHandler.setZoom( 1.0 ); zoomHandler.setDpi( 100, 100 ); QCOMPARE( zoomHandler.viewToDocument( QPointF( 0, 0 ) ), QPointF( 0, 0 ) ); // 100 view pixels are 72 postscript points at 100% zoom, 100ppi. QCOMPARE( zoomHandler.viewToDocument( QRectF( 0, 0, 100, 100 ) ), QRectF( 0, 0, 72, 72 ) ); QCOMPARE( zoomHandler.viewToDocumentX( 0 ), 0. ); QCOMPARE( zoomHandler.viewToDocumentY( 0 ), 0. ); } void zoomhandler_test::testDocumentToView() { KoZoomHandler zoomHandler; zoomHandler.setZoom( 1.0 ); zoomHandler.setDpi( 100, 100 ); QCOMPARE( zoomHandler.documentToView( QPointF( 0,0 ) ), QPointF( 0, 0 ) ); QCOMPARE( zoomHandler.documentToView( QRectF( 0, 0, 72, 72 ) ), QRectF( 0, 0, 100, 100) ); QCOMPARE( zoomHandler.documentToViewX( 72 ), 100. ); QCOMPARE( zoomHandler.documentToViewY( 72 ), 100. ); } QTEST_APPLESS_MAIN(zoomhandler_test) diff --git a/libs/widgetutils/KoGroupButton.cpp b/libs/widgetutils/KoGroupButton.cpp index 77a51996a73..572482f407e 100644 --- a/libs/widgetutils/KoGroupButton.cpp +++ b/libs/widgetutils/KoGroupButton.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Aurélien Gâteau Copyright (C) 2012 Jean-Nicolas Artaud Copyright (C) 2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #include "KoGroupButton.h" // Qt #include #include #include #include // KF5 #include class Q_DECL_HIDDEN KoGroupButton::Private { public: Private(KoGroupButton *qq, const GroupPosition position) : groupPosition(position) { // Make the policy closer to QPushButton's default but horizontal shouldn't be Fixed, // otherwise spacing gets broken qq->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } GroupPosition groupPosition; }; KoGroupButton::KoGroupButton(GroupPosition position, QWidget* parent) : QToolButton(parent), d(new Private(this, position)) { } KoGroupButton::KoGroupButton(QWidget* parent) : QToolButton(parent), d(new Private(this, NoGroup)) { } KoGroupButton::~KoGroupButton() { delete d; } void KoGroupButton::setGroupPosition(KoGroupButton::GroupPosition groupPosition) { d->groupPosition = groupPosition; } KoGroupButton::GroupPosition KoGroupButton::groupPosition() const { return d->groupPosition; } void KoGroupButton::paintEvent(QPaintEvent* event) { if (groupPosition() == NoGroup) { QToolButton::paintEvent(event); return; } QStylePainter painter(this); QStyleOptionToolButton opt; initStyleOption(&opt); QStyleOptionToolButton panelOpt = opt; // Panel QRect& panelRect = panelOpt.rect; switch (groupPosition()) { case GroupLeft: panelRect.setWidth(panelRect.width() * 2); break; case GroupCenter: panelRect.setLeft(panelRect.left() - panelRect.width()); panelRect.setWidth(panelRect.width() * 3); break; case GroupRight: panelRect.setLeft(panelRect.left() - panelRect.width()); break; case NoGroup: Q_ASSERT(0); } if (autoRaise()) { if (!isChecked() && !isDown() && !(panelOpt.state & QStyle::State_MouseOver)) { // Use 'pushed' appearance for all buttons, but those that are not really pushed // are drawn with less contrast and are toned down. panelOpt.state |= (QStyle::State_On | QStyle::State_Sunken); QPalette panelPal(panelOpt.palette); QColor c; c = panelPal.color(QPalette::Button); c.setAlpha(50); panelPal.setColor(QPalette::Button, c); c = panelPal.color(QPalette::Window); c.setAlpha(50); panelPal.setColor(QPalette::Window, c); panelOpt.palette = panelPal; painter.setOpacity(0.5); } } painter.drawPrimitive(QStyle::PE_PanelButtonTool, panelOpt); painter.setOpacity(1.0); // Separator //! @todo make specific fixes for styles such as Plastique, Cleanlooks if there's practical no alernative const int y1 = opt.rect.top() + 1; const int y2 = opt.rect.bottom() - 1; painter.setOpacity(0.4); if (d->groupPosition != GroupRight) { const int x = opt.rect.right(); painter.setPen(QPen(opt.palette.color(QPalette::Dark), 0)); painter.drawLine(x, y1, x, y2); } painter.setOpacity(1.0); // Text painter.drawControl(QStyle::CE_ToolButtonLabel, opt); // Filtering message on tooltip text for CJK to remove accelerators. // Quoting ktoolbar.cpp: // """ // CJK languages use more verbose accelerator marker: they add a Latin // letter in parenthesis, and put accelerator on that. Hence, the default // removal of ampersand only may not be enough there, instead the whole // parenthesis construct should be removed. Provide these filtering i18n // messages so that translators can use Transcript for custom removal. // """ if (!actions().isEmpty()) { - QAction* action = actions().first(); + QAction* action = actions().constFirst(); setToolTip(i18nc("@info:tooltip of custom triple button", "%1", action->toolTip())); } } diff --git a/libs/widgetutils/KoGroupButton.h b/libs/widgetutils/KoGroupButton.h index 7d4843d90d9..353e10f8da8 100644 --- a/libs/widgetutils/KoGroupButton.h +++ b/libs/widgetutils/KoGroupButton.h @@ -1,72 +1,74 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Aurélien Gâteau Copyright (C) 2012 Jean-Nicolas Artaud Copyright (C) 2012 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KOGROUPBUTTON_H #define KOGROUPBUTTON_H #include "kowidgetutils_export.h" // Qt #include /** * A thin tool button which can be visually grouped with other buttons. * * The group can thus look like one solid bar: ( button1 | button2 | button3 ) * * For groupping layout can be used. For exclusive checkable behaviour assign QButtonGroup on the buttons. */ class KOWIDGETUTILS_EXPORT KoGroupButton : public QToolButton { Q_OBJECT - Q_ENUMS( GroupPosition ) - Q_PROPERTY( GroupPosition groupPosition READ groupPosition WRITE setGroupPosition ) public: /** * Position of the button within the button group what affects the appearance. */ enum GroupPosition { NoGroup, //!< No particular position, gives the button unchanged appearance GroupLeft, //!< The button is at the left of the group, so it would have rounded the left part GroupRight, //!< The button is at the right of the group, so it would have rounded the right part GroupCenter //!< The button is on the center of the group, so it would have separators on both sides }; +private: + Q_ENUM( GroupPosition ) + Q_PROPERTY( GroupPosition groupPosition READ groupPosition WRITE setGroupPosition ) +public: explicit KoGroupButton(GroupPosition position, QWidget* parent = 0); /** * Creates button with no NoGroup position. */ explicit KoGroupButton(QWidget* parent = 0); virtual ~KoGroupButton(); void setGroupPosition(KoGroupButton::GroupPosition groupPosition); KoGroupButton::GroupPosition groupPosition() const; protected: virtual void paintEvent(QPaintEvent* event); private: class Private; Private *const d; }; #endif /* KOGROUPBUTTON_H */ diff --git a/libs/widgetutils/KoProperties.cpp b/libs/widgetutils/KoProperties.cpp index 6fb7e25c0bb..efba9dc53ed 100644 --- a/libs/widgetutils/KoProperties.cpp +++ b/libs/widgetutils/KoProperties.cpp @@ -1,203 +1,204 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (C) 2006-2008 Thomas Zander * * 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. */ #include "KoProperties.h" #include #include class Q_DECL_HIDDEN KoProperties::Private { public: QMap properties; }; KoProperties::KoProperties() : d(new Private()) { } KoProperties::KoProperties(const KoProperties & rhs) : d(new Private()) { d->properties = rhs.d->properties; } KoProperties::~KoProperties() { delete d; } QMapIterator KoProperties::propertyIterator() const { return QMapIterator(d->properties); } bool KoProperties::isEmpty() const { return d->properties.isEmpty(); } void KoProperties::load(const QDomElement &root) { d->properties.clear(); QDomElement e = root; QDomNode n = e.firstChild(); while (!n.isNull()) { // We don't nest elements. QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "property") { const QString name = e.attribute("name"); - const QString type = e.attribute("type"); const QString value = e.text(); QDataStream in(QByteArray::fromBase64(value.toLatin1())); QVariant v; in >> v; d->properties[name] = v; } } n = n.nextSibling(); } } bool KoProperties::load(const QString & s) { QDomDocument doc; if (!doc.setContent(s)) return false; load(doc.documentElement()); return true; } void KoProperties::save(QDomElement &root) const { QDomDocument doc = root.ownerDocument(); QMap::ConstIterator it; for (it = d->properties.constBegin(); it != d->properties.constEnd(); ++it) { QDomElement e = doc.createElement("property"); e.setAttribute("name", QString(it.key().toLatin1())); QVariant v = it.value(); e.setAttribute("type", v.typeName()); QByteArray bytes; QDataStream out(&bytes, QIODevice::WriteOnly); out << v; QDomText text = doc.createCDATASection(QString::fromLatin1(bytes.toBase64())); e.appendChild(text); root.appendChild(e); } } QString KoProperties::store(const QString &s) const { QDomDocument doc = QDomDocument(s); QDomElement root = doc.createElement(s); doc.appendChild(root); save(root); return doc.toString(); } void KoProperties::setProperty(const QString & name, const QVariant & value) { // If there's an existing value for this name already, replace it. d->properties.insert(name, value); } bool KoProperties::property(const QString & name, QVariant & value) const { QMap::const_iterator it = d->properties.constFind(name); if (it == d->properties.constEnd()) { return false; } else { value = *it; return true; } } QVariant KoProperties::property(const QString & name) const { return d->properties.value(name, QVariant()); } int KoProperties::intProperty(const QString & name, int def) const { const QVariant v = property(name); if (v.isValid()) return v.toInt(); else return def; } qreal KoProperties::doubleProperty(const QString & name, qreal def) const { const QVariant v = property(name); if (v.isValid()) return v.toDouble(); else return def; } bool KoProperties::boolProperty(const QString & name, bool def) const { const QVariant v = property(name); if (v.isValid()) return v.toBool(); else return def; } QString KoProperties::stringProperty(const QString & name, const QString & def) const { const QVariant v = property(name); if (v.isValid()) return v.toString(); else return def; } bool KoProperties::contains(const QString & key) const { return d->properties.contains(key); } QVariant KoProperties::value(const QString & key) const { return d->properties.value(key); } bool KoProperties::operator==(const KoProperties &other) const { if (d->properties.count() != other.d->properties.count()) return false; - foreach(const QString & key, d->properties.keys()) { - if (other.d->properties.value(key) != d->properties.value(key)) + QMapIterator i(d->properties); + while (i.hasNext()) { + i.next(); + if (other.d->properties.value(i.key()) != i.value()) return false; } return true; } diff --git a/plan/CMakeLists.txt b/plan/CMakeLists.txt index 16e2cb15789..64d3d239bce 100644 --- a/plan/CMakeLists.txt +++ b/plan/CMakeLists.txt @@ -1,188 +1,189 @@ project(kplato) # set kplato debug area add_definitions( -DKDE_DEFAULT_DEBUG_AREA=42000 ) if(KF5Holidays_FOUND) add_definitions(-DHAVE_KHOLIDAYS) endif() if (KF5AkonadiContact_FOUND) # disable for now: there is a bug # it only works if you use kde contacts (of course) but many use other stuff, so gets dissapointed # add_definitions(-DPLAN_KDEPIMLIBS_FOUND) message(WARNING "AkonadiContacs available, but funtion is disabled due to Bug 311940") endif () -if (KReport_FOUND AND KReport_VERSION_MAJOR EQUAL 3 AND KReport_VERSION_MINOR EQUAL 0 AND KReport_VERSION_PATCH LESS 80) - message(STATUS "Acceptable KReport version: ${KReport_VERSION}") - add_definitions(-DPLAN_USE_KREPORT) - set(PLAN_BUILD_REPORTS true) -else() - message(WARNING "Unacceptable KReport version: ${KReport_VERSION}") -endif() +#### Disable kreport, reconsidder when stable #### +# if (KReport_FOUND AND KReport_VERSION_MAJOR EQUAL 3 AND KReport_VERSION_MINOR EQUAL 0 AND KReport_VERSION_PATCH LESS 80) +# message(STATUS "Acceptable KReport version: ${KReport_VERSION}") +# add_definitions(-DPLAN_USE_KREPORT) +# set(PLAN_BUILD_REPORTS true) +# else() +# message(WARNING "Unacceptable KReport version: ${KReport_VERSION}") +# endif() if (PLANCHARTDEBUG) add_definitions(-DPLAN_CHART_DEBUG) endif () set( KPLATO_INCLUDES ${CMAKE_SOURCE_DIR}/plan/libs/kernel ${CMAKE_BINARY_DIR}/plan/libs/kernel ${CMAKE_SOURCE_DIR}/plan/libs/models ${CMAKE_BINARY_DIR}/plan/libs/models ${CMAKE_SOURCE_DIR}/plan/libs/ui ${CMAKE_BINARY_DIR}/plan/libs/ui ${CMAKE_BINARY_DIR}/plan ${KOMAIN_INCLUDES} ) add_subdirectory( libs ) add_subdirectory( templates ) add_subdirectory( pics ) add_subdirectory( toolbar ) add_subdirectory( plugins ) add_subdirectory( tests ) add_subdirectory( workpackage ) include_directories(${KPLATO_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"calligraplan\") ########### KPlato private library ############### set(planprivate_LIB_SRCS kptviewlistdocker.cpp kptviewlist.cpp kptviewlistdialog.cpp kptschedulesdocker.cpp kptconfig.cpp ConfigWorkVacationPanel.cpp ConfigProjectPanel.cpp kpttaskdefaultpanel.cpp kptworkpackageconfigpanel.cpp kptcolorsconfigpanel.cpp kptcontext.cpp kptfactory.cpp kptpart.cpp kptmaindocument.cpp kptview.cpp # KPtViewAdaptor.cpp kptprintingcontrolprivate.cpp kptschedulerpluginloader.cpp kptbuiltinschedulerplugin.cpp kptconfigskeleton.cpp kptinsertfiledlg.cpp about/aboutpage.cpp KPlatoXmlLoader.cpp ) ki18n_wrap_ui(planprivate_LIB_SRCS kptviewlistaddview.ui kptviewlisteditview.ui kptviewlisteditcategory.ui ConfigWorkVacationPanel.ui ConfigProjectPanel.ui kptconfigtaskpanelbase.ui kptworkpackageconfigpanel.ui kptcolorsconfigpanel.ui kptinsertfilepanel.ui ) kconfig_add_kcfg_files(plansettings_SRCS calligraplansettings.kcfgc) add_library(planprivate SHARED ${planprivate_LIB_SRCS} ${plansettings_SRCS} ) generate_export_header(planprivate BASE_NAME kplato) target_link_libraries(planprivate PUBLIC kplatokernel kplatomodels kplatoui komain PRIVATE koplugin KF5::IconThemes #KF5::KHtml ) if(KF5AkonadiContact_FOUND) target_link_libraries(planprivate PRIVATE KF5::AkonadiContact) endif() set_target_properties(planprivate PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) install(TARGETS planprivate ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### KPlato part ############### set(planpart_PART_SRCS kptfactoryinit.cpp ) add_library(calligraplanpart MODULE ${planpart_PART_SRCS}) calligra_part_desktop_to_json(calligraplanpart planpart.desktop) target_link_libraries(calligraplanpart KF5::Parts planprivate) install(TARGETS calligraplanpart DESTINATION ${PLUGIN_INSTALL_DIR}/calligra/parts) ########### KPlato executable ############### if(NOT RELEASE_BUILD) add_definitions(-DMAINTANER_WANTED_SPLASH) endif() set(calligraplan_KDEINIT_SRCS main.cpp ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/pics/*-apps-calligraplan.png") ecm_add_app_icon(calligraplan_KDEINIT_SRCS ICONS ${ICONS_SRCS}) kf5_add_kdeinit_executable( calligraplan ${calligraplan_KDEINIT_SRCS}) if (APPLE) set_target_properties(calligraplan PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.template) set_target_properties(calligraplan PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.calligra.plan") set_target_properties(calligraplan PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Plan") install( FILES ${CMAKE_CURRENT_BINARY_DIR}/calligraplan_KDEINIT_SRCS.icns DESTINATION ${BUNDLE_INSTALL_DIR}/calligraplan.app/Contents/Resources) endif () target_link_libraries(kdeinit_calligraplan komain) install(TARGETS kdeinit_calligraplan ${INSTALL_TARGETS_DEFAULT_ARGS}) target_link_libraries(calligraplan kdeinit_calligraplan komain) install(TARGETS calligraplan ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES calligraplan.rc calligraplan_readonly.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/calligraplan) install( PROGRAMS org.kde.calligraplan.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install( FILES calligraplanrc DESTINATION ${CONFIG_INSTALL_DIR}) install(FILES calligraplansettings.kcfg DESTINATION ${KCFG_INSTALL_DIR}) install(FILES org.kde.calligraplan.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) # TODO: with the new embedded JSON data for plugins there is no schema ATM to define extended properties # plan_viewplugin.desktop install(FILES about/top-left-plan.png about/main.html about/intro.html about/tips.html about/tutorial.html about/plan.css DESTINATION ${DATA_INSTALL_DIR}/calligraplan/about ) diff --git a/plan/libs/kernel/kptappointment.cpp b/plan/libs/kernel/kptappointment.cpp index 849c35e80a5..89b2207fb67 100644 --- a/plan/libs/kernel/kptappointment.cpp +++ b/plan/libs/kernel/kptappointment.cpp @@ -1,1063 +1,1063 @@ /* This file is part of the KDE project Copyright (C) 2005 - 2011 Dag Andersen Copyright (C) 2012 Dag Andersen Copyright (C) 2016 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. */ #include "kptappointment.h" #include "kptproject.h" #include "kpttask.h" #include "kptdatetime.h" #include "kptcalendar.h" #include "kpteffortcostmap.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "kptdebug.h" #include namespace KPlato { class Resource; AppointmentInterval::AppointmentInterval() : d( new AppointmentIntervalData() ) { //debugPlan<start; } void AppointmentInterval::setStartTime( const DateTime &time ) { if ( d->start != time ) { d->start = time; } } const DateTime &AppointmentInterval::endTime() const { return d->end; } void AppointmentInterval::setEndTime( const DateTime &time ) { if ( d->end != time ) { d->end = time; } } double AppointmentInterval::load() const { return d->load; } void AppointmentInterval::setLoad( double load ) { if ( d->load != load ) { d->load = load; } } Duration AppointmentInterval::effort() const { return ( d->end - d->start ) * d->load / 100; } Duration AppointmentInterval::effort(const DateTime &start, const DateTime &end) const { if (start >= d->end || end <= d->start) { return Duration::zeroDuration; } DateTime s = (start > d->start ? start : d->start); DateTime e = (end < d->end ? end : d->end); return (e - s) * d->load / 100; } -Duration AppointmentInterval::effort(const QDate &time, bool upto) const { +Duration AppointmentInterval::effort(QDate time, bool upto) const { DateTime t( time ); //debugPlan<start<end; if (upto) { if (t <= d->start) { return Duration::zeroDuration; } DateTime e = (t < d->end ? t : d->end); Duration eff = (e - d->start) * d->load / 100; //debugPlan<toString(); return eff; } // from time till end if (t >= d->end) { return Duration::zeroDuration; } DateTime s = (t > d->start ? t : d->start); return (d->end - s) * d->load / 100; } bool AppointmentInterval::loadXML(KoXmlElement &element, XMLLoaderObject &status) { //debugPlan; bool ok; - QString s = element.attribute("start"); + QString s = element.attribute(QStringLiteral("start")); if (!s.isEmpty()) d->start = DateTime::fromString(s, status.projectTimeZone()); - s = element.attribute("end"); + s = element.attribute(QStringLiteral("end")); if (!s.isEmpty()) d->end = DateTime::fromString(s, status.projectTimeZone()); - d->load = element.attribute("load", "100").toDouble(&ok); + d->load = element.attribute(QStringLiteral("load"), QStringLiteral("100")).toDouble(&ok); if (!ok) d->load = 100; if ( ! isValid() ) { - errorPlan<<"AppointmentInterval::loadXML: Invalid interval:"<<*this<start.timeZone() == d->end.timeZone()); } return isValid(); } void AppointmentInterval::saveXML(QDomElement &element) const { Q_ASSERT( isValid() ); - QDomElement me = element.ownerDocument().createElement("interval"); + QDomElement me = element.ownerDocument().createElement(QStringLiteral("interval")); element.appendChild(me); - me.setAttribute("start", d->start.toString( Qt::ISODate )); - me.setAttribute("end", d->end.toString( Qt::ISODate )); - me.setAttribute("load", QString::number(d->load)); + me.setAttribute(QStringLiteral("start"), d->start.toString( Qt::ISODate )); + me.setAttribute(QStringLiteral("end"), d->end.toString( Qt::ISODate )); + me.setAttribute(QStringLiteral("load"), QString::number(d->load)); } bool AppointmentInterval::isValid() const { return d->start.isValid() && d->end.isValid() && d->start < d->end && d->load >= 0.0; } AppointmentInterval AppointmentInterval::firstInterval(const AppointmentInterval &interval, const DateTime &from) const { //debugPlan<start<<" < "<start; return true; } else if ( other.d->start < d->start ) { //debugPlan<<"other start"<start<<" < "<start; return false; } // Start is assumed equal //debugPlan<<"this end"<end<<" < "<end; return d->end < other.d->end; } bool AppointmentInterval::intersects( const AppointmentInterval &other ) const { return ( d->start < other.d->end && d->end > other.d->start ); } AppointmentInterval AppointmentInterval::interval( const DateTime &start, const DateTime &end ) const { // TODO: Find and fix those that call with "wrong" timezone (should be local zone atm) const DateTime s = start.toTimeZone( d->start.timeZone() ); const DateTime e = end.toTimeZone( d->end.timeZone() ); if ( s <= d->start && e >= d->end ) { return *this; } return AppointmentInterval( qMax( s, d->start ), qMin( e, d->end ), d->load ); } QString AppointmentInterval::toString() const { - return QString( "%1 - %2, %3%" ).arg( d->start.toString( Qt::ISODate ) ).arg( d->end.toString( Qt::ISODate ) ).arg( d->load ); + return QStringLiteral( "%1 - %2, %3%" ).arg( d->start.toString( Qt::ISODate ) ).arg( d->end.toString( Qt::ISODate ) ).arg( d->load ); } QDebug operator<<( QDebug dbg, const KPlato::AppointmentInterval &i ) { dbg<<"AppointmentInterval["< &other) : m_map( other ) { } QMultiMap< QDate, AppointmentInterval > AppointmentIntervalList::map() { return m_map; } const QMultiMap< QDate, AppointmentInterval >& AppointmentIntervalList::map() const { return m_map; } AppointmentIntervalList &AppointmentIntervalList::operator=( const AppointmentIntervalList &lst ) { m_map = lst.m_map; return *this; } AppointmentIntervalList &AppointmentIntervalList::operator-=( const AppointmentIntervalList &lst ) { if ( lst.m_map.isEmpty() ) { return *this; } foreach ( const AppointmentInterval &ai, lst.map() ) { subtract( ai ); } return *this; } void AppointmentIntervalList::subtract( const DateTime &st, const DateTime &et, double load ) { subtract( AppointmentInterval( st, et, load ) ); } void AppointmentIntervalList::subtract( const AppointmentInterval &interval ) { //debugPlan< l; QList v = m_map.values( date ); m_map.remove( date ); foreach ( const AppointmentInterval &vi, v ) { if ( ! vi.intersects( interval ) ) { //debugPlan<<"subtract: not intersect:"< load ) { l.insert( 0, AppointmentInterval( st, qMin( vi.endTime(), et ), vi.load() - load ) ); //if ( ! l.at(0).isValid() ) { debugPlan< load ) { //debugPlan<<"subtract: interval load"< load ) { //debugPlan<<"subtract: vi==interval"< v = m_map.values( date ); m_map.remove( date ); QList l; foreach ( const AppointmentInterval &vi, v ) { if ( ! li.isValid() ) { l.insert( 0, vi ); Q_ASSERT_X(l.at(0).isValid(), "Original", "Invalid interval"); continue; } if ( ! li.intersects( vi ) ) { //debugPlan<<"not intersects:"<::const_iterator it = m_map.lowerBound( start.date() ); for ( ; it != m_map.constEnd() && it.key() <= end.date(); ++it ) { d += it.value().effort( start, end ); } return d; } void AppointmentIntervalList::saveXML( QDomElement &element ) const { foreach ( const AppointmentInterval &i, m_map ) { i.saveXML( element ); #ifndef NDEBUG if ( !i.isValid() ) { // NOTE: This should not happen, so hunt down cause if it does warnPlan<<"Invalid interval:"<::const_iterator it = i.map().constBegin(); for ( ; it != i.map().constEnd(); ++it ) { dbg<::const_iterator it = m_intervals.map().lowerBound( start.date() ); for ( ; it != m_intervals.map().constEnd() && it.key() <= end.date(); ++it ) { AppointmentInterval ai = it.value().interval( start, end ); if ( ai.isValid() ) { lst.add( ai ); //debugPlan<resource() && m_node && m_node->node() ) debugPlan<<"Mode="<resource()->name()<<" to"<node()->name()<<""<addAppointment(this, sch)) { errorPlan<<"Failed to add appointment to resource: "<name(); return false; } if (!node->addAppointment(this, sch)) { errorPlan<<"Failed to add appointment to node: "<name(); m_resource->takeAppointment(this); return false; } //debugPlan<<"res="<resource()->name()<<" node="<node()->name(); m_intervals.loadXML( element, status ); if (isEmpty()) { errorPlan<<"Appointment is empty (added anyway): "<name()<name(); return false; } return true; } void Appointment::saveXML(QDomElement &element) const { if (isEmpty()) { errorPlan<<"Incomplete appointment data: No intervals"; } if (m_resource == 0 || m_resource->resource() == 0) { errorPlan<<"Incomplete appointment data: No resource"; return; } if (m_node == 0 || m_node->node() == 0) { errorPlan<<"Incomplete appointment data: No node"; return; // shouldn't happen } //debugPlan; - QDomElement me = element.ownerDocument().createElement("appointment"); + QDomElement me = element.ownerDocument().createElement(QStringLiteral("appointment")); element.appendChild(me); - me.setAttribute("resource-id", m_resource->resource()->id()); - me.setAttribute("task-id", m_node->node()->id()); + me.setAttribute(QStringLiteral("resource-id"), m_resource->resource()->id()); + me.setAttribute(QStringLiteral("task-id"), m_node->node()->id()); //debugPlan<resource()->name()<node()->name(); m_intervals.saveXML( me ); } // Returns the total planned effort for this appointment Duration Appointment::plannedEffort( const Resource *resource, EffortCostCalculationType type) const { if ( m_resource->resource() != resource ) { return Duration::zeroDuration; } return plannedEffort( type ); } // Returns the total planned effort for this appointment Duration Appointment::plannedEffort(EffortCostCalculationType type) const { Duration d; if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) { foreach (const AppointmentInterval &i, m_intervals.map() ) { d += i.effort(); } } return d; } // Returns the planned effort on the date -Duration Appointment::plannedEffort(const QDate &date, EffortCostCalculationType type) const { +Duration Appointment::plannedEffort(QDate date, EffortCostCalculationType type) const { Duration d; if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) { QMultiMap::const_iterator it = m_intervals.map().constFind( date ); for ( ; it != m_intervals.map().constEnd() && it.key() == date; ++it ) { d += it.value().effort(); } } return d; } // Returns the planned effort on the date -Duration Appointment::plannedEffort( const Resource *resource, const QDate &date, EffortCostCalculationType type ) const { +Duration Appointment::plannedEffort( const Resource *resource, QDate date, EffortCostCalculationType type ) const { if ( resource != m_resource->resource() ) { return Duration::zeroDuration; } return plannedEffort( date, type ); } // Returns the planned effort upto and including the date -Duration Appointment::plannedEffortTo(const QDate& date, EffortCostCalculationType type) const { +Duration Appointment::plannedEffortTo(QDate date, EffortCostCalculationType type) const { Duration d; QDate e(date.addDays(1)); if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) { foreach (const AppointmentInterval &i, m_intervals.map() ) { d += i.effort(e, true); // upto e, not including } } //debugPlan<resource() ) { return Duration::zeroDuration; } return plannedEffortTo( date, type ); } -EffortCostMap Appointment::plannedPrDay(const QDate& pstart, const QDate& pend, EffortCostCalculationType type) const { +EffortCostMap Appointment::plannedPrDay(QDate pstart, QDate pend, EffortCostCalculationType type) const { //debugPlan<id()<<","<id(); EffortCostMap ec; QDate start = pstart.isValid() ? pstart : startTime().date(); QDate end = pend.isValid() ? pend : endTime().date(); double rate = m_resource && m_resource->resource() ? m_resource->normalRatePrHour() : 0.0; Resource::Type rt = m_resource && m_resource->resource() ? m_resource->resource()->type() : Resource::Type_Work; Duration zero; //debugPlan<::const_iterator it = m_intervals.map().lowerBound( start ); for ( ; it != m_intervals.map().constEnd() && it.key() <= end; ++it ) { //debugPlan<resource()) { switch ( type ) { case ECCT_Work: if ( m_resource->resource()->type() != Resource::Type_Work ) { break; } // fall through default: ec.setCost( ec.hours() * m_resource->resource()->normalRate() ); //FIXME overtime break; } } return ec; } //Calculates the planned cost on date -double Appointment::plannedCost(const QDate &date, EffortCostCalculationType type) { +double Appointment::plannedCost(QDate date, EffortCostCalculationType type) { if (m_resource && m_resource->resource()) { switch ( type ) { case ECCT_Work: if ( m_resource->resource()->type() != Resource::Type_Work ) { break; } // fall through default: return plannedEffort(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime } } return 0.0; } //Calculates the planned cost upto and including date -double Appointment::plannedCostTo(const QDate &date, EffortCostCalculationType type) { +double Appointment::plannedCostTo(QDate date, EffortCostCalculationType type) { if (m_resource && m_resource->resource()) { switch ( type ) { case ECCT_Work: if ( m_resource->resource()->type() != Resource::Type_Work ) { break; } // fall through default: return plannedEffortTo(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime } } return 0.0; } bool Appointment::attach() { //debugPlan<<"("<attatch(this); m_node->attatch(this); return true; } warnPlan<<"Failed: "<<(m_resource ? "" : "resource=0 ") <<(m_node ? "" : "node=0"); return false; } void Appointment::detach() { //debugPlan<<"("<takeAppointment(this, m_calculationMode); // takes from node also } if (m_node) { m_node->takeAppointment(this, m_calculationMode); // to make it robust } } // Returns the effort from start to end Duration Appointment::effort(const DateTime &start, const DateTime &end, EffortCostCalculationType type) const { Duration e; if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) { e = m_intervals.effort( start, end ); } return e; } // Returns the effort from start for the duration -Duration Appointment::effort(const DateTime &start, const Duration &duration, EffortCostCalculationType type) const { +Duration Appointment::effort(const DateTime &start, KPlato::Duration duration, EffortCostCalculationType type) const { Duration d; if ( type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work ) { foreach (const AppointmentInterval &i, m_intervals.map() ) { d += i.effort(start, start+duration); } } return d; } Appointment &Appointment::operator=(const Appointment &app) { copy( app ); return *this; } Appointment &Appointment::operator+=(const Appointment &app) { merge( app ); return *this; } Appointment Appointment::operator+(const Appointment &app) { Appointment a( *this ); a.merge(app); return a; } Appointment &Appointment::operator-=(const Appointment &app) { m_intervals -= app.m_intervals; return *this; } void Appointment::copy(const Appointment &app) { m_resource = 0; //app.resource(); // NOTE: Don't copy, the new appointment m_node = 0; //app.node(); // NOTE: doesn't belong to anyone yet. //TODO: incomplete but this is all we use atm m_calculationMode = app.calculationMode(); //m_repeatInterval = app.repeatInterval(); //m_repeatCount = app.repeatCount(); m_intervals.clear(); foreach (const AppointmentInterval &i, app.intervals().map() ) { addInterval(i); } } void Appointment::merge(const Appointment &app) { //debugPlan<node()->name() : "no node")<<(app.node() ? app.node()->node()->name() : "no node"); if ( app.isEmpty() ) { return; } if ( isEmpty() ) { setIntervals( app.intervals() ); return; } QList result; QList lst1 = m_intervals.map().values(); AppointmentInterval i1; QList lst2 = app.intervals().map().values(); //debugPlan<<"add"<= lst1.size()) { i2 = lst2[index2]; if (!from.isValid() || from < i2.startTime()) from = i2.startTime(); result.append(AppointmentInterval(from, i2.endTime(), i2.load())); //debugPlan<<"Interval+ (i2):"<= lst2.size()) { i1 = lst1[index1]; if (!from.isValid() || from < i1.startTime()) from = i1.startTime(); result.append(AppointmentInterval(from, i1.endTime(), i1.load())); //debugPlan<<"Interval+ (i1):"< 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. */ #ifndef KPTAPPOINTMENT_H #define KPTAPPOINTMENT_H #include "kplatokernel_export.h" #include "kptglobal.h" #include "kptduration.h" #include "kptdatetime.h" #include #include #include #include #include class QDomElement; namespace KPlato { class Effort; class Appointment; class Node; class Resource; class EffortCost; class EffortCostMap; class Schedule; class XMLLoaderObject; class DateTimeInterval; class TimeInterval; class AppointmentIntervalData : public QSharedData { public: AppointmentIntervalData() : load( 0 ) {} AppointmentIntervalData( const AppointmentIntervalData &other ) : QSharedData( other ), start( other.start ), end( other.end ), load( other.load ) {} ~AppointmentIntervalData() {} DateTime start; DateTime end; double load; //percent }; class KPLATOKERNEL_EXPORT AppointmentInterval { public: AppointmentInterval(); AppointmentInterval(const AppointmentInterval &AppointmentInterval); AppointmentInterval(const DateTime &start, const DateTime &end, double load=100); - AppointmentInterval( const QDate &date, const TimeInterval &timeInterval, double load=100 ); + AppointmentInterval( QDate date, const TimeInterval &timeInterval, double load=100 ); ~AppointmentInterval(); Duration effort() const; Duration effort(const DateTime &start, const DateTime &end) const; - Duration effort(const QDate &time, bool upto) const; + Duration effort(QDate time, bool upto) const; bool loadXML(KoXmlElement &element, XMLLoaderObject &status); void saveXML(QDomElement &element) const; const DateTime &startTime() const; void setStartTime( const DateTime &time ); const DateTime &endTime() const; void setEndTime( const DateTime &time ); double load() const; void setLoad( double load ); bool isValid() const; AppointmentInterval firstInterval(const AppointmentInterval &interval, const DateTime &from) const; bool operator==( const AppointmentInterval &interval ) const; bool operator<( const AppointmentInterval &interval ) const; bool intersects( const AppointmentInterval &other ) const; AppointmentInterval interval( const DateTime &start, const DateTime &end ) const; QString toString() const; private: QSharedDataPointer d; }; KPLATOKERNEL_EXPORT QDebug operator<<( QDebug dbg, const KPlato::AppointmentInterval& i ); /** * This list is sorted after 1) startdatetime, 2) enddatetime. * The intervals do not overlap, an interval does not start before the * previous interval ends. */ class KPLATOKERNEL_EXPORT AppointmentIntervalList { public: AppointmentIntervalList(); AppointmentIntervalList( const QMultiMap &other ); /// Add @p interval to the list. Handle overlapping with existsing intervals. void add( const AppointmentInterval &interval ); /// Add an interval to the list. Handle overlapping with existsing intervals. void add( const DateTime &st, const DateTime &et, double load ); /// Load intervals from document bool loadXML(KoXmlElement &element, XMLLoaderObject &status); /// Save intervals to document void saveXML(QDomElement &element) const; AppointmentIntervalList &operator+=( const AppointmentIntervalList &lst ); AppointmentIntervalList &operator-=( const AppointmentIntervalList &lst ); AppointmentIntervalList &operator=( const AppointmentIntervalList &lst ); /// Returns the intervals in the range @p start, @p end AppointmentIntervalList extractIntervals( const DateTime &start, const DateTime &end ) const; /// Return the total effort Duration effort() const; /// Return the effort limited to the interval @p start, @p end Duration effort(const DateTime &start, const DateTime &end) const; QMultiMap map(); const QMultiMap &map() const; bool isEmpty() const { return m_map.isEmpty(); } void clear() { m_map.clear(); } protected: void subtract( const AppointmentInterval &interval ); void subtract( const DateTime &st, const DateTime &et, double load ); private: QMultiMap m_map; }; KPLATOKERNEL_EXPORT QDebug operator<<( QDebug dbg, const KPlato::AppointmentIntervalList& i ); /** * A Resource can be scheduled to be used at any time, * this is represented internally with Appointments * There is one Appointment per resource-task pair. * An appointment can be divided into several intervals, represented with * a list of AppointmentInterval. * This list is sorted after 1) startdatetime, 2) enddatetime. * The intervals do not overlap, an interval does not start before the * previous interval ends. * An interval is a countinous time interval with the same load. It can span dates. */ class KPLATOKERNEL_EXPORT Appointment { public: explicit Appointment(); Appointment(Schedule *resource, Schedule *node, const DateTime &start, const DateTime &end, double load); Appointment(Schedule *resource, Schedule *node, const DateTime &start, Duration duration, double load); Appointment( const Appointment &app ); ~Appointment(); bool isEmpty() const { return m_intervals.isEmpty(); } void clear(); // get/set member values. Schedule *node() const { return m_node; } void setNode(Schedule *n) { m_node = n; } Schedule *resource() const { return m_resource; } void setResource(Schedule *r) { m_resource = r; } DateTime startTime() const; DateTime endTime() const; double maxLoad() const; const Duration &repeatInterval() const {return m_repeatInterval;} void setRepeatInterval(Duration ri) {m_repeatInterval=ri;} int repeatCount() const { return m_repeatCount; } void setRepeatCount(int rc) { m_repeatCount=rc; } bool isBusy(const DateTime &start, const DateTime &end); /// attach appointment to resource and node bool attach(); /// detach appointment from resource and node void detach(); void addInterval(const AppointmentInterval &a); void addInterval(const DateTime &start, const DateTime &end, double load=100); - void addInterval(const DateTime &start, const Duration &duration, double load=100); + void addInterval(const DateTime &start, KPlato::Duration duration, double load=100); void setIntervals(const AppointmentIntervalList &lst); const AppointmentIntervalList &intervals() const { return m_intervals; } int count() const { return m_intervals.map().count(); } AppointmentInterval intervalAt( int index ) const { return m_intervals.map().values().value( index ); } /// Return intervals between @p start and @p end AppointmentIntervalList intervals( const DateTime &start, const DateTime &end ) const; bool loadXML(KoXmlElement &element, XMLLoaderObject &status, Schedule &sch); void saveXML(QDomElement &element) const; /** * Returns the planned effort and cost for the interval start to end (inclusive). * Only dates with any planned effort is returned. * If start or end is not valid, startTime.date() respectivly endTime().date() is used. */ - EffortCostMap plannedPrDay(const QDate& start, const QDate& end, EffortCostCalculationType type = ECCT_All) const; + EffortCostMap plannedPrDay(QDate start, QDate end, EffortCostCalculationType type = ECCT_All) const; /// Returns the planned effort from start to end Duration effort(const DateTime &start, const DateTime &end, EffortCostCalculationType type = ECCT_All) const; /// Returns the planned effort from start for the duration - Duration effort(const DateTime &start, const Duration &duration, EffortCostCalculationType type = ECCT_All) const; + Duration effort(const DateTime &start, KPlato::Duration duration, EffortCostCalculationType type = ECCT_All) const; /// Returns the total planned effort for @p resource on this appointment Duration plannedEffort( const Resource *resource, EffortCostCalculationType type = ECCT_All ) const; /// Returns the total planned effort for this appointment Duration plannedEffort(EffortCostCalculationType type = ECCT_All) const; /// Returns the planned effort on the date - Duration plannedEffort(const QDate &date, EffortCostCalculationType type = ECCT_All) const; + Duration plannedEffort(QDate date, EffortCostCalculationType type = ECCT_All) const; /// Returns the planned effort for @p resource on the @p date date - Duration plannedEffort( const Resource *resource, const QDate &date, EffortCostCalculationType type = ECCT_All ) const; + Duration plannedEffort( const Resource *resource, QDate date, EffortCostCalculationType type = ECCT_All ) const; /// Returns the planned effort upto and including date - Duration plannedEffortTo(const QDate &date, EffortCostCalculationType type = ECCT_All) const; + Duration plannedEffortTo(QDate date, EffortCostCalculationType type = ECCT_All) const; /// Returns the planned effort upto and including date - Duration plannedEffortTo( const Resource *resource, const QDate &date, EffortCostCalculationType type = ECCT_All ) const; + Duration plannedEffortTo( const Resource *resource, QDate date, EffortCostCalculationType type = ECCT_All ) const; /// Calculates the total planned cost for this appointment EffortCost plannedCost(EffortCostCalculationType type = ECCT_All) const; /// Calculates the planned cost on date - double plannedCost(const QDate &date, EffortCostCalculationType type = ECCT_All); + double plannedCost(QDate date, EffortCostCalculationType type = ECCT_All); /// Calculates the planned cost upto and including date - double plannedCostTo(const QDate &date, EffortCostCalculationType type = ECCT_All); + double plannedCostTo(QDate date, EffortCostCalculationType type = ECCT_All); Appointment &operator=(const Appointment &app); Appointment &operator+=(const Appointment &app); Appointment operator+(const Appointment &app); Appointment &operator-=(const Appointment &app); void setCalculationMode( int mode ) { m_calculationMode = mode; } int calculationMode() const { return m_calculationMode; } void merge(const Appointment &app); Appointment extractIntervals( const DateTimeInterval &interval ) const; void setAuxcilliaryInfo( const QString &info ) { m_auxcilliaryInfo = info; } QString auxcilliaryInfo() const { return m_auxcilliaryInfo; } protected: void copy(const Appointment &app); private: Schedule *m_node; Schedule *m_resource; int m_calculationMode; // Type of appointment Duration m_repeatInterval; int m_repeatCount; QList m_extraRepeats; QList m_skipRepeats; AppointmentIntervalList m_intervals; QString m_auxcilliaryInfo; }; } //KPlato namespace #endif diff --git a/plan/libs/kernel/kptcalendar.cpp b/plan/libs/kernel/kptcalendar.cpp index e9a1f011f80..0a0055b7492 100644 --- a/plan/libs/kernel/kptcalendar.cpp +++ b/plan/libs/kernel/kptcalendar.cpp @@ -1,1708 +1,1708 @@ /* This file is part of the KDE project Copyright (C) 2003 - 2007, 2012 Dag Andersen Copyright (C) 2016 Dag Andersen Copyright (C) 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. */ #include "kptcalendar.h" #include "kptappointment.h" #include "kptmap.h" #include "kptduration.h" #include "kptdatetime.h" #include "kptproject.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "kptcommand.h" #include "kptdebug.h" #include #include #ifdef HAVE_KHOLIDAYS #include #include #endif #include #include namespace KPlato { #ifdef HAVE_KHOLIDAYS // start qt-copy (with some changes) // Copyright (C) 2013 John Layt struct TzTimeZone { QString country; QByteArray comment; }; // Define as a type as Q_GLOBAL_STATIC doesn't like it typedef QHash TzTimeZoneHash; // Parse zone.tab table, assume lists all installed zones, if not will need to read directories static TzTimeZoneHash loadTzTimeZones() { QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab"); if (!QFile::exists(path)) path = QStringLiteral("/usr/lib/zoneinfo/zone.tab"); QFile tzif(path); if (!tzif.open(QIODevice::ReadOnly)) return TzTimeZoneHash(); TzTimeZoneHash zonesHash; // TODO QTextStream inefficient, replace later QTextStream ts(&tzif); while (!ts.atEnd()) { const QString line = ts.readLine(); // Comment lines are prefixed with a # if (!line.isEmpty() && line.at(0) != '#') { // Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments QStringList parts = line.split(QLatin1Char('\t')); TzTimeZone zone; zone.country = parts.at(0); if (parts.size() > 3) zone.comment = parts.at(3).toUtf8(); zonesHash.insert(parts.at(2).toUtf8(), zone); } } return zonesHash; } // Hash of available system tz files as loaded by loadTzTimeZones() Q_GLOBAL_STATIC_WITH_ARGS(const TzTimeZoneHash, tzZones, (loadTzTimeZones())); // end qt-copy #endif QString CalendarDay::stateToString( int st, bool trans ) { return ( st == None ) ? - (trans ? i18n( "Undefined" ) : QLatin1String( "Undefined" )) : + (trans ? i18n( "Undefined" ) : QStringLiteral( "Undefined" )) : ( st == NonWorking ) ? - (trans ? i18n( "Non-working" ) : QLatin1String( "Non-working" )) : + (trans ? i18n( "Non-working" ) : QStringLiteral( "Non-working" )) : ( st == Working ) ? - (trans ? i18n( "Working" ) : QLatin1String( "Working" )) : + (trans ? i18n( "Working" ) : QStringLiteral( "Working" )) : QString(); } QStringList CalendarDay::stateList( bool trans ) { QStringList lst; return trans ? lst << i18n( "Undefined" ) << i18n( "Non-working" ) << i18n( "Working" ) - : lst << QLatin1String("Undefined") << QLatin1String("Non-working") << QLatin1String("Working"); + : lst << QStringLiteral("Undefined") << QStringLiteral("Non-working") << QStringLiteral("Working"); } ///// CalendarDay //// CalendarDay::CalendarDay() : m_date(), m_state(Undefined), m_calendar( 0 ) { //debugPlan<<"("<second)); - me.setAttribute("start", i->first.toString()); + me.setAttribute(QStringLiteral("length"), QString::number(i->second)); + me.setAttribute(QStringLiteral("start"), i->first.toString()); } } void CalendarDay::addInterval(TimeInterval *interval) { if (!interval) { return; } // TODO: check for overlapping intervals and handle them for what makes sense QList ::Iterator it; const QList ::Iterator end = m_timeIntervals.end(); QList ::Iterator position = end; for (it = m_timeIntervals.begin(); it != end; ++it) { // first found that is later? if ((*it)->startTime() > interval->startTime()) { // insert before position = it; break; } } m_timeIntervals.insert(position, interval); } bool CalendarDay::operator==(const CalendarDay *day) const { return operator==(*day); } bool CalendarDay::operator==(const CalendarDay &day) const { //debugPlan; if (m_date.isValid() && day.date().isValid()) { if (m_date != day.date()) { //debugPlan<first.toString()<<"-"<second.toString(); return false; } } return true; } bool CalendarDay::operator!=(const CalendarDay *day) const { return operator!=(*day); } bool CalendarDay::operator!=(const CalendarDay &day) const { return !operator==(day); } -Duration CalendarDay::effort(const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) { +Duration CalendarDay::effort(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) { // debugPlan<endsMidnight() && start >= i->endTime() ) { //debugPlan<<"Skip:"<="<first.addMSecs(i->second); continue; } QTime t1 = start.addMSecs( length ); if ( t1 != QTime( 0, 0, 0 ) && t1 < i->first ) { //debugPlan<<"Skip:"<first; continue; } t1 = qMax( start, i->first ); if ( i->endsMidnight() ) { l = t1.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1; } else { l = t1.msecsTo( i->endTime() ); } l = qMin( l, length - start.msecsTo( t1 ) ); if ( l <= 0 ) { continue; } //debugPlan<<"Interval:"<"<available( dti ); //FIXME needs an effort method //debugPlan<<"Checked sch:"<first<<" -"<second; d += Duration( (qint64)i->second ); } return d; } -TimeInterval CalendarDay::interval(const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) const { +TimeInterval CalendarDay::interval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan; return interval( m_date, start, length, timeZone, sch ); } -TimeInterval CalendarDay::interval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) const +TimeInterval CalendarDay::interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan<<"Inp:"< 0 ); Q_ASSERT( QTime(0,0,0).msecsTo( start ) + length <= 1000*60*60*24 ); QTime t1; int l = 0; if ( ! hasInterval() ) { return TimeInterval(); } foreach (TimeInterval *i, m_timeIntervals) { //debugPlan<<"Interval:"<first<second<first.addMSecs(i->second); if ( ! i->endsMidnight() && start >= i->endTime() ) { //debugPlan<<"Skip:"<="<first.addMSecs(i->second); continue; } QTime t1 = start.addMSecs( length ); if ( t1 != QTime( 0, 0, 0 ) && t1 < i->first ) { //debugPlan<<"Skip:"<first; continue; } t1 = qMax( start, i->first ); if ( i->endsMidnight() ) { l = t1.msecsTo( QTime( 23, 59, 59, 999 ) ) + 1; } else { l = t1.msecsTo( i->endTime() ); } l = qMin( l, length - start.msecsTo( t1 ) ); if ( l <= 0 ) { continue; } TimeInterval ti( t1, l ); //debugPlan<<"Day give:"<"<"<second ); } return dur; } void CalendarDay::removeInterval( TimeInterval *ti ) { m_timeIntervals.removeOne(ti); } int CalendarDay::numIntervals() const { return m_state == Working ? m_timeIntervals.count() : 0; } bool CalendarDay::hasInterval(const TimeInterval* interval) const { return m_timeIntervals.contains(const_cast(interval)); } DateTime CalendarDay::start() const { if ( m_state != Working || m_timeIntervals.isEmpty() ) { return DateTime(); } QDate date = m_date; if ( ! m_date.isValid() ) { date = QDate::currentDate(); } if ( m_calendar && m_calendar->timeZone().isValid() ) { return DateTime( date, m_timeIntervals.first()->startTime(), m_calendar->timeZone() ); } return DateTime( date, m_timeIntervals.first()->startTime() ); } DateTime CalendarDay::end() const { if ( m_state != Working || m_timeIntervals.isEmpty() ) { return DateTime(); } QDate date; if ( m_date.isValid() ) { date = m_timeIntervals.last()->endsMidnight() ? m_date.addDays( 1 ) : m_date; } else { date = QDate::currentDate(); } if ( m_calendar && m_calendar->timeZone().isValid() ) { return DateTime( date, m_timeIntervals.last()->endTime(), m_calendar->timeZone() ); } return DateTime( date, m_timeIntervals.last()->endTime() ); } ///// CalendarWeekdays //// CalendarWeekdays::CalendarWeekdays() : m_weekdays() { //debugPlan<<"--->"; for (int i=1; i <= 7; ++i) { m_weekdays.insert( i, new CalendarDay() ); } //debugPlan<<"<---"; } CalendarWeekdays::CalendarWeekdays( const CalendarWeekdays *weekdays ) : m_weekdays() { //debugPlan<<"--->"; copy(*weekdays); //debugPlan<<"<---"; } CalendarWeekdays::~CalendarWeekdays() { qDeleteAll( m_weekdays ); //debugPlan; } const CalendarWeekdays &CalendarWeekdays::copy(const CalendarWeekdays &weekdays) { //debugPlan; qDeleteAll( m_weekdays ); m_weekdays.clear(); QMapIterator i( weekdays.weekdayMap() ); while ( i.hasNext() ) { i.next(); m_weekdays.insert( i.key(), new CalendarDay( i.value() ) ); } return *this; } bool CalendarWeekdays::load( KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan; bool ok; - int dayNo = QString(element.attribute("day","-1")).toInt(&ok); + int dayNo = QString(element.attribute(QStringLiteral("day"),QStringLiteral("-1"))).toInt(&ok); if (dayNo < 0 || dayNo > 6) { errorPlan<<"Illegal weekday: "<load( element, status ) ) day->setState(CalendarDay::None); return true; } void CalendarWeekdays::save(QDomElement &element) const { //debugPlan; QMapIterator i( m_weekdays ); while ( i.hasNext() ) { i.next(); - QDomElement me = element.ownerDocument().createElement("weekday"); + QDomElement me = element.ownerDocument().createElement(QStringLiteral("weekday")); element.appendChild(me); - me.setAttribute( "day", QString::number(i.key() - 1) ); // 0 (monday) .. 6 (sunday) + me.setAttribute( QStringLiteral("day"), QString::number(i.key() - 1) ); // 0 (monday) .. 6 (sunday) i.value()->save(me); } } const QMap &CalendarWeekdays::weekdayMap() const { return m_weekdays; } IntMap CalendarWeekdays::stateMap() const { IntMap days; QMapIterator i( m_weekdays ); while ( i.hasNext() ) { i.next(); if ( i.value()->state() != CalendarDay::None ) days.insert( i.key(), i.value()->state() ); } return days; } -int CalendarWeekdays::state(const QDate &date) const { +int CalendarWeekdays::state(QDate date) const { return state( date.dayOfWeek() ); } int CalendarWeekdays::state( int weekday ) const { CalendarDay *day = m_weekdays.value( weekday ); return day ? day->state() : CalendarDay::None; } void CalendarWeekdays::setState(int weekday, int state) { CalendarDay *day = m_weekdays.value( weekday ); if ( day == 0 ) return; day->setState(state); } QList CalendarWeekdays::intervals(int weekday) const { CalendarDay *day = m_weekdays.value( weekday ); Q_ASSERT(day); return day->timeIntervals(); } void CalendarWeekdays::setIntervals(int weekday, const QList &intervals) { CalendarDay *day = m_weekdays.value( weekday ); if (day) { day->setIntervals( intervals ); } } void CalendarWeekdays::clearIntervals(int weekday) { CalendarDay *day = m_weekdays.value( weekday ); if (day) { day->clearIntervals(); } } bool CalendarWeekdays::operator==(const CalendarWeekdays *wd) const { if (m_weekdays.count() != wd->weekdays().count()) { return false; } QMapIterator i( wd->weekdayMap() ); while ( i.hasNext() ) { i.next(); CalendarDay *day1 = i.value(); CalendarDay *day2 = m_weekdays.value( i.key() ); if (day1 != day2) return false; } return true; } bool CalendarWeekdays::operator!=(const CalendarWeekdays *wd) const { return operator==( wd ) == false; } -Duration CalendarWeekdays::effort(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) { +Duration CalendarWeekdays::effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) { // debugPlan<<"Day of week="<state() == CalendarDay::Working) { return day->effort(date, start, length, timeZone, sch); } return Duration::zeroDuration; } -TimeInterval CalendarWeekdays::interval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) const +TimeInterval CalendarWeekdays::interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan; CalendarDay *day = weekday( date.dayOfWeek() ); if (day && day->state() == CalendarDay::Working) { return day->interval(date, start, length, timeZone, sch); } return TimeInterval(); } -bool CalendarWeekdays::hasInterval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) const +bool CalendarWeekdays::hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const { //debugPlan<hasInterval(date, start, length, timeZone, sch); } bool CalendarWeekdays::hasInterval() const { //debugPlan; foreach ( CalendarDay *d, m_weekdays ) { if (d->hasInterval()) return true; } return false; } CalendarDay *CalendarWeekdays::weekday( int day ) const { Q_ASSERT( day >= 1 && day <= 7 ); - Q_ASSERT( m_weekdays.keys().contains( day ) ); + Q_ASSERT( m_weekdays.contains( day ) ); return m_weekdays.value( day ); } //static int CalendarWeekdays::dayOfWeek(const QString& name) { QStringList lst; - lst << "Monday" << "Tuesday" << "Wednesday" << "Thursday" << "Friday" << "Saturday" << "Sunday"; + lst << QStringLiteral("Monday") << QStringLiteral("Tuesday") << QStringLiteral("Wednesday") << QStringLiteral("Thursday") << QStringLiteral("Friday") << QStringLiteral("Saturday") << QStringLiteral("Sunday"); int idx = -1; if ( lst.contains( name ) ) { idx = lst.indexOf( name ) + 1; } return idx; } Duration CalendarWeekdays::duration() const { Duration dur; foreach ( CalendarDay *d, m_weekdays ) { dur += d->duration(); } return dur; } Duration CalendarWeekdays::duration(int _weekday) const { CalendarDay *day = weekday(_weekday); if (day) return day->duration(); return Duration(); } int CalendarWeekdays::indexOf( const CalendarDay *day ) const { return m_weekdays.values().indexOf( const_cast(day) ); } ///// Calendar //// Calendar::Calendar() : QObject( 0 ), // don't use parent m_parent(0), m_project(0), m_default( false ), m_shared(false) { init(); } Calendar::Calendar(const QString& name, Calendar *parent) : QObject( 0 ), // don't use parent m_name(name), m_parent(parent), m_project(0), m_days(), m_default( false ), m_shared(false) { init(); } Calendar::~Calendar() { //debugPlan<<"deleting"<regionCode()); #endif foreach (CalendarDay *d, calendar.days()) { m_days.append(new CalendarDay(d)); } delete m_weekdays; m_weekdays = new CalendarWeekdays(calendar.weekdays()); return *this; } void Calendar::init() { #ifdef HAVE_KHOLIDAYS m_region = new KHolidays::HolidayRegion(); #endif m_weekdays = new CalendarWeekdays(); m_timeZone = QTimeZone::systemTimeZone(); m_cacheversion = 0; m_blockversion = false; } int Calendar::cacheVersion() const { return m_parent ? m_parent->cacheVersion() : m_cacheversion; } void Calendar::incCacheVersion() { if ( m_blockversion ) { return; } if ( m_parent ) { m_parent->incCacheVersion(); } else { ++m_cacheversion; debugPlan<setCacheVersion( version ); } else { m_cacheversion = version; debugPlan<changed( this ); } } void Calendar::setParentCal( Calendar *parent, int pos ) { if ( m_parent ) { m_parent->takeCalendar( this ); } m_parent = parent; if ( m_parent ) { m_parent->addCalendar( this, pos ); } } bool Calendar::isChildOf( const Calendar *cal ) const { Calendar *p = parentCal(); for (; p != 0; p = p->parentCal() ) { if ( cal == p ) { return true; } } return false; } void Calendar::setProject(Project *project) { m_project = project; } void Calendar::setTimeZone( const QTimeZone &tz ) { if (m_timeZone == tz) { return; } //debugPlan<name(); m_timeZone = tz; #ifdef HAVE_KHOLIDAYS - if (m_regionCode == "Default") { - setHolidayRegion("Default"); + if (m_regionCode == QLatin1String("Default")) { + setHolidayRegion(QStringLiteral("Default")); } #endif if ( m_project ) { m_project->changed( this ); } incCacheVersion(); } QTimeZone Calendar::projectTimeZone() const { return m_project ? m_project->timeZone() : QTimeZone::systemTimeZone(); } void Calendar::setDefault( bool on ) { m_default = on; if ( m_project ) { m_project->changed( this ); } incCacheVersion(); } // Note: only project should do this void Calendar::setId(const QString& id) { //debugPlan<setTimeZone( m_timeZone ); } void Calendar::takeCalendar( Calendar *calendar ) { int i = indexOf( calendar ); if ( i != -1 ) { m_calendars.removeAt( i ); } } int Calendar::indexOf( const Calendar *calendar ) const { return m_calendars.indexOf( const_cast(calendar) ); } bool Calendar::loadCacheVersion( KoXmlElement &element, XMLLoaderObject &status ) { Q_UNUSED(status); - m_cacheversion = element.attribute( "version", 0 ).toInt(); + m_cacheversion = element.attribute( QStringLiteral("version"), 0 ).toInt(); debugPlan<load( e, status ) ) return false; } - if (e.tagName() == "day") { + if (e.tagName() == QLatin1String("day")) { CalendarDay *day = new CalendarDay(); if ( day->load( e, status ) ) { if (!day->date().isValid()) { delete day; errorPlan<date()); if (d) { // already exists, keep the new delete takeDay(d); warnPlan<id()); + me.setAttribute(QStringLiteral("parent"), m_parent->id()); } - me.setAttribute("name", m_name); - me.setAttribute("id", m_id); + me.setAttribute(QStringLiteral("name"), m_name); + me.setAttribute(QStringLiteral("id"), m_id); if ( m_default ) { - me.setAttribute("default", QString::number(m_default)); + me.setAttribute(QStringLiteral("default"), QString::number(m_default)); } - me.setAttribute("timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString()); + me.setAttribute(QStringLiteral("timezone"), m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString()); m_weekdays->save(me); foreach (CalendarDay *d, m_days) { - QDomElement e = me.ownerDocument().createElement("day"); + QDomElement e = me.ownerDocument().createElement(QStringLiteral("day")); me.appendChild(e); d->save(e); } - me.setAttribute("shared", m_shared); + me.setAttribute(QStringLiteral("shared"), m_shared); #ifdef HAVE_KHOLIDAYS - me.setAttribute("holiday-region", m_regionCode); + me.setAttribute(QStringLiteral("holiday-region"), m_regionCode); #endif saveCacheVersion( me ); } -int Calendar::state(const QDate &date) const +int Calendar::state(QDate date) const { CalendarDay *day = findDay( date ); if ( day && day->state() != CalendarDay::Undefined ) { return day->state(); } #ifdef HAVE_KHOLIDAYS if (isHoliday(date)) { return CalendarDay::NonWorking; } #endif day = weekday( date.dayOfWeek() ); if ( day && day->state() != CalendarDay::Undefined ) { return day->state(); } return m_parent ? m_parent->state( date ) : CalendarDay::Undefined; } -CalendarDay *Calendar::findDay(const QDate &date, bool skipUndefined) const { +CalendarDay *Calendar::findDay(QDate date, bool skipUndefined) const { //debugPlan<date() == date) { if (skipUndefined && d->state() == CalendarDay::Undefined) { continue; // hmmm, break? } return d; } } //debugPlan<setState( state ); emit changed( day ); incCacheVersion(); } void Calendar::addWorkInterval( CalendarDay *day, TimeInterval *ti ) { - workIntervalToBeAdded( day, ti, day->numIntervals() ); + emit workIntervalToBeAdded( day, ti, day->numIntervals() ); day->addInterval( ti ); - workIntervalAdded( day, ti ); + emit workIntervalAdded( day, ti ); incCacheVersion(); } void Calendar::takeWorkInterval( CalendarDay *day, TimeInterval *ti ) { if ( !day->hasInterval(ti) ) { return; } - workIntervalToBeRemoved( day, ti ); + emit workIntervalToBeRemoved( day, ti ); day->removeInterval( ti ); - workIntervalRemoved( day, ti ); + emit workIntervalRemoved( day, ti ); incCacheVersion(); return; } void Calendar::setWorkInterval( TimeInterval *ti, const TimeInterval &value ) { *ti = value; emit changed( ti ); incCacheVersion(); } -void Calendar::setDate( CalendarDay *day, const QDate &date ) +void Calendar::setDate( CalendarDay *day, QDate date ) { day->setDate( date ); emit changed( day ); incCacheVersion(); } -CalendarDay *Calendar::day( const QDate &date ) const +CalendarDay *Calendar::day( QDate date ) const { foreach ( CalendarDay *d, m_days ) { if ( d->date() == date ) { return d; } } return 0; } IntMap Calendar::weekdayStateMap() const { return m_weekdays->stateMap(); } void Calendar::setWeekday( int dayno, const CalendarDay &day ) { if ( dayno < 1 || dayno > 7 ) { return; } CalendarDay *wd = weekday( dayno ); while ( ! wd->timeIntervals().isEmpty() ) { TimeInterval *ti = wd->timeIntervals().last(); emit workIntervalToBeRemoved( wd, ti ); wd->removeInterval( ti ); emit workIntervalRemoved( wd, ti ); } wd->setState( day.state() ); emit changed( wd ); foreach ( TimeInterval *ti, day.timeIntervals() ) { TimeInterval *t = new TimeInterval( *ti ); emit workIntervalToBeAdded( wd, t, wd->numIntervals() ); // hmmmm wd->addInterval( t ); emit workIntervalAdded( wd, t ); } incCacheVersion(); } bool Calendar::hasParent(Calendar *cal) { //debugPlan; if (!m_parent) return false; if (m_parent == cal) return true; return m_parent->hasParent(cal); } AppointmentIntervalList Calendar::workIntervals( const QDateTime &start, const QDateTime &end, double load ) const { //debugPlan< start.date()) { startTime = QTime(0, 0, 0); } if (date < end.date()) { length = startTime.msecsTo( QTime(23, 59, 59, 999) ) + 1; } else { length = startTime.msecsTo( end.time() ); } if ( length <= 0 ) { break; } res = firstInterval( date, startTime, length ); while ( res.isValid() ) { //debugPlan<<"interval:"<= end ) { warnPlan<<"Invalid interval"; return lst; } Q_ASSERT(m_timeZone.isValid()); QDateTime zonedStart = start.toTimeZone( m_timeZone ); QDateTime zonedEnd = end.toTimeZone( m_timeZone ); Q_ASSERT( zonedStart.isValid() && zonedEnd.isValid() ); return workIntervals( zonedStart, zonedEnd, load ); } -Duration Calendar::effort(const QDate &date, const QTime &start, int length, Schedule *sch) const { +Duration Calendar::effort(QDate date, QTime start, int length, Schedule *sch) const { // debugPlan<"<state() == CalendarDay::Working) { return day->effort(start, length, m_timeZone, sch); } else if (day->state() == CalendarDay::NonWorking) { return Duration::zeroDuration; } else { errorPlan<<"Invalid state: "<state(); return Duration::zeroDuration; } } #ifdef HAVE_KHOLIDAYS if (isHoliday(date)) { return Duration::zeroDuration; } #endif // check my own weekdays if (m_weekdays) { if (m_weekdays->state(date) == CalendarDay::Working) { return m_weekdays->effort(date, start, length, m_timeZone, sch); } if (m_weekdays->state(date) == CalendarDay::NonWorking) { return Duration::zeroDuration; } } if (m_parent) { return m_parent->effort(date, start, length, sch); } return Duration::zeroDuration; } Duration Calendar::effort(const QDateTime &start, const QDateTime &end, Schedule *sch) const { // debugPlan< t0 ) { eff += effort(date, t0, t0.msecsTo( endTime ), sch); // last day } //debugPlan<<": eff now="<resource() ) debugPlan<resource()->name()<name()<<"Available:"<resource()->availableFrom()<resource()->availableUntil(); errorPlan<<"Illegal datetime: "<interval(startTime, length, m_timeZone, sch); } #ifdef HAVE_KHOLIDAYS if (isHoliday(date)) { return TimeInterval(); } #endif if (m_weekdays) { if (m_weekdays->state(date) == CalendarDay::Working) { //debugPlan<<"Check weekday"; TimeInterval i = m_weekdays->interval(date, startTime, length, m_timeZone, sch); //debugPlan<<"Checked weekday, got"<state(date) == CalendarDay::NonWorking) { return TimeInterval(); } } if (m_parent) { //debugPlan<<"Check parent"; return m_parent->firstInterval(date, startTime, length, sch); } return TimeInterval(); } DateTimeInterval Calendar::firstInterval( const QDateTime &start, const QDateTime &end, Schedule *sch) const { TimeInterval res; QTime startTime = start.time(); int length = 0; if ( start.date() == end.date() ) { // Handle single day length = startTime.msecsTo( end.time() ); if ( length <= 0 ) { warnPlan<<"Invalid length"< start.date()) { startTime = QTime(0, 0, 0); } if (date < end.date()) { length = startTime.msecsTo( QTime(23, 59, 59, 999) ) + 1; } else { length = startTime.msecsTo( end.time() ); } if ( length <= 0 ) { break; } //debugPlan<<"Check:"<= end ) { warnPlan<<"Invalid interval"<limit?"":"(time>limit)"); return DateTime(); } if ( time == limit ) { return DateTime(); } Q_ASSERT( m_timeZone.isValid() ); QDateTime zonedTime = time.toTimeZone( m_timeZone ); QDateTime zonedLimit = limit.toTimeZone( m_timeZone ); Q_ASSERT( zonedTime.isValid() && zonedLimit.isValid() ); return firstInterval( zonedTime, zonedLimit, sch ).first; } DateTime Calendar::firstAvailableBefore(const QDateTime &time, const QDateTime &limit, Schedule *sch) { debugPlan<findCalendar(id) : 0); } bool Calendar::removeId(const QString &id) { return (m_project ? m_project->removeCalendarId(id) : false); } void Calendar::insertId(const QString &id){ if (m_project) m_project->insertCalendarId(id, this); } void Calendar::addDay( CalendarDay *day ) { emit dayToBeAdded( day, 0 ); m_days.insert(0, day); emit dayAdded( day ); incCacheVersion(); } CalendarDay *Calendar::takeDay(CalendarDay *day) { int i = m_days.indexOf(day); if (i == -1) { return 0; } emit dayToBeRemoved( day ); m_days.removeAt(i); emit dayRemoved( day ); incCacheVersion(); return day; } QList > Calendar::consecutiveVacationDays() const { QList > lst; QPair interval( 0, 0 ); foreach ( CalendarDay* day, m_days ) { if ( day->state() == CalendarDay::NonWorking ) { if ( interval.first == 0 ) { interval.first = day; } interval.second = day; } else { if ( interval.first != 0 ) { lst << QPair( interval ); } interval.first = interval.second = 0; } } return lst; } QList Calendar::workingDays() const { QList lst; foreach ( CalendarDay* day, m_days ) { if ( day->state() == CalendarDay::Working ) { lst << day; } } return lst; } bool Calendar::isShared() const { return m_shared; } void Calendar::setShared(bool on) { m_shared = on; } #ifdef HAVE_KHOLIDAYS -bool Calendar::isHoliday(const QDate &date) const +bool Calendar::isHoliday(QDate date) const { if (m_region->isValid()) { KHolidays::Holiday::List lst = m_region->holidays(date); if (!lst.isEmpty() && lst.first().dayType() != KHolidays::Holiday::Workday) { return true; } } return false; } KHolidays::HolidayRegion *Calendar::holidayRegion() const { return m_region; } void Calendar::setHolidayRegion(const QString &code) { delete m_region; m_regionCode = code; - if (code == "Default") { + if (code == QLatin1String("Default")) { QString country; if (m_timeZone.isValid()) { // TODO be more accurate when country has multiple timezones/regions country = tzZones->value(m_timeZone.id()).country; } m_region = new KHolidays::HolidayRegion(KHolidays::HolidayRegion::defaultRegionCode(country)); } else { m_region = new KHolidays::HolidayRegion(code); } debugPlan<"<isValid(); emit changed(static_cast(0)); if (m_project) { m_project->changed(this); } } QString Calendar::holidayRegionCode() const { return m_regionCode; } QStringList Calendar::holidayRegionCodes() const { QStringList lst = KHolidays::HolidayRegion::regionCodes(); lst.removeDuplicates(); return lst; } #endif ///////////// StandardWorktime::StandardWorktime( Project *project ) : m_project( project ) { init(); } StandardWorktime::StandardWorktime(StandardWorktime *worktime) { if (worktime) { m_year = worktime->durationYear(); m_month = worktime->durationMonth(); m_week = worktime->durationWeek(); m_day = worktime->durationDay(); } else { init(); } } StandardWorktime::~StandardWorktime() { //debugPlan<<"("<changed( this ); } } QList StandardWorktime::scales() const { return QList() << m_year.milliseconds() << m_month.milliseconds() << m_week.milliseconds() << m_day.milliseconds() << 60*60*1000 << 60*1000 << 1000 << 1; } bool StandardWorktime::load( KoXmlElement &element, XMLLoaderObject &status ) { //debugPlan; - m_year = Duration::fromString(element.attribute("year"), Duration::Format_Hour); - m_month = Duration::fromString(element.attribute("month"), Duration::Format_Hour); - m_week = Duration::fromString(element.attribute("week"), Duration::Format_Hour); - m_day = Duration::fromString(element.attribute("day"), Duration::Format_Hour); + m_year = Duration::fromString(element.attribute(QStringLiteral("year")), Duration::Format_Hour); + m_month = Duration::fromString(element.attribute(QStringLiteral("month")), Duration::Format_Hour); + m_week = Duration::fromString(element.attribute(QStringLiteral("week")), Duration::Format_Hour); + m_day = Duration::fromString(element.attribute(QStringLiteral("day")), Duration::Format_Hour); KoXmlNode n = element.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); - if (e.tagName() == "calendar") { + if (e.tagName() == QLatin1String("calendar")) { // pre 0.6 version stored base calendar in standard worktime - if ( status.version() >= "0.6" ) { + if ( status.version() >= QLatin1String("0.6") ) { warnPlan<<"Old format, calendar in standard worktime"; warnPlan<<"Tries to load anyway"; } // try to load anyway Calendar *calendar = new Calendar; if ( calendar->load( e, status ) ) { status.project().addCalendar( calendar ); calendar->setDefault( true ); status.project().setDefaultCalendar( calendar ); // hmmm status.setBaseCalendar( calendar ); } else { delete calendar; errorPlan<<"Failed to load calendar"; } } } return true; } void StandardWorktime::save(QDomElement &element) const { //debugPlan; - QDomElement me = element.ownerDocument().createElement("standard-worktime"); + QDomElement me = element.ownerDocument().createElement(QStringLiteral("standard-worktime")); element.appendChild(me); - me.setAttribute("year", m_year.toString(Duration::Format_Hour)); - me.setAttribute("month", m_month.toString(Duration::Format_Hour)); - me.setAttribute("week", m_week.toString(Duration::Format_Hour)); - me.setAttribute("day", m_day.toString(Duration::Format_Hour)); + me.setAttribute(QStringLiteral("year"), m_year.toString(Duration::Format_Hour)); + me.setAttribute(QStringLiteral("month"), m_month.toString(Duration::Format_Hour)); + me.setAttribute(QStringLiteral("week"), m_week.toString(Duration::Format_Hour)); + me.setAttribute(QStringLiteral("day"), m_day.toString(Duration::Format_Hour)); } } //KPlato namespace diff --git a/plan/libs/kernel/kptcalendar.h b/plan/libs/kernel/kptcalendar.h index a58d9fbeb70..c99a5df1f8e 100644 --- a/plan/libs/kernel/kptcalendar.h +++ b/plan/libs/kernel/kptcalendar.h @@ -1,695 +1,695 @@ /* This file is part of the KDE project Copyright (C) 2003 - 2007 Dag Andersen Copyright (C) 2011 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. */ #ifndef KPTCALENDAR_H #define KPTCALENDAR_H #include "kptdatetime.h" #include "kptduration.h" #include "kptdebug.h" #include "kplatokernel_export.h" #include #include #include #include #include #ifdef HAVE_KHOLIDAYS namespace KHolidays { class HolidayRegion; } #endif class KUndo2Command; class QDomElement; class QStringList; /// The main namespace. namespace KPlato { class Calendar; class Project; class IntMap; class DateTime; class Project; class Schedule; class XMLLoaderObject; class AppointmentIntervalList; class KPLATOKERNEL_EXPORT DateTimeInterval : public QPair { public: DateTimeInterval() : QPair() {} DateTimeInterval( const DateTime &t1, const DateTime &t2 ) : QPair( t1, t2 ) {} DateTimeInterval &operator=( const DateTimeInterval &other ) { first = other.first; second = other.second; return *this; } bool isValid() const { return first.isValid() && second.isValid(); } void limitTo( const DateTime &start, const DateTime &end ) { if ( ! first.isValid() || ( start.isValid() && start > first ) ) { first = start; } if ( ! second.isValid() || ( end.isValid() && end < second ) ) { second = end; } if ( isValid() && first > second ) { first = second = DateTime(); } } void limitTo( const DateTimeInterval &interval ) { limitTo( interval.first, interval.second ); } DateTimeInterval limitedTo( const DateTime &start, const DateTime &end ) const { DateTimeInterval i = *this; i.limitTo( start, end ); return i; } DateTimeInterval limitedTo( const DateTimeInterval &interval ) const { return limitedTo( interval.first, interval.second ); } QString toString() const { - return QString( "%1 to %2" ) - .arg( first.isValid()?first.toString():"''" ) - .arg( second.isValid()?second.toString():"''" ); + return QStringLiteral( "%1 to %2" ) + .arg( first.isValid()?first.toString():QStringLiteral("''") ) + .arg( second.isValid()?second.toString():QStringLiteral("''") ); } }; /// TimeInterval is defined as a start time and a length. /// The end time (start + length) must not exceed midnight class KPLATOKERNEL_EXPORT TimeInterval : public QPair { public: TimeInterval() : QPair( QTime(), -1 ) {} - explicit TimeInterval( const QPair &value ) + explicit TimeInterval( QPair value ) : QPair( value ) { init(); } - TimeInterval( const QTime &start, int length ) + TimeInterval( QTime start, int length ) : QPair( start, length ) { init(); } TimeInterval( const TimeInterval &value ) : QPair( value.first, value.second ) { init(); } /// Return the intervals start time QTime startTime() const { return first; } /// Return the intervals calculated end time. Note: It may return QTime(0,0,0) QTime endTime() const { return first.addMSecs( second ); } double hours() const { return (double)(second) / ( 1000. * 60. * 60. ); } /// Returns true if this interval ends at midnight, and thus endTime() returns QTime(0,0,0) bool endsMidnight() const { return endTime() == QTime( 0, 0, 0 ); } bool isValid() const { return first.isValid() && second > 0; } bool isNull() const { return first.isNull() || second < 0; } TimeInterval &operator=( const TimeInterval &ti ) { first = ti.first; second = ti.second; return *this; } /// Returns true if the intervals overlap in any way bool intersects( const TimeInterval &ti ) const { if ( ! isValid() || ! ti.isValid() ) { return false; } if ( endsMidnight() && ti.endsMidnight() ) { return true; } if ( endsMidnight() ) { return first < ti.endTime(); } if ( ti.endsMidnight() ) { return ti.first < endTime(); } return ( first < ti.endTime() && endTime() > ti.first ) || ( ti.first < endTime() && ti.endTime() > first ); } protected: void init() { int s = QTime( 0, 0, 0 ).msecsTo( first ); if ( ( s + second ) > 86400000 ) { second = 86400000 - s; errorPlan<<"Overflow, limiting length to"< timeIntervals() const { return m_timeIntervals; } - void addInterval( const QTime &t1, int length ) { addInterval( new TimeInterval( t1, length ) ); } + void addInterval( QTime t1, int length ) { addInterval( new TimeInterval( t1, length ) ); } /** * Caller needs to ensure that intervals are not overlapping. */ void addInterval(TimeInterval *interval); void addInterval(TimeInterval interval) { addInterval(new TimeInterval(interval)); } void clearIntervals() { m_timeIntervals.clear(); } void setIntervals(const QList &intervals) { m_timeIntervals.clear(); m_timeIntervals = intervals; } void removeInterval( TimeInterval *interval ); bool hasInterval( const TimeInterval *interval ) const; int numIntervals() const; DateTime start() const; DateTime end() const; QDate date() const { return m_date; } - void setDate(const QDate& date) { m_date = date; } + void setDate(QDate date) { m_date = date; } int state() const { return m_state; } void setState(int state) { m_state = state; } bool operator==(const CalendarDay *day) const; bool operator==(const CalendarDay &day) const; bool operator!=(const CalendarDay *day) const; bool operator!=(const CalendarDay &day) const; Duration workDuration() const; /** * Returns the amount of 'worktime' that can be done on * this day between the times start and end. */ - Duration effort(const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0); + Duration effort(QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0); /** * Returns the amount of 'worktime' that can be done on * this day between the times start and end. */ - Duration effort(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0); + Duration effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0); /** * Returns the actual 'work interval' for the interval start to end. * If no 'work interval' exists, returns the interval start, end. * Use @ref hasInterval() to check if a 'work interval' exists. */ - TimeInterval interval(const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; + TimeInterval interval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; /** * Returns the actual 'work interval' for the interval start to end. * If no 'work interval' exists, returns the interval start, end. * Use @ref hasInterval() to check if a 'work interval' exists. */ - TimeInterval interval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; + TimeInterval interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; bool hasInterval() const; /** * Returns true if at least a part of a 'work interval' exists * for the interval start to end. */ - bool hasInterval(const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; + bool hasInterval(QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; /** * Returns true if at least a part of a 'work interval' exists * for the interval @p start to @p start + @p length. * Assumes this day is date. (Used by weekday hasInterval().) * If @p sch is not 0, the schedule is checked for availability. */ - bool hasInterval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; + bool hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0) const; Duration duration() const; const CalendarDay ©(const CalendarDay &day); static QString stateToString( int st, bool trans = false ); static QStringList stateList( bool trans = false ); private: QDate m_date; //NOTE: inValid if used for weekdays int m_state; Calendar *m_calendar; QList m_timeIntervals; #ifndef NDEBUG public: void printDebug(const QString& indent=QString()); #endif }; class KPLATOKERNEL_EXPORT CalendarWeekdays { public: CalendarWeekdays(); explicit CalendarWeekdays( const CalendarWeekdays *weekdays ); ~CalendarWeekdays(); bool load( KoXmlElement &element, XMLLoaderObject &status ); void save(QDomElement &element) const; const QList weekdays() const { QList lst = m_weekdays.values(); return lst; } /** * Returns the pointer to CalendarDay for day. * @param day The weekday number, must be between 1 (monday) and 7 (sunday) */ CalendarDay *weekday(int day) const; - CalendarDay *weekday(const QDate &date) const { return weekday(date.dayOfWeek()); } + CalendarDay *weekday(QDate date) const { return weekday(date.dayOfWeek()); } static int dayOfWeek( const QString &name ); const QMap &weekdayMap() const; IntMap stateMap() const; // void setWeekday(IntMap::iterator it, int state) { m_weekdays.at(it.key())->setState(state); } - int state(const QDate &date) const; + int state(QDate date) const; int state(int weekday) const; void setState(int weekday, int state); QList intervals(int weekday) const; void setIntervals(int weekday, const QList &intervals); void clearIntervals(int weekday); bool operator==(const CalendarWeekdays *weekdays) const; bool operator!=(const CalendarWeekdays *weekdays) const; - Duration effort(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch=0); + Duration effort(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch=0); /** * Returns the actual 'work interval' on the weekday defined by date * for the interval @p start to @p start + @p length. * If no 'work interval' exists, returns the interval start, end. * Use @ref hasInterval() to check if a 'work interval' exists. * If @p sch is not 0, the schedule is checked for availability. */ - TimeInterval interval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) const; + TimeInterval interval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const; /** * Returns true if at least a part of a 'work interval' exists * on the weekday defined by date for the interval start to end. */ - bool hasInterval(const QDate &date, const QTime &start, int length, const QTimeZone &timeZone, Schedule *sch) const; + bool hasInterval(QDate date, QTime start, int length, const QTimeZone &timeZone, Schedule *sch) const; bool hasInterval() const; Duration duration() const; Duration duration(int weekday) const; const CalendarWeekdays ©(const CalendarWeekdays &weekdays); int indexOf( const CalendarDay *day ) const; private: Calendar *m_calendar; QMap m_weekdays; #ifndef NDEBUG public: void printDebug(const QString& indent=QString()); #endif }; /** * Calendar defines the working and nonworking days and hours. * A day can have the three states Undefined, NonWorking, or Working. * A calendar can have a parent calendar that defines the days that are * undefined in this calendar. * If a calendar have no parent, an undefined day defaults to Nonworking. * A Working day has one or more work intervals to define the work hours. * * The definition can consist of two parts: Weekdays and Day. * Day has highest priority. * * A typical calendar hierarchy could include calendars on 4 levels: * 1. Definition of normal weekdays and national holidays/vacation days. * 2. Definition of the company's special workdays/-time and vacation days. * 3. Definitions for groups of resources. * 4. Definitions for individual resources. * * A calendar can define a timezone different from the projects. * This enables planning with resources that does not recide in the same place. * */ class KPLATOKERNEL_EXPORT Calendar : public QObject { Q_OBJECT public: Calendar(); explicit Calendar(const QString& name, Calendar *parent=0); //Calendar( const Calendar &c ); QObject doesn't allow a copy constructor ~Calendar(); const Calendar &operator=(const Calendar &calendar ) { return copy( calendar ); } QString name() const { return m_name; } void setName(const QString& name); Calendar *parentCal() const { return m_parent; } /** * Set parent calendar to @p parent. * Removes myself from current parent and * inserts myself as child to new parent. */ void setParentCal( Calendar *parent, int pos = -1 ); bool isChildOf( const Calendar *cal ) const; Project *project() const { return m_project; } void setProject(Project *project); QString id() const { return m_id; } void setId(const QString& id); const QList &calendars() const { return m_calendars; } void addCalendar( Calendar *calendar, int pos = -1 ); void takeCalendar( Calendar *calendar ); int indexOf( const Calendar *calendar ) const; /// Return number of children int childCount() const { return m_calendars.count(); } /// Return child calendar at @p index, 0 if index out of bounds Calendar *childAt( int index ) const { return m_calendars.value( index ); } bool load( KoXmlElement &element, XMLLoaderObject &status ); void save(QDomElement &element) const; - int state(const QDate &date) const; + int state(QDate date) const; void setState( CalendarDay *day, CalendarDay::State state ); void addWorkInterval( CalendarDay *day, TimeInterval *ti ); void takeWorkInterval( CalendarDay *day, TimeInterval *ti ); void setWorkInterval( TimeInterval *ti, const TimeInterval &value ); /** * Find the definition for the day @p date. * If @p skipUndefined = true the day is NOT returned if it has state Undefined. */ - CalendarDay *findDay(const QDate &date, bool skipUndefined=false) const; + CalendarDay *findDay(QDate date, bool skipUndefined=false) const; void addDay(CalendarDay *day); CalendarDay *takeDay(CalendarDay *day); const QList &days() const { return m_days; } QList > consecutiveVacationDays() const; QList workingDays() const; int indexOf( const CalendarDay *day ) const { return m_days.indexOf( const_cast( day ) ); } CalendarDay *dayAt( int index ) { return m_days.value( index ); } int numDays() const { return m_days.count(); } - void setDate( CalendarDay *day, const QDate &date ); - CalendarDay *day( const QDate &date ) const; + void setDate( CalendarDay *day, QDate date ); + CalendarDay *day( QDate date ) const; IntMap weekdayStateMap() const; CalendarWeekdays *weekdays() const { return m_weekdays; } CalendarDay *weekday(int day) const { return m_weekdays->weekday(day); } int indexOfWeekday( const CalendarDay *day ) const { return m_weekdays->indexOf( day ); } const QList weekdayList() const { return m_weekdays->weekdays(); } int numWeekdays() const { return weekdayList().count(); } /// Sets the @p weekday data to the data in @p day void setWeekday( int weekday, const CalendarDay &day ); QString parentId() const { return m_parentId; } void setParentId(const QString& id) { m_parentId = id; } bool hasParent(Calendar *cal); /** * Returns the work intervals in the interval from @p start to @p end * Sets the load of each interval to @p load */ AppointmentIntervalList workIntervals(const DateTime &start, const DateTime &end, double load) const; /** * Returns the amount of 'worktime' that can be done in the * interval from @p start to @p end * If @p sch is not 0, the schedule is checked for availability. */ Duration effort(const DateTime &start, const DateTime &end, Schedule *sch=0) const; /** * Returns the first 'work interval' for the interval * starting at @p start and ending at @p end. * If no 'work interval' exists, returns an interval with invalid DateTime. * You can also use @ref hasInterval() to check if a 'work interval' exists. * If @p sch is not 0, the schedule is checked for availability. */ DateTimeInterval firstInterval(const DateTime &start, const DateTime &end, Schedule *sch=0) const; /** * Returns true if at least a part of a 'work interval' exists * for the interval starting at @p start and ending at @p end. * If @p sch is not 0, the schedule is checked for availability. */ bool hasInterval(const DateTime &start, const DateTime &end, Schedule *sch=0) const; /** * Find the first available time after @p time before @p limit. * Return invalid datetime if not available. * If @p sch is not 0, the schedule is checked for availability. */ DateTime firstAvailableAfter(const DateTime &time, const DateTime &limit, Schedule *sch = 0); /** * Find the first available time backwards from @p time. Search until @p limit. * Return invalid datetime if not available. * If @p sch is not 0, the schedule is checked for availability. */ DateTime firstAvailableBefore(const DateTime &time, const DateTime &limit, Schedule *sch = 0); Calendar *findCalendar() const { return findCalendar(m_id); } Calendar *findCalendar(const QString &id) const; bool removeId() { return removeId(m_id); } bool removeId(const QString &id); void insertId(const QString &id); QTimeZone timeZone() const { return m_timeZone; } void setTimeZone( const QTimeZone &tz ); /// Return the project timezone, or local timezone if no project QTimeZone projectTimeZone() const; void setDefault( bool on ); bool isDefault() const { return m_default; } int cacheVersion() const; void incCacheVersion(); void setCacheVersion( int version ); bool loadCacheVersion( KoXmlElement &element, XMLLoaderObject &status ); void saveCacheVersion( QDomElement &element ) const; /// A calendar can be local to this project, or /// defined externally and shared with other projects bool isShared() const; /// Set calendar to be local if on = false, or shared if on = true void setShared(bool on); #ifdef HAVE_KHOLIDAYS - bool isHoliday(const QDate &date) const; + bool isHoliday(QDate date) const; KHolidays::HolidayRegion *holidayRegion() const; void setHolidayRegion(const QString &code); QString holidayRegionCode() const; QStringList holidayRegionCodes() const; #endif Q_SIGNALS: void changed( Calendar* ); void changed( CalendarDay* ); void changed( TimeInterval* ); void weekdayToBeAdded( CalendarDay *day, int index ); void weekdayAdded( CalendarDay *day ); void weekdayToBeRemoved( CalendarDay *day ); void weekdayRemoved( CalendarDay *day ); void dayToBeAdded( CalendarDay *day, int index ); void dayAdded( CalendarDay *day ); void dayToBeRemoved( CalendarDay *day ); void dayRemoved( CalendarDay *day ); void workIntervalToBeAdded( CalendarDay*, TimeInterval*, int index ); void workIntervalAdded( CalendarDay*, TimeInterval* ); void workIntervalToBeRemoved( CalendarDay*, TimeInterval* ); void workIntervalRemoved( CalendarDay*, TimeInterval* ); protected: void init(); const Calendar ©(const Calendar &calendar); /** * Returns the amount of 'worktime' that can be done on * the @p date between the times @p start and @p start + @p length. * The date and times are in timespecification @p spec. * If @p sch is not 0, the schedule is checked for availability. */ - Duration effort(const QDate &date, const QTime &start, int length, Schedule *sch=0) const; + Duration effort(QDate date, QTime start, int length, Schedule *sch=0) const; /** * Returns the amount of 'worktime' that can be done in the * interval from @p start to @p end * If @p sch is not 0, the schedule is checked for availability. */ Duration effort(const QDateTime &start, const QDateTime &end, Schedule *sch=0) const; /** * Returns the first 'work interval' on date for the interval * starting at @p start and ending at @p start + @p length. * If no 'work interval' exists, returns a null interval. * You can also use @ref hasInterval() to check if a 'work interval' exists. * The date and times are in timespecification spec. * If @p sch is not 0, the schedule is checked for availability. */ - TimeInterval firstInterval(const QDate &date, const QTime &start, int length, Schedule *sch=0) const; + TimeInterval firstInterval(QDate date, QTime start, int length, Schedule *sch=0) const; /** * Returns the first 'work interval' for the interval * starting at @p start and ending at @p end. * If no 'work interval' exists, returns an interval with invalid DateTime. */ DateTimeInterval firstInterval( const QDateTime &start, const QDateTime &end, Schedule *sch=0) const; /** * Returns true if at least a part of a 'work interval' exists * for the interval on date, starting at @p start and ending at @p start + @p length. * If @p sch is not 0, the schedule is checked for availability. */ - bool hasInterval(const QDate &date, const QTime &start, int length, Schedule *sch=0) const; + bool hasInterval(QDate date, QTime start, int length, Schedule *sch=0) const; /** * Returns the work intervals in the interval from @p start to @p end * Sets the load of each interval to @p load */ AppointmentIntervalList workIntervals(const QDateTime &start, const QDateTime &end, double load) const; /** * Find the first available time backwards from @p time. Search until @p limit. * Return invalid datetime if not available. * If @p sch is not 0, the schedule is checked for availability. */ DateTime firstAvailableBefore(const QDateTime &time, const QDateTime &limit, Schedule *sch = 0); private: QString m_name; Calendar *m_parent; Project *m_project; bool m_deleted; QString m_id; QString m_parentId; QList m_days; CalendarWeekdays *m_weekdays; QList m_calendars; QTimeZone m_timeZone; bool m_default; // this is the default calendar, only used for save/load bool m_shared; #ifdef HAVE_KHOLIDAYS KHolidays::HolidayRegion *m_region; QString m_regionCode; #endif int m_cacheversion; // incremented every time a calendar is changed friend class Project; int m_blockversion; // don't update if true #ifndef NDEBUG public: void printDebug(const QString& indent=QString()); #endif }; class KPLATOKERNEL_EXPORT StandardWorktime { public: explicit StandardWorktime( Project *project = 0 ); explicit StandardWorktime(StandardWorktime* worktime); ~StandardWorktime(); /// Set Project void setProject( Project *project ) { m_project = project; } /// The work time of a normal year. Duration durationYear() const { return m_year; } /// The work time of a normal year. double year() const { return m_year.toDouble(Duration::Unit_h); } /// Set the work time of a normal year. void setYear(const Duration year) { m_year = year; } /// Set the work time of a normal year. void setYear(double hours) { m_year = Duration((qint64)(hours*60.0*60.0*1000.0)); } /// The work time of a normal month Duration durationMonth() const { return m_month; } /// The work time of a normal month double month() const { return m_month.toDouble(Duration::Unit_h); } /// Set the work time of a normal month void setMonth(const Duration month) { m_month = month; } /// Set the work time of a normal month void setMonth(double hours) { m_month = Duration((qint64)(hours*60.0*60.0*1000.0)); } /// The work time of a normal week Duration durationWeek() const { return m_week; } /// The work time of a normal week double week() const { return m_week.toDouble(Duration::Unit_h); } /// Set the work time of a normal week void setWeek(const Duration week) { m_week = week; } /// Set the work time of a normal week void setWeek(double hours) { m_week = Duration((qint64)(hours*60.0*60.0*1000.0)); } /// The work time of a normal day Duration durationDay() const { return m_day; } /// The work time of a normal day double day() const { return m_day.toDouble(Duration::Unit_h); } /// Set the work time of a normal day void setDay(const Duration day) { m_day = day; changed(); } /// Set the work time of a normal day void setDay(double hours) { m_day = Duration(hours, Duration::Unit_h); changed(); } QList scales() const; bool load( KoXmlElement &element, XMLLoaderObject &status ); void save(QDomElement &element) const; void changed(); protected: void init(); private: Project *m_project; Duration m_year; Duration m_month; Duration m_week; Duration m_day; }; } //KPlato namespace #endif diff --git a/plan/libs/kernel/kptdatetime.cpp b/plan/libs/kernel/kptdatetime.cpp index b670825c99d..d5814f76fe0 100644 --- a/plan/libs/kernel/kptdatetime.cpp +++ b/plan/libs/kernel/kptdatetime.cpp @@ -1,182 +1,177 @@ /* This file is part of the KDE project Copyright (C) 2003-2007 Dag Andersen Copyright (C) 2011 Dag Andersen Copyright (C) 2016 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. */ #include "kptdatetime.h" #include "kptdebug.h" namespace KPlato { DateTime::DateTime() : QDateTime() { } -DateTime::DateTime( const QDate &date ) +DateTime::DateTime( QDate date ) : QDateTime( date ) { } -DateTime::DateTime( const QDate &date, const QTime &time) +DateTime::DateTime( QDate date, QTime time) : QDateTime( date, time, Qt::LocalTime) { if (!isValid() && this->date().isValid() && this->time().isValid()) { QTime t = this->time(); warnPlan<<"Invalid DateTime, try to compencate for DST"<date()<date().isValid() && this->time().isValid()) { QTime t = this->time(); warnPlan<<"Invalid DateTime, try to compencate for DST"<date()< Copyright (C) 2016 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. */ #ifndef KPTDATETIME_H #define KPTDATETIME_H #include "kplatokernel_export.h" #include "kptduration.h" #include #include #include /// The main namespace. namespace KPlato { class Duration; /** * DateTime is a QDateTime which knows about Duration * Note that in Plan all datetimes shall be in the time zone specified * in the project. * Exception to this is the calendar related dates and times which has * their own time zone specification. */ class KPLATOKERNEL_EXPORT DateTime : public QDateTime { public: /// Create a DateTime. DateTime(); /// Constructs a datetime with the given date, a valid time(00:00:00.000), and sets the timeSpec() to Qt::LocalTime. - explicit DateTime( const QDate & ); + explicit DateTime( QDate ); ///Constructs a datetime with the given date and time, and sets the timeSpec() to Qt::LocalTime. /// If date is valid and time is not, the time will be set to midnight. - DateTime( const QDate &, const QTime &); + DateTime( QDate , QTime ); ///Constructs a datetime with the given date and time in the given timezone. /// If @p timeZone is not valid, local time is used. /// If @p date is valid and @p time is not, the time will be set to midnight. - DateTime( const QDate &, const QTime &, const QTimeZone &timeZone); + DateTime( QDate , QTime , const QTimeZone &timeZone); /// Constructs a copy of the @p other QDateTime DateTime( const QDateTime &other ); - /// Constructs a copy of the @p other DateTime. - DateTime( const DateTime &other ); /// Constructs a datetime from @p dt, reinterpreting it to be from timezone @p timeZone. /// dt must be of timespec LocalTime. DateTime( const QDateTime &dt, const QTimeZone &timeZone ); /** * Adds the duration @p duration to the datetime */ DateTime operator+(const Duration &duration) const; /** * Subtracts the duration @p duration from the datetime */ DateTime operator-(const Duration &duration) const ; /** * Returns the absolute duration between the two datetimes */ Duration operator-(const DateTime &dt) const { return duration(dt); } /** * Returns the absolute duration between the two datetimes */ Duration operator-(const DateTime &dt) { return duration(dt); } /// Add @p duration to this datetime. DateTime &operator+=(const Duration &duration); /// Subtract the @p duration from this datetime. DateTime &operator-=(const Duration &duration); /** * Parse a datetime string @p dts and return the DateTime in the given @p timeZone. * The string @p dts should be in Qt::ISODate format and contain no time zone information. */ static DateTime fromString(const QString &dts, const QTimeZone &timeZone = QTimeZone::systemTimeZone()); private: Duration duration(const DateTime &dt) const; void add(const Duration &duration); void subtract(const Duration &duration); }; } //KPlato namespace KPLATOKERNEL_EXPORT QDebug operator<<( QDebug dbg, const KPlato::DateTime &dt ); #endif diff --git a/plan/libs/kernel/kptduration.cpp b/plan/libs/kernel/kptduration.cpp index 1468c1721ab..08398dc223c 100644 --- a/plan/libs/kernel/kptduration.cpp +++ b/plan/libs/kernel/kptduration.cpp @@ -1,337 +1,337 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas Zander zander@kde.org Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 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. */ #include "kptduration.h" #include "kptdatetime.h" #include "kptdebug.h" #include #include #include #include #include namespace KPlato { // Set the value of Duration::zeroDuration to zero. const Duration Duration::zeroDuration( 0, 0, 0 ); Duration::Duration() { m_ms = 0; } Duration::Duration( double value, Duration::Unit unit ) { if (unit == Unit_ms) m_ms = (qint64)value; else if (unit == Unit_s) m_ms = (qint64)(value * 1000); else if (unit == Unit_m) m_ms = (qint64)(value * ( 1000 * 60 )); else if (unit == Unit_h) m_ms = (qint64)(value * ( 1000 * 60 * 60 )); else if (unit == Unit_d) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 )); else if (unit == Unit_w) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 * 7 )); else if (unit == Unit_M) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 30 )); else if (unit == Unit_Y) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 365 )); } Duration::Duration(unsigned d, unsigned h, unsigned m, unsigned s, unsigned ms) { m_ms = ms; m_ms += static_cast(s) * 1000; // cast to avoid potential overflow problem m_ms += static_cast(m) * 60 * 1000; m_ms += static_cast(h) * 60 * 60 * 1000; m_ms += static_cast(d) * 24 * 60 * 60 * 1000; } Duration::Duration(const qint64 value, Duration::Unit unit) { if (unit == Unit_ms) m_ms = value; else if (unit == Unit_s) m_ms = (qint64)(value * 1000); else if (unit == Unit_m) m_ms = (qint64)(value * ( 1000 * 60 )); else if (unit == Unit_h) m_ms = (qint64)(value * ( 1000 * 60 * 60 )); else if (unit == Unit_d) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 )); else if (unit == Unit_w) m_ms = (qint64)(value * ( 1000 * 60 * 60 * 24 * 7 )); else if (unit == Unit_M) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 30 )); else if (unit == Unit_Y) m_ms = (qint64)(value * (qint64)( 1000 * 60 * 60 ) * ( 24 * 365 )); else errorPlan<<"Unknown unit: "<toString(); m_ms = 0; return; } m_ms = tmp; } -void Duration::subtract(const Duration &delta) { +void Duration::subtract(KPlato::Duration delta) { if (m_ms < delta.m_ms) { debugPlan<<"Underflow"<toString(); m_ms = 0; return; } m_ms -= delta.m_ms; } Duration Duration::operator*(int value) const { Duration dur(*this); if (value < 0) { debugPlan<<"Underflow"<toString(); } else { dur.m_ms = m_ms * value; //FIXME } return dur; } Duration Duration::operator/(int value) const { Duration dur(*this); if (value <= 0) { debugPlan<<"Underflow"<toString(); } else { dur.m_ms = m_ms / value; //FIXME } return dur; } Duration Duration::operator*(const double value) const { Duration dur(*this); dur.m_ms = qAbs(m_ms * (qint64)value); return dur; } Duration Duration::operator*(const Duration value) const { Duration dur(*this); dur.m_ms = m_ms * value.m_ms; return dur; } -double Duration::operator/(const Duration &d) const { +double Duration::operator/(KPlato::Duration d) const { if (d == zeroDuration) { debugPlan<<"Divide by zero:"<toString(); return 0.0; } return (double)(m_ms) / (double)(d.m_ms); } QString Duration::format(Unit unit, int pres) const { /* FIXME if necessary return i18nc( "", "%1%2", QLocale().toString(toDouble(unit), 'f', pres), unitToString(unit) );*/ return QLocale().toString( toDouble( unit ), 'f', pres ) + unitToString( unit ); } QString Duration::toString(Format format) const { qint64 ms; double days; unsigned hours; unsigned minutes; unsigned seconds; QString result; switch (format) { case Format_Hour: ms = m_ms; hours = ms / (1000 * 60 * 60); ms -= (qint64)hours * (1000 * 60 * 60); minutes = ms / (1000 * 60); - result = QString("%1h%2m").arg(hours).arg(minutes); + result = QStringLiteral("%1h%2m").arg(hours).arg(minutes); break; case Format_Day: days = m_ms / (1000 * 60 * 60 * 24.0); - result = QString("%1d").arg(QString::number(days, 'f', 4)); + result = QStringLiteral("%1d").arg(QString::number(days, 'f', 4)); break; case Format_DayTime: ms = m_ms; days = m_ms / (1000 * 60 * 60 * 24); ms -= (qint64)days * (1000 * 60 * 60 * 24); hours = ms / (1000 * 60 * 60); ms -= (qint64)hours * (1000 * 60 * 60); minutes = ms / (1000 * 60); ms -= minutes * (1000 * 60); seconds = ms / (1000); ms -= seconds * (1000); result.sprintf("%u %02u:%02u:%02u.%u", (unsigned)days, hours, minutes, seconds, (unsigned)ms); break; case Format_HourFraction: result = QLocale().toString(toDouble(Unit_h), 'f', 2); break; // i18n case Format_i18nHour: ms = m_ms; hours = ms / (1000 * 60 * 60); ms -= (qint64)hours * (1000 * 60 * 60); minutes = ms / (1000 * 60); result = i18nc("h:m", "%1h:%2m", hours, minutes); break; case Format_i18nDay: result = KFormat().formatSpelloutDuration( m_ms ); break; case Format_i18nWeek: result = this->format( Unit_w, 2 ); break; case Format_i18nMonth: result = this->format( Unit_M, 2 ); break; case Format_i18nYear: result = this->format( Unit_Y, 2 ); break; case Format_i18nDayTime: ms = m_ms; days = m_ms / (1000 * 60 * 60 * 24); ms -= (qint64)days * (1000 * 60 * 60 * 24); hours = ms / (1000 * 60 * 60); ms -= (qint64)hours * (1000 * 60 * 60); minutes = ms / (1000 * 60); ms -= minutes * (1000 * 60); seconds = ms / (1000); ms -= seconds * (1000); if (days == 0) { result = toString(Format_i18nHour); } else { result = i18nc("d h:m", "%1d %2h:%3m", days, hours, minutes); } break; case Format_i18nHourFraction: result = QLocale().toString(toDouble(Unit_h), 'f', 2); break; default: qFatal("Unknown format"); break; } return result; } Duration Duration::fromString(const QString &s, Format format, bool *ok) { if (ok) *ok = false; QRegExp matcher; Duration tmp; switch (format) { case Format_Hour: { - matcher.setPattern("^(\\d*)h(\\d*)m$" ); + matcher.setPattern(QStringLiteral("^(\\d*)h(\\d*)m$") ); int pos = matcher.indexIn(s); if (pos > -1) { tmp.addHours(matcher.cap(1).toUInt()); tmp.addMinutes(matcher.cap(2).toUInt()); if (ok) *ok = true; } break; } case Format_DayTime: { - matcher.setPattern("^(\\d*) (\\d*):(\\d*):(\\d*)\\.(\\d*)$" ); + matcher.setPattern(QStringLiteral("^(\\d*) (\\d*):(\\d*):(\\d*)\\.(\\d*)$") ); int pos = matcher.indexIn(s); if (pos > -1) { tmp.addDays(matcher.cap(1).toUInt()); tmp.addHours(matcher.cap(2).toUInt()); tmp.addMinutes(matcher.cap(3).toUInt()); tmp.addSeconds(matcher.cap(4).toUInt()); tmp.addMilliseconds(matcher.cap(5).toUInt()); if (ok) *ok = true; } break; } case Format_HourFraction: { // should be in double format bool res; double f = QLocale().toDouble(s, &res); if (ok) *ok = res; if (res) { return Duration((qint64)(f)*3600*1000); } break; } default: qFatal("Unknown format"); break; } return tmp; } QStringList Duration::unitList( bool trans ) { QStringList lst; - lst << ( trans ? i18nc( "Year. Note: Letter(s) only!", "Y" ) : "Y" ) - << ( trans ? i18nc( "Month. Note: Letter(s) only!", "M" ) : "M" ) - << ( trans ? i18nc( "Week. Note: Letter(s) only!", "w" ) : "w" ) - << ( trans ? i18nc( "Day. Note: Letter(s) only!", "d" ) : "d" ) - << ( trans ? i18nc( "Hour. Note: Letter(s) only!", "h" ) : "h" ) - << ( trans ? i18nc( "Minute. Note: Letter(s) only!", "m" ) : "m" ) - << ( trans ? i18nc( "Second. Note: Letter(s) only!", "s" ) : "s" ) - << ( trans ? i18nc( "Millisecond. Note: Letter(s) only!", "ms" ) : "ms" ); + lst << ( trans ? i18nc( "Year. Note: Letter(s) only!", "Y" ) : QStringLiteral("Y") ) + << ( trans ? i18nc( "Month. Note: Letter(s) only!", "M" ) : QStringLiteral("M") ) + << ( trans ? i18nc( "Week. Note: Letter(s) only!", "w" ) : QStringLiteral("w") ) + << ( trans ? i18nc( "Day. Note: Letter(s) only!", "d" ) : QStringLiteral("d") ) + << ( trans ? i18nc( "Hour. Note: Letter(s) only!", "h" ) : QStringLiteral("h") ) + << ( trans ? i18nc( "Minute. Note: Letter(s) only!", "m" ) : QStringLiteral("m") ) + << ( trans ? i18nc( "Second. Note: Letter(s) only!", "s" ) : QStringLiteral("s") ) + << ( trans ? i18nc( "Millisecond. Note: Letter(s) only!", "ms" ) : QStringLiteral("ms") ); return lst; } QString Duration::unitToString( Duration::Unit unit, bool trans ) { return unitList( trans ).at( unit ); } Duration::Unit Duration::unitFromString( const QString &u ) { int i = unitList().indexOf( u ); if ( i < 0 ) { errorPlan<<"Illegal unit: "< "< 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. */ #ifndef KPTDURATION_H #define KPTDURATION_H #include "kplatokernel_export.h" #include class QString; class QStringList; /// The main namespace. namespace KPlato { /** * The Duration class can be used to store a timespan in a convenient format. * The timespan can be in length in many many hours down to milliseconds. */ class KPLATOKERNEL_EXPORT Duration { public: /** * DayTime = d hh:mm:ss.sss * Day = d.ddd * Hour = hh:mm * HourFraction = h.fraction of an hour */ enum Format { Format_DayTime, Format_Year, Format_Month, Format_Week, Format_Day, Format_Hour, Format_HourFraction, Format_i18nDayTime, Format_i18nYear, Format_i18nMonth, Format_i18nWeek, Format_i18nDay, Format_i18nHour, Format_i18nHourFraction }; //NOTE: These must match units in DurationSpinBox! enum Unit { Unit_Y, Unit_M, Unit_w, Unit_d, Unit_h, Unit_m, Unit_s, Unit_ms }; /// Create a zero duration Duration(); /// Create a duration of @p value, the value is in @p unit (defaut unit is milliseconds) explicit Duration(const qint64 value, Unit unit = Unit_ms); /// Create a duration of @p value, the value is in @p unit (default is hours) explicit Duration(double value, Unit unit = Unit_h); /// Create a duration of @p d days, @p h hours, @p m minutes, @p s seconds and @p ms milliseconds Duration(unsigned d, unsigned h, unsigned m, unsigned s=0, unsigned ms=0); /// Return duration in milliseconds qint64 milliseconds() const { return m_ms; } /// Return duration in whole seconds qint64 seconds() const { return m_ms / 1000; } /// Return duration in whole minutes qint64 minutes() const { return seconds() / 60; } /// Return duration in whole hours unsigned hours() const { return minutes() / 60; } /// Return duration in whole days unsigned days() const { return hours() / 24; } /** * Adds @p delta to *this. If @p delta > *this, *this is set to zeroDuration. */ void addMilliseconds(qint64 delta) { add(delta); } /** * Adds @p delta to *this. If @p delta > *this, *this is set to zeroDuration. */ void addSeconds(qint64 delta) { addMilliseconds(delta * 1000); } /** * Adds @p delta to *this. If @p delta > *this, *this is set to zeroDuration. */ void addMinutes(qint64 delta) { addSeconds(delta * 60); } /** * Adds @p delta to *this. If @p delta > *this, *this is set to zeroDuration. */ void addHours(qint64 delta) { addMinutes(delta * 60); } /** * Adds @p delta to *this. If @p delta > *this, *this is set to zeroDuration. */ void addDays(qint64 delta) { addHours(delta * 24); } - bool operator==( const Duration &d ) const { return m_ms == d.m_ms; } + bool operator==( KPlato::Duration d ) const { return m_ms == d.m_ms; } bool operator==( qint64 d ) const { return m_ms == d; } - bool operator!=( const Duration &d ) const { return m_ms != d.m_ms; } + bool operator!=( KPlato::Duration d ) const { return m_ms != d.m_ms; } bool operator!=( qint64 d ) const { return m_ms != d; } - bool operator<( const Duration &d ) const { return m_ms < d.m_ms; } + bool operator<( KPlato::Duration d ) const { return m_ms < d.m_ms; } bool operator<( qint64 d ) const { return m_ms < d; } - bool operator<=( const Duration &d ) const { return m_ms <= d.m_ms; } + bool operator<=( KPlato::Duration d ) const { return m_ms <= d.m_ms; } bool operator<=( qint64 d ) const { return m_ms <= d; } - bool operator>( const Duration &d ) const { return m_ms > d.m_ms; } + bool operator>( KPlato::Duration d ) const { return m_ms > d.m_ms; } bool operator>( qint64 d ) const { return m_ms > d; } - bool operator>=( const Duration &d ) const { return m_ms >= d.m_ms; } + bool operator>=( KPlato::Duration d ) const { return m_ms >= d.m_ms; } bool operator>=( qint64 d ) const { return m_ms >= d; } - Duration &operator=(const Duration &d ) { m_ms = d.m_ms; return *this;} Duration operator*(int value) const; Duration operator*(const double value) const; Duration operator*(const Duration value) const; /// Divide duration with the integer @p value Duration operator/(int value) const; /// Divide duration with the duration @p d - double operator/(const Duration &d) const; + double operator/(KPlato::Duration d) const; /// Add duration with duration @p d - Duration operator+(const Duration &d) const + Duration operator+(KPlato::Duration d) const {Duration dur(*this); dur.add(d); return dur; } /// Add duration with duration @p d - Duration &operator+=(const Duration &d) {add(d); return *this; } + Duration &operator+=(KPlato::Duration d) {add(d); return *this; } /// Subtract duration with duration @p d - Duration operator-(const Duration &d) const + Duration operator-(KPlato::Duration d) const {Duration dur(*this); dur.subtract(d); return dur; } /// Subtract duration with duration @p d - Duration &operator-=(const Duration &d) {subtract(d); return *this; } + Duration &operator-=(KPlato::Duration d) {subtract(d); return *this; } /// Format duration into a string with @p unit and @p presition. QString format( Unit unit = Unit_h, int presition = 1 ) const; /// Convert duration to a string with @p format QString toString(Format format = Format_DayTime) const; /// Create a duration from string @p s with @p format static Duration fromString(const QString &s, Format format = Format_DayTime, bool *ok=0); /// Return the duration scaled to hours double toHours() const; /** * Converts Duration into a double and scales it to unit @p u (default unit is hours) */ double toDouble( Unit u = Unit_h ) const; /// Return the list of units. Translated if @p trans is true. static QStringList unitList( bool trans = false ); /// Return @p unit in human readable form. Translated if @p trans is true. static QString unitToString( Duration::Unit unit, bool trans = false ); /// Convert @p unit name into Unit static Unit unitFromString( const QString &unit ); /// Returns value and unit from a coded string in @p rv and @p unit. static bool valueFromString( const QString &value, double &rv, Unit &unit ); /** * This is useful for occasions where we need a zero duration. */ static const Duration zeroDuration; private: friend class DateTime; /** * Duration in milliseconds. Signed to allow for simple calculations which * might go negative for intermediate results. */ qint64 m_ms; private: void add(qint64 delta); - void add(const Duration &delta); + void add(KPlato::Duration delta); /** * Subtracts @param delta from *this. If @param delta > *this, *this is set to zeroDuration. */ - void subtract(const Duration &delta); + void subtract(KPlato::Duration delta); }; } //KPlato namespace #endif diff --git a/plan/libs/ui/reports/items/text/text.json b/plan/libs/ui/reports/items/text/text.json index 689d6259830..d087f3d9492 100644 --- a/plan/libs/ui/reports/items/text/text.json +++ b/plan/libs/ui/reports/items/text/text.json @@ -1,66 +1,69 @@ { "KPlugin": { "Authors": [ { "Email": "calligra_devel@kde.org", "Name": "Calligra Team", "Name[ca@valencia]": "Equip del Calligra", "Name[ca]": "Equip del Calligra", "Name[cs]": "Team Calligra", "Name[de]": "Calligra-Team", "Name[es]": "Equipo de Calligra", "Name[fr]": "L'équipe de Calligra", + "Name[gl]": "Equipo de Calligra", "Name[it]": "La squadra di Calligra", "Name[nl]": "Het team van Calligra", "Name[pl]": "Zespół Calligry", "Name[pt]": "Equipa do Calligra", "Name[pt_BR]": "Equipe do Calligra", "Name[sk]": "Tím Calligra", "Name[sv]": "Calligra-gruppen", "Name[uk]": "Команда розробників Calligra", "Name[x-test]": "xxCalligra Teamxx" } ], "Category": "", "Dependencies": [], "Description": "Text element for Reports", "Description[ca@valencia]": "Element de text per informes", "Description[ca]": "Element de text per informes", "Description[cs]": "Textové prvky pro hlášení", "Description[de]": "Textelement für Berichte", "Description[es]": "Elemento de texto para informes", "Description[fr]": "Element de texte pour Rapports", + "Description[gl]": "Texto para informes.", "Description[it]": "Elemento di testo per Reports", "Description[nl]": "Tekstelement voor rapporten", "Description[pl]": "Element tekstowy dla raportów", "Description[pt]": "Elemento de texto para os Relatórios", "Description[pt_BR]": "Elemento de texto para relatórios", "Description[sk]": "Prvok textu pre Reports", "Description[sv]": "Textelement för rapporter", "Description[uk]": "Елемент тексту для звітів", "Description[x-test]": "xxText element for Reportsxx", "EnabledByDefault": true, "Icon": "text-enriched", "Id": "org.kde.kreport.plan.text", "License": "LGPL", "Name": "Text", "Name[ast]": "Testu", "Name[es]": "Texto", "Name[fr]": "Texte", + "Name[gl]": "Texto", "Name[it]": "Testo", "Name[nl]": "Tekst", "Name[pl]": "Tekst", "Name[pt]": "Texto", "Name[pt_BR]": "Texto", "Name[uk]": "Текст", "Name[x-test]": "xxTextxx", "Name[zh_CN]": "文字", "ServiceTypes": [ "KReport/Element" ], "Version": "1.0", "Website": "https://calligra.org" }, "X-KDE-PluginInfo-LegacyName": "plan.text", "X-KReport-Priority": "10" } diff --git a/sheets/ValueParser.cpp b/sheets/ValueParser.cpp index 38799ae5d65..1735c784a49 100644 --- a/sheets/ValueParser.cpp +++ b/sheets/ValueParser.cpp @@ -1,540 +1,537 @@ /* This file is part of the KDE project Copyright 2007 Stefan Nikolaus Copyright 2004 Tomas Mecir Copyright 1998,1999 Torben Weis 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. */ // Local #include "ValueParser.h" #include "CalculationSettings.h" #include "Localization.h" #include "Style.h" #include "Value.h" using namespace Calligra::Sheets; ValueParser::ValueParser(const CalculationSettings* settings) : m_settings(settings) { } const CalculationSettings* ValueParser::settings() const { return m_settings; } Value ValueParser::parse(const QString& str) const { Value val; // If the text is empty, we don't have a value // If the user stated explicitly that they wanted text // (using the format or using a quote), // then we don't parse as a value, but as string. if (str.isEmpty() || str.at(0) == '\'') { val = Value(str); return val; } bool ok; QString strStripped = str.trimmed(); // Try parsing as various datatypes, to find the type of the string // First as number val = tryParseNumber(strStripped, &ok); if (ok) return val; // Then as bool // Note - I swapped the order of these two to try parsing as a number // first because that will probably be the most common case val = tryParseBool(strStripped, &ok); if (ok) return val; // Test for money number Number money = m_settings->locale()->readMoney(strStripped, &ok); if (ok) { val = Value(money); val.setFormat(Value::fmt_Money); return val; } val = tryParseDate(strStripped, &ok); if (ok) return val; val = tryParseTime(strStripped, &ok); if (ok) return val; // Nothing particular found, then this is simply a string val = Value(str); return val; } Value ValueParser::tryParseBool(const QString& str, bool *ok) const { Value val; if (ok) *ok = false; const QString& lowerStr = str.toLower(); const QStringList localeCodes(m_settings->locale()->country()); if ((lowerStr == "true") || (lowerStr == ki18n("true").toString(localeCodes).toLower())) { val = Value(true); if (ok) *ok = true; } else if ((lowerStr == "false") || (lowerStr == ki18n("false").toString(localeCodes).toLower())) { val = Value(false); if (ok) *ok = true; } return val; } Value ValueParser::readNumber(const QString& _str, bool *ok) const { bool isInt = false; QString str = _str.trimmed(); bool neg = str.indexOf(m_settings->locale()->negativeSign()) == 0; if (neg) str.remove(0, m_settings->locale()->negativeSign().length()); /* will hold the scientific notation portion of the number. Example, with 2.34E+23, exponentialPart == "E+23" */ QString exponentialPart; int EPos = str.indexOf('E', 0, Qt::CaseInsensitive); if (EPos != -1) { exponentialPart = str.mid(EPos); str = str.left(EPos); } int pos; int fracPos; QString major; QString minor; if ((pos = str.indexOf(m_settings->locale()->decimalSymbol())) != -1) { major = str.left(pos); minor = str.mid(pos + m_settings->locale()->decimalSymbol().length()); isInt = false; } else if (((pos = str.indexOf(' ')) != -1) && ((fracPos = str.indexOf('/')) != -1)) { // try to parse fractions of this form: // [0-9]+ [0-9]+/[1-9][0-9]? major = str.left(pos); QString numerator = str.mid(pos + 1, (fracPos - pos - 1)); QString denominator = str.mid(fracPos + 1); double minorVal = numerator.toDouble() / denominator.toDouble(); if (minorVal > 1) { // assume major is just a plain number double wholePart = floor(minorVal); minorVal -= wholePart; major = QString("%1").arg(major.toInt() + (int)wholePart); } minor = QString::number(minorVal, 'f').remove(0, 2); // chop off the "0." part // debugSheets <<"fraction:" << major <<"." << minor; } else { major = str; isInt = (EPos == -1); // only, if no exponential part was found } // Remove thousand separators int thlen = m_settings->locale()->thousandsSeparator().length(); int lastpos = 0; while ((pos = major.indexOf(m_settings->locale()->thousandsSeparator())) > 0) { // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N int fromEnd = major.length() - pos; if (fromEnd % (3 + thlen) != 0 // Needs to be a multiple, otherwise it's an error || pos - lastpos > 3 // More than 3 digits between two separators -> error || pos == 0 // Can't start with a separator || (lastpos > 0 && pos - lastpos != 3)) { // Must have exactly 3 digits between two separators if (ok) *ok = false; return Value(); } lastpos = pos; major.remove(pos, thlen); } if (lastpos > 0 && major.length() - lastpos != 3) { // Must have exactly 3 digits after the last separator if (ok) *ok = false; return Value(); } // log10(2^63) ~= 18 if (isInt && major.length() > 19) isInt = false; QString tot; if (neg) tot = '-'; tot += major; if (!isInt) tot += '.' + minor + exponentialPart; return isInt ? Value(tot.toLongLong(ok)) : Value(tot.toDouble(ok)); } Number ValueParser::readImaginary(const QString& str, bool* ok) const { if (str.isEmpty()) { if (ok) *ok = false; return 0.0; } Number imag = 0.0; if (str[0] == 'i' || str[0] == 'j') { if (str.length() == 1) { if (ok) *ok = true; imag = 1.0; } else imag = readNumber(str.mid(1), ok).asFloat(); } else if (str[str.length()-1] == 'i' || str[str.length()-1] == 'j') { const QString minus(m_settings->locale()->negativeSign()); if (str.length() == 2 && str[0] == '+') { if (ok) *ok = true; imag = 1.0; } else if (str.length() == minus.length() + 1 && str.left(minus.length()) == minus) { if (ok) *ok = true; imag = -1.0; } else imag = readNumber(str.left(str.length() - 1), ok).asFloat(); } else *ok = false; return imag; } Value ValueParser::tryParseNumber(const QString& str, bool *ok) const { Value value; if (str.endsWith('%')) { // percentage const Number val = readNumber(str.left(str.length() - 1).trimmed(), ok).asFloat(); if (*ok) { //debugSheets <<"ValueParser::tryParseNumber '" << str << // "' successfully parsed as percentage: " << val << '%' << endl; value = Value(val / 100.0); value.setFormat(Value::fmt_Percent); } } else if (str.count('i') == 1 || str.count('j') == 1) { // complex number Number real = 0.0; Number imag = 0.0; const QString minus(m_settings->locale()->negativeSign()); // both parts, real and imaginary, present? int sepPos; if ((sepPos = str.indexOf('+', 1)) != -1) { // imaginary part imag = readImaginary(str.mid(sepPos + 1).trimmed(), ok); // real part if (*ok) real = readNumber(str.left(sepPos).trimmed(), ok).asFloat(); } else if ((sepPos = str.indexOf(minus, minus.length())) != -1) { // imaginary part imag = -readImaginary(str.mid(sepPos + 1).trimmed(), ok); // real part if (*ok) real = readNumber(str.left(sepPos).trimmed(), ok).asFloat(); } else { // imaginary part if (str.trimmed().length() > 1) // but don't parse a stand-alone 'i' imag = readImaginary(str.trimmed(), ok); // real part if (*ok) real = 0.0; } if (*ok) value = Value(complex(real, imag)); } else // real number value = readNumber(str, ok); return value; } Value ValueParser::tryParseDate(const QString& str, bool *ok) const { bool valid = false; QDate tmpDate = m_settings->locale()->readDate(str, &valid); if (!valid) { // Try without the year // The tricky part is that we need to remove any separator around the year // For instance %Y-%m-%d becomes %m-%d and %d/%m/%Y becomes %d/%m // If the year is in the middle, say %m-%Y/%d, we'll remove the sep. // before it (%m/%d). QString fmt = m_settings->locale()->dateFormatShort(); int yearPos = fmt.indexOf("%Y", 0, Qt::CaseInsensitive); if (yearPos > -1) { if (yearPos == 0) { fmt.remove(0, 2); while (fmt[0] != '%') fmt.remove(0, 1); } else { fmt.remove(yearPos, 2); for (; yearPos > 0 && fmt[yearPos-1] != '%'; --yearPos) fmt.remove(yearPos, 1); } //debugSheets <<"Cell::tryParseDate short format w/o date:" << fmt; tmpDate = m_settings->locale()->readDate(str, fmt, &valid); } } if (valid) { // Note: if shortdate format only specifies 2 digits year, then 3/4/1955 // will be treated as in year 3055, while 3/4/55 as year 2055 // (because 55 < 69, see KLocale) and thus there's no way to enter for // year 1995 // The following fixes the problem, 3/4/1955 will always be 1955 QString fmt = m_settings->locale()->dateFormatShort(); if ((fmt.contains("%y") == 1) && (tmpDate.year() > 2999)) tmpDate = tmpDate.addYears(-1900); // this is another HACK ! // with two digit years, 0-69 is treated as year 2000-2069 (see KLocale) // however, in Excel only 0-29 is year 2000-2029, 30 or later is 1930 // onwards // the following provides workaround for KLocale so we're compatible // with Excel // (e.g 3/4/45 is Mar 4, 1945 not Mar 4, 2045) if ((tmpDate.year() >= 2030) && (tmpDate.year() <= 2069)) { QString yearFourDigits = QString::number(tmpDate.year()); QString yearTwoDigits = QString::number(tmpDate.year() % 100); // if year is 2045, check to see if "2045" isn't there --> actual // input is "45" if ((str.count(yearTwoDigits) >= 1) && (str.count(yearFourDigits) == 0)) tmpDate = tmpDate.addYears(-100); } } if (!valid) { //try to use the standard Qt date parsing, using ISO 8601 format tmpDate = QDate::fromString(str, Qt::ISODate); if (tmpDate.isValid()) { valid = true; } } if (ok) *ok = valid; return Value(tmpDate, m_settings); } Value ValueParser::tryParseTime(const QString& str, bool *ok) const { bool valid = false; - qInfo()<locale()->country()); const QString stringPm = ki18n("pm").toString(localeCodes); const QString stringAm = ki18n("am").toString(localeCodes); int pos = 0; if ((pos = str.indexOf(stringPm, 0, Qt::CaseInsensitive)) != -1) { // cut off 'PM' QString tmp = str.mid(0, str.length() - stringPm.length()); tmp = tmp.simplified(); // try again tmpTime = readTime(tmp, true, &valid); if (!valid) tmpTime = readTime(tmp, false, &valid); if (valid && tmpTime.time().hour() > 11) valid = false; else if (valid) tmpTime = tmpTime.addSecs(43200); // add 12 hours } else if ((pos = str.indexOf(stringAm, 0, Qt::CaseInsensitive)) != -1) { // cut off 'AM' QString tmp = str.mid(0, str.length() - stringAm.length()); tmp = tmp.simplified(); // try again tmpTime = readTime(tmp, true, &valid); if (!valid) tmpTime = readTime(tmp, false, &valid); if (valid && tmpTime.time().hour() > 11) valid = false; } } if (ok) *ok = valid; Value value; if (valid) { value = Value(tmpTime, m_settings); value.setFormat(Value::fmt_Time); } return value; } QDateTime ValueParser::readTime(const QString& intstr, bool withSeconds, bool* ok) const { QString str = intstr.simplified().toLower(); QString format = m_settings->locale()->timeFormat().simplified(); if (!withSeconds) { int n = format.indexOf("%S"); format = format.left(n - 1); } QDateTime result; int hour = 0; int minute = 0; int second = 0; int msecs = 0; bool g_12h = false; bool pm = false; bool negative = false; uint strpos = 0; uint formatpos = 0; const uint l = format.length(); const uint sl = str.length(); while (l > formatpos || sl > strpos) { if (!(l > formatpos && sl > strpos)) goto error; QChar c(format.at(formatpos++)); if (c != '%') { if (c.isSpace()) ++strpos; else if (c != str.at(strpos++)) goto error; continue; } // remove space at the beginning if (sl > strpos && str.at(strpos).isSpace()) ++strpos; c = format.at(formatpos++); switch (c.toLatin1()) { case 'p': { const QStringList localeCodes(m_settings->locale()->country()); QString s(ki18n("pm").toString(localeCodes).toLower()); int len = s.length(); if (str.mid(strpos, len) == s) { pm = true; strpos += len; } else { s = ki18n("am").toString(localeCodes).toLower(); len = s.length(); if (str.mid(strpos, len) == s) { pm = false; strpos += len; } else goto error; } } break; case 'k': case 'H': g_12h = false; if (str.at(strpos) == '-') { negative = true; if (sl <= ++strpos) goto error; } hour = readInt(str, strpos); if (hour < 0) goto error; break; case 'l': case 'I': g_12h = true; if (str.at(strpos) == '-') { negative = true; if (sl <= ++strpos) goto error; } hour = readInt(str, strpos); if (hour < 1 || hour > 12) goto error; break; case 'M': minute = readInt(str, strpos); if (minute < 0 || minute > 59) goto error; break; case 'S': if (!withSeconds) break; second = readInt(str, strpos); if (second < 0 || second > 59) goto error; if (strpos < sl && str.indexOf(m_settings->locale()->decimalSymbol()) == (int)strpos) { strpos += m_settings->locale()->decimalSymbol().length(); msecs = readInt(str, strpos); if (msecs < 0 || msecs > 999) goto error; } break; } } if (g_12h) { hour %= 12; if (pm) hour += 12; } if (ok) *ok = true; result = QDateTime(m_settings->referenceDate(), QTime(0, 0), Qt::UTC); msecs += (((hour * 60 + minute) * 60 + second) * 1000); result = result.addMSecs(negative ? -msecs : msecs); - qInfo()<

El Sheets del Calligra és una eina de full de càlcul plenament funcional. S'usa per a crear i calcular ràpidament diversos fulls de càlcul relacionats amb els negocis, com els ingressos i despeses, les hores de treball dels empleats, etc.

To Calligra Sheets είναι ένα πλήρες λειτουργιών εργαλείο υπολογισμών και λογιστικών πράξεων. Χρησιμοποιήστε το για να δημιουργήσετε και να υπολογίσετε γρήγορα επιχειρησιακά φύλλα εργασίας, όπως εισόδημα και έξοδα, εργάσιμες ώρες προσωπικού κτλ.

Calligra Sheets is a fully-featured calculation and spreadsheet tool. Use it to quickly create and calculate various business-related spreadsheets, such as income and expenditure, employee working hours, etc.

Calligra Sheets es una herramienta de hojas de cálculo completa. Puede utilizarla para crear y calcular rápidamente varias hojas de cálculo relacionadas con la empresa, como por ejemplo ingresos y gastos, horas de trabajo de los empleados, etc.

Calligra Sheets on kõiki võimaliku pakkuv tabelitöötluse ja arvutamise tööriist. Selle abil saab kiiresti luua ja välja arvutada mitmesuguseid ärimaailmas vajalikke tabeleid, näiteks tulud ja kulud, töötunnid jms.

Calligra Sheets on täysominaisuuksinen taulukkolaskentasovellus. Luot sillä nopeasti erilaisia talouslaskentaan liittyviä taulukoita kuten tulo- ja menotaulukoita, työntekijöiden työtuntitaulukoita jne.

Calligra Sheets est un tableur complet. Utilisez-le pour créer rapidement différents tableaux et calculs, comme les revenus, les dépenses, les heures de travail de vos employés, etc.

Calligra Sheets é unha ferramenta completa de follas de cálculo. Úsea para crear rapidamente follas de cálculo de negocios, como por exemplo follas de gastos e ingresos, de horas de traballo, etc.

Calligra Sheets è uno strumento di calcolo e un foglio elettronico completo. Usalo per calcolare e creare rapidamente vari fogli elettronici per i tuoi affari, come entrate e spese, ore lavorate dai dipendenti, ecc.

Calligra Sheets は豊富な機能を持った表計算ツールです。収入と支出、従業員の労働時間などビジネスなどに必要なスプレッドシートの作成、計算を短時間で行うことができます。

Calligra Sheets is een hulpmiddel voor rekenen en rekenblad rijk aan functies. Gebruik het om snel verschillende zakelijke rekenbladen te maken, zoals inkomsten en uitgaven, werkuren van werknemers, etc.

Arkusze Calligra jest pełnowartościowym narzędziem arkusza kalkulacyjnego i obliczeń. Użyj go do szybkiego tworzenia i obliczania rozmaitych biznesowych arkuszy kalkulacyjnych, takich jak przychód i rozchód, godziny pracy pracowników, itp.

O Calligra Sheets é uma folha de cálculo poderosa. Use-o para criar e calcular rapidamente várias folhas de cálculo relacionadas com o negócio, como receitas e despesas, horas de trabalho de empregados, etc.

Calligra Sheets é uma planilha de cálculo poderosa. Use-o para criar e calcular rapidamente várias planilhas relacionadas com negócios, como receitas e despesas, horas de trabalho de empregados, etc.

Calligra Sheets je plnohodnotný tabuľkový a výpočtový nástroj. Použite ho na rýchle vytvorenie a výpočet rôznych obchodných tabuliek, ako príjmy a výdavky, pracovné hodiny zamestnancov, atď.

Calligra Sheets är ett fullfjädrat beräknings- och kalkylarksverktyg. Använd det för att snabbt skapa och beräkna diverse affärsrelaterade kalkylark, såsom inkomster och utgifter, anställdas arbetstimmar, etc.

Calligra Sheets — повноцінне середовище для обчислень та роботи з електронними таблицями. Програмою можна скористатися для швидкого створення та обчислень на різноманітних пов’язаних із бізнесом електронних таблицях, зокрема вхідних та вихідних рахунках, табелів робочого часу тощо.

xxCalligra Sheets is a fully-featured calculation and spreadsheet tool. Use it to quickly create and calculate various business-related spreadsheets, such as income and expenditure, employee working hours, etc.xx

Features:

Svojstva:

Característiques:

Característiques:

Vlastnosti:

Funktionen:

Χαρακτηριστικά:

Features:

Características:

Omadused:

Ominaisuuksia:

Fonctionnalités :

Funcionalidades:

Characteristicas:

Funzionalità:

機能:

Mogelijkheden:

Cechy:

Funcionalidades:

Funcionalidades:

Funkcie:

Funktioner:

Можливості:

xxFeatures:xx

功能:

  • Use shapes to take notes or mind maps
  • Koristite likove za uzimanje zabilješki ili mapa uma
  • Usa formes per prendre notes o mapes mentals
  • Usa formes per prendre notes o mapes mentals
  • Χρησιμοποιήστε σχήματα για να κρατήσετε σημειώσεις ή νοηματικούς χάρτες
  • Use shapes to take notes or mind maps
  • Usa formas para tomar notas o crear mapas mentales
  • Kujundite kasutamine märkmete või ideekaardi loomiseks
  • Muotojen käyttö muistiinpanojen tai miellekarttojen tallentamiseen
  • Utilisez des formes pour prendre des notes ou des cartes heuristiques
  • Use formas para escribir notas ou mapas mentais.
  • Usa le forme per creare note o mappe mentali
  • シェイプを使用したメモの貼り付けやマインドマップの作成
  • Gebruik vormen om notities of bedenksels vast te leggen
  • Używa kształtów do robienia notatek i map umysłu
  • Usar formas para tirar notas ou mapas mentais
  • Usa formas para fazer anotações ou mapas mentais
  • Použite tvary na vytváranie poznámok alebo myšlienkových máp
  • Använd former för att göra anteckningar eller tankekartor
  • Використання форм для зберігання нотаток або записів на пам’ять.
  • xxUse shapes to take notes or mind mapsxx
  • Large range of pre-defined templates
  • Širok opseg predefinisanih predložaka
  • Àmplia gamma de plantilles predefinides
  • Àmplia gamma de plantilles predefinides
  • Viele vordefinierte Vorlagen aus vielen Bereichen
  • Μεγάλο εύρος προκαθορισμένος προτύπων
  • Large range of pre-defined templates
  • Dispone de una amplia gama de plantillas predefinidas
  • Suur valik valmismalle
  • Laajalti erilaisia esimääritettyjä mallipohjia
  • Large éventail de modèles prédéfinis
  • Gran variedade de modelos predefinidos.
  • Ampia gamma di modelli predefiniti
  • 幅広い種類のテンプレート
  • Grote reeks van voorgedefinieerde sjablonen
  • Szeroki wybór uprzednio stworzonych szablonów
  • Gama alargada de modelos predefinidos
  • Grande variedade de modelos predefinidos
  • Veľký rozsah preddefinovaných šablón
  • Stort antal fördefinierade mallar
  • Широкий діапазон попередньо визначених шаблонів.
  • xxLarge range of pre-defined templatesxx
  • Powerful and comprehensive formula list
  • Moćna i kompaktna lista formula
  • Llista de fórmules potent i extensa
  • Llista de fórmules potent i extensa
  • Leistungsfähige und umfassende Liste von Formeln
  • Ισχυρή και κατανοητή λίστα μαθηματικών σχέσεων
  • Powerful and comprehensive formula list
  • Dispone de una lista de fórmulas potente y completa
  • Võimas ja põhjalik valemikogu
  • Tehokas ja kattava kaavaluettelo
  • Liste de formules complète et variée
  • Lista de fórmulas potentes e fáciles de entender.
  • Elenco completo e potente di formule
  • 強力かつ包括的な数式
  • Krachtige en uitgebreide lijst met formules
  • Przydatny i wyczerpujący spis równań
  • Lista de fórmulas poderosa e compreensiva
  • Lista de fórmulas poderosa e compreensiva
  • Silný a úplný zoznam vzorcov
  • Kraftfull och omfattande formellista
  • Потужний і повний список функцій.
  • xxPowerful and comprehensive formula listxx
  • Work in a familiar environment
  • Rad u poznatom okruženju
  • Treball en un entorn familiar
  • Treball en un entorn familiar
  • Arbeiten in einer bekannten Arbeitsumgebung
  • Εργαστείτε σε γνώριμο περιβάλλον
  • Work in a familiar environment
  • Funciona en un entorno familiar
  • Töötamine harjumuspärases keskkonnas
  • Työskentely tutussa ympäristössä
  • Travaillez dans un environnement familier
  • Traballe nun ambiente familiar.
  • Opera in un ambiente familiare
  • 使い慣れた環境での操作
  • Werk in een bekende omgeving
  • Praca w znajomym środowisku
  • Funciona num ambiente familiar
  • Funciona em um ambiente familiar
  • Práca v známom prostredí
  • Arbeta i en välbekant omgivning
  • Робота у знайомому середовищі.
  • xxWork in a familiar environmentxx
http://www.calligra.org/sheets/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=calligrasheets http://docs.kde.org/stable/en/calligra/sheets/index.html http://kde.org/images/screenshots/sheets.png KDE calligrasheets diff --git a/sheets/tests/TestDatetimeFunctions.cpp b/sheets/tests/TestDatetimeFunctions.cpp index 75fb8f1a129..74155b9d353 100644 --- a/sheets/tests/TestDatetimeFunctions.cpp +++ b/sheets/tests/TestDatetimeFunctions.cpp @@ -1,563 +1,587 @@ /* This file is part of the KDE project Copyright 2007 Sascha Pfau 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; only version 2 of the License. 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. */ #include "TestDatetimeFunctions.h" #include "TestKspreadCommon.h" +#include + void TestDatetimeFunctions::initTestCase() { FunctionModuleRegistry::instance()->loadFunctionModules(); } #define CHECK_EVAL(x,y) { Value z(RoundNumber(y)); QCOMPARE(evaluate(x,z), (z)); } #define CHECK_FAIL(x,y,txt) { Value z(RoundNumber(y)); QEXPECT_FAIL("", txt, Continue); QCOMPARE(evaluate(x,z), (z));} #define ROUND(x) (roundf(1e10 * x) / 1e10) // changelog ///////////////////////////////////// // 18.05.07 // - fix typo in yearfrac // - indend // - added missing tests EOMONTH() // - added missing values in DATEDIF // 02.06.07 // - added Isoweeknum tests starts on sunday // - added WEEKINYEAR unittests // - added ISLEAPYEAR unittests // - added DAYSINMONTH unittests // 15.07.07 // - modified YEARFRAC basis=1 // 30.10.07 // - fixed WEEKNUM tests // - corrected wrong DAYS360,EDATE and EOMONTH unittests // - commented out last issue on YEARFRAC #if 0 // not used? // round to get at most 10-digits number static Value RoundNumber(double f) { return Value(ROUND(f)); } #endif // round to get at most 10-digits number static Value RoundNumber(const Value& v) { if (v.isNumber()) { double d = numToDouble(v.asFloat()); if (fabs(d) < DBL_EPSILON) d = 0.0; return Value(ROUND(d)); } else return v; } Value TestDatetimeFunctions::evaluate(const QString& formula, Value& ex) { Formula f; QString expr = formula; if (expr[0] != '=') expr.prepend('='); f.setExpression(expr); Value result = f.eval(); if (result.isFloat() && ex.isInteger()) ex = Value(ex.asFloat()); if (result.isInteger() && ex.isFloat()) result = Value(result.asFloat()); return RoundNumber(result); } void TestDatetimeFunctions::testYEARFRAC() { // basis 0 US CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-06-30\" ; 0)", Value(0.4972222222)); CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-07-01\" ; 0)", Value(0.5000000000)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2000-06-30\" ; 0)", Value(0.4972222222)); CHECK_EVAL("YEARFRAC( \"2000-01-15\" ; \"2000-09-17\" ; 0)", Value(0.6722222222)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2001-01-01\" ; 0)", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-01-01\" ; \"2002-01-01\" ; 0)", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-12-05\" ; \"2001-12-30\" ; 0)", Value(0.0694444444)); CHECK_EVAL("YEARFRAC( \"2000-02-05\" ; \"2006-08-10\" ; 0)", Value(6.5138888889)); // basis 0 is default CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-06-30\")", Value(0.4972222222)); CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-07-01\")", Value(0.5000000000)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2000-06-30\")", Value(0.4972222222)); CHECK_EVAL("YEARFRAC( \"2000-01-15\" ; \"2000-09-17\")", Value(0.6722222222)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2001-01-01\")", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-01-01\" ; \"2002-01-01\")", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-12-05\" ; \"2001-12-30\")", Value(0.0694444444)); CHECK_EVAL("YEARFRAC( \"2000-02-05\" ; \"2006-08-10\")", Value(6.5138888889)); // basis 1 Actual/actual // other values are taken from OOo-2.2.1 CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-06-30\" ; 1)", Value(0.4931506849)); CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-07-01\" ; 1)", Value(0.4958904110)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2000-06-30\" ; 1)", Value(0.4945355191)); CHECK_EVAL("YEARFRAC( \"2000-01-15\" ; \"2000-09-17\" ; 1)", Value(0.6721311475)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2001-01-01\" ; 1)", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-12-05\" ; \"2001-12-30\" ; 1)", Value(0.0684931507)); CHECK_EVAL("YEARFRAC( \"2000-02-05\" ; \"2006-08-10\" ; 1)", Value(6.5099726242)); // specs 6.5099726242 OOo-2.3.0 6.5081967213 CHECK_EVAL("YEARFRAC( \"2003-12-06\" ; \"2004-03-05\" ; 1)", Value(0.2459016393)); CHECK_EVAL("YEARFRAC( \"2003-12-31\" ; \"2004-03-31\" ; 1)", Value(0.2486338798)); CHECK_EVAL("YEARFRAC( \"2004-10-01\" ; \"2005-01-11\" ; 1)", Value(0.2794520548)); CHECK_EVAL("YEARFRAC( \"2004-10-26\" ; \"2005-02-06\" ; 1)", Value(0.2821917808)); CHECK_EVAL("YEARFRAC( \"2004-11-20\" ; \"2005-03-04\" ; 1)", Value(0.2849315068)); CHECK_EVAL("YEARFRAC( \"2004-12-15\" ; \"2005-03-30\" ; 1)", Value(0.2876712329)); CHECK_EVAL("YEARFRAC( \"2000-12-01\" ; \"2001-01-16\" ; 1)", Value(0.1260273973)); CHECK_EVAL("YEARFRAC( \"2000-12-26\" ; \"2001-02-11\" ; 1)", Value(0.1287671233)); // basis 2 Actual/360 CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2000-06-30\" ; 2)", Value(0.5027777778)); CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-06-30\" ; 2)", Value(0.5000000000)); CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-07-01\" ; 2)", Value(0.5027777778)); CHECK_EVAL("YEARFRAC( \"2000-01-15\" ; \"2000-09-17\" ; 2)", Value(0.6833333333)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2001-01-01\" ; 2)", Value(1.0166666667)); CHECK_EVAL("YEARFRAC( \"2001-01-01\" ; \"2002-01-01\" ; 2)", Value(1.0138888889)); CHECK_EVAL("YEARFRAC( \"2001-12-05\" ; \"2001-12-30\" ; 2)", Value(0.0694444444)); CHECK_EVAL("YEARFRAC( \"2000-02-05\" ; \"2006-08-10\" ; 2)", Value(6.6055555556)); // basis 3 Actual/365 CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2001-01-01\" ; 3)", Value(1.0027397260)); CHECK_EVAL("YEARFRAC( \"2001-01-01\" ; \"2002-01-01\" ; 3)", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-12-05\" ; \"2001-12-30\" ; 3)", Value(0.0684931507)); CHECK_EVAL("YEARFRAC( \"2000-02-05\" ; \"2006-08-10\" ; 3)", Value(6.5150684932)); // basis 4 European 30/360 CHECK_EVAL("YEARFRAC( \"1999-01-01\" ; \"1999-06-30\" ; 4)", Value(0.4972222222)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2000-06-30\" ; 4)", Value(0.4972222222)); CHECK_EVAL("YEARFRAC( \"2000-01-15\" ; \"2000-09-17\" ; 4)", Value(0.6722222222)); CHECK_EVAL("YEARFRAC( \"2000-01-01\" ; \"2001-01-01\" ; 4)", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-01-01\" ; \"2002-01-01\" ; 4)", Value(1.0000000000)); CHECK_EVAL("YEARFRAC( \"2001-12-05\" ; \"2001-12-30\" ; 4)", Value(0.0694444444)); CHECK_EVAL("YEARFRAC( \"2000-02-05\" ; \"2006-08-10\" ; 4)", Value(6.5138888889)); // alternate function name CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETYEARFRAC(\"1999-01-01\";\"1999-06-30\";1)", Value(0.4931506849)); } void TestDatetimeFunctions::testDATEDIF() { // interval y ( years ) CHECK_EVAL("DATEDIF(DATE(1990;2;15); DATE(1993;9;15); \"y\")", Value(3)); // TODO check value; kspread says 3 // interval m ( Months. If there is not a complete month between the dates, 0 will be returned.) CHECK_EVAL("DATEDIF(DATE(1990;2;15); DATE(1993;9;15); \"m\")", Value(43)); // interval d ( Days ) CHECK_EVAL("DATEDIF(DATE(1990;2;15); DATE(1993;9;15); \"d\")", Value(1308)); // TODO check value; kspread says 1308 // interval md ( Days, ignoring months and years ) CHECK_EVAL("DATEDIF(DATE(1990;2;15); DATE(1993;9;15); \"md\")", Value(0)); // interval ym ( Months, ignoring years ) CHECK_EVAL("DATEDIF(DATE(1990;2;15); DATE(1993;9;15); \"ym\")", Value(7)); // interval yd ( Days, ignoring years ) CHECK_EVAL("DATEDIF(DATE(1990;2;15); DATE(1993;9;15); \"yd\")", Value(212)); // TODO check value; kspread says 212 } void TestDatetimeFunctions::testISLEAPYEAR() { // only every 400 years ... CHECK_EVAL("ISLEAPYEAR(1900)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2000)", Value(true)); CHECK_EVAL("ISLEAPYEAR(2100)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2200)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2300)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2400)", Value(true)); CHECK_EVAL("ISLEAPYEAR(1900)", Value(false)); // and every 4th year CHECK_EVAL("ISLEAPYEAR(2000)", Value(true)); CHECK_EVAL("ISLEAPYEAR(2001)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2002)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2003)", Value(false)); CHECK_EVAL("ISLEAPYEAR(2004)", Value(true)); // test alternate name for the ISLEAPYEAR function CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETISLEAPYEAR(1900)", Value(false)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETISLEAPYEAR(2000)", Value(true)); } void TestDatetimeFunctions::testWEEKNUM() { // is known as weeknum_add() in OOo // type default ( type 1 ) CHECK_EVAL("WEEKNUM(DATE(2000;05;21))", Value(22)); // CHECK_EVAL("WEEKNUM(DATE(2005;01;01))", Value(01)); // CHECK_EVAL("WEEKNUM(DATE(2000;01;02))", Value(02)); // CHECK_EVAL("WEEKNUM(DATE(2000;01;03))", Value(02)); // CHECK_EVAL("WEEKNUM(DATE(2000;01;04))", Value(02)); // CHECK_EVAL("WEEKNUM(DATE(2006;01;01))", Value(01)); // // type 1 CHECK_EVAL("WEEKNUM(DATE(2000;05;21);1)", Value(22)); CHECK_EVAL("WEEKNUM(DATE(2008;03;09);1)", Value(11)); // type 2 CHECK_EVAL("WEEKNUM(DATE(2000;05;21);2)", Value(21)); CHECK_EVAL("WEEKNUM(DATE(2005;01;01);2)", Value(01)); // ref. OOo-2.2.0 = 1 CHECK_EVAL("WEEKNUM(DATE(2000;01;02);2)", Value(01)); // ref. OOo-2.2.0 = 1 CHECK_EVAL("WEEKNUM(DATE(2000;01;03);2)", Value(02)); // ref. OOo-2.2.0 = 2 CHECK_EVAL("WEEKNUM(DATE(2000;01;04);2)", Value(02)); // ref. OOo-2.2.0 = 2 CHECK_EVAL("WEEKNUM(DATE(2008;03;09);2)", Value(10)); // additional tests for method 2 CHECK_EVAL("WEEKNUM(DATE(2006;01;01);2)", Value(01)); CHECK_EVAL("WEEKNUM(DATE(2006;01;02);2)", Value(02)); // alternate function name CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETWEEKNUM(DATE(2000;05;21);1)", Value(22)); } void TestDatetimeFunctions::testWEEKSINYEAR() { CHECK_EVAL("WEEKSINYEAR(1970)", Value(53)); CHECK_EVAL("WEEKSINYEAR(1995)", Value(52)); CHECK_EVAL("WEEKSINYEAR(2009)", Value(53)); CHECK_EVAL("WEEKSINYEAR(2010)", Value(52)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETWEEKSINYEAR(1992)", Value(53)); } void TestDatetimeFunctions::testWORKDAY() { // 2001 JAN 01 02 03 04 05 06 07 08 // MO TU WE TH FR SA SU MO // 01 02 -- -- CHECK_EVAL("WORKDAY(DATE(2001;01;01);2;2)=DATE(2001;01;05)", Value(true)); CHECK_EVAL("WORKDAY(DATE(2001;01;01);2;3)=DATE(2001;01;08)", Value(true)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETWORKDAY(DATE(2001;01;01);2;3)=DATE(2001;01;08)", Value(true)); } void TestDatetimeFunctions::testNETWORKDAY() { // 2001 JAN 01 02 03 04 05 06 07 08 09 // MO TU WE TH FR SA SU MO TU // 01 02 03 04 05 05 05 06 ... networkdays CHECK_EVAL("NETWORKDAY(DATE(2001;01;01);DATE(2001;01;08))", Value(5)); CHECK_EVAL("NETWORKDAY(DATE(2001;01;01);DATE(2001;01;07))", Value(5)); CHECK_EVAL("NETWORKDAY(DATE(2001;01;01);DATE(2001;01;06))", Value(5)); CHECK_EVAL("NETWORKDAY(DATE(2001;01;01);DATE(2001;01;05))", Value(4)); // 2008 FEB 25 26 27 28 29 01 02 03 04 // MO TU WE TH FR SA SU MO TU // 01 02 03 04 05 05 05 06 ... networkdays CHECK_EVAL("NETWORKDAY(DATE(2008;02;25);DATE(2008;02;28))", Value(3)); CHECK_EVAL("NETWORKDAY(DATE(2008;02;25);DATE(2008;02;29))", Value(4)); CHECK_EVAL("NETWORKDAY(DATE(2008;02;25);DATE(2008;03;01))", Value(5)); CHECK_EVAL("NETWORKDAY(DATE(2008;02;25);DATE(2008;03;02))", Value(5)); CHECK_EVAL("NETWORKDAY(DATE(2008;02;25);DATE(2008;03;03))", Value(5)); CHECK_EVAL("NETWORKDAY(DATE(2008;02;25);DATE(2008;03;04))", Value(6)); } void TestDatetimeFunctions::testUNIX2DATE() { // 01/01/2001 = 946684800 CHECK_EVAL("UNIX2DATE(946684800)=DATE(2000;01;01)", Value(true)); // TODO result of various unix-timestamp calculator is 946681200 (UTC?) } void TestDatetimeFunctions::testDATE2UNIX() { // 946681200 = 01/01/2001 CHECK_EVAL("DATE2UNIX(DATE(2000;01;01))=946684800", Value(true)); // TODO } void TestDatetimeFunctions::testDATE() { // CHECK_EVAL("DATE(2005;12;31)-DATE(1904;01;01)", Value(37255)); CHECK_EVAL("DATE(2004;02;29)=DATE(2004;02;28)+1", Value(true)); // leap year CHECK_EVAL("DATE(2000;02;29)=DATE(2000;02;28)+1", Value(true)); // leap year CHECK_EVAL("DATE(2005;03;01)=DATE(2005;02;28)+1", Value(true)); // no leap year CHECK_EVAL("DATE(2017.5;01;02)=DATE(2017;01;02)", Value(true)); // fractional values for year are truncated CHECK_EVAL("DATE(2006; 2.5; 3)=DATE(2006; 2; 3)", Value(true)); // fractional values for month are truncated CHECK_EVAL("DATE(2006;01;03.5)=DATE(2006;01;03)", Value(true)); // fractional values for day are truncated CHECK_EVAL("DATE(2006;13;03)=DATE(2007;01;03)", Value(true)); // months > 12 roll over to year CHECK_EVAL("DATE(2006;01;32)=DATE(2006;02;01)", Value(true)); // days greater than month limit roll over to month CHECK_EVAL("DATE(2006;25;34)=DATE(2008;02;03)", Value(true)); // days and months roll over transitively CHECK_EVAL("DATE(2006;-01;01)=DATE(2005;11;01)", Value(true)); // negative months roll year backward CHECK_EVAL("DATE(2006;04;-01)=DATE(2006;03;30)", Value(true)); // negative days roll month backward CHECK_EVAL("DATE(2006;-4;-1)=DATE(2005;07;30)", Value(true)); // negative days and months roll backward transitively CHECK_EVAL("DATE(2003;2;29)=DATE(2003;03;01)", Value(true)); // non-leap year rolls forward } void TestDatetimeFunctions::testDATEVALUE() { // CHECK_EVAL("DATEVALUE(\"2004-12-25\")=DATE(2004;12;25)", Value(true)); } void TestDatetimeFunctions::testDAY() { // CHECK_EVAL("DAY(DATE(2006;05;21))", Value(21)); CHECK_EVAL("DAY(\"2006-12-15\")", Value(15)); } void TestDatetimeFunctions::testDAYS() { // CHECK_EVAL("DAYS(DATE(1993;4;16); DATE(1993;9;25))", Value(-162)); // } void TestDatetimeFunctions::testDAYSINMONTH() { // non leapyear CHECK_EVAL("DAYSINMONTH(1995;01)", Value(31)); CHECK_EVAL("DAYSINMONTH(1995;02)", Value(28)); CHECK_EVAL("DAYSINMONTH(1995;03)", Value(31)); CHECK_EVAL("DAYSINMONTH(1995;04)", Value(30)); CHECK_EVAL("DAYSINMONTH(1995;05)", Value(31)); CHECK_EVAL("DAYSINMONTH(1995;06)", Value(30)); CHECK_EVAL("DAYSINMONTH(1995;07)", Value(31)); CHECK_EVAL("DAYSINMONTH(1995;08)", Value(31)); CHECK_EVAL("DAYSINMONTH(1995;09)", Value(30)); CHECK_EVAL("DAYSINMONTH(1995;10)", Value(31)); CHECK_EVAL("DAYSINMONTH(1995;11)", Value(30)); CHECK_EVAL("DAYSINMONTH(1995;12)", Value(31)); // leapyear CHECK_EVAL("DAYSINMONTH(2000;02)", Value(29)); CHECK_EVAL("DAYSINMONTH(1900;02)", Value(28)); // non leapyear CHECK_EVAL("DAYSINMONTH(2004;02)", Value(29)); // test alternate name for the DAYSINMONTH function CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDAYSINMONTH(1995;01)", Value(31)); // alternate function name } void TestDatetimeFunctions::testDAYSINYEAR() { CHECK_EVAL("DAYSINYEAR(2000)", Value(366)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDAYSINYEAR(2000)", Value(366)); // alternate function name } void TestDatetimeFunctions::testDAYS360() { // TODO Note: Lotus 1-2-3v9.8 has a function named DAYS but with different semantics. It supports an optional "Basis" parameter // with many different options. Without the optional parameter, it defaults to a 30/360 basis, not calendar days; thus, in Lotus 1-2-3v9.8, // DAYS(DATE(1993;4;16); DATE(1993;9;25)) computes -159, not -162. CHECK_EVAL("DAYS360(DATE(1993;4;16);DATE(1993;9;25); FALSE)", Value(159)); // specs. -162 but OOo and KSpread calculate 159 CHECK_EVAL("DAYS360(\"2002-02-22\"; \"2002-04-21\" ; FALSE)", Value(59)); // ref. docs } void TestDatetimeFunctions::testEDATE() { // CHECK_EVAL("EDATE(\"2006-01-01\";0) =DATE(2006;01;01)", Value(true)); // If zero, unchanged. CHECK_EVAL("EDATE(DATE(2006;01;01);0)=DATE(2006;01;01)", Value(true)); // You can pass strings or serial numbers to EDATE CHECK_EVAL("EDATE(\"2006-01-01\"; 2) =DATE(2006;03;01)", Value(true)); // CHECK_EVAL("EDATE(\"2006-01-01\";-2) =DATE(2005;11;01)", Value(true)); // 2006 is not a leap year. Last day of March, going back to February CHECK_EVAL("EDATE(\"2000-04-30\";-2) =DATE(2000; 2;29)", Value(true)); // TODO 2000 was a leap year, so the end of February is the 29th CHECK_EVAL("EDATE(\"2000-04-05\";24 )=DATE(2002;04;05)", Value(true)); // EDATE isn't limited to 12 months CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETEDATE(\"2006-01-01\";0) =DATE(2006;01;01)", Value(true)); // alternate function name } void TestDatetimeFunctions::testEOMONTH() { // CHECK_EVAL("EOMONTH(\"2006-01-01\";0) =DATE(2006;01;31)", Value(true)); // If zero, unchanged V just returns // end of that date's month. (January in this case) CHECK_EVAL("EOMONTH(DATE(2006;01;01);0)=DATE(2006;01;31)", Value(true)); // You can pass strings or serial numbers to EOMONTH CHECK_EVAL("EOMONTH(\"2006-01-01\";2) =DATE(2006;03;31)", Value(true)); // End of month of March is March 31. CHECK_EVAL("EOMONTH(\"2006-01-01\";-2) =DATE(2005;11;30)", Value(true)); // Nov. 30 is the last day of November CHECK_EVAL("EOMONTH(\"2006-03-31\";-1) =DATE(2006;02;28)", Value(true)); // 2006 is not a leap year. Last day of February is Feb. 28. CHECK_EVAL("EOMONTH(\"2000-04-30\";-2) =DATE(2000;02;29)", Value(true)); // 2000 was a leap year, so the end of February is the 29th CHECK_EVAL("EOMONTH(\"2000-04-05\";24) =DATE(2002;04;30)", Value(true)); // Not limited to 12 months, and this tests April CHECK_EVAL("EOMONTH(\"2006-01-05\";04) =DATE(2002;05;31)", Value(false)); // End of May is May 31 CHECK_EVAL("EOMONTH(\"2006-01-05\";05) =DATE(2002;06;30)", Value(false)); // June 30 CHECK_EVAL("EOMONTH(\"2006-01-05\";06) =DATE(2002;07;31)", Value(false)); // July 31 CHECK_EVAL("EOMONTH(\"2006-01-05\";07) =DATE(2002;08;31)", Value(false)); // August 31 CHECK_EVAL("EOMONTH(\"2006-01-05\";08) =DATE(2002;09;30)", Value(false)); // Sep 30 CHECK_EVAL("EOMONTH(\"2006-01-05\";09) =DATE(2002;10;31)", Value(false)); // Oct 31 CHECK_EVAL("EOMONTH(\"2006-01-05\";10) =DATE(2002;11;30)", Value(false)); // Nov. 30 CHECK_EVAL("EOMONTH(\"2006-01-05\";11) =DATE(2002;12;31)", Value(false)); // Dec. 31 CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.ANALYSIS.GETEOMONTH(\"2006-01-01\";0) =DATE(2006;01;31)", Value(true)); // alternate function name } void TestDatetimeFunctions::testHOUR() { - // + // Hacky way to test for 12h clock + KLocale locale; + bool twelveHourClock = locale.timeFormat().contains("%I"); + CHECK_EVAL("HOUR(5/24)", Value(5)); // 5/24ths of a day is 5 hours, aka 5AM. CHECK_EVAL("HOUR(5/24-1/(24*60*60))", Value(4)); // A second before 5AM, it's 4AM. - CHECK_EVAL("HOUR(\"14:00\")", Value(14)); // TimeParam accepts text + // TimeParam accepts text CHECK_EVAL("HOUR(\"9:00\")", Value(9)); CHECK_EVAL("HOUR(\"09:00\")", Value(9)); - CHECK_EVAL("HOUR(\"23:00\")", Value(23)); CHECK_EVAL("HOUR(\"11:00 PM\")", Value(23)); CHECK_EVAL("HOUR(\"11:00 AM\")", Value(11)); + + // These are locale dependent + if (twelveHourClock) { + CHECK_FAIL("HOUR(\"14:00\")", Value(14), "12h clock, hour must be <= 12"); + CHECK_FAIL("HOUR(\"23:00\")", Value(23), "12h clock, hour must be <= 12"); + } else { + CHECK_EVAL("HOUR(\"14:00\")", Value(14)); + CHECK_EVAL("HOUR(\"23:00\")", Value(23)); + } + } void TestDatetimeFunctions::testISOWEEKNUM() { // ODF-tests CHECK_EVAL("ISOWEEKNUM(DATE(1995;1;1);1)", Value(1)); // January 1, 1995 was a Sunday CHECK_EVAL("ISOWEEKNUM(DATE(1995;1;1);2)", Value(52)); // January 1, 1995 was a Sunday, so if Monday is the beginning of the week, // then it's week 52 of the previous year CHECK_EVAL("ISOWEEKNUM(DATE(1995;1;1))", Value(52)); // Default is Monday is beginning of week (per ISO) CHECK_EVAL("ISOWEEKNUM(DATE(2000;5;21))", Value(20)); // ref OOo-2.2.0 CHECK_EVAL("ISOWEEKNUM(DATE(2000;5;21);1)", Value(21)); // ref OOo-2.2.0 CHECK_EVAL("ISOWEEKNUM(DATE(2000;5;21);2)", Value(20)); // ref OOo-2.2.0 CHECK_EVAL("ISOWEEKNUM(DATE(2005;1;1))", Value(53)); // ref OOo-2.2.0 CHECK_EVAL("ISOWEEKNUM(DATE(2005;1;2))", Value(53)); // ref OOo-2.2.0 CHECK_EVAL("ISOWEEKNUM(DATE(2006;1;1))", Value(52)); // ref OOo-2.2.0 // method 2 - week begins on sunday CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;01);2)", Value(52)); // January 1, 1995 was a Sunday CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;02);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;03);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;04);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;05);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;06);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;07);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;08);2)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;09);2)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;10);2)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;11);2)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;12);2)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;13);2)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;14);2)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;15);2)", Value(2)); // // method 1 - week begins on monday CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;01);1)", Value(1)); // January 1, 1995 was a Sunday CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;02);1)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;03);1)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;04);1)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;05);1)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;06);1)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;07);1)", Value(1)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;08);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;09);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;10);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;11);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;12);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;13);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;14);1)", Value(2)); // CHECK_EVAL("ISOWEEKNUM(DATE(1995;01;15);1)", Value(3)); // } void TestDatetimeFunctions::testMINUTE() { // CHECK_EVAL("MINUTE(1/(24*60))", Value(1)); // 1 minute is 1/(24*60) of a day. CHECK_EVAL("MINUTE(TODAY()+1/(24*60))", Value(1)); // If you start with today, and add a minute, you get a minute. CHECK_EVAL("MINUTE(1/24)", Value(0)); // At the beginning of the hour, we have 0 minutes. } void TestDatetimeFunctions::testMONTH() { // CHECK_EVAL("MONTH(DATE(2006;5;21))", Value(5)); // Month extraction from DATE() value } void TestDatetimeFunctions::testMONTHS() { CHECK_EVAL("MONTHS(\"2002-01-18\"; \"2002-02-26\"; 0)", Value(1)); CHECK_EVAL("MONTHS(\"2002-01-19\"; \"2002-02-26\"; 1)", Value(0)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFMONTHS(\"2002-01-18\"; \"2002-02-26\"; 0)", Value(1)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFMONTHS(\"2002-01-19\"; \"2002-02-26\"; 1)", Value(0)); } void TestDatetimeFunctions::testNOW() { // CHECK_EVAL("NOW()>DATE(2006;1;3)", Value(true)); // NOW constantly changes, but we know it's beyond this date. CHECK_EVAL("INT(NOW())=TODAY()", Value(true)); } void TestDatetimeFunctions::testSECOND() { // CHECK_EVAL("SECOND(1/(24*60*60))", Value(1)); // This is one second into today. CHECK_EVAL("SECOND(1/(24*60*60*2))", Value(1)); // Rounds. CHECK_EVAL("SECOND(1/(24*60*60*4))", Value(0)); // Rounds. } void TestDatetimeFunctions::testTIME() { // CHECK_EVAL("TIME(0;0;0)", Value(0)); // All zero arguments becomes midnight, 12:00:00 AM. CHECK_EVAL("TIME(23;59;59)*60*60*24", Value(86399)); // This is 11:59:59 PM. CHECK_EVAL("TIME(11;125;144)*60*60*24", Value(47244)); // Seconds and minutes roll over transitively; this is 1:07:24 PM. CHECK_EVAL("TIME(11;0; -117)*60*60*24", Value(39483)); // Negative seconds roll minutes backwards, 10:58:03 AM CHECK_EVAL("TIME(11;-117;0)*60*60*24", Value(32580)); // Negative minutes roll hours backwards, 9:03:00 AM CHECK_EVAL("TIME(11;-125;-144)*60*60*24", Value(31956)); // Negative seconds and minutes roll backwards transitively, 8:52:36 AM // WARNING specs says -31956, but calc and kspread calculate 31956 } void TestDatetimeFunctions::testTIMEVALUE() { - // + // Hacky way to test for 12h clock + KLocale locale; + bool twelveHourClock = locale.timeFormat().contains("%I"); + CHECK_EVAL("TIMEVALUE(\"06:05\") =TIME(6;5;0)", Value(true)); - CHECK_EVAL("TIMEVALUE(\"06:05:07\")=TIME(6;5;7)", Value(true)); + + // When 12h clock, AM/PM is mandatory + if (twelveHourClock) { + CHECK_FAIL("TIMEVALUE(\"06:05:07\")=TIME(6;5;7)", Value(true), "12h clock, AM/PM is mandatory"); + CHECK_EVAL("TIMEVALUE(\"06:05:07 am\")=TIME(6;5;7)", Value(true)); + } else { + CHECK_EVAL("TIMEVALUE(\"06:05:07\")=TIME(6;5;7)", Value(true)); + } } void TestDatetimeFunctions::testTODAY() { // CHECK_EVAL("TODAY()>DATE(2006;1;3)", Value(true)); // Every date TODAY() changes, but we know it's beyond this date. CHECK_EVAL("INT(TODAY())=TODAY()", Value(true)); } void TestDatetimeFunctions::testWEEKDAY() { // | type 1 | type 2 | type 3 // ---+--------+---------+-------- // 01 | SU | MO | TU // 02 | MO | TU | WE // 03 | TU | WE | TH // 04 | WE | TH | FR // 05 | TH | FR | SA // 06 | FR | SA | SU // 07 | SA | SU | MO CHECK_EVAL("WEEKDAY(DATE(2006;05;21))", Value(1)); // Year-month-date format CHECK_EVAL("WEEKDAY(DATE(2005;01;01))", Value(7)); // Saturday CHECK_EVAL("WEEKDAY(DATE(2005;01;01);1)", Value(7)); // Saturday CHECK_EVAL("WEEKDAY(DATE(2005;01;01);2)", Value(6)); // Saturday CHECK_EVAL("WEEKDAY(DATE(2005;01;01);3)", Value(5)); // Saturday } void TestDatetimeFunctions::testYEAR() { CHECK_EVAL("YEAR(DATE(1904;1;1))", Value(1904)); CHECK_EVAL("YEAR(DATE(2004;1;1))", Value(2004)); } void TestDatetimeFunctions::testYEARS() { CHECK_EVAL("YEARS(\"2001-02-19\"; \"2002-02-26\"; 0)", Value(1)); CHECK_EVAL("YEARS(\"2002-02-19\"; \"2002-02-26\"; 1)", Value(0)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFYEARS(\"2001-02-19\";\"2002-02-26\";0)", Value(1)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFYEARS(\"2002-02-19\";\"2002-02-26\";1)", Value(0)); } void TestDatetimeFunctions::testWEEKS() { CHECK_EVAL("WEEKS(\"2002-02-18\"; \"2002-02-26\"; 0)", Value(1)); CHECK_EVAL("COM.SUN.STAR.SHEET.ADDIN.DATEFUNCTIONS.GETDIFFWEEKS(\"2002-02-18\"; \"2002-02-26\"; 0)", Value(1)); } QTEST_MAIN(TestDatetimeFunctions) diff --git a/stage/app/org.kde.calligrastage.appdata.xml b/stage/app/org.kde.calligrastage.appdata.xml index ca3b877e497..4ca9c2710fc 100644 --- a/stage/app/org.kde.calligrastage.appdata.xml +++ b/stage/app/org.kde.calligrastage.appdata.xml @@ -1,199 +1,200 @@ org.kde.calligrastage.desktop CC0-1.0 GPL-2.0+ Calligra Stage Calligra Stage Stage del Calligra Stage del Calligra Calligra Stage Calligra Stage Calligra Stage Calligra Stage Calligra Stage + Calligra Stage Calligra Stage Calligra Stage Scena Calligra Calligra Stage Calligra Stage Calligra Stage Calligra Stage Calligra Stage xxCalligra Stagexx Calligra Stage Presentation Prezentacija Presentació Presentació Prezentace Präsentation Παρουσίαση Presentation Presentación Esitlus Esitysgrafiikka Présentation Presentación Presentation Presentazioni Presentatie Prezentacja Apresentação Apresentação Prezentácia Presentation Презентації xxPresentationxx 演示

Calligra Stage is an easy to use yet still flexible presentation application. You can easily create presentations containing a rich variety of elements, from graphics to text, from charts to images. Calligra Stage is extensible through a plugin system, so it is easy to add new effects, new content elements or even new ways of managing your presentation. Because of the integration with Calligra, all the power and flexibility of the Calligra content elements are available to Stage.

Calligra Stage je lagana ali fleksibilna prezentacijska aplikacija. Možete lako kreirati prezentacije s dosta elemenata, iz grafike i teksta, dijagrama i slika. Calligra Stage se može proširivati kroz sistem dodataka, tako da je lako dodati nove efekte, nove sadržajne elemente ili čak nove načine upravljanja prezentacijom. Zbog integracije sa Calligra, sva moć i fleksibilnost od Calligra sadržaja elemenata su dostupni u Stage.

L'Stage del Calligra és una aplicació de presentació flexible i fàcil d'utilitzar. Podeu crear presentacions fàcilment que continguin una gran varietat d'elements, des de gràfics a text, des de diagrames a imatges. L'Stage del Calligra és ampliable mitjançant un sistema de connectors, de manera que és senzill afegir efectes nous, elements de contingut nous o inclús noves maneres de gestionar les presentacions. Gràcies a la integració amb el Calligra, tota la potència i la flexibilitat dels elements de contingut del Calligra són disponibles per l'Stage.

L'Stage del Calligra és una aplicació de presentació flexible i fàcil d'utilitzar. Podeu crear presentacions fàcilment que continguen una gran varietat d'elements, des de gràfics a text, des de diagrames a imatges. L'Stage del Calligra és ampliable mitjançant un sistema de connectors, de manera que és senzill afegir efectes nous, elements de contingut nous o inclús noves maneres de gestionar les presentacions. Gràcies a la integració amb el Calligra, tota la potència i la flexibilitat dels elements de contingut del Calligra són disponibles per l'Stage.

Το Calligra Stage είναι μια ευκολόχρηστη αλλά και ευέλικτη εφαρμογή παρουσιάσεων. Μπορείτε εύκολα να δημιουργήσετε παρουσιάσεις που να περιέχουν μια πλούσια ποικιλία αντικειμένων, από γραφικά σε κείμενο,, από διαγράμματα σε εικόνες. Το Calligra Stage είναι επεκτάσιμο μέσα από ένα σύστημα προσθέτων, έτσι είναι εύκολο να προστεθούν νέα εφέ, νέα αντικείμενα περιεχομένου ή ακόμη και νέοι τρόποι διαχείρισης παρουσιάσεων. Λόγω της ενσωμάτωσης στο Calligra, όλη η ισχύς και η ευελιξία των αντικειμένων περιεχομένου του Calligra είναι διαθέσιμη στο Stage.

Calligra Stage is an easy to use yet still flexible presentation application. You can easily create presentations containing a rich variety of elements, from graphics to text, from charts to images. Calligra Stage is extensible through a plugin system, so it is easy to add new effects, new content elements or even new ways of managing your presentation. Because of the integration with Calligra, all the power and flexibility of the Calligra content elements are available to Stage.

Calligra Stage es un aplicación para presentaciones, fácil de utilizar y flexible. Permite crear fácilmente presentaciones que contengan una amplia variedad de elementos, desde gráficos a texto, desde diagramas a imágenes. Calligra Stage es extensible a través de un sistema de complementos, por lo que es fácil añadir nuevos efectos, nuevos elementos de contenido e incluso nuevas maneras de gestionar las presentaciones. Debido a su integración con Calligra, toda la potencia y flexibilidad de los elementos de contenido de Calligra están disponibles para Stage.

Calligra Stage on hõlpsasti kasutatav, aga väga paindlik esitluste rakendus. Selle abil saab vähese vaevaga luua rikkalikult elemente sisaldavaid esitlusi, milles leidub kõike alates graafikast tekstini ja diagrammidest piltideni. Calligra Stage'i saab laiendada pluginate abil, mis võimaldab lihtsalt lisada uusi efekte, uusi sisuelemente või isegi päris uusi viise esitlusi hallata. Calligraga lõimimise tõttu saab Stage paindlikult ära kasutada kõiki Calligra sisuelementide võimalusi.

Calligra Stage on helppokäyttöinen mutta silti joustava esityssovellus. Voit luoda esityksiä, jotka sisältävät monenlaisia elementtejä grafiikasta tekstiin ja kaavioista valokuviin. Calligra Stagea voi laajentaa liitännäisin, joten uusia tehosteita, uusia sisältöelementtejä tai jopa uusia esityksesi hallinnan tapoja on helppo lisätä. Calligra-integroinnin ansiosta Calligran sisältöelementtien teho ja joustavuus ovat Stagenkin käytössä.

Calligra Stage est une application de présentation à la fois flexible et facile à utiliser. Vous pouvez facilement créer des présentations comprenant une grande variété d'éléments, allant des graphiques au texte, en passant par les diagrammes et les images. Calligra Stage propose un système d'extensions ; il est facile d'ajouter de nouveaux effets, des nouveaux éléments de contenu ou même de nouvelles façons de gérer votre présentation. Du fait de l'intégration avec Calligra, toute la puissance et la flexibilité des contenu de Calligra sont disponibles dans Stage.

Calligra Stage é un aplicativo de presentacións fácil de usar pero flexíbel. Permítelle crear presentacións cunha ampla variedade de elementos, incluíndo texto, gráficos e imaxes. As funcionalidades de Calligra Stage poden ampliarse mediante un sistema de complementos, así que resulta doado engadir efectos e elementos novos, así como novos xeitos de xestionar as presentacións. Grazas á súa integración con Calligra, Stage pode facer uso da potencia e flexibilidade dos elementos de contido dispoñíbeis en Calligra.

Calligra Stage è un'applicazione per presentazioni tanto facile da utilizzare quanto è flessibile. Puoi creare facilmente presentazioni che contengono un'ampia varietà di elementi, dalla grafica al testo, dai grafici alle immagini. Calligra Stage è espandibile tramite un sistema di estensioni, per cui è semplice aggiungere nuovi effetti o anche nuovi modi di gestire le tue presentazioni. Grazie all'integrazione con Calligra, tutta la potenza e la flessibilità degli elementi di Calligra sono disponibili in Stage.

Calligra Stage is een gemakkelijk te gebruiken maar toch flexibele toepassing voor presentaties. U kunt gemakkelijk presentaties maken met een rijke variatie van elementen, van grafische tot tekst, van grafieken tot afbeeldingen. Calligra Stage is uit te breiden met een systeem van plug-ins, zodat het gemakkelijk is om nieuwe effecten toe te voegen, nieuwe soorten inhoud of zelfs nieuwe manieren van het beheren van uw presentatie. Vanwege de integratie met Calligra, is alle kracht en flexibiliteit van de inhoudselementen van Calligra beschikbaar in Stage.

Scena Calligra jest łatwym w użyciu, a zarazem elastycznym programem do prezentowania. Można łatwo tworzyć prezentację zawierające bogatą różnorodność elementów, od grafiki do tekstu, od wykresów do obrazów. Scena Calligra jest rozszerzalna przez system wtyczek, tak że można łatwo dodać nowe efekty, nowe elementy lub nawet nowe sposoby na zarządzanie prezentacją. Ze względów na powiązanie z Calligrą, cała moc i elastyczność elementów Calligry jest dostępna również dla Sceny.

O Calligra Stage é uma aplicação de apresentações simples de usar, mas também bastante flexível. Poderá criar facilmente apresentações que contêm uma grande variedade de elementos, desde gráficos a texto, assim como de gráficos a imagens. O Calligra Stage é modular através de um sistema de 'plugins', pelo que é fácil adicionar novos efeitos, elementos de conteúdo ou até novas formas de gerir a sua apresentação. Devido à integração com o Calligra, todo o poder e flexibilidade dos elementos de conteúdo do Calligra estão disponíveis no Stage.

Calligra Stage é um aplicativo de apresentações fácil de usar, mas também bastante flexível. Você pode criar facilmente apresentações que contenham uma grande variedade de elementos, desde gráficos a texto, assim como de gráficos a imagens. O Calligra Stage é expansível através de um sistema de plugins, permitindo adicionar facilmente novos efeitos, elementos de conteúdo ou até novas formas de gerenciar sua apresentação. Devido à integração com o Calligra, todo o poder e flexibilidade dos elementos de conteúdo do Calligra também estão disponíveis no Stage.

Calligra Stage je jednoducho použiteľná a flexibilná aplikácia na prezentácie. Môžete jednoducho vytvárať prezentácie obsahujúce bohatý výber prvkov, od grafických po textové, od grafov po obrázky. Calligra Stage je rozšíriteľná cez systém pluginov, takže je jednoduché pridať nové efekty, nové obsahové prvky alebo dokonca nové spôsoby správy vašej prezentácie. Vďaka integrácii so systémom Calligra je k dispozícii celá sila a flexibilita obsahových prvkov Calligra.

Calligra Stage är ett lättanvänt men ändå flexibelt presentationsprogram. Du kan enkelt skapa presentationer som innehåller en rik variation av element, från grafik till text, från diagram till bilder. Calligra Stage är utökningsbar via ett insticksprogramsystem, så det är lätt att lägga till nya effekter, nya innehållselement eller till och med nya sätt att hantera presentationer. På grund av integration med Calligra är de kraftfulla och flexibla innehållselementen i Calligra tillgängliga för Stage.

Calligra Stage — проста, але потужна програма для роботи з презентаціями. За її допомогою ви без проблем зможете створити презентацію з багатим набором елементів, від графіки до тексту, від діаграм до зображень. Можливості Calligra Stage можна розширити за допомогою системи додатків: ви можете додати нові ефекти, нові елементи вмісту і навіть нові способи керування презентацією. Через тісну інтеграцію з рештою програм Calligra ви можете скористатися усіма потужними та гнучкими елементами решти програм Calligra безпосередньо у Stage.

xxCalligra Stage is an easy to use yet still flexible presentation application. You can easily create presentations containing a rich variety of elements, from graphics to text, from charts to images. Calligra Stage is extensible through a plugin system, so it is easy to add new effects, new content elements or even new ways of managing your presentation. Because of the integration with Calligra, all the power and flexibility of the Calligra content elements are available to Stage.xx

Features:

Svojstva:

Característiques:

Característiques:

Vlastnosti:

Funktionen:

Χαρακτηριστικά:

Features:

Características:

Omadused:

Ominaisuuksia:

Fonctionnalités :

Funcionalidades:

Characteristicas:

Funzionalità:

Mogelijkheden:

Cechy:

Funcionalidades:

Funcionalidades:

Funkcie:

Funktioner:

Можливості:

xxFeatures:xx

功能:

  • Multiple master slides
  • Više master slajdova
  • Múltiples diapositives mestres
  • Múltiples diapositives mestres
  • Mehrere Folienmaster
  • Πολλαπλές κύριες διαφάνειες
  • Multiple master slides
  • Múltiples diapositivas maestras
  • Mitme juhtslaidi kasutamise võimalus
  • Useammat pohjadiat
  • Planches-maîtresses multiples
  • Varias diapositivas mestre.
  • Diapositive master multiple
  • Meerdere voorbeelddia's
  • Wiele głównych plansz
  • Diversos 'slides'-mestres
  • Vários slides-mestres
  • Viacero hlavných snímok
  • Flera förlagssidor
  • Використання декількох основних слайдів.
  • xxMultiple master slidesxx
  • Easily switch between slides during a presentation
  • Lako prebacujte između slajdova tokom prezentacije
  • Canvi senzill entre dispositives durant una presentació
  • Canvi senzill entre dispositives durant una presentació
  • Einfacher Wechsel zwischen Folien während einer Präsentation
  • Εύκολη εναλλαγή ανάμεσα σε διαφάνειες κατά τη διάρκεια μιας παραουσίασης
  • Easily switch between slides during a presentation
  • Permite cambiar fácilmente entre diapositivas durante una presentación
  • Hõlpus lülitumine slaidide vahel esitluse ajal
  • Helppo siirtyminen diasta toiseen esityksen aikana
  • Passage facile d'une planche à une autre pendant une présentation
  • Permite cambiar facilmente entre diapositivas durante as presentacións.
  • Cambia facilmente le diapositive durante una presentazione
  • Schakel gemakkelijk tussen dia's tijdens een presentatie
  • Łatwe przełączanie pomiędzy planszami przy prezentowaniu
  • Mudar facilmente de 'slides' durante uma apresentação
  • Troca facilmente de slides durante uma apresentação
  • Jednoducho prepnúť medzi snímkami počas prezentácie
  • Enkelt byte mellan bilder under presentationer
  • Просте перемикання між слайдами під час презентації.
  • xxEasily switch between slides during a presentationxx
  • Add notes for your slide
  • Dodajte zabilješke za vaš slajd
  • Permet afegir notes a les dispositives
  • Permet afegir notes a les dispositives
  • Přidejte poznámky ke snímku
  • Hinzufügen von Notizen zu Folien
  • Προσθήκη σημειώσεων για τη διαφάνειά σας
  • Add notes for your slide
  • Permite añadir notas a una diapositiva
  • Märkmete lisamine slaididele
  • Muistiinpanojen lisäys dioihisi
  • Ajoutez des notes à vos planches
  • Permite engadir notas ás presentacións.
  • Aggiungi note alle tue diapositive
  • Voeg notities toe aan uw dia
  • Dodawanie notatek do plansz
  • Adicionar notas ao seu 'slide'
  • Adiciona notas ao seu slide
  • Pridať poznámky pre vašu snímku
  • Lägg till anteckningar för bilder
  • Додавання нотаток до слайдів.
  • xxAdd notes for your slidexx
  • Supports most ODF file transitions
  • Podržava većinu tranzicija ODF datoteka
  • Accepta la majoria de les transicions de fitxer ODF
  • Accepta la majoria de les transicions de fitxer ODF
  • Unterstützt die meisten ODF-Dateiübergänge
  • Υποστηρίζει τις περισσότερες μεταβάσεις αρχείων ODF
  • Supports most ODF file transitions
  • Implementa la mayoría de las transiciones de archivo de ODF
  • Enamiku ODF-vormingu failiüleminekute toetamine
  • Useimpien ODF-siirtymien tuki
  • Prend en charge la plupart des transitions ODF
  • Compatíbel coa meirande parte das transicións dos ficheiros ODF.
  • Supporta la maggior parte delle transizioni dei file ODF
  • Ondersteunt de meeste ODF-bestandstransities
  • Obsługa większości przejść plików ODF
  • Suporta a maioria das transições nos ficheiros ODF
  • Suporte à maioria das transições em arquivos ODF
  • Podporuje väčšinu ODF súborových prechodov
  • Stöder de flesta ODF-filövergångar
  • Підтримка більшості переходів у файлах ODF.
  • xxSupports most ODF file transitionsxx
http://www.calligra.org/stage/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=calligrastage http://docs.kde.org/development/en/calligra/stage/index.html http://kde.org/images/screenshots/stage.png KDE calligrastage
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1d9b24fbf6c..292fc84e689 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,49 +1,55 @@ find_program(BASH_PROGRAM bash) find_program(XGETTEXT_PROGRAM xgettext) find_program(MSGCAT_PROGRAM msgcat) if (BASH_PROGRAM AND XGETTEXT_PROGRAM AND MSGCAT_PROGRAM) - macro(CALLIGRA_ADD_UNIT_TEST _TEST_NAME) - add_custom_target(${_TEST_NAME} ALL - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${_TEST_NAME}.cpp" "${CMAKE_CURRENT_BINARY_DIR}/${_TEST_NAME}.cpp" - COMMENT "Copy test files" VERBATIM - ) - add_test(${_TEST_NAME} ${BASH_PROGRAM} "${CMAKE_CURRENT_SOURCE_DIR}/${_TEST_NAME}.sh" + macro(CALLIGRA_ADD_TEST_COMMAND _TEST_NAME) + add_test(${_TEST_NAME} ${BASH_PROGRAM} "${CMAKE_CURRENT_SOURCE_DIR}/${_TEST_NAME}.sh" "${CMAKE_SOURCE_DIR}/kundo2_aware_xgettext.sh" "${XGETTEXT_PROGRAM}" "${MSGCAT_PROGRAM}" "${CMAKE_CURRENT_BINARY_DIR}/po" ) endmacro() + macro(CALLIGRA_ADD_UNIT_TEST _TEST_NAME) + add_custom_target(${_TEST_NAME} ALL + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${_TEST_NAME}.cpp" "${CMAKE_CURRENT_BINARY_DIR}/${_TEST_NAME}.cpp" + COMMENT "Copy test files" VERBATIM + ) + CALLIGRA_ADD_TEST_COMMAND(${_TEST_NAME}) + endmacro() # create the podir add_custom_target(podir ALL COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/po COMMENT "Create podir" VERBATIM) # copy parameters script add_custom_target(parameters ALL COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/parameters.sh ${CMAKE_CURRENT_BINARY_DIR}/parameters.sh COMMENT "Copy parameters script" VERBATIM) # add tests here CALLIGRA_ADD_UNIT_TEST(test_i18n) CALLIGRA_ADD_UNIT_TEST(test_i18nc) CALLIGRA_ADD_UNIT_TEST(test_i18np) CALLIGRA_ADD_UNIT_TEST(test_i18nc_long) CALLIGRA_ADD_UNIT_TEST(test_i18n_noop) CALLIGRA_ADD_UNIT_TEST(test_i18n_noop2) CALLIGRA_ADD_UNIT_TEST(test_xi18n) CALLIGRA_ADD_UNIT_TEST(test_xi18nc) CALLIGRA_ADD_UNIT_TEST(test_xi18nc_long) CALLIGRA_ADD_UNIT_TEST(test_xi18np) CALLIGRA_ADD_UNIT_TEST(test_xi18ncp) CALLIGRA_ADD_UNIT_TEST(test_kundo2_i18n) CALLIGRA_ADD_UNIT_TEST(test_kundo2_i18nc) CALLIGRA_ADD_UNIT_TEST(test_kundo2_i18nc_long) + # this uses cpp files from earlier tests + CALLIGRA_ADD_TEST_COMMAND(test_i18n_mixed) + # add test of Messages.sh add_test(test_Messages ${BASH_PROGRAM} "${CMAKE_CURRENT_SOURCE_DIR}/test_messages.sh" "${CMAKE_SOURCE_DIR}") endif() diff --git a/tests/test_i18n_mixed.sh b/tests/test_i18n_mixed.sh new file mode 100644 index 00000000000..27ffb86a926 --- /dev/null +++ b/tests/test_i18n_mixed.sh @@ -0,0 +1,40 @@ +#! /bin/sh +# test_i18n_mixed: +# $1: kundo2_aware_xgettext.sh +# $2: xgettext +# $3: msgcat +# $4: podir +echo "$@" +# source the kundo2_aware_xgettext.sh script +. "$1" + +# setup environment variables for kundo2_aware_xgettext.sh +XGETTEXT_PROGRAM="$2" +MSGCAT="$3" +podir="$4" + +# get common parameters +. parameters.sh +# kundo2_aware_xgettext.sh wants this in one variable +XGETTEXT="$XGETTEXT_PROGRAM $XGETTEXT_FLAGS" + +potfile="test_i18n_mixed.pot" +cppfiles="test_i18nc.cpp test_kundo2_i18nc.cpp" + +kundo2_aware_xgettext "$potfile" "$cppfiles" + +# check result +if test ! -e "$podir/$potfile"; then + echo "FAIL: pot file not created" + exit 1 +fi +if test 1 -ne `grep qtundo-format "$podir/$potfile"|wc -l`; then + echo "FAIL: there should be 1 qtundo-format strings" + exit 2 +fi +if test 3 -ne `grep msgid "$podir/$potfile"|wc -l`; then + echo "FAIL: there should be 3 message strings" + exit 3 +fi + +exit 0 diff --git a/words/app/org.kde.calligrawords.appdata.xml b/words/app/org.kde.calligrawords.appdata.xml index 9bce172c6e7..fb8215a1137 100644 --- a/words/app/org.kde.calligrawords.appdata.xml +++ b/words/app/org.kde.calligrawords.appdata.xml @@ -1,225 +1,226 @@ org.kde.calligrawords.desktop CC0-1.0 GPL-2.0+ Calligra Words Calligra Words Words del Calligra Words del Calligra Calligra Words Calligra Words Calligra Words Calligra Words + Calligra Words Calligra Words Calligra Words Słowa Calligra Calligra Words Calligra Words Calligra Words Calligra Words Calligra Words xxCalligra Wordsxx Calligra Words Word Processor Program za obradu teksta Processador de textos Processador de textos Textový procesor Textverarbeitung Word Processor Procesador de texto Tekstitöötlus Tekstinkäsittely Traitement de texte Procesador de texto Processator de parolas Elaboratore di testi ワードプロセッサ Tekstbehandler Tekstverwerker Edytor tekstu Processador de Texto Processador de Texto Textový procesor Ordbehandlare Текстовий процесор xxWord Processorxx 文字处理器

Words is an intuitive word processor and desktop publisher application. With it, you can create informative and attractive documents with pleasure and ease.

Words je intuitivan program za obradu teksta i desktop aplikacija stonog izdavaštva. Uz to, možete kreirati informativne i atraktivne dokumente sa zadovoljstvom i lakoćom.

El Words és una aplicació intuïtiva de procés de textos i de publicació d'escriptori. Amb ella es poden crear documents informatius i atractius amb satisfacció i facilitat.

El Words és una aplicació intuïtiva de procés de textos i de publicació d'escriptori. Amb ella es poden crear documents informatius i atractius amb satisfacció i facilitat.

Words is an intuitive word processor and desktop publisher application. With it, you can create informative and attractive documents with pleasure and ease.

Words es un procesador de texto intuitivo y un editor para el escritorio. Con él, podrá crear documentos informativos y atractivos con gusto y facilidad.

Words on intuitiivselt kasutatav tekstitöötlus- ja küljendusrakendus. Selle abil saab luua lusti ja rõõmuga teabeküllaseid ja silmailu pakkuvaid dokumente.

Words on intuitiivinen tekstinkäsittely- ja taitto-ohjelma. Sen avulla voit luoda informatiivisia ja vaikuttavia tekstitiedostoja mukavasti ja helposti.

Words est un traitement de texte intuitif et une application bureautique de publication. Avec lui, vous pouvez facilement créer des documents informatifs et attractifs.

Words é un aplicativo intuitivo de procesamento de texto e publicación de escritorio. Con el pode crear documentos informativos e atractivos plácida e facilmente.

Words è un elaboratore di testi intuitivo e un'applicazione di pubblicazione per desktop. Con esso, puoi creare documenti informativi e accattivanti con soddisfazione e facilità.

Words はワードプロセッサおよびデスクトップ出版のためのアプリケーションです。直感的な操作で有益かつ魅力的な文書が簡単に作成できます

Words er en intuitiv tekstbehandler, som kan brrukes til skrivebordspublisering. Du kan lett lage informative og attraktive dokumenter med glede.

Words is een intuïtieve toepassing voor tekstverwerken en weergeven op het bureaublad. Hiermee kunt u informatieve en attractieve documenten met plezier en gemak maken.

Słowa jest intuicyjnym edytorem tekstu i biurkowym programem do publikowania. Dzięki niemu można tworzyć informacyjne i atrakcyjne dokumenty z łatwością użycia.

O Words é um processador de texto intuitivo e uma aplicação de publicações no ecrã. Com ele, pode criar documentos informativos e atraentes com prazer e facilidade.

Words é um processador de texto intuitivo e um aplicativo de publicação no desktop. Com ele, você pode criar documentos informativos e atraentes com prazer e facilidade.

Words je intuitívny textový procesor a aplikácia na desktopové publikovanie. Môžete pomocou neho vytvoriť informatívne a atraktívne dokumenty s radosťou a jednoduchosťou.

Words är en intuitiv ordbehandlare och skrivbordspubliceringsprogram. Det gör att du kan skapa informativa och attraktiva dokument med nöje och enkelhet.

Words — інтуїтивно зрозуміла у користування програма для редагування текстів та приготування матеріалів для друку. За її допомогою ви можете створювати інформативні та візуально красиві документи із задоволенням та без напруження.

xxWords is an intuitive word processor and desktop publisher application. With it, you can create informative and attractive documents with pleasure and ease.xx

Features:

Svojstva:

Característiques:

Característiques:

Vlastnosti:

Funktionen:

Features:

Características:

Omadused:

Ominaisuuksia:

Fonctionnalités :

Funcionalidades:

Characteristicas:

Funzionalità:

機能:

Egenskaper:

Mogelijkheden:

Cechy:

Funcionalidades:

Funcionalidades:

Funkcie:

Funktioner:

Можливості:

xxFeatures:xx

功能:

  • Frame-based editing
  • Uređivanje bazirano na okvirima
  • Edició basada en marcs
  • Edició basada en marcs
  • Editor auf der Grundlage von Rahmen
  • Frame-based editing
  • Edición basada en marcos
  • Paneelipõhine redigeerimine
  • Kehyksiin perustuva muokkaus
  • Édition à base de cadres
  • Edición baseada en marcos.
  • Modifica basata su riquadri
  • フレームベースの編集
  • Ramme-basert redigering
  • Bewerken op basis van frames
  • Edytowanie oparte na ramkach
  • Edição baseada em molduras
  • Edição baseada em molduras
  • Rámcovo založené editovanie
  • Rambaserad redigering
  • Редагування на основі блоків.
  • xxFrame-based editingxx
  • Allows embedding of Documents
  • Dopušta ugradnju dokumenata
  • Permet incrustar documents
  • Permet incrustar documents
  • Ermöglicht das Einbetten von Dokumenten
  • Allows embedding of Documents
  • Permite incrustar documentos
  • Dokumentide lõimimise toetamine
  • Tiedostojen upottamisen tuki
  • Permet les documents intégrés
  • Permite incrustar documentos.
  • Il permitte insertar documentos
  • Consente l'integrazione di documenti
  • 文書への埋め込みを許可
  • Tillater innebygde dokumenter
  • Staat inbedden van documenten toe
  • Umożliwia osadzanie dokumentów
  • Permite a incorporação de documentos
  • Permite a incorporação de documentos
  • Umožňuje vkladanie dokumentov
  • Tillåter att dokument inbäddas
  • Вбудовування об’єктів до документів.
  • xxAllows embedding of Documentsxx
  • Customization of user interface
  • Prilagođenje korisničkog interfejsa
  • Personalització de la interfície d'usuari
  • Personalització de la interfície d'usuari
  • Anpassung der Benutzeroberfläche
  • Customisation of user interface
  • Personalización de la interfaz de usuario
  • Kohandatav kasutajaliides
  • Käyttöliittymä mukautettavissa
  • Personnalisation de l'interface
  • Personalización da interface.
  • Prsonalisation de le Interfacie de usator
  • Personalizzazion e dell'interfaccia utente
  • ユーザインターフェースのカスタマイズ
  • Tilpassing av brukerflaten
  • Aanpassen naar behoefte van het gebruikersinterface
  • Dostosowywanie układu sterowania
  • Personalização da interface do utilizador
  • Personalização da interface do usuário
  • Úprava používateľského rozhrania
  • Anpassning av användargränssnitt
  • Налаштовуваність інтерфейсу користувача.
  • xxCustomization of user interfacexx
  • Includes all features expected from a modern word processor
  • Uključuje sve osobine koje se očekuju od modernog programa za obradu teksta
  • Inclou totes les funcionalitats esperades d'un processador de textos modern
  • Inclou totes les funcionalitats esperades d'un processador de textos modern
  • Enthält alle Funktionen einer modernen Textverarbeitung
  • Includes all features expected from a modern word processor
  • Incluye todas las características que se esperan de un procesador de texto moderno
  • Kõigi tänapäeva tekstitöötlusrakendusele omaste võimaluste pakkumine
  • Kaikki nykyaikaiselta tekstinkäsittelyohjelmalta odotetut ominaisuudet
  • Inclut toutes les fonctionnalités que l'on peut attendre d'un traitement de texte moderne
  • Inclúe todas as funcionalidades que pode esperar dun procesador de textos moderno.
  • Include tutte le funzionalità attese da un elaboratore di testi moderno
  • 現在のワードプロセッサに求められるすべての機能を含む
  • Har alle egenskaper forventet av en moderne tekstbehandler
  • Bevat alle functies die verwacht worden van een moderne tekstverwerker
  • Zawiera wszystkie cechy oczekiwane od nowoczesnego edytora tekstu
  • Inclui todas as funcionalidades esperadas de um processador de texto moderno
  • Inclui todas as funcionalidades esperadas de um processador de texto moderno
  • Obsahuje všetky funkcie očakávané od moderného textového procesora
  • Innehåller alla funktioner som förväntas av en modern ordbehandlare
  • Повний спектр можливостей сучасної програми для редагування тексту.
  • xxIncludes all features expected from a modern word processorxx
  • ODT (Open Document Text) support
  • ODT (Open Document Text) podrška
  • Admet ODT (Open Document Text)
  • Admet ODT (Open Document Text)
  • Unterstützung für ODT (Open Document Text)
  • ODT (Open Document Text) support
  • Admite el formato ODT («Open Document Text»)
  • ODT (OpenDocument Text) toetamine
  • ODT- eli OpenDocument-tekstitiedostojen tuki
  • Prise en charge du format ODT (Open Document Text)
  • Compatíbel co formato ODT (Open Document Text).
  • Supporto ODT (Open Document Text)
  • ODT (Open Document Text) サポート
  • Støtte for ODT (Open Document Text)
  • Ondersteuning van ODT (Open Document Text)
  • Obsługa ODT (Open Document Text)
  • Suporte para o ODT (Texto do Open Document)
  • Suporte para ODT (Texto Open Document)
  • Podpora ODT (Open Document Text)
  • Stöd för ODT (Open Document Text)
  • Підтримка стандартів ODT (Open Document Text).
  • xxODT (Open Document Text) supportxx
http://www.calligra.org/words/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=calligrawords http://kde.org/images/screenshots/words.png KDE calligrawords