├── .git-blame-ignore-revs ├── .gitignore ├── .gitlab-ci.yml ├── .kde-ci.yml ├── CMakeLists.txt ├── KF6TextWidgetsConfig.cmake.in ├── LICENSES ├── CC0-1.0.txt ├── LGPL-2.0-only.txt ├── LGPL-2.0-or-later.txt ├── LGPL-2.1-or-later.txt ├── LGPL-3.0-only.txt └── LicenseRef-KDE-Accepted-LGPL.txt ├── README.md ├── autotests ├── CMakeLists.txt ├── kfindtest.cpp ├── kfindtest.h ├── kpluralhandlingspinboxtest.cpp ├── kpluralhandlingspinboxtest.h ├── kreplacetest.cpp ├── kreplacetest.h ├── krichtextedittest.cpp ├── krichtextedittest.h └── ktextedit_unittest.cpp ├── docs └── pics │ ├── kfinddialog.png │ ├── kreplacedialog.png │ ├── krichtextedit.png │ └── ktextedit.png ├── metainfo.yaml ├── po ├── af │ └── ktextwidgets6.po ├── ar │ └── ktextwidgets6.po ├── as │ └── ktextwidgets6.po ├── ast │ └── ktextwidgets6.po ├── az │ └── ktextwidgets6.po ├── be │ └── ktextwidgets6.po ├── be@latin │ └── ktextwidgets6.po ├── bg │ └── ktextwidgets6.po ├── bn │ └── ktextwidgets6.po ├── bn_IN │ └── ktextwidgets6.po ├── br │ └── ktextwidgets6.po ├── bs │ └── ktextwidgets6.po ├── ca │ └── ktextwidgets6.po ├── ca@valencia │ └── ktextwidgets6.po ├── crh │ └── ktextwidgets6.po ├── cs │ └── ktextwidgets6.po ├── csb │ └── ktextwidgets6.po ├── cy │ └── ktextwidgets6.po ├── da │ └── ktextwidgets6.po ├── de │ └── ktextwidgets6.po ├── el │ └── ktextwidgets6.po ├── en_GB │ └── ktextwidgets6.po ├── eo │ └── ktextwidgets6.po ├── es │ └── ktextwidgets6.po ├── et │ └── ktextwidgets6.po ├── eu │ └── ktextwidgets6.po ├── fa │ └── ktextwidgets6.po ├── fi │ └── ktextwidgets6.po ├── fr │ └── ktextwidgets6.po ├── fy │ └── ktextwidgets6.po ├── ga │ └── ktextwidgets6.po ├── gd │ └── ktextwidgets6.po ├── gl │ └── ktextwidgets6.po ├── gu │ └── ktextwidgets6.po ├── ha │ └── ktextwidgets6.po ├── he │ └── ktextwidgets6.po ├── hi │ └── ktextwidgets6.po ├── hne │ └── ktextwidgets6.po ├── hr │ └── ktextwidgets6.po ├── hsb │ └── ktextwidgets6.po ├── hu │ └── ktextwidgets6.po ├── hy │ └── ktextwidgets6.po ├── ia │ └── ktextwidgets6.po ├── id │ └── ktextwidgets6.po ├── is │ └── ktextwidgets6.po ├── it │ └── ktextwidgets6.po ├── ja │ └── ktextwidgets6.po ├── ka │ └── ktextwidgets6.po ├── kk │ └── ktextwidgets6.po ├── km │ └── ktextwidgets6.po ├── kn │ └── ktextwidgets6.po ├── ko │ └── ktextwidgets6.po ├── ku │ └── ktextwidgets6.po ├── lb │ └── ktextwidgets6.po ├── lt │ └── ktextwidgets6.po ├── lv │ └── ktextwidgets6.po ├── mai │ └── ktextwidgets6.po ├── mk │ └── ktextwidgets6.po ├── ml │ └── ktextwidgets6.po ├── mr │ └── ktextwidgets6.po ├── ms │ └── ktextwidgets6.po ├── nb │ └── ktextwidgets6.po ├── nds │ └── ktextwidgets6.po ├── ne │ └── ktextwidgets6.po ├── nl │ └── ktextwidgets6.po ├── nn │ └── ktextwidgets6.po ├── oc │ └── ktextwidgets6.po ├── or │ └── ktextwidgets6.po ├── pa │ └── ktextwidgets6.po ├── pl │ └── ktextwidgets6.po ├── ps │ └── ktextwidgets6.po ├── pt │ └── ktextwidgets6.po ├── pt_BR │ └── ktextwidgets6.po ├── ro │ └── ktextwidgets6.po ├── ru │ └── ktextwidgets6.po ├── sa │ └── ktextwidgets6.po ├── se │ └── ktextwidgets6.po ├── si │ └── ktextwidgets6.po ├── sk │ └── ktextwidgets6.po ├── sl │ └── ktextwidgets6.po ├── sq │ └── ktextwidgets6.po ├── sr │ └── ktextwidgets6.po ├── sr@ijekavian │ └── ktextwidgets6.po ├── sr@ijekavianlatin │ └── ktextwidgets6.po ├── sr@latin │ └── ktextwidgets6.po ├── sv │ └── ktextwidgets6.po ├── ta │ └── ktextwidgets6.po ├── te │ └── ktextwidgets6.po ├── tg │ └── ktextwidgets6.po ├── th │ └── ktextwidgets6.po ├── tr │ └── ktextwidgets6.po ├── tt │ └── ktextwidgets6.po ├── ug │ └── ktextwidgets6.po ├── uk │ └── ktextwidgets6.po ├── uz │ └── ktextwidgets6.po ├── uz@cyrillic │ └── ktextwidgets6.po ├── vi │ └── ktextwidgets6.po ├── wa │ └── ktextwidgets6.po ├── xh │ └── ktextwidgets6.po ├── zh_CN │ └── ktextwidgets6.po ├── zh_HK │ └── ktextwidgets6.po └── zh_TW │ └── ktextwidgets6.po ├── src ├── CMakeLists.txt ├── Messages.sh ├── designer │ ├── CMakeLists.txt │ └── pics │ │ └── ktextedit.png ├── dialogs │ ├── klinkdialog.cpp │ └── klinkdialog_p.h ├── findreplace │ ├── kfind.cpp │ ├── kfind.h │ ├── kfind_p.h │ ├── kfinddialog.cpp │ ├── kfinddialog.h │ ├── kfinddialog_p.h │ ├── kreplace.cpp │ ├── kreplace.h │ ├── kreplacedialog.cpp │ └── kreplacedialog.h ├── ktextwidgets-index.qdoc ├── ktextwidgets.qdoc ├── ktextwidgets.qdocconf └── widgets │ ├── kpluralhandlingspinbox.cpp │ ├── kpluralhandlingspinbox.h │ ├── krichtextedit.cpp │ ├── krichtextedit.h │ ├── krichtextedit_p.h │ ├── krichtextwidget.cpp │ ├── krichtextwidget.h │ ├── ktextedit.cpp │ ├── ktextedit.h │ ├── ktextedit_p.h │ ├── nestedlisthelper.cpp │ └── nestedlisthelper_p.h └── tests ├── CMakeLists.txt └── ktextedittest.cpp /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | #clang-format 2 | 69e9d6b56a92bb8b4b388ef91d7a36f30abd4ccb 3 | 640550d62939a85c1a404abd280a4359ff4bc231 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the following files 2 | *~ 3 | *.[oa] 4 | *.diff 5 | *.kate-swp 6 | *.kdev4 7 | .kdev_include_paths 8 | *.kdevelop.pcs 9 | *.moc 10 | *.moc.cpp 11 | *.orig 12 | *.user 13 | .*.swp 14 | .swp.* 15 | Doxyfile 16 | Makefile 17 | avail 18 | random_seed 19 | /build*/ 20 | CMakeLists.txt.user* 21 | *.unc-backup* 22 | .cmake/ 23 | /.clang-format 24 | /compile_commands.json 25 | .clangd 26 | .idea 27 | .vscode 28 | /cmake-build* 29 | .cache 30 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Volker Krause 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | include: 5 | - project: sysadmin/ci-utilities 6 | file: 7 | - /gitlab-templates/linux-qt6.yml 8 | - /gitlab-templates/linux-qt6-next.yml 9 | - /gitlab-templates/linux-qt6-static.yml 10 | - /gitlab-templates/android-qt6.yml 11 | - /gitlab-templates/freebsd-qt6.yml 12 | - /gitlab-templates/windows-qt6.yml 13 | - /gitlab-templates/xml-lint.yml 14 | - /gitlab-templates/yaml-lint.yml 15 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | Dependencies: 2 | - 'on': ['@all'] 3 | 'require': 4 | 'frameworks/extra-cmake-modules': '@same' 5 | 'frameworks/kcompletion': '@same' 6 | 'frameworks/kconfig': '@same' 7 | 'frameworks/ki18n': '@same' 8 | 'frameworks/kwidgetsaddons': '@same' 9 | 'frameworks/sonnet': '@same' 10 | 11 | Options: 12 | test-before-installing: True 13 | require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows'] 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | set(KF_VERSION "6.15.0") # handled by release scripts 4 | set(KF_DEP_VERSION "6.14.0") # handled by release scripts 5 | project(KTextWidgets VERSION ${KF_VERSION}) 6 | 7 | # ECM setup 8 | include(FeatureSummary) 9 | find_package(ECM 6.14.0 NO_MODULE) 10 | set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") 11 | feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) 12 | 13 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 14 | 15 | include(KDEInstallDirs) 16 | include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) 17 | include(KDECMakeSettings) 18 | include(KDEGitCommitHooks) 19 | 20 | include(ECMGenerateExportHeader) 21 | include(ECMGenerateQDoc) 22 | include(ECMSetupVersion) 23 | include(ECMGenerateHeaders) 24 | include(CMakePackageConfigHelpers) 25 | include(ECMDeprecationSettings) 26 | include(CMakeDependentOption) 27 | 28 | set(ktextwidgets_version_header "${CMAKE_CURRENT_BINARY_DIR}/src/ktextwidgets_version.h") 29 | ecm_setup_version(PROJECT 30 | VARIABLE_PREFIX KTEXTWIDGETS 31 | VERSION_HEADER "${ktextwidgets_version_header}" 32 | PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF6TextWidgetsConfigVersion.cmake" 33 | SOVERSION 6) 34 | 35 | # Dependencies 36 | set(REQUIRED_QT_VERSION 6.7.0) 37 | 38 | option(WITH_TEXT_TO_SPEECH "Build text-to-speech support" ON) 39 | 40 | find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets) 41 | 42 | if (WITH_TEXT_TO_SPEECH) 43 | find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED TextToSpeech) 44 | add_definitions(-DHAVE_SPEECH) 45 | endif() 46 | 47 | find_package(KF6Completion ${KF_DEP_VERSION} REQUIRED) 48 | find_package(KF6Config ${KF_DEP_VERSION} REQUIRED) 49 | find_package(KF6I18n ${KF_DEP_VERSION} REQUIRED) 50 | find_package(KF6WidgetsAddons ${KF_DEP_VERSION} REQUIRED) 51 | find_package(KF6Sonnet ${KF_DEP_VERSION} REQUIRED) 52 | 53 | set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") 54 | 55 | cmake_dependent_option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON "NOT CMAKE_CROSSCOMPILING" OFF) 56 | add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") 57 | 58 | add_definitions(-DTRANSLATION_DOMAIN=\"ktextwidgets6\") 59 | 60 | ecm_set_disabled_deprecation_versions( 61 | QT 6.9 62 | KF 6.13 63 | ) 64 | 65 | ki18n_install(po) 66 | add_subdirectory(src) 67 | if (BUILD_TESTING) 68 | add_subdirectory(tests) 69 | add_subdirectory(autotests) 70 | endif() 71 | 72 | # create a Config.cmake and a ConfigVersion.cmake file and install them 73 | set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6TextWidgets") 74 | 75 | configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF6TextWidgetsConfig.cmake.in" 76 | "${CMAKE_CURRENT_BINARY_DIR}/KF6TextWidgetsConfig.cmake" 77 | INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} 78 | ) 79 | 80 | install(EXPORT KF6TextWidgetsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF6TextWidgetsTargets.cmake NAMESPACE KF6:: ) 81 | 82 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF6TextWidgetsConfig.cmake" 83 | "${CMAKE_CURRENT_BINARY_DIR}/KF6TextWidgetsConfigVersion.cmake" 84 | DESTINATION "${CMAKECONFIG_INSTALL_DIR}" 85 | COMPONENT Devel 86 | ) 87 | 88 | install(FILES ${ktextwidgets_version_header} 89 | DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KTextWidgets COMPONENT Devel 90 | ) 91 | 92 | include(ECMFeatureSummary) 93 | ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 94 | 95 | kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) 96 | -------------------------------------------------------------------------------- /KF6TextWidgetsConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(Qt6Widgets @REQUIRED_QT_VERSION@) 5 | find_dependency(KF6Sonnet "@KF_DEP_VERSION@") 6 | find_dependency(KF6I18n "@KF_DEP_VERSION@") 7 | 8 | if (NOT @BUILD_SHARED_LIBS@) 9 | find_dependency(KF6Completion @KF_DEP_VERSION@) 10 | find_dependency(KF6Config @KF_DEP_VERSION@) 11 | find_dependency(KF6WidgetsAddons @KF_DEP_VERSION@) 12 | endif() 13 | 14 | 15 | include("${CMAKE_CURRENT_LIST_DIR}/KF6TextWidgetsTargets.cmake") 16 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/LGPL-3.0-only.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | This version of the GNU Lesser General Public License incorporates the terms 11 | and conditions of version 3 of the GNU General Public License, supplemented 12 | by the additional permissions listed below. 13 | 14 | 0. Additional Definitions. 15 | 16 | 17 | 18 | As used herein, "this License" refers to version 3 of the GNU Lesser General 19 | Public License, and the "GNU GPL" refers to version 3 of the GNU General Public 20 | License. 21 | 22 | 23 | 24 | "The Library" refers to a covered work governed by this License, other than 25 | an Application or a Combined Work as defined below. 26 | 27 | 28 | 29 | An "Application" is any work that makes use of an interface provided by the 30 | Library, but which is not otherwise based on the Library. Defining a subclass 31 | of a class defined by the Library is deemed a mode of using an interface provided 32 | by the Library. 33 | 34 | 35 | 36 | A "Combined Work" is a work produced by combining or linking an Application 37 | with the Library. The particular version of the Library with which the Combined 38 | Work was made is also called the "Linked Version". 39 | 40 | 41 | 42 | The "Minimal Corresponding Source" for a Combined Work means the Corresponding 43 | Source for the Combined Work, excluding any source code for portions of the 44 | Combined Work that, considered in isolation, are based on the Application, 45 | and not on the Linked Version. 46 | 47 | 48 | 49 | The "Corresponding Application Code" for a Combined Work means the object 50 | code and/or source code for the Application, including any data and utility 51 | programs needed for reproducing the Combined Work from the Application, but 52 | excluding the System Libraries of the Combined Work. 53 | 54 | 1. Exception to Section 3 of the GNU GPL. 55 | 56 | You may convey a covered work under sections 3 and 4 of this License without 57 | being bound by section 3 of the GNU GPL. 58 | 59 | 2. Conveying Modified Versions. 60 | 61 | If you modify a copy of the Library, and, in your modifications, a facility 62 | refers to a function or data to be supplied by an Application that uses the 63 | facility (other than as an argument passed when the facility is invoked), 64 | then you may convey a copy of the modified version: 65 | 66 | a) under this License, provided that you make a good faith effort to ensure 67 | that, in the event an Application does not supply the function or data, the 68 | facility still operates, and performs whatever part of its purpose remains 69 | meaningful, or 70 | 71 | b) under the GNU GPL, with none of the additional permissions of this License 72 | applicable to that copy. 73 | 74 | 3. Object Code Incorporating Material from Library Header Files. 75 | 76 | The object code form of an Application may incorporate material from a header 77 | file that is part of the Library. You may convey such object code under terms 78 | of your choice, provided that, if the incorporated material is not limited 79 | to numerical parameters, data structure layouts and accessors, or small macros, 80 | inline functions and templates (ten or fewer lines in length), you do both 81 | of the following: 82 | 83 | a) Give prominent notice with each copy of the object code that the Library 84 | is used in it and that the Library and its use are covered by this License. 85 | 86 | b) Accompany the object code with a copy of the GNU GPL and this license document. 87 | 88 | 4. Combined Works. 89 | 90 | You may convey a Combined Work under terms of your choice that, taken together, 91 | effectively do not restrict modification of the portions of the Library contained 92 | in the Combined Work and reverse engineering for debugging such modifications, 93 | if you also do each of the following: 94 | 95 | a) Give prominent notice with each copy of the Combined Work that the Library 96 | is used in it and that the Library and its use are covered by this License. 97 | 98 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 99 | document. 100 | 101 | c) For a Combined Work that displays copyright notices during execution, include 102 | the copyright notice for the Library among these notices, as well as a reference 103 | directing the user to the copies of the GNU GPL and this license document. 104 | 105 | d) Do one of the following: 106 | 107 | 0) Convey the Minimal Corresponding Source under the terms of this License, 108 | and the Corresponding Application Code in a form suitable for, and under terms 109 | that permit, the user to recombine or relink the Application with a modified 110 | version of the Linked Version to produce a modified Combined Work, in the 111 | manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 112 | 113 | 1) Use a suitable shared library mechanism for linking with the Library. A 114 | suitable mechanism is one that (a) uses at run time a copy of the Library 115 | already present on the user's computer system, and (b) will operate properly 116 | with a modified version of the Library that is interface-compatible with the 117 | Linked Version. 118 | 119 | e) Provide Installation Information, but only if you would otherwise be required 120 | to provide such information under section 6 of the GNU GPL, and only to the 121 | extent that such information is necessary to install and execute a modified 122 | version of the Combined Work produced by recombining or relinking the Application 123 | with a modified version of the Linked Version. (If you use option 4d0, the 124 | Installation Information must accompany the Minimal Corresponding Source and 125 | Corresponding Application Code. If you use option 4d1, you must provide the 126 | Installation Information in the manner specified by section 6 of the GNU GPL 127 | for conveying Corresponding Source.) 128 | 129 | 5. Combined Libraries. 130 | 131 | You may place library facilities that are a work based on the Library side 132 | by side in a single library together with other library facilities that are 133 | not Applications and are not covered by this License, and convey such a combined 134 | library under terms of your choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based on the 137 | Library, uncombined with any other library facilities, conveyed under the 138 | terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it is a work 141 | based on the Library, and explaining where to find the accompanying uncombined 142 | form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions of the 147 | GNU Lesser General Public License from time to time. Such new versions will 148 | be similar in spirit to the present version, but may differ in detail to address 149 | new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the Library as you 152 | received it specifies that a certain numbered version of the GNU Lesser General 153 | Public License "or any later version" applies to it, you have the option of 154 | following the terms and conditions either of that published version or of 155 | any later version published by the Free Software Foundation. If the Library 156 | as you received it does not specify a version number of the GNU Lesser General 157 | Public License, you may choose any version of the GNU Lesser General Public 158 | License ever published by the Free Software Foundation. 159 | 160 | If the Library as you received it specifies that a proxy can decide whether 161 | future versions of the GNU Lesser General Public License shall apply, that 162 | proxy's public statement of acceptance of any version is permanent authorization 163 | for you to choose that version for the Library. 164 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-KDE-Accepted-LGPL.txt: -------------------------------------------------------------------------------- 1 | This library is free software; you can redistribute it and/or 2 | modify it under the terms of the GNU Lesser General Public 3 | License as published by the Free Software Foundation; either 4 | version 3 of the license or (at your option) any later version 5 | that is accepted by the membership of KDE e.V. (or its successor 6 | approved by the membership of KDE e.V.), which shall act as a 7 | proxy as defined in Section 6 of version 3 of the license. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KTextWidgets 2 | 3 | Text editing widgets 4 | 5 | ## Introduction 6 | 7 | KTextWidgets provides widgets for displaying and editing text. It supports 8 | rich text as well as plain text. 9 | 10 | -------------------------------------------------------------------------------- /autotests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(ECMAddTests) 2 | 3 | find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test) 4 | 5 | macro(ktextwidgets_unit_tests) 6 | foreach(_testname ${ARGN}) 7 | ecm_add_test(${_testname}.cpp TEST_NAME ktextwidgets-${_testname} LINK_LIBRARIES Qt6::Test KF6::TextWidgets) 8 | endforeach() 9 | endmacro() 10 | 11 | ktextwidgets_unit_tests( 12 | kfindtest 13 | kreplacetest 14 | krichtextedittest 15 | ktextedit_unittest 16 | kpluralhandlingspinboxtest 17 | ) 18 | -------------------------------------------------------------------------------- /autotests/kfindtest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 4 | SPDX-FileCopyrightText: 2010 David Faure 5 | 6 | SPDX-License-Identifier: LGPL-2.0-only 7 | */ 8 | 9 | #include "kfindtest.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | void KFindRecorder::changeText(int line, const QString &text) 19 | { 20 | Q_ASSERT(line < m_text.count()); 21 | Q_ASSERT(m_find != nullptr); 22 | 23 | m_line = line; 24 | m_text[line] = text; 25 | m_find->setData(line, text); 26 | } 27 | 28 | KFindRecorder::KFindRecorder(const QStringList &text) 29 | : QObject(nullptr) 30 | , m_text(text) 31 | , m_line(0) 32 | { 33 | } 34 | 35 | KFindRecorder::~KFindRecorder() 36 | { 37 | } 38 | 39 | void KFindRecorder::find(const QString &pattern, long options) 40 | { 41 | m_find.reset(new KFind(pattern, options, nullptr)); 42 | // Prevent dialogs from popping up 43 | m_find->closeFindNextDialog(); 44 | 45 | connect(m_find.get(), &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) { 46 | slotHighlight(text, matchingIndex, matchedLength); 47 | }); 48 | 49 | connect(m_find.get(), &KFind::textFoundAtId, this, [this](int id, int matchingIndex, int matchedLength) { 50 | slotHighlight(id, matchingIndex, matchedLength); 51 | }); 52 | 53 | m_line = 0; 54 | KFind::Result result = KFind::NoMatch; 55 | do { 56 | if (options & KFind::FindIncremental) { 57 | m_find->setData(m_line, m_text[m_line]); 58 | } else { 59 | m_find->setData(m_text[m_line]); 60 | } 61 | 62 | m_line++; 63 | 64 | result = m_find->find(); 65 | } while (result == KFind::NoMatch && m_line < m_text.count()); 66 | } 67 | 68 | bool KFindRecorder::findNext(const QString &pattern) 69 | { 70 | Q_ASSERT(m_find != nullptr); 71 | 72 | if (!pattern.isNull()) { 73 | m_find->setPattern(pattern); 74 | } 75 | 76 | KFind::Result result = KFind::NoMatch; 77 | do { 78 | // qDebug() << "m_line: " << m_line; 79 | 80 | result = m_find->find(); 81 | 82 | if (result == KFind::NoMatch && m_line < m_text.count()) { 83 | // qDebug() << "incrementing m_line..."; 84 | if (m_find->options() & KFind::FindIncremental) { 85 | m_find->setData(m_line, m_text[m_line]); 86 | } else { 87 | m_find->setData(m_text[m_line]); 88 | } 89 | 90 | m_line++; 91 | } 92 | } while (result == KFind::NoMatch && m_line < m_text.count()); 93 | // qDebug() << "find next completed" << m_line; 94 | 95 | return result != KFind::NoMatch; 96 | } 97 | 98 | void KFindRecorder::slotHighlight(const QString &text, int index, int matchedLength) 99 | { 100 | m_hits.append(QLatin1String("line: \"") + text + QLatin1String("\", index: ") + QString::number(index) + QLatin1String(", length: ") 101 | + QString::number(matchedLength) + QLatin1Char('\n')); 102 | } 103 | 104 | void KFindRecorder::slotHighlight(int id, int index, int matchedLength) 105 | { 106 | m_hits.append(QLatin1String("line: \"") + m_text[id] + QLatin1String("\", index: ") + QString::number(index) + QLatin1String(", length: ") 107 | + QString::number(matchedLength) + QLatin1Char('\n')); 108 | } 109 | 110 | //// 111 | 112 | TestKFind::TestKFind() 113 | : QObject() 114 | { 115 | m_text = QLatin1String("This file is part of the KDE project.\n") + QLatin1String("This library is free software; you can redistribute it and/or\n") 116 | + QLatin1String("modify it under the terms of the GNU Library General Public\n") 117 | + QLatin1String("License version 2, as published by the Free Software Foundation.\n") + QLatin1Char('\n') 118 | + QLatin1String(" This library is distributed in the hope that it will be useful,\n") 119 | + QLatin1String(" but WITHOUT ANY WARRANTY; without even the implied warranty of\n") 120 | + QLatin1String(" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n") 121 | + QLatin1String(" Library General Public License for more details.\n") + QLatin1Char('\n') 122 | + QLatin1String(" You should have received a copy of the GNU Library General Public License\n") 123 | + QLatin1String(" along with this library; see the file COPYING.LIB. If not, write to\n") 124 | + QLatin1String(" the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n") + QLatin1String(" Boston, MA 02110-1301, USA.\n"); 125 | } 126 | 127 | void TestKFind::testStaticFindRegexp_data() 128 | { 129 | // Tests for the core method "static KFind::find" 130 | QTest::addColumn("text"); 131 | QTest::addColumn("pattern"); 132 | QTest::addColumn("startIndex"); 133 | QTest::addColumn("options"); 134 | QTest::addColumn("expectedResult"); 135 | QTest::addColumn("expectedMatchedLength"); 136 | 137 | /* clang-format off */ 138 | QTest::newRow("simple (0)") << "abc" << "a" << 0 << 0 << 0 << 1; 139 | QTest::newRow("simple (1)") << "abc" << "b" << 0 << 0 << 1 << 1; 140 | QTest::newRow("not found") << "abca" << "ba" << 0 << 0 << -1 << 0; 141 | QTest::newRow("from index") << "abc bc" << "b" << 3 << 0 << 4 << 1; 142 | QTest::newRow("from exact index") << "abc bc" << "b" << 4 << 0 << 4 << 1; 143 | QTest::newRow("past index (not found)") << "abc bc" << "b" << 5 << 0 << -1 << 0; 144 | QTest::newRow("dot") << "abc" << "b." << 0 << 0 << 1 << 2; 145 | QTest::newRow("^simple") << "text" << "^tex" << 0 << 0 << 0 << 3; 146 | QTest::newRow("^multiline first") << "foo\nbar" << "^f" << 0 << 0 << 0 << 1; 147 | QTest::newRow("^multiline last") << "foo\nbar" << "^bar" << 0 << 0 << 4 << 3; 148 | QTest::newRow("^multiline with index") << "boo\nbar" << "^b" << 1 << 0 << 4 << 1; 149 | QTest::newRow("simple$") << "text" << "xt$" << 0 << 0 << 2 << 2; 150 | QTest::newRow("$ backwards") << "text" << "xt$" << 4 << int(KFind::FindBackwards) << 2 << 2; 151 | QTest::newRow("multiline$") << "foo\nbar" << "oo$" << 0 << 0 << 1 << 2; 152 | QTest::newRow("multiline$ intermediary line") << "foo\nbar\nagain bar" << "r$" << 0 << 0 << 6 << 1; 153 | QTest::newRow("multiline$ with index, last line") << "foo\nbar\nagain bar" << "r$" << 7 << 0 << 16 << 1; 154 | QTest::newRow("multiline$ backwards") << "foo\nbar" << "oo$" << 7 << int(KFind::FindBackwards) << 1 << 2; 155 | QTest::newRow("multiline with \\n") << "foo\nbar" << "o\nb" << 0 << 0 << 2 << 3; 156 | QTest::newRow("whole words ok") << "abc bcbc bc bmore be" << "b." << 0 << int(KFind::WholeWordsOnly) << 9 << 2; 157 | QTest::newRow("whole words not found") << "abab abx" << "ab" << 0 << int(KFind::WholeWordsOnly) << -1 << 0; 158 | QTest::newRow("whole words not found (_)") << "abab ab_" << "ab" << 0 << int(KFind::WholeWordsOnly) << -1 << 0; 159 | QTest::newRow("whole words ok (.)") << "ab." << "ab" << 0 << int(KFind::WholeWordsOnly) << 0 << 2; 160 | QTest::newRow("backwards") << "abc bcbc bc" << "b." << 10 << int(KFind::FindBackwards) << 9 << 2; 161 | QTest::newRow("empty (0)") << "a" << "" << 0 << int(0) << 0 << 0; 162 | QTest::newRow("empty (1)") << "a" << "" << 1 << int(0) << 1 << 0; // kreplacetest testReplaceBlankSearch relies on this 163 | QTest::newRow("at end, not found") << "a" << "b" << 1 << int(0) << -1 << 0; // just for catching the while(index("text"); 190 | QTest::addColumn("pattern"); 191 | QTest::addColumn("startIndex"); 192 | QTest::addColumn("options"); 193 | QTest::addColumn("expectedResult"); 194 | QTest::addColumn("expectedMatchedLength"); 195 | 196 | /* clang-format off */ 197 | // Test matching with Unicode properties in QRegularExpression 198 | QTest::newRow("unicode-word-boundary") << "aoé" << "\\b" << 1 << 0 << 3 << 0; 199 | QTest::newRow("unicode-word-char") << "aoé" << "\\w$" << 0 << 0 << 2 << 1; 200 | QTest::newRow("unicode-non-word-char") << "aoé" << "\\W" << 0 << 0 << -1 << 0; 201 | /* clang-format on */ 202 | } 203 | 204 | void TestKFind::testRegexpUnicode() 205 | { 206 | // Tests for the core method "static KFind::find(text, regexp)" 207 | QFETCH(QString, text); 208 | QFETCH(QString, pattern); 209 | QFETCH(int, startIndex); 210 | QFETCH(int, options); 211 | QFETCH(int, expectedResult); 212 | QFETCH(int, expectedMatchedLength); 213 | 214 | int matchedLength = 0; 215 | 216 | const int result = KFind::find(text, pattern, startIndex, options | KFind::RegularExpression, &matchedLength, nullptr); 217 | QCOMPARE(result, expectedResult); 218 | QCOMPARE(matchedLength, expectedMatchedLength); 219 | } 220 | 221 | void TestKFind::testSimpleSearch() 222 | { 223 | // first we do a simple text searching the text and doing a few find nexts 224 | KFindRecorder test(m_text.split(QLatin1Char('\n'))); 225 | test.find(QStringLiteral("This"), 0); 226 | while (test.findNext()) { } 227 | 228 | const QString output1 = QLatin1String("line: \"This file is part of the KDE project.\", index: 0, length: 4\n") 229 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 0, length: 4\n") 230 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 4, length: 4\n") 231 | + QLatin1String("line: \" along with this library; see the file COPYING.LIB. If not, write to\", index: 15, length: 4\n"); 232 | 233 | QCOMPARE(test.hits().join(QString()), output1); 234 | } 235 | 236 | void TestKFind::testSimpleRegexp() 237 | { 238 | KFindRecorder test(m_text.split(QLatin1Char('\n'))); 239 | test.find(QStringLiteral("W.R+ANT[YZ]"), KFind::RegularExpression | KFind::CaseSensitive); 240 | while (test.findNext()) { } 241 | const QString output = QStringLiteral("line: \" but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n"); 242 | QCOMPARE(test.hits().join(QString()), output); 243 | } 244 | 245 | void TestKFind::testLineBeginRegularExpression() 246 | { 247 | int matchedLength; 248 | QRegularExpressionMatch match; 249 | KFind::find(m_text, QStringLiteral("^License.+"), 0, KFind::RegularExpression, &matchedLength, &match); 250 | QCOMPARE(match.captured(0), QStringLiteral("License version 2, as published by the Free Software Foundation.")); 251 | } 252 | 253 | void TestKFind::testFindIncremental() 254 | { 255 | // FindIncremental with static contents... 256 | 257 | KFindRecorder test(m_text.split(QLatin1Char('\n'))); 258 | test.find(QString(), KFind::FindIncremental); 259 | test.findNext(QStringLiteral("i")); 260 | test.findNext(QStringLiteral("is")); 261 | test.findNext(QStringLiteral("ist")); 262 | test.findNext(); 263 | test.findNext(QStringLiteral("istri")); 264 | test.findNext(QStringLiteral("istr")); 265 | test.findNext(QStringLiteral("ist")); 266 | test.findNext(QStringLiteral("is")); 267 | test.findNext(QStringLiteral("W")); 268 | test.findNext(QStringLiteral("WA")); 269 | test.findNext(QStringLiteral("WARRANTY")); 270 | test.findNext(QStringLiteral("Free")); 271 | test.findNext(QStringLiteral("Software Foundation")); 272 | 273 | const QString output2 = QLatin1String("line: \"This file is part of the KDE project.\", index: 0, length: 0\n") 274 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 1\n") 275 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n") 276 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 3\n") 277 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n") 278 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 5\n") 279 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n") 280 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n") 281 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n") 282 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 25, length: 1\n") 283 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 25, length: 2\n") 284 | + QLatin1String("line: \" but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n") 285 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 16, length: 4\n") 286 | + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 44, length: 19\n"); 287 | 288 | QCOMPARE(test.hits().join(QString()), output2); 289 | } 290 | 291 | void TestKFind::testFindIncrementalDynamic() 292 | { 293 | // Now do that again but with pages that change between searches 294 | KFindRecorder test(m_text.split(QLatin1Char('\n'))); 295 | 296 | test.find(QString(), KFind::FindIncremental); 297 | test.findNext(QStringLiteral("i")); 298 | test.findNext(QStringLiteral("is")); 299 | test.findNext(QStringLiteral("ist")); 300 | test.findNext(QStringLiteral("istr")); 301 | test.findNext(); 302 | test.changeText(1, QStringLiteral("The second line now looks a whole lot different.")); 303 | test.findNext(QStringLiteral("istri")); 304 | test.findNext(QStringLiteral("istr")); 305 | test.findNext(QStringLiteral("ist")); 306 | test.findNext(QStringLiteral("is")); 307 | test.findNext(QStringLiteral("i")); 308 | test.findNext(QStringLiteral("W")); 309 | test.findNext(QStringLiteral("WA")); 310 | test.findNext(QStringLiteral("WARRANTY")); 311 | test.changeText(6, QStringLiteral(" but WITHOUT ANY xxxx; without even the implied warranty of")); 312 | test.findNext(QStringLiteral("WARRAN")); 313 | test.findNext(QStringLiteral("Free")); 314 | test.findNext(QStringLiteral("Software Foundation")); 315 | 316 | const QString output3 = QLatin1String("line: \"This file is part of the KDE project.\", index: 0, length: 0\n") 317 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 1\n") 318 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n") 319 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 3\n") 320 | + QLatin1String("line: \"This library is free software; you can redistribute it and/or\", index: 42, length: 4\n") 321 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n") 322 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 5\n") 323 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 4\n") 324 | + QLatin1String("line: \" This library is distributed in the hope that it will be useful,\", index: 21, length: 3\n") 325 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 2\n") 326 | + QLatin1String("line: \"This file is part of the KDE project.\", index: 2, length: 1\n") 327 | + QLatin1String("line: \"The second line now looks a whole lot different.\", index: 18, length: 1\n") 328 | + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 48, length: 2\n") 329 | + QLatin1String("line: \" but WITHOUT ANY WARRANTY; without even the implied warranty of\", index: 20, length: 8\n") 330 | + QLatin1String("line: \" but WITHOUT ANY xxxx; without even the implied warranty of\", index: 51, length: 6\n") 331 | + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 39, length: 4\n") 332 | + QLatin1String("line: \"License version 2, as published by the Free Software Foundation.\", index: 44, length: 19\n"); 333 | 334 | QCOMPARE(test.hits().join(QString()), output3); 335 | } 336 | 337 | QTEST_MAIN(TestKFind) 338 | 339 | #include "moc_kfindtest.cpp" 340 | -------------------------------------------------------------------------------- /autotests/kfindtest.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 4 | SPDX-FileCopyrightText: 2010 David Faure 5 | 6 | SPDX-License-Identifier: LGPL-2.0-only 7 | */ 8 | 9 | #ifndef KFINDTEST_H 10 | #define KFINDTEST_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | class KFind; 17 | class KFindRecorder : public QObject 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit KFindRecorder(const QStringList &text); 23 | 24 | ~KFindRecorder() override; 25 | void find(const QString &pattern, long options = 0); 26 | bool findNext(const QString &pattern = QString()); 27 | 28 | void changeText(int line, const QString &text); 29 | 30 | const QStringList &hits() const 31 | { 32 | return m_hits; 33 | } 34 | void clearHits() 35 | { 36 | m_hits.clear(); 37 | } 38 | 39 | public Q_SLOTS: 40 | void slotHighlight(const QString &text, int index, int matchedLength); 41 | void slotHighlight(int id, int index, int matchedLengthlength); 42 | 43 | private: 44 | std::unique_ptr m_find; 45 | QStringList m_text; 46 | int m_line; 47 | QStringList m_hits; 48 | }; 49 | 50 | class TestKFind : public QObject 51 | { 52 | Q_OBJECT 53 | 54 | public: 55 | TestKFind(); 56 | 57 | private Q_SLOTS: 58 | 59 | void testStaticFindRegexp_data(); 60 | void testStaticFindRegexp(); 61 | 62 | void testRegexpUnicode_data(); 63 | void testRegexpUnicode(); 64 | 65 | void testSimpleSearch(); 66 | void testSimpleRegexp(); 67 | 68 | void testLineBeginRegularExpression(); 69 | void testFindIncremental(); 70 | void testFindIncrementalDynamic(); 71 | 72 | private: 73 | QString m_text; 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /autotests/kpluralhandlingspinboxtest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014 Laurent Montel 3 | 4 | SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "kpluralhandlingspinboxtest.h" 8 | #include "kpluralhandlingspinbox.h" 9 | 10 | #include 11 | 12 | QTEST_MAIN(KPluralHandlingSpinBoxTest) 13 | 14 | KPluralHandlingSpinBoxTest::KPluralHandlingSpinBoxTest() 15 | { 16 | } 17 | 18 | void KPluralHandlingSpinBoxTest::shouldHaveDefautValue() 19 | { 20 | KPluralHandlingSpinBox spinbox; 21 | QCOMPARE(spinbox.suffix(), QString()); 22 | } 23 | 24 | void KPluralHandlingSpinBoxTest::shouldUseSingularValueWhenUseValueEqualToOne() 25 | { 26 | KPluralHandlingSpinBox spinbox; 27 | spinbox.setSuffix(ki18np("singular", "plural")); 28 | spinbox.setValue(1); 29 | QCOMPARE(spinbox.suffix(), QLatin1String("singular")); 30 | } 31 | 32 | void KPluralHandlingSpinBoxTest::shouldUsePlurialValueWhenUseValueSuperiorToOne() 33 | { 34 | KPluralHandlingSpinBox spinbox; 35 | spinbox.setSuffix(ki18np("singular", "plural")); 36 | spinbox.setValue(2); 37 | QCOMPARE(spinbox.suffix(), QLatin1String("plural")); 38 | } 39 | 40 | void KPluralHandlingSpinBoxTest::shouldUseSingularValueWhenWeChangeValueAndFinishWithValueEqualOne() 41 | { 42 | KPluralHandlingSpinBox spinbox; 43 | spinbox.setSuffix(ki18np("singular", "plural")); 44 | spinbox.setValue(2); 45 | spinbox.setValue(1); 46 | QCOMPARE(spinbox.suffix(), QLatin1String("singular")); 47 | QCOMPARE(spinbox.value(), 1); 48 | } 49 | 50 | void KPluralHandlingSpinBoxTest::shouldReturnEmptySuffix() 51 | { 52 | KPluralHandlingSpinBox spinbox; 53 | spinbox.setValue(2); 54 | QCOMPARE(spinbox.suffix(), QString()); 55 | } 56 | 57 | #include "moc_kpluralhandlingspinboxtest.cpp" 58 | -------------------------------------------------------------------------------- /autotests/kpluralhandlingspinboxtest.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014 Laurent Montel 3 | 4 | SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef KPLURALHANDLINGSPINBOXTEST_H 8 | #define KPLURALHANDLINGSPINBOXTEST_H 9 | 10 | #include 11 | 12 | class KPluralHandlingSpinBoxTest : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | KPluralHandlingSpinBoxTest(); 17 | 18 | private Q_SLOTS: 19 | void shouldHaveDefautValue(); 20 | void shouldUseSingularValueWhenUseValueEqualToOne(); 21 | void shouldUsePlurialValueWhenUseValueSuperiorToOne(); 22 | void shouldUseSingularValueWhenWeChangeValueAndFinishWithValueEqualOne(); 23 | void shouldReturnEmptySuffix(); 24 | }; 25 | 26 | #endif // KPLURALHANDLINGSPINBOXTEST_H 27 | -------------------------------------------------------------------------------- /autotests/kreplacetest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2002 David Faure 4 | 5 | SPDX-License-Identifier: LGPL-2.0-only 6 | */ 7 | 8 | #include "kreplacetest.h" 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | void KReplaceTest::enterLoop() 22 | { 23 | QEventLoop eventLoop; 24 | connect(this, &KReplaceTest::exitLoop, &eventLoop, &QEventLoop::quit); 25 | eventLoop.exec(QEventLoop::ExcludeUserInputEvents); 26 | } 27 | 28 | KReplaceTest::KReplaceTest(const QStringList &text, const QString &buttonName) 29 | : QObject(nullptr) 30 | , m_text(text) 31 | , m_replace(nullptr) 32 | , m_buttonName(buttonName) 33 | { 34 | } 35 | 36 | KReplaceTest::~KReplaceTest() 37 | { 38 | } 39 | 40 | void KReplaceTest::replace(const QString &pattern, const QString &replacement, long options) 41 | { 42 | m_needEventLoop = false; 43 | // This creates a replace-next-prompt dialog if needed. 44 | m_replace.reset(new KReplace(pattern, replacement, options)); 45 | 46 | // Connect highlight (or textFound) signal to code which handles highlighting of found text. 47 | connect(m_replace.get(), &KFind::textFound, this, &KReplaceTest::slotHighlight); 48 | 49 | // Connect findNext signal - called when pressing the button in the dialog 50 | connect(m_replace.get(), &KFind::findNext, this, &KReplaceTest::slotReplaceNext); 51 | 52 | // Connect replace signal - called when doing a replacement 53 | connect(m_replace.get(), &KReplace::textReplaced, this, &KReplaceTest::slotReplace); 54 | 55 | // Go to initial position 56 | if ((options & KFind::FromCursor) == 0) { 57 | if (m_text.isEmpty()) { 58 | return; 59 | } 60 | if (m_replace->options() & KFind::FindBackwards) { 61 | m_currentPos = --m_text.end(); 62 | } else { 63 | m_currentPos = m_text.begin(); 64 | } 65 | } 66 | 67 | // Launch first replacement 68 | slotReplaceNext(); 69 | 70 | if (m_needEventLoop) { 71 | enterLoop(); 72 | } 73 | } 74 | 75 | void KReplaceTest::slotHighlight(const QString &str, int matchingIndex, int matchedLength) 76 | { 77 | qDebug() << "slotHighlight Index:" << matchingIndex << " Length:" << matchedLength << " Substr:" << str.mid(matchingIndex, matchedLength); 78 | // Emulate the user saying yes 79 | // We need Qt::QueuedConnection (and the enterloop/exitloop) 80 | // otherwise we get an infinite loop (Match never returned, 81 | // so slotReplaceNext never returns) 82 | if (m_replace->options() & KReplaceDialog::PromptOnReplace) { 83 | QDialog *dlg = m_replace->replaceNextDialog(false); 84 | disconnect(dlg, &QDialog::finished, m_replace.get(), nullptr); // hack to avoid slotDialogClosed being called 85 | dlg->hide(); 86 | 87 | QPushButton *button = dlg->findChild(m_buttonName); 88 | auto clickFunc = [button]() { 89 | button->click(); 90 | }; 91 | QMetaObject::invokeMethod(button, clickFunc, Qt::QueuedConnection); 92 | 93 | m_needEventLoop = true; 94 | } 95 | } 96 | 97 | void KReplaceTest::slotReplace(const QString &text, int replacementIndex, int replacedLength, int matchedLength) 98 | { 99 | Q_UNUSED(replacementIndex); 100 | Q_UNUSED(replacedLength); 101 | Q_UNUSED(matchedLength); 102 | // qDebug() << "index=" << replacementIndex << " replacedLength=" << replacedLength << " matchedLength=" << matchedLength << " text=" << text.left( 50 ); 103 | *m_currentPos = text; // KReplace hacked the replacement into 'text' in already. 104 | } 105 | 106 | void KReplaceTest::slotReplaceNext() 107 | { 108 | // qDebug(); 109 | KFind::Result res = KFind::NoMatch; 110 | int backwards = m_replace->options() & KFind::FindBackwards; 111 | while (res == KFind::NoMatch) { 112 | if (m_replace->needData()) { 113 | m_replace->setData(*m_currentPos); 114 | } 115 | 116 | // Let KReplace inspect the text fragment, and display a dialog if a match is found 117 | res = m_replace->replace(); 118 | 119 | if (res == KFind::NoMatch) { 120 | QStringList::iterator lastItem = backwards ? m_text.begin() : --m_text.end(); 121 | if (m_currentPos == lastItem) { 122 | break; 123 | } 124 | if (m_replace->options() & KFind::FindBackwards) { 125 | m_currentPos--; 126 | } else { 127 | m_currentPos++; 128 | } 129 | } 130 | } 131 | 132 | #if 0 // commented out so that this test doesn't require interaction 133 | if (res == KFind::NoMatch) // i.e. at end 134 | if (m_replace->shouldRestart()) { 135 | if (m_replace->options() & KFind::FindBackwards) { 136 | m_currentPos = m_text.fromLast(); 137 | } else { 138 | m_currentPos = m_text.begin(); 139 | } 140 | slotReplaceNext(); 141 | } 142 | #endif 143 | if (res == KFind::NoMatch && m_needEventLoop) { 144 | Q_EMIT exitLoop(); 145 | } 146 | } 147 | 148 | void KReplaceTest::print() 149 | { 150 | QStringList::Iterator it = m_text.begin(); 151 | for (; it != m_text.end(); ++it) { 152 | qDebug() << *it; 153 | } 154 | } 155 | 156 | /* button is the button that we emulate pressing, when options includes PromptOnReplace. 157 | Valid possibilities are User1 (replace all) and User3 (replace) */ 158 | static void testReplaceSimple(int options, const QString &buttonName = QString()) 159 | { 160 | qDebug() << "testReplaceSimple: " << options; 161 | KReplaceTest test(QStringList() << QStringLiteral("hellohello"), buttonName); 162 | test.replace(QStringLiteral("hello"), QStringLiteral("HELLO"), options); 163 | QStringList textLines = test.textLines(); 164 | assert(textLines.count() == 1); 165 | if (textLines[0] != QLatin1String("HELLOHELLO")) { 166 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'HELLOHELLO'"; 167 | exit(1); 168 | } 169 | } 170 | 171 | // Replacing "a" with "". 172 | // input="aaaaaa", expected output="" 173 | static void testReplaceBlank(int options, const QString &buttonName = QString()) 174 | { 175 | qDebug() << "testReplaceBlank: " << options; 176 | KReplaceTest test(QStringList() << QStringLiteral("aaaaaa"), buttonName); 177 | test.replace(QStringLiteral("a"), QString(), options); 178 | QStringList textLines = test.textLines(); 179 | assert(textLines.count() == 1); 180 | if (!textLines[0].isEmpty()) { 181 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of ''"; 182 | exit(1); 183 | } 184 | } 185 | 186 | // Replacing "" with "foo" 187 | // input="bbbb", expected output="foobfoobfoobfoobfoo" 188 | static void testReplaceBlankSearch(int options, const QString &buttonName = QString()) 189 | { 190 | qDebug() << "testReplaceBlankSearch: " << options; 191 | KReplaceTest test(QStringList() << QStringLiteral("bbbb"), buttonName); 192 | test.replace(QString(), QStringLiteral("foo"), options); 193 | QStringList textLines = test.textLines(); 194 | assert(textLines.count() == 1); 195 | if (textLines[0] != QLatin1String("foobfoobfoobfoobfoo")) { 196 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'foobfoobfoobfoobfoo'"; 197 | exit(1); 198 | } 199 | } 200 | 201 | static void testReplaceLonger(int options, const QString &buttonName = QString()) 202 | { 203 | qDebug() << "testReplaceLonger: " << options; 204 | // Standard test of a replacement string longer than the matched string 205 | KReplaceTest test(QStringList() << QStringLiteral("aaaa"), buttonName); 206 | test.replace(QStringLiteral("a"), QStringLiteral("bb"), options); 207 | QStringList textLines = test.textLines(); 208 | assert(textLines.count() == 1); 209 | if (textLines[0] != QLatin1String("bbbbbbbb")) { 210 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'bbbbbbbb'"; 211 | exit(1); 212 | } 213 | } 214 | 215 | static void testReplaceLongerInclude(int options, const QString &buttonName = QString()) 216 | { 217 | qDebug() << "testReplaceLongerInclude: " << options; 218 | // Similar test, where the replacement string includes the search string 219 | KReplaceTest test(QStringList() << QStringLiteral("a foo b"), buttonName); 220 | test.replace(QStringLiteral("foo"), QStringLiteral("foobar"), options); 221 | QStringList textLines = test.textLines(); 222 | assert(textLines.count() == 1); 223 | if (textLines[0] != QLatin1String("a foobar b")) { 224 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'a foobar b'"; 225 | exit(1); 226 | } 227 | } 228 | 229 | static void testReplaceLongerInclude2(int options, const QString &buttonName = QString()) 230 | { 231 | qDebug() << "testReplaceLongerInclude2: " << options; 232 | // Similar test, but with more chances of matches inside the replacement string 233 | KReplaceTest test(QStringList() << QStringLiteral("aaaa"), buttonName); 234 | test.replace(QStringLiteral("a"), QStringLiteral("aa"), options); 235 | QStringList textLines = test.textLines(); 236 | assert(textLines.count() == 1); 237 | if (textLines[0] != QLatin1String("aaaaaaaa")) { 238 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of 'aaaaaaaa'"; 239 | exit(1); 240 | } 241 | } 242 | 243 | // Test for the \0 backref 244 | static void testReplaceBackRef(int options, const QString &buttonName = QString()) 245 | { 246 | KReplaceTest test(QStringList() << QStringLiteral("abc def"), buttonName); 247 | test.replace(QStringLiteral("abc"), QStringLiteral("(\\0)"), options); 248 | QStringList textLines = test.textLines(); 249 | assert(textLines.count() == 1); 250 | QString expected = options & KReplaceDialog::BackReference ? QStringLiteral("(abc) def") : QStringLiteral("(\\0) def"); 251 | if (textLines[0] != expected) { 252 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of '" << expected << "'"; 253 | exit(1); 254 | } 255 | } 256 | 257 | // Test for other backrefs 258 | static void testReplaceBackRef1(int options, const QString &buttonName = QString()) 259 | { 260 | KReplaceTest test(QStringList() << QStringLiteral("a1 b2 a3"), buttonName); 261 | test.replace(QStringLiteral("([ab])([\\d])"), QStringLiteral("\\1 and \\2 in (\\0)"), options); 262 | QStringList textLines = test.textLines(); 263 | assert(textLines.count() == 1); 264 | QString expected = QStringLiteral("a and 1 in (a1) b and 2 in (b2) a and 3 in (a3)"); 265 | if (textLines[0] != expected) { 266 | qCritical() << "ASSERT FAILED: replaced text is '" << textLines[0] << "' instead of '" << expected << "'"; 267 | exit(1); 268 | } 269 | } 270 | 271 | static void testReplacementHistory(const QStringList &findHistory, const QStringList &replaceHistory) 272 | { 273 | KReplaceDialog dlg(nullptr, 0, findHistory, replaceHistory); 274 | dlg.show(); 275 | qDebug() << "testReplacementHistory:" << dlg.replacementHistory(); 276 | assert(dlg.replacementHistory() == replaceHistory); 277 | } 278 | 279 | static void testReplacementHistory() 280 | { 281 | QStringList findHistory; 282 | QStringList replaceHistory; 283 | findHistory << QStringLiteral("foo") << QStringLiteral("bar"); 284 | replaceHistory << QStringLiteral("FOO") << QStringLiteral("BAR"); 285 | testReplacementHistory(findHistory, replaceHistory); 286 | 287 | findHistory.clear(); 288 | replaceHistory.clear(); 289 | findHistory << QStringLiteral("foo") << QStringLiteral("bar"); 290 | replaceHistory << QString() << QStringLiteral("baz"); // #130831 291 | testReplacementHistory(findHistory, replaceHistory); 292 | } 293 | 294 | int main(int argc, char **argv) 295 | { 296 | QApplication::setApplicationName(QStringLiteral("kreplacetest")); 297 | QApplication app(argc, argv); 298 | 299 | testReplacementHistory(); // #130831 300 | 301 | testReplaceBlank(0); 302 | testReplaceBlank(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 303 | testReplaceBlank(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 304 | testReplaceBlank(KFind::FindBackwards); 305 | testReplaceBlank(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 306 | testReplaceBlank(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 307 | 308 | testReplaceBlankSearch(0); 309 | testReplaceBlankSearch(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 310 | testReplaceBlankSearch(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 311 | testReplaceBlankSearch(KFind::FindBackwards); 312 | testReplaceBlankSearch(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 313 | testReplaceBlankSearch(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 314 | 315 | testReplaceSimple(0); 316 | testReplaceSimple(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 317 | testReplaceSimple(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 318 | testReplaceSimple(KFind::FindBackwards); 319 | testReplaceSimple(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 320 | testReplaceSimple(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 321 | 322 | testReplaceLonger(0); 323 | testReplaceLonger(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 324 | testReplaceLonger(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 325 | testReplaceLonger(KFind::FindBackwards); 326 | testReplaceLonger(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 327 | testReplaceLonger(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 328 | 329 | testReplaceLongerInclude(0); 330 | testReplaceLongerInclude(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 331 | testReplaceLongerInclude(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 332 | testReplaceLongerInclude(KFind::FindBackwards); 333 | testReplaceLongerInclude(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 334 | testReplaceLongerInclude(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 335 | 336 | testReplaceLongerInclude2(0); 337 | testReplaceLongerInclude2(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 338 | testReplaceLongerInclude2(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 339 | testReplaceLongerInclude2(KFind::FindBackwards); 340 | testReplaceLongerInclude2(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 341 | testReplaceLongerInclude2(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 342 | 343 | testReplaceBackRef(0); 344 | testReplaceBackRef(KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 345 | testReplaceBackRef(KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 346 | 347 | testReplaceBackRef(KFind::FindBackwards); 348 | testReplaceBackRef(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 349 | testReplaceBackRef(KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 350 | testReplaceBackRef(KReplaceDialog::BackReference | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 351 | testReplaceBackRef(KReplaceDialog::BackReference | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 352 | testReplaceBackRef(KReplaceDialog::BackReference | KFind::FindBackwards); 353 | testReplaceBackRef(KReplaceDialog::BackReference | KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("replaceButton")); // replace 354 | testReplaceBackRef(KReplaceDialog::BackReference | KFind::FindBackwards | KReplaceDialog::PromptOnReplace, QStringLiteral("allButton")); // replace all 355 | 356 | testReplaceBackRef1(KReplaceDialog::BackReference | KFind::RegularExpression, QStringLiteral("replaceButton")); // replace 357 | testReplaceBackRef1(KReplaceDialog::BackReference | KFind::RegularExpression, QStringLiteral("allButton")); // replace all 358 | 359 | QString text = QLatin1String("This file is part of the KDE project.\n") + QLatin1String("This library is free software; you can redistribute it and/or\n") 360 | + QLatin1String("modify it under the terms of the GNU Library General Public\n") 361 | + QLatin1String("License version 2, as published by the Free Software Foundation.\n") + QLatin1Char('\n') 362 | + QLatin1String(" This library is distributed in the hope that it will be useful,\n") 363 | + QLatin1String(" but WITHOUT ANY WARRANTY; without even the implied warranty of\n") 364 | + QLatin1String(" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n") 365 | + QLatin1String(" Library General Public License for more details.\n") + QLatin1Char('\n') 366 | + QLatin1String(" You should have received a copy of the GNU Library General Public License\n") 367 | + QLatin1String(" along with this library; see the file COPYING.LIB. If not, write to\n") 368 | + QLatin1String(" the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n") + QLatin1String(" Boston, MA 02110-1301, USA.\n") 369 | + QLatin1String("More tests:\n") + QLatin1String("ThisThis This, This. This\n") + QLatin1String("aGNU\n") + QLatin1String("free"); 370 | KReplaceTest test(text.split(QLatin1Char('\n')), QStringLiteral("0")); 371 | 372 | test.replace(QStringLiteral("GNU"), QStringLiteral("KDE"), 0); 373 | test.replace(QStringLiteral("free"), QStringLiteral("*free*"), 0); 374 | test.replace(QStringLiteral("This"), QStringLiteral("THIS*"), KFind::FindBackwards); 375 | 376 | test.print(); 377 | // return app.exec(); 378 | return 0; 379 | } 380 | 381 | #include "moc_kreplacetest.cpp" 382 | -------------------------------------------------------------------------------- /autotests/kreplacetest.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2002 David Faure 4 | 5 | SPDX-License-Identifier: LGPL-2.0-only 6 | */ 7 | 8 | #ifndef KREPLACETEST_H 9 | #define KREPLACETEST_H 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class KReplace; 16 | class KReplaceTest : public QObject 17 | { 18 | Q_OBJECT 19 | public: 20 | KReplaceTest(const QStringList &text, const QString &buttonName); 21 | ~KReplaceTest() override; 22 | 23 | void replace(const QString &pattern, const QString &replacement, long options); 24 | void print(); 25 | const QStringList &textLines() const 26 | { 27 | return m_text; 28 | } 29 | 30 | public Q_SLOTS: 31 | void slotHighlight(const QString &, int, int); 32 | void slotReplaceNext(); 33 | void slotReplace(const QString &text, int replacementIndex, int replacedLength, int matchedLength); 34 | 35 | Q_SIGNALS: 36 | void exitLoop(); 37 | 38 | private: 39 | void enterLoop(); 40 | 41 | QStringList::Iterator m_currentPos; 42 | QStringList m_text; 43 | std::unique_ptr m_replace; 44 | bool m_needEventLoop; 45 | QString m_buttonName; 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /autotests/krichtextedittest.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE libraries 3 | SPDX-FileCopyrightText: 2009 Thomas McGuire 4 | 5 | SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 6 | */ 7 | 8 | #ifndef KRICHTEXTEDITTEST_H 9 | #define KRICHTEXTEDITTEST_H 10 | 11 | #include 12 | 13 | class KRichTextEditTest : public QObject 14 | { 15 | Q_OBJECT 16 | 17 | private Q_SLOTS: 18 | void testLinebreaks(); 19 | void testUpdateLinkAdd(); 20 | void testUpdateLinkRemove(); 21 | void testHTMLLineBreaks(); 22 | void testHTMLOrderedLists(); 23 | void testHTMLUnorderedLists(); 24 | void testHeading(); 25 | void testRulerScroll(); 26 | void testNestedLists(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /autotests/ktextedit_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE libraries 3 | SPDX-FileCopyrightText: 2008 David Faure 4 | SPDX-FileCopyrightText: 2008 Stephen Kelly 5 | 6 | SPDX-License-Identifier: LGPL-2.0-or-later 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | class KTextEdit_UnitTest : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | private Q_SLOTS: 19 | void testPaste(); 20 | // These tests are probably invalid due to using invalid html. 21 | // void testImportWithHorizontalTraversal(); 22 | // void testImportWithVerticalTraversal(); 23 | // void testBrInsideParagraphThroughTextEdit(); 24 | }; 25 | 26 | void KTextEdit_UnitTest::testPaste() 27 | { 28 | const QString origText = QApplication::clipboard()->text(); 29 | const QString pastedText = QStringLiteral("Test paste from ktextedit_unittest"); 30 | QApplication::clipboard()->setText(pastedText); 31 | KTextEdit w; 32 | w.setPlainText(QStringLiteral("Hello world")); 33 | w.selectAll(); 34 | QTest::keyClick(&w, Qt::Key_V, Qt::ControlModifier); 35 | QCOMPARE(w.toPlainText(), pastedText); 36 | QApplication::clipboard()->setText(origText); 37 | } 38 | 39 | // void KTextEdit_UnitTest::testImportWithVerticalTraversal() 40 | // { 41 | // QTextEdit *te = new QTextEdit(); 42 | // 43 | // te->setHtml("

Foo




Bar

"); 44 | // 45 | // QTextCursor cursor = te->textCursor(); 46 | // cursor.movePosition(QTextCursor::Start); 47 | // QVERIFY(cursor.block().text() == QString( "Foo" )); 48 | // cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, 4); 49 | // 50 | // // Cursor is at the beginning of the block. 51 | // QVERIFY(cursor.block().position() == cursor.position()); 52 | // QVERIFY(cursor.block().text() == QString( "Bar" )); 53 | // } 54 | // 55 | // void KTextEdit_UnitTest::testImportWithHorizontalTraversal() 56 | // { 57 | // QTextEdit *te = new QTextEdit(); 58 | // 59 | // te->setHtml("

Foo


Bar

"); 60 | // 61 | // // br elements should be represented just like empty paragraphs. 62 | // 63 | // QTextCursor cursor = te->textCursor(); 64 | // cursor.movePosition(QTextCursor::Start); 65 | // QVERIFY(cursor.block().text() == QString( "Foo" )); 66 | // cursor.movePosition(QTextCursor::EndOfBlock); 67 | // cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2); 68 | // 69 | // // Cursor is at the beginning of the block. 70 | // QVERIFY(cursor.block().position() == cursor.position()); 71 | // QVERIFY(cursor.block().text() == QString( "Bar" )); 72 | // } 73 | // 74 | // void KTextEdit_UnitTest::testBrInsideParagraphThroughTextEdit() 75 | // { 76 | // QSKIP("This is worked around during export"); 77 | // QTextEdit *te = new QTextEdit(); 78 | // 79 | // te->setHtml("

Foo
Bar

"); 80 | // 81 | // // br elements inside paragraphs should be a single linebreak. 82 | // 83 | // QTextCursor cursor = te->textCursor(); 84 | // cursor.movePosition(QTextCursor::Start); 85 | // 86 | // // This doesn't work, because Qt puts Foo and Bar in the same block, separated by a QChar::LineSeparator 87 | // 88 | // QVERIFY(cursor.block().text() == QString( "Foo" )); 89 | // cursor.movePosition(QTextCursor::EndOfBlock); 90 | // cursor.movePosition(QTextCursor::Right); 91 | // 92 | // // Cursor is at the beginning of the block. 93 | // QVERIFY(cursor.block().position() == cursor.position()); 94 | // QVERIFY(cursor.block().text() == QString( "Bar" )); 95 | // 96 | // } 97 | 98 | QTEST_MAIN(KTextEdit_UnitTest) 99 | 100 | #include "ktextedit_unittest.moc" 101 | -------------------------------------------------------------------------------- /docs/pics/kfinddialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/ktextwidgets/558098f1c0ee991698e08d378b69723a13dcdd3f/docs/pics/kfinddialog.png -------------------------------------------------------------------------------- /docs/pics/kreplacedialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/ktextwidgets/558098f1c0ee991698e08d378b69723a13dcdd3f/docs/pics/kreplacedialog.png -------------------------------------------------------------------------------- /docs/pics/krichtextedit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/ktextwidgets/558098f1c0ee991698e08d378b69723a13dcdd3f/docs/pics/krichtextedit.png -------------------------------------------------------------------------------- /docs/pics/ktextedit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/ktextwidgets/558098f1c0ee991698e08d378b69723a13dcdd3f/docs/pics/ktextedit.png -------------------------------------------------------------------------------- /metainfo.yaml: -------------------------------------------------------------------------------- 1 | maintainer: mlaurent 2 | description: Advanced text editing widgets 3 | tier: 3 4 | type: functional 5 | platforms: 6 | - name: Linux 7 | - name: FreeBSD 8 | - name: Windows 9 | - name: macOS 10 | - name: Android 11 | portingAid: false 12 | deprecated: false 13 | release: true 14 | libraries: 15 | - cmake: "KF6::TextWidgets" 16 | cmakename: KF6TextWidgets 17 | 18 | public_lib: true 19 | group: Frameworks 20 | subgroup: Tier 3 21 | -------------------------------------------------------------------------------- /po/ast/ktextwidgets6.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 This file is copyright: 2 | # This file is distributed under the same license as the ktextwidgets package. 3 | # 4 | # SPDX-FileCopyrightText: 2023 Enol P. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: ktextwidgets\n" 8 | "Report-Msgid-Bugs-To: https://bugs.kde.org\n" 9 | "POT-Creation-Date: 2025-05-05 00:39+0000\n" 10 | "PO-Revision-Date: 2023-11-07 21:27+0100\n" 11 | "Last-Translator: Enol P. \n" 12 | "Language-Team: \n" 13 | "Language: ast\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=2; plural=n != 1;\n" 18 | "X-Generator: Lokalize 23.08.2\n" 19 | 20 | #: dialogs/klinkdialog.cpp:33 21 | #, kde-format 22 | msgid "Manage Link" 23 | msgstr "" 24 | 25 | #: dialogs/klinkdialog.cpp:40 26 | #, kde-format 27 | msgid "Link Text:" 28 | msgstr "" 29 | 30 | #: dialogs/klinkdialog.cpp:43 31 | #, kde-format 32 | msgid "Link URL:" 33 | msgstr "" 34 | 35 | #: findreplace/kfind.cpp:48 36 | #, kde-format 37 | msgid "Find Next" 38 | msgstr "" 39 | 40 | #: findreplace/kfind.cpp:52 41 | #, kde-format 42 | msgid "Find next occurrence of '%1'?" 43 | msgstr "" 44 | 45 | #: findreplace/kfind.cpp:573 findreplace/kfind.cpp:594 46 | #, kde-format 47 | msgid "1 match found." 48 | msgid_plural "%1 matches found." 49 | msgstr[0] "" 50 | msgstr[1] "" 51 | 52 | #: findreplace/kfind.cpp:575 53 | #, kde-format 54 | msgid "No matches found for '%1'." 55 | msgstr "" 56 | 57 | #: findreplace/kfind.cpp:596 58 | #, kde-format 59 | msgid "No matches found for '%1'." 60 | msgstr "" 61 | 62 | #: findreplace/kfind.cpp:600 findreplace/kreplace.cpp:384 63 | #, kde-format 64 | msgid "Beginning of document reached." 65 | msgstr "" 66 | 67 | #: findreplace/kfind.cpp:602 findreplace/kreplace.cpp:386 68 | #, kde-format 69 | msgid "End of document reached." 70 | msgstr "" 71 | 72 | #. i18n() of the first if() because of the plural form. 73 | #. Hope this word puzzle is ok, it's a different sentence 74 | #: findreplace/kfind.cpp:608 75 | #, kde-format 76 | msgid "Continue from the end?" 77 | msgstr "" 78 | 79 | #: findreplace/kfind.cpp:608 80 | #, kde-format 81 | msgid "Continue from the beginning?" 82 | msgstr "" 83 | 84 | #: findreplace/kfinddialog.cpp:35 85 | #, kde-format 86 | msgid "Find Text" 87 | msgstr "" 88 | 89 | #: findreplace/kfinddialog.cpp:76 90 | #, kde-format 91 | msgctxt "@title:group" 92 | msgid "Find" 93 | msgstr "" 94 | 95 | #: findreplace/kfinddialog.cpp:79 96 | #, kde-format 97 | msgid "&Text to find:" 98 | msgstr "" 99 | 100 | #: findreplace/kfinddialog.cpp:83 101 | #, kde-format 102 | msgid "Regular e&xpression" 103 | msgstr "" 104 | 105 | #: findreplace/kfinddialog.cpp:84 106 | #, kde-format 107 | msgctxt "@action:button" 108 | msgid "&Edit…" 109 | msgstr "" 110 | 111 | #: findreplace/kfinddialog.cpp:93 112 | #, kde-format 113 | msgid "Replace With" 114 | msgstr "" 115 | 116 | #: findreplace/kfinddialog.cpp:96 117 | #, kde-format 118 | msgid "Replace&ment text:" 119 | msgstr "" 120 | 121 | #: findreplace/kfinddialog.cpp:100 122 | #, kde-format 123 | msgid "Use p&laceholders" 124 | msgstr "" 125 | 126 | #: findreplace/kfinddialog.cpp:101 127 | #, kde-format 128 | msgid "Insert Place&holder" 129 | msgstr "" 130 | 131 | #: findreplace/kfinddialog.cpp:110 132 | #, kde-format 133 | msgid "Options" 134 | msgstr "Opciones" 135 | 136 | #: findreplace/kfinddialog.cpp:113 137 | #, kde-format 138 | msgid "C&ase sensitive" 139 | msgstr "" 140 | 141 | #: findreplace/kfinddialog.cpp:114 142 | #, kde-format 143 | msgid "&Whole words only" 144 | msgstr "" 145 | 146 | #: findreplace/kfinddialog.cpp:115 147 | #, kde-format 148 | msgid "From c&ursor" 149 | msgstr "" 150 | 151 | #: findreplace/kfinddialog.cpp:116 152 | #, kde-format 153 | msgid "Find &backwards" 154 | msgstr "" 155 | 156 | #: findreplace/kfinddialog.cpp:117 157 | #, kde-format 158 | msgid "&Selected text" 159 | msgstr "" 160 | 161 | #: findreplace/kfinddialog.cpp:124 162 | #, kde-format 163 | msgid "&Prompt on replace" 164 | msgstr "" 165 | 166 | #: findreplace/kfinddialog.cpp:204 167 | #, kde-format 168 | msgid "&Replace" 169 | msgstr "" 170 | 171 | #: findreplace/kfinddialog.cpp:206 172 | #, kde-format 173 | msgid "Start replace" 174 | msgstr "" 175 | 176 | #: findreplace/kfinddialog.cpp:207 177 | #, kde-format 178 | msgid "" 179 | "If you press the Replace button, the text you entered above is " 180 | "searched for within the document and any occurrence is replaced with the " 181 | "replacement text." 182 | msgstr "" 183 | 184 | #: findreplace/kfinddialog.cpp:212 185 | #, kde-format 186 | msgid "&Find" 187 | msgstr "" 188 | 189 | #: findreplace/kfinddialog.cpp:214 190 | #, kde-format 191 | msgid "Start searching" 192 | msgstr "" 193 | 194 | #: findreplace/kfinddialog.cpp:215 195 | #, kde-format 196 | msgid "" 197 | "If you press the Find button, the text you entered above is " 198 | "searched for within the document." 199 | msgstr "" 200 | 201 | #: findreplace/kfinddialog.cpp:220 202 | #, kde-format 203 | msgid "" 204 | "Enter a pattern to search for, or select a previous pattern from the list." 205 | msgstr "" 206 | 207 | #: findreplace/kfinddialog.cpp:221 208 | #, kde-format 209 | msgid "If enabled, search for a regular expression." 210 | msgstr "" 211 | 212 | #: findreplace/kfinddialog.cpp:222 213 | #, kde-format 214 | msgid "Click here to edit your regular expression using a graphical editor." 215 | msgstr "" 216 | 217 | #: findreplace/kfinddialog.cpp:223 218 | #, kde-format 219 | msgid "Enter a replacement string, or select a previous one from the list." 220 | msgstr "" 221 | 222 | #: findreplace/kfinddialog.cpp:225 223 | #, kde-format 224 | msgid "" 225 | "If enabled, any occurrence of \\N, where N is an integer number, will be replaced with the corresponding " 227 | "capture (\"parenthesized substring\") from the pattern.

To include (a " 228 | "literal \\N in your replacement, put an extra backslash " 229 | "in front of it, like \\\\N.

" 230 | msgstr "" 231 | 232 | #: findreplace/kfinddialog.cpp:231 233 | #, kde-format 234 | msgid "Click for a menu of available captures." 235 | msgstr "" 236 | 237 | #: findreplace/kfinddialog.cpp:232 238 | #, kde-format 239 | msgid "Require word boundaries in both ends of a match to succeed." 240 | msgstr "" 241 | 242 | #: findreplace/kfinddialog.cpp:233 243 | #, kde-format 244 | msgid "Start searching at the current cursor location rather than at the top." 245 | msgstr "" 246 | 247 | #: findreplace/kfinddialog.cpp:234 248 | #, kde-format 249 | msgid "Only search within the current selection." 250 | msgstr "" 251 | 252 | #: findreplace/kfinddialog.cpp:235 253 | #, kde-format 254 | msgid "" 255 | "Perform a case sensitive search: entering the pattern 'Joe' will not match " 256 | "'joe' or 'JOE', only 'Joe'." 257 | msgstr "" 258 | 259 | #: findreplace/kfinddialog.cpp:236 260 | #, kde-format 261 | msgid "Search backwards." 262 | msgstr "" 263 | 264 | #: findreplace/kfinddialog.cpp:237 265 | #, kde-format 266 | msgid "Ask before replacing each match found." 267 | msgstr "" 268 | 269 | #: findreplace/kfinddialog.cpp:459 270 | msgid "Any Character" 271 | msgstr "" 272 | 273 | #: findreplace/kfinddialog.cpp:460 274 | msgid "Start of Line" 275 | msgstr "" 276 | 277 | #: findreplace/kfinddialog.cpp:461 278 | msgid "End of Line" 279 | msgstr "" 280 | 281 | #: findreplace/kfinddialog.cpp:462 282 | msgid "Set of Characters" 283 | msgstr "" 284 | 285 | #: findreplace/kfinddialog.cpp:463 286 | msgid "Repeats, Zero or More Times" 287 | msgstr "" 288 | 289 | #: findreplace/kfinddialog.cpp:464 290 | msgid "Repeats, One or More Times" 291 | msgstr "" 292 | 293 | #: findreplace/kfinddialog.cpp:465 294 | msgid "Optional" 295 | msgstr "" 296 | 297 | #: findreplace/kfinddialog.cpp:466 298 | msgid "Escape" 299 | msgstr "" 300 | 301 | #: findreplace/kfinddialog.cpp:467 302 | msgid "TAB" 303 | msgstr "" 304 | 305 | #: findreplace/kfinddialog.cpp:468 306 | msgid "Newline" 307 | msgstr "" 308 | 309 | #: findreplace/kfinddialog.cpp:469 310 | msgid "Carriage Return" 311 | msgstr "" 312 | 313 | #: findreplace/kfinddialog.cpp:470 314 | msgid "White Space" 315 | msgstr "" 316 | 317 | #: findreplace/kfinddialog.cpp:471 318 | msgid "Digit" 319 | msgstr "" 320 | 321 | #: findreplace/kfinddialog.cpp:579 322 | #, kde-format 323 | msgid "Complete Match" 324 | msgstr "" 325 | 326 | #: findreplace/kfinddialog.cpp:583 327 | #, kde-format 328 | msgid "Captured Text (%1)" 329 | msgstr "" 330 | 331 | #: findreplace/kfinddialog.cpp:593 332 | #, kde-format 333 | msgid "You must enter some text to search for." 334 | msgstr "" 335 | 336 | #: findreplace/kfinddialog.cpp:600 337 | #, kde-format 338 | msgid "Invalid PCRE pattern syntax." 339 | msgstr "" 340 | 341 | #: findreplace/kreplace.cpp:48 findreplace/kreplace.cpp:59 342 | #, kde-format 343 | msgid "Replace" 344 | msgstr "" 345 | 346 | #: findreplace/kreplace.cpp:55 347 | #, kde-format 348 | msgctxt "@action:button Replace all occurrences" 349 | msgid "&All" 350 | msgstr "" 351 | 352 | #: findreplace/kreplace.cpp:57 353 | #, kde-format 354 | msgid "&Skip" 355 | msgstr "" 356 | 357 | #: findreplace/kreplace.cpp:76 358 | #, kde-format 359 | msgid "Replace '%1' with '%2'?" 360 | msgstr "" 361 | 362 | #: findreplace/kreplace.cpp:178 findreplace/kreplace.cpp:378 363 | #, kde-format 364 | msgid "No text was replaced." 365 | msgstr "" 366 | 367 | #: findreplace/kreplace.cpp:180 findreplace/kreplace.cpp:380 368 | #, kde-format 369 | msgid "1 replacement done." 370 | msgid_plural "%1 replacements done." 371 | msgstr[0] "" 372 | msgstr[1] "" 373 | 374 | #: findreplace/kreplace.cpp:393 375 | #, kde-format 376 | msgid "Do you want to restart search from the end?" 377 | msgstr "" 378 | 379 | #: findreplace/kreplace.cpp:393 380 | #, kde-format 381 | msgid "Do you want to restart search at the beginning?" 382 | msgstr "" 383 | 384 | #: findreplace/kreplace.cpp:398 385 | #, kde-format 386 | msgctxt "@action:button Restart find & replace" 387 | msgid "Restart" 388 | msgstr "" 389 | 390 | #: findreplace/kreplace.cpp:399 391 | #, kde-format 392 | msgctxt "@action:button Stop find & replace" 393 | msgid "Stop" 394 | msgstr "" 395 | 396 | #: findreplace/kreplacedialog.cpp:153 397 | #, kde-format 398 | msgid "Your replacement string is referencing a capture greater than '\\%1', " 399 | msgstr "" 400 | 401 | #: findreplace/kreplacedialog.cpp:154 402 | #, kde-format 403 | msgid "but your pattern only defines 1 capture." 404 | msgid_plural "but your pattern only defines %1 captures." 405 | msgstr[0] "" 406 | msgstr[1] "" 407 | 408 | #: findreplace/kreplacedialog.cpp:155 409 | #, kde-format 410 | msgid "but your pattern defines no captures." 411 | msgstr "" 412 | 413 | #: findreplace/kreplacedialog.cpp:156 414 | #, kde-format 415 | msgid "" 416 | "\n" 417 | "Please correct." 418 | msgstr "" 419 | 420 | #: widgets/krichtextwidget.cpp:200 421 | #, kde-format 422 | msgctxt "@action" 423 | msgid "Text &Color…" 424 | msgstr "" 425 | 426 | #: widgets/krichtextwidget.cpp:201 427 | #, kde-format 428 | msgctxt "@label stroke color" 429 | msgid "Color" 430 | msgstr "" 431 | 432 | #: widgets/krichtextwidget.cpp:214 433 | #, kde-format 434 | msgctxt "@action" 435 | msgid "Text &Highlight…" 436 | msgstr "" 437 | 438 | #: widgets/krichtextwidget.cpp:227 439 | #, kde-format 440 | msgctxt "@action" 441 | msgid "&Font" 442 | msgstr "" 443 | 444 | #: widgets/krichtextwidget.cpp:237 445 | #, kde-format 446 | msgctxt "@action" 447 | msgid "Font &Size" 448 | msgstr "" 449 | 450 | #: widgets/krichtextwidget.cpp:246 451 | #, kde-format 452 | msgctxt "@action boldify selected text" 453 | msgid "&Bold" 454 | msgstr "" 455 | 456 | #: widgets/krichtextwidget.cpp:260 457 | #, kde-format 458 | msgctxt "@action italicize selected text" 459 | msgid "&Italic" 460 | msgstr "" 461 | 462 | #: widgets/krichtextwidget.cpp:274 463 | #, kde-format 464 | msgctxt "@action underline selected text" 465 | msgid "&Underline" 466 | msgstr "" 467 | 468 | #: widgets/krichtextwidget.cpp:287 469 | #, kde-format 470 | msgctxt "@action" 471 | msgid "&Strike Out" 472 | msgstr "" 473 | 474 | #: widgets/krichtextwidget.cpp:301 475 | #, kde-format 476 | msgctxt "@action" 477 | msgid "Align &Left" 478 | msgstr "" 479 | 480 | #: widgets/krichtextwidget.cpp:302 481 | #, kde-format 482 | msgctxt "@label left justify" 483 | msgid "Left" 484 | msgstr "" 485 | 486 | #: widgets/krichtextwidget.cpp:307 487 | #, kde-format 488 | msgctxt "@action" 489 | msgid "Align &Center" 490 | msgstr "" 491 | 492 | #: widgets/krichtextwidget.cpp:308 493 | #, kde-format 494 | msgctxt "@label center justify" 495 | msgid "Center" 496 | msgstr "" 497 | 498 | #: widgets/krichtextwidget.cpp:313 499 | #, kde-format 500 | msgctxt "@action" 501 | msgid "Align &Right" 502 | msgstr "" 503 | 504 | #: widgets/krichtextwidget.cpp:314 505 | #, kde-format 506 | msgctxt "@label right justify" 507 | msgid "Right" 508 | msgstr "" 509 | 510 | #: widgets/krichtextwidget.cpp:319 511 | #, kde-format 512 | msgctxt "@action" 513 | msgid "&Justify" 514 | msgstr "" 515 | 516 | #: widgets/krichtextwidget.cpp:320 517 | #, kde-format 518 | msgctxt "@label justify fill" 519 | msgid "Justify" 520 | msgstr "" 521 | 522 | #: widgets/krichtextwidget.cpp:338 523 | #, kde-format 524 | msgctxt "@action" 525 | msgid "Left-to-Right" 526 | msgstr "" 527 | 528 | #: widgets/krichtextwidget.cpp:339 529 | #, kde-format 530 | msgctxt "@label left-to-right" 531 | msgid "Left-to-Right" 532 | msgstr "" 533 | 534 | #: widgets/krichtextwidget.cpp:344 535 | #, kde-format 536 | msgctxt "@action" 537 | msgid "Right-to-Left" 538 | msgstr "" 539 | 540 | #: widgets/krichtextwidget.cpp:345 541 | #, kde-format 542 | msgctxt "@label right-to-left" 543 | msgid "Right-to-Left" 544 | msgstr "" 545 | 546 | #: widgets/krichtextwidget.cpp:359 547 | #, kde-format 548 | msgctxt "@title:menu" 549 | msgid "List Style" 550 | msgstr "" 551 | 552 | #: widgets/krichtextwidget.cpp:362 553 | #, kde-format 554 | msgctxt "@item:inmenu no list style" 555 | msgid "None" 556 | msgstr "" 557 | 558 | #: widgets/krichtextwidget.cpp:363 559 | #, kde-format 560 | msgctxt "@item:inmenu disc list style" 561 | msgid "Disc" 562 | msgstr "" 563 | 564 | #: widgets/krichtextwidget.cpp:364 565 | #, kde-format 566 | msgctxt "@item:inmenu circle list style" 567 | msgid "Circle" 568 | msgstr "" 569 | 570 | #: widgets/krichtextwidget.cpp:365 571 | #, kde-format 572 | msgctxt "@item:inmenu square list style" 573 | msgid "Square" 574 | msgstr "" 575 | 576 | #: widgets/krichtextwidget.cpp:366 577 | #, kde-format 578 | msgctxt "@item:inmenu numbered lists" 579 | msgid "123" 580 | msgstr "" 581 | 582 | #: widgets/krichtextwidget.cpp:367 583 | #, kde-format 584 | msgctxt "@item:inmenu lowercase abc lists" 585 | msgid "abc" 586 | msgstr "" 587 | 588 | #: widgets/krichtextwidget.cpp:368 589 | #, kde-format 590 | msgctxt "@item:inmenu uppercase abc lists" 591 | msgid "ABC" 592 | msgstr "" 593 | 594 | #: widgets/krichtextwidget.cpp:369 595 | #, kde-format 596 | msgctxt "@item:inmenu lower case roman numerals" 597 | msgid "i ii iii" 598 | msgstr "" 599 | 600 | #: widgets/krichtextwidget.cpp:370 601 | #, kde-format 602 | msgctxt "@item:inmenu upper case roman numerals" 603 | msgid "I II III" 604 | msgstr "" 605 | 606 | #: widgets/krichtextwidget.cpp:390 607 | #, kde-format 608 | msgctxt "@action" 609 | msgid "Increase Indent" 610 | msgstr "" 611 | 612 | #: widgets/krichtextwidget.cpp:402 613 | #, kde-format 614 | msgctxt "@action" 615 | msgid "Decrease Indent" 616 | msgstr "" 617 | 618 | #: widgets/krichtextwidget.cpp:414 619 | #, kde-format 620 | msgctxt "@action" 621 | msgid "Insert Rule Line" 622 | msgstr "" 623 | 624 | #: widgets/krichtextwidget.cpp:423 625 | #, kde-format 626 | msgctxt "@action" 627 | msgid "Link" 628 | msgstr "" 629 | 630 | #: widgets/krichtextwidget.cpp:435 631 | #, kde-format 632 | msgctxt "@action" 633 | msgid "Format Painter" 634 | msgstr "" 635 | 636 | #: widgets/krichtextwidget.cpp:447 637 | #, kde-format 638 | msgctxt "@action" 639 | msgid "To Plain Text" 640 | msgstr "" 641 | 642 | #: widgets/krichtextwidget.cpp:456 643 | #, kde-format 644 | msgctxt "@action" 645 | msgid "Subscript" 646 | msgstr "" 647 | 648 | #: widgets/krichtextwidget.cpp:461 649 | #, kde-format 650 | msgctxt "@action" 651 | msgid "Superscript" 652 | msgstr "" 653 | 654 | #: widgets/krichtextwidget.cpp:473 655 | #, kde-format 656 | msgctxt "@title:menu" 657 | msgid "Heading Level" 658 | msgstr "" 659 | 660 | #: widgets/krichtextwidget.cpp:474 661 | #, kde-format 662 | msgctxt "@item:inmenu no heading" 663 | msgid "Basic text" 664 | msgstr "" 665 | 666 | #: widgets/krichtextwidget.cpp:475 667 | #, kde-format 668 | msgctxt "@item:inmenu heading level 1 (largest)" 669 | msgid "Title" 670 | msgstr "" 671 | 672 | #: widgets/krichtextwidget.cpp:476 673 | #, kde-format 674 | msgctxt "@item:inmenu heading level 2" 675 | msgid "Subtitle" 676 | msgstr "" 677 | 678 | #: widgets/krichtextwidget.cpp:477 679 | #, kde-format 680 | msgctxt "@item:inmenu heading level 3" 681 | msgid "Section" 682 | msgstr "" 683 | 684 | #: widgets/krichtextwidget.cpp:478 685 | #, kde-format 686 | msgctxt "@item:inmenu heading level 4" 687 | msgid "Subsection" 688 | msgstr "" 689 | 690 | #: widgets/krichtextwidget.cpp:479 691 | #, kde-format 692 | msgctxt "@item:inmenu heading level 5" 693 | msgid "Paragraph" 694 | msgstr "" 695 | 696 | #: widgets/krichtextwidget.cpp:480 697 | #, kde-format 698 | msgctxt "@item:inmenu heading level 6 (smallest)" 699 | msgid "Subparagraph" 700 | msgstr "" 701 | 702 | #: widgets/ktextedit.cpp:47 703 | #, kde-format 704 | msgid "Nothing to spell check." 705 | msgstr "" 706 | 707 | #: widgets/ktextedit.cpp:501 708 | #, kde-format 709 | msgctxt "@action:inmenu" 710 | msgid "Check Spelling…" 711 | msgstr "" 712 | 713 | #: widgets/ktextedit.cpp:506 714 | #, kde-format 715 | msgid "Spell Checking Language" 716 | msgstr "" 717 | 718 | #: widgets/ktextedit.cpp:527 719 | #, kde-format 720 | msgid "Auto Spell Check" 721 | msgstr "" 722 | 723 | #: widgets/ktextedit.cpp:533 724 | #, kde-format 725 | msgid "Allow Tabulations" 726 | msgstr "" 727 | 728 | #: widgets/ktextedit.cpp:566 729 | #, kde-format 730 | msgid "Speak Text" 731 | msgstr "" 732 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(KF6TextWidgets) 2 | add_library(KF6::TextWidgets ALIAS KF6TextWidgets) 3 | 4 | set_target_properties(KF6TextWidgets PROPERTIES 5 | VERSION ${KTEXTWIDGETS_VERSION} 6 | SOVERSION ${KTEXTWIDGETS_SOVERSION} 7 | EXPORT_NAME TextWidgets 8 | ) 9 | 10 | target_sources(KF6TextWidgets PRIVATE 11 | dialogs/klinkdialog.cpp 12 | dialogs/klinkdialog_p.h 13 | findreplace/kfind.cpp 14 | findreplace/kfinddialog.cpp 15 | findreplace/kfinddialog.h 16 | findreplace/kfinddialog_p.h 17 | findreplace/kfind.h 18 | findreplace/kfind_p.h 19 | findreplace/kreplace.cpp 20 | findreplace/kreplacedialog.cpp 21 | findreplace/kreplacedialog.h 22 | findreplace/kreplace.h 23 | widgets/kpluralhandlingspinbox.cpp 24 | widgets/kpluralhandlingspinbox.h 25 | widgets/krichtextedit.cpp 26 | widgets/krichtextedit.h 27 | widgets/krichtextedit_p.h 28 | widgets/krichtextwidget.cpp 29 | widgets/krichtextwidget.h 30 | widgets/ktextedit.cpp 31 | widgets/ktextedit.h 32 | widgets/ktextedit_p.h 33 | widgets/nestedlisthelper.cpp 34 | widgets/nestedlisthelper_p.h 35 | 36 | ) 37 | 38 | ecm_generate_export_header(KF6TextWidgets 39 | BASE_NAME KTextWidgets 40 | GROUP_BASE_NAME KF 41 | VERSION ${KF_VERSION} 42 | USE_VERSION_HEADER 43 | DEPRECATED_BASE_VERSION 0 44 | DEPRECATION_VERSIONS 6.6 45 | EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} 46 | ) 47 | 48 | set(ktextwidgets_INCLUDES 49 | ${CMAKE_CURRENT_SOURCE_DIR}/dialogs 50 | ${CMAKE_CURRENT_SOURCE_DIR}/findreplace 51 | ${CMAKE_CURRENT_SOURCE_DIR}/widgets 52 | ) 53 | target_include_directories(KF6TextWidgets PUBLIC "$" 54 | ) 55 | 56 | target_include_directories(KF6TextWidgets INTERFACE "$") 57 | 58 | target_link_libraries(KF6TextWidgets 59 | PUBLIC 60 | Qt6::Widgets 61 | KF6::SonnetUi 62 | KF6::I18n 63 | PRIVATE 64 | KF6::SonnetCore 65 | KF6::WidgetsAddons 66 | KF6::Completion 67 | KF6::ConfigGui 68 | ) 69 | 70 | if (Qt6TextToSpeech_FOUND) 71 | target_link_libraries(KF6TextWidgets 72 | PRIVATE 73 | Qt6::TextToSpeech) 74 | endif() 75 | 76 | ecm_generate_headers(KTextWidgets_HEADERS 77 | HEADER_NAMES 78 | KRichTextEdit 79 | KRichTextWidget 80 | KTextEdit 81 | KPluralHandlingSpinBox 82 | 83 | RELATIVE widgets 84 | REQUIRED_HEADERS KTextWidgets_HEADERS 85 | ) 86 | ecm_generate_headers(KTextWidgets_HEADERS 87 | HEADER_NAMES 88 | KFind 89 | KFindDialog 90 | KReplace 91 | KReplaceDialog 92 | 93 | RELATIVE findreplace 94 | REQUIRED_HEADERS KTextWidgets_HEADERS 95 | ) 96 | 97 | install(TARGETS KF6TextWidgets EXPORT KF6TextWidgetsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) 98 | 99 | install(FILES 100 | ${CMAKE_CURRENT_BINARY_DIR}/ktextwidgets_export.h 101 | ${KTextWidgets_HEADERS} 102 | DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KTextWidgets COMPONENT Devel 103 | ) 104 | 105 | if (BUILD_DESIGNERPLUGIN) 106 | add_subdirectory(designer) 107 | endif() 108 | 109 | ecm_generate_qdoc(KF6TextWidgets ktextwidgets.qdocconf) 110 | -------------------------------------------------------------------------------- /src/Messages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources. 4 | # The results are stored in a pseudo .cpp file to be picked up by xgettext. 5 | lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` 6 | if [ -n "$lst" ] ; then 7 | $EXTRACTRC $lst >> rc.cpp 8 | fi 9 | 10 | # Extract strings from all source files. 11 | # If your framework depends on KI18n, use $XGETTEXT. If it uses Qt translation 12 | # system, use $EXTRACT_TR_STRINGS. 13 | $XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/ktextwidgets6.pot 14 | -------------------------------------------------------------------------------- /src/designer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(ECMAddQtDesignerPlugin) 2 | 3 | ecm_qtdesignerplugin_widget(KRichTextEdit 4 | TOOLTIP "Rich Text Editor (KF6)" 5 | GROUP "Input (KF6)" 6 | ) 7 | ecm_qtdesignerplugin_widget(KRichTextWidget 8 | TOOLTIP "Rich Text Widget (KF6)" 9 | GROUP "Input (KF6)" 10 | ) 11 | ecm_qtdesignerplugin_widget(KTextEdit 12 | TOOLTIP "Improved QTextEdit (KF6)" 13 | WHATSTHIS "An improved version of the QTextEdit with mail or system browser invocation support" 14 | GROUP "Input (KF6)" 15 | ) 16 | ecm_qtdesignerplugin_widget(KPluralHandlingSpinBox 17 | TOOLTIP "A QSpinBox with plural handling for the suffix (KF6)" 18 | WHATSTHIS "A QSpinBox with plural handling for the suffix" 19 | GROUP "Input (KF6)" 20 | ) 21 | 22 | ecm_add_qtdesignerplugin(ktextwidgetswidgets 23 | NAME KTextWidgetsWidgets 24 | OUTPUT_NAME ktextwidgets6widgets 25 | WIDGETS 26 | KRichTextEdit 27 | KRichTextWidget 28 | KTextEdit 29 | KPluralHandlingSpinBox 30 | LINK_LIBRARIES 31 | KF6::TextWidgets 32 | INSTALL_DESTINATION "${KDE_INSTALL_QTPLUGINDIR}/designer" 33 | COMPONENT Devel 34 | ) 35 | -------------------------------------------------------------------------------- /src/designer/pics/ktextedit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/ktextwidgets/558098f1c0ee991698e08d378b69723a13dcdd3f/src/designer/pics/ktextedit.png -------------------------------------------------------------------------------- /src/dialogs/klinkdialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | klinkdialog 3 | SPDX-FileCopyrightText: 2008 Stephen Kelly 4 | 5 | SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #include "klinkdialog_p.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class KLinkDialogPrivate 20 | { 21 | public: 22 | QLabel *textLabel = nullptr; 23 | QLineEdit *textLineEdit = nullptr; 24 | QLabel *linkUrlLabel = nullptr; 25 | QLineEdit *linkUrlLineEdit = nullptr; 26 | QDialogButtonBox *buttonBox = nullptr; 27 | }; 28 | 29 | KLinkDialog::KLinkDialog(QWidget *parent) 30 | : QDialog(parent) 31 | , d(new KLinkDialogPrivate) 32 | { 33 | setWindowTitle(i18n("Manage Link")); 34 | setModal(true); 35 | 36 | QVBoxLayout *layout = new QVBoxLayout(this); 37 | 38 | QGridLayout *grid = new QGridLayout; 39 | 40 | d->textLabel = new QLabel(i18n("Link Text:"), this); 41 | d->textLineEdit = new QLineEdit(this); 42 | d->textLineEdit->setClearButtonEnabled(true); 43 | d->linkUrlLabel = new QLabel(i18n("Link URL:"), this); 44 | d->linkUrlLineEdit = new QLineEdit(this); 45 | d->linkUrlLineEdit->setClearButtonEnabled(true); 46 | 47 | grid->addWidget(d->textLabel, 0, 0); 48 | grid->addWidget(d->textLineEdit, 0, 1); 49 | grid->addWidget(d->linkUrlLabel, 1, 0); 50 | grid->addWidget(d->linkUrlLineEdit, 1, 1); 51 | 52 | layout->addLayout(grid); 53 | 54 | d->buttonBox = new QDialogButtonBox(this); 55 | d->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 56 | connect(d->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 57 | connect(d->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 58 | layout->addWidget(d->buttonBox); 59 | 60 | d->textLineEdit->setFocus(); 61 | d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 62 | connect(d->textLineEdit, &QLineEdit::textChanged, this, &KLinkDialog::slotTextChanged); 63 | } 64 | 65 | KLinkDialog::~KLinkDialog() 66 | { 67 | delete d; 68 | } 69 | 70 | void KLinkDialog::slotTextChanged(const QString &text) 71 | { 72 | d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.trimmed().isEmpty()); 73 | } 74 | 75 | void KLinkDialog::setLinkText(const QString &linkText) 76 | { 77 | d->textLineEdit->setText(linkText); 78 | if (!linkText.trimmed().isEmpty()) { 79 | d->linkUrlLineEdit->setFocus(); 80 | } 81 | } 82 | 83 | void KLinkDialog::setLinkUrl(const QString &linkUrl) 84 | { 85 | d->linkUrlLineEdit->setText(linkUrl); 86 | } 87 | 88 | QString KLinkDialog::linkText() const 89 | { 90 | return d->textLineEdit->text().trimmed(); 91 | } 92 | 93 | QString KLinkDialog::linkUrl() const 94 | { 95 | return d->linkUrlLineEdit->text(); 96 | } 97 | 98 | #include "moc_klinkdialog_p.cpp" 99 | -------------------------------------------------------------------------------- /src/dialogs/klinkdialog_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | klinkdialog 3 | SPDX-FileCopyrightText: 2008 Stephen Kelly 4 | 5 | SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #ifndef KLINKDIALOG_H 9 | #define KLINKDIALOG_H 10 | 11 | #include 12 | 13 | class KLinkDialogPrivate; 14 | class QString; 15 | 16 | /*! 17 | \brief Dialog to allow user to configure a hyperlink. 18 | \since 4.1 19 | \internal 20 | 21 | This class provides a dialog to ask the user for a link target url and 22 | text. 23 | 24 | The size of the dialog is automatically saved to and restored from the 25 | global KDE config file. 26 | */ 27 | class KLinkDialog : public QDialog 28 | { 29 | Q_OBJECT 30 | public: 31 | /*! 32 | * Create a link dialog. 33 | * 34 | * \a parent Parent widget. 35 | */ 36 | explicit KLinkDialog(QWidget *parent = nullptr); 37 | 38 | /*! 39 | * Destructor 40 | */ 41 | ~KLinkDialog() override; 42 | 43 | /*! 44 | * Returns the link text shown in the dialog 45 | * 46 | * \a linkText The initial text 47 | */ 48 | void setLinkText(const QString &linkText); 49 | 50 | /*! 51 | * Sets the target link url shown in the dialog 52 | * 53 | * \a linkUrl The initial link target url 54 | */ 55 | void setLinkUrl(const QString &linkUrl); 56 | 57 | /*! 58 | * Returns the link text entered by the user. 59 | * Returns The link text 60 | */ 61 | QString linkText() const; 62 | 63 | /*! 64 | * Returns the target link url entered by the user. 65 | * Returns The link url 66 | */ 67 | QString linkUrl() const; 68 | 69 | private Q_SLOTS: 70 | void slotTextChanged(const QString &); 71 | 72 | private: 73 | KLinkDialogPrivate *const d; 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/findreplace/kfind.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 6 | 7 | SPDX-License-Identifier: LGPL-2.0-only 8 | */ 9 | 10 | #ifndef KFIND_H 11 | #define KFIND_H 12 | 13 | #include "ktextwidgets_export.h" 14 | 15 | #include 16 | #include 17 | 18 | class QDialog; 19 | class KFindPrivate; 20 | 21 | /*! 22 | * \class KFind 23 | * \inmodule KTextWidgets 24 | * 25 | * \brief A generic implementation of the "find" function. 26 | * 27 | * \b Detail: 28 | * 29 | * This class includes prompt handling etc. Also provides some 30 | * static functions which can be used to create custom behavior 31 | * instead of using the class directly. 32 | * 33 | * \b Example: 34 | * 35 | * To use the class to implement a complete find feature: 36 | * 37 | * In the slot connected to the find action, after using KFindDialog: 38 | * \code 39 | * 40 | * // This creates a find-next-prompt dialog if needed. 41 | * m_find = new KFind(pattern, options, this); 42 | * 43 | * // Connect textFound() signal to code which handles highlighting of found text. 44 | * connect(m_find, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) { 45 | * slotHighlight(text, matchingIndex, matchedLength); 46 | * })); 47 | * // Connect findNext signal - called when pressing the button in the dialog 48 | * connect(m_find, SIGNAL(findNext()), 49 | * this, SLOT(slotFindNext())); 50 | * \endcode 51 | * 52 | * Then initialize the variables determining the "current position" 53 | * (to the cursor, if the option FromCursor is set, 54 | * to the beginning of the selection if the option SelectedText is set, 55 | * and to the beginning of the document otherwise). 56 | * Initialize the "end of search" variables as well (end of doc or end of selection). 57 | * Swap begin and end if FindBackwards. 58 | * Finally, call slotFindNext(); 59 | * 60 | * \code 61 | * void slotFindNext() 62 | * { 63 | * KFind::Result res = KFind::NoMatch; 64 | * while (res == KFind::NoMatch && ) { 65 | * if (m_find->needData()) 66 | * m_find->setData(); 67 | * 68 | * // Let KFind inspect the text fragment, and display a dialog if a match is found 69 | * res = m_find->find(); 70 | * 71 | * if (res == KFind::NoMatch) { 72 | * 73 | * } 74 | * } 75 | * 76 | * if (res == KFind::NoMatch) // i.e. at end 77 | * displayFinalDialog(); m_find->deleteLater(); m_find = nullptr; 78 | * or if (m_find->shouldRestart()) { reinit (w/o FromCursor); m_find->resetCounts(); slotFindNext(); } 79 | * else { m_find->closeFindNextDialog(); }> 80 | * } 81 | * \endcode 82 | * 83 | * Don't forget to delete m_find in the destructor of your class, 84 | * unless you gave it a parent widget on construction. 85 | * 86 | * This implementation allows to have a "Find Next" action, which resumes the 87 | * search, even if the user closed the "Find Next" dialog. 88 | * 89 | * A "Find Previous" action can simply switch temporarily the value of 90 | * FindBackwards and call slotFindNext() - and reset the value afterwards. 91 | */ 92 | class KTEXTWIDGETS_EXPORT KFind : public QObject 93 | { 94 | Q_OBJECT 95 | 96 | public: 97 | /*! 98 | * \enum KFind::Options 99 | * 100 | * \value WholeWordsOnly 101 | * Match whole words only. 102 | * \value FromCursor 103 | * Start from current cursor position. 104 | * \value SelectedText 105 | * Only search selected area. 106 | * \value CaseSensitive 107 | * Consider case when matching. 108 | * \value FindBackwards 109 | * Go backwards. 110 | * \value RegularExpression 111 | * Interpret the pattern as a regular expression. 112 | * \value FindIncremental 113 | * Find incremental. 114 | * \value MinimumUserOption 115 | * User options start with this bit 116 | */ 117 | enum Options { 118 | WholeWordsOnly = 1, 119 | FromCursor = 2, 120 | SelectedText = 4, 121 | CaseSensitive = 8, 122 | FindBackwards = 16, 123 | RegularExpression = 32, 124 | FindIncremental = 64, 125 | MinimumUserOption = 65536, 126 | }; 127 | Q_DECLARE_FLAGS(SearchOptions, Options) 128 | 129 | /*! 130 | * Only use this constructor if you don't use KFindDialog, or if 131 | * you use it as a modal dialog. 132 | */ 133 | KFind(const QString &pattern, long options, QWidget *parent); 134 | 135 | /*! 136 | * This is the recommended constructor if you also use KFindDialog (non-modal). 137 | * You should pass the pointer to it here, so that when a message box 138 | * appears it has the right parent. Don't worry about deletion, KFind 139 | * will notice if the find dialog is closed. 140 | */ 141 | KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog); 142 | ~KFind() override; 143 | 144 | /*! 145 | * \value NoMatch 146 | * \value Match 147 | */ 148 | enum Result { 149 | NoMatch, 150 | Match, 151 | }; 152 | 153 | /*! 154 | * Returns true if the application must supply a new text fragment 155 | * It also means the last call returned "NoMatch". But by storing this here 156 | * the application doesn't have to store it in a member variable (between 157 | * calls to slotFindNext()). 158 | */ 159 | bool needData() const; 160 | 161 | /*! 162 | * Call this when needData returns true, before calling find(). 163 | * 164 | * \a data is the text fragment (line). 165 | * 166 | * \a startPos if set, the index at which the search should start. 167 | * 168 | * Usually \a startPos is only necessary for the very first call to setData, 169 | * for the 'find in selection' feature. The default value of -1 170 | * means "process all the data", i.e. either 0 or data.length()-1 depending 171 | * on FindBackwards. 172 | */ 173 | void setData(const QString &data, int startPos = -1); 174 | 175 | /*! 176 | * Call this when needData returns true, before calling find(). The use of 177 | * ID's is especially useful if you're using the FindIncremental option. 178 | * 179 | * \a id the id of the text fragment. 180 | * 181 | * \a data the text fragment (line). 182 | * 183 | * \a startPos if set, the index at which the search should start. 184 | * This is only necessary for the very first call to setData usually, 185 | * for the 'find in selection' feature. A value of -1 (the default value) 186 | * means "process all the data", i.e. either 0 or data.length()-1 depending 187 | * on FindBackwards. 188 | */ 189 | void setData(int id, const QString &data, int startPos = -1); 190 | 191 | /*! 192 | * Walk the text fragment (e.g. in a text-processor line or spreadsheet 193 | * cell ...etc) looking for matches. 194 | * For each match, emits the textFound() signal and displays the find-again 195 | * dialog to ask if the user wants to find the same text again. 196 | */ 197 | Result find(); 198 | 199 | /*! 200 | * Return the current options. 201 | * 202 | * Warning: this is usually the same value as the one passed to the constructor, 203 | * but options might change _during_ the replace operation: 204 | * e.g. the "All" button resets the PromptOnReplace flag. 205 | * 206 | * \sa KFind::Options 207 | */ 208 | long options() const; 209 | 210 | /*! 211 | * Set new \a options. Usually this is used for setting or clearing the 212 | * FindBackwards options. 213 | * 214 | * \sa KFind::Options 215 | */ 216 | virtual void setOptions(long options); 217 | 218 | /*! 219 | * Returns the pattern we're currently looking for 220 | */ 221 | QString pattern() const; 222 | 223 | /*! 224 | * Sets the \a pattern to look for 225 | */ 226 | void setPattern(const QString &pattern); 227 | 228 | /*! 229 | * Returns the number of matches found (i.e. the number of times the textFound() 230 | * signal was emitted). 231 | * If 0, can be used in a dialog box to tell the user "no match was found". 232 | * The final dialog does so already, unless you used setDisplayFinalDialog(false). 233 | */ 234 | int numMatches() const; 235 | 236 | /*! 237 | * Call this to reset the numMatches count 238 | * (and the numReplacements count for a KReplace). 239 | * Can be useful if reusing the same KReplace for different operations, 240 | * or when restarting from the beginning of the document. 241 | */ 242 | virtual void resetCounts(); 243 | 244 | /*! 245 | * Virtual method, which allows applications to add extra checks for 246 | * validating a candidate match. It's only necessary to reimplement this 247 | * if the find dialog extension has been used to provide additional 248 | * criteria. 249 | * 250 | * \a text The current text fragment 251 | * 252 | * \a index The starting index where the candidate match was found 253 | * 254 | * \a matchedlength The length of the candidate match 255 | * 256 | * Returns true if the candidate matches. 257 | */ 258 | virtual bool validateMatch(const QString &text, int index, int matchedlength); 259 | 260 | /*! 261 | * Returns true if we should restart the search from scratch. 262 | * Can ask the user, or return false (if we already searched the whole document). 263 | * 264 | * \a forceAsking set to true if the user modified the document during the 265 | * search. In that case it makes sense to restart the search again. 266 | * 267 | * \a showNumMatches set to true if the dialog should show the number of 268 | * matches. Set to false if the application provides a "find previous" action, 269 | * in which case the match count will be erroneous when hitting the end, 270 | * and we could even be hitting the beginning of the document (so not all 271 | * matches have even been seen). 272 | */ 273 | virtual bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const; 274 | 275 | /*! 276 | * Search \a text for \a pattern. If a match is found, the length of the matched 277 | * string will be stored in \a matchedLength and the index of the matched string 278 | * will be returned. If no match is found -1 is returned. 279 | * 280 | * If the KFind::RegularExpression flag is set, the \a pattern will be iterpreted 281 | * as a regular expression (using QRegularExpression). 282 | * 283 | * \note Unicode support is always enabled (by setting the QRegularExpression::UseUnicodePropertiesOption flag). 284 | * 285 | * \a text The string to search in 286 | * 287 | * \a pattern The pattern to search for 288 | * 289 | * \a index The index in \a text from which to start the search 290 | * 291 | * \a options The options to use 292 | * 293 | * \a matchedLength If there is a match, its length will be stored in this parameter 294 | * 295 | * \a rmatch If there is a regular expression match (implies that the KFind::RegularExpression 296 | * flag is set) and \a rmatch is not a nullptr the match result will be stored 297 | * in this QRegularExpressionMatch object 298 | * 299 | * Returns The index at which a match was found otherwise -1 300 | * 301 | * \since 5.70 302 | */ 303 | static int find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch); 304 | 305 | /*! 306 | * Displays the final dialog saying "no match was found", if that was the case. 307 | * Call either this or shouldRestart(). 308 | */ 309 | virtual void displayFinalDialog() const; 310 | 311 | /*! 312 | * Returns (or creates if \a create is true) the dialog that shows the "find next?" prompt. 313 | * Usually you don't need to call this. 314 | * One case where it can be useful, is when the user selects the "Find" 315 | * menu item while a find operation is under way. In that case, the 316 | * program may want to call setActiveWindow() on that dialog. 317 | */ 318 | QDialog *findNextDialog(bool create = false); 319 | 320 | /*! 321 | * Close the "find next?" dialog. The application should do this when 322 | * the last match was hit. If the application deletes the KFind, then 323 | * "find previous" won't be possible anymore. 324 | * 325 | * IMPORTANT: you should also call this if you are using a non-modal 326 | * find dialog, to tell KFind not to pop up its own dialog. 327 | */ 328 | void closeFindNextDialog(); 329 | 330 | /*! 331 | * Returns the current matching index (or -1). 332 | * Same as the matchingIndex parameter passed to the textFound() signal. 333 | * You usually don't need to use this, except maybe when updating the current data, 334 | * so you need to call setData(newData, index()). 335 | */ 336 | int index() const; 337 | 338 | Q_SIGNALS: 339 | /*! 340 | * Connect to this signal to implement highlighting of found text during the find 341 | * operation. 342 | * 343 | * If you've set data with setData(id, text), use the textFoundAtId(int, int, int) signal. 344 | * 345 | * WARNING: If you're using the FindIncremental option, the text argument 346 | * passed by this signal is not necessarily the data last set through 347 | * setData(), but can also be an earlier set data block. 348 | * 349 | * \a text is the text that was found 350 | * 351 | * \a matchingIndex is the index of the start of the matched text 352 | * 353 | * \a matchedLength is the length of the matched text 354 | * 355 | * \sa setData() 356 | * 357 | * \since 5.81 358 | */ 359 | void textFound(const QString &text, int matchingIndex, int matchedLength); 360 | 361 | /*! 362 | * Connect to this signal to implement highlighting of found text during 363 | * the find operation. 364 | * 365 | * Use this signal if you've set your data with setData(id, text), 366 | * otherwise use the textFound(text, matchingIndex, matchedLength) signal. 367 | * 368 | * WARNING: If you're using the FindIncremental option, the \a id 369 | * passed by this signal is not necessarily the id of the data last set 370 | * through setData(), but can also be of an earlier set data block. 371 | * 372 | * \a matchingIndex is the index of the start of the matched text 373 | * 374 | * \a matchedLength is the length of the matched text 375 | * 376 | * \sa setData() 377 | * 378 | * \since 5.81 379 | */ 380 | void textFoundAtId(int id, int matchingIndex, int matchedLength); 381 | 382 | // ## TODO docu 383 | // findprevious will also emit findNext, after temporarily switching the value 384 | // of FindBackwards 385 | void findNext(); 386 | 387 | /*! 388 | * Emitted when the options have changed. 389 | * This can happen e.g. with "Replace All", or if our 'find next' dialog 390 | * gets a "find previous" one day. 391 | */ 392 | void optionsChanged(); 393 | 394 | /*! 395 | * Emitted when the 'find next' dialog is being closed. 396 | * Some apps might want to remove the highlighted text when this happens. 397 | * Apps without support for "Find Next" can also do m_find->deleteLater() 398 | * to terminate the find operation. 399 | */ 400 | void dialogClosed(); 401 | 402 | protected: 403 | QWidget *parentWidget() const; 404 | QWidget *dialogsParent() const; 405 | 406 | protected: 407 | KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent); 408 | KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog); 409 | 410 | protected: 411 | std::unique_ptr const d_ptr; 412 | 413 | private: 414 | Q_DECLARE_PRIVATE(KFind) 415 | }; 416 | 417 | Q_DECLARE_OPERATORS_FOR_FLAGS(KFind::SearchOptions) 418 | 419 | #endif 420 | -------------------------------------------------------------------------------- /src/findreplace/kfind_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 6 | 7 | SPDX-License-Identifier: LGPL-2.0-only 8 | */ 9 | 10 | #ifndef KFIND_P_H 11 | #define KFIND_P_H 12 | 13 | #include "kfind.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | class KFindPrivate 22 | { 23 | Q_DECLARE_PUBLIC(KFind) 24 | 25 | public: 26 | KFindPrivate(KFind *qq) 27 | : q_ptr(qq) 28 | , findDialog(nullptr) 29 | , currentId(0) 30 | , customIds(false) 31 | , patternChanged(false) 32 | , matchedPattern(QLatin1String("")) 33 | , emptyMatch(nullptr) 34 | { 35 | } 36 | 37 | virtual ~KFindPrivate() 38 | { 39 | if (dialog) { 40 | dialog->deleteLater(); 41 | } 42 | dialog = nullptr; 43 | data.clear(); 44 | delete emptyMatch; 45 | emptyMatch = nullptr; 46 | } 47 | 48 | struct Match { 49 | Match() 50 | : dataId(-1) 51 | , index(-1) 52 | , matchedLength(-1) 53 | { 54 | } 55 | bool isNull() const 56 | { 57 | return index == -1; 58 | } 59 | Match(int _dataId, int _index, int _matchedLength) 60 | : dataId(_dataId) 61 | , index(_index) 62 | , matchedLength(_matchedLength) 63 | { 64 | Q_ASSERT(index != -1); 65 | } 66 | 67 | int dataId; 68 | int index; 69 | int matchedLength; 70 | }; 71 | 72 | struct Data { 73 | Data() 74 | { 75 | } 76 | Data(int id, const QString &text, bool dirty = false) 77 | : text(text) 78 | , id(id) 79 | , dirty(dirty) 80 | { 81 | } 82 | 83 | QString text; 84 | int id = -1; 85 | bool dirty = false; 86 | }; 87 | 88 | void init(const QString &pattern); 89 | void startNewIncrementalSearch(); 90 | 91 | void slotFindNext(); 92 | void slotDialogClosed(); 93 | 94 | KFind *const q_ptr; 95 | QPointer findDialog; 96 | int currentId; 97 | bool customIds : 1; 98 | bool patternChanged : 1; 99 | QString matchedPattern; 100 | QHash incrementalPath; 101 | Match *emptyMatch; 102 | QList data; // used like a vector, not like a linked-list 103 | 104 | QString pattern; 105 | QDialog *dialog; 106 | long options; 107 | unsigned matches; 108 | 109 | QString text; // the text set by setData 110 | int index; 111 | int matchedLength; 112 | bool dialogClosed : 1; 113 | bool lastResult : 1; 114 | }; 115 | 116 | #endif // KFIND_P_H 117 | -------------------------------------------------------------------------------- /src/findreplace/kfinddialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 6 | 7 | SPDX-License-Identifier: LGPL-2.0-only 8 | */ 9 | 10 | #ifndef KFINDDIALOG_H 11 | #define KFINDDIALOG_H 12 | 13 | #include "ktextwidgets_export.h" 14 | 15 | #include 16 | #include 17 | 18 | class KFindDialogPrivate; 19 | 20 | /*! 21 | * \class KFindDialog 22 | * \inmodule KTextWidgets 23 | * 24 | * \brief A generic "find" dialog. 25 | * 26 | * \b Detail: 27 | * 28 | * This widget inherits from KDialog and implements 29 | * the following additional functionalities: a find string 30 | * object and an area for a user-defined widget to extend the dialog. 31 | * 32 | * \b Example: 33 | * 34 | * To use the basic modal find dialog, and then run the search: 35 | * 36 | * \code 37 | * KFindDialog dlg(....) 38 | * if (dlg.exec() != QDialog::Accepted) 39 | * return; 40 | * 41 | * // proceed with KFind from here 42 | * \endcode 43 | * 44 | * To create a non-modal find dialog: 45 | * \code 46 | * if (m_findDialog) { 47 | * m_findDialog->activateWindow(); 48 | * } else { 49 | * m_findDialog = new KFindDialog(...); 50 | * connect(m_findDialog, &KFindDialog::okClicked, this, [this] { 51 | * m_findDialog->close(); 52 | * delete m_find; 53 | * m_find = new KFind(m_findDialog->pattern(), m_findDialog->options(), this); 54 | * // ... see KFind documentation for what else should be done here 55 | * }); 56 | * } 57 | * \endcode 58 | * Don't forget to delete and reset m_findDialog when closed. 59 | * (But do NOT delete your KFind object at that point, it's needed for "Find Next".) 60 | * 61 | * To use your own extensions: see findExtension(). 62 | * 63 | * \image kfinddialog.png "KFindDialog Widget" 64 | */ 65 | class KTEXTWIDGETS_EXPORT KFindDialog : public QDialog 66 | { 67 | Q_OBJECT 68 | 69 | public: 70 | /*! 71 | * Construct a modal find dialog 72 | * 73 | * \a parent The parent object of this widget. 74 | * 75 | * \a options A bitfield of the Options to be checked. 76 | * 77 | * \a findStrings The find history, see findHistory() 78 | * 79 | * \a hasSelection Whether a selection exists 80 | */ 81 | explicit KFindDialog(QWidget *parent = nullptr, 82 | long options = 0, 83 | const QStringList &findStrings = QStringList(), 84 | bool hasSelection = false, 85 | bool replaceDialog = false); 86 | 87 | /*! 88 | * Destructor. 89 | */ 90 | ~KFindDialog() override; 91 | 92 | /*! 93 | * Provide the list of strings to be displayed as the history 94 | * of find strings. The \a history might get truncated if it is 95 | * too long. 96 | * 97 | * \sa findHistory 98 | */ 99 | void setFindHistory(const QStringList &history); 100 | 101 | /*! 102 | * Returns the list of history items. 103 | * 104 | * \sa setFindHistory 105 | */ 106 | QStringList findHistory() const; 107 | 108 | /*! 109 | * Enable/disable the 'search in selection' option, depending 110 | * on whether there actually is a selection. 111 | * 112 | * \a hasSelection true if a selection exists 113 | */ 114 | void setHasSelection(bool hasSelection); 115 | 116 | /*! 117 | * Hide/show the 'from cursor' option, depending 118 | * on whether the application implements a cursor. 119 | * 120 | * \a hasCursor true if the application features a cursor. 121 | * This is assumed to be the case by default. 122 | */ 123 | void setHasCursor(bool hasCursor); 124 | 125 | /*! 126 | * Enable/disable the 'Find backwards' option, depending 127 | * on whether the application supports it. 128 | * 129 | * \a supports true if the application supports backwards find. 130 | * This is assumed to be the case by default. 131 | */ 132 | void setSupportsBackwardsFind(bool supports); 133 | 134 | /*! 135 | * Enable/disable the 'Case sensitive' option, depending 136 | * on whether the application supports it. 137 | * 138 | * \a supports true if the application supports case sensitive find. 139 | * This is assumed to be the case by default. 140 | */ 141 | void setSupportsCaseSensitiveFind(bool supports); 142 | 143 | /*! 144 | * Enable/disable the 'Whole words only' option, depending 145 | * on whether the application supports it. 146 | * 147 | * \a supports true if the application supports whole words only find. 148 | * This is assumed to be the case by default. 149 | */ 150 | void setSupportsWholeWordsFind(bool supports); 151 | 152 | /*! 153 | * Enable/disable the 'Regular expression' option, depending 154 | * on whether the application supports it. 155 | * 156 | * \a supports true if the application supports regular expression find. 157 | * This is assumed to be the case by default. 158 | */ 159 | void setSupportsRegularExpressionFind(bool supports); 160 | 161 | /*! 162 | * Set the options which are checked. 163 | * 164 | * \a options The setting of the Options. 165 | * 166 | * \sa options() 167 | * \sa KFind::Options 168 | */ 169 | void setOptions(long options); 170 | 171 | /*! 172 | * Returns the state of the options. Disabled options may be returned in 173 | * an indeterminate state. 174 | * 175 | * \sa setOptions() 176 | * \sa KFind::Options 177 | */ 178 | long options() const; 179 | 180 | /*! 181 | * Returns the pattern to find. 182 | */ 183 | QString pattern() const; 184 | 185 | /*! 186 | * Sets the \a pattern to find. 187 | */ 188 | void setPattern(const QString &pattern); 189 | 190 | /*! 191 | * Returns an empty widget which the user may fill with additional UI 192 | * elements as required. The widget occupies the width of the dialog, 193 | * and is positioned immediately below the regular expression support 194 | * widgets for the pattern string. 195 | */ 196 | QWidget *findExtension() const; 197 | 198 | Q_SIGNALS: 199 | /*! 200 | * This signal is sent whenever one of the option checkboxes is toggled. 201 | * Call options() to get the new state of the checkboxes. 202 | */ 203 | void optionsChanged(); 204 | 205 | /*! 206 | * This signal is sent when the user clicks on Ok button. 207 | */ 208 | void okClicked(); 209 | 210 | /*! 211 | * This signal is sent when the user clicks on Cancel button. 212 | */ 213 | void cancelClicked(); 214 | 215 | protected: 216 | void showEvent(QShowEvent *) override; 217 | 218 | protected: 219 | KTEXTWIDGETS_NO_EXPORT explicit KFindDialog(KFindDialogPrivate &dd, 220 | QWidget *parent = nullptr, 221 | long options = 0, 222 | const QStringList &findStrings = QStringList(), 223 | bool hasSelection = false, 224 | bool replaceDialog = false); 225 | 226 | protected: 227 | std::unique_ptr const d_ptr; 228 | 229 | private: 230 | Q_DECLARE_PRIVATE(KFindDialog) 231 | }; 232 | 233 | #endif // KFINDDIALOG_H 234 | -------------------------------------------------------------------------------- /src/findreplace/kfinddialog_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | 6 | SPDX-License-Identifier: LGPL-2.0-only 7 | */ 8 | 9 | #ifndef KFINDDIALOG_P_H 10 | #define KFINDDIALOG_P_H 11 | 12 | #include "kfind.h" 13 | #include "kfinddialog.h" 14 | 15 | #include 16 | 17 | class KHistoryComboBox; 18 | 19 | class QCheckBox; 20 | class QDialogButtonBox; 21 | class QGridLayout; 22 | class QGroupBox; 23 | class QMenu; 24 | class QPushButton; 25 | 26 | class KFindDialogPrivate 27 | { 28 | Q_DECLARE_PUBLIC(KFindDialog) 29 | 30 | public: 31 | KFindDialogPrivate(KFindDialog *qq) 32 | : q_ptr(qq) 33 | , regexpDialog(nullptr) 34 | , regexpDialogQueryDone(false) 35 | , initialShowDone(false) 36 | , enabled(KFind::WholeWordsOnly | KFind::FromCursor | KFind::SelectedText | KFind::CaseSensitive | KFind::FindBackwards | KFind::RegularExpression) 37 | , findExtension(nullptr) 38 | , buttonBox(nullptr) 39 | { 40 | } 41 | virtual ~KFindDialogPrivate() = default; 42 | 43 | void init(bool forReplace, const QStringList &findStrings, bool hasSelection); 44 | 45 | void slotPlaceholdersAboutToShow(); 46 | void slotOk(); 47 | void slotReject(); 48 | void slotSelectedTextToggled(bool); 49 | void showPatterns(); 50 | void showPlaceholders(); 51 | void textSearchChanged(const QString &); 52 | 53 | KFindDialog *const q_ptr = nullptr; 54 | QDialog *regexpDialog = nullptr; 55 | bool regexpDialogQueryDone : 1; 56 | bool initialShowDone : 1; 57 | long enabled; // uses Options to define which search options are enabled 58 | QStringList findStrings; 59 | QString pattern; 60 | mutable QWidget *findExtension = nullptr; 61 | QDialogButtonBox *buttonBox = nullptr; 62 | 63 | QGroupBox *findGrp = nullptr; 64 | KHistoryComboBox *find = nullptr; 65 | QCheckBox *regExp = nullptr; 66 | QPushButton *regExpItem = nullptr; 67 | QGridLayout *findLayout = nullptr; 68 | 69 | QCheckBox *wholeWordsOnly = nullptr; 70 | QCheckBox *fromCursor = nullptr; 71 | QCheckBox *selectedText = nullptr; 72 | QCheckBox *caseSensitive = nullptr; 73 | QCheckBox *findBackwards = nullptr; 74 | 75 | QMenu *patterns = nullptr; 76 | 77 | // for the replace dialog 78 | 79 | QGroupBox *replaceGrp = nullptr; 80 | KHistoryComboBox *replace = nullptr; 81 | QCheckBox *backRef = nullptr; 82 | QPushButton *backRefItem = nullptr; 83 | QGridLayout *replaceLayout = nullptr; 84 | 85 | QCheckBox *promptOnReplace = nullptr; 86 | 87 | QMenu *placeholders = nullptr; 88 | }; 89 | 90 | #endif // KFINDDIALOG_P_H 91 | -------------------------------------------------------------------------------- /src/findreplace/kreplace.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | 6 | SPDX-License-Identifier: LGPL-2.0-only 7 | */ 8 | 9 | #include "kreplace.h" 10 | 11 | #include "kfind_p.h" 12 | #include "kreplacedialog.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | //#define DEBUG_REPLACE 24 | #define INDEX_NOMATCH -1 25 | 26 | class KReplaceNextDialog : public QDialog 27 | { 28 | Q_OBJECT 29 | public: 30 | explicit KReplaceNextDialog(QWidget *parent); 31 | void setLabel(const QString &pattern, const QString &replacement); 32 | 33 | QPushButton *replaceAllButton() const; 34 | QPushButton *skipButton() const; 35 | QPushButton *replaceButton() const; 36 | 37 | private: 38 | QLabel *m_mainLabel = nullptr; 39 | QPushButton *m_allButton = nullptr; 40 | QPushButton *m_skipButton = nullptr; 41 | QPushButton *m_replaceButton = nullptr; 42 | }; 43 | 44 | KReplaceNextDialog::KReplaceNextDialog(QWidget *parent) 45 | : QDialog(parent) 46 | { 47 | setModal(false); 48 | setWindowTitle(i18n("Replace")); 49 | 50 | QVBoxLayout *layout = new QVBoxLayout(this); 51 | 52 | m_mainLabel = new QLabel(this); 53 | layout->addWidget(m_mainLabel); 54 | 55 | m_allButton = new QPushButton(i18nc("@action:button Replace all occurrences", "&All")); 56 | m_allButton->setObjectName(QStringLiteral("allButton")); 57 | m_skipButton = new QPushButton(i18n("&Skip")); 58 | m_skipButton->setObjectName(QStringLiteral("skipButton")); 59 | m_replaceButton = new QPushButton(i18n("Replace")); 60 | m_replaceButton->setObjectName(QStringLiteral("replaceButton")); 61 | m_replaceButton->setDefault(true); 62 | 63 | QDialogButtonBox *buttonBox = new QDialogButtonBox(this); 64 | buttonBox->addButton(m_allButton, QDialogButtonBox::ActionRole); 65 | buttonBox->addButton(m_skipButton, QDialogButtonBox::ActionRole); 66 | buttonBox->addButton(m_replaceButton, QDialogButtonBox::ActionRole); 67 | buttonBox->setStandardButtons(QDialogButtonBox::Close); 68 | layout->addWidget(buttonBox); 69 | 70 | connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 71 | connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 72 | } 73 | 74 | void KReplaceNextDialog::setLabel(const QString &pattern, const QString &replacement) 75 | { 76 | m_mainLabel->setText(i18n("Replace '%1' with '%2'?", pattern, replacement)); 77 | } 78 | 79 | QPushButton *KReplaceNextDialog::replaceAllButton() const 80 | { 81 | return m_allButton; 82 | } 83 | 84 | QPushButton *KReplaceNextDialog::skipButton() const 85 | { 86 | return m_skipButton; 87 | } 88 | 89 | QPushButton *KReplaceNextDialog::replaceButton() const 90 | { 91 | return m_replaceButton; 92 | } 93 | 94 | //// 95 | 96 | class KReplacePrivate : public KFindPrivate 97 | { 98 | Q_DECLARE_PUBLIC(KReplace) 99 | 100 | public: 101 | KReplacePrivate(KReplace *q, const QString &replacement) 102 | : KFindPrivate(q) 103 | , m_replacement(replacement) 104 | { 105 | } 106 | 107 | KReplaceNextDialog *nextDialog(); 108 | void doReplace(); 109 | 110 | void slotSkip(); 111 | void slotReplace(); 112 | void slotReplaceAll(); 113 | 114 | QString m_replacement; 115 | int m_replacements = 0; 116 | QRegularExpressionMatch m_match; 117 | }; 118 | 119 | //// 120 | 121 | KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent) 122 | : KFind(*new KReplacePrivate(this, replacement), pattern, options, parent) 123 | { 124 | } 125 | 126 | KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *dlg) 127 | : KFind(*new KReplacePrivate(this, replacement), pattern, options, parent, dlg) 128 | { 129 | } 130 | 131 | KReplace::~KReplace() = default; 132 | 133 | int KReplace::numReplacements() const 134 | { 135 | Q_D(const KReplace); 136 | 137 | return d->m_replacements; 138 | } 139 | 140 | QDialog *KReplace::replaceNextDialog(bool create) 141 | { 142 | Q_D(KReplace); 143 | 144 | if (d->dialog || create) { 145 | return d->nextDialog(); 146 | } 147 | return nullptr; 148 | } 149 | 150 | KReplaceNextDialog *KReplacePrivate::nextDialog() 151 | { 152 | Q_Q(KReplace); 153 | 154 | if (!dialog) { 155 | auto *nextDialog = new KReplaceNextDialog(q->parentWidget()); 156 | q->connect(nextDialog->replaceAllButton(), &QPushButton::clicked, q, [this]() { 157 | slotReplaceAll(); 158 | }); 159 | q->connect(nextDialog->skipButton(), &QPushButton::clicked, q, [this]() { 160 | slotSkip(); 161 | }); 162 | q->connect(nextDialog->replaceButton(), &QPushButton::clicked, q, [this]() { 163 | slotReplace(); 164 | }); 165 | q->connect(nextDialog, &QDialog::finished, q, [this]() { 166 | slotDialogClosed(); 167 | }); 168 | dialog = nextDialog; 169 | } 170 | return static_cast(dialog); 171 | } 172 | 173 | void KReplace::displayFinalDialog() const 174 | { 175 | Q_D(const KReplace); 176 | 177 | if (!d->m_replacements) { 178 | KMessageBox::information(parentWidget(), i18n("No text was replaced.")); 179 | } else { 180 | KMessageBox::information(parentWidget(), i18np("1 replacement done.", "%1 replacements done.", d->m_replacements)); 181 | } 182 | } 183 | 184 | static int replaceHelper(QString &text, const QString &replacement, int index, long options, const QRegularExpressionMatch *match, int length) 185 | { 186 | QString rep(replacement); 187 | if (options & KReplaceDialog::BackReference) { 188 | // Handle backreferences 189 | if (options & KFind::RegularExpression) { // regex search 190 | Q_ASSERT(match); 191 | const int capNum = match->regularExpression().captureCount(); 192 | for (int i = 0; i <= capNum; ++i) { 193 | rep.replace(QLatin1String("\\") + QString::number(i), match->captured(i)); 194 | } 195 | } else { // with non-regex search only \0 is supported, replace it with the 196 | // right portion of 'text' 197 | rep.replace(QLatin1String("\\0"), text.mid(index, length)); 198 | } 199 | } 200 | 201 | // Then replace rep into the text 202 | text.replace(index, length, rep); 203 | return rep.length(); 204 | } 205 | 206 | KFind::Result KReplace::replace() 207 | { 208 | Q_D(KReplace); 209 | 210 | #ifdef DEBUG_REPLACE 211 | // qDebug() << "d->index=" << d->index; 212 | #endif 213 | if (d->index == INDEX_NOMATCH && d->lastResult == Match) { 214 | d->lastResult = NoMatch; 215 | return NoMatch; 216 | } 217 | 218 | do { // this loop is only because validateMatch can fail 219 | #ifdef DEBUG_REPLACE 220 | // qDebug() << "beginning of loop: d->index=" << d->index; 221 | #endif 222 | // Find the next match. 223 | d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength, d->options & KFind::RegularExpression ? &d->m_match : nullptr); 224 | 225 | #ifdef DEBUG_REPLACE 226 | // qDebug() << "KFind::find returned d->index=" << d->index; 227 | #endif 228 | if (d->index != -1) { 229 | // Flexibility: the app can add more rules to validate a possible match 230 | if (validateMatch(d->text, d->index, d->matchedLength)) { 231 | if (d->options & KReplaceDialog::PromptOnReplace) { 232 | #ifdef DEBUG_REPLACE 233 | // qDebug() << "PromptOnReplace"; 234 | #endif 235 | // Display accurate initial string and replacement string, they can vary 236 | QString matchedText(d->text.mid(d->index, d->matchedLength)); 237 | QString rep(matchedText); 238 | replaceHelper(rep, d->m_replacement, 0, d->options, d->options & KFind::RegularExpression ? &d->m_match : nullptr, d->matchedLength); 239 | d->nextDialog()->setLabel(matchedText, rep); 240 | d->nextDialog()->show(); // TODO kde5: virtual void showReplaceNextDialog(QString,QString), so that kreplacetest can skip the show() 241 | 242 | // Tell the world about the match we found, in case someone wants to 243 | // highlight it. 244 | Q_EMIT textFound(d->text, d->index, d->matchedLength); 245 | 246 | d->lastResult = Match; 247 | return Match; 248 | } else { 249 | d->doReplace(); // this moves on too 250 | } 251 | } else { 252 | // not validated -> move on 253 | if (d->options & KFind::FindBackwards) { 254 | d->index--; 255 | } else { 256 | d->index++; 257 | } 258 | } 259 | } else { 260 | d->index = INDEX_NOMATCH; // will exit the loop 261 | } 262 | } while (d->index != INDEX_NOMATCH); 263 | 264 | d->lastResult = NoMatch; 265 | return NoMatch; 266 | } 267 | 268 | int KReplace::replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength) 269 | { 270 | int matchedLength; 271 | QRegularExpressionMatch match; 272 | index = KFind::find(text, pattern, index, options, &matchedLength, options & KFind::RegularExpression ? &match : nullptr); 273 | 274 | if (index != -1) { 275 | *replacedLength = replaceHelper(text, replacement, index, options, options & KFind::RegularExpression ? &match : nullptr, matchedLength); 276 | if (options & KFind::FindBackwards) { 277 | index--; 278 | } else { 279 | index += *replacedLength; 280 | } 281 | } 282 | return index; 283 | } 284 | 285 | void KReplacePrivate::slotReplaceAll() 286 | { 287 | Q_Q(KReplace); 288 | 289 | doReplace(); 290 | options &= ~KReplaceDialog::PromptOnReplace; 291 | Q_EMIT q->optionsChanged(); 292 | Q_EMIT q->findNext(); 293 | } 294 | 295 | void KReplacePrivate::slotSkip() 296 | { 297 | Q_Q(KReplace); 298 | 299 | if (options & KFind::FindBackwards) { 300 | index--; 301 | } else { 302 | index++; 303 | } 304 | if (dialogClosed) { 305 | dialog->deleteLater(); 306 | dialog = nullptr; // hide it again 307 | } else { 308 | Q_EMIT q->findNext(); 309 | } 310 | } 311 | 312 | void KReplacePrivate::slotReplace() 313 | { 314 | Q_Q(KReplace); 315 | 316 | doReplace(); 317 | if (dialogClosed) { 318 | dialog->deleteLater(); 319 | dialog = nullptr; // hide it again 320 | } else { 321 | Q_EMIT q->findNext(); 322 | } 323 | } 324 | 325 | void KReplacePrivate::doReplace() 326 | { 327 | Q_Q(KReplace); 328 | 329 | Q_ASSERT(index >= 0); 330 | const int replacedLength = replaceHelper(text, m_replacement, index, options, &m_match, matchedLength); 331 | 332 | // Tell the world about the replacement we made, in case someone wants to 333 | // highlight it. 334 | Q_EMIT q->textReplaced(text, index, replacedLength, matchedLength); 335 | 336 | #ifdef DEBUG_REPLACE 337 | // qDebug() << "after replace() signal: d->index=" << d->index << " replacedLength=" << replacedLength; 338 | #endif 339 | m_replacements++; 340 | if (options & KFind::FindBackwards) { 341 | Q_ASSERT(index >= 0); 342 | index--; 343 | } else { 344 | index += replacedLength; 345 | // when replacing the empty pattern, move on. See also kjs/regexp.cpp for how this should be done for regexps. 346 | if (pattern.isEmpty()) { 347 | ++index; 348 | } 349 | } 350 | #ifdef DEBUG_REPLACE 351 | // qDebug() << "after adjustment: d->index=" << d->index; 352 | #endif 353 | } 354 | 355 | void KReplace::resetCounts() 356 | { 357 | Q_D(KReplace); 358 | 359 | KFind::resetCounts(); 360 | d->m_replacements = 0; 361 | } 362 | 363 | bool KReplace::shouldRestart(bool forceAsking, bool showNumMatches) const 364 | { 365 | Q_D(const KReplace); 366 | 367 | // Only ask if we did a "find from cursor", otherwise it's pointless. 368 | // ... Or if the prompt-on-replace option was set. 369 | // Well, unless the user can modify the document during a search operation, 370 | // hence the force boolean. 371 | if (!forceAsking && (d->options & KFind::FromCursor) == 0 && (d->options & KReplaceDialog::PromptOnReplace) == 0) { 372 | displayFinalDialog(); 373 | return false; 374 | } 375 | QString message; 376 | if (showNumMatches) { 377 | if (!d->m_replacements) { 378 | message = i18n("No text was replaced."); 379 | } else { 380 | message = i18np("1 replacement done.", "%1 replacements done.", d->m_replacements); 381 | } 382 | } else { 383 | if (d->options & KFind::FindBackwards) { 384 | message = i18n("Beginning of document reached."); 385 | } else { 386 | message = i18n("End of document reached."); 387 | } 388 | } 389 | 390 | message += QLatin1Char('\n'); 391 | // Hope this word puzzle is ok, it's a different sentence 392 | message += 393 | (d->options & KFind::FindBackwards) ? i18n("Do you want to restart search from the end?") : i18n("Do you want to restart search at the beginning?"); 394 | 395 | int ret = KMessageBox::questionTwoActions(parentWidget(), 396 | message, 397 | QString(), 398 | KGuiItem(i18nc("@action:button Restart find & replace", "Restart")), 399 | KGuiItem(i18nc("@action:button Stop find & replace", "Stop"))); 400 | return (ret == KMessageBox::PrimaryAction); 401 | } 402 | 403 | void KReplace::closeReplaceNextDialog() 404 | { 405 | closeFindNextDialog(); 406 | } 407 | 408 | #include "kreplace.moc" 409 | #include "moc_kreplace.cpp" 410 | -------------------------------------------------------------------------------- /src/findreplace/kreplace.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 6 | 7 | SPDX-License-Identifier: LGPL-2.0-only 8 | */ 9 | 10 | #ifndef KREPLACE_H 11 | #define KREPLACE_H 12 | 13 | #include "kfind.h" 14 | 15 | #include "ktextwidgets_export.h" 16 | 17 | class KReplacePrivate; 18 | 19 | /*! 20 | * \class KReplace 21 | * \inmodule KTextWidgets 22 | * 23 | * \brief A generic implementation of the "replace" function. 24 | * 25 | * \b Detail: 26 | * 27 | * This class includes prompt handling etc. Also provides some 28 | * static functions which can be used to create custom behavior 29 | * instead of using the class directly. 30 | * 31 | * \b Example: 32 | * 33 | * To use the class to implement a complete replace feature: 34 | * 35 | * In the slot connect to the replace action, after using KReplaceDialog: 36 | * \code 37 | * 38 | * // This creates a replace-on-prompt dialog if needed. 39 | * m_replace = new KReplace(pattern, replacement, options, this); 40 | * 41 | * // Connect signals to code which handles highlighting of found text, and 42 | * // on-the-fly replacement. 43 | * connect(m_replace, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) { 44 | * slotHighlight(text, matchingIndex, matchedLength); 45 | * }); 46 | * // Connect findNext signal - called when pressing the button in the dialog 47 | * connect( m_replace, SIGNAL( findNext() ), 48 | * this, SLOT( slotReplaceNext() ) ); 49 | * // Connect to the textReplaced() signal - emitted when a replacement is done 50 | * connect( m_replace, &KReplace::textReplaced, this, [this](const QString &text, int replacementIndex, int replacedLength, int matchedLength) { 51 | * slotReplace(text, replacementIndex, replacedLength, matchedLength); 52 | * }); 53 | * \endcode 54 | * Then initialize the variables determining the "current position" 55 | * (to the cursor, if the option FromCursor is set, 56 | * to the beginning of the selection if the option SelectedText is set, 57 | * and to the beginning of the document otherwise). 58 | * Initialize the "end of search" variables as well (end of doc or end of selection). 59 | * Swap begin and end if FindBackwards. 60 | * Finally, call slotReplaceNext(); 61 | * 62 | * \code 63 | * void slotReplaceNext() 64 | * { 65 | * KFind::Result res = KFind::NoMatch; 66 | * while ( res == KFind::NoMatch && ) { 67 | * if ( m_replace->needData() ) 68 | * m_replace->setData( ); 69 | * 70 | * // Let KReplace inspect the text fragment, and display a dialog if a match is found 71 | * res = m_replace->replace(); 72 | * 73 | * if ( res == KFind::NoMatch ) { 74 | * 75 | * } 76 | * } 77 | * 78 | * if ( res == KFind::NoMatch ) // i.e. at end 79 | * displayFinalDialog(); delete m_replace; m_replace = nullptr; 80 | * or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } 81 | * else { m_replace->closeReplaceNextDialog(); }> 82 | * } 83 | * \endcode 84 | * 85 | * Don't forget delete m_find in the destructor of your class, 86 | * unless you gave it a parent widget on construction. 87 | * 88 | */ 89 | class KTEXTWIDGETS_EXPORT KReplace : public KFind 90 | { 91 | Q_OBJECT 92 | 93 | public: 94 | /*! 95 | * Only use this constructor if you don't use KFindDialog, or if 96 | * you use it as a modal dialog. 97 | */ 98 | KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent = nullptr); 99 | /*! 100 | * This is the recommended constructor if you also use KReplaceDialog (non-modal). 101 | * You should pass the pointer to it here, so that when a message box 102 | * appears it has the right parent. Don't worry about deletion, KReplace 103 | * will notice if the find dialog is closed. 104 | */ 105 | KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *replaceDialog); 106 | 107 | ~KReplace() override; 108 | 109 | /*! 110 | * Return the number of replacements made (i.e. the number of times 111 | * the textReplaced() signal was emitted). 112 | * 113 | * Can be used in a dialog box to tell the user how many replacements were made. 114 | * The final dialog does so already, unless you used setDisplayFinalDialog(false). 115 | */ 116 | int numReplacements() const; 117 | 118 | /*! 119 | * Call this to reset the numMatches & numReplacements counts. 120 | * Can be useful if reusing the same KReplace for different operations, 121 | * or when restarting from the beginning of the document. 122 | */ 123 | void resetCounts() override; 124 | 125 | /*! 126 | * Walk the text fragment (e.g. kwrite line, kspread cell) looking for matches. 127 | * For each match, if prompt-on-replace is specified, emits the textFound() signal 128 | * and displays the prompt-for-replace dialog before doing the replace. 129 | */ 130 | Result replace(); 131 | 132 | /*! 133 | * Returns (or creates if \a create is true) the dialog that shows the "find next?" prompt. 134 | * Usually you don't need to call this. 135 | * One case where it can be useful, is when the user selects the "Find" 136 | * menu item while a find operation is under way. In that case, the 137 | * program may want to call setActiveWindow() on that dialog. 138 | */ 139 | QDialog *replaceNextDialog(bool create = false); 140 | 141 | /*! 142 | * Close the "replace next?" dialog. The application should do this when 143 | * the last match was hit. If the application deletes the KReplace, then 144 | * "find previous" won't be possible anymore. 145 | */ 146 | void closeReplaceNextDialog(); 147 | 148 | /*! 149 | * Searches the given \a text for \a pattern; if a match is found it is replaced 150 | * with \a replacement and the index of the replacement string is returned. 151 | * 152 | * \a text The string to search 153 | * 154 | * \a pattern The pattern to search for 155 | * 156 | * \a replacement The replacement string to insert into the text 157 | * 158 | * \a index The starting index into the string 159 | * 160 | * \a options The options to use 161 | * 162 | * \a replacedLength Output parameter, contains the length of the replaced string 163 | * Not always the same as replacement.length(), when backreferences are used 164 | * 165 | * Returns The index at which a match was found, or -1 otherwise 166 | */ 167 | static int replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength); 168 | 169 | /*! 170 | * Returns true if we should restart the search from scratch. 171 | * Can ask the user, or return false (if we already searched/replaced the 172 | * whole document without the PromptOnReplace option). 173 | * 174 | * \a forceAsking set to true if the user modified the document during the 175 | * search. In that case it makes sense to restart the search again. 176 | * 177 | * \a showNumMatches set to true if the dialog should show the number of 178 | * matches. Set to false if the application provides a "find previous" action, 179 | * in which case the match count will be erroneous when hitting the end, 180 | * and we could even be hitting the beginning of the document (so not all 181 | * matches have even been seen). 182 | */ 183 | bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const override; 184 | 185 | /*! 186 | * Displays the final dialog telling the user how many replacements were made. 187 | * Call either this or shouldRestart(). 188 | */ 189 | void displayFinalDialog() const override; 190 | 191 | Q_SIGNALS: 192 | /*! 193 | * Connect to this signal to implement updating of replaced text during the replace 194 | * operation. 195 | * 196 | * Extra care must be taken to properly implement the "no prompt-on-replace" case. 197 | * For instance, the textFound() signal isn't emitted in that case (some code 198 | * might rely on it), and for performance reasons one should repaint after 199 | * replace() ONLY if prompt-on-replace was selected. 200 | * 201 | * \a text The text, in which the replacement has already been done 202 | * 203 | * \a replacementIndex Starting index of the matched substring 204 | * 205 | * \a replacedLength Length of the replacement string 206 | * 207 | * \a matchedLength Length of the matched string 208 | * 209 | * \since 5.83 210 | */ 211 | void textReplaced(const QString &text, int replacementIndex, int replacedLength, int matchedLength); 212 | 213 | private: 214 | Q_DECLARE_PRIVATE(KReplace) 215 | }; 216 | #endif 217 | -------------------------------------------------------------------------------- /src/findreplace/kreplacedialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | 6 | SPDX-License-Identifier: LGPL-2.0-only 7 | */ 8 | 9 | #include "kreplacedialog.h" 10 | #include "kfinddialog_p.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | /* 23 | * we need to insert the strings after the dialog is set 24 | * up, otherwise QComboBox will deliver an awful big sizeHint 25 | * for long replacement texts. 26 | */ 27 | class KReplaceDialogPrivate : public KFindDialogPrivate 28 | { 29 | Q_DECLARE_PUBLIC(KReplaceDialog) 30 | 31 | public: 32 | explicit KReplaceDialogPrivate(KReplaceDialog *qq) 33 | : KFindDialogPrivate(qq) 34 | { 35 | } 36 | 37 | void slotOk(); 38 | 39 | QStringList replaceStrings; 40 | mutable QWidget *replaceExtension = nullptr; 41 | bool initialShowDone = false; 42 | }; 43 | 44 | KReplaceDialog::KReplaceDialog(QWidget *parent, long options, const QStringList &findStrings, const QStringList &replaceStrings, bool hasSelection) 45 | : KFindDialog(*new KReplaceDialogPrivate(this), parent, options, findStrings, hasSelection, true /*create replace dialog*/) 46 | { 47 | Q_D(KReplaceDialog); 48 | 49 | d->replaceStrings = replaceStrings; 50 | } 51 | 52 | KReplaceDialog::~KReplaceDialog() = default; 53 | 54 | void KReplaceDialog::showEvent(QShowEvent *e) 55 | { 56 | Q_D(KReplaceDialog); 57 | 58 | if (!d->initialShowDone) { 59 | d->initialShowDone = true; // only once 60 | 61 | if (!d->replaceStrings.isEmpty()) { 62 | setReplacementHistory(d->replaceStrings); 63 | d->replace->lineEdit()->setText(d->replaceStrings[0]); 64 | } 65 | } 66 | 67 | KFindDialog::showEvent(e); 68 | } 69 | 70 | long KReplaceDialog::options() const 71 | { 72 | Q_D(const KReplaceDialog); 73 | 74 | long options = 0; 75 | 76 | options = KFindDialog::options(); 77 | if (d->promptOnReplace->isChecked()) { 78 | options |= PromptOnReplace; 79 | } 80 | if (d->backRef->isChecked()) { 81 | options |= BackReference; 82 | } 83 | return options; 84 | } 85 | 86 | QWidget *KReplaceDialog::replaceExtension() const 87 | { 88 | Q_D(const KReplaceDialog); 89 | 90 | if (!d->replaceExtension) { 91 | d->replaceExtension = new QWidget(d->replaceGrp); 92 | d->replaceLayout->addWidget(d->replaceExtension, 3, 0, 1, 2); 93 | } 94 | 95 | return d->replaceExtension; 96 | } 97 | 98 | QString KReplaceDialog::replacement() const 99 | { 100 | Q_D(const KReplaceDialog); 101 | 102 | return d->replace->currentText(); 103 | } 104 | 105 | QStringList KReplaceDialog::replacementHistory() const 106 | { 107 | Q_D(const KReplaceDialog); 108 | 109 | QStringList lst = d->replace->historyItems(); 110 | // historyItems() doesn't tell us about the case of replacing with an empty string 111 | if (d->replace->lineEdit()->text().isEmpty()) { 112 | lst.prepend(QString()); 113 | } 114 | return lst; 115 | } 116 | 117 | void KReplaceDialog::setOptions(long options) 118 | { 119 | Q_D(KReplaceDialog); 120 | 121 | KFindDialog::setOptions(options); 122 | d->promptOnReplace->setChecked(options & PromptOnReplace); 123 | d->backRef->setChecked(options & BackReference); 124 | } 125 | 126 | void KReplaceDialog::setReplacementHistory(const QStringList &strings) 127 | { 128 | Q_D(KReplaceDialog); 129 | 130 | if (!strings.isEmpty()) { 131 | d->replace->setHistoryItems(strings, true); 132 | } else { 133 | d->replace->clearHistory(); 134 | } 135 | } 136 | 137 | void KReplaceDialogPrivate::slotOk() 138 | { 139 | Q_Q(KReplaceDialog); 140 | 141 | // If regex and backrefs are enabled, do a sanity check. 142 | if (regExp->isChecked() && backRef->isChecked()) { 143 | const QRegularExpression re(q->pattern(), QRegularExpression::UseUnicodePropertiesOption); 144 | const int caps = re.captureCount(); 145 | 146 | const QString rep = q->replacement(); 147 | const static QRegularExpression check(QStringLiteral("((?:\\\\)+)(\\d+)")); 148 | auto iter = check.globalMatch(rep); 149 | while (iter.hasNext()) { 150 | const QRegularExpressionMatch match = iter.next(); 151 | if ((match.captured(1).size() % 2) && match.captured(2).toInt() > caps) { 152 | KMessageBox::information(q, 153 | i18n("Your replacement string is referencing a capture greater than '\\%1', ", caps) 154 | + (caps ? i18np("but your pattern only defines 1 capture.", "but your pattern only defines %1 captures.", caps) 155 | : i18n("but your pattern defines no captures.")) 156 | + i18n("\nPlease correct.")); 157 | return; // abort OKing 158 | } 159 | } 160 | } 161 | 162 | slotOk(); 163 | replace->addToHistory(q->replacement()); 164 | } 165 | 166 | #include "moc_kreplacedialog.cpp" 167 | -------------------------------------------------------------------------------- /src/findreplace/kreplacedialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE project 3 | SPDX-FileCopyrightText: 2001 S.R. Haque . 4 | SPDX-FileCopyrightText: 2002 David Faure 5 | SPDX-FileCopyrightText: 2004 Arend van Beelen jr. 6 | 7 | SPDX-License-Identifier: LGPL-2.0-only 8 | */ 9 | 10 | #ifndef KREPLACEDIALOG_H 11 | #define KREPLACEDIALOG_H 12 | 13 | #include "ktextwidgets_export.h" 14 | 15 | #include "kfinddialog.h" 16 | 17 | class KReplaceDialogPrivate; 18 | 19 | /*! 20 | * \class KReplaceDialog 21 | * \inmodule KTextWidgets 22 | * 23 | * \brief A generic "replace" dialog. 24 | * 25 | * \b Detail: 26 | * 27 | * This widget inherits from KFindDialog and implements 28 | * the following additional functionalities: a replacement string 29 | * object and an area for a user-defined widget to extend the dialog. 30 | * 31 | * \b Example: 32 | * 33 | * To use the basic replace dialog: 34 | * 35 | * \code 36 | * \endcode 37 | * 38 | * To use your own extensions: 39 | * 40 | * \code 41 | * \endcode 42 | * 43 | * \image kreplacedialog.png "KReplaceDialog Widget" 44 | */ 45 | class KTEXTWIDGETS_EXPORT KReplaceDialog : public KFindDialog 46 | { 47 | Q_OBJECT 48 | 49 | public: 50 | /*! 51 | * \enum KReplaceDialog::Options 52 | * 53 | * \value PromptOnReplace 54 | * Should the user be prompted before the replace operation? 55 | * 56 | * \value BackReference 57 | * Use a back reference in the regular expression. 58 | */ 59 | 60 | enum Options { 61 | PromptOnReplace = 256, 62 | BackReference = 512, 63 | }; 64 | 65 | /*! 66 | * Construct a replace dialog.read-only or rather select-only combo box with a 67 | * parent object and a name. 68 | * 69 | * \a parent The parent object of this widget 70 | * 71 | * \a options A bitfield of the Options to be enabled. 72 | * 73 | * \a findStrings A QStringList to insert in the combo box of text to find 74 | * 75 | * \a replaceStrings A QStringList to insert in the combo box of text to 76 | * replace with 77 | * 78 | * \a hasSelection Whether a selection exists 79 | */ 80 | explicit KReplaceDialog(QWidget *parent = nullptr, 81 | long options = 0, 82 | const QStringList &findStrings = QStringList(), 83 | const QStringList &replaceStrings = QStringList(), 84 | bool hasSelection = true); 85 | 86 | ~KReplaceDialog() override; 87 | 88 | /*! 89 | * Provide the list of strings to be displayed as the \a history 90 | * of replacement strings. The history might get truncated if it is 91 | * too long. 92 | * 93 | * \sa replacementHistory 94 | */ 95 | void setReplacementHistory(const QStringList &history); 96 | 97 | /*! 98 | * Returns the list of history items. 99 | * 100 | * \sa setReplacementHistory 101 | */ 102 | QStringList replacementHistory() const; 103 | 104 | /*! 105 | * Set the options which are enabled. 106 | * 107 | * \a options The setting of the Options. 108 | */ 109 | void setOptions(long options); 110 | 111 | /*! 112 | * Returns the state of the options. Disabled options may be returned in 113 | * an indeterminate state. 114 | * 115 | * \sa setOptions 116 | */ 117 | long options() const; 118 | 119 | /*! 120 | * Returns the replacement string. 121 | */ 122 | QString replacement() const; 123 | 124 | /*! 125 | * Returns an empty widget which the user may fill with additional UI 126 | * elements as required. The widget occupies the width of the dialog, 127 | * and is positioned immediately the regular expression support widgets 128 | * for the replacement string. 129 | */ 130 | QWidget *replaceExtension() const; 131 | 132 | protected: 133 | void showEvent(QShowEvent *) override; 134 | 135 | private: 136 | Q_DECLARE_PRIVATE(KReplaceDialog) 137 | }; 138 | 139 | #endif // KREPLACEDIALOG_H 140 | -------------------------------------------------------------------------------- /src/ktextwidgets-index.qdoc: -------------------------------------------------------------------------------- 1 | /*! 2 | \page ktextwidgets-index.html 3 | \title KTextWidgets 4 | 5 | KTextWidgets is a collection of text editing widgets. 6 | 7 | \section1 Using the Module 8 | 9 | \include {module-use.qdocinc} {using the c++ api} 10 | 11 | \section2 Building with CMake 12 | 13 | \include {module-use.qdocinc} {building with cmake} {KF6} {TextWidgets} {KF6::TextWidgets} 14 | 15 | \section1 API Reference 16 | 17 | \list 18 | \li \l{KTextWidgets C++ Classes} 19 | \endlist 20 | */ 21 | -------------------------------------------------------------------------------- /src/ktextwidgets.qdoc: -------------------------------------------------------------------------------- 1 | /*! 2 | \module KTextWidgets 3 | \title KTextWidgets C++ Classes 4 | \ingroup modules 5 | \cmakepackage KF6 6 | \cmakecomponent TextWidgets 7 | 8 | \brief KTextWidgets is a collection of text editing widgets. 9 | 10 | KTextWidgets provides widgets for displaying and editing text. It supports 11 | rich text as well as plain text. 12 | */ 13 | -------------------------------------------------------------------------------- /src/ktextwidgets.qdocconf: -------------------------------------------------------------------------------- 1 | include($KDE_DOCS/global/qt-module-defaults.qdocconf) 2 | 3 | project = KTextWidgets 4 | description = Text editing widgets 5 | 6 | documentationinheaders = true 7 | 8 | headerdirs += . 9 | sourcedirs += . 10 | 11 | imagedirs = ../docs/pics 12 | 13 | navigation.landingpage = "KTextWidgets" 14 | 15 | depends += kde qtcore qtwidgets sonnetui 16 | 17 | qhp.projects = KTextWidgets 18 | 19 | qhp.KTextWidgets.file = ktextwidgets.qhp 20 | qhp.KTextWidgets.namespace = org.kde.ktextwidgets.$QT_VERSION_TAG 21 | qhp.KTextWidgets.virtualFolder = ktextwidgets 22 | qhp.KTextWidgets.indexTitle = KTextWidgets 23 | qhp.KTextWidgets.indexRoot = 24 | 25 | qhp.KTextWidgets.subprojects = classes 26 | qhp.KTextWidgets.subprojects.classes.title = C++ Classes 27 | qhp.KTextWidgets.subprojects.classes.indexTitle = KTextWidgets C++ Classes 28 | qhp.KTextWidgets.subprojects.classes.selectors = class fake:headerfile 29 | qhp.KTextWidgets.subprojects.classes.sortPages = true 30 | 31 | tagfile = ktextwidgets.tags 32 | -------------------------------------------------------------------------------- /src/widgets/kpluralhandlingspinbox.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014 Laurent Montel 3 | 4 | SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "kpluralhandlingspinbox.h" 8 | 9 | #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(6, 6) 10 | 11 | class KPluralHandlingSpinBoxPrivate 12 | { 13 | public: 14 | KPluralHandlingSpinBoxPrivate(QSpinBox *qq) 15 | : q(qq) 16 | { 17 | QObject::connect(q, qOverload(&QSpinBox::valueChanged), q, [this](int value) { 18 | updateSuffix(value); 19 | }); 20 | } 21 | 22 | void updateSuffix(int value) 23 | { 24 | if (!pluralSuffix.isEmpty()) { 25 | KLocalizedString s = pluralSuffix; 26 | q->setSuffix(s.subs(value).toString()); 27 | } 28 | } 29 | 30 | QSpinBox *const q; 31 | KLocalizedString pluralSuffix; 32 | }; 33 | 34 | KPluralHandlingSpinBox::KPluralHandlingSpinBox(QWidget *parent) 35 | : QSpinBox(parent) 36 | , d(new KPluralHandlingSpinBoxPrivate(this)) 37 | { 38 | } 39 | 40 | KPluralHandlingSpinBox::~KPluralHandlingSpinBox() = default; 41 | 42 | void KPluralHandlingSpinBox::setSuffix(const KLocalizedString &suffix) 43 | { 44 | d->pluralSuffix = suffix; 45 | if (suffix.isEmpty()) { 46 | QSpinBox::setSuffix(QString()); 47 | } else { 48 | d->updateSuffix(value()); 49 | } 50 | } 51 | #include "moc_kpluralhandlingspinbox.cpp" 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/widgets/kpluralhandlingspinbox.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2014 Laurent Montel 3 | 4 | SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef KPLURALHANDLINGSPINBOX_H 8 | #define KPLURALHANDLINGSPINBOX_H 9 | 10 | #include 11 | 12 | #if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(6, 6) 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | /*! 20 | * \class KPluralHandlingSpinBox 21 | * \inmodule KTextWidgets 22 | * 23 | * \brief A QSpinBox with plural handling for the suffix. 24 | * 25 | * \since 5.0 26 | * \deprecated[6.6] 27 | * Use KLocalization::setupSpinBoxFormatString() from KF6::I18n instead, 28 | * which is more powerful and does not require inheriting from a specific QSpinBox subclass. 29 | */ 30 | KTEXTWIDGETS_DEPRECATED_VERSION(6, 6, "use KLocalization::setupSpinBoxFormatString() from KF6::I18n instead") 31 | class KTEXTWIDGETS_EXPORT KPluralHandlingSpinBox : public QSpinBox 32 | { 33 | Q_OBJECT 34 | public: 35 | /*! 36 | * Default constructor 37 | */ 38 | explicit KPluralHandlingSpinBox(QWidget *parent = nullptr); 39 | ~KPluralHandlingSpinBox() override; 40 | 41 | /*! 42 | * Sets the suffix to \a suffix. 43 | * Use this to add a plural-aware suffix, e.g. by using ki18np("singular", "plural"). 44 | */ 45 | void setSuffix(const KLocalizedString &suffix); 46 | 47 | private: 48 | friend class KPluralHandlingSpinBoxPrivate; 49 | std::unique_ptr const d; 50 | 51 | Q_DISABLE_COPY(KPluralHandlingSpinBox) 52 | }; 53 | #endif 54 | 55 | #endif // KPLURALHANDLINGSPINBOX_H 56 | -------------------------------------------------------------------------------- /src/widgets/krichtextedit.h: -------------------------------------------------------------------------------- 1 | /* 2 | krichtextedit.h 3 | SPDX-FileCopyrightText: 2007 Laurent Montel 4 | SPDX-FileCopyrightText: 2008 Thomas McGuire 5 | SPDX-FileCopyrightText: 2008 Stephen Kelly 6 | 7 | SPDX-License-Identifier: LGPL-2.1-or-later 8 | */ 9 | 10 | #ifndef KRICHTEXTEDIT_H 11 | #define KRICHTEXTEDIT_H 12 | 13 | #include 14 | 15 | class QKeyEvent; 16 | 17 | class KRichTextEditPrivate; 18 | 19 | #include 20 | 21 | /*! 22 | * \class KRichTextEdit 23 | * \inmodule KTextWidgets 24 | * 25 | * \brief A KTextEdit that supports both rich text and plain text modes. 26 | * 27 | * The KRichTextEdit class provides a widget to edit and display rich text. 28 | * 29 | * It offers several additional rich text editing functions to KTextEdit and makes 30 | * them easier to access including: 31 | * 32 | * \list 33 | * \li Changing fonts, sizes. 34 | * \li Font formatting, such as bold, underline, italic, foreground and 35 | * background color. 36 | * \li Paragraph alignment 37 | * \li Ability to edit and remove hyperlinks 38 | * \li Nested list handling 39 | * \li Simple actions to insert tables. TODO 40 | * \endlist 41 | * 42 | * The KRichTextEdit can be in two modes: Rich text mode and plain text mode. 43 | * Calling functions which modify the format/style of the text will automatically 44 | * enable the rich text mode. Rich text mode is sometimes also referred to as 45 | * HTML mode. 46 | * 47 | * Do not call setAcceptRichText() or acceptRichText() yourself. Instead simply 48 | * connect to the slots which insert the rich text, use switchToPlainText() or 49 | * enableRichTextMode(). 50 | * 51 | * \image krichtextedit.png "KRichTextEdit Widget" 52 | * 53 | * \since 4.1 54 | */ 55 | class KTEXTWIDGETS_EXPORT KRichTextEdit : public KTextEdit 56 | { 57 | Q_OBJECT 58 | 59 | public: 60 | /*! 61 | * \enum KRichTextEdit::Mode 62 | * 63 | * The mode the edit widget is in. 64 | * 65 | * \value Plain 66 | * Plain text mode 67 | * \value Rich 68 | * Rich text mode 69 | */ 70 | enum Mode { 71 | Plain, 72 | Rich, 73 | }; 74 | 75 | /*! 76 | * Constructs a KRichTextEdit object 77 | * 78 | * \a text The initial text of the text edit, which is interpreted as 79 | * HTML. 80 | * 81 | * \a parent The parent widget 82 | */ 83 | explicit KRichTextEdit(const QString &text, QWidget *parent = nullptr); 84 | 85 | /*! 86 | * Constructs a KRichTextEdit object. 87 | * 88 | * \a parent The parent widget 89 | */ 90 | explicit KRichTextEdit(QWidget *parent = nullptr); 91 | 92 | ~KRichTextEdit() override; 93 | 94 | /*! 95 | * This enables rich text mode. Nothing is done except changing the internal 96 | * mode and allowing rich text pastes. 97 | */ 98 | void enableRichTextMode(); 99 | 100 | /*! 101 | * Returns the current text mode 102 | */ 103 | Mode textMode() const; 104 | 105 | /*! 106 | * Returns the plain text string if in plain text mode or the HTML code 107 | * if in rich text mode. The text is not word-wrapped. 108 | */ 109 | QString textOrHtml() const; 110 | 111 | /*! 112 | * Replaces all the content of the text edit with the given string. 113 | * If the string is in rich text format, the text is inserted as rich text, 114 | * otherwise it is inserted as plain text. 115 | * 116 | * \a text The text to insert 117 | */ 118 | void setTextOrHtml(const QString &text); 119 | 120 | /*! 121 | * Returns the text of the link at the current position or an empty string 122 | * if the cursor is not on a link. 123 | * 124 | * \sa currentLinkUrl 125 | */ 126 | QString currentLinkText() const; 127 | 128 | /*! 129 | * Returns the URL target (href) of the link at the current position or an 130 | * empty string if the cursor is not on a link. 131 | * 132 | * \sa currentLinkText 133 | */ 134 | QString currentLinkUrl() const; 135 | 136 | /*! 137 | * If the cursor is on a link, sets the \a cursor to a selection of the 138 | * text of the link. If the \a cursor is not on a link, selects the current word 139 | * or existing selection. 140 | * 141 | * \a cursor The cursor to use to select the text. 142 | * 143 | * \sa updateLink 144 | */ 145 | void selectLinkText(QTextCursor *cursor) const; 146 | 147 | /*! 148 | * Convenience function to select the link text using the active cursor. 149 | * 150 | * \sa selectLinkText 151 | */ 152 | void selectLinkText() const; 153 | 154 | /*! 155 | * Replaces the current selection with a hyperlink with the link URL \a linkUrl 156 | * and the link text \a linkText. 157 | * 158 | * \sa selectLinkText 159 | * \sa currentLinkUrl 160 | * \sa currentLinkText 161 | * 162 | * \a linkUrl The link will get this URL as href (target) 163 | * 164 | * \a linkText The link will get this alternative text, which is the 165 | * text displayed in the text edit. 166 | */ 167 | void updateLink(const QString &linkUrl, const QString &linkText); 168 | 169 | /*! 170 | * Returns true if the list item at the current position can be indented. 171 | * 172 | * \sa canDedentList 173 | */ 174 | bool canIndentList() const; 175 | 176 | /*! 177 | * Returns true if the list item at the current position can be dedented. 178 | * 179 | * \sa canIndentList 180 | */ 181 | bool canDedentList() const; 182 | 183 | public Q_SLOTS: 184 | 185 | /*! 186 | * Sets the alignment of the current block to Left Aligned 187 | */ 188 | void alignLeft(); 189 | 190 | /*! 191 | * Sets the alignment of the current block to Centered 192 | */ 193 | void alignCenter(); 194 | 195 | /*! 196 | * Sets the alignment of the current block to Right Aligned 197 | */ 198 | void alignRight(); 199 | 200 | /*! 201 | * Sets the alignment of the current block to Justified 202 | */ 203 | void alignJustify(); 204 | 205 | /*! 206 | * Sets the direction of the current block to Right-To-Left 207 | * 208 | * \since 4.6 209 | */ 210 | void makeRightToLeft(); 211 | 212 | /*! 213 | * Sets the direction of the current block to Left-To-Right 214 | * 215 | * \since 4.6 216 | */ 217 | void makeLeftToRight(); 218 | 219 | /*! 220 | * Sets the list style of the current list, or creates a new list using the 221 | * current block. The \a _styleindex corresponds to the QTextListFormat::Style 222 | * 223 | * \a _styleIndex The list will get this style 224 | */ 225 | void setListStyle(int _styleIndex); 226 | 227 | /*! 228 | * Increases the nesting level of the current block or selected blocks. 229 | * 230 | * \sa canIndentList 231 | */ 232 | void indentListMore(); 233 | 234 | /*! 235 | * Decreases the nesting level of the current block or selected blocks. 236 | * 237 | * \sa canDedentList 238 | */ 239 | void indentListLess(); 240 | 241 | /*! 242 | * Sets the current word or selection to the font family \a fontFamily 243 | * 244 | * \a fontFamily The text's font family will be changed to this one 245 | */ 246 | void setFontFamily(const QString &fontFamily); 247 | 248 | /*! 249 | * Sets the current word or selection to the font size \a size 250 | * 251 | * \a size The text's font will get this size 252 | */ 253 | void setFontSize(int size); 254 | 255 | /*! 256 | * Sets the current word or selection to the font \a font 257 | * 258 | * \a font the font of the text will be set to this font 259 | */ 260 | void setFont(const QFont &font); 261 | 262 | /*! 263 | * Toggles the bold formatting of the current word or selection at the current 264 | * cursor position. 265 | * 266 | * \a bold If true, the text will be set to bold 267 | */ 268 | void setTextBold(bool bold); 269 | 270 | /*! 271 | * Toggles the italic formatting of the current word or selection at the current 272 | * cursor position. 273 | * 274 | * \a italic If true, the text will be set to italic 275 | */ 276 | void setTextItalic(bool italic); 277 | 278 | /*! 279 | * Toggles the underline formatting of the current word or selection at the current 280 | * cursor position. 281 | * 282 | * \a underline If true, the text will be underlined 283 | */ 284 | void setTextUnderline(bool underline); 285 | 286 | /*! 287 | * Toggles the strikeout formatting of the current word or selection at the current 288 | * cursor position. 289 | * 290 | * \a strikeOut If true, the text will be struck out 291 | */ 292 | void setTextStrikeOut(bool strikeOut); 293 | 294 | /*! 295 | * Sets the foreground color of the current word or selection to \a color. 296 | * 297 | * \a color The text will get this background color 298 | */ 299 | void setTextForegroundColor(const QColor &color); 300 | 301 | /*! 302 | * Sets the background color of the current word or selection to \a color. 303 | * 304 | * \a color The text will get this foreground color 305 | */ 306 | void setTextBackgroundColor(const QColor &color); 307 | 308 | /*! 309 | * Inserts a horizontal rule below the current block. 310 | */ 311 | void insertHorizontalRule(); 312 | 313 | /*! 314 | * This will switch the editor to plain text mode. 315 | * All rich text formatting will be destroyed. 316 | */ 317 | void switchToPlainText(); 318 | 319 | /*! 320 | * This will clean some of the bad html produced by the underlying QTextEdit 321 | * It walks over all lines and cleans up a bit. Should be improved to produce 322 | * our own Html. 323 | */ 324 | QString toCleanHtml() const; 325 | 326 | /*! 327 | * Toggles the superscript formatting of the current word or selection at the current 328 | * cursor position. 329 | * 330 | * \a superscript If true, the text will be set to superscript 331 | */ 332 | void setTextSuperScript(bool superscript); 333 | 334 | /*! 335 | * Toggles the subscript formatting of the current word or selection at the current 336 | * cursor position. 337 | * 338 | * \a subscript If true, the text will be set to subscript 339 | */ 340 | void setTextSubScript(bool subscript); 341 | 342 | /*! 343 | * Sets the heading level of a current block or selection 344 | * 345 | * \a level Heading level (value should be between 0 and 6) 346 | * (0 is "normal text", 1 is the largest heading, 6 is the smallest one) 347 | * 348 | * \since 5.70 349 | */ 350 | void setHeadingLevel(int level); 351 | 352 | /*! 353 | * \since 4.10 354 | * Because of binary compatibility constraints, insertPlainText 355 | * is not virtual. Therefore it must dynamically detect and call this slot. 356 | */ 357 | void insertPlainTextImplementation(); 358 | 359 | Q_SIGNALS: 360 | 361 | /*! 362 | * Emitted whenever the text mode is changed. 363 | * 364 | * \a mode The new text mode 365 | */ 366 | void textModeChanged(KRichTextEdit::Mode mode); 367 | 368 | protected: 369 | void keyPressEvent(QKeyEvent *event) override; 370 | 371 | protected: 372 | KTEXTWIDGETS_NO_EXPORT KRichTextEdit(KRichTextEditPrivate &dd, const QString &text, QWidget *parent); 373 | KTEXTWIDGETS_NO_EXPORT KRichTextEdit(KRichTextEditPrivate &dd, QWidget *parent); 374 | 375 | private: 376 | Q_DECLARE_PRIVATE(KRichTextEdit) 377 | }; 378 | 379 | #endif 380 | -------------------------------------------------------------------------------- /src/widgets/krichtextedit_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | krichtextedit 3 | SPDX-FileCopyrightText: 2007 Laurent Montel 4 | SPDX-FileCopyrightText: 2008 Thomas McGuire 5 | SPDX-FileCopyrightText: 2008 Stephen Kelly 6 | 7 | SPDX-License-Identifier: LGPL-2.1-or-later 8 | */ 9 | 10 | #ifndef KRICHTEXTEDIT_P_H 11 | #define KRICHTEXTEDIT_P_H 12 | 13 | #include "ktextedit_p.h" 14 | #include "nestedlisthelper_p.h" 15 | 16 | class KRichTextEditPrivate : public KTextEditPrivate 17 | { 18 | Q_DECLARE_PUBLIC(KRichTextEdit) 19 | 20 | public: 21 | explicit KRichTextEditPrivate(KRichTextEdit *qq) 22 | : KTextEditPrivate(qq) 23 | , nestedListHelper(new NestedListHelper(qq)) 24 | { 25 | } 26 | 27 | ~KRichTextEditPrivate() override 28 | { 29 | delete nestedListHelper; 30 | } 31 | 32 | // 33 | // Normal functions 34 | // 35 | 36 | // If the text under the cursor is a link, the cursor's selection is set to 37 | // the complete link text. Otherwise selects the current word if there is no 38 | // selection. 39 | void selectLinkText() const; 40 | 41 | void init(); 42 | 43 | // Switches to rich text mode and emits the mode changed signal if the 44 | // mode really changed. 45 | void activateRichText(); 46 | 47 | // Applies formatting to the current word if there is no selection. 48 | void mergeFormatOnWordOrSelection(const QTextCharFormat &format); 49 | 50 | void setTextCursor(QTextCursor &cursor); 51 | 52 | // Data members 53 | KRichTextEdit::Mode mMode = KRichTextEdit::Plain; 54 | 55 | NestedListHelper *nestedListHelper; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/widgets/krichtextwidget.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE libraries 3 | SPDX-FileCopyrightText: 2008 Stephen Kelly 4 | SPDX-FileCopyrightText: 2008 Thomas McGuire 5 | 6 | SPDX-License-Identifier: LGPL-2.0-only 7 | */ 8 | 9 | #ifndef KRICHTEXTWIDGET_H 10 | #define KRICHTEXTWIDGET_H 11 | 12 | #include "krichtextedit.h" 13 | 14 | #include "ktextwidgets_export.h" 15 | 16 | class QAction; 17 | class KRichTextWidgetPrivate; 18 | 19 | /*! 20 | * \class KRichTextWidget 21 | * \inmodule KTextWidgets 22 | * 23 | * \brief A KRichTextEdit with common actions. 24 | * 25 | * This class implements common actions which are often used with KRichTextEdit. 26 | * All you need to do is to call createActions(), and the actions will be 27 | * added to your KXMLGUIWindow. Remember to also add the chosen actions to 28 | * your application ui.rc file. 29 | * 30 | * See the KRichTextWidget::RichTextSupportValues enum for an overview of 31 | * supported actions. 32 | * 33 | * \image krichtextedit.png "KRichTextWidget Widget" 34 | * 35 | * \since 4.1 36 | */ 37 | class KTEXTWIDGETS_EXPORT KRichTextWidget : public KRichTextEdit 38 | { 39 | Q_OBJECT 40 | Q_PROPERTY(RichTextSupport richTextSupport READ richTextSupport WRITE setRichTextSupport) 41 | public: 42 | /*! 43 | * \enum KRichTextWidget::RichTextSupportValues 44 | * 45 | * These flags describe what actions will be created by createActions() after 46 | * passing a combination of these flags to setRichTextSupport(). 47 | * 48 | * \value DisableRichText 49 | * No rich text support at all, no actions will be created. Do 50 | * not use in combination with other flags. 51 | * \value SupportBold 52 | * Action to format the selected text as bold. If no text is 53 | * selected, the word under the cursor is formatted bold. This 54 | * is a KToggleAction. The status is automatically updated when 55 | * the text cursor is moved. 56 | * \value SupportItalic 57 | * Action to format the selected text as italic. If no text is 58 | * selected, the word under the cursor is formatted italic. 59 | * This is a KToggleAction. The status is automatically updated 60 | * when the text cursor is moved. 61 | * \value SupportUnderline 62 | * Action to underline the selected text. If no text is 63 | * selected, the word under the cursor is underlined. This is a 64 | * KToggleAction. The status is automatically updated when the 65 | * text cursor is moved. 66 | * \value SupportStrikeOut 67 | * Action to strike out the selected text. If no text is 68 | * selected, the word under the cursor is struck out. This is a 69 | * KToggleAction. The status is automatically updated when the 70 | * text cursor is moved. 71 | * \value SupportFontFamily 72 | * Action to change the font family of the currently selected 73 | * text. If no text is selected, the font family of the word 74 | * under the cursor is changed. Displayed as a combobox when 75 | * inserted into the toolbar. This is a KFontAction. The status 76 | * is automatically updated when the text cursor is moved. 77 | * \value SupportFontSize 78 | * Action to change the font size of the currently selected 79 | * text. If no text is selected, the font size of the word under 80 | * the cursor is changed. Displayed as a combobox when inserted 81 | * into the toolbar. This is a KFontSizeAction. The status is 82 | * automatically updated when the text cursor is moved. 83 | * \value SupportTextForegroundColor 84 | * Action to change the text color of the currently selected 85 | * text. If no text is selected, the text color of the word 86 | * under the cursor is changed. Opens a QColorDialog to select 87 | * the color. 88 | * \value SupportTextBackgroundColor 89 | * Action to change the background color of the currently 90 | * selected text. If no text is selected, the background color 91 | * of the word under the cursor is changed. Opens a 92 | * QColorDialog to select the color. 93 | * \value FullTextFormattingSupport 94 | * A combination of all the flags above. Includes all actions 95 | * that change the format of the text. 96 | * \value SupportChangeListStyle 97 | * Action to make the current line a list element, change the 98 | * list style or remove list formatting. Displayed as a 99 | * combobox when inserted into a toolbar. This is a 100 | * KSelectAction. The status is automatically updated when the 101 | * text cursor is moved. 102 | * \value SupportIndentLists 103 | * Action to increase the current list nesting level. This makes 104 | * it possible to create nested lists. 105 | * \value SupportDedentLists 106 | * Action to decrease the current list nesting level. 107 | * \value FullListSupport 108 | * All of the three list actions above. Includes all 109 | * list-related actions. 110 | * \value SupportAlignment 111 | * Actions to align the current paragraph left, right, center or 112 | * justify. These actions are KToogleActions. The status is 113 | * automatically updated when the text cursor is moved. 114 | * \value SupportRuleLine 115 | * Action to insert a horizontal line. 116 | * \value SupportHyperlinks 117 | * Action to convert the current text to a hyperlink. If no text 118 | * is selected, the word under the cursor is converted. This 119 | * action opens a dialog where the user can enter the link 120 | * target. 121 | * \value SupportFormatPainting 122 | * Action to make the mouse cursor a format painter. The user 123 | * can select text with that painter. The selected text gets the 124 | * same format as the text that was previously selected. 125 | * \value SupportToPlainText 126 | * Action to change the text of the whole text edit to plain 127 | * text. All rich text formatting will get lost. 128 | * \value SupportSuperScriptAndSubScript 129 | * Actions to format text as superscript or subscript. If no 130 | * text is selected, the word under the cursor is formatted as 131 | * selected. This is a KToggleAction. The status is 132 | * automatically updated when the text cursor is moved. 133 | * \value SupportDirection 134 | * Action to change direction of text to Right-To-Left or 135 | * Left-To-Right. 136 | * \value [since 5.70] SupportHeading 137 | * Action to make the current line a heading (up to six levels, 138 | * corresponding to HTML h1...h6 tags). Displayed as a combobox 139 | * when inserted into a toolbar. This is a KSelectAction. The 140 | * status is automatically updated when the text cursor is 141 | * moved. 142 | * \value FullSupport 143 | * Includes all above actions for full rich text support 144 | */ 145 | enum RichTextSupportValues { 146 | DisableRichText = 0x00, 147 | SupportBold = 0x01, 148 | SupportItalic = 0x02, 149 | SupportUnderline = 0x04, 150 | SupportStrikeOut = 0x08, 151 | SupportFontFamily = 0x10, 152 | SupportFontSize = 0x20, 153 | SupportTextForegroundColor = 0x40, 154 | SupportTextBackgroundColor = 0x80, 155 | FullTextFormattingSupport = 0xff, 156 | SupportChangeListStyle = 0x100, 157 | SupportIndentLists = 0x200, 158 | SupportDedentLists = 0x400, 159 | FullListSupport = 0xf00, 160 | 161 | // Not implemented yet. 162 | // SupportCreateTables = 0x1000, 163 | // SupportChangeCellMargin = 0x2000, 164 | // SupportChangeCellPadding = 0x4000, 165 | // SupportChangeTableBorderWidth = 0x8000, 166 | // SupportChangeTableBorderColor = 0x10000, 167 | // SupportChangeTableBorderStyle = 0x20000, 168 | // SupportChangeCellBackground = 0x40000, 169 | // SupportCellFillPatterns = 0x80000, 170 | // 171 | // FullTableSupport = 0xff000, 172 | 173 | SupportAlignment = 0x100000, 174 | 175 | // Not yet implemented SupportImages = 0x200000, 176 | 177 | SupportRuleLine = 0x400000, 178 | SupportHyperlinks = 0x800000, 179 | SupportFormatPainting = 0x1000000, 180 | SupportToPlainText = 0x2000000, 181 | SupportSuperScriptAndSubScript = 0x4000000, 182 | 183 | // SupportChangeParagraphSpacing = 0x200000, 184 | 185 | SupportDirection = 0x8000000, 186 | SupportHeading = 0x10000000, 187 | FullSupport = 0xffffffff, 188 | }; 189 | 190 | /* 191 | * Stores a combination of #RichTextSupportValues values. 192 | */ 193 | Q_DECLARE_FLAGS(RichTextSupport, RichTextSupportValues) 194 | Q_FLAG(RichTextSupport) 195 | 196 | /*! 197 | * \brief Constructor 198 | * \a parent the parent widget 199 | */ 200 | explicit KRichTextWidget(QWidget *parent); 201 | 202 | /*! 203 | * Constructs a KRichTextWidget object 204 | * 205 | * \a text The initial text of the text edit, which is interpreted as HTML. 206 | * 207 | * \a parent The parent widget 208 | */ 209 | explicit KRichTextWidget(const QString &text, QWidget *parent = nullptr); 210 | 211 | /*! 212 | * \brief Destructor 213 | */ 214 | ~KRichTextWidget() override; 215 | 216 | /*! 217 | * \brief Creates the actions and adds them to the given action collection. 218 | * 219 | * Call this before calling setupGUI() in your application, but after 220 | * calling setRichTextSupport(). 221 | * 222 | * The XML file of your KXmlGuiWindow needs to have the action names in 223 | * them, so that the actions actually appear in the menu and in the toolbars. 224 | * 225 | * Below is a list of actions that are created, depending on the supported rich text 226 | * subset set by setRichTextSupport(). The list contains action names. 227 | * Those names need to be the same in your XML file. 228 | * 229 | * See the KRichTextWidget::RichTextSupportValues enum documentation for a 230 | * detailed explanation of each action. 231 | * 232 | * \table 233 | * \header 234 | * \li XML Name 235 | * \li RichTextSupportValues flag 236 | * \row 237 | * \li format_text_foreground_color 238 | * \li SupportTextForegroundColor 239 | * \row 240 | * \li format_text_background_color 241 | * \li SupportTextBackgroundColor 242 | * \row 243 | * \li format_font_family 244 | * \li SupportFontFamily 245 | * \row 246 | * \li format_font_size 247 | * \li SupportFontSize 248 | * \row 249 | * \li format_text_bold 250 | * \li SupportBold 251 | * \row 252 | * \li format_text_italic 253 | * \li SupportItalic 254 | * \row 255 | * \li format_text_underline 256 | * \li SupportUnderline 257 | * \row 258 | * \li format_text_strikeout 259 | * \li SupportStrikeOut 260 | * \row 261 | * \li format_align_left 262 | * \li SupportAlignment 263 | * \row 264 | * \li format_align_center 265 | * \li SupportAlignment 266 | * \row 267 | * \li format_align_right 268 | * \li SupportAlignment 269 | * \row 270 | * \li format_align_justify 271 | * \li SupportAlignment 272 | * \row 273 | * \li direction_ltr 274 | * \li SupportDirection 275 | * \row 276 | * \li direction_rtl 277 | * \li SupportDirection 278 | * \row 279 | * \li format_list_style 280 | * \li SupportChangeListStyle 281 | * \row 282 | * \li format_list_indent_more 283 | * \li SupportIndentLists 284 | * \row 285 | * \li format_list_indent_less 286 | * \li SupportDedentLists 287 | * \row 288 | * \li insert_horizontal_rule 289 | * \li SupportRuleLine 290 | * \row 291 | * \li manage_link 292 | * \li SupportHyperlinks 293 | * \row 294 | * \li format_painter 295 | * \li SupportFormatPainting 296 | * \row 297 | * \li action_to_plain_text 298 | * \li SupportToPlainText 299 | * \row 300 | * \li format_text_subscript & format_text_superscript 301 | * \li SupportSuperScriptAndSubScript 302 | * \row 303 | * \li format_heading_level 304 | * \li SupportHeading 305 | * \endtable 306 | * 307 | * \since 5.0 308 | */ 309 | virtual QList createActions(); 310 | 311 | /*! 312 | * \brief Sets the supported rich text subset available. 313 | * 314 | * The default is KRichTextWidget::FullSupport and will be set in the 315 | * constructor. 316 | * 317 | * You need to call createActions() afterwards. 318 | * 319 | * \a support The supported subset. 320 | */ 321 | void setRichTextSupport(const KRichTextWidget::RichTextSupport &support); 322 | 323 | /*! 324 | * \brief Returns the supported rich text subset available. 325 | * Returns The supported subset. 326 | */ 327 | RichTextSupport richTextSupport() const; 328 | 329 | /*! 330 | * Tells KRichTextWidget to update the state of the actions created by 331 | * createActions(). 332 | * 333 | * This is normally automatically done, but there might be a few cases where 334 | * you'll need to manually call this function. 335 | * 336 | * Call this function only after calling createActions(). 337 | */ 338 | void updateActionStates(); 339 | 340 | public Q_SLOTS: 341 | 342 | /*! 343 | * Disables or enables all of the actions created by 344 | * createActions(). 345 | * 346 | * This may be useful in cases where rich text mode may be set on or off. 347 | * 348 | * \a enabled Whether to enable or disable the actions. 349 | */ 350 | void setActionsEnabled(bool enabled); 351 | 352 | protected: 353 | void mouseReleaseEvent(QMouseEvent *event) override; 354 | 355 | private: 356 | Q_DECLARE_PRIVATE(KRichTextWidget) 357 | }; 358 | 359 | Q_DECLARE_OPERATORS_FOR_FLAGS(KRichTextWidget::RichTextSupport) 360 | 361 | #endif 362 | -------------------------------------------------------------------------------- /src/widgets/ktextedit.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE libraries 3 | SPDX-FileCopyrightText: 2002 Carsten Pfeiffer 4 | 5 | SPDX-License-Identifier: LGPL-2.0-or-later 6 | */ 7 | 8 | #ifndef KTEXTEDIT_H 9 | #define KTEXTEDIT_H 10 | 11 | #include "ktextwidgets_export.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Sonnet 18 | { 19 | class SpellCheckDecorator; 20 | } 21 | 22 | class KTextEditPrivate; 23 | 24 | /*! 25 | * \class KTextEdit 26 | * \inmodule KTextWidgets 27 | * 28 | * \brief A KDE'ified QTextEdit. 29 | * 30 | * This is just a little subclass of QTextEdit, implementing 31 | * some standard KDE features, like cursor auto-hiding, configurable 32 | * wheelscrolling (fast-scroll or zoom), spell checking and deleting of entire 33 | * words with Ctrl-Backspace or Ctrl-Delete. 34 | * 35 | * This text edit provides two ways of spell checking: background checking, 36 | * which will mark incorrectly spelled words red, and a spell check dialog, 37 | * which lets the user check and correct all incorrectly spelled words. 38 | * 39 | * Basic rule: whenever you want to use QTextEdit, use KTextEdit! 40 | * 41 | * \image ktextedit.png "KTextEdit Widget" 42 | */ 43 | class KTEXTWIDGETS_EXPORT KTextEdit : public QTextEdit // krazy:exclude=qclasses 44 | { 45 | Q_OBJECT 46 | 47 | /*! 48 | * \property KTextEdit::checkSpellingEnabled 49 | */ 50 | Q_PROPERTY(bool checkSpellingEnabled READ checkSpellingEnabled WRITE setCheckSpellingEnabled) 51 | 52 | /*! 53 | * \property KTextEdit::spellCheckingLanguage 54 | */ 55 | Q_PROPERTY(QString spellCheckingLanguage READ spellCheckingLanguage WRITE setSpellCheckingLanguage) 56 | 57 | public: 58 | /*! 59 | * Constructs a KTextEdit object. See QTextEdit::QTextEdit 60 | * for details. 61 | */ 62 | explicit KTextEdit(const QString &text, QWidget *parent = nullptr); 63 | 64 | /*! 65 | * Constructs a KTextEdit object. See QTextEdit::QTextEdit 66 | * for details. 67 | */ 68 | explicit KTextEdit(QWidget *parent = nullptr); 69 | 70 | ~KTextEdit() override; 71 | 72 | /*! 73 | * Sets the text edit control to the state of \a readOnly. 74 | * 75 | * Reimplemented to set a proper "deactivated" background color. 76 | */ 77 | virtual void setReadOnly(bool readOnly); 78 | 79 | /*! 80 | * Turns background spell checking for this text edit to the value of \a check. 81 | * Note that spell checking is only available in read-writable KTextEdits. 82 | * 83 | * Enabling spell checking will set back the current highlighter to the one 84 | * returned by createHighlighter(). 85 | * 86 | * \sa checkSpellingEnabled() 87 | * \sa setReadOnly() 88 | */ 89 | virtual void setCheckSpellingEnabled(bool check); 90 | 91 | /*! 92 | * Returns true if background spell checking is enabled for this text edit. 93 | * Note that it even returns true if this is a read-only KTextEdit, 94 | * where spell checking is actually disabled. 95 | * By default spell checking is disabled. 96 | * 97 | * \sa setCheckSpellingEnabled() 98 | */ 99 | virtual bool checkSpellingEnabled() const; 100 | 101 | /*! 102 | * Returns true if the given paragraph or \a block should be spellchecked. 103 | * For example, a mail client does not want to check quoted text, and 104 | * would return false here (by checking whether the block starts with a 105 | * quote sign). 106 | * 107 | * Always returns true by default. 108 | * 109 | */ 110 | virtual bool shouldBlockBeSpellChecked(const QString &block) const; 111 | 112 | /*! 113 | * Selects the characters at the specified position. Any previous 114 | * selection will be lost. The cursor is moved to the first character 115 | * of the new selection. 116 | * 117 | * \a length The length of the selection, in number of characters 118 | * 119 | * \a pos The position of the first character of the selection 120 | */ 121 | void highlightWord(int length, int pos); 122 | 123 | /*! 124 | * Allows to create a specific highlighter if reimplemented. 125 | * 126 | * This highlighter is set each time spell checking is toggled on by 127 | * calling setCheckSpellingEnabled(), but can later be overridden by calling 128 | * setHighlighter(). 129 | * 130 | * \sa setHighlighter() 131 | * \sa highlighter() 132 | */ 133 | virtual void createHighlighter(); 134 | 135 | /*! 136 | * Returns the current highlighter, which is 0 if spell checking is disabled. 137 | * The default highlighter is the one created by createHighlighter(), but 138 | * might be overridden by setHighlighter(). 139 | * 140 | * \sa setHighlighter() 141 | * \sa createHighlighter() 142 | */ 143 | Sonnet::Highlighter *highlighter() const; 144 | 145 | /*! 146 | * Sets a custom background spellcheck highlighter for this text edit. 147 | * Normally, the highlighter returned by createHighlighter() will be 148 | * used to detect and highlight incorrectly spelled words, but this 149 | * function allows to set a custom highlighter. 150 | * 151 | * This has to be called after enabling spell checking with 152 | * setCheckSpellingEnabled(), otherwise it has no effect. 153 | * 154 | * Ownership is transferred to the KTextEdit 155 | * 156 | * \a _highLighter is the spellcheck highlighter to use 157 | * 158 | * \sa highlighter() 159 | * \sa createHighlighter() 160 | */ 161 | void setHighlighter(Sonnet::Highlighter *_highLighter); 162 | 163 | /*! 164 | * Returns standard KTextEdit popupMenu 165 | * \since 4.1 166 | */ 167 | virtual QMenu *mousePopupMenu(); 168 | 169 | /*! 170 | * Sets the find/replace action to the value of \a enabled. 171 | * \since 4.1 172 | */ 173 | void enableFindReplace(bool enabled); 174 | 175 | /*! 176 | * Returns the spell checking language which was set by 177 | * setSpellCheckingLanguage(), the spellcheck dialog or the spellcheck 178 | * config dialog, or an empty string if that has never been called. 179 | * \since 4.2 180 | */ 181 | const QString &spellCheckingLanguage() const; 182 | 183 | /*! 184 | * Sets the show tab action to the value of \a show. 185 | * \since 4.10 186 | */ 187 | void showTabAction(bool show); 188 | 189 | /*! 190 | * Shows the auto-correct button if the value of \a show is true. 191 | * \since 4.10 192 | */ 193 | void showAutoCorrectButton(bool show); 194 | 195 | /*! 196 | * \since 4.10 197 | * create a modal spellcheck dialogbox and spellCheckingFinished signal we sent when 198 | * we finish spell checking or spellCheckingCanceled signal when we cancel spell checking 199 | */ 200 | void forceSpellChecking(); 201 | 202 | Q_SIGNALS: 203 | /*! 204 | * emit signal when we activate or not autospellchecking 205 | * 206 | * \since 4.1 207 | */ 208 | void checkSpellingChanged(bool); 209 | 210 | /*! 211 | * Signal sends when spell checking is finished/stopped/completed 212 | * \since 4.1 213 | */ 214 | void spellCheckStatus(const QString &); 215 | 216 | /*! 217 | * Emitted when the user changes the language in the spellcheck dialog 218 | * shown by checkSpelling() or when calling setSpellCheckingLanguage(). 219 | * 220 | * \a language the new language the user selected 221 | * \since 4.1 222 | */ 223 | void languageChanged(const QString &language); 224 | 225 | /*! 226 | * Emitted before the context \a menu is displayed. 227 | * 228 | * The signal allows you to add your own entries into the 229 | * the context menu that is created on demand. 230 | * 231 | * \note Do not store the pointer to the QMenu 232 | * provided through since it is created and deleted 233 | * on demand. 234 | * 235 | * \since 4.5 236 | */ 237 | void aboutToShowContextMenu(QMenu *menu); 238 | 239 | /*! 240 | * This signal is emitted when the spell checker wants the user to decide 241 | * if \a currentWord should be replaced with \a autoCorrectWord. 242 | * 243 | * \since 4.10 244 | */ 245 | void spellCheckerAutoCorrect(const QString ¤tWord, const QString &autoCorrectWord); 246 | 247 | /*! 248 | * signal spellCheckingFinished is sent when we finish spell check or we click on "Terminate" button in sonnet dialogbox 249 | * \since 4.10 250 | */ 251 | void spellCheckingFinished(); 252 | 253 | /*! 254 | * signal spellCheckingCanceled is sent when we cancel spell checking. 255 | * \since 4.10 256 | */ 257 | void spellCheckingCanceled(); 258 | 259 | public Q_SLOTS: 260 | 261 | /*! 262 | * Set the spell check \a language which will be used for highlighting spelling 263 | * mistakes and for the spellcheck dialog. 264 | * The languageChanged() signal will be emitted when the new language is 265 | * different from the old one. 266 | * 267 | * \since 4.1 268 | */ 269 | void setSpellCheckingLanguage(const QString &language); 270 | 271 | /*! 272 | * Show a dialog to check the spelling. The spellCheckStatus() signal 273 | * will be emitted when the spell checking dialog is closed. 274 | */ 275 | void checkSpelling(); 276 | 277 | /*! 278 | * Opens a Sonnet::ConfigDialog for this text edit. 279 | * The spellcheck language of the config dialog is set to the current spellcheck 280 | * language of the textedit. If the user changes the language in that dialog, 281 | * the languageChanged() signal is emitted. 282 | * 283 | * \a windowIcon the icon which is used for the titlebar of the spell dialog 284 | * window. Can be empty, then no icon is set. 285 | * 286 | * \since 4.2 287 | */ 288 | void showSpellConfigDialog(const QString &windowIcon = QString()); 289 | 290 | /*! 291 | * Create replace dialogbox 292 | * \since 4.1 293 | */ 294 | void replace(); 295 | 296 | /*! 297 | * Add custom spell checker \a decorator 298 | * \since 5.11 299 | */ 300 | void addTextDecorator(Sonnet::SpellCheckDecorator *decorator); 301 | 302 | /*! 303 | * \brief clearDecorator clear the spellcheckerdecorator 304 | * \since 5.11 305 | */ 306 | void clearDecorator(); 307 | 308 | protected Q_SLOTS: 309 | void slotDoReplace(); 310 | void slotReplaceNext(); 311 | void slotDoFind(); 312 | void slotFind(); 313 | void slotFindNext(); 314 | void slotFindPrevious(); 315 | void slotReplace(); 316 | void slotSpeakText(); 317 | 318 | protected: 319 | bool event(QEvent *) override; 320 | 321 | void keyPressEvent(QKeyEvent *) override; 322 | 323 | void focusInEvent(QFocusEvent *) override; 324 | 325 | /*! 326 | * Deletes a word backwards from the current cursor position, 327 | * if available. 328 | */ 329 | virtual void deleteWordBack(); 330 | 331 | /*! 332 | * Deletes a word forwards from the current cursor position, 333 | * if available. 334 | */ 335 | virtual void deleteWordForward(); 336 | 337 | void contextMenuEvent(QContextMenuEvent *) override; 338 | 339 | protected: 340 | KTEXTWIDGETS_NO_EXPORT KTextEdit(KTextEditPrivate &dd, const QString &text, QWidget *parent); 341 | KTEXTWIDGETS_NO_EXPORT KTextEdit(KTextEditPrivate &dd, QWidget *parent); 342 | 343 | protected: 344 | std::unique_ptr const d_ptr; 345 | 346 | private: 347 | Q_DECLARE_PRIVATE(KTextEdit) 348 | }; 349 | 350 | #endif // KTEXTEDIT_H 351 | -------------------------------------------------------------------------------- /src/widgets/ktextedit_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE libraries 3 | SPDX-FileCopyrightText: 2002 Carsten Pfeiffer 4 | SPDX-FileCopyrightText: 2005 Michael Brade 5 | SPDX-FileCopyrightText: 2012 Laurent Montel 6 | 7 | SPDX-License-Identifier: LGPL-2.0-or-later 8 | */ 9 | 10 | #ifndef KTEXTEDIT_P_H 11 | #define KTEXTEDIT_P_H 12 | 13 | #include "kfind.h" 14 | #include "kfinddialog.h" 15 | #include "kreplace.h" 16 | #include "kreplacedialog.h" 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #ifdef HAVE_SPEECH 24 | #include 25 | #endif 26 | 27 | class KTextEditPrivate 28 | { 29 | Q_DECLARE_PUBLIC(KTextEdit) 30 | 31 | public: 32 | explicit KTextEditPrivate(KTextEdit *qq) 33 | : q_ptr(qq) 34 | , customPalette(false) 35 | , spellCheckingEnabled(false) 36 | , findReplaceEnabled(true) 37 | , showTabAction(true) 38 | , showAutoCorrectionButton(false) 39 | { 40 | // Check the default sonnet settings to see if spellchecking should be enabled. 41 | QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); 42 | spellCheckingEnabled = settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool(); 43 | } 44 | 45 | virtual ~KTextEditPrivate() 46 | { 47 | delete decorator; 48 | delete findDlg; 49 | delete find; 50 | delete replace; 51 | delete repDlg; 52 | delete speller; 53 | #ifdef HAVE_SPEECH 54 | delete textToSpeech; 55 | #endif 56 | } 57 | 58 | /*! 59 | * Checks whether we should/should not consume a key used as a shortcut. 60 | * This makes it possible to handle shortcuts in the focused widget before any 61 | * window-global QAction is triggered. 62 | */ 63 | bool overrideShortcut(const QKeyEvent *e); 64 | /*! 65 | * Actually handle a shortcut event. 66 | */ 67 | bool handleShortcut(const QKeyEvent *e); 68 | 69 | void spellCheckerMisspelling(const QString &text, int pos); 70 | void spellCheckerCorrected(const QString &, int, const QString &); 71 | void spellCheckerAutoCorrect(const QString &, const QString &); 72 | void spellCheckerCanceled(); 73 | void spellCheckerFinished(); 74 | void toggleAutoSpellCheck(); 75 | 76 | void slotFindHighlight(const QString &text, int matchingIndex, int matchingLength); 77 | void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength); 78 | 79 | /*! 80 | * Similar to QTextEdit::clear(), only that it is possible to undo this 81 | * action. 82 | */ 83 | void undoableClear(); 84 | 85 | void slotAllowTab(); 86 | void menuActivated(QAction *action); 87 | 88 | void init(); 89 | 90 | void checkSpelling(bool force); 91 | 92 | KTextEdit *const q_ptr; 93 | QAction *autoSpellCheckAction; 94 | QAction *allowTab; 95 | QAction *spellCheckAction; 96 | QMenu *languagesMenu = nullptr; 97 | bool customPalette : 1; 98 | 99 | bool spellCheckingEnabled : 1; 100 | bool findReplaceEnabled : 1; 101 | bool showTabAction : 1; 102 | bool showAutoCorrectionButton : 1; 103 | QTextDocumentFragment originalDoc; 104 | QString spellCheckingLanguage; 105 | Sonnet::SpellCheckDecorator *decorator = nullptr; 106 | Sonnet::Speller *speller = nullptr; 107 | KFindDialog *findDlg = nullptr; 108 | KFind *find = nullptr; 109 | KReplaceDialog *repDlg = nullptr; 110 | KReplace *replace = nullptr; 111 | #ifdef HAVE_SPEECH 112 | QTextToSpeech *textToSpeech = nullptr; 113 | #endif 114 | 115 | int findIndex = 0; 116 | int repIndex = 0; 117 | int lastReplacedPosition = -1; 118 | }; 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /src/widgets/nestedlisthelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Nested list helper 3 | SPDX-FileCopyrightText: 2008 Stephen Kelly 4 | 5 | SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #include "nestedlisthelper_p.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "ktextedit.h" 16 | 17 | NestedListHelper::NestedListHelper(QTextEdit *te) 18 | : textEdit(te) 19 | { 20 | } 21 | 22 | NestedListHelper::~NestedListHelper() 23 | { 24 | } 25 | 26 | bool NestedListHelper::handleKeyPressEvent(QKeyEvent *event) 27 | { 28 | QTextCursor cursor = textEdit->textCursor(); 29 | if (!cursor.currentList()) { 30 | return false; 31 | } 32 | 33 | if (event->key() == Qt::Key_Backspace && !cursor.hasSelection() && cursor.atBlockStart() && canDedent()) { 34 | changeIndent(-1); 35 | return true; 36 | } 37 | 38 | if (event->key() == Qt::Key_Return && !cursor.hasSelection() && cursor.block().text().isEmpty() && canDedent()) { 39 | changeIndent(-1); 40 | return true; 41 | } 42 | 43 | if (event->key() == Qt::Key_Tab && (cursor.atBlockStart() || cursor.hasSelection()) && canIndent()) { 44 | changeIndent(+1); 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | bool NestedListHelper::canIndent() const 52 | { 53 | const QTextCursor cursor = topOfSelection(); 54 | const QTextBlock block = cursor.block(); 55 | if (!block.isValid()) { 56 | return false; 57 | } 58 | if (!block.textList()) { 59 | return true; 60 | } 61 | const QTextBlock prevBlock = block.previous(); 62 | if (!prevBlock.textList()) { 63 | return false; 64 | } 65 | return block.textList()->format().indent() <= prevBlock.textList()->format().indent(); 66 | } 67 | 68 | bool NestedListHelper::canDedent() const 69 | { 70 | const QTextCursor cursor = bottomOfSelection(); 71 | const QTextBlock block = cursor.block(); 72 | if (!block.isValid()) { 73 | return false; 74 | } 75 | if (!block.textList() || block.textList()->format().indent() <= 0) { 76 | return false; 77 | } 78 | const QTextBlock nextBlock = block.next(); 79 | if (!nextBlock.textList()) { 80 | return true; 81 | } 82 | return block.textList()->format().indent() >= nextBlock.textList()->format().indent(); 83 | } 84 | 85 | bool NestedListHelper::handleAfterDropEvent(QDropEvent *dropEvent) 86 | { 87 | Q_UNUSED(dropEvent); 88 | QTextCursor cursor = topOfSelection(); 89 | 90 | QTextBlock droppedBlock = cursor.block(); 91 | int firstDroppedItemIndent = droppedBlock.textList()->format().indent(); 92 | 93 | int minimumIndent = droppedBlock.previous().textList()->format().indent(); 94 | 95 | if (firstDroppedItemIndent < minimumIndent) { 96 | cursor = QTextCursor(droppedBlock); 97 | QTextListFormat fmt = droppedBlock.textList()->format(); 98 | fmt.setIndent(minimumIndent); 99 | QTextList *list = cursor.createList(fmt); 100 | 101 | int endOfDrop = bottomOfSelection().position(); 102 | while (droppedBlock.next().position() < endOfDrop) { 103 | droppedBlock = droppedBlock.next(); 104 | if (droppedBlock.textList()->format().indent() != firstDroppedItemIndent) { 105 | // new list? 106 | } 107 | list->add(droppedBlock); 108 | } 109 | // list.add( droppedBlock ); 110 | } 111 | 112 | return true; 113 | } 114 | 115 | void NestedListHelper::processList(QTextList *list) 116 | { 117 | QTextBlock block = list->item(0); 118 | int thisListIndent = list->format().indent(); 119 | 120 | QTextCursor cursor = QTextCursor(block); 121 | list = cursor.createList(list->format()); 122 | bool processingSubList = false; 123 | while (block.next().textList() != nullptr) { 124 | block = block.next(); 125 | 126 | QTextList *nextList = block.textList(); 127 | int nextItemIndent = nextList->format().indent(); 128 | if (nextItemIndent < thisListIndent) { 129 | return; 130 | } else if (nextItemIndent > thisListIndent) { 131 | if (processingSubList) { 132 | continue; 133 | } 134 | processingSubList = true; 135 | processList(nextList); 136 | } else { 137 | processingSubList = false; 138 | list->add(block); 139 | } 140 | } 141 | // delete nextList; 142 | // nextList = 0; 143 | } 144 | 145 | void NestedListHelper::reformatList(QTextBlock block) 146 | { 147 | if (block.textList()) { 148 | int minimumIndent = block.textList()->format().indent(); 149 | 150 | // Start at the top of the list 151 | while (block.previous().textList() != nullptr) { 152 | if (block.previous().textList()->format().indent() < minimumIndent) { 153 | break; 154 | } 155 | block = block.previous(); 156 | } 157 | 158 | processList(block.textList()); 159 | } 160 | } 161 | 162 | void NestedListHelper::reformatList() 163 | { 164 | QTextCursor cursor = textEdit->textCursor(); 165 | reformatList(cursor.block()); 166 | } 167 | 168 | QTextCursor NestedListHelper::topOfSelection() const 169 | { 170 | QTextCursor cursor = textEdit->textCursor(); 171 | 172 | if (cursor.hasSelection()) { 173 | cursor.setPosition(qMin(cursor.position(), cursor.anchor())); 174 | } 175 | return cursor; 176 | } 177 | 178 | QTextCursor NestedListHelper::bottomOfSelection() const 179 | { 180 | QTextCursor cursor = textEdit->textCursor(); 181 | 182 | if (cursor.hasSelection()) { 183 | cursor.setPosition(qMax(cursor.position(), cursor.anchor())); 184 | } 185 | return cursor; 186 | } 187 | 188 | void NestedListHelper::changeIndent(int delta) 189 | { 190 | QTextCursor cursor = textEdit->textCursor(); 191 | cursor.beginEditBlock(); 192 | 193 | const int top = qMin(cursor.position(), cursor.anchor()); 194 | const int bottom = qMax(cursor.position(), cursor.anchor()); 195 | 196 | // A reformatList should be called on the block inside selection 197 | // with the lowest indentation level 198 | int minIndentPosition; 199 | int minIndent = -1; 200 | 201 | // Changing indentation of all blocks between top and bottom 202 | cursor.setPosition(top); 203 | do { 204 | QTextList *list = cursor.currentList(); 205 | // Setting up listFormat 206 | QTextListFormat listFmt; 207 | if (!list) { 208 | if (delta > 0) { 209 | // No list, we're increasing indentation -> create a new one 210 | listFmt.setStyle(QTextListFormat::ListDisc); 211 | listFmt.setIndent(delta); 212 | } 213 | // else do nothing 214 | } else { 215 | const int newIndent = list->format().indent() + delta; 216 | if (newIndent > 0) { 217 | listFmt = list->format(); 218 | listFmt.setIndent(newIndent); 219 | } else { 220 | listFmt.setIndent(0); 221 | } 222 | } 223 | 224 | if (listFmt.indent() > 0) { 225 | // This block belongs to a list: here we create a new one 226 | // for each block, and then let reformatList() sort it out 227 | cursor.createList(listFmt); 228 | if (minIndent == -1 || minIndent > listFmt.indent()) { 229 | minIndent = listFmt.indent(); 230 | minIndentPosition = cursor.block().position(); 231 | } 232 | } else { 233 | // If the block belonged to a list, remove it from there 234 | if (list) { 235 | list->remove(cursor.block()); 236 | } 237 | // The removal does not change the indentation, we need to do it explicitly 238 | QTextBlockFormat blkFmt; 239 | blkFmt.setIndent(0); 240 | cursor.mergeBlockFormat(blkFmt); 241 | } 242 | if (!cursor.block().next().isValid()) { 243 | break; 244 | } 245 | cursor.movePosition(QTextCursor::NextBlock); 246 | } while (cursor.position() < bottom); 247 | // Reformatting the whole list 248 | if (minIndent != -1) { 249 | cursor.setPosition(minIndentPosition); 250 | reformatList(cursor.block()); 251 | } 252 | cursor.setPosition(top); 253 | reformatList(cursor.block()); 254 | cursor.endEditBlock(); 255 | } 256 | 257 | void NestedListHelper::handleOnBulletType(int styleIndex) 258 | { 259 | QTextCursor cursor = textEdit->textCursor(); 260 | if (styleIndex != 0) { 261 | QTextListFormat::Style style = static_cast(styleIndex); 262 | QTextList *currentList = cursor.currentList(); 263 | QTextListFormat listFmt; 264 | 265 | cursor.beginEditBlock(); 266 | 267 | if (currentList) { 268 | listFmt = currentList->format(); 269 | listFmt.setStyle(style); 270 | currentList->setFormat(listFmt); 271 | } else { 272 | listFmt.setStyle(style); 273 | cursor.createList(listFmt); 274 | } 275 | 276 | cursor.endEditBlock(); 277 | } else { 278 | QTextBlockFormat bfmt; 279 | bfmt.setObjectIndex(-1); 280 | cursor.setBlockFormat(bfmt); 281 | } 282 | 283 | reformatList(); 284 | } 285 | -------------------------------------------------------------------------------- /src/widgets/nestedlisthelper_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | Nested list helper 3 | SPDX-FileCopyrightText: 2008 Stephen Kelly 4 | 5 | SPDX-License-Identifier: LGPL-2.1-or-later 6 | */ 7 | 8 | #ifndef NESTEDLISTHELPER_H 9 | #define NESTEDLISTHELPER_H 10 | 11 | class QTextEdit; 12 | 13 | class QKeyEvent; 14 | class QDropEvent; 15 | class QTextCursor; 16 | class QTextList; 17 | class QTextBlock; 18 | 19 | /*! 20 | * \brief Helper class for automatic handling of nested lists in a text edit 21 | * 22 | * \since 4.1 23 | * \internal 24 | */ 25 | class NestedListHelper 26 | { 27 | public: 28 | /*! 29 | * Create a helper 30 | * 31 | * \a The text edit object to handle lists in. 32 | */ 33 | explicit NestedListHelper(QTextEdit *te); 34 | 35 | /*! 36 | * Destructor 37 | */ 38 | ~NestedListHelper(); 39 | 40 | /*! 41 | * 42 | * Handles a key press before it is processed by the text edit widget. 43 | * 44 | * This includes: 45 | * 1. Backspace at the beginning of a line decreases nesting level 46 | * 2. Return at the empty list element decreases nesting level 47 | * 3. Tab at the beginning of a line OR with a multi-line selection 48 | * increases nesting level 49 | * 50 | * \a event The event to be handled 51 | * 52 | * Returns Whether the event was completely handled by this method. 53 | */ 54 | bool handleKeyPressEvent(QKeyEvent *event); 55 | 56 | bool handleAfterDropEvent(QDropEvent *event); 57 | 58 | /*! 59 | * Changes the indent (nesting level) on a current list item or selection 60 | * by the value \a delta (typically, +1 or -1) 61 | */ 62 | void changeIndent(int delta); 63 | 64 | /*! 65 | * Changes the style of the current list or creates a new list with 66 | * the specified style. 67 | * 68 | * \a styleIndex The QTextListStyle of the list. 69 | */ 70 | void handleOnBulletType(int styleIndex); 71 | 72 | /*! 73 | * \brief Check whether the current item in the list may be indented. 74 | * 75 | * An list item must have an item above it on the same or greater level 76 | * if it can be indented. 77 | * 78 | * Also, a block which is currently part of a list can be indented. 79 | * 80 | * \sa canDedent 81 | * 82 | * Returns Whether the item can be indented. 83 | */ 84 | bool canIndent() const; 85 | 86 | /*! 87 | * \brief Check whether the current item in the list may be dedented. 88 | * 89 | * An item may be dedented if it is part of a list. 90 | * The next item must be at the same or lesser level. 91 | * 92 | * \sa canIndent 93 | * 94 | * Returns Whether the item can be dedented. 95 | */ 96 | bool canDedent() const; 97 | 98 | private: 99 | QTextCursor topOfSelection() const; 100 | QTextCursor bottomOfSelection() const; 101 | void processList(QTextList *list); 102 | void reformatList(QTextBlock block); 103 | void reformatList(); 104 | 105 | QTextEdit *const textEdit; 106 | }; 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(ECMMarkAsTest) 2 | include(ECMMarkNonGuiExecutable) 3 | 4 | find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test) 5 | 6 | macro(ktextwidgets_executable_tests) 7 | foreach(_testname ${ARGN}) 8 | add_executable(${_testname} ${_testname}.cpp) 9 | target_link_libraries(${_testname} Qt6::Test KF6::TextWidgets) 10 | ecm_mark_nongui_executable(${_testname}) 11 | ecm_mark_as_test(${_testname}) 12 | endforeach() 13 | endmacro() 14 | 15 | ktextwidgets_executable_tests( 16 | ktextedittest 17 | ) 18 | -------------------------------------------------------------------------------- /tests/ktextedittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the KDE libraries 3 | SPDX-FileCopyrightText: 2002 Carsten Pfeiffer 4 | 5 | SPDX-License-Identifier: LGPL-2.0-or-later 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int main(int argc, char **argv) 16 | { 17 | QApplication::setApplicationName(QStringLiteral("ktextedittest")); 18 | QApplication app(argc, argv); 19 | KTextEdit *edit = new KTextEdit(); 20 | 21 | // QAction* action = new QAction("Select All", edit); 22 | // action->setShortcut( Qt::CTRL | Qt::Key_Underscore ); 23 | // edit->addAction(action); 24 | // QObject::connect(action, SIGNAL(triggered()), edit, SLOT(selectAll())); 25 | 26 | QFile file(QFINDTESTDATA(QLatin1String("ktextedittest.cpp"))); 27 | if (file.open(QIODevice::ReadOnly)) { 28 | edit->setPlainText(QLatin1String(file.readAll())); 29 | file.close(); 30 | } 31 | 32 | edit->resize(600, 600); 33 | edit->show(); 34 | return app.exec(); 35 | } 36 | --------------------------------------------------------------------------------